From 3251300627693a2090ecb879437a1d2c09d4b7e3 Mon Sep 17 00:00:00 2001 From: Omar Kassem Date: Mon, 17 Feb 2025 15:29:52 +0200 Subject: [PATCH 1/5] Fix[NodeManaualFilter]: (#3900) add is in dedicated farm to the condition as the dedicated nodes in shared farms can be used as shared node --- packages/playground/src/utils/nodeSelector.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playground/src/utils/nodeSelector.ts b/packages/playground/src/utils/nodeSelector.ts index 67bbbd277c..a9d672c353 100644 --- a/packages/playground/src/utils/nodeSelector.ts +++ b/packages/playground/src/utils/nodeSelector.ts @@ -227,7 +227,7 @@ export async function validateRentContract( } try { - if (node.dedicated && node.rentedByTwinId === 0) { + if (node.dedicated && node.rentedByTwinId === 0 && node.inDedicatedFarm) { throw `Node ${node.nodeId} is not rented`; } if (node.rentContractId !== 0) { From ef8cde2a197d513eaff3a6fd60ac6fcea63a0d5d Mon Sep 17 00:00:00 2001 From: Khaled Youssef <110984055+khaledyoussef24@users.noreply.github.com> Date: Mon, 17 Feb 2025 19:23:28 +0200 Subject: [PATCH 2/5] nostr script gridclient (#3776) * nostr script * testing network * refactoring the script * fixing script and adding features and filters * adding gateway feature * packages/grid_client/scripts/applications/nostor.ts * refactors for the code and pinging node before deployment * renaming the script --- .../grid_client/scripts/applications/nostr.ts | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 packages/grid_client/scripts/applications/nostr.ts diff --git a/packages/grid_client/scripts/applications/nostr.ts b/packages/grid_client/scripts/applications/nostr.ts new file mode 100644 index 0000000000..16b8089a95 --- /dev/null +++ b/packages/grid_client/scripts/applications/nostr.ts @@ -0,0 +1,139 @@ +import { Features, FilterOptions, GatewayNameModel, GridClient, MachinesModel, NodeInfo } from "../../src"; +import { config, getClient } from "../client_loader"; +import { log, pingNodes } from "../utils"; + +async function deploy(client: GridClient, vms: MachinesModel, subdomain: string, gatewayNode: NodeInfo) { + // Deploy VM + const resultVM = await client.machines.deploy(vms); + log("================= Deploying VM ================="); + log(resultVM); + log("================= Deploying VM ================="); + + // Get WG interface details + const wgnet = (await client.machines.getObj(vms.name))[0].interfaces[0]; + + // Deploy Gateway + const gateway: GatewayNameModel = { + name: subdomain, + network: wgnet.network, + node_id: gatewayNode.nodeId, + tls_passthrough: false, + backends: [`http://${wgnet.ip}:8080`], + }; + + const resultGateway = await client.gateway.deploy_name(gateway); + log("================= Deploying Gateway ================="); + log(resultGateway); + log("================= Deploying Gateway ================="); +} + +async function getDeployment(client: GridClient, name: string, subdomain: string) { + // Get VM deployment + const resultVM = await client.machines.getObj(name); + log("================= Getting VM Deployment ================="); + log(resultVM); + log("================= Getting VM Deployment ================="); + + // Get Gateway deployment + const resultGateway = await client.gateway.getObj(subdomain); + log("================= Getting Gateway Deployment ================="); + log(resultGateway); + log(`https://${resultGateway[0].domain}`); + log("================= Getting Gateway Deployment ================="); +} + +async function cancel(client: GridClient, name: string, subdomain: string) { + // Cancel VM deployment + const resultVM = await client.machines.delete({ name }); + log("================= Canceling VM Deployment ================="); + log(resultVM); + log("================= Canceling VM Deployment ================="); + + // Cancel Gateway deployment + const resultGateway = await client.gateway.delete_name({ name: subdomain }); + log("================= Canceling Gateway Deployment ================="); + log(resultGateway); + log("================= Canceling Gateway Deployment ================="); +} + +async function main() { + const name = "newnostr1"; + const grid3 = await getClient(`nostr/${name}`); + const subdomain = `ntt${grid3.twinId}${name}`; + const instanceCapacity = { cru: 2, mru: 4, sru: 50 }; + + // VM Query Options + const vmQueryOptions: FilterOptions = { + features: [Features.wireguard, Features.mycelium], + cru: instanceCapacity.cru, + mru: instanceCapacity.mru, + sru: instanceCapacity.sru, + availableFor: grid3.twinId, + farmId: 1, + }; + + // Gateway Query Options + const gatewayQueryOptions: FilterOptions = { + features: [Features.wireguard, Features.mycelium], + gateway: true, + availableFor: grid3.twinId, + }; + + const gatewayNodes = await grid3.capacity.filterNodes(gatewayQueryOptions); + const gatewayNodeId = await pingNodes(grid3, gatewayNodes); + const gatewayNode = gatewayNodes.find(node => node.nodeId == gatewayNodeId); + const nodes = await grid3.capacity.filterNodes(vmQueryOptions); + const vmNode = await pingNodes(grid3, nodes); + const domain = `${subdomain}.${gatewayNode!.publicConfig.domain}`; + + const vms: MachinesModel = { + name, + network: { + name: "nostrnet", + ip_range: "10.252.0.0/16", + addAccess: true, + accessNodeId: gatewayNode!.nodeId, + }, + machines: [ + { + name: "nostr", + node_id: vmNode, + disks: [ + { + name: "nsDisk", + size: instanceCapacity.sru, + mountpoint: "/mnt/data", + }, + ], + planetary: true, + public_ip: false, + public_ip6: false, + mycelium: true, + cpu: instanceCapacity.cru, + memory: 1024 * instanceCapacity.mru, + rootfs_size: 0, + flist: "https://hub.grid.tf/tf-official-apps/nostr_relay-mycelium.flist", + entrypoint: "/sbin/zinit init", + env: { + SSH_KEY: config.ssh_key, + NOSTR_HOSTNAME: domain, + }, + }, + ], + metadata: "", + description: "Deploying Nostr instance via TS Grid3 client", + }; + + // Deploy VM and Gateway + await deploy(grid3, vms, subdomain, gatewayNode!); + + // Get the deployment details + await getDeployment(grid3, name, subdomain); + + // Uncomment the line below to cancel the deployment + // await cancel(grid3, name, subdomain); + + await grid3.disconnect(); +} + +main(); From fcc000b5e268449c0f056b16a8c061daa86ebb0b Mon Sep 17 00:00:00 2001 From: Omar Kassem Date: Tue, 18 Feb 2025 13:31:40 +0200 Subject: [PATCH 3/5] reload target node on reserve/unreserve (#3897) * Feature[NodeDetailsCard]: - add nodeId to FilterOptions and getNodeUrlQuery to be able to filter nodes based on its id - refactor reserveActionBtn to update the node status after action and emit the changes to parent components * Chore: cleanup debug code * Feature[AutoNodeSelector]: reset the selected node after action this should select the rented node after rent action and check if the node is valid after unreserve action --- packages/grid_client/src/modules/models.ts | 1 + packages/grid_client/src/primitives/nodes.ts | 1 + .../node_selector/TfAutoNodeSelector.vue | 9 ++++-- .../node_selector/TfNodeDetailsCard.vue | 22 +------------- .../components/reserve_action_btn.vue | 29 +++++++++---------- 5 files changed, 23 insertions(+), 39 deletions(-) diff --git a/packages/grid_client/src/modules/models.ts b/packages/grid_client/src/modules/models.ts index 5be3172bb1..9f4f6c7525 100644 --- a/packages/grid_client/src/modules/models.ts +++ b/packages/grid_client/src/modules/models.ts @@ -624,6 +624,7 @@ class FilterOptions { @Expose() @IsOptional() @IsInt({ each: true }) @Min(1, { each: true }) nodeExclude?: number[]; @Expose() @IsOptional() @IsInt({ each: true }) @Min(1, { each: true }) farmIds?: number[]; @Expose() @IsOptional() @IsInt() @Min(1) farmId?: number; + @Expose() @IsOptional() @IsInt() @Min(1) nodeId?: number; @Expose() @IsOptional() @IsString() farmName?: string; @Expose() @IsOptional() @IsString() country?: string; @Expose() @IsOptional() @IsString() city?: string; diff --git a/packages/grid_client/src/primitives/nodes.ts b/packages/grid_client/src/primitives/nodes.ts index ebc4c4ead8..7efbb1cf95 100644 --- a/packages/grid_client/src/primitives/nodes.ts +++ b/packages/grid_client/src/primitives/nodes.ts @@ -466,6 +466,7 @@ class Nodes { healthy: options.healthy, sort_by: SortBy.FreeCRU, sort_order: SortOrder.Desc, + node_id: options.nodeId, rentable_or_rented_by: options.rentableOrRentedBy, features: options.features, }; diff --git a/packages/playground/src/components/node_selector/TfAutoNodeSelector.vue b/packages/playground/src/components/node_selector/TfAutoNodeSelector.vue index ad17ad234a..9661d8ebf4 100644 --- a/packages/playground/src/components/node_selector/TfAutoNodeSelector.vue +++ b/packages/playground/src/components/node_selector/TfAutoNodeSelector.vue @@ -43,7 +43,6 @@ -
@@ -506,25 +506,6 @@ export default { } } - function onReserveChange() { - if (!props.node) { - return; - } - - const n = { ...props.node } as NodeInfo | GridNode; - const gotReserved = n.rentedByTwinId === 0; - - if (gotReserved) { - n.rentedByTwinId = profileManager.profile!.twinId; - n.rented = true; - } else { - n.rentedByTwinId = 0; - n.rented = false; - } - n.rentable = !n.rented; - ctx.emit("update:node", n); - } - function getNodeStatusColor(status: string): string { if (status === "up") { return "success"; @@ -635,7 +616,6 @@ export default { capitalize, formatResourceSize, formatSpeed, - onReserveChange, getNodeStatusColor, validateRentContract, discountTableItems, diff --git a/packages/playground/src/dashboard/components/reserve_action_btn.vue b/packages/playground/src/dashboard/components/reserve_action_btn.vue index 6eabd839d3..74eb288349 100644 --- a/packages/playground/src/dashboard/components/reserve_action_btn.vue +++ b/packages/playground/src/dashboard/components/reserve_action_btn.vue @@ -99,7 +99,13 @@ export default { function removeReserve() { openUnreserveDialog.value = true; } - + async function postActionHandler() { + notifyDelaying(); + disableButton.value = true; + await new Promise(resolve => setTimeout(resolve, 20000)); + const node = await gridStore.client.capacity.filterNodes({ nodeId: +props.node.nodeId }); + emit("update:node", node[0]); + } async function unReserveNode() { loadingUnreserveNode.value = true; try { @@ -118,13 +124,7 @@ export default { loadingUnreserveNode.value = false; openUnreserveDialog.value = false; loadingUnreserveBtn.value = true; - notifyDelaying(); - disableButton.value = true; - setTimeout(() => { - disableButton.value = false; - loadingUnreserveBtn.value = false; - emit("updateTable"); - }, 20000); + await postActionHandler(); } } catch (e) { if (e instanceof InsufficientBalanceError) { @@ -135,6 +135,9 @@ export default { } loadingUnreserveNode.value = false; openUnreserveDialog.value = false; + } finally { + disableButton.value = false; + loadingUnreserveNode.value = false; } } @@ -148,13 +151,7 @@ export default { if (props.node.status === "standby") { createCustomToast(`It might take a while for node ${props.node.nodeId} status to be up`, ToastType.warning); } - notifyDelaying(); - disableButton.value = true; - setTimeout(() => { - disableButton.value = false; - loadingReserveNode.value = false; - emit("updateTable"); - }, 20000); + await postActionHandler(); } else { createCustomToast("Please Login first to continue.", ToastType.danger); } @@ -165,6 +162,8 @@ export default { console.log(e); createCustomToast("Failed to create rent contract.", ToastType.danger); } + } finally { + disableButton.value = false; loadingReserveNode.value = false; } } From fc2f4614a63969587805ec320fa65a91463d4da1 Mon Sep 17 00:00:00 2001 From: Omar Kassem Date: Tue, 18 Feb 2025 13:38:01 +0200 Subject: [PATCH 4/5] Fix discount package (#3887) * Fix(DiscountPackage): as we get the billing reports ordred by timestamp in descending; we just need the first billing report * Refactor(GetContractConsumption): include the discountRecevied in the same request * Refactor(ContractConsumption): use returned discountLevel from getConsumption call * Refactor(DeploymentList): access the amountbilled in the consumption request --- .../src/clients/tf-grid/contracts.ts | 32 +++++++++++++------ packages/grid_client/src/modules/contracts.ts | 10 +++--- packages/playground/src/utils/contracts.ts | 12 +++---- .../playground/src/utils/load_deployment.ts | 4 +-- 4 files changed, 35 insertions(+), 23 deletions(-) diff --git a/packages/grid_client/src/clients/tf-grid/contracts.ts b/packages/grid_client/src/clients/tf-grid/contracts.ts index 715eaa07af..3d599e3773 100644 --- a/packages/grid_client/src/clients/tf-grid/contracts.ts +++ b/packages/grid_client/src/clients/tf-grid/contracts.ts @@ -109,6 +109,10 @@ export interface GetConsumptionOptions { graphqlURL: string; id: number; } +export interface Consumption { + amountBilled: number; + discountReceived: DiscountLevel; +} export interface GetDiscountPackageOptions { graphqlURL: string; @@ -268,7 +272,7 @@ class TFContracts extends Contracts { const gqlClient = new Graphql(options.graphqlURL); const body = `query getConsumption($contractId: BigInt!){ - contractBillReports(where: {contractID_eq: $contractId} , orderBy: timestamp_DESC) { + contractBillReports(where: {contractID_eq: $contractId} , orderBy: timestamp_DESC, limit:1) { discountReceived } @@ -282,7 +286,7 @@ class TFContracts extends Contracts { if (billReports.length === 0) { return "None"; } else { - const discountPackage = billReports[billReports.length - 1].discountReceived; + const discountPackage = billReports[0].discountReceived; return discountPackage; } } catch (err) { @@ -291,17 +295,19 @@ class TFContracts extends Contracts { } } /** - * Get contract consumption per hour in TFT. + * Get the contract consumption details per hour in TFT. * * @param {GetConsumptionOptions} options - * @returns {Promise} + * @returns {Promise} A promise resolving to the consumption details, + * including the amount billed and the discount received. */ - async getConsumption(options: GetConsumptionOptions): Promise { + async getConsumption(options: GetConsumptionOptions): Promise { const gqlClient = new Graphql(options.graphqlURL); const body = `query getConsumption($contractId: BigInt!){ contractBillReports(where: {contractID_eq: $contractId}, limit: 2 , orderBy: timestamp_DESC) { amountBilled timestamp + discountReceived } nodeContracts(where: {contractID_eq: $contractId}) { createdAt @@ -318,7 +324,10 @@ class TFContracts extends Contracts { const gqlConsumption: GqlConsumption = response["data"] as GqlConsumption; const billReports = gqlConsumption.contractBillReports; if (billReports.length === 0) { - return 0; + return { + amountBilled: 0, + discountReceived: "None", + }; } else { let duration = 1; const amountBilled = new Decimal(billReports[0].amountBilled); @@ -338,10 +347,13 @@ class TFContracts extends Contracts { } } } - return amountBilled - .div(duration || 1) - .div(10 ** 7) - .toNumber(); + return { + amountBilled: amountBilled + .div(duration || 1) + .div(10 ** 7) + .toNumber(), + discountReceived: billReports[0].discountReceived, + }; } } catch (err) { (err as Error).message = formatErrorMessage(`Error getting consumption for contract ${options.id}.`, err); diff --git a/packages/grid_client/src/modules/contracts.ts b/packages/grid_client/src/modules/contracts.ts index 1dca479e2f..2728b55e7b 100644 --- a/packages/grid_client/src/modules/contracts.ts +++ b/packages/grid_client/src/modules/contracts.ts @@ -10,6 +10,7 @@ import { GridClientError } from "@threefold/types"; import * as PATH from "path"; import { + Consumption, ContractsOverdue, type DiscountLevel, GqlContracts, @@ -539,17 +540,18 @@ class Contracts { return this.client.contracts.getDiscountPackage({ id: options.id, graphqlURL: this.config.graphqlURL }); } /** - * Get contract consumption per hour in TFT. + * Get the contract consumption details per hour in TFT. * - * @param {ContractConsumption} options - * @returns {Promise} + * @param {ContractConsumption} options - The contract consumption parameters. + * @returns {Promise} A promise resolving to the consumption details, + * including the amount billed and the discount received. * @decorators * - `@expose`: Exposes the method for external use. * - `@validateInput`: Validates the input options. */ @expose @validateInput - async getConsumption(options: ContractConsumption): Promise { + async getConsumption(options: ContractConsumption): Promise { return this.client.contracts.getConsumption({ id: options.id, graphqlURL: this.config.graphqlURL }); } diff --git a/packages/playground/src/utils/contracts.ts b/packages/playground/src/utils/contracts.ts index a05ccda980..b4a2a7cc58 100644 --- a/packages/playground/src/utils/contracts.ts +++ b/packages/playground/src/utils/contracts.ts @@ -1,4 +1,4 @@ -import { ContractStates, type DiscountLevel, type GridClient } from "@threefold/grid_client"; +import { type Consumption, ContractStates, type DiscountLevel, type GridClient } from "@threefold/grid_client"; import { NodeStatus } from "@threefold/gridproxy_client"; import type { Ref } from "vue"; @@ -53,15 +53,13 @@ export async function normalizeContract( expiration = new Date(exp).toLocaleString(); } - let consumption: number; + let consumption: Consumption; try { consumption = await grid.contracts.getConsumption({ id }); } catch { - consumption = 0; + consumption = { amountBilled: 0, discountReceived: "None" }; } - const discountPackage = await grid.contracts.getDiscountPackage({ id }); - return { contract_id: id, twin_id: c.twin_id, @@ -77,8 +75,8 @@ export async function normalizeContract( solutionName: data.name || "-", solutionType: data.projectName || data.type || "-", expiration, - consumption: consumption, - discountPackage: discountPackage, + consumption: consumption.amountBilled, + discountPackage: consumption.discountReceived, }; } diff --git a/packages/playground/src/utils/load_deployment.ts b/packages/playground/src/utils/load_deployment.ts index 9a007bfbb9..963b40596c 100644 --- a/packages/playground/src/utils/load_deployment.ts +++ b/packages/playground/src/utils/load_deployment.ts @@ -128,7 +128,7 @@ export async function loadVms(grid: GridClient, options: LoadVMsOptions = {}) { const data = vms.map((vm, index) => { for (let i = 0; i < vm.length; i++) { - vm[i].billing = formatConsumption(consumptions[index] as number); + vm[i].billing = formatConsumption(consumptions[index]?.amountBilled as number); if (wireguards[index] && wireguards[index].length > 0) { vm[i].wireguard = wireguards[index][0]; } @@ -235,7 +235,7 @@ export async function loadK8s(grid: GridClient) { ), ); const data = k8s.map((cluster, index) => { - cluster.masters[0].billing = formatConsumption(consumptions[index] as number); + cluster.masters[0].billing = formatConsumption(consumptions[index]?.amountBilled as number); if (wireguards && wireguards[index]) { cluster.wireguard = wireguards[index][0]; From 4dd5f0fa83128a5881de6bb51adfbb7d7a1ac9ae Mon Sep 17 00:00:00 2001 From: Omar Kassem Date: Tue, 18 Feb 2025 13:38:51 +0200 Subject: [PATCH 5/5] Use interfaces in gateway node selector (#3860) * feature: support passing interfaces as features on gateway nodes query * WIP: bind features to domain name features * chore(GatewayInterfaces): remove other interfaces keep only wireguard * chore(GatewayInterfaces): cleanup * chore(GatewayInterfaces): Fix gateway on ip, this is a workaround and maybe got enahnced later * chore(GatewayInterfaces): set the has_ipv6 to undefined instead of false * Style(CustomDomain): make the tooltip aligned center * Style(DomainDialog): reorder fiedls * Feature(GatewayDialog): - refactor: change the type of selectedIPAddress as it always got converted to string - pass the selected interface type as feature to the domainName component --- .../src/components/manage_gateway_dialog.vue | 119 ++++++++++-------- .../components/node_selector/TfDomainName.vue | 13 +- .../node_selector/TfSelectionDetails.vue | 17 ++- packages/playground/src/types/nodeSelector.ts | 4 +- 4 files changed, 97 insertions(+), 56 deletions(-) diff --git a/packages/playground/src/components/manage_gateway_dialog.vue b/packages/playground/src/components/manage_gateway_dialog.vue index b4eac015bf..193a59d245 100644 --- a/packages/playground/src/components/manage_gateway_dialog.vue +++ b/packages/playground/src/components/manage_gateway_dialog.vue @@ -89,41 +89,6 @@
- - - - - - -
- -
- - - - - -
- - - -
- - - - - + + + + + + + + + + + + + +
+ +
+ + + + +
@@ -187,12 +192,12 @@