@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
176 lines • 9.09 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 __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import { HEDERA_HAPI_PATH, LOG_CONFIG_ZIP_SUFFIX, ROOT_CONTAINER, SOLO_LOGS_DIR } from './constants.js';
import fs from 'node:fs';
import { ContainerReference } from '../integration/kube/resources/container/container-reference.js';
import * as constants from './constants.js';
import { sleep } from './helpers.js';
import { Duration } from './time/duration.js';
import { inject, injectable } from 'tsyringe-neo';
import { patchInject } from './dependency-injection/container-helper.js';
import { InjectTokens } from './dependency-injection/inject-tokens.js';
import { PathEx } from '../business/utils/path-ex.js';
import chalk from 'chalk';
/**
* Class to manage network nodes
*/
let NetworkNodes = class NetworkNodes {
logger;
k8Factory;
constructor(logger, k8Factory) {
this.logger = logger;
this.k8Factory = k8Factory;
this.logger = patchInject(logger, InjectTokens.SoloLogger, this.constructor.name);
this.k8Factory = patchInject(k8Factory, InjectTokens.K8Factory, this.constructor.name);
}
/**
* Download logs files from all network pods and save to local solo log directory
* @param namespace - the namespace of the network
* @param [contexts]
* @param [baseDirectory] - optional base directory to save logs, defaults to SOLO_LOGS_DIR
* @param [excludeSensitiveData] - when true, omit TLS certificates, private keys, and data/keys from the archive
* @returns a promise that resolves when the logs are downloaded
*/
async getLogs(namespace, contexts, baseDirectory, excludeSensitiveData) {
const podsData = [];
if (contexts) {
for (const context of contexts) {
const pods = await this.k8Factory
.getK8(context)
.pods()
.list(namespace, ['solo.hedera.com/type=network-node']);
for (const pod of pods) {
podsData.push({ pod, context });
}
}
}
else {
const pods = await this.k8Factory.default().pods().list(namespace, ['solo.hedera.com/type=network-node']);
for (const pod of pods) {
podsData.push({ pod });
}
}
const logBaseDirectory = baseDirectory || SOLO_LOGS_DIR;
const promises = [];
for (const podData of podsData) {
promises.push(this.getLog(podData.pod, namespace, logBaseDirectory, podData.context, excludeSensitiveData));
}
this.logger.showUser(`Configurations and logs saved to ${logBaseDirectory}`);
return await Promise.all(promises);
}
async getLog(pod, namespace, baseDirectory, context, excludeSensitiveData) {
const podReference = pod.podReference;
this.logger.debug(`getNodeLogs(${pod.podReference.name.name}): begin...`);
const targetDirectory = PathEx.join(baseDirectory, namespace.name);
try {
if (!fs.existsSync(targetDirectory)) {
fs.mkdirSync(targetDirectory, { recursive: true });
}
const containerReference = ContainerReference.of(podReference, ROOT_CONTAINER);
const scriptName = 'support-zip.sh';
const sourcePath = PathEx.joinWithRealPath(constants.RESOURCES_DIR, scriptName); // script source path
const k8 = this.k8Factory.getK8(context);
const container = k8.containers().readByRef(containerReference);
await container.copyTo(sourcePath, `${HEDERA_HAPI_PATH}`);
await sleep(Duration.ofSeconds(3)); // wait for the script to sync to the file system
await container.execContainer([
'bash',
'-c',
`sync ${HEDERA_HAPI_PATH} && chown hedera:hedera ${HEDERA_HAPI_PATH}/${scriptName}`,
]);
await container.execContainer(['bash', '-c', `chmod 0755 ${HEDERA_HAPI_PATH}/${scriptName}`]);
await container.execContainer(`${HEDERA_HAPI_PATH}/${scriptName} true ${excludeSensitiveData === true ? 'true' : 'false'}`);
await container.copyFrom(`${HEDERA_HAPI_PATH}/data/${podReference.name}${LOG_CONFIG_ZIP_SUFFIX}`, targetDirectory);
this.logger.showUser(`Log zip file ${podReference.name}${LOG_CONFIG_ZIP_SUFFIX} downloaded to ${targetDirectory}`);
}
catch (error) {
// not throw error here, so we can continue to finish downloading logs from other pods
// and also delete namespace in the end
this.logger.error(`${constants.NODE_LOG_FAILURE_MSG} ${podReference}`, error);
this.logger.showUser(chalk.red(`${constants.NODE_LOG_FAILURE_MSG} ${podReference}`));
}
this.logger.debug(`getNodeLogs(${pod.podReference.name.name}): ...end`);
}
/**
* Download state files from a pod
* @param namespace - the namespace of the network
* @param nodeAlias - the pod name
* @param [context]
* @param [baseDirectory] - optional base directory to save state files, defaults to SOLO_LOGS_DIR
* @returns a promise that resolves when the state files are downloaded
*/
async getStatesFromPod(namespace, nodeAlias, context, baseDirectory) {
const pods = await this.k8Factory
.getK8(context)
.pods()
.list(namespace, [`solo.hedera.com/node-name=${nodeAlias}`, 'solo.hedera.com/type=network-node']);
// get length of pods
const stateBaseDirectory = baseDirectory || SOLO_LOGS_DIR;
const promises = [];
for (const pod of pods) {
promises.push(this.getState(pod, namespace, stateBaseDirectory, context));
}
return await Promise.all(promises);
}
async getState(pod, namespace, baseDirectory, context) {
const podReference = pod.podReference;
this.logger.debug(`getNodeState(${pod.podReference.name.name}): begin...`);
const targetDirectory = PathEx.join(baseDirectory, namespace.name);
try {
if (!fs.existsSync(targetDirectory)) {
fs.mkdirSync(targetDirectory, { recursive: true });
}
// Use zip for compression, similar to tar -czf with -C flag
const containerReference = ContainerReference.of(podReference, ROOT_CONTAINER);
const k8 = this.k8Factory.getK8(context);
const zipFileName = `${HEDERA_HAPI_PATH}/${podReference.name}-state.zip`;
// Zip doesn't have a -C flag like tar, so we use sh -c with subshell to change directory
// Use the -X to archive for cross-platform compatibility
await k8
.containers()
.readByRef(containerReference)
.execContainer([
'sh',
'-c',
`(cd ${HEDERA_HAPI_PATH}/data/saved && zip -rX ${zipFileName} . && sync && test -f ${zipFileName})`,
]);
await sleep(Duration.ofSeconds(1));
await k8.containers().readByRef(containerReference).copyFrom(`${zipFileName}`, targetDirectory);
}
catch (error) {
this.logger.error(`failed to download state from pod ${podReference.name}`, error);
this.logger.showUser(`Failed to download state from pod ${podReference.name}` + error);
}
this.logger.debug(`getNodeState(${pod.podReference.name.name}): ...end`);
}
async getNetworkNodePodStatus(podReference, context) {
return this.k8Factory
.getK8(context)
.containers()
.readByRef(ContainerReference.of(podReference, constants.ROOT_CONTAINER))
.execContainer([
'bash',
'-c',
String.raw `curl -s http://localhost:9999/metrics | grep platform_PlatformStatus | grep -v \#`,
]);
}
};
NetworkNodes = __decorate([
injectable(),
__param(0, inject(InjectTokens.SoloLogger)),
__param(1, inject(InjectTokens.K8Factory)),
__metadata("design:paramtypes", [Object, Object])
], NetworkNodes);
export { NetworkNodes };
//# sourceMappingURL=network-nodes.js.map