-
Notifications
You must be signed in to change notification settings - Fork 8
/
k8s.ts
503 lines (470 loc) · 21.5 KB
/
k8s.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
import { Contract } from "@threefold/tfchain_client";
import { GridClientErrors, ValidationError } from "@threefold/types";
import { Addr } from "netaddr";
import { GridClientConfig } from "../config";
import { ZmachineData } from "../helpers";
import { events } from "../helpers/events";
import { expose } from "../helpers/expose";
import { validateInput } from "../helpers/validator";
import { KubernetesHL } from "../high_level/kubernetes";
import { DeploymentResultContracts, TwinDeployment } from "../high_level/models";
import { Network } from "../primitives/network";
import { Deployment } from "../zos";
import { Workload, WorkloadTypes } from "../zos/workload";
import { BaseModule } from "./base";
import { AddWorkerModel, DeleteWorkerModel, K8SDeleteModel, K8SGetModel, K8SModel } from "./models";
import { checkBalance } from "./utils";
class K8sModule extends BaseModule {
moduleName = "kubernetes";
workloadTypes = [
WorkloadTypes.zmachine,
WorkloadTypes.zmount,
WorkloadTypes.volume,
WorkloadTypes.qsfs,
WorkloadTypes.ip,
WorkloadTypes.ipv4,
WorkloadTypes.zlogs,
]; // TODO: remove deprecated
kubernetes: KubernetesHL;
/**
* Class representing a Kubernetes Module.
* Extends the BaseModule class.
*
* This class provides methods for managing Kubernetes deployments, including creating, updating, listing, and deleting deployments.
* @class K8sModule
* @param {GridClientConfig} config - The configuration object for initializing the client.
*/
constructor(public config: GridClientConfig) {
super(config);
this.kubernetes = new KubernetesHL(config);
}
/**
* Get the master workloads for a specific deployment.
*
* This method iterates through the deployments and retrieves the workloads that are of type `zmachine` and have an empty `K3S_URL` environment variable.
* It assigns the `contract ID` and `node ID` to each workload and adds it to the list of master workloads.
*
* @param {string} deploymentName - The name of the deployment to get master workloads for.
* @param {(Deployment | TwinDeployment)[]} deployments - The list of deployments to search for master workloads.
* @returns {Promise<Workload[]>} - A list of master workloads that match the criteria.
*/
async _getMastersWorkload(deploymentName: string, deployments: (Deployment | TwinDeployment)[]): Promise<Workload[]> {
const workloads: Workload[] = [];
for (const deployment of deployments) {
const d = deployment instanceof TwinDeployment ? deployment.deployment : deployment;
for (const workload of d.workloads) {
if (workload.type === WorkloadTypes.zmachine && workload.data["env"]["K3S_URL"] === "") {
workload["contractId"] = d.contract_id;
workload["nodeId"] = await this._getNodeIdFromContractId(deploymentName, d.contract_id);
workloads.push(workload);
}
}
}
return workloads;
}
/**
* Get the worker workloads for a specific deployment.
*
* This method iterates through the deployments and retrieves the workloads that are of type `zmachine` and have a non-empty `K3S_URL` environment variable.
* It assigns the `contract ID` and `node ID` to each workload and adds it to the list of worker workloads.
*
* @param {string} deploymentName - The name of the deployment to get worker workloads for.
* @param {(Deployment | TwinDeployment)[]} deployments - The list of deployments to search for worker workloads.
* @returns {Promise<Workload[]>} - A list of worker workloads that match the criteria.
*/
async _getWorkersWorkload(deploymentName: string, deployments: (Deployment | TwinDeployment)[]): Promise<Workload[]> {
const workloads: Workload[] = [];
for (const deployment of deployments) {
const d = deployment instanceof TwinDeployment ? deployment.deployment : deployment;
for (const workload of d.workloads) {
if (workload.type === WorkloadTypes.zmachine && workload.data["env"]["K3S_URL"] !== "") {
workload["contractId"] = d.contract_id;
workload["nodeId"] = await this._getNodeIdFromContractId(deploymentName, d.contract_id);
workloads.push(workload);
}
}
}
return workloads;
}
/**
* Get the `IP addresses` of master workloads for a specific deployment.
*
* This method retrieves the `IP addresses` of master workloads associated with the specified deployment.
* It first fetches the master workloads using the `_getMastersWorkload` method and then extracts the `IP addresses` from the network interfaces data.
*
* @param {string} deploymentName - The name of the deployment to get master IPs for.
* @param {(Deployment | TwinDeployment)[]} deployments - The list of deployments to search for master workloads.
* @returns {Promise<string[]>} A list of `IP addresses` of master workloads.
*/
async _getMastersIp(deploymentName: string, deployments: (Deployment | TwinDeployment)[]): Promise<string[]> {
const ips: string[] = [];
const workloads = await this._getMastersWorkload(deploymentName, deployments);
for (const workload of workloads) {
ips.push(workload.data["network"]["interfaces"][0]["ip"]);
}
return ips;
}
/**
* Create a deployment for `Kubernetes`.
*
* This method creates a deployment for `Kubernetes` based on the provided options.
*
* It adds master nodes and worker nodes to the deployment, along with network configuration.
*
* @param {K8SModel} options - The options for creating the `Kubernetes` deployment.
* @param {string[]} masterIps - The IP addresses of the master nodes.
* @returns {Promise<[TwinDeployment[], Network, string]>} A tuple containing the created deployments, network configuration, and Wireguard configuration.
*/
async _createDeployment(options: K8SModel, masterIps: string[] = []): Promise<[TwinDeployment[], Network, string]> {
const network = new Network(options.network.name, options.network.ip_range, this.config);
await network.load();
let deployments: TwinDeployment[] = [];
let wireguardConfig = "";
const contractMetadata = JSON.stringify({
version: 3,
type: "kubernetes",
name: options.name,
projectName: this.config.projectName || `kubernetes/${options.name}`,
});
const masters_names: string[] = [];
const workers_names: string[] = [];
for (const master of options.masters) {
if (masters_names.includes(master.name))
throw new ValidationError(`Another master with the same name ${master.name} already exists.`);
masters_names.push(master.name);
const [twinDeployments, wgConfig] = await this.kubernetes.add_master(
master.name,
master.node_id,
options.secret,
master.cpu,
master.memory,
master.rootfs_size,
master.disk_size,
master.public_ip,
master.public_ip6,
master.planetary,
master.mycelium,
master.myceliumSeed!,
network,
options.network.myceliumSeeds!,
options.ssh_key,
contractMetadata,
options.metadata,
options.description,
master.qsfs_disks,
this.config.projectName,
options.network.addAccess,
options.network.accessNodeId,
master.ip,
master.corex,
master.solutionProviderId!,
master.zlogsOutput,
master.gpus,
);
deployments = deployments.concat(twinDeployments);
if (wgConfig) {
wireguardConfig = wgConfig;
}
}
const masterWorkloads = await this._getMastersWorkload(options.name, deployments);
if (masterWorkloads.length === 0) {
throw new GridClientErrors.Workloads.WorkloadUpdateError("Couldn't get master node.");
}
const masterWorkload = masterWorkloads[masterWorkloads.length - 1];
const masterFlist = masterWorkload.data["flist"];
if (masterIps.length === 0) {
masterIps = await this._getMastersIp(options.name, deployments);
if (masterIps.length === 0) {
throw new GridClientErrors.Workloads.WorkloadCreateError("Couldn't get master ip");
}
}
for (const worker of options.workers!) {
if (workers_names.includes(worker.name))
throw new ValidationError(`Another worker with the same name ${worker.name} already exists.`);
workers_names.push(worker.name);
const [twinDeployments] = await this.kubernetes.add_worker(
worker.name,
worker.node_id,
options.secret,
masterIps[masterIps.length - 1],
worker.cpu,
worker.memory,
worker.rootfs_size,
worker.disk_size,
worker.public_ip,
worker.public_ip6,
worker.planetary,
worker.mycelium,
worker.myceliumSeed!,
network,
options.network.myceliumSeeds!,
options.ssh_key,
contractMetadata,
options.metadata,
options.description,
worker.qsfs_disks,
this.config.projectName,
options.network.addAccess,
options.network.accessNodeId,
worker.ip,
worker.corex,
worker.solutionProviderId!,
worker.zlogsOutput,
worker.gpus,
masterFlist,
);
deployments = deployments.concat(twinDeployments);
}
return [deployments, network, wireguardConfig];
}
/**
* Deploy a `Kubernetes cluster` based on the provided options.
*
* This method deploys a `Kubernetes cluster` by creating `master` and `worker` nodes, along with `network configuration`.
* It checks if multiple masters are specified and if a deployment with the same name already exists.
* It emits a log event to indicate the start of cluster creation.
*
* @param {K8SModel} options - The options for deploying the `Kubernetes cluster`.
* @returns {Promise<{ contracts: string[], wireguard_config: string }>} A promise that resolves to an object containing the contracts created and the Wireguard configuration.
* @decorators
* - `@expose`: Exposes the method for external use.
* - `@validateInput`: Validates the input options.
* - `@checkBalance`: Checks the balance to ensure there are enough funds available.
*/
@expose
@validateInput
@checkBalance
async deploy(options: K8SModel): Promise<{ contracts: DeploymentResultContracts; wireguard_config: string }> {
if (options.masters.length > 1) {
throw new ValidationError("Multiple masters are not supported");
}
if (await this.exists(options.name)) {
throw new ValidationError(`Another k8s deployment with the same name ${options.name} already exists.`);
}
events.emit("logs", `Start creating the cluster with name ${options.name}`);
const [deployments, , wireguardConfig] = await this._createDeployment(options);
const contracts = await this.twinDeploymentHandler.handle(deployments);
await this.save(options.name, contracts);
return { contracts: contracts, wireguard_config: wireguardConfig };
}
/**
* List all Kubernetes deployments.
*
* This method retrieves a list of all Kubernetes deployments.
*
* @returns {Promise<string[]>} A promise that resolves to a list of all Kubernetes deployments.
* @decorators
* - `@expose`: Exposes the method for external use.
*/
@expose
async list(): Promise<string[]> {
return await this._list();
}
/**
* Retrieve information about the master and worker workloads of a specific deployment.
*
* This method fetches the master and worker workloads associated with the specified deployment.
* It first retrieves all deployments using the `_get` method, then gets the master workloads using `_getMastersWorkload`,
* and the worker workloads using `_getWorkersWorkload`. Finally, it fetches detailed information about each workload using `_getZmachineData`.
*
* @param {string} deploymentName - The name of the deployment to retrieve information for.
* @returns {Promise<{ masters: ZmachineData[], workers: ZmachineData[] }>} A promise that resolves to an object containing information about the master and worker workloads.
*/
async getObj(deploymentName: string): Promise<{ masters: ZmachineData[]; workers: ZmachineData[] }> {
const k8s: { masters: ZmachineData[]; workers: ZmachineData[] } = { masters: [], workers: [] };
const deployments = await this._get(deploymentName);
const masters = await this._getMastersWorkload(deploymentName, deployments);
const workers = await this._getWorkersWorkload(deploymentName, deployments);
for (const master of masters) {
const masterMachine = await this._getZmachineData(deploymentName, deployments, master);
k8s.masters.push(masterMachine);
}
for (const worker of workers) {
const workerMachine = await this._getZmachineData(deploymentName, deployments, worker);
k8s.workers.push(workerMachine);
}
return k8s;
}
/**
* Retrieve a specific Kubernetes deployment.
*
* This method fetches detailed information about a specific Kubernetes deployment based on the provided deployment name.
* It retrieves all deployments using the `_get` method and then filters out the deployment matching the specified name.
*
* @param {K8SGetModel} options - The options containing the name of the deployment to retrieve.
* @returns {Promise<Deployment[]>} A promise that resolves to the deployment matching the specified name.
* @decorators
* - `@expose`: Exposes the method for external use.
* - `@validateInput`: Validates the input options.
*/
@expose
@validateInput
async get(options: K8SGetModel): Promise<Deployment[]> {
return await this._get(options.name);
}
/**
* Delete a Kubernetes deployment.
*
* This method deletes a Kubernetes deployment with the specified name.
* It emits a log event to indicate the start of the deletion process.
*
* @param {K8SDeleteModel} options - The options containing the name of the deployment to delete.
* @returns {Promise<{created: Contract[];deleted: Contract[];updated: Contract[];}>} A promise that resolves once the deployment is successfully deleted.
* @decorators
* - `@expose`: Exposes the method for external use.
* - `@validateInput`: Validates the input options.
* - `@checkBalance`: Checks the balance to ensure there are enough funds available.
*/
@expose
@validateInput
@checkBalance
async delete(options: K8SDeleteModel): Promise<DeploymentResultContracts> {
events.emit("logs", `Start deleting the cluster with name ${options.name}`);
return await this._delete(options.name);
}
/**
* Update a `Kubernetes deployment` based on the provided options.
*
* This method updates a `Kubernetes deployment` by checking if the deployment exists,
* ensuring only one master is specified, and retrieving the necessary information about the existing deployment.
* It then validates the network name and IP range, creates new deployments based on the updated options, and updates the deployment using the `_update` method.
*
* @param {K8SModel} options - The options for updating the `Kubernetes deployment`.
* @returns {Promise<{ contracts: DeploymentResultContracts }>} A promise that resolves once the deployment is successfully updated.
* @decorators
* - `@expose`: Exposes the method for external use.
* - `@validateInput`: Validates the input options.
* - `@checkBalance`: Checks the balance to ensure there are enough funds available.
*/
@expose
@validateInput
@checkBalance
async update(options: K8SModel): Promise<{ contracts: DeploymentResultContracts }> {
if (!(await this.exists(options.name))) {
throw new ValidationError(`There is no k8s deployment with the name: ${options.name}.`);
}
if (options.masters.length > 1) {
throw new ValidationError("Multiple masters are not supported.");
}
const oldDeployments = await this._get(options.name);
const masterIps = await this._getMastersIp(options.name, oldDeployments);
if (masterIps.length === 0) {
throw new GridClientErrors.Workloads.WorkloadUpdateError("Couldn't get master ip.");
}
const masterWorkloads = await this._getMastersWorkload(options.name, oldDeployments);
if (masterWorkloads.length === 0) {
throw new GridClientErrors.Workloads.WorkloadUpdateError("Couldn't get master node.");
}
const masterWorkload = masterWorkloads[0];
const networkName = masterWorkload.data["network"].interfaces[0].network;
const networkIpRange = Addr(masterWorkload.data["network"].interfaces[0].ip).mask(16).toString();
if (networkName !== options.network.name && networkIpRange !== options.network.ip_range) {
throw new GridClientErrors.Workloads.WorkloadUpdateError("Network name and ip_range can't be changed.");
}
//TODO: check that the master nodes are not changed
const [twinDeployments, network] = await this._createDeployment(options, masterIps);
return await this._update(this.kubernetes, options.name, oldDeployments, twinDeployments, network);
}
/**
* Add a worker to a Kubernetes deployment.
*
* This method adds a worker node to the specified Kubernetes deployment based on the provided options.
*
* It checks if the deployment exists, ensures that there is no worker with the same name in the cluster,
* and retrieves the necessary information about the master node.
* It then creates a new worker node using the 'add_worker' method from the Kubernetes class and adds it to the deployment.
*
* @param {AddWorkerModel} options - The options for adding the worker node to the deployment.
* @returns {Promise<{ contracts: DeploymentResultContracts }>} A promise that resolves once the worker node is successfully added to the deployment.
* @decorators
* - `@expose`: Exposes the method for external use.
* - `@validateInput`: Validates the input options.
* - `@checkBalance`: Checks the balance to ensure there are enough funds available.
*/
@expose
@validateInput
@checkBalance
async add_worker(options: AddWorkerModel): Promise<{ contracts: DeploymentResultContracts }> {
if (!(await this.exists(options.deployment_name))) {
throw new ValidationError(`There is no k8s deployment with the name: ${options.deployment_name}.`);
}
const oldDeployments = await this._get(options.deployment_name);
if (this.workloadExists(options.name, oldDeployments))
throw new ValidationError(
`There is another worker with the same name "${options.name}" in this cluster ${options.deployment_name}.`,
);
events.emit("logs", `Start adding worker: ${options.name} to cluster: ${options.deployment_name}`);
const masterWorkloads = await this._getMastersWorkload(options.deployment_name, oldDeployments);
if (masterWorkloads.length === 0) {
throw new GridClientErrors.Workloads.WorkloadUpdateError("Couldn't get master node.");
}
const masterWorkload = masterWorkloads[masterWorkloads.length - 1];
const networkName = masterWorkload.data["network"].interfaces[0].network;
const networkIpRange = Addr(masterWorkload.data["network"].interfaces[0].ip).mask(16).toString();
const network = new Network(networkName, networkIpRange, this.config);
const masterFlist = masterWorkload.data["flist"];
await network.load();
const contractMetadata = JSON.stringify({
version: 3,
type: "kubernetes",
name: options.deployment_name,
projectName: this.config.projectName || `kubernetes/${options.deployment_name}`,
});
const [twinDeployments] = await this.kubernetes.add_worker(
options.name,
options.node_id,
masterWorkload.data["env"]["K3S_TOKEN"],
masterWorkload.data["network"]["interfaces"][0]["ip"],
options.cpu,
options.memory,
options.rootfs_size,
options.disk_size,
options.public_ip,
options.public_ip6,
options.planetary,
options.mycelium,
options.myceliumSeed!,
network,
[{ nodeId: options.node_id, seed: options.myceliumNetworkSeed! }],
masterWorkload.data["env"]["SSH_KEY"],
contractMetadata,
masterWorkload.metadata,
masterWorkload.description,
options.qsfs_disks,
this.config.projectName,
false,
0,
options.ip,
options.corex,
options.solutionProviderId!,
options.zlogsOutput,
options.gpus,
masterFlist,
);
return await this._add(options.deployment_name, options.node_id, oldDeployments, twinDeployments, network);
}
/**
* Delete a worker from a Kubernetes deployment.
*
* This method deletes a worker node from the specified Kubernetes deployment based on the provided options.
* It first checks if the deployment exists, then emits a log event indicating the start of the deletion process.
*
* @param {DeleteWorkerModel} options - The options for deleting the worker node from the deployment.
* @returns {Promise<DeploymentResultContracts>} A promise that resolves once the worker node is successfully deleted from the deployment.
* @decorators
* - `@expose`: Exposes the method for external use.
* - `@validateInput`: Validates the input options.
* - `@checkBalance`: Checks the balance to ensure there are enough funds available.
*/
@expose
@validateInput
@checkBalance
async delete_worker(options: DeleteWorkerModel): Promise<DeploymentResultContracts> {
if (!(await this.exists(options.deployment_name))) {
throw new ValidationError(`There is no k8s deployment with the name: ${options.deployment_name}.`);
}
events.emit("logs", `Start deleting worker: ${options.name} from cluster: ${options.deployment_name}`);
return await this._deleteInstance(this.kubernetes, options.deployment_name, options.name);
}
}
export { K8sModule as k8s };