UNPKG

@hashgraph/solo

Version:

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

321 lines 16.7 kB
// SPDX-License-Identifier: Apache-2.0 var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var RapidFireCommand_1; import { Listr } from 'listr2'; import { SoloError } from '../core/errors/solo-error.js'; import * as constants from '../core/constants.js'; import { BaseCommand } from './base.js'; import { Flags as flags } from './flags.js'; import { ListrLock } from '../core/lock/listr-lock.js'; import { injectable } from 'tsyringe-neo'; import { NETWORK_LOAD_GENERATOR_CHART_VERSION } from '../../version.js'; import * as helpers from '../core/helpers.js'; import { ContainerReference } from '../integration/kube/resources/container/container-reference.js'; import chalk from 'chalk'; import { PassThrough } from 'node:stream'; export var NLGTestClass; (function (NLGTestClass) { NLGTestClass["HCSLoadTest"] = "HCSLoadTest"; NLGTestClass["CryptoTransferLoadTest"] = "CryptoTransferLoadTest"; NLGTestClass["NftTransferLoadTest"] = "NftTransferLoadTest"; NLGTestClass["TokenTransferLoadTest"] = "TokenTransferLoadTest"; NLGTestClass["SmartContractLoadTest"] = "SmartContractLoadTest"; NLGTestClass["HeliSwapLoadTest"] = "HeliSwapLoadTest"; NLGTestClass["LongevityLoadTest"] = "LongevityLoadTest"; })(NLGTestClass || (NLGTestClass = {})); let RapidFireCommand = class RapidFireCommand extends BaseCommand { static { RapidFireCommand_1 = this; } constructor() { super(); } static CRYPTO_TRANSFER_START_CONFIG_NAME = 'cryptoTransferStartConfig'; static STOP_CONFIG_NAME = 'stopConfig'; static START_FLAGS_LIST = { required: [flags.deployment, flags.nlgArguments, flags.performanceTest], optional: [ flags.devMode, flags.force, flags.quiet, flags.valuesFile, flags.javaHeap, flags.packageName, flags.maxTps, ], }; static STOP_FLAGS_LIST = { required: [flags.deployment, flags.performanceTest], optional: [flags.devMode, flags.force, flags.quiet, flags.packageName], }; static DESTROY_FLAGS_LIST = { required: [flags.deployment], optional: [flags.devMode, flags.force, flags.quiet], }; nglChartIsDeployed(context_) { return this.chartManager.isChartInstalled(context_.config.namespace, constants.NETWORK_LOAD_GENERATOR_RELEASE_NAME, context_.config.context); } deployNlgChart() { return { title: 'Deploy Network Load Generator chart', task: (context_, task) => { const subTasks = [ { title: 'Install Network Load Generator chart', task: async (context_) => { let valuesArgument = helpers.prepareValuesFiles(constants.RAPID_FIRE_VALUES_FILE); if (context_.config.valuesFile) { valuesArgument += helpers.prepareValuesFiles(context_.config.valuesFile); } const haproxyPods = await this.k8Factory .getK8(context_.config.context) .pods() .list(context_.config.namespace, ['solo.hedera.com/type=haproxy']); const port = constants.GRPC_PORT; const networkProperties = haproxyPods.map((pod) => { const accountId = pod.labels['solo.hedera.com/account-id'] ?? 'unknown'; // Using multiple backslashes to ensure it is not stripped when the network.properties file is generated // Final result should look like: x.x.x.x\:50211=0.0.y return String.raw `${pod.podIp}\\\:${port}=${accountId}`; }); for (const row of networkProperties) { valuesArgument += ` --set loadGenerator.properties[${networkProperties.indexOf(row)}]="${row}"`; } await this.chartManager.install(context_.config.namespace, constants.NETWORK_LOAD_GENERATOR_RELEASE_NAME, constants.NETWORK_LOAD_GENERATOR_CHART, constants.NETWORK_LOAD_GENERATOR_CHART_URL, NETWORK_LOAD_GENERATOR_CHART_VERSION, valuesArgument, context_.config.context); }, }, { title: 'Check NLG pod is ready', task: async ({ config }) => { await this.k8Factory .getK8(config.context) .pods() .waitForReadyStatus(config.namespace, constants.NETWORK_LOAD_GENERATOR_POD_LABELS, constants.NETWORK_LOAD_GENERATOR_POD_RUNNING_MAX_ATTEMPTS, constants.NETWORK_LOAD_GENERATOR_POD_RUNNING_DELAY); }, }, { title: 'Install libraries in NLG pod', task: async ({ config }) => { const nlgPods = await this.k8Factory .getK8(config.context) .pods() .list(config.namespace, constants.NETWORK_LOAD_GENERATOR_POD_LABELS); const k8Containers = this.k8Factory.getK8(config.context).containers(); for (const pod of nlgPods) { const containerReference = ContainerReference.of(pod.podReference, constants.NETWORK_LOAD_GENERATOR_CONTAINER); const container = k8Containers.readByRef(containerReference); await container.execContainer('apt-get update -qq'); await container.execContainer('apt-get install -y libsodium23'); await container.execContainer('apt-get clean -qq'); } }, }, ]; // set up the sub-tasks return task.newListr(subTasks, { concurrent: false, // no need to run concurrently since if one node is up, the rest should be up by then rendererOptions: { collapseSubtasks: false, }, }); }, skip: this.nglChartIsDeployed.bind(this), }; } startLoadTest(leaseReference) { return { title: 'Start performance load test', task: async (context_, task) => { const { performanceTest, packageName } = context_.config; const testClass = `${packageName}.${performanceTest}`; task.title = `Start performance load test: ${testClass}`; const nlgPods = await this.k8Factory .getK8(context_.config.context) .pods() .list(context_.config.namespace, constants.NETWORK_LOAD_GENERATOR_POD_LABELS); const k8Containers = this.k8Factory.getK8(context_.config.context).containers(); for (const pod of nlgPods) { const containerReference = ContainerReference.of(pod.podReference, constants.NETWORK_LOAD_GENERATOR_CONTAINER); const container = k8Containers.readByRef(containerReference); const outputStream = new PassThrough(); const errorStream = new PassThrough(); for (const stream_ of [errorStream, outputStream]) { stream_.on('data', (chunk) => { const string_ = chunk.toString(); task.output = (task.output || '') + chalk.gray(string_); }); } try { if (!this.oneShotState.isActive()) { await leaseReference.lease?.release(); } const tpsSetting = context_.config.maxTps ? `-Dbenchmark.maxtps=${context_.config.maxTps}` : ''; let commandString = `/usr/bin/env java -Xmx${context_.config.javaHeap}g ${tpsSetting} -cp /app/lib/*:/app/network-load-generator-${NETWORK_LOAD_GENERATOR_CHART_VERSION}.jar ${testClass} ${context_.config.parsedNlgArguments}`; commandString = commandString.replaceAll(' ', ' ').trim(); await container.execContainer(commandString, outputStream, errorStream); } catch (error) { throw new SoloError(`Error running ${testClass} load test: ${error.message}`, error); } if (task.output) { const showOutput = '> ' + task.output.replaceAll('\n', '\n '); this.logger.showUser(showOutput); } } }, }; } async start(argv) { const leaseReference = {}; // This allows the lease to be passed by reference to the init task const tasks = new Listr([ { title: 'Initialize', task: async (context_, task) => { await this.localConfig.load(); await this.remoteConfig.loadAndValidate(argv); if (!this.oneShotState.isActive()) { leaseReference.lease = await this.leaseManager.create(); } this.configManager.update(argv); flags.disablePrompts(RapidFireCommand_1.START_FLAGS_LIST.optional); const allFlags = [ ...RapidFireCommand_1.START_FLAGS_LIST.required, ...RapidFireCommand_1.START_FLAGS_LIST.optional, ]; await this.configManager.executePrompt(task, allFlags); const config = this.configManager.getConfig(RapidFireCommand_1.CRYPTO_TRANSFER_START_CONFIG_NAME, allFlags, ['parsedNlgArguments']); context_.config = config; config.namespace = await this.getNamespace(task); config.clusterRef = this.getClusterReference(); config.context = this.getClusterContext(config.clusterRef); // Parse nlgArguments to remove any surrounding quotes config.parsedNlgArguments = config.nlgArguments.replaceAll("'", '').replaceAll('"', ''); if (!this.oneShotState.isActive()) { return ListrLock.newAcquireLockTask(leaseReference.lease, task); } return ListrLock.newSkippedLockTask(task); }, }, this.deployNlgChart(), this.startLoadTest(leaseReference), ], constants.LISTR_DEFAULT_OPTIONS.DEFAULT); try { await tasks.run(); } catch (error) { throw new SoloError(`Error running rapid-fire: ${error.message}`, error); } finally { if (!this.oneShotState.isActive()) { await leaseReference.lease?.release(); } } return true; } stopInitializeTask(argv, leaseReference) { return { title: 'Initialize', task: async (context_, task) => { await this.localConfig.load(); await this.remoteConfig.loadAndValidate(argv); if (!this.oneShotState.isActive()) { leaseReference.lease = await this.leaseManager.create(); } this.configManager.update(argv); flags.disablePrompts(RapidFireCommand_1.STOP_FLAGS_LIST.optional); const allFlags = [ ...RapidFireCommand_1.STOP_FLAGS_LIST.required, ...RapidFireCommand_1.STOP_FLAGS_LIST.optional, ]; await this.configManager.executePrompt(task, allFlags); const config = this.configManager.getConfig(RapidFireCommand_1.STOP_CONFIG_NAME, allFlags); config.namespace = await this.getNamespace(task); config.clusterRef = this.getClusterReference(); config.context = this.getClusterContext(config.clusterRef); context_.config = config; if (!this.oneShotState.isActive()) { return ListrLock.newAcquireLockTask(leaseReference.lease, task); } return ListrLock.newSkippedLockTask(task); }, }; } async allStopTasks(argv, stopTask) { const leaseReference = {}; // This allows the lease to be passed by reference to the init task const tasks = new Listr([this.stopInitializeTask(argv, leaseReference), stopTask], constants.LISTR_DEFAULT_OPTIONS.DEFAULT); try { await tasks.run(); } catch (error) { throw new SoloError(`Error running rapid-fire stop: ${error.message}`, error); } finally { if (!this.oneShotState.isActive() && leaseReference.lease) { await leaseReference.lease.release(); } } return true; } stopLoadTest() { return { title: 'Stop load test', task: async (context_, task) => { const { performanceTest, packageName } = context_.config; const testClass = `${packageName}.${performanceTest}`; task.title = `Stop load test: ${testClass}`; const nlgPods = await this.k8Factory .getK8(context_.config.context) .pods() .list(context_.config.namespace, constants.NETWORK_LOAD_GENERATOR_POD_LABELS); const k8Containers = this.k8Factory.getK8(context_.config.context).containers(); for (const pod of nlgPods) { const containerReference = ContainerReference.of(pod.podReference, constants.NETWORK_LOAD_GENERATOR_CONTAINER); const container = k8Containers.readByRef(containerReference); try { await container.execContainer(`pkill -f ${testClass}`); } catch (error) { throw new SoloError(`Error stopping ${testClass} load test: ${error.message}`, error); } } }, }; } async stop(argv) { const leaseReference = {}; // This allows the lease to be passed by reference to the init task const tasks = new Listr([this.stopInitializeTask(argv, leaseReference), this.stopLoadTest()], constants.LISTR_DEFAULT_OPTIONS.DEFAULT); try { await tasks.run(); } catch (error) { throw new SoloError(`Error running rapid-fire stop: ${error.message}`, error); } finally { if (!this.oneShotState.isActive() && leaseReference.lease) { await leaseReference.lease.release(); } } return true; } async destroy(argv) { return this.allStopTasks(argv, { title: 'Uninstall Network Load Generator chart', task: async (context_) => { await this.chartManager.uninstall(context_.config.namespace, constants.NETWORK_LOAD_GENERATOR_RELEASE_NAME, context_.config.context); }, }); } async close() { } // no-op }; RapidFireCommand = RapidFireCommand_1 = __decorate([ injectable(), __metadata("design:paramtypes", []) ], RapidFireCommand); export { RapidFireCommand }; //# sourceMappingURL=rapid-fire.js.map