From 62338a80b18028c58903c95d2b994dc0b8f8073c Mon Sep 17 00:00:00 2001 From: instamenta Date: Wed, 12 Feb 2025 02:10:42 +0200 Subject: [PATCH 1/3] switched from node aliases to consesus nodes in order to have context for each node Signed-off-by: instamenta --- src/commands/base.ts | 3 +- src/commands/node/configs.ts | 20 +-- src/commands/node/handlers.ts | 13 +- src/commands/node/tasks.ts | 239 ++++++++++++++++++++++++++++++- src/core/model/consensus_node.ts | 3 +- src/core/platform_installer.ts | 63 ++++---- 6 files changed, 299 insertions(+), 42 deletions(-) diff --git a/src/commands/base.ts b/src/commands/base.ts index 80249ab37..bef337bcf 100644 --- a/src/commands/base.ts +++ b/src/commands/base.ts @@ -22,6 +22,7 @@ import * as constants from '../core/constants.js'; import fs from 'fs'; import {Task} from '../core/task.js'; import {ConsensusNode} from '../core/model/consensus_node.js'; +import {type NodeAlias} from '../types/aliases.js'; export interface CommandHandlers { parent: BaseCommand; @@ -258,7 +259,7 @@ export abstract class BaseCommand extends ShellRunner { Object.values(this.getRemoteConfigManager().components.consensusNodes).forEach(node => { consensusNodes.push( new ConsensusNode( - node.name, + node.name as NodeAlias, node.nodeId, node.namespace, node.cluster, diff --git a/src/commands/node/configs.ts b/src/commands/node/configs.ts index 590808037..065769b60 100644 --- a/src/commands/node/configs.ts +++ b/src/commands/node/configs.ts @@ -18,6 +18,8 @@ import {type NodeAddConfigClass} from './node_add_config.js'; import {type NamespaceName} from '../../core/kube/resources/namespace/namespace_name.js'; import {type PodRef} from '../../core/kube/resources/pod/pod_ref.js'; import {type K8Factory} from '../../core/kube/k8_factory.js'; +import {type NodeCommandHandlers} from './handlers.js'; +import {type ConsensusNode} from '../../core/model/consensus_node.js'; export const PREPARE_UPGRADE_CONFIGS_NAME = 'prepareUpgradeConfig'; export const DOWNLOAD_GENERATED_FILES_CONFIGS_NAME = 'downloadGeneratedFilesConfig'; @@ -344,20 +346,18 @@ export const startConfigBuilder = async function (argv, ctx, task) { return config; }; -export const setupConfigBuilder = async function (argv, ctx, task) { - const config = this.getConfig(SETUP_CONFIGS_NAME, argv.flags, [ +export const setupConfigBuilder = async function (this: NodeCommandHandlers, argv, ctx, task) { + ctx.config = this.getConfig(SETUP_CONFIGS_NAME, argv.flags, [ 'nodeAliases', 'podRefs', 'namespace', ]) as NodeSetupConfigClass; - config.namespace = await resolveNamespaceFromDeployment(this.parent.localConfig, this.configManager, task); - config.nodeAliases = helpers.parseNodeAliases(config.nodeAliasesUnparsed); - - await initializeSetup(config, this.k8Factory); + ctx.config.namespace = await resolveNamespaceFromDeployment(this.parent.localConfig, this.configManager, task); + ctx.config.nodeAliases = helpers.parseNodeAliases(ctx.config.nodeAliasesUnparsed); + ctx.config.consensusNodes = this.parent.getConsensusNodes(); - // set config in the context for later tasks to use - ctx.config = config; + await initializeSetup(ctx.config, this.k8Factory); return ctx.config; }; @@ -448,6 +448,10 @@ export interface NodeSetupConfigClass { releaseTag: string; nodeAliases: NodeAliases; podRefs: Record; + consensusNodes: ConsensusNode[]; + skipStop?: boolean; + keysDir: string; + stagingDir: string; getUnusedConfigs: () => string[]; } diff --git a/src/commands/node/handlers.ts b/src/commands/node/handlers.ts index 835911bda..ffc862b8e 100644 --- a/src/commands/node/handlers.ts +++ b/src/commands/node/handlers.ts @@ -42,18 +42,19 @@ import {type ComponentsDataWrapper} from '../../core/config/remote/components_da import {type Optional} from '../../types/index.js'; import {type NamespaceName} from '../../core/kube/resources/namespace/namespace_name.js'; import {Templates} from '../../core/templates.js'; +import {type CommandFlag} from '../../types/flag_types.js'; export class NodeCommandHandlers implements CommandHandlers { private readonly accountManager: AccountManager; - private readonly configManager: ConfigManager; + readonly configManager: ConfigManager; private readonly platformInstaller: PlatformInstaller; private readonly logger: SoloLogger; - private readonly k8Factory: K8Factory; + readonly k8Factory: K8Factory; private readonly tasks: NodeCommandTasks; private readonly leaseManager: LeaseManager; public readonly remoteConfigManager: RemoteConfigManager; - private getConfig: any; + public getConfig: (configName: string, flags: CommandFlag[], extraProperties?: string[]) => object; private prepareChartPath: any; public readonly parent: BaseCommand; @@ -824,9 +825,9 @@ export class NodeCommandHandlers implements CommandHandlers { this.validateAllNodeStates({ acceptedStates: [ConsensusNodeStates.INITIALIZED], }), - this.tasks.identifyNetworkPods(), - this.tasks.fetchPlatformSoftware('nodeAliases'), - this.tasks.setupNetworkNodes('nodeAliases', true), + this.tasks.identifyNetworkPodsMultiple(), + this.tasks.fetchPlatformSoftwareMultiple(), + this.tasks.setupNetworkNodesMultiple(true), this.changeAllNodeStates(ConsensusNodeStates.SETUP), ], { diff --git a/src/commands/node/tasks.ts b/src/commands/node/tasks.ts index 2a7d5085e..77e9678ee 100644 --- a/src/commands/node/tasks.ts +++ b/src/commands/node/tasks.ts @@ -54,7 +54,12 @@ import {type Listr, type ListrTaskWrapper} from 'listr2'; import {type ConfigBuilder, type NodeAlias, type NodeAliases, type SkipCheck} from '../../types/aliases.js'; import {PodName} from '../../core/kube/resources/pod/pod_name.js'; import {NodeStatusCodes, NodeStatusEnums, NodeSubcommandType} from '../../core/enumerations.js'; -import {type NodeDeleteConfigClass, type NodeRefreshConfigClass, type NodeUpdateConfigClass} from './configs.js'; +import { + type NodeDeleteConfigClass, + type NodeRefreshConfigClass, + NodeSetupConfigClass, + type NodeUpdateConfigClass, +} from './configs.js'; import {type Lease} from '../../core/lease/lease.js'; import {ListrLease} from '../../core/lease/listr_lease.js'; import {Duration} from '../../core/time/duration.js'; @@ -68,6 +73,8 @@ import {ContainerRef} from '../../core/kube/resources/container/container_ref.js import {NetworkNodes} from '../../core/network_nodes.js'; import {container} from 'tsyringe-neo'; import * as helpers from '../../core/helpers.js'; +import {type SoloListrTask, SoloListrTaskWrapper} from '../../types/index.js'; +import {ConsensusNode} from '../../core/model/consensus_node.js'; export class NodeCommandTasks { private readonly accountManager: AccountManager; @@ -826,6 +833,234 @@ export class NodeCommandTasks { }); } + //// TODO custom ------------------------ + public identifyNetworkPodsMultiple(maxAttempts?: number) { + const self = this; + return new Task( + 'Identify network pods', + (ctx: {config: NodeSetupConfigClass}, task: SoloListrTaskWrapper<{config: NodeSetupConfigClass}>) => { + return self.taskCheckNetworkNodePodsMultiple(ctx, task, maxAttempts); + }, + ); + } + + public taskCheckNetworkNodePodsMultiple( + ctx: {config: NodeSetupConfigClass}, + task: SoloListrTaskWrapper, + maxAttempts?: number, + ): Listr { + ctx.config.podRefs = {}; + const consensusNodes = ctx.config.consensusNodes; + + const subTasks: SoloListrTask<{config: NodeSetupConfigClass}>[] = []; + const self = this; + for (const consensusNode of consensusNodes) { + subTasks.push({ + title: `Check network pod: ${chalk.yellow(consensusNode.name)}`, + task: async ctx => { + try { + ctx.config.podRefs[consensusNode.name] = await self.checkNetworkNodePodMultiple( + ctx.config.namespace, + consensusNode, + maxAttempts, + ); + } catch { + ctx.config.skipStop = true; + } + }, + }); + } + + // setup the sub-tasks + return task.newListr(subTasks, { + concurrent: true, + rendererOptions: { + collapseSubtasks: false, + }, + }); + } + + async checkNetworkNodePodMultiple( + namespace: NamespaceName, + consensusNode: ConsensusNode, + maxAttempts = constants.PODS_RUNNING_MAX_ATTEMPTS, + delay = constants.PODS_RUNNING_DELAY, + ) { + const podName = Templates.renderNetworkPodName(consensusNode.name); + const podRef = PodRef.of(namespace, podName); + + try { + await this.k8Factory + .getK8(consensusNode.context) + .pods() + .waitForRunningPhase( + namespace, + [`solo.hedera.com/node-name=${consensusNode.name}`, 'solo.hedera.com/type=network-node'], + maxAttempts, + delay, + ); + + return podRef; + } catch (e) { + throw new SoloError(`no pod found for nodeAlias: ${consensusNode.name}`, e); + } + } + + fetchPlatformSoftwareMultiple(): SoloListrTask<{config: NodeSetupConfigClass}> { + const self = this; + return { + title: 'Fetch platform software into network nodes', + task: (ctx, task) => { + const {podRefs, releaseTag, localBuildPath} = ctx.config; + + if (localBuildPath !== '') { + return self._uploadPlatformSoftwareMultiple(ctx.config.consensusNodes, podRefs, task, localBuildPath); + } + return self._fetchPlatformSoftwareMultiple( + ctx.config.consensusNodes, + podRefs, + releaseTag, + task, + this.platformInstaller, + ); + }, + }; + } + + public _uploadPlatformSoftwareMultiple( + consensusNodes: ConsensusNode[], + podRefs: Record, + task: SoloListrTaskWrapper, + localBuildPath: string, + ) { + const self = this; + const subTasks: SoloListrTask<{config: NodeSetupConfigClass}>[] = []; + + this.logger.debug('no need to fetch, use local build jar files'); + + const buildPathMap = new Map(); + let defaultDataLibBuildPath: string; + const parameterPairs = localBuildPath.split(','); + for (const parameterPair of parameterPairs) { + if (parameterPair.includes('=')) { + const [nodeAlias, localDataLibBuildPath] = parameterPair.split('='); + buildPathMap.set(nodeAlias as NodeAlias, localDataLibBuildPath); + } else { + defaultDataLibBuildPath = parameterPair; + } + } + + let localDataLibBuildPath: string; + + for (const consensusNode of consensusNodes) { + const k8 = self.k8Factory.getK8(consensusNode.context); + + const podRef = podRefs[consensusNode.name]; + if (buildPathMap.has(consensusNode.name)) { + localDataLibBuildPath = buildPathMap.get(consensusNode.name); + } else { + localDataLibBuildPath = defaultDataLibBuildPath; + } + + if (!fs.existsSync(localDataLibBuildPath)) { + throw new SoloError(`local build path does not exist: ${localDataLibBuildPath}`); + } + + subTasks.push({ + title: `Copy local build to Node: ${chalk.yellow(consensusNode.name)} from ${localDataLibBuildPath}`, + task: async () => { + // filter the data/config and data/keys to avoid failures due to config and secret mounts + const filterFunction = (path: string) => { + return !(path.includes('data/keys') || path.includes('data/config')); + }; + await k8 + .containers() + .readByRef(ContainerRef.of(podRef, constants.ROOT_CONTAINER)) + .copyTo(localDataLibBuildPath, `${constants.HEDERA_HAPI_PATH}`, filterFunction); + + if (self.configManager.getFlag(flags.appConfig)) { + const testJsonFiles: string[] = this.configManager.getFlag(flags.appConfig)!.split(','); + for (const jsonFile of testJsonFiles) { + if (fs.existsSync(jsonFile)) { + await k8 + .containers() + .readByRef(ContainerRef.of(podRef, constants.ROOT_CONTAINER)) + .copyTo(jsonFile, `${constants.HEDERA_HAPI_PATH}`); + } + } + } + }, + }); + } + // set up the sub-tasks + return task.newListr(subTasks, { + concurrent: constants.NODE_COPY_CONCURRENT, + rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, + }); + } + + public _fetchPlatformSoftwareMultiple( + consensusNodes: ConsensusNode[], + podRefs: Record, + releaseTag: string, + task: SoloListrTaskWrapper, + platformInstaller: PlatformInstaller, + ) { + const subTasks: SoloListrTask<{config: NodeSetupConfigClass}>[] = []; + + for (const consensusNode of consensusNodes) { + const podRef = podRefs[consensusNode.name]; + subTasks.push({ + title: `Update node: ${chalk.yellow(consensusNode.name)} [ platformVersion = ${releaseTag} ]`, + task: async () => await platformInstaller.fetchPlatform(podRef, releaseTag, consensusNode.context), + }); + } + + // set up the sub-tasks + return task.newListr(subTasks, { + concurrent: true, // since we download in the container directly, we want this to be in parallel across all nodes + rendererOptions: { + collapseSubtasks: false, + }, + }); + } + + public setupNetworkNodesMultiple(isGenesis: boolean): SoloListrTask<{config: NodeSetupConfigClass}> { + return { + title: 'Setup network nodes', + task: async (ctx, task) => { + if (isGenesis) { + await this.generateGenesisNetworkJson( + ctx.config.namespace, + ctx.config.nodeAliases, + ctx.config.keysDir, + ctx.config.stagingDir, + ); + } + + await this.generateNodeOverridesJson(ctx.config.namespace, ctx.config.nodeAliases, ctx.config.stagingDir); + + const subTasks: SoloListrTask<{config: NodeSetupConfigClass}>[] = []; + + for (const consensusNode of ctx.config.consensusNodes) { + const podRef = ctx.config.podRefs[consensusNode.name]; + subTasks.push({ + title: `Node: ${chalk.yellow(consensusNode.name)}`, + task: () => + this.platformInstaller.taskSetup(podRef, ctx.config.stagingDir, isGenesis, consensusNode.context), + }); + } + + // set up the sub-tasks + return task.newListr(subTasks, { + concurrent: true, + rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, + }); + }, + }; + } + //// TODO custom ------------------------ + /** Check if the network node pod is running */ async checkNetworkNodePod( namespace: NamespaceName, @@ -912,7 +1147,7 @@ export class NodeCommandTasks { ); } - identifyNetworkPods(maxAttempts = undefined) { + identifyNetworkPods(maxAttempts?: number) { const self = this; return new Task('Identify network pods', (ctx: any, task: ListrTaskWrapper) => { return self.taskCheckNetworkNodePods(ctx, task, ctx.config.nodeAliases, maxAttempts); diff --git a/src/core/model/consensus_node.ts b/src/core/model/consensus_node.ts index f604a03d5..6773524e5 100644 --- a/src/core/model/consensus_node.ts +++ b/src/core/model/consensus_node.ts @@ -1,10 +1,11 @@ /** * SPDX-License-Identifier: Apache-2.0 */ +import {type NodeAlias} from '../../types/aliases.js'; export class ConsensusNode { constructor( - public readonly name: string, + public readonly name: NodeAlias, public readonly nodeId: number, public readonly namespace: string, public readonly cluster: string, diff --git a/src/core/platform_installer.ts b/src/core/platform_installer.ts index e558b8049..95aa3a8d6 100644 --- a/src/core/platform_installer.ts +++ b/src/core/platform_installer.ts @@ -84,7 +84,7 @@ export class PlatformInstaller { } /** Fetch and extract platform code into the container */ - async fetchPlatform(podRef: PodRef, tag: string) { + async fetchPlatform(podRef: PodRef, tag: string, context?: string) { if (!podRef) throw new MissingArgumentError('podRef is required'); if (!tag) throw new MissingArgumentError('tag is required'); @@ -98,10 +98,14 @@ export class PlatformInstaller { const extractScript = path.join(constants.HEDERA_USER_HOME_DIR, scriptName); // inside the container const containerRef = ContainerRef.of(podRef, constants.ROOT_CONTAINER); - await this.k8Factory.default().containers().readByRef(containerRef).execContainer(`chmod +x ${extractScript}`); - await this.k8Factory.default().containers().readByRef(containerRef).execContainer([extractScript, tag]); + + const k8Containers = context ? this.k8Factory.getK8(context).containers() : this.k8Factory.default().containers(); + + await k8Containers.readByRef(containerRef).execContainer(`chmod +x ${extractScript}`); + await k8Containers.readByRef(containerRef).execContainer([extractScript, tag]); + return true; - } catch (e: Error | any) { + } catch (e) { const message = `failed to extract platform code in this pod '${podRef.name}': ${e.message}`; this.logger.error(message, e); throw new SoloError(message, e); @@ -114,9 +118,16 @@ export class PlatformInstaller { * @param srcFiles - list of source files * @param destDir - destination directory * @param [container] - name of the container - * @returns a list of pathso of the copied files insider the container + * @param [context] + * @returns a list of paths of the copied files insider the container */ - async copyFiles(podRef: PodRef, srcFiles: string[], destDir: string, container = constants.ROOT_CONTAINER) { + async copyFiles( + podRef: PodRef, + srcFiles: string[], + destDir: string, + container = constants.ROOT_CONTAINER, + context?: string, + ) { try { const containerRef = ContainerRef.of(podRef, container); const copiedFiles: string[] = []; @@ -127,19 +138,23 @@ export class PlatformInstaller { throw new SoloError(`file does not exist: ${srcPath}`); } - if (!(await this.k8Factory.default().containers().readByRef(containerRef).hasDir(destDir))) { - await this.k8Factory.default().containers().readByRef(containerRef).mkdir(destDir); + const k8Containers = context + ? this.k8Factory.getK8(context).containers() + : this.k8Factory.default().containers(); + + if (!(await k8Containers.readByRef(containerRef).hasDir(destDir))) { + await k8Containers.readByRef(containerRef).mkdir(destDir); } this.logger.debug(`Copying file into ${podRef.name}: ${srcPath} -> ${destDir}`); - await this.k8Factory.default().containers().readByRef(containerRef).copyTo(srcPath, destDir); + await k8Containers.readByRef(containerRef).copyTo(srcPath, destDir); const fileName = path.basename(srcPath); copiedFiles.push(path.join(destDir, fileName)); } return copiedFiles; - } catch (e: Error | any) { + } catch (e) { throw new SoloError(`failed to copy files to pod '${podRef.name}': ${e.message}`, e); } } @@ -234,27 +249,27 @@ export class PlatformInstaller { mode = '0755', recursive = true, container = constants.ROOT_CONTAINER, + context?: string, ) { if (!podRef) throw new MissingArgumentError('podRef is required'); if (!destPath) throw new MissingArgumentError('destPath is required'); const containerRef = ContainerRef.of(podRef, container); const recursiveFlag = recursive ? '-R' : ''; - await this.k8Factory - .default() - .containers() + + const k8Containers = context ? this.k8Factory.getK8(context).containers() : this.k8Factory.default().containers(); + + await k8Containers .readByRef(containerRef) .execContainer(['bash', '-c', `chown ${recursiveFlag} hedera:hedera ${destPath} 2>/dev/null || true`]); - await this.k8Factory - .default() - .containers() + await k8Containers .readByRef(containerRef) .execContainer(['bash', '-c', `chmod ${recursiveFlag} ${mode} ${destPath} 2>/dev/null || true`]); return true; } - async setPlatformDirPermissions(podRef: PodRef) { + async setPlatformDirPermissions(podRef: PodRef, context?: string) { const self = this; if (!podRef) throw new MissingArgumentError('podRef is required'); @@ -262,23 +277,23 @@ export class PlatformInstaller { const destPaths = [constants.HEDERA_HAPI_PATH, constants.HEDERA_HGCAPP_DIR]; for (const destPath of destPaths) { - await self.setPathPermission(podRef, destPath); + await self.setPathPermission(podRef, destPath, undefined, undefined, undefined, context); } return true; - } catch (e: Error | any) { + } catch (e) { throw new SoloError(`failed to set permission in '${podRef.name}'`, e); } } /** Return a list of task to perform node directory setup */ - taskSetup(podRef: PodRef, stagingDir: string, isGenesis: boolean) { + taskSetup(podRef: PodRef, stagingDir: string, isGenesis: boolean, context?: string) { const self = this; return new Listr( [ { title: 'Copy configuration files', - task: async () => await self.copyConfigurationFiles(stagingDir, podRef, isGenesis), + task: async () => await self.copyConfigurationFiles(stagingDir, podRef, isGenesis, context), }, { title: 'Set file permissions', @@ -301,14 +316,14 @@ export class PlatformInstaller { * @param isGenesis - true if this is `solo node setup` and we are at genesis * @private */ - private async copyConfigurationFiles(stagingDir: string, podRef: PodRef, isGenesis: boolean) { + private async copyConfigurationFiles(stagingDir: string, podRef: PodRef, isGenesis: boolean, context?: string) { if (isGenesis) { const genesisNetworkJson = [path.join(stagingDir, 'genesis-network.json')]; - await this.copyFiles(podRef, genesisNetworkJson, `${constants.HEDERA_HAPI_PATH}/data/config`); + await this.copyFiles(podRef, genesisNetworkJson, `${constants.HEDERA_HAPI_PATH}/data/config`, undefined, context); } const nodeOverridesYaml = [path.join(stagingDir, constants.NODE_OVERRIDE_FILE)]; - await this.copyFiles(podRef, nodeOverridesYaml, `${constants.HEDERA_HAPI_PATH}/data/config`); + await this.copyFiles(podRef, nodeOverridesYaml, `${constants.HEDERA_HAPI_PATH}/data/config`, undefined, context); } /** From 1c8357f61064ca0327513f8404a88b1e06629791 Mon Sep 17 00:00:00 2001 From: instamenta Date: Wed, 12 Feb 2025 14:42:45 +0200 Subject: [PATCH 2/3] add new helper functions and remove the temporary methods Signed-off-by: instamenta --- src/commands/base.ts | 2 + src/commands/node/configs.ts | 3 +- src/commands/node/handlers.ts | 6 +- src/commands/node/tasks.ts | 283 +++++----------------------------- src/core/helpers.ts | 17 ++ 5 files changed, 63 insertions(+), 248 deletions(-) diff --git a/src/commands/base.ts b/src/commands/base.ts index bef337bcf..d7215c4a6 100644 --- a/src/commands/base.ts +++ b/src/commands/base.ts @@ -255,6 +255,8 @@ export abstract class BaseCommand extends ShellRunner { public getConsensusNodes(): ConsensusNode[] { const consensusNodes: ConsensusNode[] = []; + if (!this.getRemoteConfigManager()?.components?.consensusNodes) return []; + // using the remoteConfigManager to get the consensus nodes Object.values(this.getRemoteConfigManager().components.consensusNodes).forEach(node => { consensusNodes.push( diff --git a/src/commands/node/configs.ts b/src/commands/node/configs.ts index 065769b60..efda27f9c 100644 --- a/src/commands/node/configs.ts +++ b/src/commands/node/configs.ts @@ -18,7 +18,6 @@ import {type NodeAddConfigClass} from './node_add_config.js'; import {type NamespaceName} from '../../core/kube/resources/namespace/namespace_name.js'; import {type PodRef} from '../../core/kube/resources/pod/pod_ref.js'; import {type K8Factory} from '../../core/kube/k8_factory.js'; -import {type NodeCommandHandlers} from './handlers.js'; import {type ConsensusNode} from '../../core/model/consensus_node.js'; export const PREPARE_UPGRADE_CONFIGS_NAME = 'prepareUpgradeConfig'; @@ -346,7 +345,7 @@ export const startConfigBuilder = async function (argv, ctx, task) { return config; }; -export const setupConfigBuilder = async function (this: NodeCommandHandlers, argv, ctx, task) { +export const setupConfigBuilder = async function (argv, ctx, task) { ctx.config = this.getConfig(SETUP_CONFIGS_NAME, argv.flags, [ 'nodeAliases', 'podRefs', diff --git a/src/commands/node/handlers.ts b/src/commands/node/handlers.ts index ffc862b8e..e4e560f27 100644 --- a/src/commands/node/handlers.ts +++ b/src/commands/node/handlers.ts @@ -825,9 +825,9 @@ export class NodeCommandHandlers implements CommandHandlers { this.validateAllNodeStates({ acceptedStates: [ConsensusNodeStates.INITIALIZED], }), - this.tasks.identifyNetworkPodsMultiple(), - this.tasks.fetchPlatformSoftwareMultiple(), - this.tasks.setupNetworkNodesMultiple(true), + this.tasks.identifyNetworkPods(), + this.tasks.fetchPlatformSoftware('nodeAliases'), + this.tasks.setupNetworkNodes('nodeAliases', true), this.changeAllNodeStates(ConsensusNodeStates.SETUP), ], { diff --git a/src/commands/node/tasks.ts b/src/commands/node/tasks.ts index 77e9678ee..9f628534e 100644 --- a/src/commands/node/tasks.ts +++ b/src/commands/node/tasks.ts @@ -57,7 +57,7 @@ import {NodeStatusCodes, NodeStatusEnums, NodeSubcommandType} from '../../core/e import { type NodeDeleteConfigClass, type NodeRefreshConfigClass, - NodeSetupConfigClass, + type NodeSetupConfigClass, type NodeUpdateConfigClass, } from './configs.js'; import {type Lease} from '../../core/lease/lease.js'; @@ -73,8 +73,8 @@ import {ContainerRef} from '../../core/kube/resources/container/container_ref.js import {NetworkNodes} from '../../core/network_nodes.js'; import {container} from 'tsyringe-neo'; import * as helpers from '../../core/helpers.js'; -import {type SoloListrTask, SoloListrTaskWrapper} from '../../types/index.js'; -import {ConsensusNode} from '../../core/model/consensus_node.js'; +import {type Optional, type SoloListrTask, type SoloListrTaskWrapper} from '../../types/index.js'; +import {type ConsensusNode} from '../../core/model/consensus_node.js'; export class NodeCommandTasks { private readonly accountManager: AccountManager; @@ -203,6 +203,7 @@ export class NodeCommandTasks { podRefs: Record, task: ListrTaskWrapper, localBuildPath: string, + consensusNodes: Optional, ) { const subTasks = []; @@ -223,6 +224,7 @@ export class NodeCommandTasks { let localDataLibBuildPath: string; for (const nodeAlias of nodeAliases) { const podRef = podRefs[nodeAlias]; + const context = helpers.extractContextFromConsensusNodes(nodeAlias, consensusNodes); if (buildPathMap.has(nodeAlias)) { localDataLibBuildPath = buildPathMap.get(nodeAlias); } else { @@ -234,6 +236,9 @@ export class NodeCommandTasks { } const self = this; + + const k8 = context ? self.k8Factory.getK8(context) : self.k8Factory.default(); + subTasks.push({ title: `Copy local build to Node: ${chalk.yellow(nodeAlias)} from ${localDataLibBuildPath}`, task: async () => { @@ -241,8 +246,7 @@ export class NodeCommandTasks { const filterFunction = (path, stat) => { return !(path.includes('data/keys') || path.includes('data/config')); }; - await self.k8Factory - .default() + await k8 .containers() .readByRef(ContainerRef.of(podRef, constants.ROOT_CONTAINER)) .copyTo(localDataLibBuildPath, `${constants.HEDERA_HAPI_PATH}`, filterFunction); @@ -250,8 +254,7 @@ export class NodeCommandTasks { const testJsonFiles: string[] = this.configManager.getFlag(flags.appConfig)!.split(','); for (const jsonFile of testJsonFiles) { if (fs.existsSync(jsonFile)) { - await self.k8Factory - .default() + await k8 .containers() .readByRef(ContainerRef.of(podRef, constants.ROOT_CONTAINER)) .copyTo(jsonFile, `${constants.HEDERA_HAPI_PATH}`); @@ -274,13 +277,15 @@ export class NodeCommandTasks { releaseTag: string, task: ListrTaskWrapper, platformInstaller: PlatformInstaller, + consensusNodes?: Optional, ) { const subTasks = []; for (const nodeAlias of nodeAliases) { + const context = helpers.extractContextFromConsensusNodes(nodeAlias, consensusNodes); const podRef = podRefs[nodeAlias]; subTasks.push({ title: `Update node: ${chalk.yellow(nodeAlias)} [ platformVersion = ${releaseTag} ]`, - task: async () => await platformInstaller.fetchPlatform(podRef, releaseTag), + task: async () => await platformInstaller.fetchPlatform(podRef, releaseTag, context), }); } @@ -804,10 +809,12 @@ export class NodeCommandTasks { if (!ctx.config) ctx.config = {}; ctx.config.podRefs = {}; + const consensusNodes: Optional = ctx.config.consensusNodes; const subTasks = []; const self = this; for (const nodeAlias of nodeAliases) { + const context = helpers.extractContextFromConsensusNodes(nodeAlias, consensusNodes); subTasks.push({ title: `Check network pod: ${chalk.yellow(nodeAlias)}`, task: async (ctx: any) => { @@ -816,6 +823,8 @@ export class NodeCommandTasks { ctx.config.namespace, nodeAlias, maxAttempts, + undefined, + context, ); } catch (_) { ctx.config.skipStop = true; @@ -833,248 +842,22 @@ export class NodeCommandTasks { }); } - //// TODO custom ------------------------ - public identifyNetworkPodsMultiple(maxAttempts?: number) { - const self = this; - return new Task( - 'Identify network pods', - (ctx: {config: NodeSetupConfigClass}, task: SoloListrTaskWrapper<{config: NodeSetupConfigClass}>) => { - return self.taskCheckNetworkNodePodsMultiple(ctx, task, maxAttempts); - }, - ); - } - - public taskCheckNetworkNodePodsMultiple( - ctx: {config: NodeSetupConfigClass}, - task: SoloListrTaskWrapper, - maxAttempts?: number, - ): Listr { - ctx.config.podRefs = {}; - const consensusNodes = ctx.config.consensusNodes; - - const subTasks: SoloListrTask<{config: NodeSetupConfigClass}>[] = []; - const self = this; - for (const consensusNode of consensusNodes) { - subTasks.push({ - title: `Check network pod: ${chalk.yellow(consensusNode.name)}`, - task: async ctx => { - try { - ctx.config.podRefs[consensusNode.name] = await self.checkNetworkNodePodMultiple( - ctx.config.namespace, - consensusNode, - maxAttempts, - ); - } catch { - ctx.config.skipStop = true; - } - }, - }); - } - - // setup the sub-tasks - return task.newListr(subTasks, { - concurrent: true, - rendererOptions: { - collapseSubtasks: false, - }, - }); - } - - async checkNetworkNodePodMultiple( - namespace: NamespaceName, - consensusNode: ConsensusNode, - maxAttempts = constants.PODS_RUNNING_MAX_ATTEMPTS, - delay = constants.PODS_RUNNING_DELAY, - ) { - const podName = Templates.renderNetworkPodName(consensusNode.name); - const podRef = PodRef.of(namespace, podName); - - try { - await this.k8Factory - .getK8(consensusNode.context) - .pods() - .waitForRunningPhase( - namespace, - [`solo.hedera.com/node-name=${consensusNode.name}`, 'solo.hedera.com/type=network-node'], - maxAttempts, - delay, - ); - - return podRef; - } catch (e) { - throw new SoloError(`no pod found for nodeAlias: ${consensusNode.name}`, e); - } - } - - fetchPlatformSoftwareMultiple(): SoloListrTask<{config: NodeSetupConfigClass}> { - const self = this; - return { - title: 'Fetch platform software into network nodes', - task: (ctx, task) => { - const {podRefs, releaseTag, localBuildPath} = ctx.config; - - if (localBuildPath !== '') { - return self._uploadPlatformSoftwareMultiple(ctx.config.consensusNodes, podRefs, task, localBuildPath); - } - return self._fetchPlatformSoftwareMultiple( - ctx.config.consensusNodes, - podRefs, - releaseTag, - task, - this.platformInstaller, - ); - }, - }; - } - - public _uploadPlatformSoftwareMultiple( - consensusNodes: ConsensusNode[], - podRefs: Record, - task: SoloListrTaskWrapper, - localBuildPath: string, - ) { - const self = this; - const subTasks: SoloListrTask<{config: NodeSetupConfigClass}>[] = []; - - this.logger.debug('no need to fetch, use local build jar files'); - - const buildPathMap = new Map(); - let defaultDataLibBuildPath: string; - const parameterPairs = localBuildPath.split(','); - for (const parameterPair of parameterPairs) { - if (parameterPair.includes('=')) { - const [nodeAlias, localDataLibBuildPath] = parameterPair.split('='); - buildPathMap.set(nodeAlias as NodeAlias, localDataLibBuildPath); - } else { - defaultDataLibBuildPath = parameterPair; - } - } - - let localDataLibBuildPath: string; - - for (const consensusNode of consensusNodes) { - const k8 = self.k8Factory.getK8(consensusNode.context); - - const podRef = podRefs[consensusNode.name]; - if (buildPathMap.has(consensusNode.name)) { - localDataLibBuildPath = buildPathMap.get(consensusNode.name); - } else { - localDataLibBuildPath = defaultDataLibBuildPath; - } - - if (!fs.existsSync(localDataLibBuildPath)) { - throw new SoloError(`local build path does not exist: ${localDataLibBuildPath}`); - } - - subTasks.push({ - title: `Copy local build to Node: ${chalk.yellow(consensusNode.name)} from ${localDataLibBuildPath}`, - task: async () => { - // filter the data/config and data/keys to avoid failures due to config and secret mounts - const filterFunction = (path: string) => { - return !(path.includes('data/keys') || path.includes('data/config')); - }; - await k8 - .containers() - .readByRef(ContainerRef.of(podRef, constants.ROOT_CONTAINER)) - .copyTo(localDataLibBuildPath, `${constants.HEDERA_HAPI_PATH}`, filterFunction); - - if (self.configManager.getFlag(flags.appConfig)) { - const testJsonFiles: string[] = this.configManager.getFlag(flags.appConfig)!.split(','); - for (const jsonFile of testJsonFiles) { - if (fs.existsSync(jsonFile)) { - await k8 - .containers() - .readByRef(ContainerRef.of(podRef, constants.ROOT_CONTAINER)) - .copyTo(jsonFile, `${constants.HEDERA_HAPI_PATH}`); - } - } - } - }, - }); - } - // set up the sub-tasks - return task.newListr(subTasks, { - concurrent: constants.NODE_COPY_CONCURRENT, - rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, - }); - } - - public _fetchPlatformSoftwareMultiple( - consensusNodes: ConsensusNode[], - podRefs: Record, - releaseTag: string, - task: SoloListrTaskWrapper, - platformInstaller: PlatformInstaller, - ) { - const subTasks: SoloListrTask<{config: NodeSetupConfigClass}>[] = []; - - for (const consensusNode of consensusNodes) { - const podRef = podRefs[consensusNode.name]; - subTasks.push({ - title: `Update node: ${chalk.yellow(consensusNode.name)} [ platformVersion = ${releaseTag} ]`, - task: async () => await platformInstaller.fetchPlatform(podRef, releaseTag, consensusNode.context), - }); - } - - // set up the sub-tasks - return task.newListr(subTasks, { - concurrent: true, // since we download in the container directly, we want this to be in parallel across all nodes - rendererOptions: { - collapseSubtasks: false, - }, - }); - } - - public setupNetworkNodesMultiple(isGenesis: boolean): SoloListrTask<{config: NodeSetupConfigClass}> { - return { - title: 'Setup network nodes', - task: async (ctx, task) => { - if (isGenesis) { - await this.generateGenesisNetworkJson( - ctx.config.namespace, - ctx.config.nodeAliases, - ctx.config.keysDir, - ctx.config.stagingDir, - ); - } - - await this.generateNodeOverridesJson(ctx.config.namespace, ctx.config.nodeAliases, ctx.config.stagingDir); - - const subTasks: SoloListrTask<{config: NodeSetupConfigClass}>[] = []; - - for (const consensusNode of ctx.config.consensusNodes) { - const podRef = ctx.config.podRefs[consensusNode.name]; - subTasks.push({ - title: `Node: ${chalk.yellow(consensusNode.name)}`, - task: () => - this.platformInstaller.taskSetup(podRef, ctx.config.stagingDir, isGenesis, consensusNode.context), - }); - } - - // set up the sub-tasks - return task.newListr(subTasks, { - concurrent: true, - rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, - }); - }, - }; - } - //// TODO custom ------------------------ - /** Check if the network node pod is running */ async checkNetworkNodePod( namespace: NamespaceName, nodeAlias: NodeAlias, maxAttempts = constants.PODS_RUNNING_MAX_ATTEMPTS, delay = constants.PODS_RUNNING_DELAY, + context?: Optional, ) { nodeAlias = nodeAlias.trim() as NodeAlias; const podName = Templates.renderNetworkPodName(nodeAlias); const podRef = PodRef.of(namespace, podName); try { - await this.k8Factory - .default() + const k8 = context ? this.k8Factory.getK8(context) : this.k8Factory.default(); + + await k8 .pods() .waitForRunningPhase( namespace, @@ -1159,10 +942,22 @@ export class NodeCommandTasks { return new Task('Fetch platform software into network nodes', (ctx: any, task: ListrTaskWrapper) => { const {podRefs, releaseTag, localBuildPath} = ctx.config; - if (localBuildPath !== '') { - return self._uploadPlatformSoftware(ctx.config[aliasesField], podRefs, task, localBuildPath); - } - return self._fetchPlatformSoftware(ctx.config[aliasesField], podRefs, releaseTag, task, this.platformInstaller); + return localBuildPath !== '' + ? self._uploadPlatformSoftware( + ctx.config[aliasesField], + podRefs, + task, + localBuildPath, + ctx.config.consensusNodes, + ) + : self._fetchPlatformSoftware( + ctx.config[aliasesField], + podRefs, + releaseTag, + task, + this.platformInstaller, + ctx.config.consensusNodes, + ); }); } @@ -1190,12 +985,14 @@ export class NodeCommandTasks { await this.generateNodeOverridesJson(ctx.config.namespace, ctx.config.nodeAliases, ctx.config.stagingDir); + const consensusNodes = ctx.config.consensusNodes; const subTasks = []; for (const nodeAlias of ctx.config[nodeAliasesProperty]) { const podRef = ctx.config.podRefs[nodeAlias]; + const context = helpers.extractContextFromConsensusNodes(nodeAlias, consensusNodes); subTasks.push({ title: `Node: ${chalk.yellow(nodeAlias)}`, - task: () => this.platformInstaller.taskSetup(podRef, ctx.config.stagingDir, isGenesis), + task: () => this.platformInstaller.taskSetup(podRef, ctx.config.stagingDir, isGenesis, context), }); } diff --git a/src/core/helpers.ts b/src/core/helpers.ts index 39c454a89..e579aae0b 100644 --- a/src/core/helpers.ts +++ b/src/core/helpers.ts @@ -15,6 +15,8 @@ import {type CommandFlag} from '../types/flag_types.js'; import {type SoloLogger} from './logging.js'; import {type Duration} from './time/duration.js'; import {type NodeAddConfigClass} from '../commands/node/node_add_config.js'; +import {type ConsensusNode} from './model/consensus_node.js'; +import {type Optional} from '../types/index.js'; export function sleep(duration: Duration) { return new Promise(resolve => { @@ -381,3 +383,18 @@ export function populateHelmArgs(valuesMapping: Record { + if (!consensusNodes) return undefined; + if (!consensusNodes.length) return undefined; + const consensusNode = consensusNodes.find(node => node.name === nodeAlias); + return consensusNode ? consensusNode.context : undefined; +} From 2c8d889e844b0b8115548ebad343d2fc0c1ee9ae Mon Sep 17 00:00:00 2001 From: instamenta Date: Wed, 12 Feb 2025 15:25:49 +0200 Subject: [PATCH 3/3] added try catch to prevent erroring on getConsensusNodes() and removed obsolete changes Signed-off-by: instamenta --- src/commands/base.ts | 6 +++++- src/commands/node/configs.ts | 13 ++++++++----- src/commands/node/handlers.ts | 4 ++-- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/commands/base.ts b/src/commands/base.ts index d7215c4a6..3345a824c 100644 --- a/src/commands/base.ts +++ b/src/commands/base.ts @@ -255,7 +255,11 @@ export abstract class BaseCommand extends ShellRunner { public getConsensusNodes(): ConsensusNode[] { const consensusNodes: ConsensusNode[] = []; - if (!this.getRemoteConfigManager()?.components?.consensusNodes) return []; + try { + if (!this.getRemoteConfigManager()?.components?.consensusNodes) return []; + } catch { + return []; + } // using the remoteConfigManager to get the consensus nodes Object.values(this.getRemoteConfigManager().components.consensusNodes).forEach(node => { diff --git a/src/commands/node/configs.ts b/src/commands/node/configs.ts index efda27f9c..860e5bb22 100644 --- a/src/commands/node/configs.ts +++ b/src/commands/node/configs.ts @@ -346,17 +346,20 @@ export const startConfigBuilder = async function (argv, ctx, task) { }; export const setupConfigBuilder = async function (argv, ctx, task) { - ctx.config = this.getConfig(SETUP_CONFIGS_NAME, argv.flags, [ + const config = this.getConfig(SETUP_CONFIGS_NAME, argv.flags, [ 'nodeAliases', 'podRefs', 'namespace', ]) as NodeSetupConfigClass; - ctx.config.namespace = await resolveNamespaceFromDeployment(this.parent.localConfig, this.configManager, task); - ctx.config.nodeAliases = helpers.parseNodeAliases(ctx.config.nodeAliasesUnparsed); - ctx.config.consensusNodes = this.parent.getConsensusNodes(); + config.namespace = await resolveNamespaceFromDeployment(this.parent.localConfig, this.configManager, task); + config.nodeAliases = helpers.parseNodeAliases(config.nodeAliasesUnparsed); + config.consensusNodes = this.parent.getConsensusNodes(); - await initializeSetup(ctx.config, this.k8Factory); + await initializeSetup(config, this.k8Factory); + + // set config in the context for later tasks to use + ctx.config = config; return ctx.config; }; diff --git a/src/commands/node/handlers.ts b/src/commands/node/handlers.ts index e4e560f27..ef0cb4a1b 100644 --- a/src/commands/node/handlers.ts +++ b/src/commands/node/handlers.ts @@ -46,10 +46,10 @@ import {type CommandFlag} from '../../types/flag_types.js'; export class NodeCommandHandlers implements CommandHandlers { private readonly accountManager: AccountManager; - readonly configManager: ConfigManager; + private readonly configManager: ConfigManager; private readonly platformInstaller: PlatformInstaller; private readonly logger: SoloLogger; - readonly k8Factory: K8Factory; + private readonly k8Factory: K8Factory; private readonly tasks: NodeCommandTasks; private readonly leaseManager: LeaseManager; public readonly remoteConfigManager: RemoteConfigManager;