diff --git a/packages/grid_client/tests/modules/applications/gittea.test.ts b/packages/grid_client/tests/modules/applications/gittea.test.ts new file mode 100644 index 0000000000..10540c663b --- /dev/null +++ b/packages/grid_client/tests/modules/applications/gittea.test.ts @@ -0,0 +1,224 @@ +import axios from "axios"; +import { setTimeout } from "timers/promises"; + +import { FilterOptions, GatewayNameModel, generateString, GridClient, MachinesModel, randomChoice } from "../../../src"; +import { config, getClient } from "../../client_loader"; +import { GBToBytes, generateInt, getOnlineNode, log, splitIP } from "../../utils"; + +jest.setTimeout(1250000); + +let gridClient: GridClient; +let deploymentName: string; + +beforeAll(async () => { + gridClient = await getClient(); + deploymentName = "gt" + generateString(10); + gridClient.clientOptions.projectName = `gitea/${deploymentName}`; + gridClient._connect(); + return gridClient; +}); + +// Private IP Regex +const ipRegex = /(^127\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)/; + +test("TCXXXX - Applications: Deploy Gitea", async () => { + // Test Data + const name = "gw" + generateString(10).toLowerCase(); + const tlsPassthrough = false; + const cpu = 2; + const memory = 4; + const rootfsSize = 2; + const diskSize = 50; + const networkName = generateString(15); + const vmName = generateString(15); + const diskName = generateString(15); + const mountPoint = "/mnt/data"; + const publicIp = false; + const ipRangeClassA = "10." + generateInt(1, 255) + ".0.0/16"; + const ipRangeClassB = "172." + generateInt(16, 31) + ".0.0/16"; + const ipRangeClassC = "192.168.0.0/16"; + const ipRange = randomChoice([ipRangeClassA, ipRangeClassB, ipRangeClassC]); + const metadata = "{'deploymentType': 'gitea'}"; + const description = "Test deploying Gitea via ts grid3 client"; + + // GatewayNode Selection + const gatewayNodes = await gridClient.capacity.filterNodes({ + gateway: true, + farmId: 1, + availableFor: await gridClient.twins.get_my_twin_id(), + } as FilterOptions); + if (gatewayNodes.length == 0) throw new Error("No nodes available to complete this test"); + const GatewayNode = gatewayNodes[generateInt(0, gatewayNodes.length - 1)]; + + // Node Selection + const nodes = await gridClient.capacity.filterNodes({ + cru: cpu, + mru: memory, + sru: rootfsSize + diskSize, + farmId: 1, + availableFor: await gridClient.twins.get_my_twin_id(), + } as FilterOptions); + const nodeId = await getOnlineNode(nodes); + if (nodeId == -1) throw new Error("No nodes available to complete this test"); + const domain = name + "." + GatewayNode.publicConfig.domain; + + // VM Model + const vms: MachinesModel = { + name: deploymentName, + network: { + name: networkName, + ip_range: ipRange, + }, + machines: [ + { + name: vmName, + node_id: nodeId, + cpu: cpu, + memory: 1024 * memory, + rootfs_size: rootfsSize, + disks: [ + { + name: diskName, + size: diskSize, + mountpoint: mountPoint, + }, + ], + flist: "https://hub.grid.tf/tf-official-apps/gitea-mycelium.flist", + entrypoint: "/sbin/zinit init", + public_ip: publicIp, + planetary: true, + mycelium: true, + env: { + SSH_KEY: config.ssh_key, + GITEA__HOSTNAME: domain, + GITEA__mailer__PROTOCOL: "smtp", + GITEA__mailer__ENABLED: "true", + GITEA__mailer__HOST: "smtp.example.com", + GITEA__mailer__FROM: "admin@example.com", + GITEA__mailer__PORT: "25", + GITEA__mailer__USER: "admin", + GITEA__mailer__PASSWD: "123456", + }, + }, + ], + metadata: metadata, + description: description, + }; + const res = await gridClient.machines.deploy(vms); + log(res); + + // Contracts Assertions + expect(res.contracts.created).toHaveLength(1); + expect(res.contracts.updated).toHaveLength(0); + expect(res.contracts.deleted).toHaveLength(0); + + const vmsList = await gridClient.machines.list(); + log(vmsList); + + // VM List Assertions + expect(vmsList.length).toBeGreaterThanOrEqual(1); + expect(vmsList).toContain(vms.name); + + const result = await gridClient.machines.getObj(vms.name); + log(result); + + // VM Assertions + expect(result[0].nodeId).toBe(nodeId); + expect(result[0].status).toBe("ok"); + expect(result[0].flist).toBe(vms.machines[0].flist); + expect(result[0].entrypoint).toBe(vms.machines[0].entrypoint); + expect(result[0].mounts).toHaveLength(1); + expect(result[0].interfaces[0]["network"]).toBe(vms.network.name); + expect(result[0].interfaces[0]["ip"]).toContain(splitIP(vms.network.ip_range)); + expect(result[0].interfaces[0]["ip"]).toMatch(ipRegex); + expect(result[0].capacity["cpu"]).toBe(cpu); + expect(result[0].capacity["memory"]).toBe(memory * 1024); + expect(result[0].planetary).toBeDefined(); + expect(result[0].myceliumIP).toBeDefined(); + expect(result[0].publicIP).toBeNull(); + expect(result[0].description).toBe(description); + expect(result[0].mounts[0]["name"]).toBe(diskName); + expect(result[0].mounts[0]["size"]).toBe(GBToBytes(diskSize)); + expect(result[0].mounts[0]["mountPoint"]).toBe(mountPoint); + expect(result[0].mounts[0]["state"]).toBe("ok"); + + const backends = ["http://[" + result[0].planetary + "]:3000"]; + log(backends); + + // Name Gateway Model + const gw: GatewayNameModel = { + name: name, + node_id: GatewayNode.nodeId, + tls_passthrough: tlsPassthrough, + backends: backends, + }; + + const gatewayRes = await gridClient.gateway.deploy_name(gw); + log(gatewayRes); + + // Contracts Assertions + expect(gatewayRes.contracts.created).toHaveLength(1); + expect(gatewayRes.contracts.updated).toHaveLength(0); + expect(gatewayRes.contracts.deleted).toHaveLength(0); + expect(gatewayRes.contracts.created[0].contractType.nodeContract.nodeId).toBe(GatewayNode.nodeId); + + const gatewayResult = await gridClient.gateway.getObj(gw.name); + log(gatewayResult); + + // Gateway Assertions + expect(gatewayResult[0].name).toBe(name); + expect(gatewayResult[0].status).toBe("ok"); + expect(gatewayResult[0].type).toContain("name"); + expect(gatewayResult[0].domain).toContain(name); + expect(gatewayResult[0].tls_passthrough).toBe(tlsPassthrough); + expect(gatewayResult[0].backends).toStrictEqual(backends); + + const site = "http://" + gatewayResult[0].domain; + let reachable = false; + + for (let i = 0; i <= 250; i++) { + const wait = await setTimeout(5000, "Waiting for gateway to be ready"); + log(wait); + + await axios + .get(site) + .then(res => { + log("gateway is reachable"); + log(res.status); + log(res.statusText); + log(res.data); + expect(res.status).toBe(200); + reachable = true; + }) + .catch(() => { + log("gateway is not reachable"); + }); + if (reachable) { + break; + } else if (i === 250) { + throw new Error("Gateway is unreachable after multiple retries"); + } + } +}); + +afterAll(async () => { + const vmNames = await gridClient.machines.list(); + for (const name of vmNames) { + const res = await gridClient.machines.delete({ name }); + log(res); + expect(res.created).toHaveLength(0); + expect(res.updated).toHaveLength(0); + expect(res.deleted).toBeDefined(); + } + + const gwNames = await gridClient.gateway.list(); + for (const name of gwNames) { + const res = await gridClient.gateway.delete_name({ name }); + log(res); + expect(res.created).toHaveLength(0); + expect(res.updated).toHaveLength(0); + expect(res.deleted).toBeDefined(); + } + + return await gridClient.disconnect(); +}, 130000); diff --git a/packages/grid_client/tests/modules/applications/jenkins.test.ts b/packages/grid_client/tests/modules/applications/jenkins.test.ts new file mode 100644 index 0000000000..194f430f98 --- /dev/null +++ b/packages/grid_client/tests/modules/applications/jenkins.test.ts @@ -0,0 +1,219 @@ +import axios from "axios"; +import { setTimeout } from "timers/promises"; + +import { FilterOptions, GatewayNameModel, generateString, GridClient, MachinesModel, randomChoice } from "../../../src"; +import { config, getClient } from "../../client_loader"; +import { GBToBytes, generateInt, getOnlineNode, log, splitIP } from "../../utils"; + +jest.setTimeout(1250000); + +let gridClient: GridClient; +let deploymentName: string; + +beforeAll(async () => { + gridClient = await getClient(); + deploymentName = "jk" + generateString(10); + gridClient.clientOptions.projectName = `jenkins/${deploymentName}`; + gridClient._connect(); + return gridClient; +}); + +// Private IP Regex +const ipRegex = /(^127\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)/; + +test("TCXXXX - Applications: Deploy Jenkins", async () => { + // Test Data + const name = "gw" + generateString(10).toLowerCase(); + const tlsPassthrough = false; + const cpu = 4; + const memory = 8; + const rootfsSize = 10; + const diskSize = 100; + const networkName = generateString(15); + const vmName = generateString(15); + const diskName = generateString(15); + const mountPoint = "/mnt/data"; + const publicIp = false; + const ipRangeClassA = "10." + generateInt(1, 255) + ".0.0/16"; + const ipRangeClassB = "172." + generateInt(16, 31) + ".0.0/16"; + const ipRangeClassC = "192.168.0.0/16"; + const ipRange = randomChoice([ipRangeClassA, ipRangeClassB, ipRangeClassC]); + const metadata = "{'deploymentType': 'jenkins'}"; + const description = "Test deploying Jenkins via ts grid3 client"; + + // GatewayNode Selection + const gatewayNodes = await gridClient.capacity.filterNodes({ + gateway: true, + farmId: 1, + availableFor: await gridClient.twins.get_my_twin_id(), + } as FilterOptions); + if (gatewayNodes.length == 0) throw new Error("No nodes available to complete this test"); + const GatewayNode = gatewayNodes[generateInt(0, gatewayNodes.length - 1)]; + + // Node Selection + const nodes = await gridClient.capacity.filterNodes({ + cru: cpu, + mru: memory, + sru: rootfsSize + diskSize, + farmId: 1, + availableFor: await gridClient.twins.get_my_twin_id(), + } as FilterOptions); + const nodeId = await getOnlineNode(nodes); + if (nodeId == -1) throw new Error("No nodes available to complete this test"); + const domain = name + "." + GatewayNode.publicConfig.domain; + + // VM Model + const vms: MachinesModel = { + name: deploymentName, + network: { + name: networkName, + ip_range: ipRange, + }, + machines: [ + { + name: vmName, + node_id: nodeId, + cpu: cpu, + memory: 1024 * memory, + rootfs_size: rootfsSize, + disks: [ + { + name: diskName, + size: diskSize, + mountpoint: mountPoint, + }, + ], + flist: "https://hub.grid.tf/tf-official-apps/jenkins-latest.flist", + entrypoint: "/sbin/zinit init", + public_ip: publicIp, + planetary: true, + mycelium: true, + env: { + SSH_KEY: config.ssh_key, + JENKINS_HOSTNAME: domain, + JENKINS_ADMIN_USERNAME: "admin", + JENKINS_ADMIN_PASSWORD: "admin123", + }, + }, + ], + metadata: metadata, + description: description, + }; + const res = await gridClient.machines.deploy(vms); + log(res); + + // Contracts Assertions + expect(res.contracts.created).toHaveLength(1); + expect(res.contracts.updated).toHaveLength(0); + expect(res.contracts.deleted).toHaveLength(0); + + const vmsList = await gridClient.machines.list(); + log(vmsList); + + // VM List Assertions + expect(vmsList.length).toBeGreaterThanOrEqual(1); + expect(vmsList).toContain(vms.name); + + const result = await gridClient.machines.getObj(vms.name); + log(result); + + // VM Assertions + expect(result[0].nodeId).toBe(nodeId); + expect(result[0].status).toBe("ok"); + expect(result[0].flist).toBe(vms.machines[0].flist); + expect(result[0].entrypoint).toBe(vms.machines[0].entrypoint); + expect(result[0].mounts).toHaveLength(1); + expect(result[0].interfaces[0]["network"]).toBe(vms.network.name); + expect(result[0].interfaces[0]["ip"]).toContain(splitIP(vms.network.ip_range)); + expect(result[0].interfaces[0]["ip"]).toMatch(ipRegex); + expect(result[0].capacity["cpu"]).toBe(cpu); + expect(result[0].capacity["memory"]).toBe(memory * 1024); + expect(result[0].planetary).toBeDefined(); + expect(result[0].myceliumIP).toBeDefined(); + expect(result[0].publicIP).toBeNull(); + expect(result[0].description).toBe(description); + expect(result[0].mounts[0]["name"]).toBe(diskName); + expect(result[0].mounts[0]["size"]).toBe(GBToBytes(diskSize)); + expect(result[0].mounts[0]["mountPoint"]).toBe(mountPoint); + expect(result[0].mounts[0]["state"]).toBe("ok"); + + const backends = ["http://[" + result[0].planetary + "]:8080"]; + log(backends); + + // Name Gateway Model + const gw: GatewayNameModel = { + name: name, + node_id: GatewayNode.nodeId, + tls_passthrough: tlsPassthrough, + backends: backends, + }; + + const gatewayRes = await gridClient.gateway.deploy_name(gw); + log(gatewayRes); + + // Contracts Assertions + expect(gatewayRes.contracts.created).toHaveLength(1); + expect(gatewayRes.contracts.updated).toHaveLength(0); + expect(gatewayRes.contracts.deleted).toHaveLength(0); + expect(gatewayRes.contracts.created[0].contractType.nodeContract.nodeId).toBe(GatewayNode.nodeId); + + const gatewayResult = await gridClient.gateway.getObj(gw.name); + log(gatewayResult); + + // Gateway Assertions + expect(gatewayResult[0].name).toBe(name); + expect(gatewayResult[0].status).toBe("ok"); + expect(gatewayResult[0].type).toContain("name"); + expect(gatewayResult[0].domain).toContain(name); + expect(gatewayResult[0].tls_passthrough).toBe(tlsPassthrough); + expect(gatewayResult[0].backends).toStrictEqual(backends); + + const site = "http://" + gatewayResult[0].domain; + let reachable = false; + + for (let i = 0; i <= 250; i++) { + const wait = await setTimeout(5000, "Waiting for gateway to be ready"); + log(wait); + + await axios + .get(site) + .then(res => { + log("gateway is reachable"); + log(res.status); + log(res.statusText); + log(res.data); + expect(res.status).toBe(200); + reachable = true; + }) + .catch(() => { + log("gateway is not reachable"); + }); + if (reachable) { + break; + } else if (i === 250) { + throw new Error("Gateway is unreachable after multiple retries"); + } + } +}); + +afterAll(async () => { + const vmNames = await gridClient.machines.list(); + for (const name of vmNames) { + const res = await gridClient.machines.delete({ name }); + log(res); + expect(res.created).toHaveLength(0); + expect(res.updated).toHaveLength(0); + expect(res.deleted).toBeDefined(); + } + + const gwNames = await gridClient.gateway.list(); + for (const name of gwNames) { + const res = await gridClient.gateway.delete_name({ name }); + log(res); + expect(res.created).toHaveLength(0); + expect(res.updated).toHaveLength(0); + expect(res.deleted).toBeDefined(); + } + + return await gridClient.disconnect(); +}, 130000); diff --git a/packages/grid_client/tests/modules/applications/nostr.test.ts b/packages/grid_client/tests/modules/applications/nostr.test.ts new file mode 100644 index 0000000000..2f873dd8a6 --- /dev/null +++ b/packages/grid_client/tests/modules/applications/nostr.test.ts @@ -0,0 +1,217 @@ +import axios from "axios"; +import { setTimeout } from "timers/promises"; + +import { FilterOptions, GatewayNameModel, generateString, GridClient, MachinesModel, randomChoice } from "../../../src"; +import { config, getClient } from "../../client_loader"; +import { GBToBytes, generateInt, getOnlineNode, log, splitIP } from "../../utils"; + +jest.setTimeout(1250000); + +let gridClient: GridClient; +let deploymentName: string; + +beforeAll(async () => { + gridClient = await getClient(); + deploymentName = "ns" + generateString(10); + gridClient.clientOptions.projectName = `nostr/${deploymentName}`; + gridClient._connect(); + return gridClient; +}); + +// Private IP Regex +const ipRegex = /(^127\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)/; + +test("TCXXXX - Applications: Deploy Nostr", async () => { + // Test Data + const name = "gw" + generateString(10).toLowerCase(); + const tlsPassthrough = false; + const cpu = 2; + const memory = 4; + const rootfsSize = 2; + const diskSize = 50; + const networkName = generateString(15); + const vmName = generateString(15); + const diskName = generateString(15); + const mountPoint = "/mnt/data"; + const publicIp = false; + const ipRangeClassA = "10." + generateInt(1, 255) + ".0.0/16"; + const ipRangeClassB = "172." + generateInt(16, 31) + ".0.0/16"; + const ipRangeClassC = "192.168.0.0/16"; + const ipRange = randomChoice([ipRangeClassA, ipRangeClassB, ipRangeClassC]); + const metadata = "{'deploymentType': 'nostr'}"; + const description = "Test deploying Nostr via ts grid3 client"; + + // GatewayNode Selection + const gatewayNodes = await gridClient.capacity.filterNodes({ + gateway: true, + farmId: 1, + availableFor: await gridClient.twins.get_my_twin_id(), + } as FilterOptions); + if (gatewayNodes.length == 0) throw new Error("No nodes available to complete this test"); + const GatewayNode = gatewayNodes[generateInt(0, gatewayNodes.length - 1)]; + + // Node Selection + const nodes = await gridClient.capacity.filterNodes({ + cru: cpu, + mru: memory, + sru: rootfsSize + diskSize, + farmId: 1, + availableFor: await gridClient.twins.get_my_twin_id(), + } as FilterOptions); + const nodeId = await getOnlineNode(nodes); + if (nodeId == -1) throw new Error("No nodes available to complete this test"); + const domain = name + "." + GatewayNode.publicConfig.domain; + + // VM Model + const vms: MachinesModel = { + name: deploymentName, + network: { + name: networkName, + ip_range: ipRange, + }, + machines: [ + { + name: vmName, + node_id: nodeId, + cpu: cpu, + memory: 1024 * memory, + rootfs_size: rootfsSize, + disks: [ + { + name: diskName, + size: diskSize, + mountpoint: mountPoint, + }, + ], + flist: "https://hub.grid.tf/tf-official-apps/nostr_relay-mycelium.flist", + entrypoint: "/sbin/zinit init", + public_ip: publicIp, + planetary: true, + mycelium: true, + env: { + SSH_KEY: config.ssh_key, + NOSTR_HOSTNAME: domain, + }, + }, + ], + metadata: metadata, + description: description, + }; + const res = await gridClient.machines.deploy(vms); + log(res); + + // Contracts Assertions + expect(res.contracts.created).toHaveLength(1); + expect(res.contracts.updated).toHaveLength(0); + expect(res.contracts.deleted).toHaveLength(0); + + const vmsList = await gridClient.machines.list(); + log(vmsList); + + // VM List Assertions + expect(vmsList.length).toBeGreaterThanOrEqual(1); + expect(vmsList).toContain(vms.name); + + const result = await gridClient.machines.getObj(vms.name); + log(result); + + // VM Assertions + expect(result[0].nodeId).toBe(nodeId); + expect(result[0].status).toBe("ok"); + expect(result[0].flist).toBe(vms.machines[0].flist); + expect(result[0].entrypoint).toBe(vms.machines[0].entrypoint); + expect(result[0].mounts).toHaveLength(1); + expect(result[0].interfaces[0]["network"]).toBe(vms.network.name); + expect(result[0].interfaces[0]["ip"]).toContain(splitIP(vms.network.ip_range)); + expect(result[0].interfaces[0]["ip"]).toMatch(ipRegex); + expect(result[0].capacity["cpu"]).toBe(cpu); + expect(result[0].capacity["memory"]).toBe(memory * 1024); + expect(result[0].planetary).toBeDefined(); + expect(result[0].myceliumIP).toBeDefined(); + expect(result[0].publicIP).toBeNull(); + expect(result[0].description).toBe(description); + expect(result[0].mounts[0]["name"]).toBe(diskName); + expect(result[0].mounts[0]["size"]).toBe(GBToBytes(diskSize)); + expect(result[0].mounts[0]["mountPoint"]).toBe(mountPoint); + expect(result[0].mounts[0]["state"]).toBe("ok"); + + const backends = ["http://[" + result[0].planetary + "]:8080"]; + log(backends); + + // Name Gateway Model + const gw: GatewayNameModel = { + name: name, + node_id: GatewayNode.nodeId, + tls_passthrough: tlsPassthrough, + backends: backends, + }; + + const gatewayRes = await gridClient.gateway.deploy_name(gw); + log(gatewayRes); + + // Contracts Assertions + expect(gatewayRes.contracts.created).toHaveLength(1); + expect(gatewayRes.contracts.updated).toHaveLength(0); + expect(gatewayRes.contracts.deleted).toHaveLength(0); + expect(gatewayRes.contracts.created[0].contractType.nodeContract.nodeId).toBe(GatewayNode.nodeId); + + const gatewayResult = await gridClient.gateway.getObj(gw.name); + log(gatewayResult); + + // Gateway Assertions + expect(gatewayResult[0].name).toBe(name); + expect(gatewayResult[0].status).toBe("ok"); + expect(gatewayResult[0].type).toContain("name"); + expect(gatewayResult[0].domain).toContain(name); + expect(gatewayResult[0].tls_passthrough).toBe(tlsPassthrough); + expect(gatewayResult[0].backends).toStrictEqual(backends); + + const site = "http://" + gatewayResult[0].domain; + let reachable = false; + + for (let i = 0; i <= 250; i++) { + const wait = await setTimeout(5000, "Waiting for gateway to be ready"); + log(wait); + + await axios + .get(site) + .then(res => { + log("gateway is reachable"); + log(res.status); + log(res.statusText); + log(res.data); + expect(res.status).toBe(200); + reachable = true; + }) + .catch(() => { + log("gateway is not reachable"); + }); + if (reachable) { + break; + } else if (i === 250) { + throw new Error("Gateway is unreachable after multiple retries"); + } + } +}); + +afterAll(async () => { + const vmNames = await gridClient.machines.list(); + for (const name of vmNames) { + const res = await gridClient.machines.delete({ name }); + log(res); + expect(res.created).toHaveLength(0); + expect(res.updated).toHaveLength(0); + expect(res.deleted).toBeDefined(); + } + + const gwNames = await gridClient.gateway.list(); + for (const name of gwNames) { + const res = await gridClient.gateway.delete_name({ name }); + log(res); + expect(res.created).toHaveLength(0); + expect(res.updated).toHaveLength(0); + expect(res.deleted).toBeDefined(); + } + + return await gridClient.disconnect(); +}, 130000);