From 9274d2f9c29ecaaa2a34fa67e9f05284f79499d0 Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Wed, 22 Mar 2023 12:24:37 +0200 Subject: [PATCH 1/7] Test runner simplifications and fix - simplify some logic and move it to dedicated functions - stop the networks even if no tests were run --- .../orchestrator/src/test-runner/index.ts | 147 +++++++++--------- 1 file changed, 72 insertions(+), 75 deletions(-) diff --git a/javascript/packages/orchestrator/src/test-runner/index.ts b/javascript/packages/orchestrator/src/test-runner/index.ts index b088cf098..ca4aae127 100644 --- a/javascript/packages/orchestrator/src/test-runner/index.ts +++ b/javascript/packages/orchestrator/src/test-runner/index.ts @@ -26,6 +26,64 @@ export interface BackchannelMap { [propertyName: string]: any; } +function showNetworkLogsLocation( + network: Network, + logsPath: string, + inCI: boolean, +) { + console.log( + `\n\t${decorators.magenta( + "📓 To see the full logs of the nodes please go to:", + )}`, + ); + switch (network.client.providerName) { + case "kubernetes": + if (inCI) { + // show links to grafana and also we need to move the logs to artifacts + const networkEndTime = new Date().getTime(); + for (const node of network.relay) { + const loki_url = getLokiUrl( + network.namespace, + node.name, + network.networkStartTime!, + networkEndTime, + ); + console.log( + `\t${decorators.magenta(node.name)}: ${decorators.green(loki_url)}`, + ); + } + + for (const [paraId, parachain] of Object.entries(network.paras)) { + console.log(`\n\tParaId: ${decorators.magenta(paraId)}`); + for (const node of parachain.nodes) { + const loki_url = getLokiUrl( + network.namespace, + node.name, + network.networkStartTime!, + networkEndTime, + ); + console.log( + `\t\t${decorators.magenta(node.name)}: ${decorators.green( + loki_url, + )}`, + ); + } + } + + // logs are also collected as artifacts + console.log( + `\n\n\t ${decorators.yellow( + "📓 Logs are also available in the artifacts' pipeline in gitlab", + )}`, + ); + } + break; + default: + console.log(`\n\t${decorators.magenta(logsPath)}`); + break; + } +} + export async function run( configBasePath: string, testName: string, @@ -87,7 +145,7 @@ export async function run( this.timeout(launchTimeout * 1000); try { if (!runningNetworkSpecPath) { - console.log(`\t Launching network... this can take a while.`); + console.log(`\n\n\t Launching network... this can take a while.`); network = await start(creds!, config, { spawnConcurrency: concurrency, inCI, @@ -126,82 +184,21 @@ export async function run( suite.afterAll("teardown", async function () { this.timeout(180 * 1000); - if (network && !network.wasRunning) { - const logsPath = await network.dumpLogs(false); - const tests = this.test?.parent?.tests; - - if (tests) { - const failed = tests.filter((test) => { - return test.state !== "passed"; - }); - if (failed.length) { - console.log( - `\n\n\t${decorators.red("❌ One or more of your test failed...")}`, - ); - } + const tests = this.test?.parent?.tests; - // All test passed, just remove the network - console.log(`\n\t ${decorators.green("Deleting network")}`); - await network.stop(); + const failed = tests?.some((test) => test.state !== "passed") ?? false; + if (failed) { + console.log( + `\n\n\t${decorators.red("❌ One or more of your tests failed...")}`, + ); + } - // show logs - console.log( - `\n\n\t${decorators.magenta( - "📓 To see the full logs of the nodes please go to:", - )}`, - ); - switch (network.client.providerName) { - case "podman": - case "native": - console.log(`\n\t${decorators.magenta(logsPath)}`); - break; - case "kubernetes": - if (inCI) { - // show links to grafana and also we need to move the logs to artifacts - const networkEndtime = new Date().getTime(); - for (const node of network.relay) { - const loki_url = getLokiUrl( - network.namespace, - node.name, - network.networkStartTime!, - networkEndtime, - ); - console.log( - `\t${decorators.magenta(node.name)}: ${decorators.green( - loki_url, - )}`, - ); - } - - for (const [paraId, parachain] of Object.entries(network.paras)) { - console.log(`\n\tParaId: ${decorators.magenta(paraId)}`); - for (const node of parachain.nodes) { - const loki_url = getLokiUrl( - network.namespace, - node.name, - network.networkStartTime!, - networkEndtime, - ); - console.log( - `\t\t${decorators.magenta(node.name)}: ${decorators.green( - loki_url, - )}`, - ); - } - } - - // logs are also collaected as artifacts - console.log( - `\n\n\t ${decorators.yellow( - "📓 Logs are also available in the artifacts' pipeline in gitlab", - )}`, - ); - } else { - console.log(`\n\t${decorators.magenta(logsPath)}`); - } - break; - } - } + if (network && !network.wasRunning) { + console.log("\n"); + const logsPath = await network.dumpLogs(false); + console.log(`\n\t ${decorators.green("Deleting network")}`); + await network.stop(); + showNetworkLogsLocation(network, logsPath, inCI); } return; }); From cf1a0dc23d3877831e4e7976216cee651e65ba1b Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Wed, 22 Mar 2023 12:39:32 +0200 Subject: [PATCH 2/7] Move fileUploadCache to KubeClient We need each KubeClient to have a local fileUploadCache when we start multiple networks --- .../orchestrator/src/providers/k8s/kubeClient.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/javascript/packages/orchestrator/src/providers/k8s/kubeClient.ts b/javascript/packages/orchestrator/src/providers/k8s/kubeClient.ts index fb12987e2..59fe51d58 100644 --- a/javascript/packages/orchestrator/src/providers/k8s/kubeClient.ts +++ b/javascript/packages/orchestrator/src/providers/k8s/kubeClient.ts @@ -43,10 +43,6 @@ export function initClient( return client; } -// Here we cache each file we upload from local -// to just cp between pods and not upload again the same file. -const fileUploadCache: any = {}; - export class KubeClient extends Client { namespace: string; chainId?: string; @@ -59,6 +55,9 @@ export class KubeClient extends Client { localMagicFilepath: string; remoteDir: string; dataDir: string; + // Here we cache each file we upload from local + // to just cp between pods and not upload again the same file. + fileUploadCache: any = {}; constructor(configPath: string, namespace: string, tmpDir: string) { super(configPath, namespace, tmpDir, "kubectl", "kubernetes"); @@ -421,7 +420,7 @@ export class KubeClient extends Client { const fileHash = getSha256(fileBuffer.toString()); const parts = localFilePath.split("/"); const fileName = parts[parts.length - 1]; - if (!fileUploadCache[fileHash]) { + if (!this.fileUploadCache[fileHash]) { await this.uploadToFileserver(localFilePath, fileName, fileHash); } @@ -863,7 +862,7 @@ export class KubeClient extends Client { debug("copyFileToPod", args); const result = await this.runCommand(args); debug(result); - fileUploadCache[fileHash] = fileName; + this.fileUploadCache[fileHash] = fileName; } getLogsCommand(name: string): string { return `kubectl logs -f ${name} -c ${name} -n ${this.namespace}`; From 07cc333a101c026da4dabd12708aa81f021619b5 Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Thu, 9 Mar 2023 16:17:30 +0100 Subject: [PATCH 3/7] zndsl: support multiple networks --- crates/parser/src/ast.rs | 2 +- crates/parser/src/lib.rs | 11 ++-- crates/parser/src/tests.rs | 101 ++++++++++++++++++++++++------- crates/parser/src/zombienet.pest | 2 +- 4 files changed, 89 insertions(+), 27 deletions(-) diff --git a/crates/parser/src/ast.rs b/crates/parser/src/ast.rs index d6b31b7cf..e0d153cc0 100644 --- a/crates/parser/src/ast.rs +++ b/crates/parser/src/ast.rs @@ -147,7 +147,7 @@ pub struct Assertion { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct TestDefinition { pub description: Option, - pub network: String, + pub networks: Vec, pub creds: String, pub assertions: Vec, } diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index 148c1c40d..c4206aabc 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -212,7 +212,7 @@ pub fn parse(unparsed_file: &str) -> Result return Err(errors::ParserError::ParseError(e.to_string())), }; - let mut network: Option = None; + let mut networks: Vec = vec![]; let mut creds: Option = None; let mut description: Option = None; let mut assertions: Vec = vec![]; @@ -233,7 +233,10 @@ pub fn parse(unparsed_file: &str) -> Result { - network = Some(record.into_inner().as_str().to_owned()); + let mut pairs = record.into_inner(); + let file_path = get_pair(&mut pairs, "file_path")?.as_str(); + + networks.push(file_path.to_owned()); } Rule::creds => { let mut pairs = record.into_inner(); @@ -604,7 +607,7 @@ pub fn parse(unparsed_file: &str) -> Result Result Date: Thu, 16 Mar 2023 15:51:59 +0200 Subject: [PATCH 4/7] Use locally compiled `dsl-parser-wrapper` --- Cargo.lock | 2 +- crates/parser-wrapper/Cargo.toml | 2 +- javascript/package-lock.json | 15 ++++++++------- javascript/package.json | 4 ++-- javascript/packages/cli/package.json | 2 +- javascript/packages/cli/src/actions/test.ts | 2 +- .../cli/zombienet-dsl-parser-wrapper-0.1.8.tgz | Bin 0 -> 61189 bytes javascript/packages/orchestrator/README.md | 10 ++++++++++ 8 files changed, 24 insertions(+), 13 deletions(-) create mode 100644 javascript/packages/cli/zombienet-dsl-parser-wrapper-0.1.8.tgz diff --git a/Cargo.lock b/Cargo.lock index 89cb24031..1a4993698 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -285,7 +285,7 @@ dependencies = [ [[package]] name = "dsl-parser-wrapper" -version = "0.1.7" +version = "0.1.8" dependencies = [ "parser", "serde_json", diff --git a/crates/parser-wrapper/Cargo.toml b/crates/parser-wrapper/Cargo.toml index 0e629f45c..0c691d4cc 100644 --- a/crates/parser-wrapper/Cargo.toml +++ b/crates/parser-wrapper/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dsl-parser-wrapper" -version = "0.1.7" +version = "0.1.8" edition = "2021" description = "Zombienet DSL parser: produces a test definition, in json format, that can be used with the ZombieNet's test-runnner." license = "GPL-3.0-or-later" diff --git a/javascript/package-lock.json b/javascript/package-lock.json index a650a7d76..1112b3393 100644 --- a/javascript/package-lock.json +++ b/javascript/package-lock.json @@ -1503,9 +1503,10 @@ "link": true }, "node_modules/@zombienet/dsl-parser-wrapper": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@zombienet/dsl-parser-wrapper/-/dsl-parser-wrapper-0.1.7.tgz", - "integrity": "sha512-pJqo1LZbNqnL6Of1h67eYtTTu0BLcf+U/nGQtj8pYr0V4XVDWdCYvT4C9kXkrh5964nKz3KGB7Eky43NqRJZqg==" + "version": "0.1.8", + "resolved": "file:packages/cli/zombienet-dsl-parser-wrapper-0.1.8.tgz", + "integrity": "sha512-nSNMCFLc2iGlYg2j/eoPgmADQcPY+HOER+ZqChbmFQa8o+T8EMcPuSgETCC2vn5Y4pySx2jyMq4aFTurhDefiw==", + "license": "GPL-3.0-or-later" }, "node_modules/@zombienet/orchestrator": { "resolved": "packages/orchestrator", @@ -6524,11 +6525,11 @@ }, "packages/cli": { "name": "@zombienet/cli", - "version": "1.3.43", + "version": "1.3.44", "license": "GPL-3.0-or-later", "dependencies": { - "@zombienet/dsl-parser-wrapper": "^0.1.7", - "@zombienet/orchestrator": "^0.0.34", + "@zombienet/dsl-parser-wrapper": "file:./zombienet-dsl-parser-wrapper-0.1.8.tgz", + "@zombienet/orchestrator": "^0.0.35", "@zombienet/utils": "^0.0.19", "cli-progress": "^3.12.0", "commander": "^10.0.1", @@ -6550,7 +6551,7 @@ }, "packages/orchestrator": { "name": "@zombienet/orchestrator", - "version": "0.0.34", + "version": "0.0.35", "license": "GPL-3.0-or-later", "dependencies": { "@polkadot/api": "^10.4.1", diff --git a/javascript/package.json b/javascript/package.json index 19b7accca..7a8fc10f4 100644 --- a/javascript/package.json +++ b/javascript/package.json @@ -13,8 +13,8 @@ "scripts": { "clean": "npm run clean --workspaces --if-present", "build": "npm run clean && npm run build --workspaces --if-present", - "lint": "eslint './**' && npx prettier -c .", - "lint:write": "eslint './**' --fix && npx prettier --write .", + "lint": "eslint './**/*.ts' && npx prettier -c .", + "lint:write": "eslint './**/*.ts' --fix && npx prettier --write .", "package": "npm run -w packages/cli package", "package:linux": "npm run -w packages/cli package:linux", "package:macos": "npm run -w packages/cli package:macos", diff --git a/javascript/packages/cli/package.json b/javascript/packages/cli/package.json index cb35cedac..e10a173d3 100644 --- a/javascript/packages/cli/package.json +++ b/javascript/packages/cli/package.json @@ -52,7 +52,7 @@ "url": "https://github.com/paritytech/zombienet/issues" }, "dependencies": { - "@zombienet/dsl-parser-wrapper": "^0.1.7", + "@zombienet/dsl-parser-wrapper": "file:./zombienet-dsl-parser-wrapper-0.1.8.tgz", "@zombienet/orchestrator": "^0.0.35", "@zombienet/utils": "^0.0.19", "cli-progress": "^3.12.0", diff --git a/javascript/packages/cli/src/actions/test.ts b/javascript/packages/cli/src/actions/test.ts index 353e024c0..03f32130c 100644 --- a/javascript/packages/cli/src/actions/test.ts +++ b/javascript/packages/cli/src/actions/test.ts @@ -8,7 +8,7 @@ import path from "path"; import { AVAILABLE_PROVIDERS } from "../constants"; /** - * Test - performs test/assertions agins the spawned network, using a set of natural + * Test - performs test/assertions against the spawned network, using a set of natural * language expressions that allow to make assertions based on metrics, logs and some * built-in function that query the network using polkadot.js * Read more here: https://paritytech.github.io/zombienet/cli/testing.html diff --git a/javascript/packages/cli/zombienet-dsl-parser-wrapper-0.1.8.tgz b/javascript/packages/cli/zombienet-dsl-parser-wrapper-0.1.8.tgz new file mode 100644 index 0000000000000000000000000000000000000000..2b535346a6c02f65633a9eb9139c7a500483e5c8 GIT binary patch literal 61189 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%0D)&+j5ECg77ge!|Hh>t{ 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 K&8@=#m;(Sr%=PsE literal 0 HcmV?d00001 diff --git a/javascript/packages/orchestrator/README.md b/javascript/packages/orchestrator/README.md index 97551ee29..a0bfd1d6c 100644 --- a/javascript/packages/orchestrator/README.md +++ b/javascript/packages/orchestrator/README.md @@ -322,6 +322,16 @@ npm install npm run build ``` +### Build `parser-wrapper` locally (optional) + +```bash +cd zombienet/crates/parser-wrapper +wasm-pack build --release --target nodejs --scope zombienet +cd zombienet/javascript +npm link ../crates/parser-wrapper/pkg/ +npm run build +``` + ### Download and install needed artifacts (optional) For an easier and faster setup of your local environment, run: From fdb6f857e379e4736ac9c9ba754b4f1f5db11b5c Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Tue, 14 Mar 2023 16:52:08 +0200 Subject: [PATCH 5/7] test-runner: Handle multiple networks --- javascript/packages/cli/src/actions/test.ts | 6 +- javascript/packages/cli/src/cli.ts | 2 +- .../packages/orchestrator/src/network.ts | 31 +++- .../orchestrator/src/providers/client.ts | 5 +- .../orchestrator/src/test-runner/index.ts | 170 ++++++++++++------ javascript/packages/orchestrator/src/types.ts | 2 +- 6 files changed, 149 insertions(+), 67 deletions(-) diff --git a/javascript/packages/cli/src/actions/test.ts b/javascript/packages/cli/src/actions/test.ts index 03f32130c..3344f062a 100644 --- a/javascript/packages/cli/src/actions/test.ts +++ b/javascript/packages/cli/src/actions/test.ts @@ -13,13 +13,13 @@ import { AVAILABLE_PROVIDERS } from "../constants"; * built-in function that query the network using polkadot.js * Read more here: https://paritytech.github.io/zombienet/cli/testing.html * @param testFile - * @param runningNetworkSpec + * @param runningNetworksSpec * @param opts (commander) * @param program (commander) */ export async function test( testFile: string, - runningNetworkSpec: string | undefined, + runningNetworksSpec: string[], cmdOpts: any, program: any, ) { @@ -67,7 +67,7 @@ export async function test( inCI, opts.spawnConcurrency, false, - runningNetworkSpec, + runningNetworksSpec, dir, ); } diff --git a/javascript/packages/cli/src/cli.ts b/javascript/packages/cli/src/cli.ts index 415e883d8..2cacd3e4f 100644 --- a/javascript/packages/cli/src/cli.ts +++ b/javascript/packages/cli/src/cli.ts @@ -107,7 +107,7 @@ program .description("Run tests on the network defined") .argument("", "ZNDSL file (.zndsl) describing the tests") .argument( - "[runningNetworkSpec]", + "[runningNetworksSpec...]", "Path to the network spec json, for using a running network for running the test", ) .action(asyncAction(test)); diff --git a/javascript/packages/orchestrator/src/network.ts b/javascript/packages/orchestrator/src/network.ts index fb4ddaa7c..2c273b7bf 100644 --- a/javascript/packages/orchestrator/src/network.ts +++ b/javascript/packages/orchestrator/src/network.ts @@ -259,7 +259,7 @@ export class Network { const nodes = this.groups[nodeOrGroupName]; if (!nodes) - throw new Error(`Noode or Group: ${nodeOrGroupName} not present`); + throw new Error(`Node or Group: ${nodeOrGroupName} not present`); return nodes; } @@ -283,6 +283,35 @@ export class Network { } } + getNetworkInfo() { + return { + tmpDir: this.tmpDir, + chainSpecPath: this.chainSpecFullPath, + relay: this.relay.map((node: any) => { + const { name, wsUri, prometheusUri, userDefinedTypes } = node; + return { name, wsUri, prometheusUri, userDefinedTypes }; + }), + paras: Object.keys(this.paras).reduce((memo: any, paraId: any) => { + const { chainSpecPath, wasmPath, statePath } = this.paras[paraId]; + memo[paraId] = { chainSpecPath, wasmPath, statePath }; + memo[paraId].nodes = this.paras[paraId].nodes.map((node) => { + return { ...node }; + }); + return memo; + }, {}), + nodesByName: Object.keys(this.nodesByName).reduce( + (memo: any, nodeName) => { + const { name, wsUri, prometheusUri, userDefinedTypes, parachainId } = + this.nodesByName[nodeName]; + memo[nodeName] = { name, wsUri, prometheusUri, userDefinedTypes }; + if (parachainId) memo[nodeName].parachainId = parachainId; + return memo; + }, + {}, + ), + }; + } + // show links for access and debug showNetworkInfo(provider: string) { const logTable = new CreateLogTable({ diff --git a/javascript/packages/orchestrator/src/providers/client.ts b/javascript/packages/orchestrator/src/providers/client.ts index ccf557be9..6b09a90ca 100644 --- a/javascript/packages/orchestrator/src/providers/client.ts +++ b/javascript/packages/orchestrator/src/providers/client.ts @@ -103,13 +103,12 @@ export abstract class Client { abstract getLogsCommand(name: string): string; } -let client: Client; +let client: Client | undefined; export function getClient(): Client { if (!client) throw new Error("Client not initialized"); return client; } -export function setClient(c: Client) { - if (client) throw new Error("Client already initialized"); +export function setClient(c: Client | undefined) { client = c; } diff --git a/javascript/packages/orchestrator/src/test-runner/index.ts b/javascript/packages/orchestrator/src/test-runner/index.ts index ca4aae127..22c546c7f 100644 --- a/javascript/packages/orchestrator/src/test-runner/index.ts +++ b/javascript/packages/orchestrator/src/test-runner/index.ts @@ -11,6 +11,7 @@ import path from "path"; import { Network, rebuildNetwork } from "../network"; import { start } from "../orchestrator"; import { Providers } from "../providers"; +import { setClient } from "../providers/client"; import { LaunchConfig, TestDefinition } from "../types"; import assertions from "./assertions"; import commands from "./commands"; @@ -26,6 +27,29 @@ export interface BackchannelMap { [propertyName: string]: any; } +function findNetwork( + networks: Network[], + nodeOrGroupName?: string, +): Network | undefined { + if (!nodeOrGroupName) { + return networks[0]; + } + + const network = networks.find((network) => { + try { + network.getNodes(nodeOrGroupName); + return true; + } catch { + // continue searching + } + }); + + if (!network) + throw new Error(`Node or Group: ${nodeOrGroupName} not present`); + + return network; +} + function showNetworkLogsLocation( network: Network, logsPath: string, @@ -92,27 +116,33 @@ export async function run( inCI = false, concurrency = 1, silent = false, - runningNetworkSpecPath: string | undefined, + runningNetworksSpec: string[] | undefined, dir: string | undefined, ) { setSilent(silent); - let network: Network; + const networks: Network[] = []; const backchannelMap: BackchannelMap = {}; let suiteName: string = testName; if (testDef.description) suiteName += `( ${testDef.description} )`; - // read network file - const networkConfigFilePath = fs.existsSync(testDef.network) - ? testDef.network - : path.resolve(configBasePath, testDef.network); + const networkConfigs: LaunchConfig[] = []; + for (let networkConfigFilePath of testDef.networks) { + // read network file + if (!fs.existsSync(networkConfigFilePath)) + networkConfigFilePath = path.resolve( + configBasePath, + networkConfigFilePath, + ); + const networkConfig = readNetworkConfig(networkConfigFilePath); - const config: LaunchConfig = readNetworkConfig(networkConfigFilePath); + // set the provider + if (!networkConfig.settings) + networkConfig.settings = { provider, timeout: DEFAULT_GLOBAL_TIMEOUT }; + else networkConfig.settings.provider = provider; - // set the provider - if (!config.settings) - config.settings = { provider, timeout: DEFAULT_GLOBAL_TIMEOUT }; - else config.settings.provider = provider; + networkConfigs.push(networkConfig); + } // find creds file const credsFile = inCI ? "config" : testDef.creds; @@ -134,52 +164,66 @@ export async function run( if (credsFileExistInPath) creds = credsFileExistInPath + "/" + credsFile; } - if (!creds && config.settings.provider === "kubernetes") - throw new Error(`Invalid credential file path: ${credsFile}`); + for (const networkConfig of networkConfigs) { + if (!creds && networkConfig.settings.provider === "kubernetes") + throw new Error(`Invalid credential file path: ${credsFile}`); + } // create suite const suite = Suite.create(mocha.suite, suiteName); suite.beforeAll("launching", async function () { - const launchTimeout = config.settings?.timeout || 500; - this.timeout(launchTimeout * 1000); - try { - if (!runningNetworkSpecPath) { - console.log(`\n\n\t Launching network... this can take a while.`); - network = await start(creds!, config, { - spawnConcurrency: concurrency, - inCI, - silent, - dir, - }); - } else { - const runningNetworkSpec: any = require(runningNetworkSpecPath); - if (provider !== runningNetworkSpec.client.providerName) - throw new Error( - `Invalid provider, the provider set doesn't match with the running network definition`, + for (const [networkConfigIdx, networkConfig] of networkConfigs.entries()) { + const launchTimeout = networkConfig.settings?.timeout || 500; + this.timeout(launchTimeout * 1000); + + const runningNetworkSpecPath = + runningNetworksSpec && runningNetworksSpec[networkConfigIdx]; + try { + if (runningNetworkSpecPath) + console.log("runningNetworkSpecPath", runningNetworkSpecPath); + + let network: Network; + if (!runningNetworkSpecPath) { + console.log( + `\n\n\t Launching network ${testDef.networks[networkConfigIdx]} ... this can take a while.`, ); + network = await start(creds!, networkConfig, { + spawnConcurrency: concurrency, + inCI, + silent, + dir, + }); + } else { + const runningNetworkSpec: any = require(runningNetworkSpecPath); + if (provider !== runningNetworkSpec.client.providerName) + throw new Error( + `Invalid provider, the provider set doesn't match with the running network definition`, + ); - const { client, namespace, tmpDir } = runningNetworkSpec; - // initialize the Client - const initClient = Providers.get( - runningNetworkSpec.client.providerName, - ).initClient(client.configPath, namespace, tmpDir); - // initialize the network - network = rebuildNetwork(initClient, runningNetworkSpec); - } - - network.showNetworkInfo(config.settings.provider); + const { client, namespace, tmpDir } = runningNetworkSpec; + // initialize the Client + const initClient = Providers.get( + runningNetworkSpec.client.providerName, + ).initClient(client.configPath, namespace, tmpDir); + // initialize the network + network = rebuildNetwork(initClient, runningNetworkSpec); + } - await sleep(5 * 1000); - return; - } catch (err) { - console.log( - `\n${decorators.red( - "Error launching the network!", - )} \t ${decorators.bright(err)}`, - ); - exitMocha(100); + networks.push(network); + network.showNetworkInfo(networkConfig.settings.provider); + } catch (err) { + console.log( + `\n${decorators.red( + "Error launching the network!", + )} \t ${decorators.bright(err)}`, + ); + exitMocha(100); + } } + + await sleep(5 * 1000); + return; }); suite.afterAll("teardown", async function () { @@ -193,12 +237,18 @@ export async function run( ); } - if (network && !network.wasRunning) { - console.log("\n"); - const logsPath = await network.dumpLogs(false); - console.log(`\n\t ${decorators.green("Deleting network")}`); - await network.stop(); - showNetworkLogsLocation(network, logsPath, inCI); + for (const [networkIdx, network] of networks.entries()) { + if (network && !network.wasRunning) { + console.log("\n"); + const logsPath = await network.dumpLogs(false); + console.log( + `\n\t ${decorators.green( + `Deleting network ${testDef.networks[networkIdx]}`, + )}`, + ); + await network.stop(); + showNetworkLogsLocation(network, logsPath, inCI); + } } return; }); @@ -217,10 +267,14 @@ export async function run( } const testFn = generator(assertion.parsed.args); - const test = new Test( - assertion.original_line, - async () => await testFn(network, backchannelMap, configBasePath), - ); + const test = new Test(assertion.original_line, async () => { + // Find the first network that contains the node and run the test on it. + const network = findNetwork(networks, assertion.parsed.args.node_name); + + setClient(network?.client); + await testFn(network, backchannelMap, configBasePath); + return; + }); suite.addTest(test); test.timeout(0); } diff --git a/javascript/packages/orchestrator/src/types.ts b/javascript/packages/orchestrator/src/types.ts index c772c963f..fc5c2f9a4 100644 --- a/javascript/packages/orchestrator/src/types.ts +++ b/javascript/packages/orchestrator/src/types.ts @@ -288,7 +288,7 @@ export interface MultiAddressByNode { } export interface TestDefinition { - network: string; + networks: string[]; creds: string; description?: string; assertions: Assertion[]; From f1d6962630d1107072533464d9293721a7f50f22 Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Wed, 22 Mar 2023 14:31:13 +0200 Subject: [PATCH 6/7] Delete all networks on error --- javascript/packages/orchestrator/src/orchestrator.ts | 4 ++-- javascript/packages/orchestrator/src/test-runner/index.ts | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/javascript/packages/orchestrator/src/orchestrator.ts b/javascript/packages/orchestrator/src/orchestrator.ts index d8a8ed1c9..6e17e4517 100644 --- a/javascript/packages/orchestrator/src/orchestrator.ts +++ b/javascript/packages/orchestrator/src/orchestrator.ts @@ -528,8 +528,8 @@ export async function start( await network.dumpLogs(); await network.stop(); } - if (cronInterval) clearInterval(cronInterval); - process.exit(1); + clearInterval(cronInterval); + throw error; } } diff --git a/javascript/packages/orchestrator/src/test-runner/index.ts b/javascript/packages/orchestrator/src/test-runner/index.ts index 22c546c7f..290ed3bc7 100644 --- a/javascript/packages/orchestrator/src/test-runner/index.ts +++ b/javascript/packages/orchestrator/src/test-runner/index.ts @@ -218,6 +218,9 @@ export async function run( "Error launching the network!", )} \t ${decorators.bright(err)}`, ); + for (const network of networks) { + await network.stop(); + } exitMocha(100); } } From b7159e90cda15cd16c31e032e627f1fe295c4101 Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Mon, 20 Mar 2023 17:23:34 +0200 Subject: [PATCH 7/7] Add multiple networks CI test + example --- .gitlab-ci.yml | 29 +++++++++++++++++++++++++++ examples/0005-multiple-networks.zndsl | 17 ++++++++++++++++ tests/0014-multiple-networks.zndsl | 16 +++++++++++++++ tests/0014-network-1.toml | 16 +++++++++++++++ tests/0014-network-2.toml | 25 +++++++++++++++++++++++ 5 files changed, 103 insertions(+) create mode 100644 examples/0005-multiple-networks.zndsl create mode 100644 tests/0014-multiple-networks.zndsl create mode 100644 tests/0014-network-1.toml create mode 100644 tests/0014-network-2.toml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2fe63effc..14fb5da9a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -300,3 +300,32 @@ db-snapshot: retry: 2 tags: - zombienet-polkadot-integration-test + +multiple-networks: + stage: deploy + <<: *kubernetes-env + image: "paritypr/zombienet:${CI_COMMIT_SHORT_SHA}" + rules: + - if: $CI_PIPELINE_SOURCE == "schedule" + - if: $CI_COMMIT_REF_NAME == "master" + - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 + # needs: + # - job: publish-docker-pr + + variables: + GH_DIR: "https://github.com/paritytech/zombienet/tree/${CI_COMMIT_SHORT_SHA}/tests" + + before_script: + - echo "Zombienet multiple networks test" + - echo "paritypr/zombienet:${CI_COMMIT_SHORT_SHA}" + - echo "${GH_DIR}" + - export DEBUG=zombie* + + script: + - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh + --test="0014-multiple-networks.zndsl" + allow_failure: true + retry: 2 + tags: + - zombienet-polkadot-integration-test diff --git a/examples/0005-multiple-networks.zndsl b/examples/0005-multiple-networks.zndsl new file mode 100644 index 000000000..24e4d88ab --- /dev/null +++ b/examples/0005-multiple-networks.zndsl @@ -0,0 +1,17 @@ +Description: Multiple Networks test +Network: ./0000-test-config-small-network.toml +Network: ./0003-big-network.toml +Creds: config + +# 0000-test-config-small-network - metrics +alice: reports node_roles is 4 +alice: reports sub_libp2p_is_major_syncing is 0 + +# 0000-test-config-small-network - logs +bob: log line matches glob "*rted #1*" within 10 seconds +bob: log line matches "Imported #[0-9]+" within 10 seconds + +# 0003-big-network - metrics +a: reports node_roles is 4 +b: reports sub_libp2p_is_major_syncing is 0 + diff --git a/tests/0014-multiple-networks.zndsl b/tests/0014-multiple-networks.zndsl new file mode 100644 index 000000000..a8fb694b8 --- /dev/null +++ b/tests/0014-multiple-networks.zndsl @@ -0,0 +1,16 @@ +Description: Multiple Networks test +Network: ./0014-network-1.toml +Network: ./0014-network-2.toml +Creds: config + +# network-1 - metrics +alice: reports node_roles is 4 +alice: reports sub_libp2p_is_major_syncing is 0 + +# network-1 - logs +bob: log line matches glob "*rted #1*" within 10 seconds +bob: log line matches "Imported #[0-9]+" within 10 seconds + +# network-2 - metrics +a: reports node_roles is 4 +b: reports sub_libp2p_is_major_syncing is 0 \ No newline at end of file diff --git a/tests/0014-network-1.toml b/tests/0014-network-1.toml new file mode 100644 index 000000000..a802cdfaf --- /dev/null +++ b/tests/0014-network-1.toml @@ -0,0 +1,16 @@ +[relaychain] +default_image = "docker.io/parity/polkadot:latest" +default_command = "polkadot" +default_args = [ "-lparachain=debug" ] + +chain = "rococo-local" + + [[relaychain.nodes]] + name = "alice" + validator = true + + [[relaychain.nodes]] + name = "bob" + image = "docker.io/parity/polkadot:latest" + validator = true + args = ["--database=paritydb-experimental"] diff --git a/tests/0014-network-2.toml b/tests/0014-network-2.toml new file mode 100644 index 000000000..0dcccc334 --- /dev/null +++ b/tests/0014-network-2.toml @@ -0,0 +1,25 @@ +[relaychain] +default_image = "docker.io/parity/polkadot:latest" +default_command = "polkadot" +default_args = [ "-lparachain=debug" ] + +chain = "rococo-local" + + [[relaychain.node_groups]] + name = "a" + args = [ "-lparachain=debug", "--database=paritydb-experimental" ] + count = 5 + + [[relaychain.node_groups]] + name = "b" + count = 5 + +[[parachains]] +id = 100 + + [[parachains.collator_groups]] + count = 2 + [parachains.collator_groups.collator] + name = "collator" + command = "polkadot-parachain" + image = "docker.io/parity/polkadot-parachain:latest" \ No newline at end of file