UNPKG

@hashgraph/solo

Version:

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

1,020 lines (893 loc) 34.4 kB
/** * SPDX-License-Identifier: Apache-2.0 */ import * as helpers from '../../core/helpers.js'; import * as NodeFlags from './flags.js'; import { addConfigBuilder, deleteConfigBuilder, downloadGeneratedFilesConfigBuilder, keysConfigBuilder, logsConfigBuilder, prepareUpgradeConfigBuilder, refreshConfigBuilder, setupConfigBuilder, startConfigBuilder, statesConfigBuilder, stopConfigBuilder, updateConfigBuilder, upgradeConfigBuilder, } from './configs.js'; import * as constants from '../../core/constants.js'; import {type AccountManager} from '../../core/account_manager.js'; import {type ConfigManager} from '../../core/config_manager.js'; import {type PlatformInstaller} from '../../core/platform_installer.js'; import {type K8Factory} from '../../core/kube/k8_factory.js'; import {type LeaseManager} from '../../core/lease/lease_manager.js'; import {type RemoteConfigManager} from '../../core/config/remote/remote_config_manager.js'; import {IllegalArgumentError, SoloError} from '../../core/errors.js'; import {ComponentType, ConsensusNodeStates} from '../../core/config/remote/enumerations.js'; import {type SoloLogger} from '../../core/logging.js'; import {type NodeCommandTasks} from './tasks.js'; import {type Lease} from '../../core/lease/lease.js'; import {NodeSubcommandType} from '../../core/enumerations.js'; import {type BaseCommand, type CommandHandlers} from '../base.js'; import {NodeHelper} from './helper.js'; import {type NodeAlias, type NodeAliases} from '../../types/aliases.js'; import {ConsensusNodeComponent} from '../../core/config/remote/components/consensus_node_component.js'; import {type Listr, type ListrTask} from 'listr2'; import chalk from 'chalk'; import {type ComponentsDataWrapper} from '../../core/config/remote/components_data_wrapper.js'; import {type Optional} from '../../types/index.js'; import {type NamespaceName} from '../../core/kube/resources/namespace/namespace_name.js'; import {type CommandFlag} from '../../types/flag_types.js'; import {type ConsensusNode} from '../../core/model/consensus_node.js'; export class NodeCommandHandlers implements CommandHandlers { private readonly accountManager: AccountManager; private readonly configManager: ConfigManager; private readonly platformInstaller: PlatformInstaller; private readonly logger: SoloLogger; private readonly k8Factory: K8Factory; private readonly tasks: NodeCommandTasks; private readonly leaseManager: LeaseManager; public readonly remoteConfigManager: RemoteConfigManager; public contexts: string[]; public consensusNodes: ConsensusNode[]; public getConfig: (configName: string, flags: CommandFlag[], extraProperties?: string[]) => object; private prepareChartPath: any; public readonly parent: BaseCommand; constructor(opts: any) { if (!opts || !opts.accountManager) throw new IllegalArgumentError('An instance of core/AccountManager is required', opts.accountManager); if (!opts || !opts.configManager) throw new Error('An instance of core/ConfigManager is required'); if (!opts || !opts.logger) throw new Error('An instance of core/Logger is required'); if (!opts || !opts.tasks) throw new Error('An instance of NodeCommandTasks is required'); if (!opts || !opts.k8Factory) throw new Error('An instance of core/K8Factory is required'); if (!opts || !opts.platformInstaller) throw new IllegalArgumentError('An instance of core/PlatformInstaller is required', opts.platformInstaller); this.logger = opts.logger; this.tasks = opts.tasks; this.accountManager = opts.accountManager; this.configManager = opts.configManager; this.k8Factory = opts.k8Factory; this.platformInstaller = opts.platformInstaller; this.leaseManager = opts.leaseManager; this.remoteConfigManager = opts.remoteConfigManager; this.getConfig = opts.parent.getConfig.bind(opts.parent); this.prepareChartPath = opts.parent.prepareChartPath.bind(opts.parent); this.parent = opts.parent; } static readonly ADD_CONTEXT_FILE = 'node-add.json'; static readonly DELETE_CONTEXT_FILE = 'node-delete.json'; static readonly UPDATE_CONTEXT_FILE = 'node-update.json'; static readonly UPGRADE_CONTEXT_FILE = 'node-upgrade.json'; private init() { this.consensusNodes = this.parent.getConsensusNodes(); this.contexts = this.parent.getContexts(); } /** ******** Task Lists **********/ deletePrepareTaskList(argv: any, lease: Lease) { return [ this.tasks.initialize(argv, deleteConfigBuilder.bind(this), lease), this.validateSingleNodeState({excludedStates: []}), this.tasks.identifyExistingNodes(), this.tasks.loadAdminKey(), this.tasks.prepareUpgradeZip(), this.tasks.checkExistingNodesStakedAmount(), ]; } deleteSubmitTransactionsTaskList(argv: any) { return [ this.tasks.sendNodeDeleteTransaction(), this.tasks.sendPrepareUpgradeTransaction(), this.tasks.sendFreezeUpgradeTransaction(), ]; } deleteExecuteTaskList(argv: any) { return [ this.tasks.checkAllNodesAreFrozen('existingNodeAliases'), this.tasks.downloadNodeGeneratedFiles(), this.tasks.prepareStagingDirectory('existingNodeAliases'), this.tasks.refreshNodeList(), this.tasks.copyNodeKeysToSecrets(), this.tasks.getNodeLogsAndConfigs(), this.tasks.updateChartWithConfigMap('Delete network node', NodeSubcommandType.DELETE), this.tasks.killNodes(), this.tasks.sleep('Give time for pods to come up after being killed', 20000), this.tasks.checkNodePodsAreRunning(), this.tasks.populateServiceMap(), this.tasks.fetchPlatformSoftware('allNodeAliases'), this.tasks.setupNetworkNodes('allNodeAliases', false), this.tasks.startNodes('allNodeAliases'), this.tasks.enablePortForwarding(), this.tasks.checkAllNodesAreActive('allNodeAliases'), this.tasks.checkAllNodeProxiesAreActive(), this.tasks.triggerStakeWeightCalculate(NodeSubcommandType.DELETE), this.tasks.finalize(), ]; } addPrepareTasks(argv: any, lease: Lease) { return [ this.tasks.initialize(argv, addConfigBuilder.bind(this), lease), // TODO instead of validating the state we need to do a remote config add component, and we will need to manually // the nodeAlias based on the next available node ID + 1 // this.validateSingleNodeState({excludedStates: []}), this.tasks.checkPVCsEnabled(), this.tasks.identifyExistingNodes(), this.tasks.determineNewNodeAccountNumber(), this.tasks.copyGrpcTlsCertificates(), this.tasks.generateGossipKey(), this.tasks.generateGrpcTlsKey(), this.tasks.loadSigningKeyCertificate(), this.tasks.computeMTLSCertificateHash(), this.tasks.prepareGossipEndpoints(), this.tasks.prepareGrpcServiceEndpoints(), this.tasks.prepareUpgradeZip(), this.tasks.checkExistingNodesStakedAmount(), ]; } addSubmitTransactionsTasks(argv: any) { return [ this.tasks.sendNodeCreateTransaction(), this.tasks.sendPrepareUpgradeTransaction(), this.tasks.sendFreezeUpgradeTransaction(), ]; } addExecuteTasks(argv: any) { return [ this.tasks.checkAllNodesAreFrozen('existingNodeAliases'), this.tasks.downloadNodeGeneratedFiles(), this.tasks.prepareStagingDirectory('allNodeAliases'), this.tasks.copyNodeKeysToSecrets(), this.tasks.getNodeLogsAndConfigs(), this.tasks.updateChartWithConfigMap('Deploy new network node', NodeSubcommandType.ADD), this.tasks.killNodes(), this.tasks.checkNodePodsAreRunning(), this.tasks.populateServiceMap(), this.tasks.fetchPlatformSoftware('allNodeAliases'), this.tasks.downloadLastState(), this.tasks.uploadStateToNewNode(), this.tasks.setupNetworkNodes('allNodeAliases', false), this.tasks.startNodes('allNodeAliases'), this.tasks.enablePortForwarding(), this.tasks.checkAllNodesAreActive('allNodeAliases'), this.tasks.checkAllNodeProxiesAreActive(), this.tasks.stakeNewNode(), this.tasks.triggerStakeWeightCalculate(NodeSubcommandType.ADD), this.tasks.finalize(), ]; } updatePrepareTasks(argv, lease: Lease) { return [ this.tasks.initialize(argv, updateConfigBuilder.bind(this), lease), this.validateSingleNodeState({excludedStates: []}), this.tasks.identifyExistingNodes(), this.tasks.loadAdminKey(), this.tasks.prepareUpgradeZip(), this.tasks.checkExistingNodesStakedAmount(), ]; } updateSubmitTransactionsTasks(argv) { return [ this.tasks.sendNodeUpdateTransaction(), this.tasks.sendPrepareUpgradeTransaction(), this.tasks.sendFreezeUpgradeTransaction(), ]; } updateExecuteTasks(argv) { return [ this.tasks.checkAllNodesAreFrozen('existingNodeAliases'), this.tasks.downloadNodeGeneratedFiles(), this.tasks.prepareStagingDirectory('allNodeAliases'), this.tasks.copyNodeKeysToSecrets(), this.tasks.getNodeLogsAndConfigs(), this.tasks.updateChartWithConfigMap( 'Update chart to use new configMap due to account number change', NodeSubcommandType.UPDATE, (ctx: any) => !ctx.config.newAccountNumber && !ctx.config.debugNodeAlias, ), this.tasks.killNodesAndUpdateConfigMap(), this.tasks.checkNodePodsAreRunning(), this.tasks.fetchPlatformSoftware('allNodeAliases'), this.tasks.setupNetworkNodes('allNodeAliases', false), this.tasks.startNodes('allNodeAliases'), this.tasks.enablePortForwarding(), this.tasks.checkAllNodesAreActive('allNodeAliases'), this.tasks.checkAllNodeProxiesAreActive(), this.tasks.triggerStakeWeightCalculate(NodeSubcommandType.UPDATE), this.tasks.finalize(), ]; } upgradePrepareTasks(argv, lease: Lease) { return [ this.tasks.initialize(argv, upgradeConfigBuilder.bind(this), lease), this.validateAllNodeStates({excludedStates: []}), this.tasks.identifyExistingNodes(), this.tasks.loadAdminKey(), this.tasks.prepareUpgradeZip(), this.tasks.checkExistingNodesStakedAmount(), ]; } upgradeSubmitTransactionsTasks(argv) { return [this.tasks.sendPrepareUpgradeTransaction(), this.tasks.sendFreezeUpgradeTransaction()]; } upgradeExecuteTasks(argv) { return [ this.tasks.checkAllNodesAreFrozen('existingNodeAliases'), this.tasks.downloadNodeUpgradeFiles(), this.tasks.getNodeLogsAndConfigs(), this.tasks.startNodes('allNodeAliases'), this.tasks.enablePortForwarding(), this.tasks.checkAllNodesAreActive('allNodeAliases'), this.tasks.checkAllNodeProxiesAreActive(), this.tasks.finalize(), ]; } /** ******** Handlers **********/ async prepareUpgrade(argv: any) { argv = helpers.addFlagsToArgv(argv, NodeFlags.DEFAULT_FLAGS); const lease = await this.leaseManager.create(); const action = this.parent.commandActionBuilder( [ this.tasks.initialize(argv, prepareUpgradeConfigBuilder.bind(this), lease), this.tasks.prepareUpgradeZip(), this.tasks.sendPrepareUpgradeTransaction(), ], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, 'Error in preparing node upgrade', lease, ); await action(argv, this); return true; } async freezeUpgrade(argv: any) { argv = helpers.addFlagsToArgv(argv, NodeFlags.DEFAULT_FLAGS); const action = this.parent.commandActionBuilder( [ this.tasks.initialize(argv, prepareUpgradeConfigBuilder.bind(this), null), this.tasks.prepareUpgradeZip(), this.tasks.sendFreezeUpgradeTransaction(), ], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, 'Error in executing node freeze upgrade', null, ); await action(argv, this); return true; } async downloadGeneratedFiles(argv: any) { argv = helpers.addFlagsToArgv(argv, NodeFlags.DEFAULT_FLAGS); const lease = await this.leaseManager.create(); const action = this.parent.commandActionBuilder( [ this.tasks.initialize(argv, downloadGeneratedFilesConfigBuilder.bind(this), lease), this.tasks.identifyExistingNodes(), this.tasks.downloadNodeGeneratedFiles(), ], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, 'Error in downloading generated files', lease, ); await action(argv, this); return true; } async update(argv: any) { argv = helpers.addFlagsToArgv(argv, NodeFlags.UPDATE_FLAGS); const lease = await this.leaseManager.create(); const action = this.parent.commandActionBuilder( [ ...this.updatePrepareTasks(argv, lease), ...this.updateSubmitTransactionsTasks(argv), ...this.updateExecuteTasks(argv), ], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, 'Error in updating nodes', lease, ); await action(argv, this); return true; } async updatePrepare(argv) { argv = helpers.addFlagsToArgv(argv, NodeFlags.UPDATE_PREPARE_FLAGS); const lease = await this.leaseManager.create(); const action = this.parent.commandActionBuilder( [ ...this.updatePrepareTasks(argv, lease), this.tasks.saveContextData(argv, NodeCommandHandlers.UPDATE_CONTEXT_FILE, NodeHelper.updateSaveContextParser), ], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, 'Error in preparing node update', lease, ); await action(argv, this); return true; } async updateSubmitTransactions(argv) { const lease = await this.leaseManager.create(); argv = helpers.addFlagsToArgv(argv, NodeFlags.UPDATE_SUBMIT_TRANSACTIONS_FLAGS); const action = this.parent.commandActionBuilder( [ this.tasks.initialize(argv, updateConfigBuilder.bind(this), lease), this.tasks.loadContextData(argv, NodeCommandHandlers.UPDATE_CONTEXT_FILE, NodeHelper.updateLoadContextParser), ...this.updateSubmitTransactionsTasks(argv), ], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, 'Error in submitting transactions for node update', lease, ); await action(argv, this); return true; } async updateExecute(argv) { const lease = await this.leaseManager.create(); argv = helpers.addFlagsToArgv(argv, NodeFlags.UPDATE_EXECUTE_FLAGS); const action = this.parent.commandActionBuilder( [ this.tasks.initialize(argv, updateConfigBuilder.bind(this), lease, false), this.tasks.loadContextData(argv, NodeCommandHandlers.UPDATE_CONTEXT_FILE, NodeHelper.updateLoadContextParser), ...this.updateExecuteTasks(argv), ], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, 'Error in executing network upgrade', lease, ); await action(argv, this); return true; } async upgradePrepare(argv) { argv = helpers.addFlagsToArgv(argv, NodeFlags.UPGRADE_PREPARE_FLAGS); const lease = await this.leaseManager.create(); const action = this.parent.commandActionBuilder( [ ...this.upgradePrepareTasks(argv, lease), this.tasks.saveContextData(argv, NodeCommandHandlers.UPGRADE_CONTEXT_FILE, NodeHelper.upgradeSaveContextParser), ], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, 'Error in preparing node upgrade', lease, ); await action(argv, this); return true; } async upgradeSubmitTransactions(argv) { const lease = await this.leaseManager.create(); argv = helpers.addFlagsToArgv(argv, NodeFlags.UPGRADE_SUBMIT_TRANSACTIONS_FLAGS); const action = this.parent.commandActionBuilder( [ this.tasks.initialize(argv, upgradeConfigBuilder.bind(this), lease), this.tasks.loadContextData(argv, NodeCommandHandlers.UPGRADE_CONTEXT_FILE, NodeHelper.upgradeLoadContextParser), ...this.upgradeSubmitTransactionsTasks(argv), ], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, 'Error in submitting transactions for node upgrade', lease, ); await action(argv, this); return true; } async upgradeExecute(argv) { const lease = await this.leaseManager.create(); argv = helpers.addFlagsToArgv(argv, NodeFlags.UPGRADE_FLAGS); const action = this.parent.commandActionBuilder( [ this.tasks.initialize(argv, upgradeConfigBuilder.bind(this), lease, false), this.tasks.loadContextData(argv, NodeCommandHandlers.UPGRADE_CONTEXT_FILE, NodeHelper.upgradeLoadContextParser), ...this.upgradeExecuteTasks(argv), ], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, 'Error in executing network upgrade', lease, ); await action(argv, this); return true; } async upgrade(argv: any) { argv = helpers.addFlagsToArgv(argv, NodeFlags.UPGRADE_FLAGS); const lease = await this.leaseManager.create(); const action = this.parent.commandActionBuilder( [ ...this.upgradePrepareTasks(argv, lease), ...this.upgradeSubmitTransactionsTasks(argv), ...this.upgradeExecuteTasks(argv), ], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, 'Error in upgrade network', lease, ); await action(argv, this); return true; } async delete(argv: any) { argv = helpers.addFlagsToArgv(argv, NodeFlags.DELETE_FLAGS); const lease = await this.leaseManager.create(); const action = this.parent.commandActionBuilder( [ ...this.deletePrepareTaskList(argv, lease), ...this.deleteSubmitTransactionsTaskList(argv), ...this.deleteExecuteTaskList(argv), ], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, 'Error in deleting nodes', lease, ); await action(argv, this); return true; } async deletePrepare(argv: any) { argv = helpers.addFlagsToArgv(argv, NodeFlags.DELETE_PREPARE_FLAGS); const lease = await this.leaseManager.create(); const action = this.parent.commandActionBuilder( [ ...this.deletePrepareTaskList(argv, lease), this.tasks.saveContextData(argv, NodeCommandHandlers.DELETE_CONTEXT_FILE, NodeHelper.deleteSaveContextParser), ], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, 'Error in preparing to delete a node', lease, ); await action(argv, this); return true; } async deleteSubmitTransactions(argv: any) { argv = helpers.addFlagsToArgv(argv, NodeFlags.DELETE_SUBMIT_TRANSACTIONS_FLAGS); const lease = await this.leaseManager.create(); const action = this.parent.commandActionBuilder( [ this.tasks.initialize(argv, deleteConfigBuilder.bind(this), lease), this.tasks.loadContextData(argv, NodeCommandHandlers.DELETE_CONTEXT_FILE, NodeHelper.deleteLoadContextParser), ...this.deleteSubmitTransactionsTaskList(argv), ], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, 'Error in deleting a node', lease, ); await action(argv, this); return true; } async deleteExecute(argv: any) { argv = helpers.addFlagsToArgv(argv, NodeFlags.DELETE_EXECUTE_FLAGS); const lease = await this.leaseManager.create(); const action = this.parent.commandActionBuilder( [ this.tasks.initialize(argv, deleteConfigBuilder.bind(this), lease, false), this.tasks.loadContextData(argv, NodeCommandHandlers.DELETE_CONTEXT_FILE, NodeHelper.deleteLoadContextParser), ...this.deleteExecuteTaskList(argv), ], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, 'Error in deleting a node', lease, ); await action(argv, this); return true; } async add(argv: any) { argv = helpers.addFlagsToArgv(argv, NodeFlags.ADD_FLAGS); const lease = await this.leaseManager.create(); const action = this.parent.commandActionBuilder( [...this.addPrepareTasks(argv, lease), ...this.addSubmitTransactionsTasks(argv), ...this.addExecuteTasks(argv)], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, 'Error in adding node', lease, ); await action(argv, this); return true; } async addPrepare(argv: any) { argv = helpers.addFlagsToArgv(argv, NodeFlags.ADD_PREPARE_FLAGS); const lease = await this.leaseManager.create(); const action = this.parent.commandActionBuilder( [ ...this.addPrepareTasks(argv, lease), this.tasks.saveContextData(argv, NodeCommandHandlers.ADD_CONTEXT_FILE, helpers.addSaveContextParser), ], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, 'Error in preparing node', lease, ); await action(argv, this); return true; } async addSubmitTransactions(argv: any) { argv = helpers.addFlagsToArgv(argv, NodeFlags.ADD_SUBMIT_TRANSACTIONS_FLAGS); const lease = await this.leaseManager.create(); const action = this.parent.commandActionBuilder( [ this.tasks.initialize(argv, addConfigBuilder.bind(this), lease), this.tasks.loadContextData(argv, NodeCommandHandlers.ADD_CONTEXT_FILE, helpers.addLoadContextParser), ...this.addSubmitTransactionsTasks(argv), ], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, '`Error in submitting transactions to node', lease, ); await action(argv, this); return true; } async addExecute(argv: any) { argv = helpers.addFlagsToArgv(argv, NodeFlags.ADD_EXECUTE_FLAGS); const lease = await this.leaseManager.create(); const action = this.parent.commandActionBuilder( [ this.tasks.initialize(argv, addConfigBuilder.bind(this), lease, false), this.tasks.identifyExistingNodes(), this.tasks.loadContextData(argv, NodeCommandHandlers.ADD_CONTEXT_FILE, helpers.addLoadContextParser), ...this.addExecuteTasks(argv), ], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, 'Error in adding node', lease, ); await action(argv, this); return true; } async logs(argv: any) { argv = helpers.addFlagsToArgv(argv, NodeFlags.LOGS_FLAGS); const action = this.parent.commandActionBuilder( [this.tasks.initialize(argv, logsConfigBuilder.bind(this), null), this.tasks.getNodeLogsAndConfigs()], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, 'Error in downloading log from nodes', null, ); await action(argv, this); return true; } async states(argv: any) { argv = helpers.addFlagsToArgv(argv, NodeFlags.STATES_FLAGS); const action = this.parent.commandActionBuilder( [this.tasks.initialize(argv, statesConfigBuilder.bind(this), null), this.tasks.getNodeStateFiles()], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, 'Error in downloading states from nodes', null, ); await action(argv, this); return true; } async refresh(argv: any) { argv = helpers.addFlagsToArgv(argv, NodeFlags.REFRESH_FLAGS); const lease = await this.leaseManager.create(); const action = this.parent.commandActionBuilder( [ this.tasks.initialize(argv, refreshConfigBuilder.bind(this), lease), this.validateAllNodeStates({ acceptedStates: [ConsensusNodeStates.STARTED, ConsensusNodeStates.SETUP, ConsensusNodeStates.INITIALIZED], }), this.tasks.identifyNetworkPods(), this.tasks.dumpNetworkNodesSaveState(), this.tasks.fetchPlatformSoftware('nodeAliases'), this.tasks.setupNetworkNodes('nodeAliases', true), this.tasks.startNodes('nodeAliases'), this.tasks.checkAllNodesAreActive('nodeAliases'), this.tasks.checkNodeProxiesAreActive(), ], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, 'Error in refreshing nodes', lease, ); await action(argv, this); return true; } async keys(argv: any) { this.init(); argv = helpers.addFlagsToArgv(argv, NodeFlags.KEYS_FLAGS); const action = this.parent.commandActionBuilder( [ this.tasks.initialize(argv, keysConfigBuilder.bind(this), null), this.tasks.generateGossipKeys(), this.tasks.generateGrpcTlsKeys(), this.tasks.finalize(), ], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, 'Error generating keys', null, ); await action(argv, this); return true; } async stop(argv: any) { argv = helpers.addFlagsToArgv(argv, NodeFlags.STOP_FLAGS); const lease = await this.leaseManager.create(); const action = this.parent.commandActionBuilder( [ this.tasks.initialize(argv, stopConfigBuilder.bind(this), lease), this.validateAllNodeStates({ acceptedStates: [ConsensusNodeStates.STARTED, ConsensusNodeStates.SETUP], }), this.tasks.identifyNetworkPods(1), this.tasks.stopNodes(), this.changeAllNodeStates(ConsensusNodeStates.INITIALIZED), ], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, 'Error stopping node', lease, ); await action(argv, this); return true; } async start(argv: any) { argv = helpers.addFlagsToArgv(argv, NodeFlags.START_FLAGS); const lease = await this.leaseManager.create(); const action = this.parent.commandActionBuilder( [ this.tasks.initialize(argv, startConfigBuilder.bind(this), lease), this.validateAllNodeStates({acceptedStates: [ConsensusNodeStates.SETUP]}), this.tasks.identifyExistingNodes(), this.tasks.uploadStateFiles((ctx: any) => ctx.config.stateFile.length === 0), this.tasks.startNodes('nodeAliases'), this.tasks.enablePortForwarding(), this.tasks.checkAllNodesAreActive('nodeAliases'), this.tasks.checkNodeProxiesAreActive(), this.changeAllNodeStates(ConsensusNodeStates.STARTED), this.tasks.addNodeStakes(), ], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, 'Error starting node', lease, ); await action(argv, this); return true; } async setup(argv: any) { argv = helpers.addFlagsToArgv(argv, NodeFlags.SETUP_FLAGS); const lease = await this.leaseManager.create(); const action = this.parent.commandActionBuilder( [ this.tasks.initialize(argv, setupConfigBuilder.bind(this), lease), this.validateAllNodeStates({ acceptedStates: [ConsensusNodeStates.INITIALIZED], }), this.tasks.identifyNetworkPods(), this.tasks.fetchPlatformSoftware('nodeAliases'), this.tasks.setupNetworkNodes('nodeAliases', true), this.changeAllNodeStates(ConsensusNodeStates.SETUP), ], { concurrent: false, rendererOptions: constants.LISTR_DEFAULT_RENDERER_OPTION, }, 'Error in setting up nodes', lease, ); await action(argv, this); return true; } /** Removes the consensus node, envoy and haproxy components from remote config. */ public removeNodeAndProxies(): ListrTask<any, any, any> { return { skip: (): boolean => !this.remoteConfigManager.isLoaded(), title: 'Remove node and proxies from remote config', task: async (): Promise<void> => { await this.remoteConfigManager.modify(async remoteConfig => { remoteConfig.components.remove('Consensus node name', ComponentType.ConsensusNode); remoteConfig.components.remove('Envoy proxy name', ComponentType.EnvoyProxy); remoteConfig.components.remove('HaProxy name', ComponentType.HaProxy); }); }, }; } /** * Changes the state from all consensus nodes components in remote config. * * @param state - to which to change the consensus node component */ public changeAllNodeStates(state: ConsensusNodeStates): ListrTask<any, any, any> { interface Context { config: {namespace: NamespaceName; consensusNodes: ConsensusNode[]}; } return { title: `Change node state to ${state} in remote config`, skip: (): boolean => !this.remoteConfigManager.isLoaded(), task: async (ctx: Context): Promise<void> => { await this.remoteConfigManager.modify(async remoteConfig => { const { config: {namespace}, } = ctx; for (const consensusNode of ctx.config.consensusNodes) { remoteConfig.components.edit( consensusNode.name, new ConsensusNodeComponent( consensusNode.name, consensusNode.cluster, namespace.name, state, consensusNode.nodeId, ), ); } }); }, }; } /** * Creates tasks to validate that each node state is either one of the accepted states or not one of the excluded. * * @param acceptedStates - the state at which the nodes can be, not matching any of the states throws an error * @param excludedStates - the state at which the nodes can't be, matching any of the states throws an error */ public validateAllNodeStates({ acceptedStates, excludedStates, }: { acceptedStates?: ConsensusNodeStates[]; excludedStates?: ConsensusNodeStates[]; }): ListrTask<any, any, any> { interface Context { config: {namespace: string; nodeAliases: NodeAliases}; } return { title: 'Validate nodes states', skip: (): boolean => !this.remoteConfigManager.isLoaded(), task: (ctx: Context, task): Listr<any, any, any> => { const nodeAliases = ctx.config.nodeAliases; const components = this.remoteConfigManager.components; const subTasks: ListrTask<Context, any, any>[] = nodeAliases.map(nodeAlias => ({ title: `Validating state for node ${nodeAlias}`, task: (_, task): void => { const state = this.validateNodeState(nodeAlias, components, acceptedStates, excludedStates); task.title += ` - ${chalk.green('valid state')}: ${chalk.cyan(state)}`; }, })); return task.newListr(subTasks, { concurrent: false, rendererOptions: {collapseSubtasks: false}, }); }, }; } /** * Creates tasks to validate that specific node state is either one of the accepted states or not one of the excluded. * * @param acceptedStates - the state at which the node can be, not matching any of the states throws an error * @param excludedStates - the state at which the node can't be, matching any of the states throws an error */ public validateSingleNodeState({ acceptedStates, excludedStates, }: { acceptedStates?: ConsensusNodeStates[]; excludedStates?: ConsensusNodeStates[]; }): ListrTask<any, any, any> { interface Context { config: {namespace: string; nodeAlias: NodeAlias}; } return { title: 'Validate nodes state', skip: (): boolean => !this.remoteConfigManager.isLoaded(), task: (ctx: Context, task): void => { const nodeAlias = ctx.config.nodeAlias; task.title += ` ${nodeAlias}`; const components = this.remoteConfigManager.components; const state = this.validateNodeState(nodeAlias, components, acceptedStates, excludedStates); task.title += ` - ${chalk.green('valid state')}: ${chalk.cyan(state)}`; }, }; } /** * @param nodeAlias - the alias of the node whose state to validate * @param components - the component data wrapper * @param acceptedStates - the state at which the node can be, not matching any of the states throws an error * @param excludedStates - the state at which the node can't be, matching any of the states throws an error */ private validateNodeState( nodeAlias: NodeAlias, components: ComponentsDataWrapper, acceptedStates: Optional<ConsensusNodeStates[]>, excludedStates: Optional<ConsensusNodeStates[]>, ): ConsensusNodeStates { let nodeComponent: ConsensusNodeComponent; try { nodeComponent = components.getComponent<ConsensusNodeComponent>(ComponentType.ConsensusNode, nodeAlias); } catch { throw new SoloError(`${nodeAlias} not found in remote config`); } // TODO: Enable once the states have been mapped // if (acceptedStates && !acceptedStates.includes(nodeComponent.state)) { // const errorMessageData = // `accepted states: ${acceptedStates.join(', ')}, ` + `current state: ${nodeComponent.state}`; // // throw new SoloError(`${nodeAlias} has invalid state - ` + errorMessageData); // } // // if (excludedStates && excludedStates.includes(nodeComponent.state)) { // const errorMessageData = // `excluded states: ${excludedStates.join(', ')}, ` + `current state: ${nodeComponent.state}`; // // throw new SoloError(`${nodeAlias} has invalid state - ` + errorMessageData); // } return nodeComponent.state; } }