UNPKG

@hashgraph/solo

Version:

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

176 lines 9.09 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 __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