@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
321 lines • 16.7 kB
JavaScript
// 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