UNPKG

@hashgraph/solo

Version:

An opinionated CLI tool to deploy and manage private Hedera Networks.

263 lines 17 kB
// SPDX-License-Identifier: Apache-2.0 import { BaseCommandTest } from './base-command-test.js'; import { main } from '../../../../src/index.js'; import { Flags } from '../../../../src/commands/flags.js'; import { InjectTokens } from '../../../../src/core/dependency-injection/inject-tokens.js'; import { expect } from 'chai'; import { container } from 'tsyringe-neo'; import { DeploymentCommandDefinition } from '../../../../src/commands/command-definitions/deployment-command-definition.js'; import fs from 'node:fs/promises'; import yaml from 'yaml'; import { PathEx } from '../../../../src/business/utils/path-ex.js'; export class DeploymentTest extends BaseCommandTest { static soloDeploymentCreateArgv(testName, deployment, namespace, realm, shard) { const { newArgv, optionFromFlag, argvPushGlobalFlags } = DeploymentTest; const argv = newArgv(); argv.push(DeploymentCommandDefinition.COMMAND_NAME, DeploymentCommandDefinition.CONFIG_SUBCOMMAND_NAME, DeploymentCommandDefinition.CONFIG_CREATE, optionFromFlag(Flags.deployment), deployment, optionFromFlag(Flags.namespace), namespace.name, optionFromFlag(Flags.realm), String(realm), optionFromFlag(Flags.shard), String(shard)); argvPushGlobalFlags(argv, testName); return argv; } static create(options) { const { testName, testLogger, deployment, namespace, realm, shard } = options; const { soloDeploymentCreateArgv } = DeploymentTest; it(`${testName}: solo deployment config create`, async () => { testLogger.info(`${testName}: beginning solo deployment config create`); await main(soloDeploymentCreateArgv(testName, deployment, namespace, realm, shard)); // TODO check that the deployment was created testLogger.info(`${testName}: finished solo deployment config create`); }); } static soloDeploymentAddClusterArgv(testName, deployment, clusterReference, numberOfNodes) { const { newArgv, optionFromFlag, argvPushGlobalFlags } = DeploymentTest; const argv = newArgv(); argv.push(DeploymentCommandDefinition.COMMAND_NAME, DeploymentCommandDefinition.CLUSTER_SUBCOMMAND_NAME, DeploymentCommandDefinition.CLUSTER_ATTACH, optionFromFlag(Flags.deployment), deployment, optionFromFlag(Flags.clusterRef), clusterReference, optionFromFlag(Flags.numberOfConsensusNodes), numberOfNodes.toString()); argvPushGlobalFlags(argv, testName); return argv; } static addCluster(options) { const { testName, testLogger, deployment, clusterReferenceNameArray, consensusNodesCount } = options; const { soloDeploymentAddClusterArgv } = DeploymentTest; it(`${testName}: solo deployment cluster attach`, async () => { testLogger.info(`${testName}: beginning solo deployment cluster attach`); // Compute distribution const clusterCount = clusterReferenceNameArray.length; const base = Math.floor(consensusNodesCount / clusterCount); const remainder = consensusNodesCount % clusterCount; const nodeCountsPerCluster = clusterReferenceNameArray.map((_, index) => index < remainder ? base + 1 : base); // Now attach clusters with correct node count for (const [index, element] of clusterReferenceNameArray.entries()) { const nodeCount = nodeCountsPerCluster[index]; await main(soloDeploymentAddClusterArgv(testName, deployment, element, nodeCount)); } const remoteConfig = container.resolve(InjectTokens.RemoteConfigRuntimeState); expect(remoteConfig.isLoaded(), 'remote config manager should be loaded').to.be.true; const consensusNodes = remoteConfig.configuration.components.state.consensusNodes; expect(Object.entries(consensusNodes).length, `consensus node count should be ${consensusNodesCount}`).to.equal(consensusNodesCount); // Verify each cluster received the expected number of nodes (not a fixed index mapping). const nodeCountsByCluster = new Map(); for (const node of Object.values(consensusNodes)) { const cluster = node.metadata.cluster; nodeCountsByCluster.set(cluster, (nodeCountsByCluster.get(cluster) ?? 0) + 1); } for (const [index, element] of clusterReferenceNameArray.entries()) { expect(nodeCountsByCluster.get(element) ?? 0, `cluster ${element} should have ${nodeCountsPerCluster[index]} node(s)`).to.equal(nodeCountsPerCluster[index]); } testLogger.info(`${testName}: finished solo deployment cluster attach`); }); } static soloDeploymentDiagnosticsLogsArgv(deployment) { const { newArgv, optionFromFlag } = DeploymentTest; const argv = newArgv(); argv.push(DeploymentCommandDefinition.COMMAND_NAME, DeploymentCommandDefinition.DIAGNOSTICS_SUBCOMMAND_NAME, DeploymentCommandDefinition.DIAGNOSTICS_LOGS, optionFromFlag(Flags.deployment), deployment); return argv; } static soloDeploymentConfigCreateArgv(deployment, namespace) { const { newArgv, optionFromFlag } = DeploymentTest; const argv = newArgv(); argv.push(DeploymentCommandDefinition.COMMAND_NAME, DeploymentCommandDefinition.CONFIG_SUBCOMMAND_NAME, DeploymentCommandDefinition.CONFIG_CREATE, optionFromFlag(Flags.deployment), deployment, optionFromFlag(Flags.namespace), namespace.name); return argv; } static soloDeploymentClusterAttachArgv(deployment, clusterReference, consensusNodesCount) { const { newArgv, optionFromFlag } = DeploymentTest; const argv = newArgv(); argv.push(DeploymentCommandDefinition.COMMAND_NAME, DeploymentCommandDefinition.CLUSTER_SUBCOMMAND_NAME, DeploymentCommandDefinition.CLUSTER_ATTACH, optionFromFlag(Flags.clusterRef), clusterReference, optionFromFlag(Flags.deployment), deployment, optionFromFlag(Flags.numberOfConsensusNodes), consensusNodesCount.toString()); return argv; } static soloDeploymentConfigListArgv(testName, clusterReference) { const { newArgv, optionFromFlag, argvPushGlobalFlags } = DeploymentTest; const argv = newArgv(); argv.push(DeploymentCommandDefinition.COMMAND_NAME, DeploymentCommandDefinition.CONFIG_SUBCOMMAND_NAME, DeploymentCommandDefinition.CONFIG_LIST); if (clusterReference) { argv.push(optionFromFlag(Flags.clusterRef), clusterReference); } argvPushGlobalFlags(argv, testName); return argv; } /** * Lists all local deployment configurations or deployments in a specific cluster. * Tests both scenarios: * 1. Without cluster-ref: Lists all deployments from local configuration * 2. With cluster-ref: Lists deployments from the specified Kubernetes cluster */ static listDeployments(options) { const { testName, testLogger, clusterReferenceNameArray } = options; const { soloDeploymentConfigListArgv } = DeploymentTest; it(`${testName}: solo deployment config list (without cluster-ref)`, async () => { testLogger.info(`${testName}: beginning solo deployment config list without cluster-ref`); await main(soloDeploymentConfigListArgv(testName)); testLogger.info(`${testName}: finished solo deployment config list without cluster-ref`); }); if (clusterReferenceNameArray && clusterReferenceNameArray.length > 0) { it(`${testName}: solo deployment config list (with cluster-ref)`, async () => { testLogger.info(`${testName}: beginning solo deployment config list with cluster-ref`); await main(soloDeploymentConfigListArgv(testName, clusterReferenceNameArray[0])); testLogger.info(`${testName}: finished solo deployment config list with cluster-ref`); }); } } static soloDeploymentConfigInfoArgv(testName, deployment) { const { newArgv, optionFromFlag, argvPushGlobalFlags } = DeploymentTest; const argv = newArgv(); argv.push(DeploymentCommandDefinition.COMMAND_NAME, DeploymentCommandDefinition.CONFIG_SUBCOMMAND_NAME, DeploymentCommandDefinition.CONFIG_INFO, optionFromFlag(Flags.deployment), deployment); argvPushGlobalFlags(argv, testName); return argv; } static info(options) { const { testName, testLogger, deployment } = options; const { soloDeploymentConfigInfoArgv } = DeploymentTest; it(`${testName}: solo deployment config info`, async () => { testLogger.info(`${testName}: beginning solo deployment config info`); await main(soloDeploymentConfigInfoArgv(testName, deployment)); testLogger.info(`${testName}: finished solo deployment config info`); }); } static verifyDeploymentConfigInfo(options) { const { testName, testLogger, deployment, namespace } = options; const { soloDeploymentConfigInfoArgv, runMainAndCaptureOutputToJson } = DeploymentTest; it(`${testName}: verify deployment config info output`, async () => { testLogger.info(`${testName}: beginning deployment config info output verification`); const { stdout, outputFilePath } = await runMainAndCaptureOutputToJson(soloDeploymentConfigInfoArgv(testName, deployment), { testName, outputFileName: 'deployment-config-info-output.json', metadata: { command: 'deployment config info', deployment, namespace: namespace.name, }, }); expect(stdout).to.contain('Deployment:'); expect(stdout).to.contain(deployment); expect(stdout).to.contain('Namespace:'); expect(stdout).to.contain(namespace.name); testLogger.info(`${testName}: deployment config info output saved to ${outputFilePath}`); testLogger.info(`${testName}: finished deployment config info output verification`); }); } static soloDeploymentConfigPortsArgv(testName, deployment, clusterReference, output, cacheDirectory) { const { newArgv, optionFromFlag, argvPushGlobalFlags } = DeploymentTest; const argv = newArgv(); argv.push(DeploymentCommandDefinition.COMMAND_NAME, DeploymentCommandDefinition.CONFIG_SUBCOMMAND_NAME, DeploymentCommandDefinition.CONFIG_PORTS, optionFromFlag(Flags.deployment), deployment, optionFromFlag(Flags.clusterRef), clusterReference, optionFromFlag(Flags.cacheDir), cacheDirectory); if (output) { argv.push(optionFromFlag(Flags.output), output); } argvPushGlobalFlags(argv, testName); return argv; } static verifyDeploymentConfigPorts(options) { const { testName, testLogger, deployment, clusterReferenceNameArray, namespace, testCacheDirectory } = options; const { soloDeploymentConfigPortsArgv, runMainAndCaptureOutputToJson, assertPortsFile } = DeploymentTest; it(`${testName}: verify deployment config ports output`, async () => { testLogger.info(`${testName}: beginning deployment config ports output verification`); const clusterReference = clusterReferenceNameArray[0]; const outputDirectory = PathEx.join(testCacheDirectory, 'output'); const jsonPortsFile = PathEx.join(outputDirectory, 'forwarded-ports.json'); const yamlPortsFile = PathEx.join(outputDirectory, 'forwarded-ports.yaml'); await fs.rm(jsonPortsFile, { force: true }); await fs.rm(yamlPortsFile, { force: true }); console.log(options); const wideResult = await runMainAndCaptureOutputToJson(soloDeploymentConfigPortsArgv(testName, deployment, clusterReference, 'wide', testCacheDirectory), { testName, outputFileName: 'deployment-config-ports-wide-output.json', metadata: { command: 'deployment config ports', deployment, namespace: namespace.name, clusterReference, output: 'wide', }, }); expect(wideResult.stdout).to.contain('Port-forwards for deployment'); expect(wideResult.stdout).to.contain(deployment); expect(wideResult.stdout).to.contain('Cluster:'); expect(wideResult.stdout).to.contain(clusterReference); expect(wideResult.stdout).to.contain('Namespace:'); expect(wideResult.stdout).to.contain('Consensus node gRPC'); expect(wideResult.stdout).to.contain('Mirror node REST'); expect(wideResult.stdout).to.contain('JSON-RPC relay'); expect(wideResult.stdout).to.contain('Explorer'); const jsonResult = await runMainAndCaptureOutputToJson(soloDeploymentConfigPortsArgv(testName, deployment, clusterReference, 'json', testCacheDirectory), { testName, outputFileName: 'deployment-config-ports-json-output.json', metadata: { command: 'deployment config ports', deployment, namespace: namespace.name, clusterReference, output: 'json', }, }); expect(jsonResult.stdout).to.contain('"deployment"'); expect(jsonResult.stdout).to.contain(deployment); expect(jsonResult.stdout).to.contain('"clusterReference"'); expect(jsonResult.stdout).to.contain(clusterReference); expect(jsonResult.stdout).to.contain('"namespace"'); expect(jsonResult.stdout).to.contain('"services"'); expect(jsonResult.stdout).to.contain('"consensusNodeGrpc"'); expect(jsonResult.stdout).to.contain('"mirrorNodeRest"'); expect(jsonResult.stdout).to.contain('"jsonRpcRelay"'); expect(jsonResult.stdout).to.contain('"explorer"'); expect(jsonResult.stdout).to.contain('"blockNode"'); await assertPortsFile(jsonPortsFile, 'json', deployment, clusterReference); const yamlResult = await runMainAndCaptureOutputToJson(soloDeploymentConfigPortsArgv(testName, deployment, clusterReference, 'yaml', testCacheDirectory), { testName, outputFileName: 'deployment-config-ports-yaml-output.json', metadata: { command: 'deployment config ports', deployment, namespace: namespace.name, clusterReference, output: 'yaml', }, }); expect(yamlResult.stdout).to.contain('deployment:'); expect(yamlResult.stdout).to.contain(deployment); expect(yamlResult.stdout).to.contain('clusterReference:'); expect(yamlResult.stdout).to.contain(clusterReference); expect(yamlResult.stdout).to.contain('namespace:'); expect(yamlResult.stdout).to.contain('services:'); expect(yamlResult.stdout).to.contain('consensusNodeGrpc:'); expect(yamlResult.stdout).to.contain('mirrorNodeRest:'); expect(yamlResult.stdout).to.contain('jsonRpcRelay:'); expect(yamlResult.stdout).to.contain('explorer:'); expect(yamlResult.stdout).to.contain('blockNode:'); await assertPortsFile(yamlPortsFile, 'yaml', deployment, clusterReference); testLogger.info(`${testName}: deployment config ports wide output saved to ${wideResult.outputFilePath}`); testLogger.info(`${testName}: deployment config ports json output saved to ${jsonResult.outputFilePath}`); testLogger.info(`${testName}: deployment config ports yaml output saved to ${yamlResult.outputFilePath}`); testLogger.info(`${testName}: finished deployment config ports output verification`); }); } static async assertPortsFile(filePath, format, deployment, clusterReference) { const raw = await fs.readFile(filePath, 'utf8'); const parsed = format === 'json' ? JSON.parse(raw) : yaml.parse(raw); expect(parsed.deployment).to.equal(deployment); expect(parsed.clusterReference).to.equal(clusterReference); expect(parsed.services).to.be.an('object'); expect(parsed.services).to.have.property('consensusNodeGrpc').that.is.an('array'); expect(parsed.services).to.have.property('mirrorNodeRest').that.is.an('array'); expect(parsed.services).to.have.property('jsonRpcRelay').that.is.an('array'); expect(parsed.services).to.have.property('explorer').that.is.an('array'); expect(parsed.services).to.have.property('blockNode').that.is.an('array'); } } //# sourceMappingURL=deployment-test.js.map