diff --git a/packages/grid_client/scripts/add_worker.ts b/packages/grid_client/scripts/add_worker.ts index 450b43a549..4eaaac06c2 100644 --- a/packages/grid_client/scripts/add_worker.ts +++ b/packages/grid_client/scripts/add_worker.ts @@ -26,7 +26,8 @@ async function deleteWorker(client, worker) { } async function main() { - const grid3 = await getClient(); + const name = "testk8s"; + const grid3 = await getClient(`kubernetes/${name}`); const workerQueryOptions: FilterOptions = { cru: 2, @@ -36,7 +37,7 @@ async function main() { }; const worker: AddWorkerModel = { - deployment_name: "testk8s", + deployment_name: name, name: "worker2", node_id: +(await grid3.capacity.filterNodes(workerQueryOptions))[0].nodeId, cpu: 2, @@ -53,10 +54,10 @@ async function main() { await addWorker(grid3, worker); //Get worker information - await getWorker(grid3, worker.deployment_name); + await getWorker(grid3, name); //Uncomment the line below to delete the worker - // await deleteWorker(grid3, { name: worker.name, deployment_name: worker.deployment_name }); + // await deleteWorker(grid3, { name: worker.name, deployment_name: name }); await grid3.disconnect(); } diff --git a/packages/grid_client/scripts/caprover_leader.ts b/packages/grid_client/scripts/caprover_leader.ts index b7e645c543..1456b365ce 100644 --- a/packages/grid_client/scripts/caprover_leader.ts +++ b/packages/grid_client/scripts/caprover_leader.ts @@ -25,7 +25,8 @@ async function cancel(client, vms) { } async function main() { - const grid3 = await getClient(); + const name = "newVMS5"; + const grid3 = await getClient(`caprover/${name}`); const vmQueryOptions: FilterOptions = { cru: 4, @@ -35,7 +36,7 @@ async function main() { }; const vms: MachinesModel = { - name: "newVMS5", + name, network: { name: "wedtest", ip_range: "10.249.0.0/16", @@ -77,10 +78,10 @@ async function main() { await deploy(grid3, vms); //Get the deployment - await getDeployment(grid3, vms.name); + await getDeployment(grid3, name); //Uncomment the line below to cancel the deployment - // await cancel(grid3, { name: vms.name }); + // await cancel(grid3, { name }); await grid3.disconnect(); } diff --git a/packages/grid_client/scripts/caprover_worker.ts b/packages/grid_client/scripts/caprover_worker.ts index feb89fc058..23b53149ca 100644 --- a/packages/grid_client/scripts/caprover_worker.ts +++ b/packages/grid_client/scripts/caprover_worker.ts @@ -24,7 +24,8 @@ async function cancel(client, vms) { } async function main() { - const grid3 = await getClient(); + const name = "newVMS5"; + const grid3 = await getClient(`caprover/${name}`); const vmQueryOptions: FilterOptions = { cru: 4, @@ -34,7 +35,7 @@ async function main() { }; const vms: MachinesModel = { - name: "newVMS6", + name, network: { name: "wedtest", ip_range: "10.249.0.0/16", @@ -76,10 +77,10 @@ async function main() { await deploy(grid3, vms); //Get the deployment - await getDeployment(grid3, vms.name); + await getDeployment(grid3, name); //Uncomment the line below to cancel the deployment - // await cancel(grid3, { name: vms.name }); + // await cancel(grid3, { name }); await grid3.disconnect(); } diff --git a/packages/grid_client/scripts/client_loader.ts b/packages/grid_client/scripts/client_loader.ts index 3df59daf83..ca9a263951 100644 --- a/packages/grid_client/scripts/client_loader.ts +++ b/packages/grid_client/scripts/client_loader.ts @@ -23,11 +23,12 @@ if (network === undefined || mnemonic === undefined || ssh_key === undefined) { }; } -async function getClient(): Promise<GridClient> { +async function getClient(projectName = ""): Promise<GridClient> { const gridClient = new GridClient({ network: config.network, mnemonic: config.mnemonic, storeSecret: config.storeSecret, + projectName, }); await gridClient.connect(); return gridClient; diff --git a/packages/grid_client/scripts/dynamic_single_vm.ts b/packages/grid_client/scripts/dynamic_single_vm.ts index de548114f8..b9d0afb2ea 100644 --- a/packages/grid_client/scripts/dynamic_single_vm.ts +++ b/packages/grid_client/scripts/dynamic_single_vm.ts @@ -24,7 +24,8 @@ async function cancel(client, vms) { } async function main() { - const grid3 = await getClient(); + const name = "dynamicVMS"; + const grid3 = await getClient(`vm/${name}`); const vmQueryOptions: FilterOptions = { cru: 1, @@ -35,7 +36,7 @@ async function main() { }; const vms: MachinesModel = { - name: "dynamicVMS", + name, network: { name: "dynamictest", ip_range: "10.249.0.0/16", @@ -73,10 +74,10 @@ async function main() { await deploy(grid3, vms); //Get the deployment - await getDeployment(grid3, vms.name); + await getDeployment(grid3, name); //Uncomment the line below to cancel the deployment - // await cancel(grid3, { name: vms.name }); + // await cancel(grid3, { name }); await grid3.disconnect(); } diff --git a/packages/grid_client/scripts/kubernetes.ts b/packages/grid_client/scripts/kubernetes.ts index 91700fc2e1..3e09595079 100644 --- a/packages/grid_client/scripts/kubernetes.ts +++ b/packages/grid_client/scripts/kubernetes.ts @@ -24,7 +24,8 @@ async function cancel(client, k8s) { } async function main() { - const grid3 = await getClient(); + const name = "testk8s"; + const grid3 = await getClient(`kubernetes/${name}`); const masterQueryOptions: FilterOptions = { cru: 2, @@ -43,7 +44,7 @@ async function main() { }; const k: K8SModel = { - name: "testk8s", + name, secret: "secret", network: { name: "monNetwork", @@ -87,10 +88,10 @@ async function main() { await deploy(grid3, k); //Get the deployment - await getDeployment(grid3, k.name); + await getDeployment(grid3, name); //Uncomment the line below to cancel the deployment - // await cancel(grid3, { name: k.name }); + // await cancel(grid3, { name }); await grid3.disconnect(); } diff --git a/packages/grid_client/scripts/kubernetes_mycelium.ts b/packages/grid_client/scripts/kubernetes_mycelium.ts index 8b285e6e14..e927979964 100644 --- a/packages/grid_client/scripts/kubernetes_mycelium.ts +++ b/packages/grid_client/scripts/kubernetes_mycelium.ts @@ -24,10 +24,11 @@ async function cancel(client, k8s) { } async function main() { - const grid3 = await getClient(); + const name = "testk8sMy"; + const grid3 = await getClient(`kubernetes/${name}`); const k: K8SModel = { - name: "testk8sMy", + name, secret: "secret", network: { name: "monNetwork", @@ -100,10 +101,10 @@ async function main() { await deploy(grid3, k); //Get the deployment - await getDeployment(grid3, k.name); + await getDeployment(grid3, name); //Uncomment the line below to cancel the deployment - // await cancel(grid3, { name: k.name }); + // await cancel(grid3, { name }); await grid3.disconnect(); } diff --git a/packages/grid_client/scripts/kubernetes_with_qsfs.ts b/packages/grid_client/scripts/kubernetes_with_qsfs.ts index 0fcbb06633..e55b24ce4d 100644 --- a/packages/grid_client/scripts/kubernetes_with_qsfs.ts +++ b/packages/grid_client/scripts/kubernetes_with_qsfs.ts @@ -38,7 +38,8 @@ async function deleteQsfs(client, qsfs) { } async function main() { - const grid3 = await getClient(); + const name = "testk8sqsfs"; + const grid3 = await getClient(`kubernetes/${name}`); const qsfs_name = "testQsfsK8sq1"; @@ -64,7 +65,7 @@ async function main() { farmId: 1, }; - const qsfsNodes = []; + const qsfsNodes: number[] = []; const allNodes = await grid3.capacity.filterNodes(qsfsQueryOptions); if (allNodes.length >= 2) { @@ -85,7 +86,7 @@ async function main() { }; const k: K8SModel = { - name: "testk8sqsfs", + name, secret: "secret", network: { name: "k8sqsfsNetwork", @@ -143,7 +144,7 @@ async function main() { await deploy(grid3, k); //Get the deployment - await getDeployment(grid3, k.name); + await getDeployment(grid3, name); // //Uncomment the line below to cancel the deployment // await cancel(grid3, { name: k.name }); diff --git a/packages/grid_client/scripts/multiple_vms.ts b/packages/grid_client/scripts/multiple_vms.ts index 8ea6f7d647..9ba3a443e6 100644 --- a/packages/grid_client/scripts/multiple_vms.ts +++ b/packages/grid_client/scripts/multiple_vms.ts @@ -24,7 +24,8 @@ async function cancel(client, vms) { } async function main() { - const grid3 = await getClient(); + const name = "monVMS2"; + const grid3 = await getClient(`vm/${name}`); const vmQueryOptions: FilterOptions = { cru: 1, @@ -35,7 +36,7 @@ async function main() { }; const vms: MachinesModel = { - name: "monVMS2", + name, network: { name: "monNetwork", ip_range: "10.238.0.0/16", @@ -96,10 +97,10 @@ async function main() { await deploy(grid3, vms); //Get the deployment - await getDeployment(grid3, vms.name); + await getDeployment(grid3, name); // //Uncomment the line below to cancel the deployment - // await cancel(grid3, { name: vms.name }); + // await cancel(grid3, { name }); await grid3.disconnect(); } diff --git a/packages/grid_client/scripts/single_vm.ts b/packages/grid_client/scripts/single_vm.ts index 6364c752e7..a64ac5a9e4 100644 --- a/packages/grid_client/scripts/single_vm.ts +++ b/packages/grid_client/scripts/single_vm.ts @@ -24,10 +24,11 @@ async function cancel(client, vms) { } async function main() { - const grid3 = await getClient(); + const name = "newVMS"; + const grid3 = await getClient(`vm/${name}`); const vms: MachinesModel = { - name: "newVMS", + name, network: { name: "wedtest", ip_range: "10.249.0.0/16", @@ -65,10 +66,10 @@ async function main() { await deploy(grid3, vms); //Get the deployment - await getDeployment(grid3, vms.name); + await getDeployment(grid3, name); //Uncomment the line below to cancel the deployment - // await cancel(grid3, { name: vms.name }); + // await cancel(grid3, { name }); await grid3.disconnect(); } diff --git a/packages/grid_client/scripts/single_vm_mycelium.ts b/packages/grid_client/scripts/single_vm_mycelium.ts index 2f9b65704b..07b7152260 100644 --- a/packages/grid_client/scripts/single_vm_mycelium.ts +++ b/packages/grid_client/scripts/single_vm_mycelium.ts @@ -24,10 +24,11 @@ async function cancel(client, vms) { } async function main() { - const grid3 = await getClient(); + const name = "newMY"; + const grid3 = await getClient(`vm/${name}`); const vms: MachinesModel = { - name: "newMY", + name, network: { name: "hellotest", ip_range: "10.249.0.0/16", @@ -72,10 +73,10 @@ async function main() { await deploy(grid3, vms); //Get the deployment - await getDeployment(grid3, vms.name); + await getDeployment(grid3, name); //Uncomment the line below to cancel the deployment - // await cancel(grid3, { name: vms.name }); + // await cancel(grid3, { name }); await grid3.disconnect(); } diff --git a/packages/grid_client/scripts/vm_with_gpu.ts b/packages/grid_client/scripts/vm_with_gpu.ts index 5ec8005d48..33c7763719 100644 --- a/packages/grid_client/scripts/vm_with_gpu.ts +++ b/packages/grid_client/scripts/vm_with_gpu.ts @@ -24,7 +24,8 @@ async function cancel(client, vms) { } async function main() { - const grid3 = await getClient(); + const name = "vmgpu"; + const grid3 = await getClient(`vm/${name}`); const vmQueryOptions: FilterOptions = { cru: 8, @@ -48,7 +49,7 @@ async function main() { } const vms: MachinesModel = { - name: "vmgpu", + name, network: { name: "vmgpuNetwork", ip_range: "10.249.0.0/16", @@ -76,7 +77,7 @@ async function main() { env: { SSH_KEY: config.ssh_key, }, - gpus: gpuList[0].id, + gpus: [gpuList[0].id], }, ], metadata: "", @@ -87,10 +88,10 @@ async function main() { await deploy(grid3, vms); //Get the deployment - await getDeployment(grid3, vms.name); + await getDeployment(grid3, name); //Uncomment the line below to cancel the deployment - // await cancel(grid3, { name: vms.name }); + // await cancel(grid3, { name }); await grid3.disconnect(); } diff --git a/packages/grid_client/scripts/vm_with_qsfs.ts b/packages/grid_client/scripts/vm_with_qsfs.ts index 01ddd68fbe..ffef85948f 100644 --- a/packages/grid_client/scripts/vm_with_qsfs.ts +++ b/packages/grid_client/scripts/vm_with_qsfs.ts @@ -38,10 +38,10 @@ async function deleteQsfs(client, qsfs) { } async function main() { - const grid3 = await getClient(); + const name = "wed2710t1"; + const grid3 = await getClient(`vm/${name}`); const qsfs_name = "wed2710q1"; - const machines_name = "wed2710t1"; const vmQueryOptions: FilterOptions = { cru: 1, @@ -57,7 +57,7 @@ async function main() { farmId: 1, }; - const qsfsNodes = []; + const qsfsNodes: number[] = []; const allNodes = await grid3.capacity.filterNodes(qsfsQueryOptions); if (allNodes.length >= 2) { @@ -79,7 +79,7 @@ async function main() { }; const vms: MachinesModel = { - name: machines_name, + name, network: { name: "wed2710n1", ip_range: "10.201.0.0/16", @@ -132,10 +132,10 @@ async function main() { await deploy(grid3, vms); //Get the deployment - await getDeployment(grid3, vms.name); + await getDeployment(grid3, name); //Uncomment the line below to cancel the deployment - // await cancel(grid3, { name: machines_name }); + // await cancel(grid3, { name }); // await deleteQsfs(grid3, { name: qsfs_name }); await grid3.disconnect(); diff --git a/packages/grid_client/scripts/wireguard_vm.ts b/packages/grid_client/scripts/wireguard_vm.ts index 42cf5a9027..acb0f3ec77 100644 --- a/packages/grid_client/scripts/wireguard_vm.ts +++ b/packages/grid_client/scripts/wireguard_vm.ts @@ -2,6 +2,8 @@ import { FilterOptions, GatewayNameModel, MachineModel, MachinesModel, NetworkMo import { config, getClient } from "./client_loader"; import { log } from "./utils"; +const name = "newVMs"; + function createNetworkModel(gwNode: number, name: string): NetworkModel { return { name, @@ -30,7 +32,7 @@ function createMachineModel(node: number) { } function createMachinesModel(vm: MachineModel, network: NetworkModel): MachinesModel { return { - name: "newVMs", + name, network, machines: [vm], metadata: "", @@ -91,7 +93,7 @@ async function deleteGw(client, gw) { } async function main() { - const grid3 = await getClient(); + const grid3 = await getClient(`vm/${name}`); const gwNode = +(await grid3.capacity.filterNodes({ gateway: true }))[0].nodeId; @@ -112,7 +114,7 @@ async function main() { await deployVM(grid3, machines); //Get VM deployment - const deployedVm = await getVMDeployment(grid3, machines.name); + const deployedVm = await getVMDeployment(grid3, name); const vmPrivateIP = (deployedVm as { interfaces: { ip: string }[] }[])[0].interfaces[0].ip; const gateway = createGwModel(gwNode, vmPrivateIP, network.name, "pyserver1", 8000); @@ -125,7 +127,7 @@ async function main() { await getGwDeployment(grid3, gateway.name); // delete - // await deleteVM(grid3, { name: machines.name }); + await deleteVM(grid3, { name }); // await deleteGw(grid3, { name: gateway.name }); await grid3.disconnect(); diff --git a/packages/grid_client/scripts/zdb.ts b/packages/grid_client/scripts/zdb.ts index 0818b64fda..e696f78c01 100644 --- a/packages/grid_client/scripts/zdb.ts +++ b/packages/grid_client/scripts/zdb.ts @@ -24,7 +24,8 @@ async function cancel(client, zdb) { } async function main() { - const grid3 = await getClient(); + const name = "tttzdbs"; + const grid3 = await getClient(`zdb/${name}`); const zdbQueryOptions: FilterOptions = { sru: 1, @@ -34,7 +35,7 @@ async function main() { }; const zdbs: ZDBSModel = { - name: "tttzdbs", + name, zdbs: [ { name: "hamada", @@ -53,10 +54,10 @@ async function main() { await deploy(grid3, zdbs); //Get the deployment - await getDeployment(grid3, zdbs.name); + await getDeployment(grid3, name); //Uncomment the line below to cancel the deployment - // await cancel(grid3, { name: zdbs.name }); + // await cancel(grid3, { name }); await grid3.disconnect(); } diff --git a/packages/grid_client/src/clients/tf-grid/contracts.ts b/packages/grid_client/src/clients/tf-grid/contracts.ts index 0b7a5c2749..73bd994ba4 100644 --- a/packages/grid_client/src/clients/tf-grid/contracts.ts +++ b/packages/grid_client/src/clients/tf-grid/contracts.ts @@ -17,6 +17,9 @@ export interface ListContractByTwinIdOptions { graphqlURL: string; twinId: number; stateList?: ContractStates[]; + type?: string; + projectName?: string; + nodeId?: number; } export interface ContractUsedResources { @@ -46,7 +49,8 @@ export interface GqlNodeContract extends GqlBaseContract { deploymentData: string; deploymentHash: string; numberOfPublicIPs: number; - resourcesUsed: ContractUsedResources; + resourcesUsed: ContractUsedResources | undefined; + parsedDeploymentData?: { type: string; name: string; projectName: string }; } export interface GqlRentContract extends GqlBaseContract { @@ -81,6 +85,9 @@ export interface ListContractByAddressOptions { export interface ListMyContractOptions { graphqlURL: string; stateList?: ContractStates[]; + type?: string; + projectName?: string; + nodeId?: number; } export interface GetConsumptionOptions { @@ -141,6 +148,56 @@ class TFContracts extends Contracts { } } + async listNodeContractsByTwinId(options: ListContractByTwinIdOptions): Promise<GqlNodeContract[]> { + options.stateList = options.stateList || [ContractStates.Created, ContractStates.GracePeriod]; + const state = `[${options.stateList.join(", ")}]`; + const gqlClient = new Graphql(options.graphqlURL); + const opts = `(where: {twinID_eq: ${options.twinId}, state_in: ${state}}, orderBy: twinID_ASC)`; + + // filter contracts based on deploymentData + let filterQuery = ""; + if (options.nodeId) { + filterQuery += ` , nodeID_eq: ${options.nodeId}`; + } + if (options.type || options.projectName) { + filterQuery += " , AND: ["; + + if (options.type) { + // eslint-disable-next-line no-useless-escape + filterQuery += `{ deploymentData_contains: \"\\\"type\\\":\\\"${options.type}\\\"\" },`; + } + + if (options.projectName) { + // eslint-disable-next-line no-useless-escape + filterQuery += `{ deploymentData_contains: \"\\\"projectName\\\":\\\"${options.projectName}\" }`; + } + + filterQuery += "]"; + } + + try { + const nodeContractsCount = await gqlClient.getItemTotalCount("nodeContracts", opts); + const body = `query getContracts($nodeContractsCount: Int!){ + nodeContracts(where: {twinID_eq: ${options.twinId}, state_in: ${state}${filterQuery}}, limit: $nodeContractsCount) { + contractID + deploymentData + state + createdAt + nodeID + numberOfPublicIPs + } + }`; + const response = await gqlClient.query(body, { + nodeContractsCount, + }); + + return (response["data"] as GqlContracts).nodeContracts; + } catch (err) { + (err as Error).message = formatErrorMessage(`Error listing contracts by twin id ${options.twinId}.`, err); + throw err; + } + } + /** * Get contract consumption per hour in TFT. * @@ -221,6 +278,18 @@ class TFContracts extends Contracts { }); } + async listMyNodeContracts(options: ListMyContractOptions): Promise<GqlNodeContract[]> { + const twinId = await this.client.twins.getMyTwinId(); + return await this.listNodeContractsByTwinId({ + graphqlURL: options.graphqlURL, + twinId: twinId, + stateList: options.stateList, + type: options.type, + projectName: options.projectName, + nodeId: options.nodeId, + }); + } + async contractLock(options: ContractLockOptions) { const res = await super.contractLock(options); const amountLocked = new Decimal(res.amountLocked); diff --git a/packages/grid_client/src/high_level/base.ts b/packages/grid_client/src/high_level/base.ts index f0de74c782..4ca842d2eb 100644 --- a/packages/grid_client/src/high_level/base.ts +++ b/packages/grid_client/src/high_level/base.ts @@ -102,7 +102,7 @@ class HighLevelBase { events.emit("logs", `Deleting ip: ${machineIp} from node: ${node_id}, network ${network.name}`); const deletedIp = network.deleteReservedIp(node_id, machineIp); if (remainingWorkloads.length === 0) { - twinDeployments.push(new TwinDeployment(deployment, Operations.delete, 0, 0, network)); + twinDeployments.push(new TwinDeployment(deployment, Operations.delete, 0, 0, "", network)); } const numberOfIps = network.getNodeReservedIps(node_id).length; if (numberOfIps !== 0) { @@ -122,7 +122,7 @@ class HighLevelBase { const contract_id = await network.deleteNode(node_id); if (contract_id === deployment.contract_id) { if (remainingWorkloads.length === 1) { - twinDeployments.push(new TwinDeployment(deployment, Operations.delete, 0, 0, network)); + twinDeployments.push(new TwinDeployment(deployment, Operations.delete, 0, 0, "", network)); remainingWorkloads = []; } else { remainingWorkloads = remainingWorkloads.filter(item => item.name !== network?.name); @@ -137,10 +137,10 @@ class HighLevelBase { continue; } if (d.workloads.length === 1) { - twinDeployments.push(new TwinDeployment(d, Operations.delete, 0, 0, network)); + twinDeployments.push(new TwinDeployment(d, Operations.delete, 0, 0, "", network)); } else { d.workloads = d.workloads.filter(item => item.name !== network?.name); - twinDeployments.push(new TwinDeployment(d, Operations.update, 0, 0, network)); + twinDeployments.push(new TwinDeployment(d, Operations.update, 0, 0, "", network)); } } } @@ -153,10 +153,10 @@ class HighLevelBase { continue; } if (d.workloads.length === 1) { - twinDeployments.push(new TwinDeployment(d, Operations.delete, 0, 0, network)); + twinDeployments.push(new TwinDeployment(d, Operations.delete, 0, 0, "", network)); } else { d.workloads = d.workloads.filter(item => item.name !== network?.name); - twinDeployments.push(new TwinDeployment(d, Operations.update, 0, 0, network)); + twinDeployments.push(new TwinDeployment(d, Operations.update, 0, 0, "", network)); } } } @@ -193,7 +193,7 @@ class HighLevelBase { const deletedMachineWorkloads = filteredWorkloads[1]; if (remainingWorkloads.length === 0 && deletedMachineWorkloads.length === 0) { - twinDeployments.push(new TwinDeployment(deployment, Operations.delete, 0, 0)); + twinDeployments.push(new TwinDeployment(deployment, Operations.delete, 0, 0, "")); } const [newTwinDeployments, newRemainingWorkloads, deletedNodes, deletedIps, network] = await this._deleteMachineNetwork(deployment, remainingWorkloads, deletedMachineWorkloads, node_id); @@ -207,7 +207,7 @@ class HighLevelBase { network!.deleteReservedIp(node_id, deleteIp); } deployment.workloads = remainingWorkloads; - twinDeployments.push(new TwinDeployment(deployment, Operations.update, 0, 0, network!)); + twinDeployments.push(new TwinDeployment(deployment, Operations.update, 0, 0, "", network!)); } return twinDeployments; } diff --git a/packages/grid_client/src/high_level/gateway.ts b/packages/grid_client/src/high_level/gateway.ts index 4557854d1e..3857e7c858 100644 --- a/packages/grid_client/src/high_level/gateway.ts +++ b/packages/grid_client/src/high_level/gateway.ts @@ -1,4 +1,5 @@ import { DeploymentFactory, GWPrimitive } from "../primitives/index"; +import { Workload } from "../zos"; import { HighLevelBase } from "./base"; import { Operations, TwinDeployment } from "./models"; @@ -9,6 +10,7 @@ class GatewayHL extends HighLevelBase { tls_passthrough: boolean, backends: string[], network: string, + contractMetadata: string, metadata: string, description: string, fqdn = "", @@ -16,17 +18,25 @@ class GatewayHL extends HighLevelBase { ): Promise<TwinDeployment[]> { const public_ips = 0; const gw = new GWPrimitive(); - const workloads = []; + const workloads: Workload[] = []; if (fqdn != "") { - workloads.push(gw.createFQDN(fqdn, tls_passthrough, backends, name, network, metadata, description)); + workloads.push(gw.createFQDN(fqdn, tls_passthrough, backends, name, network, "", description)); } else { - workloads.push(gw.createName(name, tls_passthrough, backends, network, metadata, description)); + workloads.push(gw.createName(name, tls_passthrough, backends, network, "", description)); } const deploymentFactory = new DeploymentFactory(this.config); const deployment = deploymentFactory.create(workloads, 1626394539, metadata, description, 0); - const twinDeployments = []; + const twinDeployments: TwinDeployment[] = []; twinDeployments.push( - new TwinDeployment(deployment, Operations.deploy, public_ips, node_id, null, solutionProviderId), + new TwinDeployment( + deployment, + Operations.deploy, + public_ips, + node_id, + contractMetadata, + null, + solutionProviderId, + ), ); return twinDeployments; } diff --git a/packages/grid_client/src/high_level/kubernetes.ts b/packages/grid_client/src/high_level/kubernetes.ts index 79c0b5153c..1a43a4f6fd 100644 --- a/packages/grid_client/src/high_level/kubernetes.ts +++ b/packages/grid_client/src/high_level/kubernetes.ts @@ -25,6 +25,7 @@ class KubernetesHL extends HighLevelBase { network: Network, myceliumNetworkSeeds: MyceliumNetworkModel[] = [], sshKey: string, + contractMetadata: string, metadata = "", description = "", qsfs_disks: QSFSDiskModel[] = [], @@ -70,6 +71,7 @@ class KubernetesHL extends HighLevelBase { myceliumNetworkSeeds!, "/sbin/zinit init", env, + contractMetadata, metadata, description, qsfs_disks, @@ -101,6 +103,7 @@ class KubernetesHL extends HighLevelBase { network: Network, myceliumNetworkSeeds: MyceliumNetworkModel[] = [], sshKey: string, + contractMetadata, metadata = "", description = "", qsfs_disks: QSFSDiskModel[] = [], @@ -146,6 +149,7 @@ class KubernetesHL extends HighLevelBase { myceliumNetworkSeeds, "/sbin/zinit init", env, + contractMetadata, metadata, description, qsfs_disks, diff --git a/packages/grid_client/src/high_level/machine.ts b/packages/grid_client/src/high_level/machine.ts index f095774cf4..76fd23988b 100644 --- a/packages/grid_client/src/high_level/machine.ts +++ b/packages/grid_client/src/high_level/machine.ts @@ -40,6 +40,7 @@ class VMHL extends HighLevelBase { myceliumNetworkSeeds: MyceliumNetworkModel[] = [], entrypoint: string, env: Record<string, unknown>, + contractMetadata: string, metadata = "", description = "", qsfsDisks: QSFSDiskModel[] = [], @@ -61,7 +62,7 @@ class VMHL extends HighLevelBase { const disk = new DiskPrimitive(); for (const d of disks) { totalDisksSize += d.size; - workloads.push(disk.create(d.size, d.name, metadata, description)); + workloads.push(disk.create(d.size, d.name, "", description)); diskMounts.push(disk.createMount(d.name, d.mountpoint)); } @@ -136,7 +137,7 @@ class VMHL extends HighLevelBase { if (publicIp || publicIp6) { const ip = new PublicIPPrimitive(); ipName = `${name}_pubip`; - workloads.push(ip.create(ipName, metadata, description, 0, publicIp, publicIp6)); + workloads.push(ip.create(ipName, "", description, 0, publicIp, publicIp6)); if (publicIp) { publicIps++; } @@ -176,7 +177,8 @@ class VMHL extends HighLevelBase { accessNodeSubnet = network.getFreeSubnet(); } // network - const networkMetadata = JSON.stringify({ + const networkContractMetadata = JSON.stringify({ + version: 3, type: "network", name: network.name, projectName: this.config.projectName, @@ -219,7 +221,6 @@ class VMHL extends HighLevelBase { access_net_workload = await network.addNode( access_node_id, mycelium, - networkMetadata, description, accessNodeSubnet, myceliumNetworkSeeds, @@ -230,55 +231,78 @@ class VMHL extends HighLevelBase { if (network.nodeExists(nodeId)) { const deployment = await network.checkMycelium(nodeId, mycelium, myceliumNetworkSeeds); if (deployment) { - deployments.push(new TwinDeployment(deployment, Operations.update, 0, 0, network)); + deployments.push(new TwinDeployment(deployment, Operations.update, 0, 0, networkContractMetadata, network)); } } - const znet_workload = await network.addNode( - nodeId, - mycelium, - networkMetadata, - description, - userIPsubnet, - myceliumNetworkSeeds, - ); + const znet_workload = await network.addNode(nodeId, mycelium, description, userIPsubnet, myceliumNetworkSeeds); if ((await network.exists()) && (znet_workload || access_net_workload)) { // update network for (const deployment of network.deployments) { const d = await deploymentFactory.fromObj(deployment); for (const workload of d["workloads"]) { if ( - workload["type"] !== WorkloadTypes.network || - !Addr(network.ipRange).contains(Addr(workload["data"]["subnet"])) + workload.type !== WorkloadTypes.network || + !Addr(network.ipRange).contains(Addr(workload.data["subnet"])) ) { continue; } - workload.data = network.updateNetwork(workload["data"]); + workload.data = network.getUpdatedNetwork(workload["data"]); workload.version += 1; break; } - deployments.push(new TwinDeployment(d, Operations.update, 0, 0, network)); + deployments.push(new TwinDeployment(d, Operations.update, 0, 0, networkContractMetadata, network)); } if (znet_workload) { - const deployment = deploymentFactory.create([znet_workload], 0, networkMetadata, description, 0); - deployments.push(new TwinDeployment(deployment, Operations.deploy, 0, nodeId, network, solutionProviderId)); + const deployment = deploymentFactory.create([znet_workload], 0, networkContractMetadata, description, 0); + deployments.push( + new TwinDeployment( + deployment, + Operations.deploy, + 0, + nodeId, + networkContractMetadata, + network, + solutionProviderId, + ), + ); } } else if (znet_workload) { // node not exist on the network if (!access_net_workload && !hasAccessNode && addAccess) { // this node is access node, so add access point on it wgConfig = await network.addAccess(nodeId, true); - znet_workload["data"] = network.updateNetwork(znet_workload.data); + znet_workload["data"] = network.getUpdatedNetwork(znet_workload.data); } - const deployment = deploymentFactory.create([znet_workload], 0, networkMetadata, description, 0); - deployments.push(new TwinDeployment(deployment, Operations.deploy, 0, nodeId, network, solutionProviderId)); + const deployment = deploymentFactory.create([znet_workload], 0, networkContractMetadata, description, 0); + deployments.push( + new TwinDeployment( + deployment, + Operations.deploy, + 0, + nodeId, + networkContractMetadata, + network, + solutionProviderId, + ), + ); } if (access_net_workload) { // network is not exist, and the node provide is not an access node const accessNodeId = access_net_workload.data["node_id"]; - access_net_workload["data"] = network.updateNetwork(access_net_workload.data); - const deployment = deploymentFactory.create([access_net_workload], 0, networkMetadata, description, 0); - deployments.push(new TwinDeployment(deployment, Operations.deploy, 0, accessNodeId, network, solutionProviderId)); + access_net_workload["data"] = network.getUpdatedNetwork(access_net_workload.data); + const deployment = deploymentFactory.create([access_net_workload], 0, networkContractMetadata, description, 0); + deployments.push( + new TwinDeployment( + deployment, + Operations.deploy, + 0, + accessNodeId, + networkContractMetadata, + network, + solutionProviderId, + ), + ); } // vm @@ -322,7 +346,7 @@ class VMHL extends HighLevelBase { ipName, entrypoint, env, - metadata, + "", description, 0, corex, @@ -332,14 +356,24 @@ class VMHL extends HighLevelBase { if (zlogsOutput) { const zlogs = new ZlogsPrimitive(); - workloads.push(zlogs.create(name, zlogsOutput, metadata, description)); + workloads.push(zlogs.create(name, zlogsOutput, "", description)); } // deployment // NOTE: expiration is not used for zos deployment const deployment = deploymentFactory.create(workloads, 0, metadata, description, 0); - deployments.push(new TwinDeployment(deployment, Operations.deploy, publicIps, nodeId, network, solutionProviderId)); + deployments.push( + new TwinDeployment( + deployment, + Operations.deploy, + publicIps, + nodeId, + contractMetadata, + network, + solutionProviderId, + ), + ); return [deployments, wgConfig]; } diff --git a/packages/grid_client/src/high_level/models.ts b/packages/grid_client/src/high_level/models.ts index ed6470a750..bff2730a71 100644 --- a/packages/grid_client/src/high_level/models.ts +++ b/packages/grid_client/src/high_level/models.ts @@ -13,6 +13,7 @@ class TwinDeployment { public operation: Operations, public publicIps: number, public nodeId: number, + public metadata: string, public network: Network | null = null, public solutionProviderId: number | null = null, public returnNetworkContracts = false, diff --git a/packages/grid_client/src/high_level/network.ts b/packages/grid_client/src/high_level/network.ts index 984bba8333..06b41664b2 100644 --- a/packages/grid_client/src/high_level/network.ts +++ b/packages/grid_client/src/high_level/network.ts @@ -1,13 +1,30 @@ import { ValidationError } from "@threefold/types"; import { Addr } from "netaddr"; +import * as PATH from "path"; +import { GridClientConfig } from "../config"; import { MyceliumNetworkModel } from "../modules"; -import { DeploymentFactory, Network } from "../primitives"; +import { DeploymentFactory, Network, UserAccess } from "../primitives"; +import { BackendStorage } from "../storage"; import { WorkloadTypes, Znet } from "../zos"; import { HighLevelBase } from "./base"; import { Operations, TwinDeployment } from "./models"; class NetworkHL extends HighLevelBase { + backendStorage: BackendStorage; + constructor(public config: GridClientConfig) { + super(config); + this.backendStorage = new BackendStorage( + config.backendStorageType, + config.substrateURL, + config.mnemonic, + config.storeSecret, + config.keypairType, + config.backendStorage, + config.seed, + ); + } + async addNode( networkName: string, ipRange: string, @@ -20,29 +37,23 @@ class NetworkHL extends HighLevelBase { ) { const network = new Network(networkName, ipRange, this.config); await network.load(); - const networkMetadata = JSON.stringify({ + const contractMetadata = JSON.stringify({ + version: 3, type: "network", name: networkName, projectName: this.config.projectName, }); - const workload = await network.addNode( - nodeId, - mycelium, - networkMetadata, - description, - subnet, - myceliumNetworkSeeds, - ); + const workload = await network.addNode(nodeId, mycelium, description, subnet, myceliumNetworkSeeds); if (!workload) { throw new ValidationError(`Node ${nodeId} already exists on network ${networkName}.`); } const twinDeployments: TwinDeployment[] = []; const deploymentFactory = new DeploymentFactory(this.config); - const deployment = deploymentFactory.create([workload], 0, networkMetadata, description, 0); + const deployment = deploymentFactory.create([workload], 0, contractMetadata, description, 0); twinDeployments.push( - new TwinDeployment(deployment, Operations.deploy, 0, nodeId, network, solutionProviderId, true), + new TwinDeployment(deployment, Operations.deploy, 0, nodeId, contractMetadata, network, solutionProviderId, true), ); if (!(await network.exists())) { @@ -56,11 +67,13 @@ class NetworkHL extends HighLevelBase { if (workload.type !== WorkloadTypes.network || !Addr(network.ipRange).contains(Addr(data.subnet))) { continue; } - workload.data = network.updateNetwork(data); + workload.data = network.getUpdatedNetwork(data); workload.version += 1; break; } - twinDeployments.push(new TwinDeployment(d, Operations.update, 0, 0, network, solutionProviderId, true)); + twinDeployments.push( + new TwinDeployment(d, Operations.update, 0, 0, contractMetadata, network, solutionProviderId, true), + ); } return twinDeployments; } @@ -70,6 +83,31 @@ class NetworkHL extends HighLevelBase { await network.load(); return network.nodeExists(nodeId); } + + async getWireguardConfigs(networkName: string, ipRange: string, nodeId?: number): Promise<string[]> { + const configs: string[] = []; + const network = new Network(networkName, ipRange, this.config); + await network.load(); + if (nodeId && !network.nodeExists(nodeId)) { + return configs; + } + let userAccesses: UserAccess[] = network.userAccesses; + if (network.userAccesses && network.userAccesses.length > 0) { + if (nodeId && network.nodeExists(nodeId)) { + userAccesses = network.userAccesses.filter(userAccess => userAccess.node_id === nodeId); + } + for (const userAccess of userAccesses) { + const nodesWGPubkey = await network.getNodeWGPublicKey(userAccess.node_id); + const endpoint = await network.getAccessNodeEndpoint(userAccess.node_id); + configs.push(network.getWireguardConfig(userAccess.subnet, userAccess.private_key, nodesWGPubkey, endpoint)); + } + return configs; + } else { + const path = PATH.join(this.config.storePath, "networks", networkName, "info.json"); + const networkInfo = await this.backendStorage.load(path); + return networkInfo["wireguardConfigs"] || []; + } + } } export { NetworkHL }; diff --git a/packages/grid_client/src/high_level/twinDeploymentHandler.ts b/packages/grid_client/src/high_level/twinDeploymentHandler.ts index 9f75e559cd..c2983d9a38 100644 --- a/packages/grid_client/src/high_level/twinDeploymentHandler.ts +++ b/packages/grid_client/src/high_level/twinDeploymentHandler.ts @@ -136,14 +136,13 @@ class TwinDeploymentHandler { } async saveNetworks(twinDeployments: TwinDeployment[]) { + // left just to delete the old keys for (const twinDeployment of twinDeployments) { if (twinDeployment.network) { if (twinDeployment.operation === Operations.delete) { await twinDeployment.network.save(); continue; } - // deploy or update operations - await twinDeployment.network.save(twinDeployment.deployment.contract_id, twinDeployment.nodeId); } } } @@ -433,6 +432,7 @@ class TwinDeploymentHandler { Operations.update, 0, old_contract.contractType.nodeContract.nodeId, + "", ), ); } @@ -459,7 +459,7 @@ class TwinDeploymentHandler { } const extrinsic = await this.tfclient.contracts.createNode({ hash: twinDeployment.deployment.challenge_hash(), - data: twinDeployment.deployment.metadata, + data: twinDeployment.metadata, nodeId: twinDeployment.nodeId, numberOfPublicIps: twinDeployment.publicIps, solutionProviderId: twinDeployment.solutionProviderId, @@ -505,8 +505,16 @@ class TwinDeploymentHandler { await this.checkNodesCapacity(twinDeployments); await this.checkFarmIps(twinDeployments); - const contracts = { created: [], updated: [], deleted: [] }; - const resultContracts = { created: [], updated: [], deleted: [] }; + const contracts: { created: Contract[]; updated: Contract[]; deleted: { contractId: number }[] } = { + created: [], + updated: [], + deleted: [], + }; + const resultContracts: { created: Contract[]; updated: Contract[]; deleted: { contractId: number }[] } = { + created: [], + updated: [], + deleted: [], + }; let nodeExtrinsics: ExtrinsicResult<Contract>[] = []; let nameExtrinsics: ExtrinsicResult<Contract>[] = []; let deletedExtrinsics: ExtrinsicResult<number>[] = []; @@ -518,7 +526,7 @@ class TwinDeploymentHandler { } if (workload.type === WorkloadTypes.network) { events.emit("logs", `Updating network workload with name: ${workload.name}`); - workload["data"] = twinDeployment.network.updateNetwork(workload.data); + twinDeployment.network.updateWorkload(twinDeployment.nodeId, workload); } } const extrinsics = await this.PrepareExtrinsic(twinDeployment, contracts); diff --git a/packages/grid_client/src/high_level/zdb.ts b/packages/grid_client/src/high_level/zdb.ts index 132762424a..f3168cbaaa 100644 --- a/packages/grid_client/src/high_level/zdb.ts +++ b/packages/grid_client/src/high_level/zdb.ts @@ -17,6 +17,7 @@ class ZdbHL extends HighLevelBase { mode: ZdbModes, password: string, publicNamespace: boolean, + contractMetadata: string, metadata = "", description = "", solutionProviderId?: number, @@ -31,7 +32,7 @@ class ZdbHL extends HighLevelBase { const zdbFactory = new ZdbPrimitive(); const zdbWorkload = zdbFactory.create(name, disk_size, mode, password, publicNamespace, metadata, description); const deployment = deploymentFactory.create([zdbWorkload], 1626394539, metadata, description, 0); - return new TwinDeployment(deployment, Operations.deploy, 0, node_id, null, solutionProviderId); + return new TwinDeployment(deployment, Operations.deploy, 0, node_id, contractMetadata, null, solutionProviderId); } async delete(deployment: Deployment, names: string[]) { return await this._delete(deployment, names, [WorkloadTypes.zdb]); diff --git a/packages/grid_client/src/modules/base.ts b/packages/grid_client/src/modules/base.ts index de2c611f22..83985877db 100644 --- a/packages/grid_client/src/modules/base.ts +++ b/packages/grid_client/src/modules/base.ts @@ -1,7 +1,8 @@ +import { Contract } from "@threefold/tfchain_client"; import { GridClientErrors, ValidationError } from "@threefold/types"; import * as PATH from "path"; -import { RMB } from "../clients"; +import { GqlNodeContract, RMB } from "../clients"; import { TFClient } from "../clients/tf-grid/client"; import { GridClientConfig } from "../config"; import { formatErrorMessage } from "../helpers"; @@ -21,7 +22,15 @@ import { QuantumSafeFS, QuantumSafeFSResult } from "../zos/qsfs"; import { Workload, WorkloadTypes } from "../zos/workload"; import { Zmachine, ZmachineResult } from "../zos/zmachine"; import { Zmount } from "../zos/zmount"; - +import { ContractStates } from "./models"; + +const modulesNames = { + machines: "vm", + kubernetes: "kubernetes", + gateways: "gateway", + qsfs_zdbs: "QSFS", + zdb: "zdb", +}; class BaseModule { moduleName = ""; projectName = ""; @@ -31,6 +40,9 @@ class BaseModule { twinDeploymentHandler: TwinDeploymentHandler; backendStorage: BackendStorage; tfClient: TFClient; + contracts: Required<GqlNodeContract>[]; + static newContracts: GqlNodeContract[] = []; + static deletedContracts: number[] = []; constructor(public config: GridClientConfig) { this.projectName = config.projectName; @@ -57,7 +69,7 @@ class BaseModule { return PATH.join(this.config.storePath, this.projectName, this.moduleName, name); } - async getDeploymentContracts(name: string) { + async getOldDeploymentContracts(name: string) { const path = this.getNewDeploymentPath(name, "contracts.json"); const contracts = await this.backendStorage.load(path); if (!contracts) { @@ -66,23 +78,39 @@ class BaseModule { return contracts; } - async save(name: string, contracts: Record<string, unknown[]>) { + async getDeploymentContracts(name: string) { + const contracts = await this.getMyContracts(); + return contracts.filter(c => c.parsedDeploymentData.name === name); + } + + async save(name: string, contracts: { created: Contract[]; updated: Contract[]; deleted: { contractId: number }[] }) { const contractsPath = PATH.join(this.getNewDeploymentPath(name), "contracts.json"); const wireguardPath = PATH.join(this.getDeploymentPath(name), `${name}.conf`); - const oldContracts = await this.getDeploymentContracts(name); + const oldContracts = await this.getOldDeploymentContracts(name); let StoreContracts = oldContracts; let backendOperations = []; for (const contract of contracts["created"]) { - StoreContracts.push({ - contract_id: contract["contractId"], - node_id: contract["contractType"]["nodeContract"]["nodeId"], + if (!contract.contractType.nodeContract) continue; + BaseModule.newContracts.push({ + contractID: String(contract.contractId), + createdAt: Date.now().toString(), + deploymentData: contract.contractType.nodeContract.deploymentData, + deploymentHash: contract.contractType.nodeContract.deploymentHash, + gridVersion: "4", + id: "", + nodeID: contract.contractType.nodeContract.nodeId, + numberOfPublicIPs: contract.contractType.nodeContract.publicIps, + solutionProviderID: String(contract.solutionProviderId), + state: ContractStates.Created, + twinID: String(contract.twinId), + parsedDeploymentData: JSON.parse(contract.contractType.nodeContract.deploymentData), + resourcesUsed: undefined, }); - const contractPath = PATH.join(this.config.storePath, "contracts", `${contract["contractId"]}.json`); - const contractInfo = { projectName: this.projectName, moduleName: this.moduleName, deploymentName: name }; - backendOperations = backendOperations.concat(await this.backendStorage.dump(contractPath, contractInfo)); } + for (const contract of contracts["deleted"]) { + BaseModule.deletedContracts.push(contract.contractId); StoreContracts = StoreContracts.filter(c => c["contract_id"] !== contract["contractId"]); const contractPath = PATH.join(this.config.storePath, "contracts", `${contract["contractId"]}.json`); backendOperations = backendOperations.concat(await this.backendStorage.dump(contractPath, "")); @@ -147,9 +175,52 @@ class BaseModule { await Promise.all(keys.map(__updatePath).flat(1)); } + private async getMyContracts(fetch = false) { + if (fetch || !this.contracts) { + let contracts = await this.tfClient.contracts.listMyNodeContracts({ + graphqlURL: this.config.graphqlURL, + type: modulesNames[this.moduleName], + projectName: this.projectName, + }); + const alreadyFetchedContracts: GqlNodeContract[] = []; + for (const contract of BaseModule.newContracts) { + if (contract.parsedDeploymentData?.projectName !== this.projectName) continue; + if (contract.parsedDeploymentData.type !== modulesNames[this.moduleName]) continue; + const c = contracts.filter(c => +c.contractID === +contract.contractID); + if (c.length > 0) { + alreadyFetchedContracts.push(contract); + continue; + } + contracts.push(contract); + } + + for (const contract of alreadyFetchedContracts) { + const index = BaseModule.newContracts.indexOf(contract); + if (index > -1) BaseModule.newContracts.splice(index, 1); + } + + contracts = contracts.filter(c => !BaseModule.deletedContracts.includes(+c.contractID)); + + const parsedContracts: Required<GqlNodeContract>[] = []; + for (const contract of contracts) { + const parsedDeploymentData = JSON.parse(contract.deploymentData); + parsedContracts.push({ ...contract, parsedDeploymentData }); + } + + this.contracts = parsedContracts; + } + + return this.contracts; + } + + private _getContractsName(contracts: Required<GqlNodeContract>[]): string[] { + return Array.from(new Set(contracts.map(c => c.parsedDeploymentData.name))); + } + async _list(): Promise<string[]> { await this._migrateListKeys(); - return this.backendStorage.list(this.getNewDeploymentPath("")); + const contracts = await this.getMyContracts(true); + return this._getContractsName(contracts); } async exists(name: string): Promise<boolean> { @@ -166,10 +237,10 @@ class BaseModule { } async _getDeploymentNodeIds(name: string): Promise<number[]> { - const nodeIds = []; + const nodeIds: number[] = []; const contracts = await this.getDeploymentContracts(name); for (const contract of contracts) { - nodeIds.push(contract["node_id"]); + nodeIds.push(contract.nodeID); } return nodeIds; } @@ -177,18 +248,20 @@ class BaseModule { async _getContractIdFromNodeId(name: string, nodeId: number): Promise<number> { const contracts = await this.getDeploymentContracts(name); for (const contract of contracts) { - if (contract["node_id"] === nodeId) { - return contract["contract_id"]; + if (contract.nodeID === nodeId) { + return +contract.contractID; } } + return 0; } async _getNodeIdFromContractId(name: string, contractId: number): Promise<number> { const contracts = await this.getDeploymentContracts(name); for (const contract of contracts) { - if (contract["contract_id"] === contractId) { - return contract["node_id"]; + if (+contract.contractID === contractId) { + return contract.nodeID; } } + return 0; } async _getWorkloadsByTypes(deploymentName: string, deployments, types: WorkloadTypes[]): Promise<Workload[]> { @@ -241,11 +314,13 @@ class BaseModule { cpu: data.compute_capacity.cpu, memory: data.compute_capacity.memory / 1024 ** 2, // MB }, - mounts: data.mounts.map(m => ({ - name: m.name, - mountPoint: m.mountpoint, - ...this._getDiskData(deployments, m.name, workload["contractId"]), - })), + mounts: data.mounts + ? data.mounts.map(m => ({ + name: m.name, + mountPoint: m.mountpoint, + ...this._getDiskData(deployments, m.name, workload["contractId"]), + })) + : [], env: data.env, entrypoint: data.entrypoint, metadata: workload.metadata, @@ -292,18 +367,18 @@ class BaseModule { } const contracts = await this.getDeploymentContracts(name); if (contracts.length === 0) { - await this.save(name, { created: [], deleted: [] }); + await this.save(name, { created: [], updated: [], deleted: [] }); } await this.tfClient.connect(); for (const contract of contracts) { - const c = await this.tfClient.contracts.get({ id: contract["contract_id"] }); + const c = await this.tfClient.contracts.get({ id: +contract.contractID }); if (c === null) { - await this.save(name, { created: [], deleted: [{ contractId: contract["contract_id"] }] }); + await this.save(name, { created: [], updated: [], deleted: [{ contractId: +contract.contractID }] }); continue; } const nodes = new Nodes(this.config.graphqlURL, this.config.proxyURL, this.config.rmbClient); - const node_twin_id = await nodes.getNodeTwinId(contract["node_id"]); - const payload = JSON.stringify({ contract_id: contract["contract_id"] }); + const node_twin_id = await nodes.getNodeTwinId(contract.nodeID); + const payload = JSON.stringify({ contract_id: +contract.contractID }); let deployment; try { deployment = await this.rmb.request([node_twin_id], "zos.deployment.get", payload); @@ -320,7 +395,7 @@ class BaseModule { if (found) { deployments.push(deployment); } else { - await this.save(name, { created: [], deleted: [{ contractId: contract["contract_id"] }] }); + await this.save(name, { created: [], updated: [], deleted: [{ contractId: +contract.contractID }] }); } } return deployments; @@ -363,7 +438,7 @@ class BaseModule { if (!updateOldDeployment) { continue; } - finalTwinDeployments.push(new TwinDeployment(updateOldDeployment, Operations.update, 0, 0, network)); + finalTwinDeployments.push(new TwinDeployment(updateOldDeployment, Operations.update, 0, 0, "", network)); } if (!doneDeploymentIPWorkloadNames.includes(pubIPOLdWorkload)) { const tDeployments = await module.delete(oldDeployment, []); @@ -408,7 +483,7 @@ class BaseModule { if (!updateOldDeployment) { continue; } - finalTwinDeployments.push(new TwinDeployment(updateOldDeployment, Operations.update, 0, 0, network)); + finalTwinDeployments.push(new TwinDeployment(updateOldDeployment, Operations.update, 0, 0, "", network)); break; } if (!deploymentFound) { diff --git a/packages/grid_client/src/modules/gateway.ts b/packages/grid_client/src/modules/gateway.ts index f9e2a00cf8..b4f5639e3c 100644 --- a/packages/grid_client/src/modules/gateway.ts +++ b/packages/grid_client/src/modules/gateway.ts @@ -36,7 +36,8 @@ class GWModule extends BaseModule { throw new ValidationError(`Another gateway deployment with the same name ${options.name} already exists.`); } events.emit("logs", `Start creating the gateway deployment with name ${options.name}`); - const metadata = JSON.stringify({ + const contractMetadata = JSON.stringify({ + version: 3, type: "gateway", name: options.name, projectName: this.config.projectName, @@ -47,7 +48,8 @@ class GWModule extends BaseModule { options.tls_passthrough, options.backends, options.network, - options.metadata || metadata, + contractMetadata, + options.metadata, options.description, options.fqdn, options.solutionProviderId, @@ -65,7 +67,8 @@ class GWModule extends BaseModule { throw new ValidationError(`Another gateway deployment with the same name ${options.name} already exists.`); } events.emit("logs", `Start creating the gateway deployment with name ${options.name}`); - const metadata = JSON.stringify({ + const contractMetadata = JSON.stringify({ + version: 3, type: "gateway", name: options.name, projectName: this.config.projectName, @@ -76,7 +79,8 @@ class GWModule extends BaseModule { options.tls_passthrough, options.backends, options.network, - options.metadata || metadata, + contractMetadata, + options.metadata, options.description, "", options.solutionProviderId, diff --git a/packages/grid_client/src/modules/k8s.ts b/packages/grid_client/src/modules/k8s.ts index 74ccd53d4c..5a4590a789 100644 --- a/packages/grid_client/src/modules/k8s.ts +++ b/packages/grid_client/src/modules/k8s.ts @@ -81,10 +81,11 @@ class K8sModule extends BaseModule { let deployments: TwinDeployment[] = []; let wireguardConfig = ""; - const metadata = JSON.stringify({ + const contractMetadata = JSON.stringify({ + version: 3, type: "kubernetes", name: options.name, - projectName: this.config.projectName, + projectName: this.config.projectName || `kubernetes/${options.name}`, }); const masters_names: string[] = []; const workers_names: string[] = []; @@ -109,7 +110,8 @@ class K8sModule extends BaseModule { network, options.network.myceliumSeeds!, options.ssh_key, - options.metadata || metadata, + contractMetadata, + options.metadata, options.description, master.qsfs_disks, this.config.projectName, @@ -156,7 +158,8 @@ class K8sModule extends BaseModule { network, options.network.myceliumSeeds!, options.ssh_key, - options.metadata || metadata, + contractMetadata, + options.metadata, options.description, worker.qsfs_disks, this.config.projectName, @@ -280,6 +283,12 @@ class K8sModule extends BaseModule { const networkIpRange = Addr(masterWorkload.data["network"].interfaces[0].ip).mask(16).toString(); const network = new Network(networkName, networkIpRange, this.config); 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, @@ -297,6 +306,7 @@ class K8sModule extends BaseModule { network, [{ nodeId: options.node_id, seed: options.myceliumNetworkSeed! }], masterWorkload.data["env"]["SSH_KEY"], + contractMetadata, masterWorkload.metadata, masterWorkload.description, options.qsfs_disks, diff --git a/packages/grid_client/src/modules/machines.ts b/packages/grid_client/src/modules/machines.ts index 4a02a2390e..1e17d115b3 100644 --- a/packages/grid_client/src/modules/machines.ts +++ b/packages/grid_client/src/modules/machines.ts @@ -35,10 +35,11 @@ class MachinesModule extends BaseModule { let twinDeployments: TwinDeployment[] = []; let wireguardConfig = ""; - const metadata = JSON.stringify({ + const contractMetadata = JSON.stringify({ + version: 3, type: "vm", name: options.name, - projectName: this.config.projectName, + projectName: this.config.projectName || `vm/${options.name}`, }); const machines_names: string[] = []; @@ -65,7 +66,8 @@ class MachinesModule extends BaseModule { options.network.myceliumSeeds!, machine.entrypoint, machine.env, - options.metadata || metadata, + contractMetadata, + options.metadata, options.description, machine.qsfs_disks, this.config.projectName, @@ -168,7 +170,12 @@ class MachinesModule extends BaseModule { const networkIpRange = Addr(workload.data["network"].interfaces[0].ip).mask(16).toString(); const network = new Network(networkName, networkIpRange, this.config); await network.load(); - + const contractMetadata = JSON.stringify({ + version: 3, + type: "vm", + name: options.deployment_name, + projectName: this.config.projectName || `vm/${options.name}`, + }); const [twinDeployments] = await this.vm.create( options.name, options.node_id, @@ -186,6 +193,7 @@ class MachinesModule extends BaseModule { [{ nodeId: options.node_id, seed: options.myceliumNetworkSeed! }], options.entrypoint, options.env, + contractMetadata, workload.metadata, workload.description, options.qsfs_disks, diff --git a/packages/grid_client/src/modules/models.ts b/packages/grid_client/src/modules/models.ts index f84170e172..7c7d83766a 100644 --- a/packages/grid_client/src/modules/models.ts +++ b/packages/grid_client/src/modules/models.ts @@ -182,7 +182,7 @@ class K8SDeleteModel extends BaseGetDeleteModel {} class AddWorkerModel extends KubernetesNodeModel { @Expose() @IsString() @IsNotEmpty() @IsAlphanumeric() @MaxLength(NameLength) deployment_name: string; - @Expose() @IsString() @IsOptional() myceliumNetworkSeed: string; + @Expose() @IsString() @IsOptional() myceliumNetworkSeed?: string; } class DeleteWorkerModel { @@ -802,6 +802,7 @@ class NetworkHasNodeModel { class NetworkGetModel { @Expose() @IsString() @IsNotEmpty() @IsAlphanumeric() @MaxLength(NameLength) name: string; + @Expose() @IsString() @IsNotEmpty() ipRange: string; } class SetDedicatedNodeExtraFeesModel { diff --git a/packages/grid_client/src/modules/networks.ts b/packages/grid_client/src/modules/networks.ts index e1ee8b41ce..e67dc35ad2 100644 --- a/packages/grid_client/src/modules/networks.ts +++ b/packages/grid_client/src/modules/networks.ts @@ -48,9 +48,7 @@ class NetworkModule extends BaseModule { @expose @validateInput async getWireGuardConfigs(options: NetworkGetModel) { - const path = PATH.join(this.config.storePath, this.moduleName, options.name, "info.json"); - const networkInfo = await this.backendStorage.load(path); - return networkInfo["wireguardConfigs"]; + return await this.network.getWireguardConfigs(options.name, options.ipRange); } } diff --git a/packages/grid_client/src/modules/qsfs_zdbs.ts b/packages/grid_client/src/modules/qsfs_zdbs.ts index 4a1dab5354..7fa228cbdb 100644 --- a/packages/grid_client/src/modules/qsfs_zdbs.ts +++ b/packages/grid_client/src/modules/qsfs_zdbs.ts @@ -7,8 +7,8 @@ import { validateInput } from "../helpers/validator"; import { TwinDeployment } from "../high_level/models"; import { ZdbHL } from "../high_level/zdb"; import { ZdbBackend } from "../zos/qsfs"; -import { WorkloadTypes } from "../zos/workload"; -import { ZdbModes } from "../zos/zdb"; +import { Workload, WorkloadTypes } from "../zos/workload"; +import { Zdb, ZdbModes, ZdbResult } from "../zos/zdb"; import { BaseModule } from "./base"; import { QSFSZDBDeleteModel, QSFSZDBGetModel, QSFSZDBSModel } from "./models"; import { checkBalance } from "./utils"; @@ -27,9 +27,10 @@ class QSFSZdbsModule extends BaseModule { throw new ValidationError("QSFS zdbs count can't be less than 3."); } const count = options.count + 4; // 4 zdbs for meta - const twinDeployments = []; - const metadata = JSON.stringify({ - type: "QSFS", + const twinDeployments: TwinDeployment[] = []; + const contractMetadata = JSON.stringify({ + version: 3, + type: "qsfs", name: options.name, projectName: this.config.projectName, }); @@ -47,7 +48,8 @@ class QSFSZdbsModule extends BaseModule { mode as ZdbModes, options.password, true, - options.metadata || metadata, + contractMetadata, + options.metadata, options.description, options.solutionProviderId, ); @@ -91,7 +93,7 @@ class QSFSZdbsModule extends BaseModule { async getZdbs(name: string) { const deployments = await this._get(name); - const zdbs = []; + const zdbs: Workload[] = []; for (const deployment of deployments) { for (const workload of deployment.workloads) { if (workload.type !== WorkloadTypes.zdb) { @@ -102,16 +104,18 @@ class QSFSZdbsModule extends BaseModule { zdbs.push(workload); } } - const qsfsZdbs = { meta: [], groups: [] }; + const qsfsZdbs: { meta: ZdbBackend[]; groups: ZdbBackend[] } = { meta: [], groups: [] }; for (const zdb of zdbs) { + const data = zdb.data as Zdb; + const result = zdb.result.data as ZdbResult; const zdbBackend = new ZdbBackend(); - zdbBackend.namespace = zdb.result.data.Namespace; - zdbBackend.password = zdb.data.password; - zdbBackend.address = `[${zdb.result.data.IPs[1]}]:${zdb.result.data.Port}`; - zdbBackend["size"] = zdb.data.size; + zdbBackend.namespace = result.Namespace; + zdbBackend.password = data.password; + zdbBackend.address = `[${result.IPs[1]}]:${result.Port}`; + zdbBackend["size"] = data.size; zdbBackend["contractId"] = zdb["contractId"]; zdbBackend["nodeId"] = zdb["nodeId"]; - if (zdb.data.mode === ZdbModes.user) { + if (data.mode === ZdbModes.user) { qsfsZdbs.meta.push(zdbBackend); } else { qsfsZdbs.groups.push(zdbBackend); diff --git a/packages/grid_client/src/modules/zdb.ts b/packages/grid_client/src/modules/zdb.ts index 260fce306a..24200bf939 100644 --- a/packages/grid_client/src/modules/zdb.ts +++ b/packages/grid_client/src/modules/zdb.ts @@ -13,7 +13,7 @@ import { AddZDBModel, DeleteZDBModel, ZDBDeleteModel, ZDBGetModel, ZDBSModel } f import { checkBalance } from "./utils"; class ZdbsModule extends BaseModule { - fileName = "zdbs.json"; + moduleName = "zdb"; workloadTypes = [WorkloadTypes.zdb]; zdb: ZdbHL; constructor(public config: GridClientConfig) { @@ -24,10 +24,11 @@ class ZdbsModule extends BaseModule { async _createDeployment(options: ZDBSModel): Promise<TwinDeployment[]> { const twinDeployments: TwinDeployment[] = []; const zdbs_names: string[] = []; - const metadata = JSON.stringify({ + const contractMetadata = JSON.stringify({ + version: 3, type: "zdb", name: options.name, - projectName: this.config.projectName, + projectName: this.config.projectName || `zdb/${options.name}`, }); for (const instance of options.zdbs) { if (zdbs_names.includes(instance.name)) @@ -41,7 +42,8 @@ class ZdbsModule extends BaseModule { instance.mode, instance.password, instance.publicNamespace, - options.metadata || metadata, + contractMetadata, + options.metadata, options.description, instance.solutionProviderId, ); @@ -133,6 +135,12 @@ class ZdbsModule extends BaseModule { throw new ValidationError( `There is another zdb with the same name "${options.name}" in this deployment ${options.deployment_name}.`, ); + const contractMetadata = JSON.stringify({ + version: 3, + type: "zdb", + name: options.deployment_name, + projectName: this.config.projectName || `zdb/${options.name}`, + }); events.emit("logs", `Start adding ZDB instance: ${options.name} to deployment: ${options.deployment_name}`); const twinDeployment = await this.zdb.create( options.name, @@ -141,8 +149,9 @@ class ZdbsModule extends BaseModule { options.mode, options.password, options.publicNamespace, + contractMetadata, oldDeployments[0].metadata, - oldDeployments[0].metadata, + oldDeployments[0].description, options.solutionProviderId, ); diff --git a/packages/grid_client/src/modules/zos.ts b/packages/grid_client/src/modules/zos.ts index a9591d61a7..3821ec5935 100644 --- a/packages/grid_client/src/modules/zos.ts +++ b/packages/grid_client/src/modules/zos.ts @@ -45,7 +45,7 @@ class Zos { } } console.log(`Deploying on node_id: ${node_id} with number of public IPs: ${publicIps}`); - const twinDeployment = new TwinDeployment(deployment, Operations.deploy, publicIps, node_id); + const twinDeployment = new TwinDeployment(deployment, Operations.deploy, publicIps, node_id, deployment.metadata); const twinDeploymentHandler = new TwinDeploymentHandler(this.config); return await twinDeploymentHandler.handle([twinDeployment]); } diff --git a/packages/grid_client/src/primitives/network.ts b/packages/grid_client/src/primitives/network.ts index ae258e60d3..20d4f97737 100644 --- a/packages/grid_client/src/primitives/network.ts +++ b/packages/grid_client/src/primitives/network.ts @@ -8,6 +8,7 @@ import { default as TweetNACL } from "tweetnacl"; import { RMB } from "../clients/rmb/client"; import { TFClient } from "../clients/tf-grid/client"; +import { GqlNodeContract } from "../clients/tf-grid/contracts"; import { GridClientConfig } from "../config"; import { events } from "../helpers/events"; import { formatErrorMessage, generateRandomHexSeed, getRandomNumber, randomChoice } from "../helpers/utils"; @@ -15,6 +16,7 @@ import { validateHexSeed } from "../helpers/validator"; import { MyceliumNetworkModel } from "../modules"; import { DeploymentFactory } from "../primitives/deployment"; import { BackendStorage, BackendStorageType } from "../storage/backend"; +import { Zmachine } from "../zos"; import { Deployment } from "../zos/deployment"; import { Workload, WorkloadTypes } from "../zos/workload"; import { Peer, Znet } from "../zos/znet"; @@ -37,13 +39,26 @@ class AccessPoint { node_id: number; } +export interface UserAccess { + subnet: string; + private_key: string; + node_id: number; +} + +interface NetworkMetadata { + version: number; + user_accesses: UserAccess[]; +} + class Network { capacity: Nodes; nodes: Node[] = []; deployments: Deployment[] = []; reservedSubnets: string[] = []; networks: Znet[] = []; + contracts: Required<GqlNodeContract>[]; accessPoints: AccessPoint[] = []; + userAccesses: UserAccess[] = []; backendStorage: BackendStorage; _endpoints: Record<string, string> = {}; _accessNodes: number[] = []; @@ -82,31 +97,54 @@ class Network { } } - async addAccess(node_id: number, ipv4: boolean): Promise<string> { - if (!this.nodeExists(node_id)) { - throw new ValidationError(`Node ${node_id} does not exist in the network. Please add it first.`); + private getUpdatedMetadata(nodeId: number, metadata: string): string { + for (const node of this.nodes) { + if (node.node_id === nodeId) { + const parsedMetadata: NetworkMetadata = JSON.parse(metadata || "{}"); + parsedMetadata.version = 3; + parsedMetadata.user_accesses = this.userAccesses; + return JSON.stringify(parsedMetadata); + } } - events.emit("logs", `Adding access to node ${node_id}`); + return metadata; + } + + updateWorkload(nodeId: number, workload: Workload): Workload { + workload.data = this.getUpdatedNetwork(workload.data); + workload.metadata = this.getUpdatedMetadata(nodeId, workload.metadata); + return workload; + } + + async getAccessNodeEndpoint(nodeId: number, ipv4 = true): Promise<string> { const accessNodes = await this.capacity.getAccessNodes(this.config.twinId); - if (Object.keys(accessNodes).includes(node_id.toString())) { - if (ipv4 && !accessNodes[node_id]["ipv4"]) { - throw new GridClientErrors.Nodes.InvalidResourcesError(`Node ${node_id} does not have ipv4 public config.`); + if (Object.keys(accessNodes).includes(nodeId.toString())) { + if (ipv4 && !accessNodes[nodeId]["ipv4"]) { + throw new GridClientErrors.Nodes.InvalidResourcesError(`Node ${nodeId} does not have ipv4 public config.`); } } else { - throw new GridClientErrors.Nodes.AccessNodeError(`Node ${node_id} is not an access node.`); + throw new GridClientErrors.Nodes.AccessNodeError(`Node ${nodeId} is not an access node.`); } - const nodeWGListeningPort = this.getNodeWGListeningPort(node_id); + const nodeWGListeningPort = this.getNodeWGListeningPort(nodeId); let endpoint = ""; - if (accessNodes[node_id]["ipv4"]) { - endpoint = `${accessNodes[node_id]["ipv4"].split("/")[0]}:${nodeWGListeningPort}`; - } else if (accessNodes[node_id]["ipv6"]) { - endpoint = `[${accessNodes[node_id]["ipv6"].split("/")[0]}]:${nodeWGListeningPort}`; + if (accessNodes[nodeId]["ipv4"]) { + endpoint = `${accessNodes[nodeId]["ipv4"].split("/")[0]}:${nodeWGListeningPort}`; + } else if (accessNodes[nodeId]["ipv6"]) { + endpoint = `[${accessNodes[nodeId]["ipv6"].split("/")[0]}]:${nodeWGListeningPort}`; } else { throw new GridClientErrors.Nodes.InvalidResourcesError( - `Couldn't find ipv4 or ipv6 in the public config of node ${node_id}.`, + `Couldn't find ipv4 or ipv6 in the public config of node ${nodeId}.`, ); } + return endpoint; + } + + async addAccess(node_id: number, ipv4: boolean): Promise<string> { + if (!this.nodeExists(node_id)) { + throw new ValidationError(`Node ${node_id} does not exist in the network. Please add it first.`); + } + events.emit("logs", `Adding access to node ${node_id}`); + const endpoint = await this.getAccessNodeEndpoint(node_id, ipv4); const nodesWGPubkey = await this.getNodeWGPublicKey(node_id); const keypair = this.generateWireguardKeypair(); @@ -118,6 +156,11 @@ class Network { this.accessPoints.push(accessPoint); await this.generatePeers(); this.updateNetworkDeployments(); + this.userAccesses.push({ + node_id, + private_key: keypair.privateKey, + subnet: accessPoint.subnet, + }); this.wireguardConfig = this.getWireguardConfig(accessPoint.subnet, keypair.privateKey, nodesWGPubkey, endpoint); return this.wireguardConfig; } @@ -147,7 +190,7 @@ class Network { hex_key: seed, peers: [], }; - this.updateNetwork(network); + this.getUpdatedNetwork(network); this.updateNetworkDeployments(); const deploymentFactory = new DeploymentFactory(this.config); @@ -157,7 +200,7 @@ class Network { const d = await deploymentFactory.fromObj(deployment); for (const workload of d["workloads"]) { workload.data["mycelium"]["hex_key"] = seed; - workload.data = this.updateNetwork(workload["data"]); + workload.data = this.getUpdatedNetwork(workload["data"]); workload.version += 1; } return d; @@ -169,7 +212,6 @@ class Network { async addNode( nodeId: number, mycelium: boolean, - metadata = "", description = "", subnet = "", myceliumSeeds: MyceliumNetworkModel[] = [], @@ -208,14 +250,14 @@ class Network { this.networks.push(znet); await this.generatePeers(); this.updateNetworkDeployments(); - znet = this.updateNetwork(znet); + znet = this.getUpdatedNetwork(znet); const znet_workload = new Workload(); znet_workload.version = 0; znet_workload.name = this.name; znet_workload.type = WorkloadTypes.network; znet_workload.data = znet; - znet_workload.metadata = metadata; + znet_workload.metadata = ""; znet_workload.description = description; const node = new Node(); @@ -247,7 +289,7 @@ class Network { return contract_id; } - updateNetwork(znet): Znet { + getUpdatedNetwork(znet): Znet { for (const net of this.networks) { if (net.subnet === znet.subnet) { return net; @@ -279,47 +321,95 @@ class Network { return; } events.emit("logs", `Loading network ${this.name}`); - const network = await this.getNetwork(); - if (network["ip_range"] !== this.ipRange) { - throw new ValidationError(`The same network name ${this.name} with a different ip range already exists.`); + + if (await this.existOnNewNetwork()) { + await this.loadNetworkFromContracts(); + } else { + const network = await this.getNetwork(); + if (network["ip_range"] !== this.ipRange) { + throw new ValidationError(`The same network name ${this.name} with a different ip range already exists.`); + } + + await this.tfClient.connect(); + for (const node of network["nodes"]) { + const contract = await this.tfClient.contracts.get({ + id: node.contract_id, + }); + if (contract === null) continue; + const node_twin_id = await this.capacity.getNodeTwinId(node.node_id); + const payload = JSON.stringify({ contract_id: node.contract_id }); + let res; + try { + res = await this.rmb.request([node_twin_id], "zos.deployment.get", payload); + } catch (e) { + (e as Error).message = formatErrorMessage(`Failed to load network deployment ${node.contract_id}`, e); + throw e; + } + res["node_id"] = node.node_id; + for (const workload of res["workloads"]) { + if ( + workload["type"] !== WorkloadTypes.network || + !Addr(this.ipRange).contains(Addr(workload["data"]["subnet"])) + ) { + continue; + } + if (workload.result.state === "deleted") { + continue; + } + const znet = this._fromObj(workload["data"]); + znet["node_id"] = node.node_id; + const n: Node = node; + this.nodes.push(n); + this.networks.push(znet); + this.deployments.push(res); + } + } + await this.getAccessPoints(); + await this.save(); } + } - await this.tfClient.connect(); - for (const node of network["nodes"]) { - const contract = await this.tfClient.contracts.get({ - id: node.contract_id, - }); - if (contract === null) continue; - const node_twin_id = await this.capacity.getNodeTwinId(node.node_id); - const payload = JSON.stringify({ contract_id: node.contract_id }); - let res; + private async loadNetworkFromContracts() { + const contracts = await this.getDeploymentContracts(this.name); + for (const contract of contracts) { + const node_twin_id = await this.capacity.getNodeTwinId(contract.nodeID); + const payload = JSON.stringify({ contract_id: +contract.contractID }); + let res: Deployment; try { res = await this.rmb.request([node_twin_id], "zos.deployment.get", payload); } catch (e) { - (e as Error).message = formatErrorMessage(`Failed to load network deployment ${node.contract_id}`, e); + (e as Error).message = formatErrorMessage(`Failed to load network deployment ${contract.contractID}`, e); throw e; } - res["node_id"] = node.node_id; - for (const workload of res["workloads"]) { - if ( - workload["type"] !== WorkloadTypes.network || - !Addr(this.ipRange).contains(Addr(workload["data"]["subnet"])) - ) { + res["node_id"] = contract.nodeID; + for (const workload of res.workloads) { + const data = workload.data as Znet; + if (workload.type !== WorkloadTypes.network || workload.name !== this.name) { continue; } if (workload.result.state === "deleted") { continue; } - const znet = this._fromObj(workload["data"]); - znet["node_id"] = node.node_id; - const n: Node = node; + const znet = this._fromObj(data); + znet["node_id"] = contract.nodeID; + const parsedMetadata: NetworkMetadata = JSON.parse(workload.metadata); + const reservedIps = await this.getReservedIps(contract.nodeID); + + if (znet.ip_range !== this.ipRange) { + throw new ValidationError(`The same network name ${this.name} with a different ip range already exists.`); + } + this.userAccesses = parsedMetadata.user_accesses; + const n: Node = { + contract_id: +contract.contractID, + node_id: contract.nodeID, + reserved_ips: reservedIps, + }; this.nodes.push(n); this.networks.push(znet); this.deployments.push(res); } } await this.getAccessPoints(); - await this.save(); } _fromObj(net: Znet): Znet { @@ -537,9 +627,63 @@ class Network { return await this.backendStorage.load(PATH.join(path, this.name, "info.json")); } + private async getMyNetworkContracts(fetch = false) { + if (fetch || !this.contracts) { + const contracts = await this.tfClient.contracts.listMyNodeContracts({ + graphqlURL: this.config.graphqlURL, + type: "network", + }); + + const parsedContracts: Required<GqlNodeContract>[] = []; + + for (const contract of contracts) { + const parsedDeploymentData = JSON.parse(contract.deploymentData); + parsedContracts.push({ ...contract, parsedDeploymentData }); + } + + this.contracts = parsedContracts; + } + + return this.contracts; + } + + private async getReservedIps(nodeId: number): Promise<string[]> { + const node_twin_id = await this.capacity.getNodeTwinId(nodeId); + const payload = JSON.stringify({ network_name: this.name }); + let reservedIps: string[]; + try { + reservedIps = await this.rmb.request([node_twin_id], "zos.network.list_private_ips", payload); + } catch (e) { + (e as Error).message = formatErrorMessage(`Failed to list reserved ips from node ${nodeId}`, e); + throw e; + } + return reservedIps; + } + + private async getDeploymentContracts(name: string) { + const contracts = await this.getMyNetworkContracts(); + return contracts.filter(c => c.parsedDeploymentData.name === name); + } + + private getContractsName(contracts: Required<GqlNodeContract>[]): string[] { + return Array.from(new Set(contracts.map(c => c.parsedDeploymentData.name))); + } + + private async listNewNetworks() { + const contracts = await this.getMyNetworkContracts(true); + return this.getContractsName(contracts); + } + + private async existOnNewNetwork() { + return (await this.listNewNetworks()).includes(this.name); + } + async getNetworkNames(): Promise<string[]> { + const newNames = await this.listNewNetworks(); + const path = this.getNetworksPath(); - return await this.backendStorage.list(path); + const oldNames = await this.backendStorage.list(path); + return Array.from(new Set([...newNames, ...oldNames])); } async getFreePort(node_id: number): Promise<number> { @@ -628,56 +772,11 @@ AllowedIPs = ${this.ipRange}, ${networkIP} PersistentKeepalive = 25\nEndpoint = ${endpoint}`; } - async save(contract_id = 0, node_id = 0) { - let network; - if (await this.exists()) { - network = await this.getNetwork(); - } else { - network = { - ip_range: this.ipRange, - nodes: [], - wireguardConfigs: [], - }; - } - - if (this.wireguardConfig && !network.wireguardConfigs.includes(this.wireguardConfig)) { - network.wireguardConfigs.push(this.wireguardConfig); - } - + async save() { if (this.nodes.length === 0) { await this.delete(); return; } - - const nodes = []; - for (const node of this.nodes) { - if (!node.contract_id && node.node_id === node_id) { - node.contract_id = contract_id; - } - if (!node.contract_id) { - continue; - } - nodes.push({ - contract_id: node.contract_id, - node_id: node.node_id, - reserved_ips: this.getNodeReservedIps(node.node_id), - }); - } - network.nodes = nodes; - if (nodes.length !== 0) { - await this._save(network); - } else { - await this.delete(); - } - } - - async _save(network): Promise<void> { - const path = PATH.join(this.getNetworksPath(), this.name, "info.json"); - const current = await this.backendStorage.load(path); - if (JSON.stringify(current) !== JSON.stringify(network)) { - const updateOperations = await this.backendStorage.dump(path, network); - await this.saveIfKVStoreBackend(updateOperations); - } } async delete(): Promise<void> { diff --git a/packages/playground/src/components/icon_action_btn.vue b/packages/playground/src/components/icon_action_btn.vue index 857bf748c8..e5b5cd25f9 100644 --- a/packages/playground/src/components/icon_action_btn.vue +++ b/packages/playground/src/components/icon_action_btn.vue @@ -10,6 +10,7 @@ variant="tonal" :height="height" class="my-1 mr-1" + :disabled="disabled" > <v-icon> {{ icon }}</v-icon> </v-btn> @@ -24,6 +25,7 @@ defineProps<{ color?: string; href?: string; height?: string; + disabled?: boolean; }>(); defineEmits(["click"]); </script> diff --git a/packages/playground/src/components/k8s_deployment_table.vue b/packages/playground/src/components/k8s_deployment_table.vue index a0b644f278..53443644c1 100644 --- a/packages/playground/src/components/k8s_deployment_table.vue +++ b/packages/playground/src/components/k8s_deployment_table.vue @@ -44,8 +44,8 @@ }} <template v-if="deployment.contracts && deployment.contracts.length > 0"> with contract id: - <span v-for="contract in deployment.contracts" :key="contract.contract_id"> - {{ contract.contract_id }} . + <span v-for="contract in deployment.contracts" :key="contract.contractID"> + {{ contract.contractID }} . </span> </template> </li> @@ -59,6 +59,10 @@ <AccessDeploymentAlert /> + <InputTooltip tooltip="List all deployments, including those created outside the Dashboard." inline> + <VSwitch inset color="primary" label="Show All Deployments" v-model="showAllDeployments" /> + </InputTooltip> + <ListTable :headers="[ { title: 'PLACEHOLDER', key: 'data-table-select' }, @@ -72,7 +76,7 @@ { title: 'Health', key: 'status', sortable: false }, { title: 'Actions', key: 'actions', sortable: false }, ]" - :items="items" + :items="showAllDeployments ? items : items.filter(i => !i.fromAnotherClient)" :loading="loading" :deleting="deleting" :model-value="$props.modelValue" @@ -128,10 +132,12 @@ import { getNodeHealthColor, NodeHealth } from "@/utils/get_nodes"; import { useProfileManager } from "../stores"; import { getGrid, updateGrid } from "../utils/grid"; +import { markAsFromAnotherClient } from "../utils/helpers"; import { loadK8s, mergeLoadedDeployments } from "../utils/load_deployment"; const profileManager = useProfileManager(); const showDialog = ref(false); const showEncryption = ref(false); +const showAllDeployments = ref(false); const failedDeployments = ref<any[]>([]); const props = defineProps<{ @@ -144,7 +150,6 @@ defineEmits<{ (event: "update:model-value", value: any[]): void }>(); const count = ref<number>(); const items = ref<any[]>([]); const loading = ref(false); -const namesOfFailedDeployments = ref(""); onMounted(loadDeployments); async function loadDeployments() { @@ -155,6 +160,10 @@ async function loadDeployments() { const chunk2 = await loadK8s(updateGrid(grid!, { projectName: props.projectName.toLowerCase() })); const chunk3 = await loadK8s(updateGrid(grid!, { projectName: "" })); + chunk3.items = chunk3.items.map(i => { + return !i.projectName || i.projectName === "Kubernetes" ? markAsFromAnotherClient(i) : i; + }); + const clusters = mergeLoadedDeployments(chunk1, chunk2, chunk3); failedDeployments.value = [ ...(Array.isArray((chunk1 as any).failedDeployments) ? (chunk1 as any).failedDeployments : []), diff --git a/packages/playground/src/components/vm_deployment_table.vue b/packages/playground/src/components/vm_deployment_table.vue index 09c214faeb..f68c27d16b 100644 --- a/packages/playground/src/components/vm_deployment_table.vue +++ b/packages/playground/src/components/vm_deployment_table.vue @@ -48,9 +48,17 @@ <AccessDeploymentAlert /> + <InputTooltip + v-if="props.projectName.toLowerCase() === 'vm'" + tooltip="List all deployments, including those created outside the Dashboard." + inline + > + <VSwitch inset color="primary" label="Show All Deployments" v-model="showAllDeployments" /> + </InputTooltip> + <ListTable :headers="filteredHeaders" - :items="items" + :items="showAllDeployments ? items : items.filter(i => !i.fromAnotherClient)" :loading="loading" :deleting="deleting" :model-value="$props.modelValue" @@ -75,7 +83,7 @@ </template> <template #[`item.wireguard`]="{ item }"> - {{ item.value.interfaces[0].ip || "-" }} + {{ item.value.interfaces?.[0]?.ip || "-" }} </template> <template #[`item.flist`]="{ item }"> @@ -139,6 +147,7 @@ import { getNodeHealthColor, NodeHealth } from "@/utils/get_nodes"; import { useProfileManager } from "../stores"; import { getGrid, updateGrid } from "../utils/grid"; +import { markAsFromAnotherClient } from "../utils/helpers"; import { loadVms, mergeLoadedDeployments } from "../utils/load_deployment"; const profileManager = useProfileManager(); @@ -155,11 +164,12 @@ const count = ref<number>(); const items = ref<any[]>([]); const showDialog = ref(false); const showEncryption = ref(false); +const showAllDeployments = ref(false); const failedDeployments = ref< { name: string; nodes?: number[]; - contracts?: { contract_id: number; node_id: number }[]; + contracts?: { contractID: number; node_id: number }[]; }[] >([]); @@ -181,19 +191,17 @@ async function loadDeployments() { await migrateModule(grid!.gateway); } - const filter = + const chunk3 = props.projectName.toLowerCase() === ProjectName.VM.toLowerCase() - ? undefined - : ([vm]: [{ flist: string }]) => vm.flist.replace(/-/g, "").includes(props.projectName.toLowerCase()); + ? await loadVms(updateGrid(grid!, { projectName: "" })) + : { count: 0, items: [] }; - const chunk3 = - props.projectName.toLowerCase() === ProjectName.Fullvm.toLowerCase() - ? { count: 0, items: [] } - : await loadVms(updateGrid(grid!, { projectName: "" }), { filter }); if (chunk3.count > 0 && migrateGateways) { await migrateModule(grid!.gateway); } + chunk3.items = chunk3.items.map(markAsFromAnotherClient); + const vms = mergeLoadedDeployments(chunk1, chunk2, chunk3 as any); failedDeployments.value = [ ...(Array.isArray((chunk1 as any).failedDeployments) ? (chunk1 as any).failedDeployments : []), @@ -272,7 +280,7 @@ const failedDeploymentList = computed(() => { const contractMessage = contracts.length > 0 ? ` <span class="ml-4 text-primary font-weight-bold">With Contract ID:</span> ${contracts - .map(c => c.contract_id) + .map(c => c.contractID) .join(", ")}.` : ""; diff --git a/packages/playground/src/utils/deploy_k8s.ts b/packages/playground/src/utils/deploy_k8s.ts index a18dc941c4..1f47a4d1ea 100644 --- a/packages/playground/src/utils/deploy_k8s.ts +++ b/packages/playground/src/utils/deploy_k8s.ts @@ -28,7 +28,7 @@ export async function deployK8s(grid: GridClient, options: DeployK8SOptions) { k8s.ssh_key = options.sshKey; await grid.k8s.deploy(k8s); const data = (await loadK8S(grid, k8s.name)) as { masters: any[]; workers: any[]; wireguard?: string }; - const wireguard = await getWireguardConfig(grid, k8s.network.name).catch(() => []); + const wireguard = await getWireguardConfig(grid, k8s.network.name, k8s.network.ip_range).catch(() => []); data.wireguard = wireguard[0]; return data; } diff --git a/packages/playground/src/utils/deploy_vm.ts b/packages/playground/src/utils/deploy_vm.ts index 8f48651376..03ec474b25 100644 --- a/packages/playground/src/utils/deploy_vm.ts +++ b/packages/playground/src/utils/deploy_vm.ts @@ -30,7 +30,7 @@ export async function loadVM(grid: GridClient, name: string) { const vm = (await grid.machines.getObj(name)) as any; vm.deploymentName = name; vm.projectName = grid.clientOptions.projectName; - const wireguard = await getWireguardConfig(grid, vm[0].interfaces[0].network).catch(() => []); + const wireguard = await getWireguardConfig(grid, vm[0].interfaces[0].network, vm[0].interfaces[0].ip).catch(() => []); vm.wireguard = wireguard[0]; return vm; } diff --git a/packages/playground/src/utils/helpers.ts b/packages/playground/src/utils/helpers.ts index 367388570d..b44d036627 100644 --- a/packages/playground/src/utils/helpers.ts +++ b/packages/playground/src/utils/helpers.ts @@ -75,3 +75,14 @@ export function toGigaBytes(value?: number) { const gb = value / giga; return parseFloat(gb.toFixed(2)); } + +export function markAsFromAnotherClient(deployment: any): any { + deployment.fromAnotherClient = true; + if (Array.isArray(deployment)) { + deployment.map(t => { + t.fromAnotherClient = true; + return t; + }); + } + return deployment; +} diff --git a/packages/playground/src/utils/load_deployment.ts b/packages/playground/src/utils/load_deployment.ts index c0d53369df..5a5b157a07 100644 --- a/packages/playground/src/utils/load_deployment.ts +++ b/packages/playground/src/utils/load_deployment.ts @@ -61,7 +61,13 @@ export async function loadVms(grid: GridClient, options: LoadVMsOptions = {}) { return; } - const machinePromise = grids[index].machines.getObj(name); + const machinePromise = grids[index].machines.getObj(name).then(res => { + if (!projectName && (!Array.isArray(res) || res.length === 0)) { + grids[index] = updateGrid(grids[index], { projectName: "" }); + return grids[index].machines.getObj(name); + } + return res; + }); const timeoutPromise = new Promise((resolve, reject) => { setTimeout(() => { reject(new Error("Timeout")); @@ -117,7 +123,9 @@ export async function loadVms(grid: GridClient, options: LoadVMsOptions = {}) { }), ); const wireguards = await Promise.all( - vms.map((vm, index) => getWireguardConfig(grids[index], vm[0].interfaces[0].network).catch(() => [])), + vms.map((vm, index) => + getWireguardConfig(grids[index], vm[0].interfaces[0].network, vm[0].interfaces[0].ip).catch(() => []), + ), ); const data = vms.map((vm, index) => { @@ -135,10 +143,15 @@ export async function loadVms(grid: GridClient, options: LoadVMsOptions = {}) { failedDeployments, }; } -export function getWireguardConfig(grid: GridClient, name: string) { +export function getWireguardConfig(grid: GridClient, name: string, ipRange: string) { const projectName = grid.clientOptions!.projectName; + if (!ipRange.endsWith("/16")) { + const parts = ipRange.split("."); + parts[2] = parts[3] = "0"; + ipRange = parts.join(".") + "/16"; + } return updateGrid(grid, { projectName: "" }) - .networks.getWireGuardConfigs({ name }) + .networks.getWireGuardConfigs({ name, ipRange }) .finally(() => updateGrid(grid, { projectName })); } @@ -151,7 +164,7 @@ export async function loadK8s(grid: GridClient) { const projectName = grid.clientOptions.projectName; const grids = (await Promise.all( - clusters.map(n => getGrid(grid.clientOptions, `${projectName}/${n}`)), + clusters.map(n => getGrid(grid.clientOptions, projectName ? `${projectName}/${n}` : n)), )) as GridClient[]; const failedDeployments: FailedDeployment[] = []; @@ -168,7 +181,14 @@ export async function loadK8s(grid: GridClient) { } try { - const clusterPromise = grids[index].k8s.getObj(name); + const clusterPromise = grids[index].k8s.getObj(name).then(res => { + if (!projectName && res && res.masters && res.masters.length === 0) { + grids[index] = updateGrid(grids[index], { projectName: "" }); + return grids[index].k8s.getObj(name); + } + + return res; + }); const timeoutPromise = new Promise((resolve, reject) => { setTimeout(() => { reject(new Error("Timeout")); @@ -211,7 +231,11 @@ export async function loadK8s(grid: GridClient) { const wireguards = await Promise.all( k8s.map((cluster, index) => - getWireguardConfig(grids[index], cluster.masters[0].interfaces[0].network).catch(() => []), + getWireguardConfig( + grids[index], + cluster.masters[0].interfaces[0].network, + cluster.masters[0].interfaces[0].ip, + ).catch(() => []), ), ); const data = k8s.map((cluster, index) => { @@ -233,10 +257,24 @@ export async function loadK8s(grid: GridClient) { export function mergeLoadedDeployments<T>(...deployments: LoadedDeployments<T>[]) { return deployments.reduce( (res, current) => { - res.count += current.count; - res.items = res.items.concat(current.items); + insertIfNotFound(current, res); + res.count = res.items.length; return res; }, { count: 0, items: [] }, ); } + +function insertIfNotFound(newItems: LoadedDeployments<any>, oldItems: LoadedDeployments<any>) { + for (const item of newItems.items) { + let found = false; + for (const i of oldItems.items) { + if (item.deploymentName === i.deploymentName) { + found = true; + } + } + if (!found) { + oldItems.items.push(item); + } + } +} diff --git a/packages/playground/src/utils/nodeSelector.ts b/packages/playground/src/utils/nodeSelector.ts index ebb9f2343f..c91eecff4f 100644 --- a/packages/playground/src/utils/nodeSelector.ts +++ b/packages/playground/src/utils/nodeSelector.ts @@ -162,7 +162,7 @@ export function normalizeNodeOptions( size: window.env.PAGE_SIZE, page: pagination.value.page, location: location || {}, - twinId: gridStore.client.twinId, + twinId: gridStore.client?.twinId, farm, }; } diff --git a/packages/playground/src/weblets/tf_deployment_list.vue b/packages/playground/src/weblets/tf_deployment_list.vue index 5dcde95dd8..cbb7c51eb3 100644 --- a/packages/playground/src/weblets/tf_deployment_list.vue +++ b/packages/playground/src/weblets/tf_deployment_list.vue @@ -42,7 +42,12 @@ @click="openDialog(tabs[activeTab].value, item)" /> - <IconActionBtn icon="mdi-cog" tooltip="Manage Domains" @click="dialog = item.value.deploymentName" /> + <IconActionBtn + icon="mdi-cog" + tooltip="Manage Domains" + :disabled="item.value.fromAnotherClient" + @click="dialog = item.value.deploymentName" + /> <ManageGatewayDialog v-if="dialog === item.value.deploymentName" @@ -306,7 +311,13 @@ icon="mdi-eye-outline" @click="openDialog(tabs[activeTab].value, item)" /> - <IconActionBtn icon="mdi-cog" tooltip="Manage Workers" @click="dialog = item.value.deploymentName" /> + + <IconActionBtn + icon="mdi-cog" + :disabled="item.value.fromAnotherClient" + tooltip="Manage Workers" + @click="dialog = item.value.deploymentName" + /> <ManageK8SWorkerDialog v-if="dialog === item.value.deploymentName" diff --git a/packages/playground/src/weblets/tf_kubernetes.vue b/packages/playground/src/weblets/tf_kubernetes.vue index b3cf24d186..a9286ec2bd 100644 --- a/packages/playground/src/weblets/tf_kubernetes.vue +++ b/packages/playground/src/weblets/tf_kubernetes.vue @@ -86,6 +86,7 @@ import { createWorker } from "../components/k8s_worker.vue"; import { useLayout } from "../components/weblet_layout.vue"; import { useProfileManager } from "../stores"; import type { K8SWorker as K8sWorker } from "../types"; +import { ProjectName } from "../types"; import { deployK8s } from "../utils/deploy_k8s"; import { getGrid } from "../utils/grid"; import { generateName, generatePassword } from "../utils/strings"; @@ -107,7 +108,8 @@ async function deploy() { try { layout.value?.validateSSH(); - const grid = await getGrid(profileManager.profile!, name.value); + const projectName = ProjectName.Kubernetes.toLowerCase() + "/" + name.value; + const grid = await getGrid(profileManager.profile!, projectName); await layout.value.validateBalance(grid!); diff --git a/packages/weblets/src/utils/getWireguardConfig.ts b/packages/weblets/src/utils/getWireguardConfig.ts index 7170fe91be..6e5aeaf03d 100644 --- a/packages/weblets/src/utils/getWireguardConfig.ts +++ b/packages/weblets/src/utils/getWireguardConfig.ts @@ -9,8 +9,11 @@ export default async function getWireguardConfig(network: NetworkGetModel) { const profile = get(window.configs.baseConfig); if (!profile) return; const client = await getGrid(profile as unknown as IProfile, c => c, ""); + const parts = network.ipRange.split("."); + parts[2] = parts[3] = "0"; + network.ipRange = parts.join(".") + "/16"; - const wireguard = await client.networks.getWireGuardConfigs({ name: network.name }); + const wireguard = await client.networks.getWireGuardConfigs({ name: network.name, ipRange: network.ipRange }); return wireguard; } catch (error) { console.log("error", error);