diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 3c06066bd..bc1dc34bd 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -16,11 +16,10 @@ Maintainers are assigned the following scopes in this repository: | Name | GitHub ID | Scope | LFID | Discord ID | Email | Company Affiliation | |----- | ------------- | ---------- | ---- | ------------------- | ----- | ------------------- | -| | jeromy-cannon | Maintainer | | jeromy_at_hashgraph | | Hashgraph | +| | jeromy-cannon | Maintainer | | jeromy\_at\_hashgraph | | Hashgraph | | | leninmehedy | Maintainer | | | | Hashgraph | | | nathanklick | Maintainer | | nathan.hashgraph | | Hashgraph | - ## Emeritus Maintainers | Name | GitHub ID | Scope | LFID | Discord ID | Email | Company Affiliation | @@ -31,24 +30,24 @@ Maintainers are assigned the following scopes in this repository: Maintainers are expected to perform the following duties for this repository. The duties are listed in more or less priority order: -- Review, respond, and act on any security vulnerabilities reported against the repository. -- Review, provide feedback on, and merge or reject GitHub Pull Requests from +* Review, respond, and act on any security vulnerabilities reported against the repository. +* Review, provide feedback on, and merge or reject GitHub Pull Requests from Contributors. -- Review, triage, comment on, and close GitHub Issues +* Review, triage, comment on, and close GitHub Issues submitted by Contributors. -- When appropriate, lead/facilitate architectural discussions in the community. -- When appropriate, lead/facilitate the creation of a product roadmap. -- Create, clarify, and label issues to be worked on by Contributors. -- Ensure that there is a well defined (and ideally automated) product test and +* When appropriate, lead/facilitate architectural discussions in the community. +* When appropriate, lead/facilitate the creation of a product roadmap. +* Create, clarify, and label issues to be worked on by Contributors. +* Ensure that there is a well defined (and ideally automated) product test and release pipeline, including the publication of release artifacts. -- When appropriate, execute the product release process. -- Maintain the repository CONTRIBUTING.md file and getting started documents to +* When appropriate, execute the product release process. +* Maintain the repository CONTRIBUTING.md file and getting started documents to give guidance and encouragement to those wanting to contribute to the product, and those wanting to become maintainers. -- Contribute to the product via GitHub Pull Requests. -- Monitor requests from the LF Decentralized Trust Technical Advisory Council about the -contents and management of LFDT repositories, such as branch handling, -required files in repositories and so on. -- Contribute to the LFDT Project's Quarterly Report. +* Contribute to the product via GitHub Pull Requests. +* Monitor requests from the LF Decentralized Trust Technical Advisory Council about the + contents and management of LFDT repositories, such as branch handling, + required files in repositories and so on. +* Contribute to the LFDT Project's Quarterly Report. ## Becoming a Maintainer @@ -56,21 +55,21 @@ This community welcomes contributions. Interested contributors are encouraged to progress to become maintainers. To become a maintainer the following steps occur, roughly in order. -- The proposed maintainer establishes their reputation in the community, +* The proposed maintainer establishes their reputation in the community, including authoring five (5) significant merged pull requests, and expresses an interest in becoming a maintainer for the repository. -- A PR is created to update this file to add the proposed maintainer to the list of active maintainers. -- The PR is authored by an existing maintainer or has a comment on the PR from an existing maintainer supporting the proposal. -- The PR is authored by the proposed maintainer or has a comment on the PR from the proposed maintainer confirming their interest in being a maintainer. - - The PR or comment from the proposed maintainer must include their +* A PR is created to update this file to add the proposed maintainer to the list of active maintainers. +* The PR is authored by an existing maintainer or has a comment on the PR from an existing maintainer supporting the proposal. +* The PR is authored by the proposed maintainer or has a comment on the PR from the proposed maintainer confirming their interest in being a maintainer. + * The PR or comment from the proposed maintainer must include their willingness to be a long-term (more than 6 month) maintainer. -- Once the PR and necessary comments have been received, an approval timeframe begins. -- The PR **MUST** be communicated on all appropriate communication channels, including relevant community calls, chat channels and mailing lists. Comments of support from the community are welcome. -- The PR is merged and the proposed maintainer becomes a maintainer if either: - - Two weeks have passed since at least three (3) Maintainer PR approvals have been recorded, OR - - An absolute majority of maintainers have approved the PR. -- If the PR does not get the requisite PR approvals, it may be closed. -- Once the add maintainer PR has been merged, any necessary updates to the GitHub Teams are made. +* Once the PR and necessary comments have been received, an approval timeframe begins. +* The PR **MUST** be communicated on all appropriate communication channels, including relevant community calls, chat channels and mailing lists. Comments of support from the community are welcome. +* The PR is merged and the proposed maintainer becomes a maintainer if either: + * Two weeks have passed since at least three (3) Maintainer PR approvals have been recorded, OR + * An absolute majority of maintainers have approved the PR. +* If the PR does not get the requisite PR approvals, it may be closed. +* Once the add maintainer PR has been merged, any necessary updates to the GitHub Teams are made. ## Removing Maintainers @@ -78,28 +77,28 @@ Being a maintainer is not a status symbol or a title to be carried indefinitely. It will occasionally be necessary and appropriate to move a maintainer to emeritus status. This can occur in the following situations: -- Resignation of a maintainer. -- Violation of the Code of Conduct warranting removal. -- Inactivity. - - A general measure of inactivity will be no commits or code review comments +* Resignation of a maintainer. +* Violation of the Code of Conduct warranting removal. +* Inactivity. + * A general measure of inactivity will be no commits or code review comments for one reporting quarter. This will not be strictly enforced if the maintainer expresses a reasonable intent to continue contributing. - - Reasonable exceptions to inactivity will be granted for known long term + * Reasonable exceptions to inactivity will be granted for known long term leave such as parental leave and medical leave. -- Other circumstances at the discretion of the other Maintainers. +* Other circumstances at the discretion of the other Maintainers. The process to move a maintainer from active to emeritus status is comparable to the process for adding a maintainer, outlined above. In the case of voluntary resignation, the Pull Request can be merged following a maintainer PR approval. If the removal is for any other reason, the following steps **SHOULD** be followed: -- A PR is created to update this file to move the maintainer to the list of emeritus maintainers. -- The PR is authored by, or has a comment supporting the proposal from, an existing maintainer or a member of the project's Technical Steering Commitee (TSC). -- Once the PR and necessary comments have been received, the approval timeframe begins. -- The PR **MAY** be communicated on appropriate communication channels, including relevant community calls, chat channels and mailing lists. -- The PR is merged and the maintainer transitions to maintainer emeritus if: - - The PR is approved by the maintainer to be transitioned, OR - - Two weeks have passed since at least three (3) Maintainer PR approvals have been recorded, OR - - An absolute majority of maintainers have approved the PR. -- If the PR does not get the requisite PR approvals, it may be closed. +* A PR is created to update this file to move the maintainer to the list of emeritus maintainers. +* The PR is authored by, or has a comment supporting the proposal from, an existing maintainer or a member of the project's Technical Steering Commitee (TSC). +* Once the PR and necessary comments have been received, the approval timeframe begins. +* The PR **MAY** be communicated on appropriate communication channels, including relevant community calls, chat channels and mailing lists. +* The PR is merged and the maintainer transitions to maintainer emeritus if: + * The PR is approved by the maintainer to be transitioned, OR + * Two weeks have passed since at least three (3) Maintainer PR approvals have been recorded, OR + * An absolute majority of maintainers have approved the PR. +* If the PR does not get the requisite PR approvals, it may be closed. Returning to active status from emeritus status uses the same steps as adding a new maintainer. Note that the emeritus maintainer already has the 5 required diff --git a/docs/site/content/en/_index.md b/docs/site/content/en/_index.md index 4a79935a7..13d37bb78 100644 --- a/docs/site/content/en/_index.md +++ b/docs/site/content/en/_index.md @@ -2,7 +2,7 @@ title: Solo docs --- -{{< blocks/cover title="Welcome to Solo" image_anchor="top" height="full" >}} +{{< blocks/cover title="Welcome to Solo" image\_anchor="top" height="full" >}} Learn More {{< blocks/link-down color="info" >}} {{< /blocks/cover >}} diff --git a/docs/site/content/en/about/index.md b/docs/site/content/en/about/index.md index 9b06fddbe..0675bac69 100644 --- a/docs/site/content/en/about/index.md +++ b/docs/site/content/en/about/index.md @@ -4,7 +4,7 @@ linkTitle: About menu: {main: {weight: 10}} --- -{{% blocks/cover title="About Solo" image_anchor="bottom" height="auto" %}} +{{% blocks/cover title="About Solo" image\_anchor="bottom" height="auto" %}} ### A Hiero Ledger tool for deploying and managing Hiero Ledger networks. diff --git a/eslint.config.mjs b/eslint.config.mjs index 04a96ce52..ca747b8fb 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -137,7 +137,7 @@ export default [ 'warn', { allowExpressions: false, - allowTypedFunctionExpressions: false, + allowTypedFunctionExpressions: true, allowHigherOrderFunctions: false, }, ], diff --git a/src/business/errors/read-remote-config-before-load-error.ts b/src/business/errors/read-remote-config-before-load-error.ts new file mode 100644 index 000000000..2b876779a --- /dev/null +++ b/src/business/errors/read-remote-config-before-load-error.ts @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 + +import {SoloError} from '../../core/errors/solo-error.js'; + +export class ReadRemoteConfigBeforeLoadError extends SoloError { + public constructor(message: string, cause?: Error, meta?: object) { + super(message, cause, meta); + } +} diff --git a/src/business/errors/write-remote-config-before-load-error.ts b/src/business/errors/write-remote-config-before-load-error.ts new file mode 100644 index 000000000..165073cd0 --- /dev/null +++ b/src/business/errors/write-remote-config-before-load-error.ts @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 + +import {SoloError} from '../../core/errors/solo-error.js'; + +export class WriteRemoteConfigBeforeLoadError extends SoloError { + public constructor(message: string, cause?: Error, meta?: object) { + super(message, cause, meta); + } +} diff --git a/src/business/runtime-state/api/remote-config-runtime-state-api.ts b/src/business/runtime-state/api/remote-config-runtime-state-api.ts new file mode 100644 index 000000000..13fd7aa1b --- /dev/null +++ b/src/business/runtime-state/api/remote-config-runtime-state-api.ts @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 + +import { + type ClusterReference, + type ClusterReferences, + type Context, + type DeploymentName, +} from '../../../types/index.js'; +import {type NamespaceName} from '../../../types/namespace/namespace-name.js'; +import {type ConfigMap} from '../../../integration/kube/resources/config-map/config-map.js'; +import {type AnyObject, type ArgvStruct, type NodeAliases} from '../../../types/aliases.js'; +import {type LedgerPhase} from '../../../data/schema/model/remote/ledger-phase.js'; +import {type ConsensusNode} from '../../../core/model/consensus-node.js'; +import {type ComponentFactoryApi} from '../../../core/config/remote/api/component-factory-api.js'; +import {type RemoteConfig} from '../config/remote/remote-config.js'; + +export interface RemoteConfigRuntimeStateApi { + currentCluster: ClusterReference; + configuration?: RemoteConfig; + + getClusterRefs(): ClusterReferences; + getContexts(): Context[]; + getConsensusNodes(): ConsensusNode[]; + deleteComponents(): Promise; + + isLoaded(): boolean; + load(namespace?: NamespaceName, context?: Context): Promise; + populateRemoteConfig(configMap: ConfigMap): Promise; + write(): Promise; + persist(): Promise; + + create( + argv: ArgvStruct, + ledgerPhase: LedgerPhase, + nodeAliases: NodeAliases, + namespace: NamespaceName, + deployment: DeploymentName, + clusterReference: ClusterReference, + context: Context, + dnsBaseDomain: string, + dnsConsensusNodePattern: string, + ): Promise; + + createFromExisting( + namespace: NamespaceName, + clusterReference: ClusterReference, + deployment: DeploymentName, + componentFactory: ComponentFactoryApi, + dnsBaseDomain: string, + dnsConsensusNodePattern: string, + existingClusterContext: Context, + argv: ArgvStruct, + nodeAliases: NodeAliases, + ): Promise; + + addCommandToHistory(command: string): void; + createConfigMap(namespace: NamespaceName, context: Context): Promise; + getConfigMap(namespace?: NamespaceName, context?: Context): Promise; + loadAndValidate( + argv: {_: string[]} & AnyObject, + validate?: boolean, + skipConsensusNodesValidation?: boolean, + ): Promise; +} diff --git a/src/business/runtime-state/config/local/local-config-runtime-state.ts b/src/business/runtime-state/config/local/local-config-runtime-state.ts index 409cc5cf6..9f661d3b8 100644 --- a/src/business/runtime-state/config/local/local-config-runtime-state.ts +++ b/src/business/runtime-state/config/local/local-config-runtime-state.ts @@ -51,7 +51,8 @@ export class LocalConfigRuntimeState { // Loads the source data and writes it back in case of migrations. public async load(): Promise { if (!this.configFileExists()) { - return await this.persist(); + await this.persist(); + return; } try { diff --git a/src/business/runtime-state/config/remote/remote-config-runtime-state.ts b/src/business/runtime-state/config/remote/remote-config-runtime-state.ts new file mode 100644 index 000000000..a1086e585 --- /dev/null +++ b/src/business/runtime-state/config/remote/remote-config-runtime-state.ts @@ -0,0 +1,528 @@ +// SPDX-License-Identifier: Apache-2.0 + +import {inject, injectable} from 'tsyringe-neo'; +import {type ObjectMapper} from '../../../../data/mapper/api/object-mapper.js'; +import {ClassToObjectMapper} from '../../../../data/mapper/impl/class-to-object-mapper.js'; +import {ConfigKeyFormatter} from '../../../../data/key/config-key-formatter.js'; +import {ReadRemoteConfigBeforeLoadError} from '../../../errors/read-remote-config-before-load-error.js'; +import {WriteRemoteConfigBeforeLoadError} from '../../../errors/write-remote-config-before-load-error.js'; +import {RemoteConfigSource} from '../../../../data/configuration/impl/remote-config-source.js'; +import {YamlConfigMapStorageBackend} from '../../../../data/backend/impl/yaml-config-map-storage-backend.js'; +import {type ConfigMap} from '../../../../integration/kube/resources/config-map/config-map.js'; +import {LedgerPhase} from '../../../../data/schema/model/remote/ledger-phase.js'; +import {SemVer} from 'semver'; +import {ComponentsDataWrapperApi} from '../../../../core/config/remote/api/components-data-wrapper-api.js'; +import {InjectTokens} from '../../../../core/dependency-injection/inject-tokens.js'; +import {type K8Factory} from '../../../../integration/kube/k8-factory.js'; +import {type SoloLogger} from '../../../../core/logging/solo-logger.js'; +import {type ConfigManager} from '../../../../core/config-manager.js'; +import {patchInject} from '../../../../core/dependency-injection/container-helper.js'; +import { + type ClusterReference, + type ClusterReferences, + type Context, + type DeploymentName, + type NamespaceNameAsString, +} from '../../../../types/index.js'; +import {type AnyObject, type ArgvStruct, type NodeAlias, type NodeAliases} from '../../../../types/aliases.js'; +import {NamespaceName} from '../../../../types/namespace/namespace-name.js'; +import {ComponentStateMetadataSchema} from '../../../../data/schema/model/remote/state/component-state-metadata-schema.js'; +import {Templates} from '../../../../core/templates.js'; +import {DeploymentPhase} from '../../../../data/schema/model/remote/deployment-phase.js'; +import {getSoloVersion} from '../../../../../version.js'; +import * as constants from '../../../../core/constants.js'; +import {SoloError} from '../../../../core/errors/solo-error.js'; +import {Flags as flags} from '../../../../commands/flags.js'; +import {promptTheUserForDeployment} from '../../../../core/resolvers.js'; +import {ConsensusNode} from '../../../../core/model/consensus-node.js'; +import {RemoteConfigRuntimeStateApi} from '../../api/remote-config-runtime-state-api.js'; +import {type RemoteConfigValidatorApi} from '../../../../core/config/remote/api/remote-config-validator-api.js'; +import {ComponentFactoryApi} from '../../../../core/config/remote/api/component-factory-api.js'; +import {ComponentTypes} from '../../../../core/config/remote/enumerations/component-types.js'; +import {LocalConfigRuntimeState} from '../local/local-config-runtime-state.js'; +import {RemoteConfigMetadataSchema} from '../../../../data/schema/model/remote/remote-config-metadata-schema.js'; +import {ApplicationVersionsSchema} from '../../../../data/schema/model/common/application-versions-schema.js'; +import {ClusterSchema} from '../../../../data/schema/model/common/cluster-schema.js'; +import {DeploymentStateSchema} from '../../../../data/schema/model/remote/deployment-state-schema.js'; +import {DeploymentHistorySchema} from '../../../../data/schema/model/remote/deployment-history-schema.js'; +import {RemoteConfigSchemaDefinition} from '../../../../data/schema/migration/impl/remote/remote-config-schema-definition.js'; +import {RemoteConfigSchema} from '../../../../data/schema/model/remote/remote-config-schema.js'; +import {ConsensusNodeStateSchema} from '../../../../data/schema/model/remote/state/consensus-node-state-schema.js'; +import {UserIdentitySchema} from '../../../../data/schema/model/common/user-identity-schema.js'; +import {Deployment} from '../local/deployment.js'; +import {RemoteConfig} from './remote-config.js'; + +enum RuntimeStatePhase { + Loaded = 'loaded', + NotLoaded = 'not_loaded', +} + +@injectable() +export class RemoteConfigRuntimeState implements RemoteConfigRuntimeStateApi { + private phase: RuntimeStatePhase = RuntimeStatePhase.NotLoaded; + + public clusterReferences: Map = new Map(); + private namespace: NamespaceName; + + private source?: RemoteConfigSource; + private backend?: YamlConfigMapStorageBackend; + private objectMapper?: ObjectMapper; + + private _remoteConfig?: RemoteConfig; + + public constructor( + @inject(InjectTokens.K8Factory) private readonly k8Factory?: K8Factory, + @inject(InjectTokens.SoloLogger) private readonly logger?: SoloLogger, + @inject(InjectTokens.LocalConfigRuntimeState) private readonly localConfig?: LocalConfigRuntimeState, + @inject(InjectTokens.ConfigManager) private readonly configManager?: ConfigManager, + @inject(InjectTokens.RemoteConfigValidator) private readonly remoteConfigValidator?: RemoteConfigValidatorApi, + ) { + this.k8Factory = patchInject(k8Factory, InjectTokens.K8Factory, this.constructor.name); + this.logger = patchInject(logger, InjectTokens.SoloLogger, this.constructor.name); + this.localConfig = patchInject(localConfig, InjectTokens.LocalConfigRuntimeState, this.constructor.name); + this.configManager = patchInject(configManager, InjectTokens.ConfigManager, this.constructor.name); + } + + public get configuration(): RemoteConfig { + this.failIfNotLoaded(); + return this._remoteConfig; + } + + public get components(): Readonly { + this.failIfNotLoaded(); + return this._remoteConfig.components; + } + + public get currentCluster(): ClusterReference { + return this.k8Factory.default().clusters().readCurrent(); + } + + public async load(namespace?: NamespaceName, context?: Context): Promise { + if (this.isLoaded()) { + return; + } + + const configMap: ConfigMap = await this.getConfigMap(namespace, context); + await this.populateRemoteConfig(configMap); + } + + public async populateRemoteConfig(configMap: ConfigMap): Promise { + this.backend = new YamlConfigMapStorageBackend(configMap); + this.objectMapper = new ClassToObjectMapper(ConfigKeyFormatter.instance()); + this.source = new RemoteConfigSource( + new RemoteConfigSchemaDefinition(this.objectMapper), + this.objectMapper, + this.backend, + ); + await this.source.load(); + this._remoteConfig = new RemoteConfig(this.source.modelData); + this.phase = RuntimeStatePhase.Loaded; + } + + public async write(): Promise { + await this.source.persist(); + const remoteConfigDataBytes: Buffer = await this.backend.readBytes(constants.SOLO_REMOTE_CONFIGMAP_DATA_KEY); + const remoteConfigData: Record = { + [constants.SOLO_REMOTE_CONFIGMAP_DATA_KEY]: remoteConfigDataBytes.toString('utf8'), + }; + + const promises: Promise[] = []; + + for (const contexts of this.clusterReferences.keys()) { + promises.push(this.updateConfigMap(contexts, this.namespace, remoteConfigData)); + } + + await Promise.all(promises); + } + + private async updateConfigMap( + context: Context, + namespace: NamespaceName, + data: Record, + ): Promise { + console.log({context, namespace, data}); + await this.k8Factory.getK8(context).configMaps().update(namespace, constants.SOLO_REMOTE_CONFIGMAP_NAME, data); + } + + public isLoaded(): boolean { + return this.phase === RuntimeStatePhase.Loaded; + } + + private failIfNotLoaded(): void { + if (!this.isLoaded()) { + throw new ReadRemoteConfigBeforeLoadError('Attempting to read from remote config before loading it'); + } + } + + public async persist(): Promise { + if (!this.isLoaded()) { + throw new WriteRemoteConfigBeforeLoadError('Attempting to persist remote config before loading it'); + } + await this.write(); + } + + public async create( + argv: ArgvStruct, + ledgerPhase: LedgerPhase, + nodeAliases: NodeAliases, + namespace: NamespaceName, + deployment: DeploymentName, + clusterReference: ClusterReference, + context: Context, + dnsBaseDomain: string, + dnsConsensusNodePattern: string, + ): Promise { + const consensusNodeStates: ConsensusNodeStateSchema[] = nodeAliases.map( + (nodeAlias: NodeAlias): ConsensusNodeStateSchema => { + return new ConsensusNodeStateSchema( + new ComponentStateMetadataSchema( + Templates.nodeIdFromNodeAlias(nodeAlias), + namespace.name, + clusterReference, + DeploymentPhase.REQUESTED, + ), + ); + }, + ); + + const userIdentity: Readonly = this.localConfig.configuration.userIdentity; + const cliVersion: SemVer = new SemVer(getSoloVersion()); + const command: string = argv._.join(' '); + + const cluster: ClusterSchema = new ClusterSchema( + clusterReference, + namespace.name, + deployment, + dnsBaseDomain, + dnsConsensusNodePattern, + ); + + const remoteConfig: RemoteConfigSchema = new RemoteConfigSchema( + undefined, + new RemoteConfigMetadataSchema(new Date(), userIdentity), + new ApplicationVersionsSchema(cliVersion), + [cluster], + new DeploymentStateSchema(ledgerPhase, consensusNodeStates), + new DeploymentHistorySchema([command], command), + ); + + const configMap: ConfigMap = await this.createConfigMap(namespace, context); + await this.populateRemoteConfig(configMap); + await this.write(); + } + + public async createFromExisting( + namespace: NamespaceName, + clusterReference: ClusterReference, + deployment: DeploymentName, + componentFactory: ComponentFactoryApi, + dnsBaseDomain: string, + dnsConsensusNodePattern: string, + existingClusterContext: Context, + argv: ArgvStruct, + nodeAliases: NodeAliases, + ): Promise { + const existingRemoteConfigConfigMap: ConfigMap = await this.getConfigMap(namespace, existingClusterContext); + await this.populateRemoteConfig(existingRemoteConfigConfigMap); + + //? Create copy of the existing remote config inside the new cluster + await this.createConfigMap(namespace, existingClusterContext); + await this.write(); + + //* update the command history + this.addCommandToHistory(argv._.join(' ')); + + //* add the new clusters + this.configuration.addCluster( + new ClusterSchema(clusterReference, namespace.name, deployment, dnsBaseDomain, dnsConsensusNodePattern), + ); + + //* add the new nodes to components + for (const nodeAlias of nodeAliases) { + this.configuration.components.addNewComponent( + componentFactory.createNewConsensusNodeComponent( + Templates.nodeIdFromNodeAlias(nodeAlias), + clusterReference, + namespace, + DeploymentPhase.REQUESTED, + ), + ComponentTypes.ConsensusNode, + ); + } + + await this.persist(); + } + + public addCommandToHistory(command: string): void { + this.source.modelData.history.commands.push(command); + this.source.modelData.history.lastExecutedCommand = command; + + if (this.source.modelData.history.commands.length > constants.SOLO_REMOTE_CONFIG_MAX_COMMAND_IN_HISTORY) { + this.source.modelData.history.commands.shift(); + } + } + + public async createConfigMap(namespace: NamespaceName, context: Context): Promise { + const name: string = constants.SOLO_REMOTE_CONFIGMAP_NAME; + const labels: Record = constants.SOLO_REMOTE_CONFIGMAP_LABELS; + await this.k8Factory + .getK8(context) + .configMaps() + .create(namespace, name, labels, {[constants.SOLO_REMOTE_CONFIGMAP_DATA_KEY]: '{}'}); + return await this.k8Factory.getK8(context).configMaps().read(namespace, name); + } + + public async getConfigMap(namespace?: NamespaceName, context?: Context): Promise { + const configMap: ConfigMap = await this.k8Factory + .getK8(context) + .configMaps() + .read(namespace, constants.SOLO_REMOTE_CONFIGMAP_NAME); + + if (!configMap) { + throw new SoloError(`Remote config ConfigMap not found for namespace: ${namespace}, context: ${context}`); + } + + return configMap; + } + + /** + * Performs the loading of the remote configuration. + * Checks if the configuration is already loaded, otherwise loads and adds the command to history. + * + * @param argv - arguments containing command input for historical reference. + * @param validate - whether to validate the remote configuration. + * @param [skipConsensusNodesValidation] - whether or not to validate the consensusNodes + */ + public async loadAndValidate( + argv: {_: string[]} & AnyObject, + validate: boolean = true, + skipConsensusNodesValidation: boolean = true, + ): Promise { + await this.setDefaultNamespaceAndDeploymentIfNotSet(argv); + this.setDefaultContextIfNotSet(); + + const deploymentName: DeploymentName = this.configManager.getFlag(flags.deployment); + const deployment: Deployment = this.localConfig.configuration.deploymentByName(deploymentName); + this.namespace = NamespaceName.of(deployment.namespace); + const context: Context = this.localConfig.configuration.clusterRefs.get(deployment.clusters[0])?.toString(); + + for (const clusterReference of deployment.clusters) { + const context: Context = this.localConfig.configuration.clusterRefs.get(clusterReference.toString())?.toString(); + this.clusterReferences.set(context, clusterReference.toString()); + } + + // TODO: Compare configs from clusterReferences + + await this.load(this.namespace, context); + + this.logger.info('Remote config loaded'); + if (!validate) { + return; + } + + await this.remoteConfigValidator.validateComponents( + this.namespace, + skipConsensusNodesValidation, + this.configuration.state, + ); + + const currentCommand: string = argv._?.join(' '); + const commandArguments: string = flags.stringifyArgv(argv); + + this.addCommandToHistory( + `Executed by ${this.localConfig.configuration.userIdentity.name}: ${currentCommand} ${commandArguments}`.trim(), + ); + + this.populateVersionsInMetadata(argv, this.source.modelData); + + await this.persist(); + } + + private populateVersionsInMetadata(argv: AnyObject, remoteConfig: RemoteConfigSchema): void { + const command: string = argv._[0]; + const subcommand: string = argv._[1]; + + const isCommandUsingSoloChartVersionFlag: boolean = + (command === 'network' && subcommand === 'deploy') || + (command === 'network' && subcommand === 'refresh') || + (command === 'node' && subcommand === 'update') || + (command === 'node' && subcommand === 'update-execute') || + (command === 'node' && subcommand === 'add') || + (command === 'node' && subcommand === 'add-execute') || + (command === 'node' && subcommand === 'delete') || + (command === 'node' && subcommand === 'delete-execute'); + + if (argv[flags.soloChartVersion.name]) { + remoteConfig.versions.cli = new SemVer(argv[flags.soloChartVersion.name]); + } else if (isCommandUsingSoloChartVersionFlag) { + remoteConfig.versions.cli = new SemVer(flags.soloChartVersion.definition.defaultValue as string); + } + + const isCommandUsingReleaseTagVersionFlag: boolean = + (command === 'node' && subcommand !== 'keys' && subcommand !== 'logs' && subcommand !== 'states') || + (command === 'network' && subcommand === 'deploy'); + + if (argv[flags.releaseTag.name]) { + remoteConfig.versions.consensusNode = new SemVer(argv[flags.releaseTag.name]); + } else if (isCommandUsingReleaseTagVersionFlag) { + remoteConfig.versions.consensusNode = new SemVer(flags.releaseTag.definition.defaultValue as string); + } + + if (argv[flags.mirrorNodeVersion.name]) { + remoteConfig.versions.mirrorNodeChart = new SemVer(argv[flags.mirrorNodeVersion.name]); + } else if (command === 'mirror-node' && subcommand === 'deploy') { + remoteConfig.versions.mirrorNodeChart = new SemVer(flags.mirrorNodeVersion.definition.defaultValue as string); + } + + if (argv[flags.explorerVersion.name]) { + remoteConfig.versions.explorerChart = new SemVer(argv[flags.explorerVersion.name]); + } else if (command === 'explorer' && subcommand === 'deploy') { + remoteConfig.versions.explorerChart = new SemVer(flags.explorerVersion.definition.defaultValue as string); + } + + if (argv[flags.relayReleaseTag.name]) { + remoteConfig.versions.jsonRpcRelayChart = new SemVer(argv[flags.relayReleaseTag.name]); + } else if (command === 'relay' && subcommand === 'deploy') { + remoteConfig.versions.jsonRpcRelayChart = new SemVer(flags.relayReleaseTag.definition.defaultValue as string); + } + } + + public async deleteComponents(): Promise { + this._remoteConfig.state.consensusNodes = []; + this._remoteConfig.state.blockNodes = []; + this._remoteConfig.state.envoyProxies = []; + this._remoteConfig.state.haProxies = []; + this._remoteConfig.state.explorers = []; + this._remoteConfig.state.mirrorNodes = []; + this._remoteConfig.state.relayNodes = []; + } + + private async setDefaultNamespaceAndDeploymentIfNotSet(argv: AnyObject): Promise { + if (this.configManager.hasFlag(flags.namespace)) { + return; + } + + // TODO: Current quick fix for commands where namespace is not passed + let deploymentName: DeploymentName = this.configManager.getFlag(flags.deployment); + let currentDeployment: Deployment = this.localConfig.configuration.deploymentByName(deploymentName); + + if (!deploymentName) { + deploymentName = await promptTheUserForDeployment(this.configManager); + currentDeployment = this.localConfig.configuration.deploymentByName(deploymentName); + // TODO: Fix once we have the DataManager, + // without this the user will be prompted a second time for the deployment + // TODO: we should not be mutating argv + argv[flags.deployment.name] = deploymentName; + this.logger.warn( + `Deployment name not found in flags or local config, setting it in argv and config manager to: ${deploymentName}`, + ); + this.configManager.setFlag(flags.deployment, deploymentName); + } + + if (!currentDeployment) { + throw new SoloError(`Selected deployment name is not set in local config - ${deploymentName}`); + } + + const namespace: NamespaceNameAsString = currentDeployment.namespace; + + this.logger.warn(`Namespace not found in flags, setting it to: ${namespace}`); + this.configManager.setFlag(flags.namespace, namespace); + argv[flags.namespace.name] = namespace; + } + + private setDefaultContextIfNotSet(): void { + if (this.configManager.hasFlag(flags.context)) { + return; + } + + const context: Context = this.getContextForFirstCluster() ?? this.k8Factory.default().contexts().readCurrent(); + + if (!context) { + throw new SoloError("Context is not passed and default one can't be acquired"); + } + + this.logger.warn(`Context not found in flags, setting it to: ${context}`); + this.configManager.setFlag(flags.context, context); + } + + //* Common Commands + + /** + * Get the consensus nodes from the remoteConfig and use the localConfig to get the context + * @returns an array of ConsensusNode objects + */ + public getConsensusNodes(): ConsensusNode[] { + if (!this.isLoaded()) { + throw new SoloError('Remote configuration is not loaded, and was expected to be loaded'); + } + + const consensusNodes: ConsensusNode[] = []; + + for (const node of Object.values(this.configuration.state.consensusNodes)) { + const cluster: ClusterSchema = this.configuration.clusters.find( + (cluster: ClusterSchema): boolean => cluster.name === node.metadata.cluster, + ); + const context: Context = this.localConfig.configuration.clusterRefs.get(node.metadata.cluster)?.toString(); + const nodeAlias: NodeAlias = Templates.renderNodeAliasFromNumber(node.metadata.id + 1); + + consensusNodes.push( + new ConsensusNode( + nodeAlias, + node.metadata.id, + node.metadata.namespace, + node.metadata.cluster, + context, + cluster.dnsBaseDomain, + cluster.dnsConsensusNodePattern, + Templates.renderConsensusNodeFullyQualifiedDomainName( + nodeAlias, + node.metadata.id, + node.metadata.namespace, + node.metadata.cluster, + cluster.dnsBaseDomain, + cluster.dnsConsensusNodePattern, + ), + ), + ); + } + + // return the consensus nodes + return consensusNodes; + } + + /** + * Gets a list of distinct contexts from the consensus nodes. + * @returns an array of context strings. + */ + public getContexts(): Context[] { + return [...new Set(this.getConsensusNodes().map((node): Context => node.context))]; + } + + /** + * Gets a list of distinct cluster references from the consensus nodes. + * @returns an object of cluster references. + */ + public getClusterRefs(): ClusterReferences { + const nodes: ConsensusNode[] = this.getConsensusNodes(); + const accumulator: ClusterReferences = new Map(); + + for (const node of nodes) { + accumulator.set(node.cluster, node.context); + } + + return accumulator; + } + + private getContextForFirstCluster(): string { + const deploymentName: DeploymentName = this.configManager.getFlag(flags.deployment); + + const clusterReference: ClusterReference = + this.localConfig.configuration.deploymentByName(deploymentName).clusters[0]; + + const context: Context = this.localConfig.configuration.clusterRefs.get(clusterReference)?.toString(); + + this.logger.debug(`Using context ${context} for cluster ${clusterReference} for deployment ${deploymentName}`); + + return context; + } +} diff --git a/src/business/runtime-state/config/remote/remote-config.ts b/src/business/runtime-state/config/remote/remote-config.ts new file mode 100644 index 000000000..912fa2dc3 --- /dev/null +++ b/src/business/runtime-state/config/remote/remote-config.ts @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache-2.0 + +import {ComponentsDataWrapper} from '../../../../core/config/remote/components-data-wrapper.js'; +import {type Facade} from '../../facade/facade.js'; +import {type RemoteConfigSchema} from '../../../../data/schema/model/remote/remote-config-schema.js'; +import {type ComponentsDataWrapperApi} from '../../../../core/config/remote/api/components-data-wrapper-api.js'; +import {type RemoteConfigMetadataSchema} from '../../../../data/schema/model/remote/remote-config-metadata-schema.js'; +import {type ApplicationVersionsSchema} from '../../../../data/schema/model/common/application-versions-schema.js'; +import {type ClusterSchema} from '../../../../data/schema/model/common/cluster-schema.js'; +import {type DeploymentStateSchema} from '../../../../data/schema/model/remote/deployment-state-schema.js'; +import {type DeploymentHistorySchema} from '../../../../data/schema/model/remote/deployment-history-schema.js'; + +export class RemoteConfig implements Facade { + private readonly _components: ComponentsDataWrapperApi; + private readonly _schemaVersion: number; + private readonly _metadata: Readonly; + private readonly _versions: Readonly; + private readonly _clusters: Readonly[]; + private readonly _state: Readonly; + private readonly _history: Readonly; + + public constructor(public readonly encapsulatedObject: RemoteConfigSchema) { + this._components = new ComponentsDataWrapper(encapsulatedObject.state); + this._schemaVersion = encapsulatedObject.schemaVersion; + this._metadata = encapsulatedObject.metadata; + this._versions = encapsulatedObject.versions; + this._clusters = encapsulatedObject.clusters; + this._state = encapsulatedObject.state; + } + + public get components(): ComponentsDataWrapperApi { + return this._components; + } + + public get schemaVersion(): number { + return this._schemaVersion; + } + + public get metadata(): Readonly { + return this._metadata; + } + + public get versions(): Readonly { + return this._versions; + } + + public get clusters(): Readonly[]> { + return this._clusters; + } + + public get state(): DeploymentStateSchema { + return this._state; + } + + public get history(): Readonly { + return this._history; + } + + public addCluster(cluster: ClusterSchema): void { + this._clusters.push(cluster); + } +} diff --git a/src/commands/account.ts b/src/commands/account.ts index 26c67d1ee..2b44ef1aa 100644 --- a/src/commands/account.ts +++ b/src/commands/account.ts @@ -258,7 +258,7 @@ export class AccountCommand extends BaseCommand { namespace: await resolveNamespaceFromDeployment(this.localConfig, this.configManager, task), nodeAliases: helpers.parseNodeAliases( this.configManager.getFlag(flags.nodeAliasesUnparsed), - this.remoteConfigManager.getConsensusNodes(), + this.remoteConfig.getConsensusNodes(), this.configManager, ), } as Config; @@ -278,7 +278,7 @@ export class AccountCommand extends BaseCommand { await self.accountManager.loadNodeClient( config.namespace, - self.remoteConfigManager.getClusterRefs(), + self.remoteConfig.getClusterRefs(), self.configManager.getFlag(flags.deployment), self.configManager.getFlag(flags.forcePortForward), ); @@ -359,7 +359,7 @@ export class AccountCommand extends BaseCommand { const nodeId = Templates.nodeIdFromNodeAlias(nodeAlias); const nodeClient = await self.accountManager.refreshNodeClient( context_.config.namespace, - self.remoteConfigManager.getClusterRefs(), + self.remoteConfig.getClusterRefs(), nodeAlias, context_.config.deployment, ); @@ -518,7 +518,7 @@ export class AccountCommand extends BaseCommand { await self.accountManager.loadNodeClient( context_.config.namespace, - self.remoteConfigManager.getClusterRefs(), + self.remoteConfig.getClusterRefs(), config.deployment, self.configManager.getFlag(flags.forcePortForward), ); @@ -608,7 +608,7 @@ export class AccountCommand extends BaseCommand { await self.accountManager.loadNodeClient( config.namespace, - self.remoteConfigManager.getClusterRefs(), + self.remoteConfig.getClusterRefs(), config.deployment, self.configManager.getFlag(flags.forcePortForward), ); @@ -710,7 +710,7 @@ export class AccountCommand extends BaseCommand { await self.accountManager.loadNodeClient( config.namespace, - self.remoteConfigManager.getClusterRefs(), + self.remoteConfig.getClusterRefs(), config.deployment, self.configManager.getFlag(flags.forcePortForward), ); diff --git a/src/commands/base.ts b/src/commands/base.ts index 74893579f..e5e159932 100644 --- a/src/commands/base.ts +++ b/src/commands/base.ts @@ -3,7 +3,6 @@ import {SoloError} from '../core/errors/solo-error.js'; import {ShellRunner} from '../core/shell-runner.js'; import {type LockManager} from '../core/lock/lock-manager.js'; -import {type RemoteConfigManager} from '../core/config/remote/remote-config-manager.js'; import {type ChartManager} from '../core/chart-manager.js'; import {type ConfigManager} from '../core/config-manager.js'; import {type DependencyManager} from '../core/dependency-managers/index.js'; @@ -18,6 +17,7 @@ import {PathEx} from '../business/utils/path-ex.js'; import {inject} from 'tsyringe-neo'; import {patchInject} from '../core/dependency-injection/container-helper.js'; import {InjectTokens} from '../core/dependency-injection/inject-tokens.js'; +import {type RemoteConfigRuntimeStateApi} from '../business/runtime-state/api/remote-config-runtime-state-api.js'; export abstract class BaseCommand extends ShellRunner { constructor( @@ -28,7 +28,7 @@ export abstract class BaseCommand extends ShellRunner { @inject(InjectTokens.DependencyManager) protected readonly depManager?: DependencyManager, @inject(InjectTokens.LockManager) protected readonly leaseManager?: LockManager, @inject(InjectTokens.LocalConfigRuntimeState) public readonly localConfig?: LocalConfigRuntimeState, - @inject(InjectTokens.RemoteConfigManager) protected readonly remoteConfigManager?: RemoteConfigManager, + @inject(InjectTokens.RemoteConfigRuntimeState) protected readonly remoteConfig?: RemoteConfigRuntimeStateApi, ) { super(); @@ -39,11 +39,7 @@ export abstract class BaseCommand extends ShellRunner { this.depManager = patchInject(depManager, InjectTokens.DependencyManager, this.constructor.name); this.leaseManager = patchInject(leaseManager, InjectTokens.LockManager, this.constructor.name); this.localConfig = patchInject(localConfig, InjectTokens.LocalConfigRuntimeState, this.constructor.name); - this.remoteConfigManager = patchInject( - remoteConfigManager, - InjectTokens.RemoteConfigManager, - this.constructor.name, - ); + this.remoteConfig = patchInject(remoteConfig, InjectTokens.RemoteConfigRuntimeState, this.constructor.name); } /** @@ -53,7 +49,7 @@ export abstract class BaseCommand extends ShellRunner { * 1. Chart's default values file (if chartDirectory is set) * 2. Profile values file * 3. User's values file - * @param clusterRefs + * @param clusterReferences * @param valuesFileInput - the values file input string * @param chartDirectory - the chart directory * @param profileValuesFile - mapping of clusterRef to the profile values file full path diff --git a/src/commands/block-node.ts b/src/commands/block-node.ts index dfaff177c..d980e4a64 100644 --- a/src/commands/block-node.ts +++ b/src/commands/block-node.ts @@ -16,19 +16,28 @@ import { type NodeAliases, } from '../types/aliases.js'; import {ListrLock} from '../core/lock/listr-lock.js'; -import {type ClusterReference, type DeploymentName} from '../types/index.js'; -import {type CommandDefinition, type Optional, type SoloListrTask, type SoloListrTaskWrapper} from '../types/index.js'; +import { + type ClusterReference, + type CommandDefinition, + type DeploymentName, + type Optional, + type SoloListrTask, + type SoloListrTaskWrapper, +} from '../types/index.js'; import * as versions from '../../version.js'; import {type CommandFlag, type CommandFlags} from '../types/flag-types.js'; import {type Lock} from '../core/lock/lock.js'; import {type NamespaceName} from '../types/namespace/namespace-name.js'; -import {BlockNodeComponent} from '../core/config/remote/components/block-node-component.js'; import {ContainerReference} from '../integration/kube/resources/container/container-reference.js'; import {Duration} from '../core/time/duration.js'; import {type PodReference} from '../integration/kube/resources/pod/pod-reference.js'; import chalk from 'chalk'; import {CommandBuilder, CommandGroup, Subcommand} from '../core/command-path-builders/command-builder.js'; import {type Pod} from '../integration/kube/resources/pod/pod.js'; +import {BlockNodeStateSchema} from '../data/schema/model/remote/state/block-node-state-schema.js'; +import {ComponentStateMetadataSchema} from '../data/schema/model/remote/state/component-state-metadata-schema.js'; +import {DeploymentPhase} from '../data/schema/model/remote/deployment-phase.js'; +import {ComponentTypes} from '../core/config/remote/enumerations/component-types.js'; interface BlockNodeDeployConfigClass { chartVersion: string; @@ -44,7 +53,7 @@ interface BlockNodeDeployConfigClass { nodeAliases: NodeAliases; // from remote config context: string; valuesArg: string; - newBlockNodeComponent: BlockNodeComponent; + newBlockNodeComponent: BlockNodeStateSchema; releaseName: string; } @@ -81,7 +90,7 @@ export class BlockNodeCommand extends BaseCommand { valuesArgument += helpers.prepareValuesFiles(config.valuesFile); } - valuesArgument += helpers.populateHelmArguments({nameOverride: config.newBlockNodeComponent.name}); + valuesArgument += helpers.populateHelmArguments({nameOverride: config.newBlockNodeComponent.metadata.id}); if (config.domainName) { valuesArgument += helpers.populateHelmArguments({ @@ -129,15 +138,13 @@ export class BlockNodeCommand extends BaseCommand { task, ); - context_.config.nodeAliases = this.remoteConfigManager - .getConsensusNodes() - .map((node): NodeAlias => node.name); + context_.config.nodeAliases = this.remoteConfig.getConsensusNodes().map((node): NodeAlias => node.name); if (!context_.config.clusterRef) { context_.config.clusterRef = this.k8Factory.default().clusters().readCurrent(); } - context_.config.context = this.remoteConfigManager.getClusterRefs()[context_.config.clusterRef]; + context_.config.context = this.remoteConfig.getClusterRefs()[context_.config.clusterRef]; this.logger.debug('Initialized config', {config: context_.config}); @@ -151,10 +158,8 @@ export class BlockNodeCommand extends BaseCommand { config.releaseName = this.getReleaseName(); - config.newBlockNodeComponent = new BlockNodeComponent( - config.releaseName, - config.clusterRef, - config.namespace.name, + config.newBlockNodeComponent = new BlockNodeStateSchema( + new ComponentStateMetadataSchema(1, config.namespace.name, config.clusterRef, DeploymentPhase.DEPLOYED), ); }, }, @@ -260,12 +265,12 @@ export class BlockNodeCommand extends BaseCommand { private addBlockNodeComponent(): SoloListrTask { return { title: 'Add block node component in remote config', - skip: (): boolean => !this.remoteConfigManager.isLoaded(), + skip: (): boolean => !this.remoteConfig.isLoaded(), task: async (context_): Promise => { - await this.remoteConfigManager.modify(async remoteConfig => { + await this.remoteConfig.modify(async (_, components) => { const config: BlockNodeDeployConfigClass = context_.config; - remoteConfig.components.add(config.newBlockNodeComponent); + components.addNewComponent(config.newBlockNodeComponent, ComponentTypes.BlockNode); }); }, }; diff --git a/src/commands/deployment.ts b/src/commands/deployment.ts index 511ce80cb..8e631db2f 100644 --- a/src/commands/deployment.ts +++ b/src/commands/deployment.ts @@ -23,12 +23,12 @@ import {container, inject, injectable} from 'tsyringe-neo'; import {InjectTokens} from '../core/dependency-injection/inject-tokens.js'; import {type AnyYargs, type ArgvStruct, type NodeAliases} from '../types/aliases.js'; import {Templates} from '../core/templates.js'; -import {Cluster} from '../core/config/remote/cluster.js'; import {resolveNamespaceFromDeployment} from '../core/resolvers.js'; import {patchInject} from '../core/dependency-injection/container-helper.js'; -import {ConsensusNodeStates} from '../core/config/remote/enumerations/consensus-node-states.js'; import {DeploymentStates} from '../core/config/remote/enumerations/deployment-states.js'; -import {ConsensusNodeComponent} from '../core/config/remote/components/consensus-node-component.js'; +import {LedgerPhase} from '../data/schema/model/remote/ledger-phase.js'; +import {type ConfigMap} from '../integration/kube/resources/config-map/config-map.js'; +import {type ComponentFactoryApi} from '../core/config/remote/api/component-factory-api.js'; import {StringFacade} from '../business/runtime-state/facade/string-facade.js'; import {Deployment} from '../business/runtime-state/config/local/deployment.js'; @@ -44,7 +44,7 @@ interface DeploymentAddClusterConfig { dnsBaseDomain: string; dnsConsensusNodePattern: string; - state?: DeploymentStates; + ledgerPhase?: LedgerPhase; nodeAliases: NodeAliases; existingNodesCount: number; @@ -57,7 +57,10 @@ export interface DeploymentAddClusterContext { @injectable() export class DeploymentCommand extends BaseCommand { - constructor(@inject(InjectTokens.ClusterCommandTasks) private readonly tasks: ClusterCommandTasks) { + public constructor( + @inject(InjectTokens.ClusterCommandTasks) private readonly tasks: ClusterCommandTasks, + @inject(InjectTokens.ComponentFactory) private readonly componentFactory: ComponentFactoryApi, + ) { super(); this.tasks = patchInject(tasks, InjectTokens.ClusterCommandTasks, this.constructor.name); @@ -225,7 +228,7 @@ export class DeploymentCommand extends BaseCommand { for (const clusterReference of clusterReferences) { const context = self.localConfig.configuration.clusterRefs.get(clusterReference.toString()).toString(); const namespace = NamespaceName.of(self.localConfig.configuration.deploymentByName(deployment).namespace); - const remoteConfigExists = await self.remoteConfigManager.get(context); + const remoteConfigExists: ConfigMap = await self.remoteConfig.getConfigMap(namespace, context); const namespaceExists = await self.k8Factory.getK8(context).namespaces().has(namespace); const existingConfigMaps = await self.k8Factory .getK8(context) @@ -291,6 +294,7 @@ export class DeploymentCommand extends BaseCommand { try { await tasks.run(); } catch (error: Error | unknown) { + console.error(error); throw new SoloError('Error adding cluster to deployment', error); } @@ -541,8 +545,8 @@ export class DeploymentCommand extends BaseCommand { } /** - * Checks the network state: - * - if remote config is found check's the state field to see if it's pre or post genesis. + * Checks the ledger phase: + * - if remote config is found check's the ledgerPhase field to see if it's pre or post genesis. * - pre genesis: * - prompts user if needed. * - generates node aliases based on '--number-of-consensus-nodes' @@ -554,15 +558,15 @@ export class DeploymentCommand extends BaseCommand { */ public checkNetworkState(): SoloListrTask { return { - title: 'check network state', + title: 'check ledger phase', task: async (context_, task) => { - const {deployment, numberOfConsensusNodes, quiet} = context_.config; + const {deployment, numberOfConsensusNodes, quiet, namespace} = context_.config; const existingClusterReferences = this.localConfig.configuration.deploymentByName(deployment).clusters; - // if there is no remote config don't validate deployment state + // if there is no remote config don't validate deployment ledger phase if (existingClusterReferences.length === 0) { - context_.config.state = DeploymentStates.PRE_GENESIS; + context_.config.ledgerPhase = LedgerPhase.UNINITIALIZED; // if the user can't be prompted for '--num-consensus-nodes' fail if (!numberOfConsensusNodes && quiet) { @@ -585,22 +589,28 @@ export class DeploymentCommand extends BaseCommand { ?.toString(); context_.config.existingClusterContext = existingClusterContext; - const remoteConfig = await this.remoteConfigManager.get(existingClusterContext); + const remoteConfigConfigMap: ConfigMap = await this.remoteConfig.getConfigMap( + namespace, + existingClusterContext, + ); + + await this.remoteConfig.populateRemoteConfig(remoteConfigConfigMap); - const state = remoteConfig.metadata.state; - context_.config.state = state; + const ledgerPhase: LedgerPhase = this.remoteConfig.state.ledgerPhase; - const existingNodesCount = Object.keys(remoteConfig.components.consensusNodes).length; + context_.config.ledgerPhase = ledgerPhase; + + const existingNodesCount: number = Object.keys(this.remoteConfig.state.consensusNodes).length; context_.config.nodeAliases = Templates.renderNodeAliasesFromCount(numberOfConsensusNodes, existingNodesCount); - // If state is pre-genesis and user can't be prompted for the '--num-consensus-nodes' fail - if (state === DeploymentStates.PRE_GENESIS && !numberOfConsensusNodes && quiet) { - throw new SoloError(`--${flags.numberOfConsensusNodes} must be specified ${DeploymentStates.PRE_GENESIS}`); + // If ledgerPhase is pre-genesis and user can't be prompted for the '--num-consensus-nodes' fail + if (ledgerPhase === LedgerPhase.UNINITIALIZED && !numberOfConsensusNodes && quiet) { + throw new SoloError(`--${flags.numberOfConsensusNodes} must be specified ${LedgerPhase.UNINITIALIZED}`); } - // If state is pre-genesis prompt the user for the '--num-consensus-nodes' - else if (state === DeploymentStates.PRE_GENESIS && !numberOfConsensusNodes) { + // If ledgerPhase is pre-genesis prompt the user for the '--num-consensus-nodes' + else if (ledgerPhase === LedgerPhase.UNINITIALIZED && !numberOfConsensusNodes) { await this.configManager.executePrompt(task, [flags.numberOfConsensusNodes]); context_.config.numberOfConsensusNodes = this.configManager.getFlag(flags.numberOfConsensusNodes); context_.config.nodeAliases = Templates.renderNodeAliasesFromCount( @@ -609,10 +619,10 @@ export class DeploymentCommand extends BaseCommand { ); } - // if the state is post-genesis and '--num-consensus-nodes' is specified throw - else if (state === DeploymentStates.POST_GENESIS && numberOfConsensusNodes) { + // if the ledgerPhase is post-genesis and '--num-consensus-nodes' is specified throw + else if (ledgerPhase === LedgerPhase.INITIALIZED && numberOfConsensusNodes) { throw new SoloError( - `--${flags.numberOfConsensusNodes.name}=${numberOfConsensusNodes} shouldn't be specified ${state}`, + `--${flags.numberOfConsensusNodes.name}=${numberOfConsensusNodes} shouldn't be specified ${ledgerPhase}`, ); } }, @@ -625,12 +635,12 @@ export class DeploymentCommand extends BaseCommand { public testClusterConnection(): SoloListrTask { return { title: 'Test cluster connection', - task: async (context_, task) => { + task: async (context_, task): Promise => { const {clusterRef, context} = context_.config; task.title += `: ${clusterRef}, context: ${context}`; - const isConnected = await this.k8Factory + const isConnected: boolean = await this.k8Factory .getK8(context) .namespaces() .list() @@ -647,7 +657,7 @@ export class DeploymentCommand extends BaseCommand { public verifyClusterAddPrerequisites(): SoloListrTask { return { title: 'Verify prerequisites', - task: async () => { + task: async (): Promise => { // TODO: Verifies Kubernetes cluster & namespace-level prerequisites (e.g., cert-manager, HAProxy, etc.) }, }; @@ -659,7 +669,7 @@ export class DeploymentCommand extends BaseCommand { public addClusterRefToDeployments(): SoloListrTask { return { title: 'add cluster-ref in local config deployments', - task: async (context_, task) => { + task: async (context_, task): Promise => { const {clusterRef, deployment} = context_.config; task.title = `add cluster-ref: ${clusterRef} for deployment: ${deployment} in local config`; @@ -677,12 +687,12 @@ export class DeploymentCommand extends BaseCommand { public createOrEditRemoteConfigForNewDeployment(argv: ArgvStruct): SoloListrTask { return { title: 'create remote config for deployment', - task: async (context_, task) => { + task: async (context_, task): Promise => { const { deployment, clusterRef, context, - state, + ledgerPhase, nodeAliases, namespace, existingClusterContext, @@ -698,50 +708,29 @@ export class DeploymentCommand extends BaseCommand { await this.k8Factory.getK8(context).namespaces().create(namespace); } - if (!existingClusterContext) { - await this.remoteConfigManager.create( - argv, - state, - nodeAliases, - namespace, - deployment, - clusterRef, - context, - dnsBaseDomain, - dnsConsensusNodePattern, - ); - - return; - } - - await this.remoteConfigManager.get(existingClusterContext); - - //? Create copy of the existing remote config inside the new cluster - await this.remoteConfigManager.createConfigMap(context); - - //? Update remote configs inside the clusters - await this.remoteConfigManager.modify(async remoteConfig => { - //* update the command history - remoteConfig.addCommandToHistory(argv._.join(' ')); - - //* add the new clusters - remoteConfig.addCluster( - new Cluster(clusterRef, namespace.name, deployment, dnsBaseDomain, dnsConsensusNodePattern), - ); - - //* add the new nodes to components - for (const nodeAlias of nodeAliases) { - remoteConfig.components.add( - new ConsensusNodeComponent( - nodeAlias, - clusterRef, - namespace.name, - ConsensusNodeStates.NON_DEPLOYED, - Templates.nodeIdFromNodeAlias(nodeAlias), - ), - ); - } - }); + await (existingClusterContext + ? this.remoteConfig.createFromExisting( + namespace, + clusterRef, + deployment, + this.componentFactory, + dnsBaseDomain, + dnsConsensusNodePattern, + existingClusterContext, + argv, + nodeAliases, + ) + : this.remoteConfig.create( + argv, + ledgerPhase, + nodeAliases, + namespace, + deployment, + clusterRef, + context, + dnsBaseDomain, + dnsConsensusNodePattern, + )); }, }; } diff --git a/src/commands/explorer.ts b/src/commands/explorer.ts index 7954cb20b..c0a86f8b5 100644 --- a/src/commands/explorer.ts +++ b/src/commands/explorer.ts @@ -15,9 +15,8 @@ import { import {type ProfileManager} from '../core/profile-manager.js'; import {BaseCommand} from './base.js'; import {Flags as flags} from './flags.js'; -import {type AnyYargs, type ArgvStruct} from '../types/aliases.js'; +import {type AnyListrContext, type AnyYargs, type ArgvStruct} from '../types/aliases.js'; import {ListrLock} from '../core/lock/listr-lock.js'; -import {MirrorNodeExplorerComponent} from '../core/config/remote/components/mirror-node-explorer-component.js'; import * as helpers from '../core/helpers.js'; import {prepareValuesFiles, showVersionBanner} from '../core/helpers.js'; import { @@ -36,7 +35,8 @@ import {KeyManager} from '../core/key-manager.js'; import {INGRESS_CONTROLLER_VERSION} from '../../version.js'; import {patchInject} from '../core/dependency-injection/container-helper.js'; import {ComponentTypes} from '../core/config/remote/enumerations/component-types.js'; -import {ListrRemoteConfig} from '../core/config/remote/listr-config-tasks.js'; +import {type MirrorNodeStateSchema} from '../data/schema/model/remote/state/mirror-node-state-schema.js'; +import {type ComponentFactoryApi} from '../core/config/remote/api/component-factory-api.js'; interface ExplorerDeployConfigClass { cacheDir: string; @@ -81,6 +81,7 @@ export class ExplorerCommand extends BaseCommand { public constructor( @inject(InjectTokens.ProfileManager) private readonly profileManager: ProfileManager, @inject(InjectTokens.ClusterChecks) private readonly clusterChecks: ClusterChecks, + @inject(InjectTokens.ComponentFactory) private readonly componentFactory: ComponentFactoryApi, ) { super(); @@ -263,7 +264,7 @@ export class ExplorerCommand extends BaseCommand { return ListrLock.newAcquireLockTask(lease, task); }, }, - ListrRemoteConfig.loadRemoteConfig(this.remoteConfigManager, argv), + this.loadRemoteConfigTask(argv), { title: 'Install cert manager', task: async context_ => { @@ -476,7 +477,7 @@ export class ExplorerCommand extends BaseCommand { const clusterReference: ClusterReference = this.configManager.hasFlag(flags.clusterRef) ? this.configManager.getFlag(flags.clusterRef) - : this.remoteConfigManager.currentCluster; + : this.remoteConfig.currentCluster; const clusterContext: Context = this.localConfig.configuration.clusterRefs .get(clusterReference) @@ -500,7 +501,7 @@ export class ExplorerCommand extends BaseCommand { return ListrLock.newAcquireLockTask(lease, task); }, }, - ListrRemoteConfig.loadRemoteConfig(this.remoteConfigManager, argv), + this.loadRemoteConfigTask(argv), { title: 'Destroy explorer', task: async context_ => { @@ -534,7 +535,7 @@ export class ExplorerCommand extends BaseCommand { }); }, }, - this.removeMirrorNodeExplorerComponents(), + this.disableMirrorNodeExplorerComponents(), ], { concurrent: false, @@ -614,14 +615,33 @@ export class ExplorerCommand extends BaseCommand { }; } + private loadRemoteConfigTask(argv: ArgvStruct): SoloListrTask { + return { + title: 'Load remote config', + task: async (): Promise => { + await this.remoteConfig.loadAndValidate(argv); + }, + }; + } + /** Removes the explorer components from remote config. */ - private removeMirrorNodeExplorerComponents(): SoloListrTask { + private disableMirrorNodeExplorerComponents(): SoloListrTask { return { title: 'Remove explorer from remote config', - skip: (): boolean => !this.remoteConfigManager.isLoaded(), - task: async (): Promise => { - await this.remoteConfigManager.modify(async remoteConfig => { - remoteConfig.components.remove('mirrorNodeExplorer', ComponentTypes.MirrorNodeExplorer); + skip: (): boolean => !this.remoteConfig.isLoaded(), + task: async (context_): Promise => { + const clusterReference: ClusterReference = context_.config.clusterReference; + + await this.remoteConfig.modify(async (_, components) => { + const explorerComponents: MirrorNodeStateSchema[] = + components.getComponentsByClusterReference( + ComponentTypes.Explorers, + clusterReference, + ); + + for (const explorerComponent of explorerComponents) { + components.removeComponent(explorerComponent.metadata.id, ComponentTypes.Explorers); + } }); }, }; @@ -631,14 +651,14 @@ export class ExplorerCommand extends BaseCommand { private addMirrorNodeExplorerComponents(): SoloListrTask { return { title: 'Add explorer to remote config', - skip: (): boolean => !this.remoteConfigManager.isLoaded(), + skip: (): boolean => !this.remoteConfig.isLoaded(), task: async (context_): Promise => { - await this.remoteConfigManager.modify(async remoteConfig => { - const { - config: {namespace, clusterRef}, - } = context_; - remoteConfig.components.add( - new MirrorNodeExplorerComponent('mirrorNodeExplorer', clusterRef, namespace.name), + await this.remoteConfig.modify(async (_, components) => { + const {namespace, clusterRef} = context_.config; + + components.addNewComponent( + this.componentFactory.createNewExplorerComponent(clusterRef, namespace), + ComponentTypes.Explorers, ); }); }, diff --git a/src/commands/mirror-node.ts b/src/commands/mirror-node.ts index d092365c8..8baeaad30 100644 --- a/src/commands/mirror-node.ts +++ b/src/commands/mirror-node.ts @@ -22,7 +22,6 @@ import {prepareValuesFiles, showVersionBanner} from '../core/helpers.js'; import {type AnyYargs, type ArgvStruct} from '../types/aliases.js'; import {type PodName} from '../integration/kube/resources/pod/pod-name.js'; import {ListrLock} from '../core/lock/listr-lock.js'; -import {MirrorNodeComponent} from '../core/config/remote/components/mirror-node-component.js'; import * as fs from 'node:fs'; import { type ClusterReference, @@ -49,6 +48,8 @@ import {InjectTokens} from '../core/dependency-injection/inject-tokens.js'; import {patchInject} from '../core/dependency-injection/container-helper.js'; import {ComponentTypes} from '../core/config/remote/enumerations/component-types.js'; import {type AccountId} from '@hashgraph/sdk'; +import {type MirrorNodeStateSchema} from '../data/schema/model/remote/state/mirror-node-state-schema.js'; +import {type ComponentFactoryApi} from '../core/config/remote/api/component-factory-api.js'; interface MirrorNodeDeployConfigClass { cacheDir: string; @@ -103,6 +104,7 @@ export class MirrorNodeCommand extends BaseCommand { public constructor( @inject(InjectTokens.AccountManager) private readonly accountManager?: AccountManager, @inject(InjectTokens.ProfileManager) private readonly profileManager?: ProfileManager, + @inject(InjectTokens.ComponentFactory) private readonly componentFactory?: ComponentFactoryApi, ) { super(); @@ -365,7 +367,7 @@ export class MirrorNodeCommand extends BaseCommand { const deploymentName: DeploymentName = self.configManager.getFlag(flags.deployment); await self.accountManager.loadNodeClient( context_.config.namespace, - self.remoteConfigManager.getClusterRefs(), + self.remoteConfig.getClusterRefs(), deploymentName, self.configManager.getFlag(flags.forcePortForward), ); @@ -478,7 +480,7 @@ export class MirrorNodeCommand extends BaseCommand { const portForward = this.configManager.getFlag(flags.forcePortForward); context_.addressBook = await self.accountManager.prepareAddressBookBase64( context_.config.namespace, - this.remoteConfigManager.getClusterRefs(), + this.remoteConfig.getClusterRefs(), deployment, this.configManager.getFlag(flags.operatorId), this.configManager.getFlag(flags.operatorKey), @@ -601,7 +603,7 @@ export class MirrorNodeCommand extends BaseCommand { const exchangeRatesFileIdNumber = 112; const timestamp = Date.now(); - const clusterReferences = this.remoteConfigManager.getClusterRefs(); + const clusterReferences = this.remoteConfig.getClusterRefs(); const deployment = this.configManager.getFlag(flags.deployment); const fees = await this.accountManager.getFileContents( namespace, @@ -770,7 +772,7 @@ export class MirrorNodeCommand extends BaseCommand { await self.accountManager.loadNodeClient( context_.config.namespace, - self.remoteConfigManager.getClusterRefs(), + self.remoteConfig.getClusterRefs(), self.configManager.getFlag(flags.deployment), self.configManager.getFlag(flags.forcePortForward), ); @@ -832,7 +834,7 @@ export class MirrorNodeCommand extends BaseCommand { }); }, }, - this.removeMirrorNodeComponents(), + this.disableMirrorNodeComponents(), ], { concurrent: false, @@ -919,13 +921,23 @@ export class MirrorNodeCommand extends BaseCommand { } /** Removes the mirror node components from remote config. */ - public removeMirrorNodeComponents(): SoloListrTask { + public disableMirrorNodeComponents(): SoloListrTask { return { title: 'Remove mirror node from remote config', - skip: (): boolean => !this.remoteConfigManager.isLoaded(), - task: async (): Promise => { - await this.remoteConfigManager.modify(async remoteConfig => { - remoteConfig.components.remove('mirrorNode', ComponentTypes.MirrorNode); + skip: (): boolean => !this.remoteConfig.isLoaded(), + task: async (context_): Promise => { + const clusterReference: ClusterReference = context_.config.clusterRef; + + await this.remoteConfig.modify(async (_, components) => { + const mirrorNodeComponents: MirrorNodeStateSchema[] = + components.getComponentsByClusterReference( + ComponentTypes.MirrorNode, + clusterReference, + ); + + for (const mirrorNodeComponent of mirrorNodeComponents) { + components.removeComponent(mirrorNodeComponent.metadata.id, ComponentTypes.MirrorNode); + } }); }, }; @@ -935,14 +947,15 @@ export class MirrorNodeCommand extends BaseCommand { public addMirrorNodeComponents(): SoloListrTask { return { title: 'Add mirror node to remote config', - skip: (): boolean => !this.remoteConfigManager.isLoaded(), + skip: (): boolean => !this.remoteConfig.isLoaded(), task: async (context_): Promise => { - await this.remoteConfigManager.modify(async remoteConfig => { - const { - config: {namespace, clusterRef}, - } = context_; + await this.remoteConfig.modify(async (_, components) => { + const {namespace, clusterRef} = context_.config; - remoteConfig.components.add(new MirrorNodeComponent('mirrorNode', clusterRef, namespace.name)); + components.addNewComponent( + this.componentFactory.createNewMirrorNodeComponent(clusterRef, namespace), + ComponentTypes.MirrorNode, + ); }); }, }; diff --git a/src/commands/network.ts b/src/commands/network.ts index 8ef9cdc8f..694ed3904 100644 --- a/src/commands/network.ts +++ b/src/commands/network.ts @@ -9,7 +9,6 @@ import {UserBreak} from '../core/errors/user-break.js'; import {BaseCommand} from './base.js'; import {Flags as flags} from './flags.js'; import * as constants from '../core/constants.js'; -import {SOLO_DEPLOYMENT_CHART} from '../core/constants.js'; import {Templates} from '../core/templates.js'; import { addDebugOptions, @@ -24,26 +23,28 @@ import {type KeyManager} from '../core/key-manager.js'; import {type PlatformInstaller} from '../core/platform-installer.js'; import {type ProfileManager} from '../core/profile-manager.js'; import {type CertificateManager} from '../core/certificate-manager.js'; -import {type AnyYargs, type IP, type NodeAlias, type NodeAliases} from '../types/aliases.js'; +import { + type AnyYargs, + type ArgvStruct, + type IP, + type NodeAlias, + type NodeAliases, + type NodeId, +} from '../types/aliases.js'; import {ListrLock} from '../core/lock/listr-lock.js'; -import {ConsensusNodeComponent} from '../core/config/remote/components/consensus-node-component.js'; -import {EnvoyProxyComponent} from '../core/config/remote/components/envoy-proxy-component.js'; -import {HaProxyComponent} from '../core/config/remote/components/ha-proxy-component.js'; import {v4 as uuidv4} from 'uuid'; import { type ClusterReference, type ClusterReferences, type CommandDefinition, + type Context, type DeploymentName, type Realm, type Shard, + type SoloListr, type SoloListrTask, type SoloListrTaskWrapper, } from '../types/index.js'; -import {NamespaceName} from '../types/namespace/namespace-name.js'; -import {PvcReference} from '../integration/kube/resources/pvc/pvc-reference.js'; -import {PvcName} from '../integration/kube/resources/pvc/pvc-name.js'; -import {type ConsensusNode} from '../core/model/consensus-node.js'; import {Base64} from 'js-base64'; import {SecretType} from '../integration/kube/resources/secret/secret-type.js'; import {Duration} from '../core/time/duration.js'; @@ -53,9 +54,15 @@ import {PathEx} from '../business/utils/path-ex.js'; import {inject, injectable} from 'tsyringe-neo'; import {InjectTokens} from '../core/dependency-injection/inject-tokens.js'; import {patchInject} from '../core/dependency-injection/container-helper.js'; -import {ConsensusNodeStates} from '../core/config/remote/enumerations/consensus-node-states.js'; import {lt as SemVersionLessThan, SemVer} from 'semver'; import {Deployment} from '../business/runtime-state/config/local/deployment.js'; +import {type ComponentFactoryApi} from '../core/config/remote/api/component-factory-api.js'; +import {DeploymentPhase} from '../data/schema/model/remote/deployment-phase.js'; +import {ComponentTypes} from '../core/config/remote/enumerations/component-types.js'; +import {PvcName} from '../integration/kube/resources/pvc/pvc-name.js'; +import {PvcReference} from '../integration/kube/resources/pvc/pvc-reference.js'; +import {NamespaceName} from '../types/namespace/namespace-name.js'; +import {ConsensusNode} from '../core/model/consensus-node.js'; export interface NetworkDeployConfigClass { applicationEnv: string; @@ -109,6 +116,12 @@ export interface NetworkDeployConfigClass { clusterRefs: ClusterReferences; domainNames?: string; domainNamesMapping?: Record; + debugNodeAlias: NodeAlias; + app: string; +} + +interface NetworkDeployContext { + config: NetworkDeployConfigClass; } export interface NetworkDestroyContext { @@ -133,6 +146,7 @@ export class NetworkCommand extends BaseCommand { @inject(InjectTokens.KeyManager) private readonly keyManager: KeyManager, @inject(InjectTokens.PlatformInstaller) private readonly platformInstaller: PlatformInstaller, @inject(InjectTokens.ProfileManager) private readonly profileManager: ProfileManager, + @inject(InjectTokens.ComponentFactory) private readonly componentFactory: ComponentFactoryApi, ) { super(); @@ -359,36 +373,7 @@ export class NetworkCommand extends BaseCommand { * Prepare values args string for each cluster-ref * @param config */ - async prepareValuesArgMap(config: { - chartDirectory?: string; - app?: string; - nodeAliases: string[]; - debugNodeAlias?: NodeAlias; - enablePrometheusSvcMonitor?: boolean; - releaseTag?: string; - persistentVolumeClaims?: string; - valuesFile?: string; - haproxyIpsParsed?: Record; - envoyIpsParsed?: Record; - storageType: constants.StorageType; - resolvedThrottlesFile: string; - gcsWriteAccessKey: string; - gcsWriteSecrets: string; - gcsEndpoint: string; - gcsBucket: string; - gcsBucketPrefix: string; - awsWriteAccessKey: string; - awsWriteSecrets: string; - awsEndpoint: string; - awsBucket: string; - awsBucketPrefix: string; - backupBucket: string; - loadBalancerEnabled: boolean; - clusterRefs: ClusterReferences; - consensusNodes: ConsensusNode[]; - domainNamesMapping?: Record; - cacheDir: string; - }): Promise> { + private async prepareValuesArgMap(config: NetworkDeployConfigClass): Promise> { const valuesArguments: Record = this.prepareValuesArg(config); // prepare values files for each cluster @@ -430,33 +415,7 @@ export class NetworkCommand extends BaseCommand { * Prepare the values argument for the helm chart for a given config * @param config */ - prepareValuesArg(config: { - chartDirectory?: string; - app?: string; - consensusNodes: ConsensusNode[]; - debugNodeAlias?: NodeAlias; - enablePrometheusSvcMonitor?: boolean; - releaseTag?: string; - persistentVolumeClaims?: string; - valuesFile?: string; - haproxyIpsParsed?: Record; - envoyIpsParsed?: Record; - storageType: constants.StorageType; - resolvedThrottlesFile: string; - gcsWriteAccessKey: string; - gcsWriteSecrets: string; - gcsEndpoint: string; - gcsBucket: string; - gcsBucketPrefix: string; - awsWriteAccessKey: string; - awsWriteSecrets: string; - awsEndpoint: string; - awsBucket: string; - awsBucketPrefix: string; - backupBucket: string; - loadBalancerEnabled: boolean; - domainNamesMapping?: Record; - }): Record { + private prepareValuesArg(config: NetworkDeployConfigClass): Record { const valuesArguments: Record = {}; const clusterReferences: ClusterReference[] = []; let extraEnvironmentIndex = 0; @@ -645,7 +604,10 @@ export class NetworkCommand extends BaseCommand { } } - async prepareConfig(task: any, argv: any, promptForNodeAliases: boolean = false) { + private async prepareConfig( + task: SoloListrTaskWrapper, + argv: ArgvStruct, + ): Promise { this.configManager.update(argv); this.logger.debug('Updated config with argv', {config: this.configManager.config}); @@ -744,9 +706,9 @@ export class NetworkCommand extends BaseCommand { flags.genesisThrottlesFile.definition.defaultValue as string, ); - config.consensusNodes = this.remoteConfigManager.getConsensusNodes(); - config.contexts = this.remoteConfigManager.getContexts(); - config.clusterRefs = this.remoteConfigManager.getClusterRefs(); + config.consensusNodes = this.remoteConfig.getConsensusNodes(); + config.contexts = this.remoteConfig.getContexts(); + config.clusterRefs = this.remoteConfig.getClusterRefs(); config.nodeAliases = parseNodeAliases(config.nodeAliasesUnparsed, config.consensusNodes, this.configManager); argv[flags.nodeAliasesUnparsed.name] = config.nodeAliases.join(','); @@ -796,7 +758,7 @@ export class NetworkCommand extends BaseCommand { await Promise.all( context_.config.contexts.map(async context => { // Delete all if found - this.k8Factory + await this.k8Factory .getK8(context) .configMaps() .delete(context_.config.namespace, constants.SOLO_REMOTE_CONFIGMAP_NAME); @@ -850,16 +812,12 @@ export class NetworkCommand extends BaseCommand { const self = this; const lease = await self.leaseManager.create(); - interface Context { - config: NetworkDeployConfigClass; - } - - const tasks = new Listr( + const tasks: Listr = new Listr( [ { title: 'Initialize', task: async (context_, task) => { - context_.config = await self.prepareConfig(task, argv, true); + context_.config = await self.prepareConfig(task, argv); return ListrLock.newAcquireLockTask(lease, task); }, }, @@ -965,7 +923,7 @@ export class NetworkCommand extends BaseCommand { config.valuesArgMap[clusterReference], config.clusterRefs.get(clusterReference), ); - showVersionBanner(self.logger, SOLO_DEPLOYMENT_CHART, config.soloChartVersion); + showVersionBanner(self.logger, constants.SOLO_DEPLOYMENT_CHART, config.soloChartVersion); } }, }, @@ -973,9 +931,9 @@ export class NetworkCommand extends BaseCommand { { title: 'Check for load balancer', skip: context_ => context_.config.loadBalancerEnabled === false, - task: (context_, task) => { - const subTasks: SoloListrTask[] = []; - const config = context_.config; + task: (context_, task): SoloListr => { + const subTasks: SoloListrTask[] = []; + const config: NetworkDeployConfigClass = context_.config; //Add check for network node service to be created and load balancer to be assigned (if load balancer is enabled) for (const consensusNode of config.consensusNodes) { @@ -1079,9 +1037,9 @@ export class NetworkCommand extends BaseCommand { self.waitForNetworkPods(), { title: 'Check proxy pods are running', - task: (context_, task) => { - const subTasks: SoloListrTask[] = []; - const config = context_.config; + task: (context_, task): SoloListr => { + const subTasks: SoloListrTask[] = []; + const config: NetworkDeployConfigClass = context_.config; // HAProxy for (const consensusNode of config.consensusNodes) { @@ -1129,7 +1087,7 @@ export class NetworkCommand extends BaseCommand { { title: 'Check auxiliary pods are ready', task: (_, task) => { - const subTasks: SoloListrTask[] = []; + const subTasks: SoloListrTask[] = []; // minio subTasks.push({ @@ -1213,7 +1171,7 @@ export class NetworkCommand extends BaseCommand { namespace: await resolveNamespaceFromDeployment(this.localConfig, this.configManager, task), enableTimeout: self.configManager.getFlag(flags.enableTimeout) as boolean, force: self.configManager.getFlag(flags.force) as boolean, - contexts: self.remoteConfigManager.getContexts(), + contexts: self.remoteConfig.getContexts(), }; return ListrLock.newAcquireLockTask(lease, task); @@ -1252,7 +1210,7 @@ export class NetworkCommand extends BaseCommand { } else { // If the namespace is not being deleted, // remove all components data from the remote configuration - await self.remoteConfigManager.deleteComponents(); + await self.remoteConfig.deleteComponents(); } }, constants.NETWORK_DESTROY_WAIT_TIMEOUT * 1000); @@ -1346,33 +1304,27 @@ export class NetworkCommand extends BaseCommand { } /** Adds the consensus node, envoy and haproxy components to remote config. */ - public addNodesAndProxies(): SoloListrTask { + public addNodesAndProxies(): SoloListrTask { return { title: 'Add node and proxies to remote config', - skip: (): boolean => !this.remoteConfigManager.isLoaded(), + skip: (): boolean => !this.remoteConfig.isLoaded(), task: async (context_): Promise => { - const { - config: {namespace}, - } = context_; + const {namespace} = context_.config; - await this.remoteConfigManager.modify(async remoteConfig => { + await this.remoteConfig.modify(async (_, components) => { for (const consensusNode of context_.config.consensusNodes) { - remoteConfig.components.edit( - new ConsensusNodeComponent( - consensusNode.name, - consensusNode.cluster, - namespace.name, - ConsensusNodeStates.REQUESTED, - consensusNode.nodeId, - ), - ); + const nodeId: NodeId = Templates.nodeIdFromNodeAlias(consensusNode.name); + const clusterReference: ClusterReference = consensusNode.cluster; - remoteConfig.components.add( - new EnvoyProxyComponent(`envoy-proxy-${consensusNode.name}`, consensusNode.cluster, namespace.name), - ); + components.changeNodePhase(nodeId, DeploymentPhase.REQUESTED); - remoteConfig.components.add( - new HaProxyComponent(`haproxy-${consensusNode.name}`, consensusNode.cluster, namespace.name), + components.addNewComponent( + this.componentFactory.createNewEnvoyProxyComponent(clusterReference, namespace), + ComponentTypes.EnvoyProxy, + ); + components.addNewComponent( + this.componentFactory.createNewHaProxyComponent(clusterReference, namespace), + ComponentTypes.HaProxy, ); } }); diff --git a/src/commands/node/configs.ts b/src/commands/node/configs.ts index 7b0f96231..13f9e27b0 100644 --- a/src/commands/node/configs.ts +++ b/src/commands/node/configs.ts @@ -18,7 +18,6 @@ import {InjectTokens} from '../../core/dependency-injection/inject-tokens.js'; import {type ConfigManager} from '../../core/config-manager.js'; import {patchInject} from '../../core/dependency-injection/container-helper.js'; import {type AccountManager} from '../../core/account-manager.js'; -import {type RemoteConfigManager} from '../../core/config/remote/remote-config-manager.js'; import {PathEx} from '../../business/utils/path-ex.js'; import {type NodeSetupConfigClass} from './config-interfaces/node-setup-config-class.js'; import {type NodeStartConfigClass} from './config-interfaces/node-start-config-class.js'; @@ -51,6 +50,7 @@ import {type NodeRestartContext} from './config-interfaces/node-restart-context. import {type NodeSetupContext} from './config-interfaces/node-setup-context.js'; import {type NodePrepareUpgradeContext} from './config-interfaces/node-prepare-upgrade-context.js'; import {LocalConfigRuntimeState} from '../../business/runtime-state/config/local/local-config-runtime-state.js'; +import {type RemoteConfigRuntimeStateApi} from '../../business/runtime-state/api/remote-config-runtime-state-api.js'; const PREPARE_UPGRADE_CONFIGS_NAME = 'prepareUpgradeConfig'; const DOWNLOAD_GENERATED_FILES_CONFIGS_NAME = 'downloadGeneratedFilesConfig'; @@ -68,7 +68,7 @@ export class NodeCommandConfigs { public constructor( @inject(InjectTokens.ConfigManager) private readonly configManager: ConfigManager, @inject(InjectTokens.LocalConfigRuntimeState) private readonly localConfig: LocalConfigRuntimeState, - @inject(InjectTokens.RemoteConfigManager) private readonly remoteConfigManager: RemoteConfigManager, + @inject(InjectTokens.RemoteConfigRuntimeState) private readonly remoteConfig: RemoteConfigRuntimeStateApi, @inject(InjectTokens.K8Factory) private readonly k8Factory: K8Factory, @inject(InjectTokens.AccountManager) private readonly accountManager: AccountManager, ) { @@ -76,11 +76,7 @@ export class NodeCommandConfigs { this.localConfig = patchInject(localConfig, InjectTokens.LocalConfigRuntimeState, this.constructor.name); this.k8Factory = patchInject(k8Factory, InjectTokens.K8Factory, this.constructor.name); this.accountManager = patchInject(accountManager, InjectTokens.AccountManager, this.constructor.name); - this.remoteConfigManager = patchInject( - remoteConfigManager, - InjectTokens.RemoteConfigManager, - this.constructor.name, - ); + this.remoteConfig = patchInject(remoteConfig, InjectTokens.RemoteConfigRuntimeState, this.constructor.name); } private async initializeSetup(config: AnyObject, k8Factory: K8Factory): Promise { @@ -122,7 +118,7 @@ export class NodeCommandConfigs { await this.initializeSetup(context_.config, this.k8Factory); context_.config.nodeClient = await this.accountManager.loadNodeClient( context_.config.namespace, - this.remoteConfigManager.getClusterRefs(), + this.remoteConfig.getClusterRefs(), context_.config.deployment, ); @@ -181,7 +177,7 @@ export class NodeCommandConfigs { context_.config.existingNodeAliases = []; context_.config.nodeAliases = helpers.parseNodeAliases( context_.config.nodeAliasesUnparsed, - this.remoteConfigManager.getConsensusNodes(), + this.remoteConfig.getConsensusNodes(), this.configManager, ); @@ -190,7 +186,7 @@ export class NodeCommandConfigs { if (shouldLoadNodeClient) { context_.config.nodeClient = await this.accountManager.loadNodeClient( context_.config.namespace, - this.remoteConfigManager.getClusterRefs(), + this.remoteConfig.getClusterRefs(), context_.config.deployment, ); } @@ -235,7 +231,7 @@ export class NodeCommandConfigs { if (shouldLoadNodeClient) { context_.config.nodeClient = await this.accountManager.loadNodeClient( context_.config.namespace, - this.remoteConfigManager.getClusterRefs(), + this.remoteConfig.getClusterRefs(), context_.config.deployment, ); } @@ -293,7 +289,7 @@ export class NodeCommandConfigs { if (shouldLoadNodeClient) { context_.config.nodeClient = await this.accountManager.loadNodeClient( context_.config.namespace, - this.remoteConfigManager.getClusterRefs(), + this.remoteConfig.getClusterRefs(), context_.config.deployment, ); } @@ -356,7 +352,7 @@ export class NodeCommandConfigs { if (shouldLoadNodeClient) { context_.config.nodeClient = await this.accountManager.loadNodeClient( context_.config.namespace, - this.remoteConfigManager.getClusterRefs(), + this.remoteConfig.getClusterRefs(), context_.config.deployment, ); } @@ -377,12 +373,12 @@ export class NodeCommandConfigs { context_.config.serviceMap = await this.accountManager.getNodeServiceMap( context_.config.namespace, - this.remoteConfigManager.getClusterRefs(), + this.remoteConfig.getClusterRefs(), context_.config.deployment, ); - context_.config.consensusNodes = this.remoteConfigManager.getConsensusNodes(); - context_.config.contexts = this.remoteConfigManager.getContexts(); + context_.config.consensusNodes = this.remoteConfig.getConsensusNodes(); + context_.config.contexts = this.remoteConfig.getContexts(); if (!context_.config.clusterRef) { context_.config.clusterRef = this.k8Factory.default().clusters().readCurrent(); @@ -404,13 +400,13 @@ export class NodeCommandConfigs { namespace: await resolveNamespaceFromDeployment(this.localConfig, this.configManager, task), nodeAliases: helpers.parseNodeAliases( this.configManager.getFlag(flags.nodeAliasesUnparsed), - this.remoteConfigManager.getConsensusNodes(), + this.remoteConfig.getConsensusNodes(), this.configManager, ), nodeAliasesUnparsed: this.configManager.getFlag(flags.nodeAliasesUnparsed), deployment: this.configManager.getFlag(flags.deployment), - consensusNodes: this.remoteConfigManager.getConsensusNodes(), - contexts: this.remoteConfigManager.getContexts(), + consensusNodes: this.remoteConfig.getConsensusNodes(), + contexts: this.remoteConfig.getContexts(), } as NodeLogsConfigClass; return context_.config; @@ -421,7 +417,7 @@ export class NodeCommandConfigs { context_: NodeStatesContext, task: SoloListrTaskWrapper, ): Promise { - const consensusNodes: ConsensusNode[] = this.remoteConfigManager.getConsensusNodes(); + const consensusNodes: ConsensusNode[] = this.remoteConfig.getConsensusNodes(); context_.config = { namespace: await resolveNamespaceFromDeployment(this.localConfig, this.configManager, task), nodeAliases: helpers.parseNodeAliases( @@ -432,7 +428,7 @@ export class NodeCommandConfigs { nodeAliasesUnparsed: this.configManager.getFlag(flags.nodeAliasesUnparsed), deployment: this.configManager.getFlag(flags.deployment), consensusNodes, - contexts: this.remoteConfigManager.getContexts(), + contexts: this.remoteConfig.getContexts(), } as NodeStatesConfigClass; return context_.config; @@ -454,7 +450,7 @@ export class NodeCommandConfigs { context_.config.namespace = await resolveNamespaceFromDeployment(this.localConfig, this.configManager, task); context_.config.nodeAliases = helpers.parseNodeAliases( context_.config.nodeAliasesUnparsed, - this.remoteConfigManager.getConsensusNodes(), + this.remoteConfig.getConsensusNodes(), this.configManager, ); @@ -479,7 +475,7 @@ export class NodeCommandConfigs { context_.config.curDate = new Date(); context_.config.nodeAliases = helpers.parseNodeAliases( context_.config.nodeAliasesUnparsed, - this.remoteConfigManager.getConsensusNodes(), + this.remoteConfig.getConsensusNodes(), this.configManager, ); @@ -496,7 +492,7 @@ export class NodeCommandConfigs { context_: NodeStopContext, task: SoloListrTaskWrapper, ): Promise { - const consensusNodes: ConsensusNode[] = this.remoteConfigManager.getConsensusNodes(); + const consensusNodes: ConsensusNode[] = this.remoteConfig.getConsensusNodes(); context_.config = { namespace: await resolveNamespaceFromDeployment(this.localConfig, this.configManager, task), nodeAliases: helpers.parseNodeAliases( @@ -507,7 +503,7 @@ export class NodeCommandConfigs { nodeAliasesUnparsed: this.configManager.getFlag(flags.nodeAliasesUnparsed), deployment: this.configManager.getFlag(flags.deployment), consensusNodes, - contexts: this.remoteConfigManager.getContexts(), + contexts: this.remoteConfig.getContexts(), } as NodeStopConfigClass; await checkNamespace(context_.config.consensusNodes, this.k8Factory, context_.config.namespace); @@ -522,8 +518,8 @@ export class NodeCommandConfigs { context_.config = { namespace: await resolveNamespaceFromDeployment(this.localConfig, this.configManager, task), deployment: this.configManager.getFlag(flags.deployment), - consensusNodes: this.remoteConfigManager.getConsensusNodes(), - contexts: this.remoteConfigManager.getContexts(), + consensusNodes: this.remoteConfig.getConsensusNodes(), + contexts: this.remoteConfig.getContexts(), } as NodeFreezeConfigClass; await checkNamespace(context_.config.consensusNodes, this.k8Factory, context_.config.namespace); @@ -550,7 +546,7 @@ export class NodeCommandConfigs { 'contexts', ]) as NodeStartConfigClass; context_.config.namespace = await resolveNamespaceFromDeployment(this.localConfig, this.configManager, task); - context_.config.consensusNodes = this.remoteConfigManager.getConsensusNodes(); + context_.config.consensusNodes = this.remoteConfig.getConsensusNodes(); for (const consensusNode of context_.config.consensusNodes) { const k8 = this.k8Factory.getK8(consensusNode.context); @@ -576,8 +572,8 @@ export class NodeCommandConfigs { context_.config = { namespace: await resolveNamespaceFromDeployment(this.localConfig, this.configManager, task), deployment: this.configManager.getFlag(flags.deployment), - consensusNodes: this.remoteConfigManager.getConsensusNodes(), - contexts: this.remoteConfigManager.getContexts(), + consensusNodes: this.remoteConfig.getConsensusNodes(), + contexts: this.remoteConfig.getContexts(), } as NodeRestartConfigClass; await checkNamespace(context_.config.consensusNodes, this.k8Factory, context_.config.namespace); @@ -599,7 +595,7 @@ export class NodeCommandConfigs { ]) as NodeSetupConfigClass; context_.config.namespace = await resolveNamespaceFromDeployment(this.localConfig, this.configManager, task); - context_.config.consensusNodes = this.remoteConfigManager.getConsensusNodes(); + context_.config.consensusNodes = this.remoteConfig.getConsensusNodes(); context_.config.nodeAliases = helpers.parseNodeAliases( context_.config.nodeAliasesUnparsed, context_.config.consensusNodes, diff --git a/src/commands/node/handlers.ts b/src/commands/node/handlers.ts index 501b37931..1a09521cb 100644 --- a/src/commands/node/handlers.ts +++ b/src/commands/node/handlers.ts @@ -5,17 +5,14 @@ import * as NodeFlags from './flags.js'; import {type NodeCommandConfigs} from './configs.js'; import * as constants from '../../core/constants.js'; import {type LockManager} from '../../core/lock/lock-manager.js'; -import {type RemoteConfigManager} from '../../core/config/remote/remote-config-manager.js'; import {SoloError} from '../../core/errors/solo-error.js'; import {type Lock} from '../../core/lock/lock.js'; import {type NodeCommandTasks} from './tasks.js'; import {NodeSubcommandType} from '../../core/enumerations.js'; import {NodeHelper} from './helper.js'; -import {type ArgvStruct, type NodeAlias, type NodeAliases} from '../../types/aliases.js'; -import {ConsensusNodeComponent} from '../../core/config/remote/components/consensus-node-component.js'; +import {type ArgvStruct, type NodeAlias, type NodeAliases, NodeId} from '../../types/aliases.js'; import {type Listr} from 'listr2'; import chalk from 'chalk'; -import {type ComponentsDataWrapper} from '../../core/config/remote/components-data-wrapper.js'; import {type Optional, type SoloListrTask} from '../../types/index.js'; import {inject, injectable} from 'tsyringe-neo'; import {patchInject} from '../../core/dependency-injection/container-helper.js'; @@ -28,7 +25,11 @@ import {type NodeAddContext} from './config-interfaces/node-add-context.js'; import {type NodeUpdateContext} from './config-interfaces/node-update-context.js'; import {type NodeUpgradeContext} from './config-interfaces/node-upgrade-context.js'; import {ComponentTypes} from '../../core/config/remote/enumerations/component-types.js'; -import {ConsensusNodeStates} from '../../core/config/remote/enumerations/consensus-node-states.js'; +import {DeploymentPhase} from '../../data/schema/model/remote/deployment-phase.js'; +import {Templates} from '../../core/templates.js'; +import {ConsensusNodeStateSchema} from '../../data/schema/model/remote/state/consensus-node-state-schema.js'; +import {type RemoteConfigRuntimeStateApi} from '../../business/runtime-state/api/remote-config-runtime-state-api.js'; +import {ComponentsDataWrapperApi} from '../../core/config/remote/api/components-data-wrapper-api.js'; @injectable() export class NodeCommandHandlers extends CommandHandler { @@ -37,18 +38,14 @@ export class NodeCommandHandlers extends CommandHandler { public constructor( @inject(InjectTokens.LockManager) private readonly leaseManager: LockManager, - @inject(InjectTokens.RemoteConfigManager) private readonly remoteConfigManager: RemoteConfigManager, + @inject(InjectTokens.RemoteConfigRuntimeState) private readonly remoteConfig: RemoteConfigRuntimeStateApi, @inject(InjectTokens.NodeCommandTasks) private readonly tasks: NodeCommandTasks, @inject(InjectTokens.NodeCommandConfigs) private readonly configs: NodeCommandConfigs, ) { super(); this.leaseManager = patchInject(leaseManager, InjectTokens.LockManager, this.constructor.name); this.configs = patchInject(configs, InjectTokens.NodeCommandConfigs, this.constructor.name); - this.remoteConfigManager = patchInject( - remoteConfigManager, - InjectTokens.RemoteConfigManager, - this.constructor.name, - ); + this.remoteConfig = patchInject(remoteConfig, InjectTokens.RemoteConfigRuntimeState, this.constructor.name); this.tasks = patchInject(tasks, InjectTokens.NodeCommandTasks, this.constructor.name); } @@ -58,8 +55,8 @@ export class NodeCommandHandlers extends CommandHandler { private static readonly UPGRADE_CONTEXT_FILE = 'node-upgrade.json'; private init() { - this.consensusNodes = this.remoteConfigManager.getConsensusNodes(); - this.contexts = this.remoteConfigManager.getContexts(); + this.consensusNodes = this.remoteConfig.getConsensusNodes(); + this.contexts = this.remoteConfig.getContexts(); } /** ******** Task Lists **********/ @@ -67,7 +64,7 @@ export class NodeCommandHandlers extends CommandHandler { private deletePrepareTaskList(argv: ArgvStruct, lease: Lock): SoloListrTask[] { return [ this.tasks.initialize(argv, this.configs.deleteConfigBuilder.bind(this.configs), lease), - this.validateSingleNodeState({excludedStates: []}), + this.validateSingleNodeState({excludedPhases: []}), this.tasks.identifyExistingNodes(), this.tasks.loadAdminKey(), this.tasks.prepareUpgradeZip(), @@ -113,7 +110,7 @@ export class NodeCommandHandlers extends CommandHandler { this.tasks.initialize(argv, this.configs.addConfigBuilder.bind(this.configs), 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.validateSingleNodeState({excludedPhases: []}), this.tasks.checkPVCsEnabled(), this.tasks.identifyExistingNodes(), this.tasks.determineNewNodeAccountNumber(), @@ -166,7 +163,7 @@ export class NodeCommandHandlers extends CommandHandler { private updatePrepareTasks(argv: ArgvStruct, lease: Lock): SoloListrTask[] { return [ this.tasks.initialize(argv, this.configs.updateConfigBuilder.bind(this.configs), lease), - this.validateSingleNodeState({excludedStates: []}), + this.validateSingleNodeState({excludedPhases: []}), this.tasks.identifyExistingNodes(), this.tasks.loadAdminKey(), this.tasks.prepareUpgradeZip(), @@ -210,7 +207,7 @@ export class NodeCommandHandlers extends CommandHandler { private upgradePrepareTasks(argv: ArgvStruct, lease: Lock): SoloListrTask[] { return [ this.tasks.initialize(argv, this.configs.upgradeConfigBuilder.bind(this.configs), lease), - this.validateAllNodeStates({excludedStates: []}), + this.validateAllNodePhases({excludedPhases: []}), this.tasks.identifyExistingNodes(), this.tasks.loadAdminKey(), this.tasks.prepareUpgradeZip(), @@ -717,8 +714,8 @@ export class NodeCommandHandlers extends CommandHandler { argv, [ this.tasks.initialize(argv, this.configs.refreshConfigBuilder.bind(this.configs), lease), - this.validateAllNodeStates({ - acceptedStates: [ConsensusNodeStates.STARTED, ConsensusNodeStates.SETUP, ConsensusNodeStates.INITIALIZED], + this.validateAllNodePhases({ + acceptedPhases: [DeploymentPhase.STARTED, DeploymentPhase.CONFIGURED, DeploymentPhase.DEPLOYED], }), this.tasks.identifyNetworkPods(), this.tasks.dumpNetworkNodesSaveState(), @@ -771,12 +768,12 @@ export class NodeCommandHandlers extends CommandHandler { argv, [ this.tasks.initialize(argv, this.configs.stopConfigBuilder.bind(this.configs), lease), - this.validateAllNodeStates({ - acceptedStates: [ConsensusNodeStates.STARTED, ConsensusNodeStates.SETUP], + this.validateAllNodePhases({ + acceptedPhases: [DeploymentPhase.STARTED, DeploymentPhase.CONFIGURED], }), this.tasks.identifyNetworkPods(1), this.tasks.stopNodes('nodeAliases'), - this.changeAllNodeStates(ConsensusNodeStates.INITIALIZED), + this.changeAllNodePhases(DeploymentPhase.STARTED), ], { concurrent: false, @@ -798,14 +795,14 @@ export class NodeCommandHandlers extends CommandHandler { argv, [ this.tasks.initialize(argv, this.configs.startConfigBuilder.bind(this.configs), lease), - this.validateAllNodeStates({acceptedStates: [ConsensusNodeStates.SETUP]}), + this.validateAllNodePhases({acceptedPhases: [DeploymentPhase.CONFIGURED]}), this.tasks.identifyExistingNodes(), this.tasks.uploadStateFiles(context_ => context_.config.stateFile.length === 0), this.tasks.startNodes('nodeAliases'), this.tasks.enablePortForwarding(), this.tasks.checkAllNodesAreActive('nodeAliases'), this.tasks.checkNodeProxiesAreActive(), - this.changeAllNodeStates(ConsensusNodeStates.STARTED), + this.changeAllNodePhases(DeploymentPhase.STARTED), this.tasks.addNodeStakes(), ], { @@ -828,13 +825,13 @@ export class NodeCommandHandlers extends CommandHandler { argv, [ this.tasks.initialize(argv, this.configs.setupConfigBuilder.bind(this.configs), lease), - this.validateAllNodeStates({ - acceptedStates: [ConsensusNodeStates.INITIALIZED], + this.validateAllNodePhases({ + acceptedPhases: [DeploymentPhase.DEPLOYED], }), this.tasks.identifyNetworkPods(), this.tasks.fetchPlatformSoftware('nodeAliases'), this.tasks.setupNetworkNodes('nodeAliases', true), - this.changeAllNodeStates(ConsensusNodeStates.SETUP), + this.changeAllNodePhases(DeploymentPhase.CONFIGURED), ], { concurrent: false, @@ -859,7 +856,7 @@ export class NodeCommandHandlers extends CommandHandler { this.tasks.sendFreezeTransaction(), this.tasks.checkAllNodesAreFrozen('existingNodeAliases'), this.tasks.stopNodes('existingNodeAliases'), - this.changeAllNodeStates(ConsensusNodeStates.INITIALIZED), + this.changeAllNodePhases(DeploymentPhase.FROZEN), ], { concurrent: false, @@ -885,7 +882,7 @@ export class NodeCommandHandlers extends CommandHandler { this.tasks.enablePortForwarding(), this.tasks.checkAllNodesAreActive('existingNodeAliases'), this.tasks.checkNodeProxiesAreActive(), - this.changeAllNodeStates(ConsensusNodeStates.STARTED), + this.changeAllNodePhases(DeploymentPhase.STARTED), ], { concurrent: false, @@ -898,52 +895,24 @@ export class NodeCommandHandlers extends CommandHandler { return true; } - // TODO MOVE TO TASKS - - /** Removes the consensus node, envoy and haproxy components from remote config. */ - public removeNodeAndProxies(): SoloListrTask { - return { - skip: (): boolean => !this.remoteConfigManager.isLoaded(), - title: 'Remove node and proxies from remote config', - task: async (): Promise => { - await this.remoteConfigManager.modify(async remoteConfig => { - remoteConfig.components.remove('Consensus node name', ComponentTypes.ConsensusNode); - remoteConfig.components.remove('Envoy proxy name', ComponentTypes.EnvoyProxy); - remoteConfig.components.remove('HaProxy name', ComponentTypes.HaProxy); - }); - }, - }; - } - /** * Changes the state from all consensus nodes components in remote config. * - * @param state - to which to change the consensus node component + * @param phase - to which to change the consensus node component */ - public changeAllNodeStates(state: ConsensusNodeStates): SoloListrTask { + public changeAllNodePhases(phase: DeploymentPhase): SoloListrTask { interface Context { config: {namespace: NamespaceName; consensusNodes: ConsensusNode[]}; } return { - title: `Change node state to ${state} in remote config`, - skip: (): boolean => !this.remoteConfigManager.isLoaded(), + title: `Change node state to ${phase} in remote config`, + skip: (): boolean => !this.remoteConfig.isLoaded(), task: async (context_: Context): Promise => { - await this.remoteConfigManager.modify(async remoteConfig => { - const { - config: {namespace}, - } = context_; - + await this.remoteConfig.modify(async (_, components) => { for (const consensusNode of context_.config.consensusNodes) { - remoteConfig.components.edit( - new ConsensusNodeComponent( - consensusNode.name, - consensusNode.cluster, - namespace.name, - state, - consensusNode.nodeId, - ), - ); + const nodeId: NodeId = Templates.nodeIdFromNodeAlias(consensusNode.name); + components.changeNodePhase(nodeId, phase); } }); }, @@ -953,15 +922,15 @@ export class NodeCommandHandlers extends CommandHandler { /** * 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 + * @param acceptedPhases - the state at which the nodes can be, not matching any of the states throws an error + * @param excludedPhases - the state at which the nodes can't be, matching any of the states throws an error */ - public validateAllNodeStates({ - acceptedStates, - excludedStates, + public validateAllNodePhases({ + acceptedPhases, + excludedPhases, }: { - acceptedStates?: ConsensusNodeStates[]; - excludedStates?: ConsensusNodeStates[]; + acceptedPhases?: DeploymentPhase[]; + excludedPhases?: DeploymentPhase[]; }): SoloListrTask { interface Context { config: {namespace: string; nodeAliases: NodeAliases}; @@ -969,16 +938,16 @@ export class NodeCommandHandlers extends CommandHandler { return { title: 'Validate nodes states', - skip: (): boolean => !this.remoteConfigManager.isLoaded(), + skip: (): boolean => !this.remoteConfig.isLoaded(), task: (context_: Context, task): Listr => { const nodeAliases = context_.config.nodeAliases; - const components = this.remoteConfigManager.components; + const components = this.remoteConfig.components; const subTasks: SoloListrTask[] = nodeAliases.map(nodeAlias => ({ title: `Validating state for node ${nodeAlias}`, task: (_, task): void => { - const state = this.validateNodeState(nodeAlias, components, acceptedStates, excludedStates); + const state = this.validateNodeState(nodeAlias, components, acceptedPhases, excludedPhases); task.title += ` - ${chalk.green('valid state')}: ${chalk.cyan(state)}`; }, @@ -995,15 +964,15 @@ export class NodeCommandHandlers extends CommandHandler { /** * 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 + * @param acceptedPhases - the state at which the node can be, not matching any of the states throws an error + * @param excludedPhases - the state at which the node can't be, matching any of the states throws an error */ public validateSingleNodeState({ - acceptedStates, - excludedStates, + acceptedPhases, + excludedPhases, }: { - acceptedStates?: ConsensusNodeStates[]; - excludedStates?: ConsensusNodeStates[]; + acceptedPhases?: DeploymentPhase[]; + excludedPhases?: DeploymentPhase[]; }): SoloListrTask { interface Context { config: {namespace: string; nodeAlias: NodeAlias}; @@ -1011,15 +980,15 @@ export class NodeCommandHandlers extends CommandHandler { return { title: 'Validate nodes state', - skip: (): boolean => !this.remoteConfigManager.isLoaded(), + skip: (): boolean => !this.remoteConfig.isLoaded(), task: (context_: Context, task): void => { const nodeAlias = context_.config.nodeAlias; task.title += ` ${nodeAlias}`; // TODO: Disabled for now until the node's state mapping is completed - // const components = this.remoteConfigManager.components; - // const state = this.validateNodeState(nodeAlias, components, acceptedStates, excludedStates); + // const components = this.remoteConfig.components; + // const state = this.validateNodeState(nodeAlias, components, acceptedPhases, excludedPhases); // task.title += ` - ${chalk.green('valid state')}: ${chalk.cyan(state)}`; }, }; @@ -1028,37 +997,40 @@ export class NodeCommandHandlers extends CommandHandler { /** * @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 + * @param acceptedPhases - the state at which the node can be, not matching any of the states throws an error + * @param excludedPhases - 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, - excludedStates: Optional, - ): ConsensusNodeStates { - let nodeComponent: ConsensusNodeComponent; + components: ComponentsDataWrapperApi, + acceptedPhases: Optional, + excludedPhases: Optional, + ): DeploymentPhase { + let nodeComponent: ConsensusNodeStateSchema; try { - nodeComponent = components.getComponent(ComponentTypes.ConsensusNode, nodeAlias); + nodeComponent = components.getComponent( + ComponentTypes.ConsensusNode, + Templates.nodeIdFromNodeAlias(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)) { + // if (acceptedPhases && !acceptedPhases.includes(nodeComponent.state)) { // const errorMessageData = - // `accepted states: ${acceptedStates.join(', ')}, ` + `current state: ${nodeComponent.state}`; + // `accepted states: ${acceptedPhases.join(', ')}, ` + `current state: ${nodeComponent.state}`; // // throw new SoloError(`${nodeAlias} has invalid state - ` + errorMessageData); // } // - // if (excludedStates && excludedStates.includes(nodeComponent.state)) { + // if (excludedPhases && excludedPhases.includes(nodeComponent.state)) { // const errorMessageData = - // `excluded states: ${excludedStates.join(', ')}, ` + `current state: ${nodeComponent.state}`; + // `excluded states: ${excludedPhases.join(', ')}, ` + `current state: ${nodeComponent.state}`; // // throw new SoloError(`${nodeAlias} has invalid state - ` + errorMessageData); // } - return nodeComponent.state; + return nodeComponent.metadata.phase; } } diff --git a/src/commands/node/tasks.ts b/src/commands/node/tasks.ts index 233b457d5..7b2ea15cc 100644 --- a/src/commands/node/tasks.ts +++ b/src/commands/node/tasks.ts @@ -79,8 +79,8 @@ import {NetworkNodes} from '../../core/network-nodes.js'; import {container, inject, injectable} from 'tsyringe-neo'; import { type ClusterReference, + type Context, type DeploymentName, - type NamespaceNameAsString, type Optional, type SoloListr, type SoloListrTask, @@ -91,11 +91,7 @@ import {ConsensusNode} from '../../core/model/consensus-node.js'; import {type K8} from '../../integration/kube/k8.js'; import {Base64} from 'js-base64'; import {InjectTokens} from '../../core/dependency-injection/inject-tokens.js'; -import {type RemoteConfigManager} from '../../core/config/remote/remote-config-manager.js'; import {BaseCommand} from '../base.js'; -import {ConsensusNodeComponent} from '../../core/config/remote/components/consensus-node-component.js'; -import {EnvoyProxyComponent} from '../../core/config/remote/components/envoy-proxy-component.js'; -import {HaProxyComponent} from '../../core/config/remote/components/ha-proxy-component.js'; import {HEDERA_PLATFORM_VERSION} from '../../../version.js'; import {ShellRunner} from '../../core/shell-runner.js'; import {PathEx} from '../../business/utils/path-ex.js'; @@ -119,8 +115,12 @@ import {type NodeKeysConfigClass} from './config-interfaces/node-keys-config-cla import {type NodeStartConfigClass} from './config-interfaces/node-start-config-class.js'; import {type CheckedNodesConfigClass, type CheckedNodesContext} from './config-interfaces/node-common-config-class.js'; import {type NetworkNodeServices} from '../../core/network-node-services.js'; -import {LocalConfigRuntimeState} from '../../business/runtime-state/config/local/local-config-runtime-state.js'; -import {ConsensusNodeStates} from '../../core/config/remote/enumerations/consensus-node-states.js'; +import {ComponentTypes} from '../../core/config/remote/enumerations/component-types.js'; +import {DeploymentPhase} from '../../data/schema/model/remote/deployment-phase.js'; +import {type RemoteConfigRuntimeStateApi} from '../../business/runtime-state/api/remote-config-runtime-state-api.js'; +import {type ComponentFactoryApi} from '../../core/config/remote/api/component-factory-api.js'; +import {type LocalConfigRuntimeState} from '../../business/runtime-state/config/local/local-config-runtime-state.js'; +import {ClusterSchema} from '../../data/schema/model/common/cluster-schema.js'; @injectable() export class NodeCommandTasks { @@ -134,8 +134,9 @@ export class NodeCommandTasks { @inject(InjectTokens.ProfileManager) private readonly profileManager: ProfileManager, @inject(InjectTokens.ChartManager) private readonly chartManager: ChartManager, @inject(InjectTokens.CertificateManager) private readonly certificateManager: CertificateManager, - @inject(InjectTokens.RemoteConfigManager) private readonly remoteConfigManager: RemoteConfigManager, + @inject(InjectTokens.RemoteConfigRuntimeState) private readonly remoteConfig: RemoteConfigRuntimeStateApi, @inject(InjectTokens.LocalConfigRuntimeState) private readonly localConfig: LocalConfigRuntimeState, + @inject(InjectTokens.ComponentFactory) private readonly componentFactory: ComponentFactoryApi, ) { this.logger = patchInject(logger, InjectTokens.SoloLogger, this.constructor.name); this.accountManager = patchInject(accountManager, InjectTokens.AccountManager, this.constructor.name); @@ -147,11 +148,7 @@ export class NodeCommandTasks { this.chartManager = patchInject(chartManager, InjectTokens.ChartManager, this.constructor.name); this.certificateManager = patchInject(certificateManager, InjectTokens.CertificateManager, this.constructor.name); this.localConfig = patchInject(localConfig, InjectTokens.LocalConfigRuntimeState, this.constructor.name); - this.remoteConfigManager = patchInject( - remoteConfigManager, - InjectTokens.RemoteConfigManager, - this.constructor.name, - ); + this.remoteConfig = patchInject(remoteConfig, InjectTokens.RemoteConfigRuntimeState, this.constructor.name); } private getFileUpgradeId(deploymentName: DeploymentName): FileId { @@ -446,7 +443,7 @@ export class NodeCommandTasks { const podReference = PodReference.of(namespace, podName); task.title = `${title} - status ${chalk.yellow('STARTING')}, attempt ${chalk.blueBright(`0/${maxAttempts}`)}`; - const consensusNodes = this.remoteConfigManager.getConsensusNodes(); + const consensusNodes = this.remoteConfig.getConsensusNodes(); if (!context) { context = helpers.extractContextFromConsensusNodes(nodeAlias, consensusNodes); } @@ -638,7 +635,7 @@ export class NodeCommandTasks { const deploymentName = this.configManager.getFlag(flags.deployment); await this.accountManager.loadNodeClient( namespace, - this.remoteConfigManager.getClusterRefs(), + this.remoteConfig.getClusterRefs(), deploymentName, this.configManager.getFlag(flags.forcePortForward), ); @@ -849,7 +846,7 @@ export class NodeCommandTasks { try { const nodeClient = await this.accountManager.loadNodeClient( namespace, - this.remoteConfigManager.getClusterRefs(), + this.remoteConfig.getClusterRefs(), deployment, ); const futureDate = new Date(); @@ -1078,7 +1075,7 @@ export class NodeCommandTasks { task: async (context_, task) => { const config = context_.config; config.existingNodeAliases = []; - const clusterReferences = this.remoteConfigManager.getClusterRefs(); + const clusterReferences = this.remoteConfig.getClusterRefs(); config.serviceMap = await self.accountManager.getNodeServiceMap( config.namespace, clusterReferences, @@ -1192,7 +1189,7 @@ export class NodeCommandTasks { task: async context_ => { context_.config.serviceMap = await this.accountManager.getNodeServiceMap( context_.config.namespace, - this.remoteConfigManager.getClusterRefs(), + this.remoteConfig.getClusterRefs(), context_.config.deployment, ); if (!context_.config.serviceMap.has(context_.config.nodeAlias)) { @@ -1220,7 +1217,7 @@ export class NodeCommandTasks { context_.config.nodeAliases = helpers.parseNodeAliases( // @ts-ignore context_.config.nodeAliasesUnparsed, - this.remoteConfigManager.getConsensusNodes(), + this.remoteConfig.getConsensusNodes(), this.configManager, ); } @@ -1277,7 +1274,7 @@ export class NodeCommandTasks { const deploymentName = this.configManager.getFlag(flags.deployment); const networkNodeServiceMap = await this.accountManager.getNodeServiceMap( namespace, - this.remoteConfigManager.getClusterRefs(), + this.remoteConfig.getClusterRefs(), deploymentName, ); @@ -1305,7 +1302,7 @@ export class NodeCommandTasks { const deploymentName = this.configManager.getFlag(flags.deployment); const networkNodeServiceMap = await this.accountManager.getNodeServiceMap( namespace, - this.remoteConfigManager.getClusterRefs(), + this.remoteConfig.getClusterRefs(), deploymentName, ); @@ -1509,7 +1506,7 @@ export class NodeCommandTasks { config.nodeClient = await self.accountManager.refreshNodeClient( config.namespace, - this.remoteConfigManager.getClusterRefs(), + this.remoteConfig.getClusterRefs(), skipNodeAlias, this.configManager.getFlag(flags.deployment), ); @@ -1569,7 +1566,7 @@ export class NodeCommandTasks { task: async context_ => { await self.accountManager.refreshNodeClient( context_.config.namespace, - this.remoteConfigManager.getClusterRefs(), + this.remoteConfig.getClusterRefs(), context_.config.nodeAlias, this.configManager.getFlag(flags.deployment), this.configManager.getFlag(flags.forcePortForward), @@ -1866,7 +1863,7 @@ export class NodeCommandTasks { if (config.existingNodeAliases.length > 1) { config.nodeClient = await self.accountManager.refreshNodeClient( config.namespace, - this.remoteConfigManager.getClusterRefs(), + this.remoteConfig.getClusterRefs(), config.nodeAlias, this.configManager.getFlag(flags.deployment), ); @@ -1957,7 +1954,7 @@ export class NodeCommandTasks { // Prepare parameter and update the network node chart const config = context_.config; const consensusNodes = context_.config.consensusNodes as ConsensusNode[]; - const clusterReferences = this.remoteConfigManager.getClusterRefs(); + const clusterReferences = this.remoteConfig.getClusterRefs(); // Make sure valuesArgMap is initialized with empty strings const valuesArgumentMap: Record = {}; @@ -2291,7 +2288,7 @@ export class NodeCommandTasks { title: 'Kill nodes to pick up updated configMaps', task: async context_ => { const config = context_.config; - const clusterReferences = this.remoteConfigManager.getClusterRefs(); + const clusterReferences = this.remoteConfig.getClusterRefs(); // the updated node will have a new pod ID if its account ID changed which is a label config.serviceMap = await this.accountManager.getNodeServiceMap( config.namespace, @@ -2531,8 +2528,8 @@ export class NodeCommandTasks { const config = await configInit(argv, context_, task, shouldLoadNodeClient); context_.config = config; - config.consensusNodes = this.remoteConfigManager.getConsensusNodes(); - config.contexts = this.remoteConfigManager.getContexts(); + config.consensusNodes = this.remoteConfig.getConsensusNodes(); + config.contexts = this.remoteConfig.getContexts(); for (const flag of required) { if (config[flag.constName] === undefined) { @@ -2553,48 +2550,53 @@ export class NodeCommandTasks { return { title: 'Add new node to remote config', task: async (context_, task) => { - const nodeAlias = context_.config.nodeAlias; - const namespace: NamespaceNameAsString = context_.config.namespace.name; - const clusterReference = context_.config.clusterRef; - const context = this.localConfig.configuration.clusterRefs.get(clusterReference); + const nodeAlias: NodeAlias = context_.config.nodeAlias; + const nodeId: NodeId = Templates.nodeIdFromNodeAlias(nodeAlias); + const namespace: NamespaceName = context_.config.namespace; + const clusterReference: ClusterReference = context_.config.clusterRef; + const context: Context = this.localConfig.configuration.clusterRefs.get(clusterReference)?.toString(); task.title += `: ${nodeAlias}`; - await this.remoteConfigManager.modify(async remoteConfig => { - remoteConfig.components.add( - new ConsensusNodeComponent( - nodeAlias, + await this.remoteConfig.modify(async (_, components) => { + components.addNewComponent( + this.componentFactory.createNewConsensusNodeComponent( + nodeId, clusterReference, namespace, - ConsensusNodeStates.STARTED, - Templates.nodeIdFromNodeAlias(nodeAlias), + DeploymentPhase.STARTED, ), + ComponentTypes.ConsensusNode, + ); + components.addNewComponent( + this.componentFactory.createNewEnvoyProxyComponent(clusterReference, namespace), + ComponentTypes.EnvoyProxy, + ); + components.addNewComponent( + this.componentFactory.createNewHaProxyComponent(clusterReference, namespace), + ComponentTypes.HaProxy, ); - - remoteConfig.components.add(new EnvoyProxyComponent(`envoy-proxy-${nodeAlias}`, clusterReference, namespace)); - - remoteConfig.components.add(new HaProxyComponent(`haproxy-${nodeAlias}`, clusterReference, namespace)); }); - context_.config.consensusNodes = this.remoteConfigManager.getConsensusNodes(); + context_.config.consensusNodes = this.remoteConfig.getConsensusNodes(); // if the consensusNodes does not contain the nodeAlias then add it if (!context_.config.consensusNodes.some((node: ConsensusNode) => node.name === nodeAlias)) { - const cluster = this.remoteConfigManager.clusters[clusterReference]; + const cluster: ClusterSchema = this.remoteConfig.clusters.find(cluster => cluster.name === clusterReference); context_.config.consensusNodes.push( new ConsensusNode( nodeAlias, - Templates.nodeIdFromNodeAlias(nodeAlias), - namespace, + nodeId, + namespace.name, clusterReference, context.toString(), cluster.dnsBaseDomain, cluster.dnsConsensusNodePattern, Templates.renderConsensusNodeFullyQualifiedDomainName( nodeAlias, - Templates.nodeIdFromNodeAlias(nodeAlias), - namespace, + nodeId, + namespace.name, clusterReference, cluster.dnsBaseDomain, cluster.dnsConsensusNodePattern, diff --git a/src/commands/relay.ts b/src/commands/relay.ts index 762873370..5492e5f72 100644 --- a/src/commands/relay.ts +++ b/src/commands/relay.ts @@ -11,11 +11,9 @@ import {type AccountManager} from '../core/account-manager.js'; import {BaseCommand} from './base.js'; import {Flags as flags} from './flags.js'; import {resolveNamespaceFromDeployment} from '../core/resolvers.js'; -import {type AnyYargs, type ArgvStruct, type NodeAliases} from '../types/aliases.js'; +import {type AnyYargs, type ArgvStruct, type NodeAlias, type NodeAliases, type NodeId} from '../types/aliases.js'; import {ListrLock} from '../core/lock/listr-lock.js'; -import {RelayComponent} from '../core/config/remote/components/relay-component.js'; import * as Base64 from 'js-base64'; -import {NamespaceName} from '../types/namespace/namespace-name.js'; import { type ClusterReference, type CommandDefinition, @@ -28,6 +26,10 @@ import {inject, injectable} from 'tsyringe-neo'; import {InjectTokens} from '../core/dependency-injection/inject-tokens.js'; import {patchInject} from '../core/dependency-injection/container-helper.js'; import {ComponentTypes} from '../core/config/remote/enumerations/component-types.js'; +import {Templates} from '../core/templates.js'; +import {NamespaceName} from '../types/namespace/namespace-name.js'; +import {type RelayNodeStateSchema} from '../data/schema/model/remote/state/relay-node-state-schema.js'; +import {type ComponentFactoryApi} from '../core/config/remote/api/component-factory-api.js'; interface RelayDestroyConfigClass { chartDirectory: string; @@ -75,6 +77,7 @@ export class RelayCommand extends BaseCommand { public constructor( @inject(InjectTokens.ProfileManager) private readonly profileManager: ProfileManager, @inject(InjectTokens.AccountManager) private readonly accountManager: AccountManager, + @inject(InjectTokens.ComponentFactory) private readonly componentFactory: ComponentFactoryApi, ) { super(); @@ -213,7 +216,7 @@ export class RelayCommand extends BaseCommand { const accountMap = this.accountManager.getNodeAccountMap(nodeAliases, deploymentName); const networkNodeServicesMap = await this.accountManager.getNodeServiceMap( namespace, - this.remoteConfigManager.getClusterRefs(), + this.remoteConfig.getClusterRefs(), deploymentName, ); for (const nodeAlias of nodeAliases) { @@ -276,13 +279,13 @@ export class RelayCommand extends BaseCommand { ); context_.config.nodeAliases = helpers.parseNodeAliases( context_.config.nodeAliasesUnparsed, - this.remoteConfigManager.getConsensusNodes(), + this.remoteConfig.getConsensusNodes(), this.configManager, ); context_.config.releaseName = self.prepareReleaseName(context_.config.nodeAliases); if (context_.config.clusterRef) { - const context = self.remoteConfigManager.getClusterRefs()[context_.config.clusterRef]; + const context = self.remoteConfig.getClusterRefs()[context_.config.clusterRef]; if (context) { context_.config.context = context; } @@ -311,7 +314,7 @@ export class RelayCommand extends BaseCommand { const config = context_.config; await self.accountManager.loadNodeClient( context_.config.namespace, - self.remoteConfigManager.getClusterRefs(), + self.remoteConfig.getClusterRefs(), self.configManager.getFlag(flags.deployment), self.configManager.getFlag(flags.forcePortForward), ); @@ -430,14 +433,14 @@ export class RelayCommand extends BaseCommand { namespace: await resolveNamespaceFromDeployment(this.localConfig, this.configManager, task), nodeAliases: helpers.parseNodeAliases( self.configManager.getFlag(flags.nodeAliasesUnparsed) as string, - this.remoteConfigManager.getConsensusNodes(), + this.remoteConfig.getConsensusNodes(), this.configManager, ), clusterRef: self.configManager.getFlag(flags.clusterRef) as string, } as RelayDestroyConfigClass; if (context_.config.clusterRef) { - const context = self.remoteConfigManager.getClusterRefs()[context_.config.clusterRef]; + const context = self.remoteConfig.getClusterRefs()[context_.config.clusterRef]; if (context) { context_.config.context = context; } @@ -460,7 +463,7 @@ export class RelayCommand extends BaseCommand { task: async context_ => { const config = context_.config; - await this.chartManager.uninstall(config.namespace, config.releaseName, context_.config.context); + await this.chartManager.uninstall(config.namespace, config.releaseName, config.context); this.logger.showList( 'Destroyed Relays', @@ -472,7 +475,7 @@ export class RelayCommand extends BaseCommand { }, skip: context_ => !context_.config.isChartInstalled, }, - this.removeRelayComponent(), + this.disableRelayComponent(), ], { concurrent: false, @@ -546,29 +549,44 @@ export class RelayCommand extends BaseCommand { public addRelayComponent(): SoloListrTask { return { title: 'Add relay component in remote config', - skip: (): boolean => !this.remoteConfigManager.isLoaded(), + skip: (): boolean => !this.remoteConfig.isLoaded(), task: async (context_): Promise => { - await this.remoteConfigManager.modify(async remoteConfig => { - const { - config: {namespace, nodeAliases}, - } = context_; - const cluster = this.remoteConfigManager.currentCluster; - - remoteConfig.components.add(new RelayComponent('relay', cluster, namespace.name, nodeAliases)); - }); + const {namespace, nodeAliases, clusterRef} = context_.config; + + const nodeIds: NodeId[] = nodeAliases.map((nodeAlias: NodeAlias) => Templates.nodeIdFromNodeAlias(nodeAlias)); + + this.remoteConfig.configuration.components.addNewComponent( + this.componentFactory.createNewRelayComponent(clusterRef, namespace, nodeIds), + ComponentTypes.RelayNodes, + ); + + await this.remoteConfig.persist(); }, }; } /** Remove the relay component from remote config. */ - public removeRelayComponent(): SoloListrTask { + public disableRelayComponent(): SoloListrTask { return { title: 'Remove relay component from remote config', - skip: (): boolean => !this.remoteConfigManager.isLoaded(), - task: async (): Promise => { - await this.remoteConfigManager.modify(async remoteConfig => { - remoteConfig.components.remove('relay', ComponentTypes.Relay); - }); + skip: (): boolean => !this.remoteConfig.isLoaded(), + task: async (context_): Promise => { + const clusterReference: ClusterReference = context_.config.clusterRef; + + const relayComponents: RelayNodeStateSchema[] = + this.remoteConfig.configuration.components.getComponentsByClusterReference( + ComponentTypes.RelayNodes, + clusterReference, + ); + + for (const relayComponent of relayComponents) { + this.remoteConfig.configuration.components.removeComponent( + relayComponent.metadata.id, + ComponentTypes.RelayNodes, + ); + } + + await this.remoteConfig.persist(); }, }; } diff --git a/src/core/account-manager.ts b/src/core/account-manager.ts index f06c06bad..ad9b85803 100644 --- a/src/core/account-manager.ts +++ b/src/core/account-manager.ts @@ -46,12 +46,12 @@ import {InjectTokens} from './dependency-injection/inject-tokens.js'; import {type ClusterReferences, type DeploymentName, Realm, Shard} from './../types/index.js'; import {type Service} from '../integration/kube/resources/service/service.js'; import {SoloService} from './model/solo-service.js'; -import {type RemoteConfigManager} from './config/remote/remote-config-manager.js'; import {PathEx} from '../business/utils/path-ex.js'; import {type NodeServiceMapping} from '../types/mappings/node-service-mapping.js'; import {type ConsensusNode} from './model/consensus-node.js'; import {NetworkNodeServicesBuilder} from './network-node-services-builder.js'; import {LocalConfigRuntimeState} from '../business/runtime-state/config/local/local-config-runtime-state.js'; +import {type RemoteConfigRuntimeStateApi} from '../business/runtime-state/api/remote-config-runtime-state-api.js'; const REASON_FAILED_TO_GET_KEYS = 'failed to get keys for accountId'; const REASON_SKIPPED = 'skipped since it does not have a genesis key'; @@ -69,16 +69,12 @@ export class AccountManager { constructor( @inject(InjectTokens.SoloLogger) private readonly logger?: SoloLogger, @inject(InjectTokens.K8Factory) private readonly k8Factory?: K8Factory, - @inject(InjectTokens.RemoteConfigManager) private readonly remoteConfigManager?: RemoteConfigManager, + @inject(InjectTokens.RemoteConfigRuntimeState) private readonly remoteConfig?: RemoteConfigRuntimeStateApi, @inject(InjectTokens.LocalConfigRuntimeState) private readonly localConfig?: LocalConfigRuntimeState, ) { this.logger = patchInject(logger, InjectTokens.SoloLogger, this.constructor.name); this.k8Factory = patchInject(k8Factory, InjectTokens.K8Factory, this.constructor.name); - this.remoteConfigManager = patchInject( - remoteConfigManager, - InjectTokens.RemoteConfigManager, - this.constructor.name, - ); + this.remoteConfig = patchInject(remoteConfig, InjectTokens.RemoteConfigRuntimeState, this.constructor.name); this.localConfig = patchInject(localConfig, InjectTokens.LocalConfigRuntimeState, this.constructor.name); this._portForwards = []; @@ -94,7 +90,7 @@ export class AccountManager { accountId: string, namespace: NamespaceName, ): Promise { - const contexts = this.remoteConfigManager.getContexts(); + const contexts = this.remoteConfig.getContexts(); for (const context of contexts) { try { @@ -585,7 +581,7 @@ export class AccountManager { break; } } - const consensusNode: ConsensusNode = this.remoteConfigManager + const consensusNode: ConsensusNode = this.remoteConfig .getConsensusNodes() .find(node => node.name === serviceBuilder.nodeAlias); serviceBuilder.withExternalAddress( @@ -754,7 +750,7 @@ export class AccountManager { }; try { - const contexts = this.remoteConfigManager.getContexts(); + const contexts = this.remoteConfig.getContexts(); for (const context of contexts) { const secretName = Templates.renderAccountKeySecretName(accountId); const secretLabels = Templates.renderAccountKeySecretLabelObject(accountId); diff --git a/src/core/chart-manager.ts b/src/core/chart-manager.ts index 15b70fa59..7344b7304 100644 --- a/src/core/chart-manager.ts +++ b/src/core/chart-manager.ts @@ -46,7 +46,7 @@ export class ChartManager { } return await Promise.all(promises); // urls - } catch (error: Error | any) { + } catch (error) { throw new SoloError(`failed to setup chart repositories: ${error.message}`, error); } } diff --git a/src/core/config/remote/api/component-factory-api.ts b/src/core/config/remote/api/component-factory-api.ts new file mode 100644 index 000000000..91e3f60f7 --- /dev/null +++ b/src/core/config/remote/api/component-factory-api.ts @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 + +import {type ClusterReference} from '../../../../types/index.js'; +import {type NodeId} from '../../../../types/aliases.js'; +import {type NamespaceName} from '../../../../types/namespace/namespace-name.js'; +import {type DeploymentPhase} from '../../../../data/schema/model/remote/deployment-phase.js'; +import {type ExplorerStateSchema} from '../../../../data/schema/model/remote/state/explorer-state-schema.js'; +import {type MirrorNodeStateSchema} from '../../../../data/schema/model/remote/state/mirror-node-state-schema.js'; +import {type HAProxyStateSchema} from '../../../../data/schema/model/remote/state/haproxy-state-schema.js'; +import {type EnvoyProxyStateSchema} from '../../../../data/schema/model/remote/state/envoy-proxy-state-schema.js'; +import {type ConsensusNodeStateSchema} from '../../../../data/schema/model/remote/state/consensus-node-state-schema.js'; +import {type RelayNodeStateSchema} from '../../../../data/schema/model/remote/state/relay-node-state-schema.js'; + +export interface ComponentFactoryApi { + createNewRelayComponent( + clusterReference: ClusterReference, + namespace: NamespaceName, + nodeIds: NodeId[], + ): RelayNodeStateSchema; + + createNewExplorerComponent(clusterReference: ClusterReference, namespace: NamespaceName): ExplorerStateSchema; + + createNewMirrorNodeComponent(clusterReference: ClusterReference, namespace: NamespaceName): MirrorNodeStateSchema; + + createNewHaProxyComponent(clusterReference: ClusterReference, namespace: NamespaceName): HAProxyStateSchema; + + createNewEnvoyProxyComponent(clusterReference: ClusterReference, namespace: NamespaceName): EnvoyProxyStateSchema; + + createNewConsensusNodeComponent( + nodeId: NodeId, + clusterReference: ClusterReference, + namespace: NamespaceName, + phase: DeploymentPhase.REQUESTED | DeploymentPhase.STARTED, + ): ConsensusNodeStateSchema; + + createConsensusNodeComponentsFromNodeIds( + nodeIds: NodeId[], + clusterReference: ClusterReference, + namespace: NamespaceName, + ): ConsensusNodeStateSchema[]; +} diff --git a/src/core/config/remote/api/components-data-wrapper-api.ts b/src/core/config/remote/api/components-data-wrapper-api.ts new file mode 100644 index 000000000..b3e69b2e6 --- /dev/null +++ b/src/core/config/remote/api/components-data-wrapper-api.ts @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 + +import {type BaseStateSchema} from '../../../../data/schema/model/remote/state/base-state-schema.js'; +import {type ComponentTypes} from '../enumerations/component-types.js'; +import {type DeploymentPhase} from '../../../../data/schema/model/remote/deployment-phase.js'; +import {type ClusterReference, type ComponentId} from '../../../../types/index.js'; +import {type DeploymentStateSchema} from '../../../../data/schema/model/remote/deployment-state-schema.js'; + +export interface ComponentsDataWrapperApi { + state: DeploymentStateSchema; + + addNewComponent(component: BaseStateSchema, type: ComponentTypes): void; + + changeNodePhase(componentId: ComponentId, phase: DeploymentPhase): void; + + removeComponent(componentId: ComponentId, type: ComponentTypes): void; + + getComponent(type: ComponentTypes, componentId: ComponentId): T; + + getComponentsByClusterReference( + type: ComponentTypes, + clusterReference: ClusterReference, + ): T[]; + + getComponentById(type: ComponentTypes, id: number): T; + + getNewComponentId(componentType: ComponentTypes): number; +} diff --git a/src/core/config/remote/api/remote-config-validator-api.ts b/src/core/config/remote/api/remote-config-validator-api.ts new file mode 100644 index 000000000..fde95fb61 --- /dev/null +++ b/src/core/config/remote/api/remote-config-validator-api.ts @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: Apache-2.0 + +import {type NamespaceName} from '../../../../types/namespace/namespace-name.js'; +import {type DeploymentStateSchema} from '../../../../data/schema/model/remote/deployment-state-schema.js'; + +export interface RemoteConfigValidatorApi { + validateComponents( + namespace: NamespaceName, + skipConsensusNodes: boolean, + state: Readonly, + ): Promise; +} diff --git a/src/core/config/remote/cluster.ts b/src/core/config/remote/cluster.ts deleted file mode 100644 index 7f1d83669..000000000 --- a/src/core/config/remote/cluster.ts +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {type ToObject} from '../../../types/index.js'; -import {type ClusterReference, type DeploymentName, type NamespaceNameAsString} from '../../../types/index.js'; -import {SoloError} from '../../errors/solo-error.js'; -import {type ClusterStruct} from './interfaces/cluster-struct.js'; - -export class Cluster implements ClusterStruct, ToObject { - public constructor( - public readonly name: string, - public readonly namespace: NamespaceNameAsString, - public readonly deployment: DeploymentName, - public readonly dnsBaseDomain: string = 'cluster.local', // example: 'us-west-2.gcp.charlie.sphere'` - public readonly dnsConsensusNodePattern: string = 'network-{nodeAlias}-svc.{namespace}.svc', // example: '{nodeId}.consensus.prod'` - ) { - if (!name) { - throw new SoloError('name is required'); - } - if (typeof name !== 'string') { - throw new SoloError(`Invalid type for name: ${typeof name}`); - } - - if (!namespace) { - throw new SoloError('namespace is required'); - } - if (typeof namespace !== 'string') { - throw new SoloError(`Invalid type for namespace: ${typeof namespace}`); - } - - if (!deployment) { - throw new SoloError('deployment is required'); - } - if (typeof deployment !== 'string') { - throw new SoloError(`Invalid type for deployment: ${typeof deployment}`); - } - } - - public toObject(): ClusterStruct { - return { - name: this.name, - namespace: this.namespace, - deployment: this.deployment, - dnsBaseDomain: this.dnsBaseDomain, - dnsConsensusNodePattern: this.dnsConsensusNodePattern, - }; - } - - public static fromObject(cluster: ClusterStruct) { - return new Cluster( - cluster.name, - cluster.namespace, - cluster.deployment, - cluster.dnsBaseDomain, - cluster.dnsConsensusNodePattern, - ); - } - - public static toClustersMapObject(clustersMap: Record) { - const entries = Object.entries(clustersMap).map(([reference, cluster]) => [reference, cluster.toObject()]); - return Object.fromEntries(entries); - } - - public static fromClustersMapObject(object: any): Record { - const clusters: Record = {}; - - for (const [reference, cluster] of Object.entries(object)) { - clusters[reference] = Cluster.fromObject(cluster as ClusterStruct); - } - - return clusters; - } -} diff --git a/src/core/config/remote/common-flags-data-wrapper.ts b/src/core/config/remote/common-flags-data-wrapper.ts deleted file mode 100644 index a7a5e81b6..000000000 --- a/src/core/config/remote/common-flags-data-wrapper.ts +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {Flags as flags} from '../../../commands/flags.js'; -import {type ToObject} from '../../../types/index.js'; -import {type ConfigManager} from '../../config-manager.js'; -import {type CommandFlag} from '../../../types/flag-types.js'; -import {type AnyObject} from '../../../types/aliases.js'; -import {select as selectPrompt} from '@inquirer/prompts'; -import {type RemoteConfigCommonFlagsStruct} from './interfaces/remote-config-common-flags-struct.js'; - -export class CommonFlagsDataWrapper implements ToObject { - private static readonly COMMON_FLAGS: CommandFlag[] = [ - flags.releaseTag, - flags.chartDirectory, - flags.relayReleaseTag, - flags.soloChartVersion, - flags.mirrorNodeVersion, - flags.nodeAliasesUnparsed, - flags.explorerVersion, - ]; - - private constructor( - private readonly configManager: ConfigManager, - private readonly flags: RemoteConfigCommonFlagsStruct, - ) {} - - /** - * Updates the flags or populates them inside the remote config - */ - public async handleFlags(argv: AnyObject): Promise { - for (const flag of CommonFlagsDataWrapper.COMMON_FLAGS) { - await this.handleFlag(flag, argv); - } - } - - private async handleFlag(flag: CommandFlag, argv: AnyObject): Promise { - const detectFlagMismatch = async () => { - const oldValue = this.flags[flag.constName] as string; - const newValue = this.configManager.getFlag(flag); - - // if the old value is not present, override it with the new one - if (!oldValue && newValue) { - this.flags[flag.constName] = newValue; - return; - } - - // if its present but there is a mismatch warn user - else if (oldValue && oldValue !== newValue) { - const isQuiet = this.configManager.getFlag(flags.quiet); - const isForced = this.configManager.getFlag(flags.force); - - // if the quiet or forced flag is passed don't prompt the user - if (isQuiet === true || isForced === true) { - return; - } - - const answer = await selectPrompt({ - message: 'Value in remote config differs with the one you are passing, choose which you want to use', - choices: [ - { - name: `[old value] ${oldValue}`, - value: oldValue, - }, - { - name: `[new value] ${newValue}`, - value: newValue, - }, - ], - }); - - // Override if user chooses new the new value, else override and keep the old one - if (answer === newValue) { - this.flags[flag.constName] = newValue; - } else { - this.configManager.setFlag(flag, oldValue); - argv[flag.constName] = oldValue; - } - } - }; - - // if the flag is set, inspect the value - if (this.configManager.hasFlag(flag)) { - await detectFlagMismatch(); - } - - // use remote config value if no user supplied value - else if (this.flags[flag.constName]) { - argv[flag.constName] = this.flags[flag.constName]; - this.configManager.setFlag(flag, this.flags[flag.constName]); - } - } - - public static async initialize(configManager: ConfigManager, argv: AnyObject): Promise { - const commonFlagsDataWrapper = new CommonFlagsDataWrapper(configManager, {}); - await commonFlagsDataWrapper.handleFlags(argv); - return commonFlagsDataWrapper; - } - - public static fromObject(configManager: ConfigManager, data: RemoteConfigCommonFlagsStruct): CommonFlagsDataWrapper { - return new CommonFlagsDataWrapper(configManager, data); - } - - public toObject(): RemoteConfigCommonFlagsStruct { - return { - nodeAliasesUnparsed: this.flags.nodeAliasesUnparsed, - releaseTag: this.flags.releaseTag, - relayReleaseTag: this.flags.relayReleaseTag, - explorerVersion: this.flags.explorerVersion, - mirrorNodeVersion: this.flags.mirrorNodeVersion, - }; - } -} diff --git a/src/core/config/remote/component-factory.ts b/src/core/config/remote/component-factory.ts new file mode 100644 index 000000000..dfcdbef38 --- /dev/null +++ b/src/core/config/remote/component-factory.ts @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: Apache-2.0 + +import {ComponentTypes} from './enumerations/component-types.js'; +import {DeploymentPhase} from '../../../data/schema/model/remote/deployment-phase.js'; +import {type NodeId} from '../../../types/aliases.js'; +import {ComponentStateMetadataSchema} from '../../../data/schema/model/remote/state/component-state-metadata-schema.js'; +import {type NamespaceName} from '../../../types/namespace/namespace-name.js'; +import {type ClusterReference, type ComponentId} from '../../../types/index.js'; +import {type RemoteConfigRuntimeStateApi} from '../../../business/runtime-state/api/remote-config-runtime-state-api.js'; +import {inject, injectable} from 'tsyringe-neo'; +import {patchInject} from '../../dependency-injection/container-helper.js'; +import {InjectTokens} from '../../dependency-injection/inject-tokens.js'; +import {type ComponentFactoryApi} from './api/component-factory-api.js'; +import {RelayNodeStateSchema} from '../../../data/schema/model/remote/state/relay-node-state-schema.js'; +import {ExplorerStateSchema} from '../../../data/schema/model/remote/state/explorer-state-schema.js'; +import {MirrorNodeStateSchema} from '../../../data/schema/model/remote/state/mirror-node-state-schema.js'; +import {HAProxyStateSchema} from '../../../data/schema/model/remote/state/haproxy-state-schema.js'; +import {EnvoyProxyStateSchema} from '../../../data/schema/model/remote/state/envoy-proxy-state-schema.js'; +import {ConsensusNodeStateSchema} from '../../../data/schema/model/remote/state/consensus-node-state-schema.js'; + +@injectable() +export class ComponentFactory implements ComponentFactoryApi { + public constructor( + @inject(InjectTokens.RemoteConfigRuntimeState) private readonly remoteConfig: RemoteConfigRuntimeStateApi, + ) { + this.remoteConfig = patchInject(remoteConfig, InjectTokens.RemoteConfigRuntimeState, this.constructor.name); + } + + public createNewRelayComponent( + clusterReference: ClusterReference, + namespace: NamespaceName, + nodeIds: NodeId[], + ): RelayNodeStateSchema { + const id: ComponentId = this.remoteConfig.configuration.components.getNewComponentId(ComponentTypes.RelayNodes); + const phase: DeploymentPhase.DEPLOYED = DeploymentPhase.DEPLOYED; + const metadata: ComponentStateMetadataSchema = new ComponentStateMetadataSchema( + id, + namespace.name, + clusterReference, + phase, + ); + + return new RelayNodeStateSchema(metadata, nodeIds); + } + + public createNewExplorerComponent(clusterReference: ClusterReference, namespace: NamespaceName): ExplorerStateSchema { + const id: ComponentId = this.remoteConfig.configuration.components.getNewComponentId(ComponentTypes.Explorers); + const phase: DeploymentPhase.DEPLOYED = DeploymentPhase.DEPLOYED; + const metadata: ComponentStateMetadataSchema = new ComponentStateMetadataSchema( + id, + namespace.name, + clusterReference, + phase, + ); + + return new ExplorerStateSchema(metadata); + } + + public createNewMirrorNodeComponent( + clusterReference: ClusterReference, + namespace: NamespaceName, + ): MirrorNodeStateSchema { + const id: ComponentId = this.remoteConfig.configuration.components.getNewComponentId(ComponentTypes.MirrorNode); + const phase: DeploymentPhase.DEPLOYED = DeploymentPhase.DEPLOYED; + const metadata: ComponentStateMetadataSchema = new ComponentStateMetadataSchema( + id, + namespace.name, + clusterReference, + phase, + ); + + return new MirrorNodeStateSchema(metadata); + } + + public createNewHaProxyComponent(clusterReference: ClusterReference, namespace: NamespaceName): HAProxyStateSchema { + const id: ComponentId = this.remoteConfig.configuration.components.getNewComponentId(ComponentTypes.HaProxy); + const phase: DeploymentPhase.DEPLOYED = DeploymentPhase.DEPLOYED; + const metadata: ComponentStateMetadataSchema = new ComponentStateMetadataSchema( + id, + namespace.name, + clusterReference, + phase, + ); + + return new HAProxyStateSchema(metadata); + } + + public createNewEnvoyProxyComponent( + clusterReference: ClusterReference, + namespace: NamespaceName, + ): EnvoyProxyStateSchema { + const id: ComponentId = this.remoteConfig.configuration.components.getNewComponentId(ComponentTypes.EnvoyProxy); + const phase: DeploymentPhase.DEPLOYED = DeploymentPhase.DEPLOYED; + const metadata: ComponentStateMetadataSchema = new ComponentStateMetadataSchema( + id, + clusterReference, + namespace.name, + phase, + ); + + return new EnvoyProxyStateSchema(metadata); + } + + public createNewConsensusNodeComponent( + nodeId: NodeId, + clusterReference: ClusterReference, + namespace: NamespaceName, + phase: DeploymentPhase.REQUESTED | DeploymentPhase.STARTED, + ): ConsensusNodeStateSchema { + const metadata: ComponentStateMetadataSchema = new ComponentStateMetadataSchema( + nodeId, + namespace.name, + clusterReference, + phase, + ); + + return new ConsensusNodeStateSchema(metadata); + } + + public createConsensusNodeComponentsFromNodeIds( + nodeIds: NodeId[], + clusterReference: ClusterReference, + namespace: NamespaceName, + ): ConsensusNodeStateSchema[] { + return nodeIds.map((nodeId: NodeId) => + this.createNewConsensusNodeComponent(nodeId, clusterReference, namespace, DeploymentPhase.REQUESTED), + ); + } +} diff --git a/src/core/config/remote/components-data-wrapper.ts b/src/core/config/remote/components-data-wrapper.ts index 302463899..63af7c142 100644 --- a/src/core/config/remote/components-data-wrapper.ts +++ b/src/core/config/remote/components-data-wrapper.ts @@ -1,346 +1,189 @@ // SPDX-License-Identifier: Apache-2.0 import {SoloError} from '../../errors/solo-error.js'; -import {BaseComponent} from './components/base-component.js'; -import {RelayComponent} from './components/relay-component.js'; -import {HaProxyComponent} from './components/ha-proxy-component.js'; -import {BlockNodeComponent} from './components/block-node-component.js'; -import {MirrorNodeComponent} from './components/mirror-node-component.js'; -import {EnvoyProxyComponent} from './components/envoy-proxy-component.js'; -import {ConsensusNodeComponent} from './components/consensus-node-component.js'; -import {MirrorNodeExplorerComponent} from './components/mirror-node-explorer-component.js'; -import {type ClusterReference, type ComponentName, type NamespaceNameAsString} from '../../../types/index.js'; import {ComponentTypes} from './enumerations/component-types.js'; -import {ConsensusNodeStates} from './enumerations/consensus-node-states.js'; -import {type BaseComponentStruct} from './components/interfaces/base-component-struct.js'; -import {type RelayComponentStruct} from './components/interfaces/relay-component-struct.js'; -import {type ConsensusNodeComponentStruct} from './components/interfaces/consensus-node-component-struct.js'; -import {type ComponentsDataStruct} from './interfaces/components-data-struct.js'; -import {Templates} from '../../templates.js'; -import {type NodeAliases} from '../../../types/aliases.js'; - -/** - * Represent the components in the remote config and handles: - * - CRUD operations on the components. - * - Validation. - * - Conversion FROM and TO plain object. - */ -export class ComponentsDataWrapper { - private constructor( - public readonly relays: Record = {}, - public readonly haProxies: Record = {}, - public readonly mirrorNodes: Record = {}, - public readonly envoyProxies: Record = {}, - public readonly consensusNodes: Record = {}, - public readonly mirrorNodeExplorers: Record = {}, - public readonly blockNodes: Record = {}, - ) { - this.validate(); - } +import {BaseStateSchema} from '../../../data/schema/model/remote/state/base-state-schema.js'; +import {isValidEnum} from '../../util/validation-helpers.js'; +import {type DeploymentPhase} from '../../../data/schema/model/remote/deployment-phase.js'; +import {type ClusterReference, type ComponentId} from '../../../types/index.js'; +import {type ComponentsDataWrapperApi} from './api/components-data-wrapper-api.js'; +import {type DeploymentStateSchema} from '../../../data/schema/model/remote/deployment-state-schema.js'; + +export class ComponentsDataWrapper implements ComponentsDataWrapperApi { + public constructor(public state: DeploymentStateSchema) {} /* -------- Modifiers -------- */ /** Used to add new component to their respective group. */ - public add(component: BaseComponent): void { - const self = this; - - const serviceName = component.name; + public addNewComponent(component: BaseStateSchema, type: ComponentTypes): void { + const componentId: ComponentId = component.metadata.id; - if (!serviceName || typeof serviceName !== 'string') { - throw new SoloError(`Service name is required ${serviceName}`); + if (typeof componentId !== 'number' || componentId < 0) { + throw new SoloError(`Component id is required ${componentId}`); } - if (!(component instanceof BaseComponent)) { - throw new SoloError('Component must be instance of BaseComponent', null, BaseComponent); + if (!(component instanceof BaseStateSchema)) { + throw new SoloError('Component must be instance of BaseState', undefined, BaseStateSchema); } - function addComponentCallback(components: Record): void { - if (self.exists(components, component)) { - throw new SoloError('Component exists', null, component.toObject()); + const addComponentCallback: (components: BaseStateSchema[]) => void = components => { + if (this.checkComponentExists(components, component)) { + throw new SoloError('Component exists', undefined, component); } - components[serviceName] = component; - } + components[componentId] = component; + }; - self.applyCallbackToComponentGroup(component.type, addComponentCallback, serviceName); + this.applyCallbackToComponentGroup(type, addComponentCallback, componentId); } - /** Used to edit an existing component from their respective group. */ - public edit(component: BaseComponent): void { - const serviceName: ComponentName = component.name; - - if (!serviceName || typeof serviceName !== 'string') { - throw new SoloError(`Service name is required ${serviceName}`); - } - if (!(component instanceof BaseComponent)) { - throw new SoloError('Component must be instance of BaseComponent', null, BaseComponent); - } - - function editComponentCallback(components: Record): void { - if (!components[serviceName]) { - throw new SoloError(`Component doesn't exist, name: ${serviceName}`, null, {component}); - } - components[serviceName] = component; + public changeNodePhase(componentId: ComponentId, phase: DeploymentPhase): void { + if (!this.state.consensusNodes[componentId]) { + throw new SoloError(`Consensus node ${componentId} doesn't exist`); } - this.applyCallbackToComponentGroup(component.type, editComponentCallback, serviceName); + this.state.consensusNodes[componentId].metadata.phase = phase; } /** Used to remove specific component from their respective group. */ - public remove(serviceName: ComponentName, type: ComponentTypes): void { - if (!serviceName || typeof serviceName !== 'string') { - throw new SoloError(`Service name is required ${serviceName}`); + public removeComponent(componentId: ComponentId, type: ComponentTypes): void { + if (typeof componentId !== 'number' || componentId < 0) { + throw new SoloError(`Component id is required ${componentId}`); } - if (!Object.values(ComponentTypes).includes(type)) { + + if (!isValidEnum(type, ComponentTypes)) { throw new SoloError(`Invalid component type ${type}`); } - function deleteComponentCallback(components: Record): void { - if (!components[serviceName]) { - throw new SoloError(`Component ${serviceName} of type ${type} not found while attempting to remove`); + const removeComponentCallback: (components: BaseStateSchema[]) => void = components => { + const index: number = components.findIndex(component => component.metadata.id === componentId); + if (index === -1) { + throw new SoloError(`Component ${componentId} of type ${type} not found while attempting to remove`); } - delete components[serviceName]; - } - this.applyCallbackToComponentGroup(type, deleteComponentCallback, serviceName); + components.splice(index, 1); + }; + + this.applyCallbackToComponentGroup(type, removeComponentCallback, componentId); } /* -------- Utilities -------- */ - public getComponent(type: ComponentTypes, componentName: ComponentName): T { + public getComponent(type: ComponentTypes, componentId: ComponentId): T { let component: T; - const getComponentCallback: (components: Record) => void = components => { - if (!components[componentName]) { - throw new SoloError(`Component ${componentName} of type ${type} not found while attempting to read`); + const getComponentCallback: (components: BaseStateSchema[]) => void = components => { + component = components.find(component => component.metadata.id === componentId) as T; + + if (!component) { + throw new SoloError(`Component ${componentId} of type ${type} not found while attempting to read`); } - component = components[componentName] as T; }; - this.applyCallbackToComponentGroup(type, getComponentCallback, componentName); + this.applyCallbackToComponentGroup(type, getComponentCallback, componentId); return component; } + public getComponentsByClusterReference( + type: ComponentTypes, + clusterReference: ClusterReference, + ): T[] { + let filteredComponents: T[] = []; + + const getComponentsByClusterReferenceCallback: (components: T[]) => void = components => { + filteredComponents = components.filter(component => component.metadata.cluster === clusterReference); + }; + + this.applyCallbackToComponentGroup(type, getComponentsByClusterReferenceCallback); + + return filteredComponents; + } + + public getComponentById(type: ComponentTypes, id: number): T { + let filteredComponent: T; + + const getComponentByIdCallback: (components: T[]) => void = components => { + filteredComponent = components.find(component => +component.metadata.id === id); + }; + + this.applyCallbackToComponentGroup(type, getComponentByIdCallback); + + if (!filteredComponent) { + throw new SoloError(`Component of type ${type} with id ${id} was not found in remote config`); + } + + return filteredComponent; + } + /** * Method used to map the type to the specific component group * and pass it to a callback to apply modifications */ private applyCallbackToComponentGroup( componentType: ComponentTypes, - callback: (components: Record) => void, - componentName?: ComponentName, + callback: (components: BaseStateSchema[]) => void, + componentId?: ComponentId, ): void { switch (componentType) { - case ComponentTypes.Relay: { - callback(this.relays); + case ComponentTypes.RelayNodes: { + callback(this.state.relayNodes); break; } case ComponentTypes.HaProxy: { - callback(this.haProxies); + callback(this.state.haProxies); break; } case ComponentTypes.MirrorNode: { - callback(this.mirrorNodes); + callback(this.state.mirrorNodes); break; } case ComponentTypes.EnvoyProxy: { - callback(this.envoyProxies); + callback(this.state.envoyProxies); break; } case ComponentTypes.ConsensusNode: { - callback(this.consensusNodes); + callback(this.state.consensusNodes); break; } - case ComponentTypes.MirrorNodeExplorer: { - callback(this.mirrorNodeExplorers); - break; - } - - case ComponentTypes.BlockNode: { - callback(this.blockNodes); + case ComponentTypes.Explorers: { + callback(this.state.explorers); break; } default: { - throw new SoloError(`Unknown component type ${componentType}, component name: ${componentName}`); + throw new SoloError(`Unknown component type ${componentType}, component id: ${componentId}`); } } + } - this.validate(); + /** checks if component exists in the respective group */ + private checkComponentExists(components: BaseStateSchema[], newComponent: BaseStateSchema): boolean { + return components.some((component): boolean => component.metadata.id === newComponent.metadata.id); } /** - * Handles creating instance of the class from plain object. - * - * @param components - component groups distinguished by their type. + * Checks all existing components of specified type and gives you a new unique index */ - public static fromObject(components: ComponentsDataStruct): ComponentsDataWrapper { - const relays: Record = {}; - const haProxies: Record = {}; - const mirrorNodes: Record = {}; - const envoyProxies: Record = {}; - const consensusNodes: Record = {}; - const mirrorNodeExplorers: Record = {}; - const blockNodes: Record = {}; - - for (const [componentType, subComponents] of Object.entries(components)) { - switch (componentType) { - case ComponentTypes.Relay: { - for (const [componentName, component] of Object.entries(subComponents)) { - relays[componentName] = RelayComponent.fromObject(component as RelayComponentStruct); - } - break; - } + public getNewComponentId(componentType: ComponentTypes): number { + let newComponentId: number = 0; - case ComponentTypes.HaProxy: { - for (const [componentName, component] of Object.entries(subComponents)) { - haProxies[componentName] = HaProxyComponent.fromObject(component); - } - break; - } + const calculateNewComponentIndexCallback: (components: BaseStateSchema[]) => void = components => { + const componentIds: ComponentId[] = components.map(component => component.metadata.id); - case ComponentTypes.MirrorNode: { - for (const [componentName, component] of Object.entries(subComponents)) { - mirrorNodes[componentName] = MirrorNodeComponent.fromObject(component); - } - break; - } - - case ComponentTypes.EnvoyProxy: { - for (const [componentName, component] of Object.entries(subComponents)) { - envoyProxies[componentName] = EnvoyProxyComponent.fromObject(component); - } - break; - } - - case ComponentTypes.ConsensusNode: { - for (const [componentName, component] of Object.entries(subComponents)) { - consensusNodes[componentName] = ConsensusNodeComponent.fromObject( - component as ConsensusNodeComponentStruct, - ); - } - break; - } - - case ComponentTypes.MirrorNodeExplorer: { - for (const [componentName, component] of Object.entries(subComponents)) { - mirrorNodeExplorers[componentName] = MirrorNodeExplorerComponent.fromObject(component); - } - break; - } - - case ComponentTypes.BlockNode: { - for (const [componentName, component] of Object.entries(subComponents)) { - blockNodes[componentName] = BlockNodeComponent.fromObject(component); - } - break; - } - - default: { - throw new SoloError(`Unknown component type ${componentType}`); + for (const componentId of componentIds) { + if (newComponentId <= +componentId) { + newComponentId = +componentId + 1; } } - } - - return new ComponentsDataWrapper( - relays, - haProxies, - mirrorNodes, - envoyProxies, - consensusNodes, - mirrorNodeExplorers, - blockNodes, - ); - } - - /** Used to create an empty instance used to keep the constructor private */ - public static initializeEmpty(): ComponentsDataWrapper { - return new ComponentsDataWrapper(); - } - - public static initializeWithNodes( - nodeAliases: NodeAliases, - clusterReference: ClusterReference, - namespace: NamespaceNameAsString, - ): ComponentsDataWrapper { - const consensusNodeComponents: Record = {}; - - for (const nodeAlias of nodeAliases) { - consensusNodeComponents[nodeAlias] = new ConsensusNodeComponent( - nodeAlias, - clusterReference, - namespace, - ConsensusNodeStates.NON_DEPLOYED, - Templates.nodeIdFromNodeAlias(nodeAlias), - ); - } - - return new ComponentsDataWrapper(undefined, undefined, undefined, undefined, consensusNodeComponents, undefined); - } - - /** checks if component exists in the respective group */ - private exists(components: Record, newComponent: BaseComponent): boolean { - return Object.values(components).some(component => BaseComponent.compare(component, newComponent)); - } - - public validate(): void { - function testComponentsObject(components: Record, expectedInstance: any): void { - for (const [name, component] of Object.entries(components)) { - if (!name || typeof name !== 'string') { - throw new SoloError(`Invalid component service name ${{[name]: component?.constructor?.name}}`); - } - - if (!(component instanceof expectedInstance)) { - throw new SoloError( - `Invalid component type, service name: ${name}, ` + - `expected ${expectedInstance?.name}, actual: ${component?.constructor?.name}`, - null, - {component}, - ); - } - } - } - - testComponentsObject(this.relays, RelayComponent); - testComponentsObject(this.haProxies, HaProxyComponent); - testComponentsObject(this.mirrorNodes, MirrorNodeComponent); - testComponentsObject(this.envoyProxies, EnvoyProxyComponent); - testComponentsObject(this.consensusNodes, ConsensusNodeComponent); - testComponentsObject(this.mirrorNodeExplorers, MirrorNodeExplorerComponent); - } - - private transformComponentGroupToObject( - components: Record, - ): Record { - const transformedComponents: Record = {}; - - for (const [componentName, component] of Object.entries(components)) { - transformedComponents[componentName] = component.toObject() as BaseComponentStruct; - } - - return transformedComponents; - } - - public toObject(): ComponentsDataStruct { - return { - [ComponentTypes.Relay]: this.transformComponentGroupToObject(this.relays), - [ComponentTypes.HaProxy]: this.transformComponentGroupToObject(this.haProxies), - [ComponentTypes.MirrorNode]: this.transformComponentGroupToObject(this.mirrorNodes), - [ComponentTypes.EnvoyProxy]: this.transformComponentGroupToObject(this.envoyProxies), - [ComponentTypes.ConsensusNode]: this.transformComponentGroupToObject(this.consensusNodes), - [ComponentTypes.MirrorNodeExplorer]: this.transformComponentGroupToObject(this.mirrorNodeExplorers), - [ComponentTypes.BlockNode]: this.transformComponentGroupToObject(this.blockNodes), }; - } - public clone(): ComponentsDataWrapper { - const data: ComponentsDataStruct = this.toObject(); + this.applyCallbackToComponentGroup(componentType, calculateNewComponentIndexCallback); - return ComponentsDataWrapper.fromObject(data); + return newComponentId; } } diff --git a/src/core/config/remote/components/base-component.ts b/src/core/config/remote/components/base-component.ts deleted file mode 100644 index 7bb535563..000000000 --- a/src/core/config/remote/components/base-component.ts +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {SoloError} from '../../../errors/solo-error.js'; -import {type ClusterReference, type ComponentName, type NamespaceNameAsString} from '../../../../types/index.js'; -import {type ToObject, type Validate} from '../../../../types/index.js'; -import {ComponentTypes} from '../enumerations/component-types.js'; -import {isValidEnum} from '../../../util/validation-helpers.js'; -import {type BaseComponentStruct} from './interfaces/base-component-struct.js'; - -/** - * Represents the base structure and common functionality for all components within the system. - * This class provides validation, comparison, and serialization functionality for components. - */ -export class BaseComponent implements BaseComponentStruct, Validate, ToObject { - /** - * @param type - type for identifying. - * @param name - the name to distinguish components. - * @param cluster - the cluster in which the component is deployed. - * @param namespace - the namespace associated with the component. - * @param state - the state of the component - */ - protected constructor( - public readonly type: ComponentTypes, - public readonly name: ComponentName, - public readonly cluster: ClusterReference, - public readonly namespace: NamespaceNameAsString, - ) {} - - /* -------- Utilities -------- */ - - /** - * Compares two BaseComponent instances for equality. - * - * @param x - The first component to compare - * @param y - The second component to compare - * @returns boolean - true if the components are equal - */ - public static compare(x: BaseComponent, y: BaseComponent): boolean { - return x.name === y.name && x.type === y.type && x.cluster === y.cluster && x.namespace === y.namespace; - } - - public validate(): void { - if (!this.name || typeof this.name !== 'string') { - throw new SoloError(`Invalid name: ${this.name}`); - } - - if (!this.cluster || typeof this.cluster !== 'string') { - throw new SoloError(`Invalid cluster: ${this.cluster}`); - } - - if (!this.namespace || typeof this.namespace !== 'string') { - throw new SoloError( - `Invalid namespace: ${this.namespace}, is typeof 'string': ${typeof this.namespace !== 'string'}`, - ); - } - - if (!isValidEnum(this.type, ComponentTypes)) { - throw new SoloError(`Invalid component type: ${this.type}`); - } - } - - public toObject(): BaseComponentStruct { - return { - name: this.name, - cluster: this.cluster, - namespace: this.namespace, - }; - } -} diff --git a/src/core/config/remote/components/block-node-component.ts b/src/core/config/remote/components/block-node-component.ts deleted file mode 100644 index 4177e12ef..000000000 --- a/src/core/config/remote/components/block-node-component.ts +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {BaseComponent} from './base-component.js'; -import {type ClusterReference, type ComponentName, type NamespaceNameAsString} from '../../../../types/index.js'; -import {ComponentTypes} from '../enumerations/component-types.js'; -import {type BaseComponentStruct} from './interfaces/base-component-struct.js'; - -export class BlockNodeComponent extends BaseComponent { - public constructor(name: ComponentName, cluster: ClusterReference, namespace: NamespaceNameAsString) { - super(ComponentTypes.BlockNode, name, cluster, namespace); - this.validate(); - } - - /* -------- Utilities -------- */ - - /** Handles creating instance of the class from plain object. */ - public static fromObject(component: BaseComponentStruct): BlockNodeComponent { - const {name, cluster, namespace} = component; - return new BlockNodeComponent(name, cluster, namespace); - } -} diff --git a/src/core/config/remote/components/consensus-node-component.ts b/src/core/config/remote/components/consensus-node-component.ts deleted file mode 100644 index 549d60d12..000000000 --- a/src/core/config/remote/components/consensus-node-component.ts +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {BaseComponent} from './base-component.js'; -import {SoloError} from '../../../errors/solo-error.js'; -import {ComponentTypes} from '../enumerations/component-types.js'; -import {ConsensusNodeStates} from '../enumerations/consensus-node-states.js'; -import {isValidEnum} from '../../../util/validation-helpers.js'; -import {type ClusterReference, type ComponentName, type NamespaceNameAsString} from '../../../../types/index.js'; -import {type ToObject} from '../../../../types/index.js'; -import {type ConsensusNodeComponentStruct} from './interfaces/consensus-node-component-struct.js'; - -/** - * Represents a consensus node component within the system. - * - * A `ConsensusNodeComponent` extends the functionality of `BaseComponent` and includes additional properties and behaviors - * specific to consensus nodes, such as maintaining and validating the node's state. - */ -export class ConsensusNodeComponent - extends BaseComponent - implements ConsensusNodeComponentStruct, ToObject -{ - /** - * @param name - the name to distinguish components. - * @param nodeId - node id of the consensus node - * @param cluster - associated to component - * @param namespace - associated to component - * @param state - of the consensus node - */ - public constructor( - name: ComponentName, - cluster: ClusterReference, - namespace: NamespaceNameAsString, - public readonly state: ConsensusNodeStates, - public readonly nodeId: number, - ) { - super(ComponentTypes.ConsensusNode, name, cluster, namespace); - - this.validate(); - } - - /* -------- Utilities -------- */ - - /** Handles creating instance of the class from plain object. */ - public static fromObject(component: ConsensusNodeComponentStruct): ConsensusNodeComponent { - const {name, cluster, namespace, state, nodeId} = component; - return new ConsensusNodeComponent(name, cluster, namespace, state, nodeId); - } - - public override validate(): void { - super.validate(); - - if (!isValidEnum(this.state, ConsensusNodeStates)) { - throw new SoloError(`Invalid consensus node state: ${this.state}`); - } - - if (typeof this.nodeId !== 'number') { - throw new SoloError(`Invalid node id. It must be a number: ${this.nodeId}`); - } - - if (this.nodeId < 0) { - throw new SoloError(`Invalid node id. It cannot be negative: ${this.nodeId}`); - } - } - - public override toObject(): ConsensusNodeComponentStruct { - return { - ...super.toObject(), - state: this.state, - nodeId: this.nodeId, - }; - } -} diff --git a/src/core/config/remote/components/envoy-proxy-component.ts b/src/core/config/remote/components/envoy-proxy-component.ts deleted file mode 100644 index 833e687b2..000000000 --- a/src/core/config/remote/components/envoy-proxy-component.ts +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {BaseComponent} from './base-component.js'; -import {ComponentTypes} from '../enumerations/component-types.js'; -import {type ClusterReference, type ComponentName, type NamespaceNameAsString} from '../../../../types/index.js'; -import {type BaseComponentStruct} from './interfaces/base-component-struct.js'; - -export class EnvoyProxyComponent extends BaseComponent { - public constructor(name: ComponentName, cluster: ClusterReference, namespace: NamespaceNameAsString) { - super(ComponentTypes.EnvoyProxy, name, cluster, namespace); - this.validate(); - } - - /* -------- Utilities -------- */ - - /** Handles creating instance of the class from plain object. */ - public static fromObject(component: BaseComponentStruct): EnvoyProxyComponent { - const {name, cluster, namespace} = component; - return new EnvoyProxyComponent(name, cluster, namespace); - } -} diff --git a/src/core/config/remote/components/ha-proxy-component.ts b/src/core/config/remote/components/ha-proxy-component.ts deleted file mode 100644 index 36915b0d9..000000000 --- a/src/core/config/remote/components/ha-proxy-component.ts +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {BaseComponent} from './base-component.js'; -import {type ClusterReference, type ComponentName, type NamespaceNameAsString} from '../../../../types/index.js'; -import {ComponentTypes} from '../enumerations/component-types.js'; -import {type BaseComponentStruct} from './interfaces/base-component-struct.js'; - -export class HaProxyComponent extends BaseComponent { - public constructor(name: ComponentName, cluster: ClusterReference, namespace: NamespaceNameAsString) { - super(ComponentTypes.HaProxy, name, cluster, namespace); - this.validate(); - } - - /* -------- Utilities -------- */ - - /** Handles creating instance of the class from plain object. */ - public static fromObject(component: BaseComponentStruct): HaProxyComponent { - const {name, cluster, namespace} = component; - return new HaProxyComponent(name, cluster, namespace); - } -} diff --git a/src/core/config/remote/components/interfaces/base-component-struct.ts b/src/core/config/remote/components/interfaces/base-component-struct.ts deleted file mode 100644 index 8d9f31c97..000000000 --- a/src/core/config/remote/components/interfaces/base-component-struct.ts +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {type ClusterReference, type ComponentName, type NamespaceNameAsString} from '../../../../../types/index.js'; - -export interface BaseComponentStruct { - name: ComponentName; - cluster: ClusterReference; - namespace: NamespaceNameAsString; -} diff --git a/src/core/config/remote/components/interfaces/consensus-node-component-struct.ts b/src/core/config/remote/components/interfaces/consensus-node-component-struct.ts deleted file mode 100644 index 8b10c1bf8..000000000 --- a/src/core/config/remote/components/interfaces/consensus-node-component-struct.ts +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {type BaseComponentStruct} from './base-component-struct.js'; -import {type ConsensusNodeStates} from '../../enumerations/consensus-node-states.js'; - -export interface ConsensusNodeComponentStruct extends BaseComponentStruct { - nodeId: number; - state: ConsensusNodeStates; -} diff --git a/src/core/config/remote/components/interfaces/relay-component-struct.ts b/src/core/config/remote/components/interfaces/relay-component-struct.ts deleted file mode 100644 index 358a14a54..000000000 --- a/src/core/config/remote/components/interfaces/relay-component-struct.ts +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {type BaseComponentStruct} from './base-component-struct.js'; -import {type NodeAliases} from '../../../../../types/aliases.js'; - -export interface RelayComponentStruct extends BaseComponentStruct { - consensusNodeAliases: NodeAliases; -} diff --git a/src/core/config/remote/components/mirror-node-component.ts b/src/core/config/remote/components/mirror-node-component.ts deleted file mode 100644 index b15223a80..000000000 --- a/src/core/config/remote/components/mirror-node-component.ts +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {BaseComponent} from './base-component.js'; -import {ComponentTypes} from '../enumerations/component-types.js'; -import {type ClusterReference, type ComponentName, type NamespaceNameAsString} from '../../../../types/index.js'; -import {type BaseComponentStruct} from './interfaces/base-component-struct.js'; - -export class MirrorNodeComponent extends BaseComponent { - public constructor(name: ComponentName, cluster: ClusterReference, namespace: NamespaceNameAsString) { - super(ComponentTypes.MirrorNode, name, cluster, namespace); - this.validate(); - } - - /* -------- Utilities -------- */ - - /** Handles creating instance of the class from plain object. */ - public static fromObject(component: BaseComponentStruct): MirrorNodeComponent { - const {name, cluster, namespace} = component; - return new MirrorNodeComponent(name, cluster, namespace); - } -} diff --git a/src/core/config/remote/components/mirror-node-explorer-component.ts b/src/core/config/remote/components/mirror-node-explorer-component.ts deleted file mode 100644 index 0565d265f..000000000 --- a/src/core/config/remote/components/mirror-node-explorer-component.ts +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {BaseComponent} from './base-component.js'; -import {ComponentTypes} from '../enumerations/component-types.js'; -import {type ClusterReference, type ComponentName, type NamespaceNameAsString} from '../../../../types/index.js'; -import {type BaseComponentStruct} from './interfaces/base-component-struct.js'; - -export class MirrorNodeExplorerComponent extends BaseComponent { - public constructor(name: ComponentName, cluster: ClusterReference, namespace: NamespaceNameAsString) { - super(ComponentTypes.MirrorNodeExplorer, name, cluster, namespace); - this.validate(); - } - - /* -------- Utilities -------- */ - - /** Handles creating instance of the class from plain object. */ - public static fromObject(component: BaseComponentStruct): MirrorNodeExplorerComponent { - const {name, cluster, namespace} = component; - return new MirrorNodeExplorerComponent(name, cluster, namespace); - } -} diff --git a/src/core/config/remote/components/relay-component.ts b/src/core/config/remote/components/relay-component.ts deleted file mode 100644 index 2c4aaedb2..000000000 --- a/src/core/config/remote/components/relay-component.ts +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {SoloError} from '../../../errors/solo-error.js'; -import {BaseComponent} from './base-component.js'; -import {ComponentTypes} from '../enumerations/component-types.js'; -import {type ClusterReference, type ComponentName, type NamespaceNameAsString} from '../../../../types/index.js'; -import {type NodeAliases} from '../../../../types/aliases.js'; -import {type ToObject} from '../../../../types/index.js'; -import {type RelayComponentStruct} from './interfaces/relay-component-struct.js'; - -export class RelayComponent extends BaseComponent implements RelayComponentStruct, ToObject { - /** - * @param name - to distinguish components. - * @param clusterReference - in which the component is deployed. - * @param namespace - associated with the component. - * @param consensusNodeAliases - list node aliases - */ - public constructor( - name: ComponentName, - clusterReference: ClusterReference, - namespace: NamespaceNameAsString, - public readonly consensusNodeAliases: NodeAliases = [], - ) { - super(ComponentTypes.Relay, name, clusterReference, namespace); - this.validate(); - } - - /* -------- Utilities -------- */ - - /** Handles creating instance of the class from plain object. */ - public static fromObject(component: RelayComponentStruct): RelayComponent { - const {name, cluster, namespace, consensusNodeAliases} = component; - return new RelayComponent(name, cluster, namespace, consensusNodeAliases); - } - - public override validate(): void { - super.validate(); - - for (const nodeAlias of this.consensusNodeAliases) { - if (!nodeAlias || typeof nodeAlias !== 'string') { - throw new SoloError(`Invalid consensus node alias: ${nodeAlias}, aliases ${this.consensusNodeAliases}`); - } - } - } - - public override toObject(): RelayComponentStruct { - return { - consensusNodeAliases: this.consensusNodeAliases, - ...super.toObject(), - }; - } -} diff --git a/src/core/config/remote/enumerations/component-types.ts b/src/core/config/remote/enumerations/component-types.ts index 4c01bcc88..985b94da0 100644 --- a/src/core/config/remote/enumerations/component-types.ts +++ b/src/core/config/remote/enumerations/component-types.ts @@ -9,6 +9,6 @@ export enum ComponentTypes { HaProxy = 'haProxies', EnvoyProxy = 'envoyProxies', MirrorNode = 'mirrorNodes', - MirrorNodeExplorer = 'mirrorNodeExplorers', - Relay = 'relays', + Explorers = 'explorers', + RelayNodes = 'relayNodes', } diff --git a/src/core/config/remote/enumerations/consensus-node-states.ts b/src/core/config/remote/enumerations/consensus-node-states.ts deleted file mode 100644 index b429d8944..000000000 --- a/src/core/config/remote/enumerations/consensus-node-states.ts +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -/** - * Enumerations that represent the state of consensus node in remote config - */ -export enum ConsensusNodeStates { - NON_DEPLOYED = 'non-deployed', - REQUESTED = 'requested', - INITIALIZED = 'initialized', - SETUP = 'setup', - STARTED = 'started', - FROZEN = 'frozen', - STOPPED = 'stopped', -} diff --git a/src/core/config/remote/interfaces/cluster-struct.ts b/src/core/config/remote/interfaces/cluster-struct.ts deleted file mode 100644 index ecf16eec4..000000000 --- a/src/core/config/remote/interfaces/cluster-struct.ts +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {type DeploymentName} from '../../../../types/index.js'; - -export interface ClusterStruct { - name: string; - namespace: string; - deployment: DeploymentName; - dnsBaseDomain: string; - dnsConsensusNodePattern: string; -} diff --git a/src/core/config/remote/interfaces/components-data-struct.ts b/src/core/config/remote/interfaces/components-data-struct.ts deleted file mode 100644 index 24eb61524..000000000 --- a/src/core/config/remote/interfaces/components-data-struct.ts +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {type ComponentTypes} from '../enumerations/component-types.js'; -import {type BaseComponentStruct} from '../components/interfaces/base-component-struct.js'; -import {type ComponentName} from '../../../../types/index.js'; - -export type ComponentsDataStruct = Record>; diff --git a/src/core/config/remote/interfaces/migration-struct.ts b/src/core/config/remote/interfaces/migration-struct.ts deleted file mode 100644 index 9e6e1a525..000000000 --- a/src/core/config/remote/interfaces/migration-struct.ts +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {type Version} from '../../../../types/index.js'; -import {type UserIdentitySchema} from '../../../../data/schema/model/common/user-identity-schema.js'; - -export interface MigrationStruct { - migratedAt: Date; - migratedBy: UserIdentitySchema; - fromVersion: Version; -} diff --git a/src/core/config/remote/interfaces/remote-config-common-flags-struct.ts b/src/core/config/remote/interfaces/remote-config-common-flags-struct.ts deleted file mode 100644 index 80ed4d656..000000000 --- a/src/core/config/remote/interfaces/remote-config-common-flags-struct.ts +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -export type RemoteConfigCommonFlagsStruct = { - releaseTag?: string; - chartDirectory?: string; - relayReleaseTag?: string; - soloChartVersion?: string; - mirrorNodeVersion?: string; - nodeAliasesUnparsed?: string; - explorerVersion?: string; -}; diff --git a/src/core/config/remote/interfaces/remote-config-data-struct.ts b/src/core/config/remote/interfaces/remote-config-data-struct.ts deleted file mode 100644 index 9734382c4..000000000 --- a/src/core/config/remote/interfaces/remote-config-data-struct.ts +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {type ClusterStruct} from './cluster-struct.js'; -import {type RemoteConfigCommonFlagsStruct} from './remote-config-common-flags-struct.js'; -import {type ClusterReference, type Version} from '../../../../types/index.js'; -import {type RemoteConfigMetadataStruct} from './remote-config-metadata-struct.js'; -import {type ComponentsDataStruct} from './components-data-struct.js'; - -export interface RemoteConfigDataStruct { - metadata: RemoteConfigMetadataStruct; - version: Version; - clusters: Record; - components: ComponentsDataStruct; - commandHistory: string[]; - lastExecutedCommand: string; - flags: RemoteConfigCommonFlagsStruct; -} diff --git a/src/core/config/remote/interfaces/remote-config-metadata-struct.ts b/src/core/config/remote/interfaces/remote-config-metadata-struct.ts deleted file mode 100644 index 7a77d95e7..000000000 --- a/src/core/config/remote/interfaces/remote-config-metadata-struct.ts +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {type DeploymentStates} from '../enumerations/deployment-states.js'; -import {type MigrationStruct} from './migration-struct.js'; -import {type DeploymentName, type NamespaceNameAsString, type Version} from '../../../../types/index.js'; -import {type UserIdentitySchema} from '../../../../data/schema/model/common/user-identity-schema.js'; - -export interface RemoteConfigMetadataStruct { - namespace: NamespaceNameAsString; - state: DeploymentStates; - deploymentName: DeploymentName; - lastUpdatedAt: Date; - lastUpdateBy: UserIdentitySchema; - soloVersion: Version; - soloChartVersion: Version; - hederaPlatformVersion: Version; - hederaMirrorNodeChartVersion: Version; - explorerChartVersion: Version; - hederaJsonRpcRelayChartVersion: Version; - migration?: MigrationStruct; -} diff --git a/src/core/config/remote/listr-config-tasks.ts b/src/core/config/remote/listr-config-tasks.ts deleted file mode 100644 index 5c1ccb551..000000000 --- a/src/core/config/remote/listr-config-tasks.ts +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {type SoloListrTask} from '../../../types/index.js'; -import {type AnyObject} from '../../../types/aliases.js'; -import {type RemoteConfigManager} from './remote-config-manager.js'; - -/** - * Static class that handles all tasks related to remote config used by other commands. - */ -export class ListrRemoteConfig { - /** - * Loads the remote config from the config class and performs component validation. - * - * @param remoteConfigManager - * @param argv - used to update the last executed command and command history - */ - public static loadRemoteConfig( - remoteConfigManager: RemoteConfigManager, - argv: {_: string[]} & AnyObject, - ): SoloListrTask { - return { - title: 'Load remote config', - task: async (): Promise => { - await remoteConfigManager.loadAndValidate(argv); - }, - }; - } -} diff --git a/src/core/config/remote/metadata.ts b/src/core/config/remote/metadata.ts deleted file mode 100644 index c6bdd0c4a..000000000 --- a/src/core/config/remote/metadata.ts +++ /dev/null @@ -1,152 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {Migration} from './migration.js'; -import {SoloError} from '../../errors/solo-error.js'; -import { - type DeploymentName, - type NamespaceNameAsString, - type Optional, - type ToObject, - type Validate, - type Version, -} from '../../../types/index.js'; -import {type UserIdentitySchema} from '../../../data/schema/model/common/user-identity-schema.js'; -import {DeploymentStates} from './enumerations/deployment-states.js'; -import {isValidEnum} from '../../util/validation-helpers.js'; -import {type RemoteConfigMetadataStruct} from './interfaces/remote-config-metadata-struct.js'; - -/** - * Represent the remote config metadata object and handles: - * - Validation - * - Reading - * - Making a migration - * - Converting from and to plain object - */ -export class RemoteConfigMetadata - implements RemoteConfigMetadataStruct, Validate, ToObject -{ - private _migration?: Migration; - - public constructor( - public readonly namespace: NamespaceNameAsString, - public readonly deploymentName: DeploymentName, - public readonly state: DeploymentStates, - public readonly lastUpdatedAt: Date, - public readonly lastUpdateBy: UserIdentitySchema, - public readonly soloVersion: Version, - public soloChartVersion: Version = '', - public hederaPlatformVersion: Version = '', - public hederaMirrorNodeChartVersion: Version = '', - public explorerChartVersion: Version = '', - public hederaJsonRpcRelayChartVersion: Version = '', - migration?: Migration, - ) { - this._migration = migration; - this.validate(); - } - - /* -------- Modifiers -------- */ - - /** Simplifies making a migration */ - public makeMigration(userIdentity: UserIdentitySchema, fromVersion: Version): void { - this._migration = new Migration(new Date(), userIdentity, fromVersion); - } - - /* -------- Getters -------- */ - - /** Retrieves the migration if such exists */ - public get migration(): Optional { - return this._migration; - } - - /* -------- Utilities -------- */ - - /** Handles conversion from a plain object to instance */ - public static fromObject(metadata: RemoteConfigMetadataStruct): RemoteConfigMetadata { - let migration: Optional = undefined; - - if (metadata.migration) { - const { - migration: {migratedAt, migratedBy, fromVersion}, - } = metadata; - migration = new Migration(new Date(migratedAt), migratedBy, fromVersion); - } - - return new RemoteConfigMetadata( - metadata.namespace, - metadata.deploymentName, - metadata.state, - new Date(metadata.lastUpdatedAt), - metadata.lastUpdateBy, - metadata.soloVersion, - metadata.soloChartVersion, - metadata.hederaPlatformVersion, - metadata.hederaMirrorNodeChartVersion, - metadata.explorerChartVersion, - metadata.hederaJsonRpcRelayChartVersion, - migration, - ); - } - - public validate(): void { - if (!this.namespace || !(typeof this.namespace === 'string')) { - throw new SoloError( - `Invalid namespace: ${this.namespace}, is type string: ${typeof this.namespace === 'string'}`, - ); - } - - if (!this.deploymentName || !(typeof this.deploymentName === 'string')) { - throw new SoloError( - `Invalid deploymentName: ${this.deploymentName}, is type string: ${typeof this.deploymentName === 'string'}`, - ); - } - - if (!(this.lastUpdatedAt instanceof Date)) { - throw new SoloError(`Invalid lastUpdatedAt: ${this.lastUpdatedAt}`); - } - - if ( - !this.lastUpdateBy || - !this.lastUpdateBy.name || - !this.lastUpdateBy.hostname || - typeof this.lastUpdateBy.name !== 'string' || - typeof this.lastUpdateBy.hostname !== 'string' - ) { - throw new SoloError(`Invalid lastUpdateBy: ${JSON.stringify(this.lastUpdateBy)}`); - } - - if (!this.soloVersion || typeof this.soloVersion !== 'string') { - throw new SoloError(`Invalid soloVersion: ${this.soloVersion}`); - } - - if (!isValidEnum(this.state, DeploymentStates)) { - throw new SoloError(`Invalid cluster state: ${this.state}`); - } - - if (this.migration && !(this.migration instanceof Migration)) { - throw new SoloError(`Invalid migration: ${this.migration}`); - } - } - - public toObject(): RemoteConfigMetadataStruct { - const data: RemoteConfigMetadataStruct = { - namespace: this.namespace, - deploymentName: this.deploymentName, - state: this.state, - lastUpdatedAt: this.lastUpdatedAt, - lastUpdateBy: this.lastUpdateBy, - soloChartVersion: this.soloChartVersion, - hederaPlatformVersion: this.hederaPlatformVersion, - hederaMirrorNodeChartVersion: this.hederaMirrorNodeChartVersion, - explorerChartVersion: this.explorerChartVersion, - hederaJsonRpcRelayChartVersion: this.hederaJsonRpcRelayChartVersion, - soloVersion: this.soloVersion, - } as RemoteConfigMetadataStruct; - - if (this.migration) { - data.migration = this.migration.toObject(); - } - - return data; - } -} diff --git a/src/core/config/remote/migration.ts b/src/core/config/remote/migration.ts deleted file mode 100644 index 46a7895c1..000000000 --- a/src/core/config/remote/migration.ts +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {SoloError} from '../../errors/solo-error.js'; -import {type Version} from '../../../types/index.js'; -import {type MigrationStruct} from './interfaces/migration-struct.js'; -import {type UserIdentitySchema} from '../../../data/schema/model/common/user-identity-schema.js'; - -export class Migration implements MigrationStruct { - private readonly _migratedAt: Date; - private readonly _migratedBy: UserIdentitySchema; - private readonly _fromVersion: Version; - - public constructor(migratedAt: Date, migratedBy: UserIdentitySchema, fromVersion: Version) { - this._migratedAt = migratedAt; - this._migratedBy = migratedBy; - this._fromVersion = fromVersion; - this.validate(); - } - - /* -------- Getters -------- */ - - public get migratedAt(): Date { - return this._migratedAt; - } - public get migratedBy(): UserIdentitySchema { - return this._migratedBy; - } - public get fromVersion(): Version { - return this._fromVersion; - } - - /* -------- Utilities -------- */ - - public validate(): void { - if (!(this.migratedAt instanceof Date)) { - throw new SoloError(`Invalid migratedAt: ${this.migratedAt}`); - } - - if ( - !this.migratedBy || - !this.migratedBy.name || - !this.migratedBy.hostname || - typeof this.migratedBy.name !== 'string' || - typeof this.migratedBy.hostname !== 'string' - ) { - throw new SoloError(`Invalid migratedBy: ${this.migratedBy}`); - } - - if (!this.fromVersion || typeof this.fromVersion !== 'string') { - throw new SoloError(`Invalid fromVersion: ${this.fromVersion}`); - } - } - - public toObject(): MigrationStruct { - return { - migratedAt: this.migratedAt, - migratedBy: this.migratedBy, - fromVersion: this.fromVersion, - }; - } -} diff --git a/src/core/config/remote/remote-config-data-wrapper.ts b/src/core/config/remote/remote-config-data-wrapper.ts deleted file mode 100644 index 25c6b1884..000000000 --- a/src/core/config/remote/remote-config-data-wrapper.ts +++ /dev/null @@ -1,168 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {SoloError} from '../../errors/solo-error.js'; -import * as yaml from 'yaml'; -import {RemoteConfigMetadata} from './metadata.js'; -import {ComponentsDataWrapper} from './components-data-wrapper.js'; -import * as constants from '../../constants.js'; -import {CommonFlagsDataWrapper} from './common-flags-data-wrapper.js'; -import {type ClusterReference, type Version} from '../../../types/index.js'; -import {type ToObject, type Validate} from '../../../types/index.js'; -import {type ConfigManager} from '../../config-manager.js'; -import {type RemoteConfigData} from './remote-config-data.js'; -import {Cluster} from './cluster.js'; -import {type ConfigMap} from '../../../integration/kube/resources/config-map/config-map.js'; -import {type RemoteConfigDataStruct} from './interfaces/remote-config-data-struct.js'; - -export class RemoteConfigDataWrapper implements Validate, ToObject { - private readonly _version: Version = '1.0.0'; - private _metadata: RemoteConfigMetadata; - private readonly _clusters: Record; - private _components: ComponentsDataWrapper; - private _commandHistory: string[]; - private _lastExecutedCommand: string; - private readonly _flags: CommonFlagsDataWrapper; - - public constructor(data: RemoteConfigData) { - this._metadata = data.metadata; - this._clusters = Cluster.fromClustersMapObject(data.clusters); - this._components = data.components; - this._commandHistory = data.commandHistory; - this._lastExecutedCommand = data.lastExecutedCommand ?? ''; - this._flags = data.flags; - this.validate(); - } - - //! -------- Modifiers -------- // - - public addCommandToHistory(command: string): void { - this._commandHistory.push(command); - this.lastExecutedCommand = command; - - if (this._commandHistory.length > constants.SOLO_REMOTE_CONFIG_MAX_COMMAND_IN_HISTORY) { - this._commandHistory.shift(); - } - - this.validate(); - } - - //! -------- Getters & Setters -------- // - - private get version(): Version { - return this._version; - } - - public get metadata(): RemoteConfigMetadata { - return this._metadata; - } - - public set metadata(metadata: RemoteConfigMetadata) { - this._metadata = metadata; - this.validate(); - } - - public get clusters(): Record { - return this._clusters; - } - - public addCluster(cluster: Cluster): void { - this._clusters[cluster.name] = cluster; - this.validate(); - } - - public get components(): ComponentsDataWrapper { - return this._components; - } - - public set components(components: ComponentsDataWrapper) { - this._components = components; - this.validate(); - } - - public get lastExecutedCommand(): string { - return this._lastExecutedCommand; - } - - private set lastExecutedCommand(lastExecutedCommand: string) { - this._lastExecutedCommand = lastExecutedCommand; - this.validate(); - } - - public get commandHistory(): string[] { - return this._commandHistory; - } - - private set commandHistory(commandHistory: string[]) { - this._commandHistory = commandHistory; - this.validate(); - } - - public get flags() { - return this._flags; - } - - //! -------- Utilities -------- // - - public static fromConfigmap(configManager: ConfigManager, configMap: ConfigMap): RemoteConfigDataWrapper { - const data: any = yaml.parse(configMap.data['remote-config-data']); - - return new RemoteConfigDataWrapper({ - metadata: RemoteConfigMetadata.fromObject(data.metadata), - components: ComponentsDataWrapper.fromObject(data.components), - clusters: data.clusters, - commandHistory: data.commandHistory, - lastExecutedCommand: data.lastExecutedCommand, - flags: CommonFlagsDataWrapper.fromObject(configManager, data.flags), - }); - } - - public validate(): void { - if (!this._version || typeof this._version !== 'string') { - throw new SoloError(`Invalid remote config version: ${this._version}`); - } - - if (!this.metadata || !(this.metadata instanceof RemoteConfigMetadata)) { - throw new SoloError(`Invalid remote config metadata: ${this.metadata}`); - } - - if (!this.lastExecutedCommand || typeof this.lastExecutedCommand !== 'string') { - throw new SoloError(`Invalid remote config last executed command: ${this.lastExecutedCommand}`); - } - - if (!Array.isArray(this.commandHistory) || this.commandHistory.some(c => typeof c !== 'string')) { - throw new SoloError(`Invalid remote config command history: ${this.commandHistory}`); - } - - for (const [clusterReference, cluster] of Object.entries(this.clusters)) { - if (!clusterReference || typeof clusterReference !== 'string') { - throw new SoloError(`Invalid remote config cluster-ref: ${clusterReference}`); - } - - if (!cluster) { - throw new SoloError(`No cluster info is found for cluster-ref: ${clusterReference}`); - } - - if (!cluster.name || typeof cluster.name !== 'string') { - throw new SoloError(`Invalid remote config cluster name: ${cluster.name} for cluster-ref: ${clusterReference}`); - } - - if (!cluster.namespace || typeof cluster.namespace !== 'string') { - throw new SoloError( - `Invalid remote config namespace: ${cluster.namespace} for cluster-ref: ${clusterReference}`, - ); - } - } - } - - public toObject(): RemoteConfigDataStruct { - return { - metadata: this.metadata.toObject(), - version: this.version, - clusters: Cluster.toClustersMapObject(this.clusters), - components: this.components.toObject(), - commandHistory: this.commandHistory, - lastExecutedCommand: this.lastExecutedCommand, - flags: this.flags.toObject(), - }; - } -} diff --git a/src/core/config/remote/remote-config-data.ts b/src/core/config/remote/remote-config-data.ts deleted file mode 100644 index c10012d9a..000000000 --- a/src/core/config/remote/remote-config-data.ts +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {type RemoteConfigMetadata} from './metadata.js'; -import {type ComponentsDataWrapper} from './components-data-wrapper.js'; -import {type CommonFlagsDataWrapper} from './common-flags-data-wrapper.js'; -import {type Cluster} from './cluster.js'; -import {type ClusterReference} from '../../../types/index.js'; - -export interface RemoteConfigData { - metadata: RemoteConfigMetadata; - clusters: Record; - components: ComponentsDataWrapper; - lastExecutedCommand: string; - commandHistory: string[]; - flags: CommonFlagsDataWrapper; -} diff --git a/src/core/config/remote/remote-config-manager.ts b/src/core/config/remote/remote-config-manager.ts deleted file mode 100644 index d12382e59..000000000 --- a/src/core/config/remote/remote-config-manager.ts +++ /dev/null @@ -1,550 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import * as constants from '../../constants.js'; -import {SoloError} from '../../errors/solo-error.js'; -import {RemoteConfigDataWrapper} from './remote-config-data-wrapper.js'; -import {RemoteConfigMetadata} from './metadata.js'; -import {Flags as flags} from '../../../commands/flags.js'; -import * as yaml from 'yaml'; -import {ComponentsDataWrapper} from './components-data-wrapper.js'; -import {RemoteConfigValidator} from './remote-config-validator.js'; -import {type K8Factory} from '../../../integration/kube/k8-factory.js'; -import { - type ClusterReference, - type ClusterReferences, - type Context, - type DeploymentName, - type NamespaceNameAsString, - type Optional, - type Version, -} from '../../../types/index.js'; -import {type SoloLogger} from '../../logging/solo-logger.js'; -import {type ConfigManager} from '../../config-manager.js'; -import {inject, injectable} from 'tsyringe-neo'; -import {patchInject} from '../../dependency-injection/container-helper.js'; -import {ErrorMessages} from '../../error-messages.js'; -import {CommonFlagsDataWrapper} from './common-flags-data-wrapper.js'; -import {type AnyObject, type ArgvStruct, type NodeAlias, type NodeAliases} from '../../../types/aliases.js'; -import {type NamespaceName} from '../../../types/namespace/namespace-name.js'; -import {InjectTokens} from '../../dependency-injection/inject-tokens.js'; -import {Cluster} from './cluster.js'; -import {ConsensusNode} from '../../model/consensus-node.js'; -import {Templates} from '../../templates.js'; -import {promptTheUserForDeployment, resolveNamespaceFromDeployment} from '../../resolvers.js'; -import {type ConfigMap} from '../../../integration/kube/resources/config-map/config-map.js'; -import {getSoloVersion} from '../../../../version.js'; -import {LocalConfigRuntimeState} from '../../../business/runtime-state/config/local/local-config-runtime-state.js'; -import {DeploymentStates} from './enumerations/deployment-states.js'; -import {FacadeArray} from '../../../business/runtime-state/collection/facade-array.js'; -import {StringFacade} from '../../../business/runtime-state/facade/string-facade.js'; - -/** - * Uses Kubernetes ConfigMaps to manage the remote configuration data by creating, loading, modifying, - * and saving the configuration data to and from a Kubernetes cluster. - */ -@injectable() -export class RemoteConfigManager { - /** Stores the loaded remote configuration data. */ - private remoteConfig: Optional; - - /** - * @param k8Factory - The Kubernetes client used for interacting with ConfigMaps. - * @param logger - The logger for recording activity and errors. - * @param localConfig - Local configuration for the remote config. - * @param configManager - Manager to retrieve application flags and settings. - */ - public constructor( - @inject(InjectTokens.K8Factory) private readonly k8Factory?: K8Factory, - @inject(InjectTokens.SoloLogger) private readonly logger?: SoloLogger, - @inject(InjectTokens.LocalConfigRuntimeState) private readonly localConfig?: LocalConfigRuntimeState, - @inject(InjectTokens.ConfigManager) private readonly configManager?: ConfigManager, - ) { - this.k8Factory = patchInject(k8Factory, InjectTokens.K8Factory, this.constructor.name); - this.logger = patchInject(logger, InjectTokens.SoloLogger, this.constructor.name); - this.localConfig = patchInject(localConfig, InjectTokens.LocalConfigRuntimeState, this.constructor.name); - this.configManager = patchInject(configManager, InjectTokens.ConfigManager, this.constructor.name); - } - - /* ---------- Getters ---------- */ - - public get currentCluster(): ClusterReference { - return this.k8Factory.default().clusters().readCurrent(); - } - - /** @returns the components data wrapper cloned */ - public get components(): ComponentsDataWrapper { - return this.remoteConfig.components.clone(); - } - - /** - * @returns the remote configuration data's clusters cloned - */ - public get clusters(): Record { - return structuredClone(this.remoteConfig.clusters); - } - - /* ---------- Readers and Modifiers ---------- */ - - /** - * Modifies the loaded remote configuration data using a provided callback function. - * The callback operates on the configuration data, which is then saved to the cluster. - * - * @param callback - an async function that modifies the remote configuration data. - * @throws if the configuration is not loaded before modification, will throw a SoloError {@link SoloError} - */ - public async modify(callback: (remoteConfig: RemoteConfigDataWrapper) => Promise): Promise { - if (!this.remoteConfig) { - throw new SoloError('Attempting to modify remote config without loading it first'); - } - - // Call the callback function to modify the remote config - await callback(this.remoteConfig); - - // Save the modified version of the remote config - await this.save(); - } - - /** - * Creates a new remote configuration in the Kubernetes cluster. - * Gathers data from the local configuration and constructs a new ConfigMap - * entry in the cluster with initial command history and metadata. - */ - public async create( - argv: ArgvStruct, - state: DeploymentStates, - nodeAliases: NodeAliases, - namespace: NamespaceName, - deployment: DeploymentName, - clusterReference: ClusterReference, - context: Context, - dnsBaseDomain: string, - dnsConsensusNodePattern: string, - ): Promise { - const clusters: Record = { - [clusterReference]: new Cluster( - clusterReference, - namespace.name, - deployment, - dnsBaseDomain, - dnsConsensusNodePattern, - ), - }; - - const lastUpdatedAt = new Date(); - const userIdentity = this.localConfig.configuration.userIdentity.encapsulatedObject; - const soloVersion = getSoloVersion(); - const currentCommand = argv._.join(' '); - - this.remoteConfig = new RemoteConfigDataWrapper({ - clusters, - metadata: new RemoteConfigMetadata(namespace.name, deployment, state, lastUpdatedAt, userIdentity, soloVersion), - commandHistory: [currentCommand], - lastExecutedCommand: currentCommand, - components: ComponentsDataWrapper.initializeWithNodes(nodeAliases, clusterReference, namespace.name), - flags: await CommonFlagsDataWrapper.initialize(this.configManager, argv), - }); - - await this.createConfigMap(context); - } - - /** - * Saves the currently loaded remote configuration data to the Kubernetes cluster. - * @throws {@link SoloError} if there is no remote configuration data to save. - */ - private async save(): Promise { - if (!this.remoteConfig) { - throw new SoloError('Attempted to save remote config without data'); - } - - await this.replaceConfigMap(); - } - - /** - * Loads the remote configuration from the Kubernetes cluster if it exists. - * @param namespace - The namespace to search for the ConfigMap. - * @param context - The context to use for the Kubernetes client. - * @returns true if the configuration is loaded successfully. - */ - private async load(namespace?: NamespaceName, context?: Context): Promise { - if (this.remoteConfig) { - return; - } - try { - const configMap = await this.getConfigMap(namespace, context); - - this.remoteConfig = RemoteConfigDataWrapper.fromConfigmap(this.configManager, configMap); - } catch (error) { - throw new SoloError('Failed to load remote config from cluster', error); - } - } - - /** - * Loads the remote configuration, performs a validation and returns it - * @returns RemoteConfigDataWrapper - */ - public async get(context?: Context): Promise { - const namespace = this.configManager.getFlag(flags.namespace) ?? (await this.getNamespace()); - - await this.load(namespace, context); - try { - await RemoteConfigValidator.validateComponents( - namespace, - this.remoteConfig.components, - this.k8Factory, - this.localConfig, - false, - ); - } catch (error) { - throw new SoloError( - ErrorMessages.REMOTE_CONFIG_IS_INVALID(this.k8Factory.getK8(context).clusters().readCurrent()), - error, - ); - } - return this.remoteConfig; - } - - /** Unload the remote config from the remote config manager. */ - public unload(): void { - this.remoteConfig = undefined; - } - - public static compare(remoteConfig1: RemoteConfigDataWrapper, remoteConfig2: RemoteConfigDataWrapper): boolean { - // Compare clusters - const clusters1 = Object.keys(remoteConfig1.clusters); - const clusters2 = Object.keys(remoteConfig2.clusters); - if (clusters1.length !== clusters2.length) { - return false; - } - - for (const index in clusters1) { - if (clusters1[index] !== clusters2[index]) { - return false; - } - } - - return true; - } - - /* ---------- Listr Task Builders ---------- */ - - /** - * Performs the loading of the remote configuration. - * Checks if the configuration is already loaded, otherwise loads and adds the command to history. - * - * @param argv - arguments containing command input for historical reference. - * @param validate - whether to validate the remote configuration. - * @param [skipConsensusNodesValidation] - whether or not to validate the consensusNodes - */ - public async loadAndValidate( - argv: {_: string[]} & AnyObject, - validate: boolean = true, - skipConsensusNodesValidation: boolean = true, - ): Promise { - await this.setDefaultNamespaceAndDeploymentIfNotSet(argv); - this.setDefaultContextIfNotSet(); - - await this.load(); - - this.logger.info('Remote config loaded'); - if (!validate) { - return; - } - - await RemoteConfigValidator.validateComponents( - this.configManager.getFlag(flags.namespace), - this.remoteConfig.components, - this.k8Factory, - this.localConfig, - skipConsensusNodesValidation, - ); - - const currentCommand = argv._?.join(' '); - const commandArguments = flags.stringifyArgv(argv); - - this.remoteConfig!.addCommandToHistory( - `Executed by ${this.localConfig.configuration.userIdentity.name}: ${currentCommand} ${commandArguments}`.trim(), - ); - - this.populateVersionsInMetadata(argv); - - await this.remoteConfig.flags.handleFlags(argv); - - await this.save(); - } - - private populateVersionsInMetadata(argv: AnyObject): void { - const command: string = argv._[0]; - const subcommand: string = argv._[1]; - - const isCommandUsingSoloChartVersionFlag = - (command === 'network' && subcommand === 'deploy') || - (command === 'network' && subcommand === 'refresh') || - (command === 'node' && subcommand === 'update') || - (command === 'node' && subcommand === 'update-execute') || - (command === 'node' && subcommand === 'add') || - (command === 'node' && subcommand === 'add-execute') || - (command === 'node' && subcommand === 'delete') || - (command === 'node' && subcommand === 'delete-execute'); - - if (argv[flags.soloChartVersion.name]) { - this.remoteConfig.metadata.soloChartVersion = argv[flags.soloChartVersion.name] as Version; - } else if (isCommandUsingSoloChartVersionFlag) { - this.remoteConfig.metadata.soloChartVersion = flags.soloChartVersion.definition.defaultValue as Version; - } - - const isCommandUsingReleaseTagVersionFlag = - (command === 'node' && subcommand !== 'keys' && subcommand !== 'logs' && subcommand !== 'states') || - (command === 'network' && subcommand === 'deploy'); - - if (argv[flags.releaseTag.name]) { - this.remoteConfig.metadata.hederaPlatformVersion = argv[flags.releaseTag.name] as Version; - } else if (isCommandUsingReleaseTagVersionFlag) { - this.remoteConfig.metadata.hederaPlatformVersion = flags.releaseTag.definition.defaultValue as Version; - } - - if (argv[flags.mirrorNodeVersion.name]) { - this.remoteConfig.metadata.hederaMirrorNodeChartVersion = argv[flags.mirrorNodeVersion.name] as Version; - } else if (command === 'mirror-node' && subcommand === 'deploy') { - this.remoteConfig.metadata.hederaMirrorNodeChartVersion = flags.mirrorNodeVersion.definition - .defaultValue as Version; - } - - if (argv[flags.explorerVersion.name]) { - this.remoteConfig.metadata.explorerChartVersion = argv[flags.explorerVersion.name] as Version; - } else if (command === 'explorer' && subcommand === 'deploy') { - this.remoteConfig.metadata.explorerChartVersion = flags.explorerVersion.definition.defaultValue as Version; - } - - if (argv[flags.relayReleaseTag.name]) { - this.remoteConfig.metadata.hederaJsonRpcRelayChartVersion = argv[flags.relayReleaseTag.name] as Version; - } else if (command === 'relay' && subcommand === 'deploy') { - this.remoteConfig.metadata.hederaJsonRpcRelayChartVersion = flags.relayReleaseTag.definition - .defaultValue as Version; - } - } - - /* ---------- Utilities ---------- */ - - /** Empties the component data inside the remote config */ - public async deleteComponents(): Promise { - await this.modify(async remoteConfig => { - remoteConfig.components = ComponentsDataWrapper.initializeEmpty(); - }); - } - - public isLoaded(): boolean { - return !!this.remoteConfig; - } - - /** - * Retrieves the ConfigMap containing the remote configuration from the Kubernetes cluster. - * - * @param namespace - The namespace to search for the ConfigMap. - * @param context - The context to use for the Kubernetes client. - * @returns the remote configuration data. - * @throws if the ConfigMap could not be read and the error is not a 404 status, will throw a SoloError {@link SoloError} - */ - public async getConfigMap(namespace?: NamespaceName, context?: Context): Promise { - if (!namespace) { - namespace = await this.getNamespace(); - } - if (!context) { - context = this.configManager.getFlag(flags.context) ?? this.getContextForFirstCluster(); - } - - try { - const configMap = await this.k8Factory - .getK8(context) - .configMaps() - .read(namespace, constants.SOLO_REMOTE_CONFIGMAP_NAME); - - if (!configMap) { - throw new SoloError(`Remote config ConfigMap not found for namespace: ${namespace}, context: ${context}`); - } - - return configMap; - } catch (error) { - throw new SoloError( - `Failed to read remote config from cluster for namespace: ${namespace}, context: ${context}`, - error, - ); - } - } - - /** - * Creates a new ConfigMap entry in the Kubernetes cluster with the remote configuration data. - */ - public async createConfigMap(context?: Context): Promise { - const namespace = await this.getNamespace(); - const name = constants.SOLO_REMOTE_CONFIGMAP_NAME; - const labels = constants.SOLO_REMOTE_CONFIGMAP_LABELS; - const data = {'remote-config-data': yaml.stringify(this.remoteConfig.toObject())}; - - await this.k8Factory.getK8(context).configMaps().create(namespace, name, labels, data); - } - - /** - * Replaces an existing ConfigMap in the Kubernetes cluster with the current remote configuration data. - */ - private async replaceConfigMap(): Promise { - const namespace = await this.getNamespace(); - const name = constants.SOLO_REMOTE_CONFIGMAP_NAME; - const labels = constants.SOLO_REMOTE_CONFIGMAP_LABELS; - const data = {'remote-config-data': yaml.stringify(this.remoteConfig.toObject())}; - - const deploymentName = this.configManager.getFlag(flags.deployment); - - if (!deploymentName) { - throw new SoloError('Failed to get deployment'); - } - - const clusterReferences: FacadeArray = - this.localConfig.configuration.deploymentByName(deploymentName).clusters; - - if (!clusterReferences) { - throw new SoloError(`Failed to get get cluster refs from local config for deployment ${deploymentName}`); - } - - const contexts: Context[] = clusterReferences.map((clusterReference): string => - this.localConfig.configuration.clusterRefs.get(clusterReference.toString())?.toString(), - ); - - await Promise.all( - contexts.map(context => this.k8Factory.getK8(context).configMaps().replace(namespace, name, labels, data)), - ); - } - - private async setDefaultNamespaceAndDeploymentIfNotSet(argv: AnyObject): Promise { - if (this.configManager.hasFlag(flags.namespace)) { - return; - } - - // TODO: Current quick fix for commands where namespace is not passed - let deploymentName = this.configManager.getFlag(flags.deployment); - let currentDeployment = this.localConfig.configuration.deploymentByName(deploymentName); - - if (!deploymentName) { - deploymentName = await promptTheUserForDeployment(this.configManager); - currentDeployment = this.localConfig.configuration.deploymentByName(deploymentName); - // TODO: Fix once we have the DataManager, - // without this the user will be prompted a second time for the deployment - // TODO: we should not be mutating argv - argv[flags.deployment.name] = deploymentName; - this.logger.warn( - `Deployment name not found in flags or local config, setting it in argv and config manager to: ${deploymentName}`, - ); - this.configManager.setFlag(flags.deployment, deploymentName); - } - - if (!currentDeployment) { - throw new SoloError(`Selected deployment name is not set in local config - ${deploymentName}`); - } - - const namespace: NamespaceNameAsString = currentDeployment.namespace; - - this.logger.warn(`Namespace not found in flags, setting it to: ${namespace}`); - this.configManager.setFlag(flags.namespace, namespace); - argv[flags.namespace.name] = namespace; - } - - private setDefaultContextIfNotSet(): void { - if (this.configManager.hasFlag(flags.context)) { - return; - } - - const context: Context = this.getContextForFirstCluster() ?? this.k8Factory.default().contexts().readCurrent(); - - if (!context) { - throw new SoloError("Context is not passed and default one can't be acquired"); - } - - this.logger.warn(`Context not found in flags, setting it to: ${context}`); - this.configManager.setFlag(flags.context, context); - } - - /** - * Retrieves the namespace value from the configuration manager's flags. - * @returns string - The namespace value if set. - */ - private async getNamespace(): Promise { - return await resolveNamespaceFromDeployment(this.localConfig, this.configManager); - } - - //* Common Commands - - /** - * Get the consensus nodes from the remoteConfigManager and use the localConfig to get the context - * @returns an array of ConsensusNode objects - */ - public getConsensusNodes(): ConsensusNode[] { - if (!this.isLoaded()) { - throw new SoloError('Remote configuration is not loaded, and was expected to be loaded'); - } - - const consensusNodes: ConsensusNode[] = []; - - for (const node of Object.values(this.components.consensusNodes)) { - const cluster: Cluster = this.clusters[node.cluster]; - const context: Context = this.localConfig.configuration.clusterRefs.get(node.cluster)?.toString(); - - consensusNodes.push( - new ConsensusNode( - node.name as NodeAlias, - node.nodeId, - node.namespace, - node.cluster, - context, - cluster.dnsBaseDomain, - cluster.dnsConsensusNodePattern, - Templates.renderConsensusNodeFullyQualifiedDomainName( - node.name as NodeAlias, - node.nodeId, - node.namespace, - node.cluster, - cluster.dnsBaseDomain, - cluster.dnsConsensusNodePattern, - ), - ), - ); - } - - // return the consensus nodes - return consensusNodes; - } - - /** - * Gets a list of distinct contexts from the consensus nodes. - * @returns an array of context strings. - */ - public getContexts(): Context[] { - return [...new Set(this.getConsensusNodes().map((node): Context => node.context))]; - } - - /** - * Gets a list of distinct cluster references from the consensus nodes. - * @returns an object of cluster references. - */ - public getClusterRefs(): ClusterReferences { - const nodes = this.getConsensusNodes(); - const accumulator: ClusterReferences = new Map(); - - for (const node of nodes) { - accumulator.set(node.cluster, node.context); - } - - return accumulator; - } - - private getContextForFirstCluster(): string { - const deploymentName = this.configManager.getFlag(flags.deployment); - - const clusterReference: ClusterReference = - this.localConfig.configuration.deploymentByName(deploymentName).clusters[0]; - - const context: Context = this.localConfig.configuration.clusterRefs.get(clusterReference)?.toString(); - - this.logger.debug(`Using context ${context} for cluster ${clusterReference} for deployment ${deploymentName}`); - - return context; - } -} diff --git a/src/core/config/remote/remote-config-validator.ts b/src/core/config/remote/remote-config-validator.ts index 9d6849baa..e8e5a6ea0 100644 --- a/src/core/config/remote/remote-config-validator.ts +++ b/src/core/config/remote/remote-config-validator.ts @@ -3,196 +3,153 @@ import * as constants from '../../constants.js'; import {SoloError} from '../../errors/solo-error.js'; import {type K8Factory} from '../../../integration/kube/k8-factory.js'; -import {type ComponentsDataWrapper} from './components-data-wrapper.js'; -import {type BaseComponent} from './components/base-component.js'; import {type NamespaceName} from '../../../types/namespace/namespace-name.js'; import {type Pod} from '../../../integration/kube/resources/pod/pod.js'; import {type LocalConfigRuntimeState} from '../../../business/runtime-state/config/local/local-config-runtime-state.js'; -import {ConsensusNodeStates} from './enumerations/consensus-node-states.js'; import {type Context} from '../../../types/index.js'; +import {type NodeAlias} from '../../../types/aliases.js'; +import {Templates} from '../../templates.js'; +import {DeploymentPhase} from '../../../data/schema/model/remote/deployment-phase.js'; +import {type ConsensusNodeStateSchema} from '../../../data/schema/model/remote/state/consensus-node-state-schema.js'; +import {type BaseStateSchema} from '../../../data/schema/model/remote/state/base-state-schema.js'; +import {inject, injectable} from 'tsyringe-neo'; +import {type RemoteConfigRuntimeStateApi} from '../../../business/runtime-state/api/remote-config-runtime-state-api.js'; +import {patchInject} from '../../dependency-injection/container-helper.js'; +import {InjectTokens} from '../../dependency-injection/inject-tokens.js'; +import {RemoteConfigValidatorApi} from './api/remote-config-validator-api.js'; +import {DeploymentStateSchema} from '../../../data/schema/model/remote/deployment-state-schema.js'; /** * Static class is used to validate that components in the remote config * are present in the kubernetes cluster, and throw errors if there is mismatch. */ -export class RemoteConfigValidator { - /** - * Gathers and handles validation of all components. - * - * @param namespace - namespace to validate the components in. - * @param components - components to validate. - * @param k8Factory - to validate the elements. - * @param localConfig - to get the context from cluster - * @param skipConsensusNodes - whether to validate consensus nodes - */ - public static async validateComponents( - namespace: NamespaceName, - components: ComponentsDataWrapper, - k8Factory: K8Factory, - localConfig: LocalConfigRuntimeState, - skipConsensusNodes: boolean, - ): Promise { - await Promise.all([ - ...RemoteConfigValidator.validateRelays(namespace, components, k8Factory, localConfig), - ...RemoteConfigValidator.validateHaProxies(namespace, components, k8Factory, localConfig), - ...RemoteConfigValidator.validateMirrorNodes(namespace, components, k8Factory, localConfig), - ...RemoteConfigValidator.validateEnvoyProxies(namespace, components, k8Factory, localConfig), - ...RemoteConfigValidator.validateMirrorNodeExplorers(namespace, components, k8Factory, localConfig), - ...RemoteConfigValidator.validateBlockNodes(namespace, components, k8Factory, localConfig), - ...(skipConsensusNodes - ? [] - : RemoteConfigValidator.validateConsensusNodes(namespace, components, k8Factory, localConfig)), - ]); +@injectable() +export class RemoteConfigValidator implements RemoteConfigValidatorApi { + public constructor( + @inject(InjectTokens.K8Factory) private readonly k8Factory?: K8Factory, + @inject(InjectTokens.LocalConfigRuntimeState) private readonly localConfig?: LocalConfigRuntimeState, + ) { + this.k8Factory = patchInject(k8Factory, InjectTokens.K8Factory, this.constructor.name); + this.localConfig = patchInject(localConfig, InjectTokens.LocalConfigRuntimeState, this.constructor.name); } - private static validateRelays( - namespace: NamespaceName, - components: ComponentsDataWrapper, - k8Factory: K8Factory, - localConfig: LocalConfigRuntimeState, - ): Promise[] { - return Object.values(components.relays).map(async component => { - const context: Context = localConfig.configuration.clusterRefs.get(component.cluster)?.toString(); - const labels: string[] = [constants.SOLO_RELAY_LABEL]; - try { - const pods: Pod[] = await k8Factory.getK8(context).pods().list(namespace, labels); - - if (pods.length === 0) { - throw new Error('Pod not found'); - } // to return the generic error message - } catch (error) { - RemoteConfigValidator.throwValidationError('Relay', component, error); - } - }); + private static getRelayLabels(): string[] { + // TODO: + // https://github.com/hashgraph/solo/issues/1823 + // Add logic for selecting by specific label, + // when multiple instances can be deployed at the same time. + return [constants.SOLO_RELAY_LABEL]; } - private static validateHaProxies( - namespace: NamespaceName, - components: ComponentsDataWrapper, - k8Factory: K8Factory, - localConfig: LocalConfigRuntimeState, - ): Promise[] { - return Object.values(components.haProxies).map(async component => { - const context: Context = localConfig.configuration.clusterRefs.get(component.cluster)?.toString(); - const labels: string[] = [`app=${component.name}`]; - try { - const pods: Pod[] = await k8Factory.getK8(context).pods().list(namespace, labels); + private static getHaProxyLabels(component: BaseStateSchema): string[] { + const nodeAlias: NodeAlias = Templates.renderNodeAliasFromNumber(component.metadata.id + 1); + return [`app=haproxy-${nodeAlias}`, 'solo.hedera.com/type=haproxy']; + } - if (pods.length === 0) { - throw new Error('Pod not found'); - } // to return the generic error message - } catch (error) { - RemoteConfigValidator.throwValidationError('HaProxy', component, error); - } - }); + private static getMirrorNodeLabels(): string[] { + // TODO: + // https://github.com/hashgraph/solo/issues/1823 + // Add logic for selecting by specific label, + // when multiple instances can be deployed at the same time. + return constants.SOLO_HEDERA_MIRROR_IMPORTER; } - private static validateMirrorNodes( - namespace: NamespaceName, - components: ComponentsDataWrapper, - k8Factory: K8Factory, - localConfig: LocalConfigRuntimeState, - ): Promise[] { - return Object.values(components.mirrorNodes).map(async component => { - const context: Context = localConfig.configuration.clusterRefs.get(component.cluster)?.toString(); - const labels: string[] = constants.SOLO_HEDERA_MIRROR_IMPORTER; - try { - const pods: Pod[] = await k8Factory.getK8(context).pods().list(namespace, labels); + private static getEnvoyProxyLabels(component: BaseStateSchema): string[] { + const nodeAlias: NodeAlias = Templates.renderNodeAliasFromNumber(component.metadata.id + 1); + return [`solo.hedera.com/node-name=${nodeAlias}`, 'solo.hedera.com/type=envoy-proxy']; + } - if (pods.length === 0) { - throw new Error('Pod not found'); - } // to return the generic error message - } catch (error) { - RemoteConfigValidator.throwValidationError('Mirror node', component, error); - } - }); + private static getMirrorNodeExplorerLabels(): string[] { + // TODO: + // https://github.com/hashgraph/solo/issues/1823 + // Add logic for selecting by specific label, + // when multiple instances can be deployed at the same time. + return [constants.SOLO_EXPLORER_LABEL]; } - private static validateEnvoyProxies( - namespace: NamespaceName, - components: ComponentsDataWrapper, - k8Factory: K8Factory, - localConfig: LocalConfigRuntimeState, - ): Promise[] { - return Object.values(components.envoyProxies).map(async component => { - const context: Context = localConfig.configuration.clusterRefs.get(component.cluster)?.toString(); - const labels: string[] = [`app=${component.name}`]; - try { - const pods: Pod[] = await k8Factory.getK8(context).pods().list(namespace, labels); + private static getConsensusNodeLabels(component: BaseStateSchema): string[] { + return [`app=network-${Templates.renderNodeAliasFromNumber(component.metadata.id + 1)}`]; + } - if (pods.length === 0) { - throw new Error('Pod not found'); - } // to return the generic error message - } catch (error) { - RemoteConfigValidator.throwValidationError('Envoy proxy', component, error); - } - }); + private static consensusNodeSkipConditionCallback(nodeComponent: ConsensusNodeStateSchema): boolean { + return ( + nodeComponent.metadata.phase === DeploymentPhase.REQUESTED || + nodeComponent.metadata.phase === DeploymentPhase.STOPPED + ); } - private static validateConsensusNodes( + private static componentValidationsMapping: Record< + string, + { + getLabelsCallback: (component: BaseStateSchema) => string[]; + displayName: string; + skipCondition?: (component: BaseStateSchema) => boolean; + } + > = { + relays: { + displayName: 'Relay', + getLabelsCallback: RemoteConfigValidator.getRelayLabels, + }, + haProxies: { + displayName: 'HaProxy', + getLabelsCallback: RemoteConfigValidator.getHaProxyLabels, + }, + mirrorNodes: { + displayName: 'Mirror node', + getLabelsCallback: RemoteConfigValidator.getMirrorNodeLabels, + }, + envoyProxies: { + displayName: 'Envoy proxy', + getLabelsCallback: RemoteConfigValidator.getEnvoyProxyLabels, + }, + mirrorNodeExplorers: { + displayName: 'Mirror node explorer', + getLabelsCallback: RemoteConfigValidator.getMirrorNodeExplorerLabels, + }, + consensusNodes: { + displayName: 'Consensus node', + getLabelsCallback: RemoteConfigValidator.getConsensusNodeLabels, + skipCondition: RemoteConfigValidator.consensusNodeSkipConditionCallback, + }, + }; + + public async validateComponents( namespace: NamespaceName, - components: ComponentsDataWrapper, - k8Factory: K8Factory, - localConfig: LocalConfigRuntimeState, - ): Promise[] { - return Object.values(components.consensusNodes).map(async component => { - if (component.state === ConsensusNodeStates.REQUESTED || component.state === ConsensusNodeStates.NON_DEPLOYED) { - return; - } - - const context: Context = localConfig.configuration.clusterRefs.get(component.cluster)?.toString(); - const labels: string[] = [`app=network-${component.name}`]; - try { - const pods: Pod[] = await k8Factory.getK8(context).pods().list(namespace, labels); + skipConsensusNodes: boolean, + state: Readonly, + ): Promise { + const validationPromises: Promise[] = Object.entries(RemoteConfigValidator.componentValidationsMapping) + .filter(([key]) => key !== 'consensusNodes' || !skipConsensusNodes) + .flatMap(([key, {getLabelsCallback, displayName, skipCondition}]): Promise[] => + this.validateComponentGroup(namespace, state[key], getLabelsCallback, displayName, skipCondition), + ); - if (pods.length === 0) { - throw new Error('Pod not found'); - } // to return the generic error message - } catch (error) { - RemoteConfigValidator.throwValidationError('Consensus node', component, error); - } - }); + await Promise.all(validationPromises); } - private static validateMirrorNodeExplorers( + private validateComponentGroup( namespace: NamespaceName, - components: ComponentsDataWrapper, - k8Factory: K8Factory, - localConfig: LocalConfigRuntimeState, + state: Record, + getLabelsCallback: (component: BaseStateSchema) => string[], + displayName: string, + skipCondition?: (component: BaseStateSchema) => boolean, ): Promise[] { - return Object.values(components.mirrorNodeExplorers).map(async component => { - const context: Context = localConfig.configuration.clusterRefs.get(component.cluster)?.toString(); - const labels: string[] = [constants.SOLO_EXPLORER_LABEL]; - - try { - const pods: Pod[] = await k8Factory.getK8(context).pods().list(namespace, labels); - - if (pods.length === 0) { - throw new Error('Pod not found'); - } // to return the generic error message - } catch (error) { - RemoteConfigValidator.throwValidationError('Mirror node explorer', component, error); + return Object.values(state).map(async (component): Promise => { + if (skipCondition?.(component)) { + return; } - }); - } - private static validateBlockNodes( - namespace: NamespaceName, - components: ComponentsDataWrapper, - k8Factory: K8Factory, - localConfig: LocalConfigRuntimeState, - ): Promise[] { - return Object.values(components.blockNodes).map(async component => { - const context: Context = localConfig.configuration.clusterRefs[component.cluster]; - const labels: string[] = [constants.SOLO_EXPLORER_LABEL]; // TODO: ADD BLOCK SELECT + const context: Context = this.localConfig.configuration.clusterRefs.get(component.metadata.cluster)?.toString(); + const labels: string[] = getLabelsCallback(component); + try { - const pods: Pod[] = await k8Factory.getK8(context).pods().list(namespace, labels); + const pods: Pod[] = await this.k8Factory.getK8(context).pods().list(namespace, labels); if (pods.length === 0) { - throw new Error('Pod not found'); - } // to return the generic error message + throw new Error('Pod not found'); // to return the generic error message + } } catch (error) { - RemoteConfigValidator.throwValidationError('Block node', component, error); + throw RemoteConfigValidator.buildValidationError(displayName, component, error); } }); } @@ -200,16 +157,23 @@ export class RemoteConfigValidator { /** * Generic handler that throws errors. * - * @param type - name to display in error message + * @param displayName - name to display in error message * @param component - component which is not found in the cluster * @param error - original error for the kube client */ - private static throwValidationError(type: string, component: BaseComponent, error: Error | unknown): never { - throw new SoloError( - `${type} in remote config with name ${component.name} ` + - `was not found in namespace: ${component.namespace}, cluster: ${component.cluster}`, - error, - {component: component.toObject()}, + private static buildValidationError( + displayName: string, + component: BaseStateSchema, + error: Error | unknown, + ): SoloError { + return new SoloError(RemoteConfigValidator.buildValidationErrorMessage(displayName, component), error, component); + } + + public static buildValidationErrorMessage(displayName: string, component: BaseStateSchema): string { + return ( + `${displayName} in remote config with id ${component.metadata.id} was not found in ` + + `namespace: ${component.metadata.namespace}, ` + + `cluster: ${component.metadata.cluster}` ); } } diff --git a/src/core/constants.ts b/src/core/constants.ts index d31f720ae..b99fc138f 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -24,6 +24,7 @@ export const SOLO_REMOTE_CONFIGMAP_NAME = 'solo-remote-config'; export const SOLO_REMOTE_CONFIGMAP_LABELS = {'solo.hedera.com/type': 'remote-config'}; export const SOLO_REMOTE_CONFIG_MAX_COMMAND_IN_HISTORY = 50; export const SOLO_REMOTE_CONFIGMAP_LABEL_SELECTOR = 'solo.hedera.com/type=remote-config'; +export const SOLO_REMOTE_CONFIGMAP_DATA_KEY = 'remote-config-data'; export const NODE_COPY_CONCURRENT = Number(process.env.NODE_COPY_CONCURRENT) || 4; export const SKIP_NODE_PING = Boolean(process.env.SKIP_NODE_PING) || false; export const DEFAULT_LOCK_ACQUIRE_ATTEMPTS = +process.env.SOLO_LEASE_ACQUIRE_ATTEMPTS || 10; diff --git a/src/core/dependency-injection/container-init.ts b/src/core/dependency-injection/container-init.ts index 4755df2e1..0cd877c7e 100644 --- a/src/core/dependency-injection/container-init.ts +++ b/src/core/dependency-injection/container-init.ts @@ -16,7 +16,6 @@ import {ProfileManager} from '../profile-manager.js'; import {IntervalLockRenewalService} from '../lock/interval-lock-renewal.js'; import {LockManager} from '../lock/lock-manager.js'; import {CertificateManager} from '../certificate-manager.js'; -import {RemoteConfigManager} from '../config/remote/remote-config-manager.js'; import os from 'node:os'; import * as version from '../../../version.js'; import {NetworkNodes} from '../network-nodes.js'; @@ -30,6 +29,7 @@ import {NodeCommandTasks} from '../../commands/node/tasks.js'; import {ClusterCommandConfigs} from '../../commands/cluster/configs.js'; import {NodeCommandConfigs} from '../../commands/node/configs.js'; import {ErrorHandler} from '../error-handler.js'; +import {ClassToObjectMapper} from '../../data/mapper/impl/class-to-object-mapper.js'; import {HelmExecutionBuilder} from '../../integration/helm/execution/helm-execution-builder.js'; import {DefaultHelmClient} from '../../integration/helm/impl/default-helm-client.js'; import {HelpRenderer} from '../help-renderer.js'; @@ -51,7 +51,9 @@ import {ValueContainer} from './value-container.js'; import {BlockNodeCommand} from '../../commands/block-node.js'; import {LocalConfigRuntimeState} from '../../business/runtime-state/config/local/local-config-runtime-state.js'; import {LocalConfigSource} from '../../data/configuration/impl/local-config-source.js'; -import {ClassToObjectMapper} from '../../data/mapper/impl/class-to-object-mapper.js'; +import {RemoteConfigRuntimeState} from '../../business/runtime-state/config/remote/remote-config-runtime-state.js'; +import {ComponentFactory} from '../config/remote/component-factory.js'; +import {RemoteConfigValidator} from '../config/remote/remote-config-validator.js'; export type InstanceOverrides = Map; @@ -115,7 +117,7 @@ export class Container { new SingletonContainer(InjectTokens.CertificateManager, CertificateManager), new SingletonContainer(InjectTokens.LocalConfigRuntimeState, LocalConfigRuntimeState), new SingletonContainer(InjectTokens.LocalConfigSource, LocalConfigSource), - new SingletonContainer(InjectTokens.RemoteConfigManager, RemoteConfigManager), + new SingletonContainer(InjectTokens.RemoteConfigRuntimeState, RemoteConfigRuntimeState), new SingletonContainer(InjectTokens.ClusterChecks, ClusterChecks), new SingletonContainer(InjectTokens.NetworkNodes, NetworkNodes), new SingletonContainer(InjectTokens.Middlewares, Middlewares), @@ -139,6 +141,8 @@ export class Container { new SingletonContainer(InjectTokens.NodeCommandConfigs, NodeCommandConfigs), new SingletonContainer(InjectTokens.ErrorHandler, ErrorHandler), new SingletonContainer(InjectTokens.ObjectMapper, ClassToObjectMapper), + new SingletonContainer(InjectTokens.ComponentFactory, ComponentFactory), + new SingletonContainer(InjectTokens.RemoteConfigValidator, RemoteConfigValidator), ]; const valueContainers: ValueContainer[] = [ diff --git a/src/core/dependency-injection/inject-tokens.ts b/src/core/dependency-injection/inject-tokens.ts index 2791b0dc1..bf5c5c276 100644 --- a/src/core/dependency-injection/inject-tokens.ts +++ b/src/core/dependency-injection/inject-tokens.ts @@ -4,6 +4,8 @@ * Dependency injection tokens */ export const InjectTokens = { + ComponentFactory: Symbol.for('ComponentFactory'), + RemoteConfigValidator: Symbol.for('RemoteConfigValidator'), LogLevel: Symbol.for('LogLevel'), DevelopmentMode: Symbol.for('DevelopmentMode'), OsPlatform: Symbol.for('OsPlatform'), @@ -29,7 +31,7 @@ export const InjectTokens = { KeyManager: Symbol.for('KeyManager'), ProfileManager: Symbol.for('ProfileManager'), CertificateManager: Symbol.for('CertificateManager'), - RemoteConfigManager: Symbol.for('RemoteConfigManager'), + RemoteConfigRuntimeState: Symbol.for('RemoteConfigRuntimeState'), ClusterChecks: Symbol.for('ClusterChecks'), NetworkNodes: Symbol.for('NetworkNodes'), AccountCommand: Symbol.for('AccountCommand'), diff --git a/src/core/dependency-managers/dependency-manager.ts b/src/core/dependency-managers/dependency-manager.ts index b4c8809fa..ff0813148 100644 --- a/src/core/dependency-managers/dependency-manager.ts +++ b/src/core/dependency-managers/dependency-manager.ts @@ -13,7 +13,7 @@ import {type SoloListrTask} from '../../types/index.js'; export class DependencyManager extends ShellRunner { private readonly depManagerMap: Map; - constructor(@inject(InjectTokens.HelmDependencyManager) helmDepManager?: HelmDependencyManager) { + public constructor(@inject(InjectTokens.HelmDependencyManager) helmDepManager?: HelmDependencyManager) { super(); this.depManagerMap = helmDepManager ? new Map().set(constants.HELM, helmDepManager) @@ -25,13 +25,12 @@ export class DependencyManager extends ShellRunner { * @param dep - is the name of the program * @param [shouldInstall] - Whether or not install the dependency if not installed */ - async checkDependency(dep: string, shouldInstall = true) { + public async checkDependency(dep: string, shouldInstall: boolean = true): Promise { this.logger.debug(`Checking for dependency: ${dep}`); - let status = false; - const manager = this.depManagerMap.get(dep); + let status: boolean = false; + const manager: HelmDependencyManager = this.depManagerMap.get(dep); if (manager) { - // @ts-ignore status = await manager.checkVersion(shouldInstall); } @@ -43,12 +42,12 @@ export class DependencyManager extends ShellRunner { return true; } - taskCheckDependencies(deps: string[]) { + public taskCheckDependencies(deps: string[]): SoloListrTask[] { return deps.map(dep => { return { title: `Check dependency: ${dep} [OS: ${os.platform()}, Release: ${os.release()}, Arch: ${os.arch()}]`, task: () => this.checkDependency(dep), - } as SoloListrTask; + }; }); } } diff --git a/src/core/helpers.ts b/src/core/helpers.ts index b2159108d..3757764a5 100644 --- a/src/core/helpers.ts +++ b/src/core/helpers.ts @@ -454,7 +454,7 @@ export function populateHelmArguments(valuesMapping: Record { if (!consensusNodes) { return undefined; diff --git a/src/core/middlewares.ts b/src/core/middlewares.ts index ca6f08ba5..546bb8467 100644 --- a/src/core/middlewares.ts +++ b/src/core/middlewares.ts @@ -7,8 +7,7 @@ import {type NamespaceName} from '../types/namespace/namespace-name.js'; import {type ConfigManager} from './config-manager.js'; import {type K8Factory} from '../integration/kube/k8-factory.js'; import {type SoloLogger} from './logging/solo-logger.js'; -import {type AnyObject} from '../types/aliases.js'; -import {type RemoteConfigManager} from './config/remote/remote-config-manager.js'; +import {type AnyObject, ArgvStruct} from '../types/aliases.js'; import {type ClusterReference} from './../types/index.js'; import {SoloError} from './errors/solo-error.js'; import {SilentBreak} from './errors/silent-break.js'; @@ -17,23 +16,20 @@ import {patchInject} from './dependency-injection/container-helper.js'; import {InjectTokens} from './dependency-injection/inject-tokens.js'; import {inject, injectable} from 'tsyringe-neo'; import {LocalConfigRuntimeState} from '../business/runtime-state/config/local/local-config-runtime-state.js'; +import {type RemoteConfigRuntimeStateApi} from '../business/runtime-state/api/remote-config-runtime-state-api.js'; @injectable() export class Middlewares { constructor( @inject(InjectTokens.ConfigManager) private readonly configManager: ConfigManager, - @inject(InjectTokens.RemoteConfigManager) private readonly remoteConfigManager: RemoteConfigManager, + @inject(InjectTokens.RemoteConfigRuntimeState) private readonly remoteConfig: RemoteConfigRuntimeStateApi, @inject(InjectTokens.K8Factory) private readonly k8Factory: K8Factory, @inject(InjectTokens.SoloLogger) private readonly logger: SoloLogger, @inject(InjectTokens.LocalConfigRuntimeState) private readonly localConfig: LocalConfigRuntimeState, @inject(InjectTokens.HelpRenderer) private readonly helpRenderer: HelpRenderer, ) { this.configManager = patchInject(configManager, InjectTokens.ConfigManager, this.constructor.name); - this.remoteConfigManager = patchInject( - remoteConfigManager, - InjectTokens.RemoteConfigManager, - this.constructor.name, - ); + this.remoteConfig = patchInject(remoteConfig, InjectTokens.RemoteConfigRuntimeState, this.constructor.name); this.k8Factory = patchInject(k8Factory, InjectTokens.K8Factory, this.constructor.name); this.logger = patchInject(logger, InjectTokens.SoloLogger, this.constructor.name); this.localConfig = patchInject(localConfig, InjectTokens.LocalConfigRuntimeState, this.constructor.name); @@ -46,7 +42,7 @@ export class Middlewares { /** * @param argv - listr Argv */ - return (argv: any) => { + return (argv: any): void => { if (!argv['help']) { return; } @@ -58,8 +54,8 @@ export class Middlewares { }; } - public setLoggerDevFlag() { - const logger = this.logger; + public setLoggerDevFlag(): (argv: ArgvStruct) => AnyObject { + const logger: SoloLogger = this.logger; /** * @param argv - listr Argv @@ -80,9 +76,9 @@ export class Middlewares { * @returns callback function to be executed from listr */ public processArgumentsAndDisplayHeader() { - const k8Factory = this.k8Factory; - const configManager = this.configManager; - const logger = this.logger; + const k8Factory: K8Factory = this.k8Factory; + const configManager: ConfigManager = this.configManager; + const logger: SoloLogger = this.logger; /** * @param argv - listr Argv @@ -144,7 +140,7 @@ export class Middlewares { * @returns callback function to be executed from listr */ public loadRemoteConfig() { - const remoteConfigManager = this.remoteConfigManager; + const remoteConfig = this.remoteConfig; const logger = this.logger; /** @@ -174,7 +170,7 @@ export class Middlewares { const skipConsensusNodeValidation = command === 'network' && subCommand === 'deploy'; if (!skip) { - await remoteConfigManager.loadAndValidate(argv, validateRemoteConfig, skipConsensusNodeValidation); + await remoteConfig.loadAndValidate(argv, validateRemoteConfig, skipConsensusNodeValidation); } return argv; diff --git a/src/core/profile-manager.ts b/src/core/profile-manager.ts index c3f7b262e..15069e7f3 100644 --- a/src/core/profile-manager.ts +++ b/src/core/profile-manager.ts @@ -26,11 +26,11 @@ import {NamespaceName} from '../types/namespace/namespace-name.js'; import {InjectTokens} from './dependency-injection/inject-tokens.js'; import {type ConsensusNode} from './model/consensus-node.js'; import {type K8Factory} from '../integration/kube/k8-factory.js'; -import {type RemoteConfigManager} from './config/remote/remote-config-manager.js'; import {type ClusterReference, DeploymentName, Realm, Shard} from './../types/index.js'; import {PathEx} from '../business/utils/path-ex.js'; import {AccountManager} from './account-manager.js'; import {LocalConfigRuntimeState} from '../business/runtime-state/config/local/local-config-runtime-state.js'; +import {type RemoteConfigRuntimeStateApi} from '../business/runtime-state/api/remote-config-runtime-state-api.js'; @injectable() export class ProfileManager { @@ -38,7 +38,7 @@ export class ProfileManager { private readonly configManager: ConfigManager; private readonly cacheDir: DirectoryPath; private readonly k8Factory: K8Factory; - private readonly remoteConfigManager: RemoteConfigManager; + private readonly remoteConfig: RemoteConfigRuntimeStateApi; private readonly accountManager: AccountManager; private readonly localConfig: LocalConfigRuntimeState; @@ -50,7 +50,7 @@ export class ProfileManager { @inject(InjectTokens.ConfigManager) configManager?: ConfigManager, @inject(InjectTokens.CacheDir) cacheDirectory?: DirectoryPath, @inject(InjectTokens.K8Factory) k8Factory?: K8Factory, - @inject(InjectTokens.RemoteConfigManager) remoteConfigManager?: RemoteConfigManager, + @inject(InjectTokens.RemoteConfigRuntimeState) remoteConfig?: RemoteConfigRuntimeStateApi, @inject(InjectTokens.AccountManager) accountManager?: AccountManager, @inject(InjectTokens.LocalConfigRuntimeState) localConfig?: LocalConfigRuntimeState, ) { @@ -58,11 +58,7 @@ export class ProfileManager { this.configManager = patchInject(configManager, InjectTokens.ConfigManager, this.constructor.name); this.cacheDir = PathEx.resolve(patchInject(cacheDirectory, InjectTokens.CacheDir, this.constructor.name)); this.k8Factory = patchInject(k8Factory, InjectTokens.K8Factory, this.constructor.name); - this.remoteConfigManager = patchInject( - remoteConfigManager, - InjectTokens.RemoteConfigManager, - this.constructor.name, - ); + this.remoteConfig = patchInject(remoteConfig, InjectTokens.RemoteConfigRuntimeState, this.constructor.name); this.accountManager = patchInject(accountManager, InjectTokens.AccountManager, this.constructor.name); this.localConfig = patchInject(localConfig, InjectTokens.LocalConfigRuntimeState, this.constructor.name); @@ -391,7 +387,7 @@ export class ProfileManager { const filesMapping: Record = {}; - for (const [clusterReference] of this.remoteConfigManager.getClusterRefs()) { + for (const [clusterReference] of this.remoteConfig.getClusterRefs()) { const nodeAliases: NodeAliases = consensusNodes .filter(consensusNode => consensusNode.cluster === clusterReference) .map(consensusNode => consensusNode.name); diff --git a/src/core/templates.ts b/src/core/templates.ts index 0a5d85efb..e8c39db8b 100644 --- a/src/core/templates.ts +++ b/src/core/templates.ts @@ -181,7 +181,7 @@ export class Templates { } public static nodeIdFromNodeAlias(nodeAlias: NodeAlias): NodeId { - for (let index = nodeAlias.length - 1; index > 0; index--) { + for (let index: number = nodeAlias.length - 1; index > 0; index--) { if (Number.isNaN(Number.parseInt(nodeAlias[index]))) { return Number.parseInt(nodeAlias.substring(index + 1, nodeAlias.length)) - 1; } @@ -242,18 +242,6 @@ export class Templates { } } - public static renderEnvoyProxyName(nodeAlias: NodeAlias): string { - return `envoy-proxy-${nodeAlias}`; - } - - public static renderHaProxyName(nodeAlias: NodeAlias): string { - return `haproxy-${nodeAlias}`; - } - - public static renderFullyQualifiedHaProxyName(nodeAlias: NodeAlias, namespace: NamespaceName): string { - return `${Templates.renderHaProxyName(nodeAlias)}-svc.${namespace}.svc.cluster.local`; - } - public static parseNodeAliasToIpMapping(unparsed: string): Record { const mapping: Record = {}; diff --git a/src/data/schema/model/local/deployment-schema.ts b/src/data/schema/model/local/deployment-schema.ts index b89efc7bf..df2c44c1e 100644 --- a/src/data/schema/model/local/deployment-schema.ts +++ b/src/data/schema/model/local/deployment-schema.ts @@ -1,18 +1,24 @@ // SPDX-License-Identifier: Apache-2.0 import {Exclude, Expose} from 'class-transformer'; -import {type Realm, type Shard} from '../../../../types/index.js'; +import { + type ClusterReference, + type DeploymentName, + type NamespaceNameAsString, + type Realm, + type Shard, +} from '../../../../types/index.js'; @Exclude() export class DeploymentSchema { @Expose() - public name: string; + public name: DeploymentName; @Expose() - public namespace: string; + public namespace: NamespaceNameAsString; @Expose() - public clusters: string[]; + public clusters: ClusterReference[]; @Expose() public realm: Realm; @@ -20,7 +26,13 @@ export class DeploymentSchema { @Expose() public shard: Shard; - public constructor(name?: string, namespace?: string, clusters?: string[], realm?: Realm, shard?: Shard) { + public constructor( + name?: DeploymentName, + namespace?: NamespaceNameAsString, + clusters?: ClusterReference[], + realm?: Realm, + shard?: Shard, + ) { this.name = name ?? ''; this.namespace = namespace ?? ''; this.clusters = clusters ?? []; diff --git a/src/data/schema/model/remote/remote-config-metadata-schema.ts b/src/data/schema/model/remote/remote-config-metadata-schema.ts index 5ae2c4ae3..ee194a0a0 100644 --- a/src/data/schema/model/remote/remote-config-metadata-schema.ts +++ b/src/data/schema/model/remote/remote-config-metadata-schema.ts @@ -11,4 +11,13 @@ export class RemoteConfigMetadataSchema { @Expose() @Type(() => UserIdentitySchema) public lastUpdatedBy: UserIdentitySchema; + + public constructor(lastUpdatedAt?: Date, lastUpdatedBy?: UserIdentitySchema) { + if (lastUpdatedAt) { + this.lastUpdatedAt = lastUpdatedAt; + } + if (lastUpdatedBy) { + this.lastUpdatedBy = lastUpdatedBy; + } + } } diff --git a/src/data/schema/model/remote/state/base-state-schema.ts b/src/data/schema/model/remote/state/base-state-schema.ts new file mode 100644 index 000000000..ba5a25c29 --- /dev/null +++ b/src/data/schema/model/remote/state/base-state-schema.ts @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 + +import {Exclude, Expose} from 'class-transformer'; +import {ComponentStateMetadataSchema} from './component-state-metadata-schema.js'; + +@Exclude() +export class BaseStateSchema { + @Expose() + public metadata: ComponentStateMetadataSchema; + + public constructor(metadata?: ComponentStateMetadataSchema) { + this.metadata = metadata; + } +} diff --git a/src/data/schema/model/remote/state/block-node-state-schema.ts b/src/data/schema/model/remote/state/block-node-state-schema.ts index 9024b78a5..0bf30043a 100644 --- a/src/data/schema/model/remote/state/block-node-state-schema.ts +++ b/src/data/schema/model/remote/state/block-node-state-schema.ts @@ -1,28 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 -import {Exclude, Expose, Transform} from 'class-transformer'; -import {Transformations} from '../../utils/transformations.js'; -import {type DeploymentPhase} from '../deployment-phase.js'; +import {Exclude} from 'class-transformer'; +import {BaseStateSchema} from './base-state-schema.js'; @Exclude() -export class BlockNodeStateSchema { - @Expose() - public name: string; - - @Expose() - public namespace: string; - - @Expose() - public cluster: string; - - @Expose() - @Transform(Transformations.DeploymentPhase) - public phase: DeploymentPhase; - - public constructor(name?: string, namespace?: string, cluster?: string, phase?: DeploymentPhase) { - this.name = name; - this.namespace = namespace; - this.cluster = cluster; - this.phase = phase; - } -} +export class BlockNodeStateSchema extends BaseStateSchema {} diff --git a/src/data/schema/model/remote/state/component-state-metadata-schema.ts b/src/data/schema/model/remote/state/component-state-metadata-schema.ts new file mode 100644 index 000000000..f8659c87a --- /dev/null +++ b/src/data/schema/model/remote/state/component-state-metadata-schema.ts @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 + +import {Exclude, Expose, Transform} from 'class-transformer'; +import {Transformations} from '../../utils/transformations.js'; +import {type DeploymentPhase} from '../deployment-phase.js'; +import {type ClusterReference, type ComponentId, type NamespaceNameAsString} from '../../../../../types/index.js'; + +@Exclude() +export class ComponentStateMetadataSchema { + @Expose() + public id: ComponentId; + + @Expose() + public namespace: NamespaceNameAsString; + + @Expose() + public cluster: ClusterReference; + + @Expose() + @Transform(Transformations.DeploymentPhase) + public phase: DeploymentPhase; + + public constructor( + id?: ComponentId, + namespace?: NamespaceNameAsString, + cluster?: ClusterReference, + phase?: DeploymentPhase, + ) { + this.id = id; + this.namespace = namespace; + this.cluster = cluster; + this.phase = phase; + } +} diff --git a/src/data/schema/model/remote/state/consensus-node-state-schema.ts b/src/data/schema/model/remote/state/consensus-node-state-schema.ts index 23bfdeefe..1d1a01d3b 100644 --- a/src/data/schema/model/remote/state/consensus-node-state-schema.ts +++ b/src/data/schema/model/remote/state/consensus-node-state-schema.ts @@ -1,32 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 -import {Exclude, Expose, Transform} from 'class-transformer'; -import {type DeploymentPhase} from '../deployment-phase.js'; -import {Transformations} from '../../utils/transformations.js'; +import {Exclude} from 'class-transformer'; +import {BaseStateSchema} from './base-state-schema.js'; @Exclude() -export class ConsensusNodeStateSchema { - @Expose() - public id: number; - - @Expose() - public name: string; - - @Expose() - public namespace: string; - - @Expose() - public cluster: string; - - @Expose() - @Transform(Transformations.DeploymentPhase) - public phase: DeploymentPhase; - - public constructor(id?: number, name?: string, namespace?: string, cluster?: string, phase?: DeploymentPhase) { - this.id = id; - this.name = name; - this.namespace = namespace; - this.cluster = cluster; - this.phase = phase; - } -} +export class ConsensusNodeStateSchema extends BaseStateSchema {} diff --git a/src/data/schema/model/remote/state/envoy-proxy-state-schema.ts b/src/data/schema/model/remote/state/envoy-proxy-state-schema.ts index b1fddc685..3153e01b2 100644 --- a/src/data/schema/model/remote/state/envoy-proxy-state-schema.ts +++ b/src/data/schema/model/remote/state/envoy-proxy-state-schema.ts @@ -1,28 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 -import {Exclude, Expose, Transform} from 'class-transformer'; -import {Transformations} from '../../utils/transformations.js'; -import {type DeploymentPhase} from '../deployment-phase.js'; +import {Exclude} from 'class-transformer'; +import {BaseStateSchema} from './base-state-schema.js'; @Exclude() -export class EnvoyProxyStateSchema { - @Expose() - public name: string; - - @Expose() - public namespace: string; - - @Expose() - public cluster: string; - - @Expose() - @Transform(Transformations.DeploymentPhase) - public phase: DeploymentPhase; - - public constructor(name?: string, namespace?: string, cluster?: string, phase?: DeploymentPhase) { - this.name = name; - this.namespace = namespace; - this.cluster = cluster; - this.phase = phase; - } -} +export class EnvoyProxyStateSchema extends BaseStateSchema {} diff --git a/src/data/schema/model/remote/state/explorer-state-schema.ts b/src/data/schema/model/remote/state/explorer-state-schema.ts index 01d65bb8d..87cf4beee 100644 --- a/src/data/schema/model/remote/state/explorer-state-schema.ts +++ b/src/data/schema/model/remote/state/explorer-state-schema.ts @@ -1,28 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 -import {Exclude, Expose, Transform} from 'class-transformer'; -import {Transformations} from '../../utils/transformations.js'; -import {type DeploymentPhase} from '../deployment-phase.js'; +import {Exclude} from 'class-transformer'; +import {BaseStateSchema} from './base-state-schema.js'; @Exclude() -export class ExplorerStateSchema { - @Expose() - public name: string; - - @Expose() - public namespace: string; - - @Expose() - public cluster: string; - - @Expose() - @Transform(Transformations.DeploymentPhase) - public phase: DeploymentPhase; - - public constructor(name?: string, namespace?: string, cluster?: string, phase?: DeploymentPhase) { - this.name = name; - this.namespace = namespace; - this.cluster = cluster; - this.phase = phase; - } -} +export class ExplorerStateSchema extends BaseStateSchema {} diff --git a/src/data/schema/model/remote/state/haproxy-state-schema.ts b/src/data/schema/model/remote/state/haproxy-state-schema.ts index 19637f556..feae03158 100644 --- a/src/data/schema/model/remote/state/haproxy-state-schema.ts +++ b/src/data/schema/model/remote/state/haproxy-state-schema.ts @@ -1,28 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 -import {Exclude, Expose, Transform} from 'class-transformer'; -import {Transformations} from '../../utils/transformations.js'; -import {type DeploymentPhase} from '../deployment-phase.js'; +import {Exclude} from 'class-transformer'; +import {BaseStateSchema} from './base-state-schema.js'; @Exclude() -export class HAProxyStateSchema { - @Expose() - public name: string; - - @Expose() - public namespace: string; - - @Expose() - public cluster: string; - - @Expose() - @Transform(Transformations.DeploymentPhase) - public phase: DeploymentPhase; - - public constructor(name?: string, namespace?: string, cluster?: string, phase?: DeploymentPhase) { - this.name = name; - this.namespace = namespace; - this.cluster = cluster; - this.phase = phase; - } -} +export class HAProxyStateSchema extends BaseStateSchema {} diff --git a/src/data/schema/model/remote/state/mirror-node-state-schema.ts b/src/data/schema/model/remote/state/mirror-node-state-schema.ts index 21417b8fd..7f8e960d3 100644 --- a/src/data/schema/model/remote/state/mirror-node-state-schema.ts +++ b/src/data/schema/model/remote/state/mirror-node-state-schema.ts @@ -1,28 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 -import {Exclude, Expose, Transform} from 'class-transformer'; -import {Transformations} from '../../utils/transformations.js'; -import {type DeploymentPhase} from '../deployment-phase.js'; +import {Exclude} from 'class-transformer'; +import {BaseStateSchema} from './base-state-schema.js'; @Exclude() -export class MirrorNodeStateSchema { - @Expose() - public name: string; - - @Expose() - public namespace: string; - - @Expose() - public cluster: string; - - @Expose() - @Transform(Transformations.DeploymentPhase) - public phase: DeploymentPhase; - - public constructor(name?: string, namespace?: string, cluster?: string, phase?: DeploymentPhase) { - this.name = name; - this.namespace = namespace; - this.cluster = cluster; - this.phase = phase; - } -} +export class MirrorNodeStateSchema extends BaseStateSchema {} diff --git a/src/data/schema/model/remote/state/relay-node-state-schema.ts b/src/data/schema/model/remote/state/relay-node-state-schema.ts index 95a6df4fc..fabdf54b7 100644 --- a/src/data/schema/model/remote/state/relay-node-state-schema.ts +++ b/src/data/schema/model/remote/state/relay-node-state-schema.ts @@ -1,38 +1,17 @@ // SPDX-License-Identifier: Apache-2.0 -import {Exclude, Expose, Transform} from 'class-transformer'; -import {Transformations} from '../../utils/transformations.js'; -import {type DeploymentPhase} from '../deployment-phase.js'; +import {Exclude, Expose} from 'class-transformer'; +import {ComponentStateMetadataSchema} from './component-state-metadata-schema.js'; +import {BaseStateSchema} from './base-state-schema.js'; +import {NodeId} from '../../../../../types/aliases.js'; @Exclude() -export class RelayNodeStateSchema { - @Expose() - public name: string; - - @Expose() - public namespace: string; - - @Expose() - public cluster: string; - +export class RelayNodeStateSchema extends BaseStateSchema { @Expose() public consensusNodeIds: number[]; - @Expose() - @Transform(Transformations.DeploymentPhase) - public phase: DeploymentPhase; - - public constructor( - name?: string, - namespace?: string, - cluster?: string, - consensusNodeIds?: number[], - phase?: DeploymentPhase, - ) { - this.name = name; - this.namespace = namespace; - this.cluster = cluster; + public constructor(metadata?: ComponentStateMetadataSchema, consensusNodeIds?: NodeId[]) { + super(metadata); this.consensusNodeIds = consensusNodeIds; - this.phase = phase; } } diff --git a/src/types/index.ts b/src/types/index.ts index fad125b2c..5f728080c 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -135,9 +135,9 @@ export type Version = string; /// TODO - see if we can use NamespaceName and use some annotations and overrides to covert to strings export type NamespaceNameAsString = string; export type Context = string; -export type ComponentName = string; +export type ComponentId = number; export type DeploymentName = string; export type Realm = number | Long; export type Shard = number | Long; export type ClusterReference = string; -export type ClusterReferences = Map; +export type ClusterReferences = Map; diff --git a/test/e2e/commands/account.test.ts b/test/e2e/commands/account.test.ts index ea4ab08ff..24e70f8dd 100644 --- a/test/e2e/commands/account.test.ts +++ b/test/e2e/commands/account.test.ts @@ -68,7 +68,7 @@ endToEndTestSuite(testName, argv, {containerOverrides: overrides}, bootstrapResp let testLogger: SoloLogger; const { - opts: {k8Factory, accountManager, configManager, commandInvoker, remoteConfigManager}, + opts: {k8Factory, accountManager, configManager, commandInvoker, remoteConfig}, cmd: {nodeCmd}, } = bootstrapResp; @@ -125,7 +125,7 @@ endToEndTestSuite(testName, argv, {containerOverrides: overrides}, bootstrapResp await accountManager.loadNodeClient( namespace, - remoteConfigManager.getClusterRefs(), + remoteConfig.getClusterRefs(), argv.getArg(flags.deployment), argv.getArg(flags.forcePortForward), ); @@ -139,7 +139,7 @@ endToEndTestSuite(testName, argv, {containerOverrides: overrides}, bootstrapResp it('Node admin key should have been updated, not equal to genesis key', async () => { const nodeAliases = helpers.parseNodeAliases( argv.getArg(flags.nodeAliasesUnparsed), - bootstrapResp.opts.remoteConfigManager.getConsensusNodes(), + bootstrapResp.opts.remoteConfig.getConsensusNodes(), bootstrapResp.opts.configManager, ); for (const nodeAlias of nodeAliases) { @@ -356,7 +356,7 @@ endToEndTestSuite(testName, argv, {containerOverrides: overrides}, bootstrapResp await accountManager.loadNodeClient( namespace, - remoteConfigManager.getClusterRefs(), + remoteConfig.getClusterRefs(), argv.getArg(flags.deployment), argv.getArg(flags.forcePortForward), ); @@ -384,7 +384,7 @@ endToEndTestSuite(testName, argv, {containerOverrides: overrides}, bootstrapResp try { await accountManager.loadNodeClient( namespace, - remoteConfigManager.getClusterRefs(), + remoteConfig.getClusterRefs(), argv.getArg(flags.deployment), argv.getArg(flags.forcePortForward), ); diff --git a/test/e2e/commands/dual-cluster-full.test.ts b/test/e2e/commands/dual-cluster-full.test.ts index f9ff19397..75b935127 100644 --- a/test/e2e/commands/dual-cluster-full.test.ts +++ b/test/e2e/commands/dual-cluster-full.test.ts @@ -9,15 +9,15 @@ import {resetForTest} from '../../test-container.js'; import { type ClusterReference, type ClusterReferences, - type DeploymentName, type ExtendedNetServer, + type DeploymentName, + type ComponentId, } from '../../../src/types/index.js'; import {NamespaceName} from '../../../src/types/namespace/namespace-name.js'; import {type K8Factory} from '../../../src/integration/kube/k8-factory.js'; import {container} from 'tsyringe-neo'; import {InjectTokens} from '../../../src/core/dependency-injection/inject-tokens.js'; import {type CommandFlag} from '../../../src/types/flag-types.js'; -import {type RemoteConfigManager} from '../../../src/core/config/remote/remote-config-manager.js'; import {expect} from 'chai'; import fs from 'node:fs'; import {type K8ClientFactory} from '../../../src/integration/kube/k8-client/k8-client-factory.js'; @@ -30,7 +30,6 @@ import { ROOT_CONTAINER, } from '../../../src/core/constants.js'; import {Duration} from '../../../src/core/time/duration.js'; -import {type ConsensusNodeComponent} from '../../../src/core/config/remote/components/consensus-node-component.js'; import {type Pod} from '../../../src/integration/kube/resources/pod/pod.js'; import {Templates} from '../../../src/core/templates.js'; import {PathEx} from '../../../src/business/utils/path-ex.js'; @@ -50,9 +49,11 @@ import { type TransactionResponse, } from '@hashgraph/sdk'; import {type PackageDownloader} from '../../../src/core/package-downloader.js'; +import {type RemoteConfigRuntimeStateApi} from '../../../src/business/runtime-state/api/remote-config-runtime-state-api.js'; import {type LocalConfigRuntimeState} from '../../../src/business/runtime-state/config/local/local-config-runtime-state.js'; import {type FacadeMap} from '../../../src/business/runtime-state/collection/facade-map.js'; import {type StringFacade} from '../../../src/business/runtime-state/facade/string-facade.js'; +import {type ConsensusNodeStateSchema} from '../../../src/data/schema/model/remote/state/consensus-node-state-schema.js'; const testName: string = 'dual-cluster-full'; @@ -134,12 +135,13 @@ describe('Dual Cluster Full E2E Test', function dualClusterFullEndToEndTest() { for (const element of testClusterArray) { await main(soloDeploymentAddClusterArgv(deployment, element, 1)); } - const remoteConfigManager: RemoteConfigManager = container.resolve(InjectTokens.RemoteConfigManager); - expect(remoteConfigManager.isLoaded(), 'remote config manager should be loaded').to.be.true; - const consensusNodes: Record = remoteConfigManager.components.consensusNodes; + const remoteConfig: RemoteConfigRuntimeStateApi = container.resolve(InjectTokens.RemoteConfigRuntimeState); + expect(remoteConfig.isLoaded(), 'remote config manager should be loaded').to.be.true; + const consensusNodes: Record = + remoteConfig.configuration.components.state.consensusNodes; expect(Object.entries(consensusNodes).length, 'consensus node count should be 2').to.equal(2); - expect(consensusNodes['node1'].cluster).to.equal(testClusterArray[0]); - expect(consensusNodes['node2'].cluster).to.equal(testClusterArray[1]); + expect(consensusNodes[0].metadata.cluster).to.equal(testClusterArray[0]); + expect(consensusNodes[1].metadata.cluster).to.equal(testClusterArray[1]); testLogger.info(`${testName}: finished solo deployment add-cluster`); }); diff --git a/test/e2e/commands/node-delete.test.ts b/test/e2e/commands/node-delete.test.ts index 731f3254a..52d8ec3ba 100644 --- a/test/e2e/commands/node-delete.test.ts +++ b/test/e2e/commands/node-delete.test.ts @@ -46,7 +46,7 @@ let updateAcccountPrivateKey: string; endToEndTestSuite(namespace.name, argv, {}, bootstrapResp => { describe('Node delete', async () => { const { - opts: {k8Factory, commandInvoker, accountManager, remoteConfigManager, logger}, + opts: {k8Factory, commandInvoker, accountManager, remoteConfig, logger}, cmd: {nodeCmd, accountCmd}, } = bootstrapResp; @@ -105,9 +105,9 @@ endToEndTestSuite(namespace.name, argv, {}, bootstrapResp => { await accountManager.close(); }).timeout(Duration.ofMinutes(30).toMillis()); - balanceQueryShouldSucceed(accountManager, namespace, remoteConfigManager, logger, deleteNodeAlias); + balanceQueryShouldSucceed(accountManager, namespace, remoteConfig, logger, deleteNodeAlias); - accountCreationShouldSucceed(accountManager, namespace, remoteConfigManager, logger, deleteNodeAlias); + accountCreationShouldSucceed(accountManager, namespace, remoteConfig, logger, deleteNodeAlias); it('deleted consensus node should not be running', async () => { // read config.txt file from first node, read config.txt line by line, it should not contain value of nodeAlias diff --git a/test/e2e/commands/node-local-hedera.test.ts b/test/e2e/commands/node-local-hedera.test.ts index 6a3284232..148578233 100644 --- a/test/e2e/commands/node-local-hedera.test.ts +++ b/test/e2e/commands/node-local-hedera.test.ts @@ -41,7 +41,7 @@ argv.setArg(flags.releaseTag, TEST_LOCAL_HEDERA_PLATFORM_VERSION); endToEndTestSuite(namespace.name, argv, {}, bootstrapResp => { describe('Node for hedera app should have started successfully', () => { const { - opts: {k8Factory, commandInvoker, remoteConfigManager}, + opts: {k8Factory, commandInvoker, remoteConfig}, cmd: {nodeCmd, accountCmd}, manager: {accountManager}, } = bootstrapResp; @@ -50,7 +50,7 @@ endToEndTestSuite(namespace.name, argv, {}, bootstrapResp => { // create an account so later we can verify its balance after restart await accountManager.loadNodeClient( namespace, - remoteConfigManager.getClusterRefs(), + remoteConfig.getClusterRefs(), argv.getArg(flags.deployment), argv.getArg(flags.forcePortForward), ); @@ -116,7 +116,7 @@ endToEndTestSuite(namespace.name, argv, {}, bootstrapResp => { // check balance of accountInfo.accountId await accountManager.loadNodeClient( namespace, - remoteConfigManager.getClusterRefs(), + remoteConfig.getClusterRefs(), argv.getArg(flags.deployment), argv.getArg(flags.forcePortForward), ); diff --git a/test/e2e/commands/node-update.test.ts b/test/e2e/commands/node-update.test.ts index c028708c3..495b17f85 100644 --- a/test/e2e/commands/node-update.test.ts +++ b/test/e2e/commands/node-update.test.ts @@ -50,7 +50,7 @@ argv.setArg(flags.shard, hederaPlatformSupportsNonZeroRealms() ? 1 : 0); endToEndTestSuite(namespace.name, argv, {}, bootstrapResp => { const { - opts: {k8Factory, commandInvoker, accountManager, remoteConfigManager, logger, keyManager}, + opts: {k8Factory, commandInvoker, accountManager, remoteConfig, logger, keyManager}, cmd: {nodeCmd, accountCmd}, } = bootstrapResp; @@ -76,7 +76,7 @@ endToEndTestSuite(namespace.name, argv, {}, bootstrapResp => { it('cache current version of private keys', async () => { existingServiceMap = await accountManager.getNodeServiceMap( namespace, - remoteConfigManager.getClusterRefs(), + remoteConfig.getClusterRefs(), argv.getArg(flags.deployment), ); @@ -122,9 +122,9 @@ endToEndTestSuite(namespace.name, argv, {}, bootstrapResp => { await accountManager.close(); }).timeout(Duration.ofMinutes(30).toMillis()); - balanceQueryShouldSucceed(accountManager, namespace, remoteConfigManager, logger, updateNodeId); + balanceQueryShouldSucceed(accountManager, namespace, remoteConfig, logger, updateNodeId); - accountCreationShouldSucceed(accountManager, namespace, remoteConfigManager, logger, updateNodeId); + accountCreationShouldSucceed(accountManager, namespace, remoteConfig, logger, updateNodeId); it('signing key and tls key should not match previous one', async () => { const currentNodeIdsPrivateKeysHash = await getNodeAliasesPrivateKeysHash( diff --git a/test/e2e/commands/separate-node-add.test.ts b/test/e2e/commands/separate-node-add.test.ts index 41debea79..86c2129ed 100644 --- a/test/e2e/commands/separate-node-add.test.ts +++ b/test/e2e/commands/separate-node-add.test.ts @@ -49,7 +49,7 @@ argvExecute.setArg(flags.inputDir, temporaryDirectory); endToEndTestSuite(namespace.name, argv, {}, bootstrapResp => { const { - opts: {k8Factory, commandInvoker, accountManager, remoteConfigManager, logger}, + opts: {k8Factory, commandInvoker, accountManager, remoteConfig, logger}, cmd: {nodeCmd, accountCmd, networkCmd}, } = bootstrapResp; @@ -83,7 +83,7 @@ endToEndTestSuite(namespace.name, argv, {}, bootstrapResp => { it('cache current version of private keys', async () => { existingServiceMap = await accountManager.getNodeServiceMap( namespace, - remoteConfigManager.getClusterRefs(), + remoteConfig.getClusterRefs(), argv.getArg(flags.deployment), ); existingNodeIdsPrivateKeysHash = await getNodeAliasesPrivateKeysHash( @@ -127,9 +127,9 @@ endToEndTestSuite(namespace.name, argv, {}, bootstrapResp => { await accountManager.close(); }).timeout(Duration.ofMinutes(12).toMillis()); - balanceQueryShouldSucceed(accountManager, namespace, remoteConfigManager, logger); + balanceQueryShouldSucceed(accountManager, namespace, remoteConfig, logger); - accountCreationShouldSucceed(accountManager, namespace, remoteConfigManager, logger); + accountCreationShouldSucceed(accountManager, namespace, remoteConfig, logger); it('existing nodes private keys should not have changed', async () => { const currentNodeIdsPrivateKeysHash = await getNodeAliasesPrivateKeysHash( diff --git a/test/e2e/commands/separate-node-delete.test.ts b/test/e2e/commands/separate-node-delete.test.ts index 3c1c4981b..9b3b4ac5a 100644 --- a/test/e2e/commands/separate-node-delete.test.ts +++ b/test/e2e/commands/separate-node-delete.test.ts @@ -45,7 +45,7 @@ argvExecute.setArg(flags.inputDir, temporaryDirectory); endToEndTestSuite(namespace.name, argv, {}, bootstrapResp => { const { - opts: {k8Factory, accountManager, remoteConfigManager, logger, commandInvoker}, + opts: {k8Factory, accountManager, remoteConfig, logger, commandInvoker}, cmd: {nodeCmd, accountCmd}, } = bootstrapResp; @@ -91,9 +91,9 @@ endToEndTestSuite(namespace.name, argv, {}, bootstrapResp => { await accountManager.close(); }).timeout(Duration.ofMinutes(10).toMillis()); - balanceQueryShouldSucceed(accountManager, namespace, remoteConfigManager, logger, nodeAlias); + balanceQueryShouldSucceed(accountManager, namespace, remoteConfig, logger, nodeAlias); - accountCreationShouldSucceed(accountManager, namespace, remoteConfigManager, logger, nodeAlias); + accountCreationShouldSucceed(accountManager, namespace, remoteConfig, logger, nodeAlias); it('deleted consensus node should not be running', async () => { // read config.txt file from first node, read config.txt line by line, it should not contain value of nodeAlias diff --git a/test/e2e/commands/separate-node-update.test.ts b/test/e2e/commands/separate-node-update.test.ts index fc017554e..60fb1c0bd 100644 --- a/test/e2e/commands/separate-node-update.test.ts +++ b/test/e2e/commands/separate-node-update.test.ts @@ -46,7 +46,7 @@ argv.setArg(flags.persistentVolumeClaims, true); endToEndTestSuite(namespace.name, argv, {}, bootstrapResp => { const { - opts: {k8Factory, logger, remoteConfigManager, commandInvoker, accountManager, keyManager}, + opts: {k8Factory, logger, remoteConfig, commandInvoker, accountManager, keyManager}, cmd: {nodeCmd, accountCmd}, } = bootstrapResp; @@ -72,7 +72,7 @@ endToEndTestSuite(namespace.name, argv, {}, bootstrapResp => { it('cache current version of private keys', async () => { existingServiceMap = await accountManager.getNodeServiceMap( namespace, - remoteConfigManager.getClusterRefs(), + remoteConfig.getClusterRefs(), argv.getArg(flags.deployment), ); existingNodeIdsPrivateKeysHash = await getNodeAliasesPrivateKeysHash( @@ -138,9 +138,9 @@ endToEndTestSuite(namespace.name, argv, {}, bootstrapResp => { await accountManager.close(); }).timeout(Duration.ofMinutes(30).toMillis()); - balanceQueryShouldSucceed(accountManager, namespace, remoteConfigManager, logger, updateNodeId); + balanceQueryShouldSucceed(accountManager, namespace, remoteConfig, logger, updateNodeId); - accountCreationShouldSucceed(accountManager, namespace, remoteConfigManager, logger, updateNodeId); + accountCreationShouldSucceed(accountManager, namespace, remoteConfig, logger, updateNodeId); it('signing key and tls key should not match previous one', async () => { const currentNodeIdsPrivateKeysHash = await getNodeAliasesPrivateKeysHash( diff --git a/test/e2e/end-to-end-node-utility.ts b/test/e2e/end-to-end-node-utility.ts index b4a61a04f..17b885a9b 100644 --- a/test/e2e/end-to-end-node-utility.ts +++ b/test/e2e/end-to-end-node-utility.ts @@ -46,7 +46,7 @@ export function endToEndNodeKeyRefreshTest(testName: string, mode: string, relea const defaultTimeout = Duration.ofMinutes(2).toMillis(); const { - opts: {accountManager, k8Factory, remoteConfigManager, logger, commandInvoker}, + opts: {accountManager, k8Factory, remoteConfig, logger, commandInvoker}, cmd: {nodeCmd}, } = bootstrapResp; @@ -66,9 +66,9 @@ export function endToEndNodeKeyRefreshTest(testName: string, mode: string, relea }); describe(`Node should have started successfully [mode ${mode}, release ${releaseTag}]`, () => { - balanceQueryShouldSucceed(accountManager, namespace, remoteConfigManager, logger); + balanceQueryShouldSucceed(accountManager, namespace, remoteConfig, logger); - accountCreationShouldSucceed(accountManager, namespace, remoteConfigManager, logger); + accountCreationShouldSucceed(accountManager, namespace, remoteConfig, logger); it(`Node Proxy should be UP [mode ${mode}, release ${releaseTag}`, async () => { try { @@ -115,9 +115,9 @@ export function endToEndNodeKeyRefreshTest(testName: string, mode: string, relea nodeRefreshShouldSucceed(nodeAlias, nodeCmd, argv); - balanceQueryShouldSucceed(accountManager, namespace, remoteConfigManager, logger); + balanceQueryShouldSucceed(accountManager, namespace, remoteConfig, logger); - accountCreationShouldSucceed(accountManager, namespace, remoteConfigManager, logger); + accountCreationShouldSucceed(accountManager, namespace, remoteConfig, logger); }); function nodePodShouldBeRunning(nodeCmd: NodeCommand, namespace: NamespaceName, nodeAlias: NodeAlias) { diff --git a/test/e2e/integration/core/remote-config-validator.test.ts b/test/e2e/integration/core/remote-config-validator.test.ts index 37990ae17..134570cf7 100644 --- a/test/e2e/integration/core/remote-config-validator.test.ts +++ b/test/e2e/integration/core/remote-config-validator.test.ts @@ -1,23 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 -import {describe, it} from 'mocha'; +import {beforeEach, describe, it} from 'mocha'; import {expect} from 'chai'; -import * as constants from '../../../../src/core/constants.js'; -import {type ConfigManager} from '../../../../src/core/config-manager.js'; -import {Templates} from '../../../../src/core/templates.js'; -import {Flags as flags} from '../../../../src/commands/flags.js'; import {RemoteConfigValidator} from '../../../../src/core/config/remote/remote-config-validator.js'; import {ComponentsDataWrapper} from '../../../../src/core/config/remote/components-data-wrapper.js'; import {SoloError} from '../../../../src/core/errors/solo-error.js'; -import {RelayComponent} from '../../../../src/core/config/remote/components/relay-component.js'; -import {HaProxyComponent} from '../../../../src/core/config/remote/components/ha-proxy-component.js'; -import {MirrorNodeComponent} from '../../../../src/core/config/remote/components/mirror-node-component.js'; -import {ConsensusNodeComponent} from '../../../../src/core/config/remote/components/consensus-node-component.js'; -import {MirrorNodeExplorerComponent} from '../../../../src/core/config/remote/components/mirror-node-explorer-component.js'; -import {EnvoyProxyComponent} from '../../../../src/core/config/remote/components/envoy-proxy-component.js'; - -import {type ArgvStruct, type NodeAlias, type NodeAliases} from '../../../../src/types/aliases.js'; +import {type NodeId} from '../../../../src/types/aliases.js'; import {container} from 'tsyringe-neo'; import {NamespaceName} from '../../../../src/types/namespace/namespace-name.js'; import {PodReference} from '../../../../src/integration/kube/resources/pod/pod-reference.js'; @@ -27,198 +16,239 @@ import {InjectTokens} from '../../../../src/core/dependency-injection/inject-tok import {type K8Factory} from '../../../../src/integration/kube/k8-factory.js'; import {getTestCacheDirectory} from '../../../test-utility.js'; import {Duration} from '../../../../src/core/time/duration.js'; +import {type ClusterReference} from '../../../../src/types/index.js'; +import {DeploymentPhase} from '../../../../src/data/schema/model/remote/deployment-phase.js'; +import {Templates} from '../../../../src/core/templates.js'; +import {type BaseStateSchema} from '../../../../src/data/schema/model/remote/state/base-state-schema.js'; +import {ComponentTypes} from '../../../../src/core/config/remote/enumerations/component-types.js'; +import {type ComponentFactoryApi} from '../../../../src/core/config/remote/api/component-factory-api.js'; +import {ComponentFactory} from '../../../../src/core/config/remote/component-factory.js'; +import {type ComponentsDataWrapperApi} from '../../../../src/core/config/remote/api/components-data-wrapper-api.js'; +import {type ExplorerStateSchema} from '../../../../src/data/schema/model/remote/state/explorer-state-schema.js'; +import {type MirrorNodeStateSchema} from '../../../../src/data/schema/model/remote/state/mirror-node-state-schema.js'; +import {type RelayNodeStateSchema} from '../../../../src/data/schema/model/remote/state/relay-node-state-schema.js'; +import {type ConsensusNodeStateSchema} from '../../../../src/data/schema/model/remote/state/consensus-node-state-schema.js'; +import {type HAProxyStateSchema} from '../../../../src/data/schema/model/remote/state/haproxy-state-schema.js'; +import {type EnvoyProxyStateSchema} from '../../../../src/data/schema/model/remote/state/envoy-proxy-state-schema.js'; +import {DeploymentStateSchema} from '../../../../src/data/schema/model/remote/deployment-state-schema.js'; +import {RemoteConfigSchema} from '../../../../src/data/schema/model/remote/remote-config-schema.js'; import {LocalConfigRuntimeState} from '../../../../src/business/runtime-state/config/local/local-config-runtime-state.js'; -import {ConsensusNodeStates} from '../../../../src/core/config/remote/enumerations/consensus-node-states.js'; + +interface ComponentsRecord { + explorer: ExplorerStateSchema; + mirrorNode: MirrorNodeStateSchema; + relay: RelayNodeStateSchema; + consensusNode: ConsensusNodeStateSchema; + haProxy: HAProxyStateSchema; + envoyProxy: EnvoyProxyStateSchema; +} + +interface LabelRecord { + explorer: string[]; + mirrorNode: string[]; + relay: string[]; + consensusNode: string[]; + haProxy: string[]; + envoyProxy: string[]; +} + +interface ComponentsData { + namespace: NamespaceName; + components: ComponentsRecord; + labelRecord: LabelRecord; + componentsDataWrapper: ComponentsDataWrapperApi; + podNames: Record; + componentFactory: ComponentFactoryApi; +} + +function prepareComponentsData(namespace: NamespaceName): ComponentsData { + const remoteConfigMock: any = {components: {getNewComponentId: (): number => 1}}; + + const clusterReference: ClusterReference = 'cluster'; + const nodeState: DeploymentPhase = DeploymentPhase.STARTED; + const nodeId: NodeId = 0; + + const componentFactory: ComponentFactoryApi = new ComponentFactory(remoteConfigMock); + + const components: ComponentsRecord = { + explorer: componentFactory.createNewExplorerComponent(clusterReference, namespace), + mirrorNode: componentFactory.createNewMirrorNodeComponent(clusterReference, namespace), + relay: componentFactory.createNewRelayComponent(clusterReference, namespace, [0]), + consensusNode: componentFactory.createNewConsensusNodeComponent(nodeId, clusterReference, namespace, nodeState), + haProxy: componentFactory.createNewHaProxyComponent(clusterReference, namespace), + envoyProxy: componentFactory.createNewEnvoyProxyComponent(clusterReference, namespace), + }; + + const labelRecord: LabelRecord = { + // @ts-expect-error - to access private property + relay: RemoteConfigValidator.getRelayLabels(), + // @ts-expect-error - to access private property + haProxy: RemoteConfigValidator.getHaProxyLabels(components.haProxy), + // @ts-expect-error - to access private property + mirrorNode: RemoteConfigValidator.getMirrorNodeLabels(), + // @ts-expect-error - to access private property + envoyProxy: RemoteConfigValidator.getEnvoyProxyLabels(components.envoyProxy), + // @ts-expect-error - to access private property + explorer: RemoteConfigValidator.getMirrorNodeExplorerLabels(), + // @ts-expect-error - to access private property + consensusNode: RemoteConfigValidator.getConsensusNodeLabels(components.consensusNode), + }; + + const podNames: Record = { + explorer: `hedera-explorer-${components.explorer.metadata.id}`, + mirrorNode: `mirror-importer-${components.mirrorNode.metadata.id}`, + relay: `relay-${components.relay.metadata.id}`, + consensusNode: Templates.renderNetworkPodName( + Templates.renderNodeAliasFromNumber(components.consensusNode.metadata.id + 1), + ).name, + haProxy: `haproxy-node1-${Templates.renderNodeAliasFromNumber(components.haProxy.metadata.id + 1)}`, + envoyProxy: `envoy-proxy-${Templates.renderNodeAliasFromNumber(components.envoyProxy.metadata.id + 1)}`, + }; + + const state: DeploymentStateSchema = new DeploymentStateSchema(); + const remoteConfig: RemoteConfigSchema = new RemoteConfigSchema(undefined, undefined, undefined, undefined, state); + + const componentsDataWrapper: ComponentsDataWrapperApi = new ComponentsDataWrapper(remoteConfig.state); + + return {namespace, components, labelRecord, componentsDataWrapper, podNames, componentFactory}; +} describe('RemoteConfigValidator', () => { - const namespace = NamespaceName.of('remote-config-validator'); + const namespace: NamespaceName = NamespaceName.of('remote-config-validator'); - let configManager: ConfigManager; let k8Factory: K8Factory; let localConfig: LocalConfigRuntimeState; + let components: ComponentsRecord; + let labelRecord: LabelRecord; + let componentsDataWrapper: ComponentsDataWrapperApi; + let podNames: Record; + let componentFactory: ComponentFactoryApi; + before(async () => { - configManager = container.resolve(InjectTokens.ConfigManager); - configManager.update({[flags.namespace.name]: namespace} as ArgvStruct); k8Factory = container.resolve(InjectTokens.K8Factory); localConfig = new LocalConfigRuntimeState(`${getTestCacheDirectory('LocalConfig')}`, 'localConfig.yaml'); await localConfig.load(); await k8Factory.default().namespaces().create(namespace); }); + beforeEach(() => { + const testData: ComponentsData = prepareComponentsData(namespace); + podNames = testData.podNames; + components = testData.components; + labelRecord = testData.labelRecord; + componentsDataWrapper = testData.componentsDataWrapper; + componentFactory = testData.componentFactory; + }); + after(async function () { this.timeout(Duration.ofMinutes(5).toMillis()); await k8Factory.default().namespaces().delete(namespace); }); - const cluster = 'cluster'; - const state = ConsensusNodeStates.STARTED; - - const nodeAlias = 'node1' as NodeAlias; - const haProxyName = Templates.renderHaProxyName(nodeAlias); - const envoyProxyName = Templates.renderEnvoyProxyName(nodeAlias); - const relayName = 'relay'; - const mirrorNodeName = 'mirror-node'; - const mirrorNodeExplorerName = 'mirror-node-explorer'; - - const consensusNodeAliases = [nodeAlias] as NodeAliases; - - // @ts-expect-error - TS2673: Constructor of class ComponentsDataWrapper is private - const components = new ComponentsDataWrapper( - {[relayName]: new RelayComponent(relayName, cluster, namespace.name, consensusNodeAliases)}, - {[haProxyName]: new HaProxyComponent(haProxyName, cluster, namespace.name)}, - {[mirrorNodeName]: new MirrorNodeComponent(mirrorNodeName, cluster, namespace.name)}, - {[envoyProxyName]: new EnvoyProxyComponent(envoyProxyName, cluster, namespace.name)}, - { - [nodeAlias]: new ConsensusNodeComponent( - nodeAlias, - cluster, - namespace.name, - state, - Templates.nodeIdFromNodeAlias(nodeAlias), - ), - }, - {[mirrorNodeExplorerName]: new MirrorNodeExplorerComponent(mirrorNodeExplorerName, cluster, namespace.name)}, - ); - - async function createPod(name: string, labels: Record) { - try { - await k8Factory - .default() - .pods() - .create( - PodReference.of(namespace, PodName.of(name)), - labels, - ContainerName.of(name), - 'alpine:latest', - ['/bin/sh', '-c', 'apk update && apk upgrade && apk add --update bash && sleep 7200'], - ['bash', '-c', 'exit 0'], - ); - } catch (error) { - console.error(error); - throw new Error('Error creating pod'); + async function createPod(name: string, labelsRaw: string[]): Promise { + const labels: Record = {}; + + for (const rawLabel of labelsRaw) { + const [key, value] = rawLabel.split('='); + labels[key] = value; } + + await k8Factory + .default() + .pods() + .create( + PodReference.of(namespace, PodName.of(name)), + labels, + ContainerName.of(name), + 'alpine:latest', + ['/bin/sh', '-c', 'apk update && apk upgrade && apk add --update bash && sleep 7200'], + ['bash', '-c', 'exit 0'], + ); } - describe('Relays validation', () => { - it('should fail if component is not present', async () => { - try { - // @ts-expect-error - TS2341: Property is private - await Promise.all(RemoteConfigValidator.validateRelays(namespace, components, k8Factory, localConfig)); - throw new Error(); - } catch (error) { - expect(error).to.be.instanceOf(SoloError); - } + const testCasesForIndividualComponents: Array<{ + componentKey: keyof ComponentsRecord; + displayName: string; + type: ComponentTypes; + }> = [ + {componentKey: 'relay', displayName: 'Relay', type: ComponentTypes.RelayNodes}, + {componentKey: 'haProxy', displayName: 'HaProxy', type: ComponentTypes.HaProxy}, + {componentKey: 'mirrorNode', displayName: 'Mirror node', type: ComponentTypes.MirrorNode}, + {componentKey: 'envoyProxy', displayName: 'Envoy proxy', type: ComponentTypes.EnvoyProxy}, + {componentKey: 'consensusNode', displayName: 'Consensus node', type: ComponentTypes.ConsensusNode}, + {componentKey: 'explorer', displayName: 'Mirror node explorer', type: ComponentTypes.Explorers}, + ]; + + const remoteConfigValidator: RemoteConfigValidator = new RemoteConfigValidator(k8Factory, localConfig); + const remoteConfigData: any = {state: componentsDataWrapper.state}; + + for (const {componentKey, displayName, type} of testCasesForIndividualComponents) { + describe(`${displayName} validation`, () => { + it('should fail if component is not present', async () => { + const component: BaseStateSchema = components[componentKey]; + + componentsDataWrapper.addNewComponent(component, type); + + try { + await remoteConfigValidator.validateComponents(namespace, true, remoteConfigData); + expect.fail(); + } catch (error) { + expect(error).to.be.instanceOf(SoloError); + expect(error.message).to.equal(RemoteConfigValidator.buildValidationErrorMessage(displayName, component)); + } + }); + + it('should succeed if component is present', async () => { + await createPod(podNames[componentKey], labelRecord[componentKey]); + + await remoteConfigValidator.validateComponents(namespace, false, remoteConfigData); + }); }); + } - it('should succeed if component is present', async () => { - const [key, value] = constants.SOLO_RELAY_LABEL.split('='); - await createPod(relayName, {[key]: value}); + describe('Additional test cases', () => { + it('Should not validate consensus nodes if skipConsensusNodes is enabled', async () => { + const skipConsensusNodes: boolean = true; - // @ts-expect-error - TS2341: Property is private - await Promise.all(RemoteConfigValidator.validateRelays(namespace, components, k8Factory, localConfig)); - }); - }); + const nodeIds: NodeId[] = [0, 1, 2]; - describe('HaProxies validation', () => { - it('should fail if component is not present', async () => { - try { - // @ts-expect-error - TS2341: Property is private - await Promise.all(RemoteConfigValidator.validateHaProxies(namespace, components, k8Factory, localConfig)); - throw new Error(); - } catch (error) { - expect(error).to.be.instanceOf(SoloError); - } - }); + const consensusNodeComponents: ConsensusNodeStateSchema[] = + componentFactory.createConsensusNodeComponentsFromNodeIds(nodeIds, 'cluster-ref', namespace); - it('should succeed if component is present', async () => { - await createPod(haProxyName, {app: haProxyName}); + // @ts-expect-error - to mock + const componentsDataWrapper: ComponentsDataWrapperApi = new ComponentsDataWrapper({ + consensusNodes: consensusNodeComponents, + }); - // @ts-expect-error - TS2341: Property is private - await Promise.all(RemoteConfigValidator.validateHaProxies(namespace, components, k8Factory, localConfig)); - }); - }); - - describe('Mirror Node Components validation', () => { - it('should fail if component is not present', async () => { - try { - // @ts-expect-error - TS2341: Property is private - await Promise.all(RemoteConfigValidator.validateMirrorNodes(namespace, components, k8Factory, localConfig)); - throw new Error(); - } catch (error) { - expect(error).to.be.instanceOf(SoloError); + for (const nodeId of nodeIds) { + // Make sure the status is STARTED + componentsDataWrapper.changeNodePhase(nodeId, DeploymentPhase.STARTED); } - }); - it('should succeed if component is present', async () => { - const [key1, value1] = constants.SOLO_HEDERA_MIRROR_IMPORTER[0].split('='); - const [key2, value2] = constants.SOLO_HEDERA_MIRROR_IMPORTER[1].split('='); - await createPod(mirrorNodeName, {[key1]: value1, [key2]: value2}); - - // @ts-expect-error - TS2341: Property is private - await Promise.all(RemoteConfigValidator.validateMirrorNodes(namespace, components, k8Factory, localConfig)); - }); - }); - - describe('Envoy Proxies validation', () => { - it('should fail if component is not present', async () => { - try { - // @ts-expect-error - TS2341: Property is private - await Promise.all(RemoteConfigValidator.validateEnvoyProxies(namespace, components, k8Factory, localConfig)); - throw new Error(); - } catch (error) { - expect(error).to.be.instanceOf(SoloError); - } + await remoteConfigValidator.validateComponents(namespace, skipConsensusNodes, remoteConfigData); }); - it('should succeed if component is present', async () => { - await createPod(envoyProxyName, {app: envoyProxyName}); - - // @ts-expect-error - TS2341: Property is private - await Promise.all(RemoteConfigValidator.validateEnvoyProxies(namespace, components, k8Factory, localConfig)); - }); - }); - - describe('Consensus Nodes validation', () => { - it('should fail if component is not present', async () => { - try { - // @ts-expect-error - TS2341: Property is private - await Promise.all(RemoteConfigValidator.validateConsensusNodes(namespace, components, k8Factory, localConfig)); - throw new Error(); - } catch (error) { - expect(error).to.be.instanceOf(SoloError); - } - }); + const nodeStates: DeploymentPhase[] = [DeploymentPhase.REQUESTED, DeploymentPhase.STOPPED]; - it('should succeed if component is present', async () => { - await createPod(nodeAlias, {app: `network-${nodeAlias}`}); + for (const nodeState of nodeStates) { + it(`Should not validate consensus nodes if status is ${nodeState} `, async () => { + const nodeIds: NodeId[] = [0, 1, 2]; - // @ts-expect-error - TS2341: Property is private - await Promise.all(RemoteConfigValidator.validateConsensusNodes(namespace, components, k8Factory, localConfig)); - }); - }); + const consensusNodeComponents: ConsensusNodeStateSchema[] = + componentFactory.createConsensusNodeComponentsFromNodeIds(nodeIds, 'cluster-ref', namespace); - describe('Mirror Node Explorers validation', () => { - it('should fail if component is not present', async () => { - try { - await Promise.all( - // @ts-expect-error - TS2341: Property is private - RemoteConfigValidator.validateMirrorNodeExplorers(namespace, components, k8Factory, localConfig), - ); - throw new Error(); - } catch (error) { - expect(error).to.be.instanceOf(SoloError); - } - }); + // @ts-expect-error - to mock + const componentsDataWrapper: ComponentsDataWrapperApi = new ComponentsDataWrapper({ + consensusNodes: consensusNodeComponents, + }); - it('should succeed if component is present', async () => { - const [key, value] = constants.SOLO_EXPLORER_LABEL.split('='); - await createPod(mirrorNodeExplorerName, {[key]: value}); + for (const nodeId of nodeIds) { + componentsDataWrapper.changeNodePhase(nodeId, nodeState); + } - await Promise.all( - // @ts-expect-error - TS2341: Property is private - RemoteConfigValidator.validateMirrorNodeExplorers(namespace, components, k8Factory, localConfig), - ); - }); + await remoteConfigValidator.validateComponents(namespace, false, remoteConfigData); + }); + } }); }); diff --git a/test/helpers/command-invoker.ts b/test/helpers/command-invoker.ts index 5d71bf669..c95326456 100644 --- a/test/helpers/command-invoker.ts +++ b/test/helpers/command-invoker.ts @@ -2,7 +2,6 @@ import {type Middlewares} from '../../src/core/middlewares.js'; import {Flags as flags} from '../../src/commands/flags.js'; -import {type RemoteConfigManager} from '../../src/core/config/remote/remote-config-manager.js'; import {type AnyObject, type ArgvStruct} from '../../src/types/aliases.js'; import {type Argv} from './argv-wrapper.js'; import {type ConfigManager} from '../../src/core/config-manager.js'; @@ -11,23 +10,20 @@ import {type K8Factory} from '../../src/integration/kube/k8-factory.js'; import {InjectTokens} from '../../src/core/dependency-injection/inject-tokens.js'; import {inject, injectable} from 'tsyringe-neo'; import {patchInject} from '../../src/core/dependency-injection/container-helper.js'; +import {type RemoteConfigRuntimeStateApi} from '../../src/business/runtime-state/api/remote-config-runtime-state-api.js'; @injectable() export class CommandInvoker { public constructor( @inject(InjectTokens.Middlewares) private readonly middlewares?: Middlewares, @inject(InjectTokens.ConfigManager) private readonly configManager?: ConfigManager, - @inject(InjectTokens.RemoteConfigManager) private readonly remoteConfigManager?: RemoteConfigManager, + @inject(InjectTokens.RemoteConfigRuntimeState) private readonly remoteConfig?: RemoteConfigRuntimeStateApi, @inject(InjectTokens.K8Factory) private readonly k8Factory?: K8Factory, @inject(InjectTokens.SoloLogger) private readonly logger?: SoloLogger, ) { this.middlewares = patchInject(middlewares, InjectTokens.Middlewares, this.constructor.name); this.configManager = patchInject(configManager, InjectTokens.ConfigManager, this.constructor.name); - this.remoteConfigManager = patchInject( - remoteConfigManager, - InjectTokens.RemoteConfigManager, - this.constructor.name, - ); + this.remoteConfig = patchInject(remoteConfig, InjectTokens.RemoteConfigRuntimeState, this.constructor.name); this.k8Factory = patchInject(k8Factory, InjectTokens.K8Factory, this.constructor.name); this.logger = patchInject(logger, InjectTokens.SoloLogger, this.constructor.name); } @@ -44,7 +40,7 @@ export class CommandInvoker { subcommand?: string; }): Promise { // unload the remote config from the manager - this.remoteConfigManager.unload(); + // this.remoteConfig.unload(); // TODO: unload using runtime state if (!argv.getArg(flags.context)) { argv.setArg(flags.context, this.k8Factory.default().contexts().readCurrent()); diff --git a/test/test-add.ts b/test/test-add.ts index 0252859d8..ec68a9b52 100644 --- a/test/test-add.ts +++ b/test/test-add.ts @@ -54,7 +54,7 @@ export function testNodeAdd( endToEndTestSuite(namespace.name, argv, {}, bootstrapResp => { const { - opts: {k8Factory, accountManager, remoteConfigManager, logger, commandInvoker}, + opts: {k8Factory, accountManager, remoteConfig, logger, commandInvoker}, cmd: {nodeCmd, accountCmd, networkCmd}, } = bootstrapResp; @@ -87,7 +87,7 @@ export function testNodeAdd( it('cache current version of private keys', async () => { existingServiceMap = await accountManager.getNodeServiceMap( namespace, - remoteConfigManager.getClusterRefs(), + remoteConfig.getClusterRefs(), argv.getArg(flags.deployment), ); existingNodeIdsPrivateKeysHash = await getNodeAliasesPrivateKeysHash( @@ -126,9 +126,9 @@ export function testNodeAdd( }); }); - balanceQueryShouldSucceed(accountManager, namespace, remoteConfigManager, logger); + balanceQueryShouldSucceed(accountManager, namespace, remoteConfig, logger); - accountCreationShouldSucceed(accountManager, namespace, remoteConfigManager, logger); + accountCreationShouldSucceed(accountManager, namespace, remoteConfig, logger); it('existing nodes private keys should not have changed', async () => { const currentNodeIdsPrivateKeysHash = await getNodeAliasesPrivateKeysHash( diff --git a/test/test-utility.ts b/test/test-utility.ts index 4d346dd0e..4ce2e2a93 100644 --- a/test/test-utility.ts +++ b/test/test-utility.ts @@ -28,7 +28,6 @@ import {type PlatformInstaller} from '../src/core/platform-installer.js'; import {type ProfileManager} from '../src/core/profile-manager.js'; import {type LockManager} from '../src/core/lock/lock-manager.js'; import {type CertificateManager} from '../src/core/certificate-manager.js'; -import {type RemoteConfigManager} from '../src/core/config/remote/remote-config-manager.js'; import {Templates} from '../src/core/templates.js'; import {type ConfigManager} from '../src/core/config-manager.js'; import {type ChartManager} from '../src/core/chart-manager.js'; @@ -55,6 +54,7 @@ import {HEDERA_PLATFORM_VERSION} from '../version.js'; import {gte as semVersionGte} from 'semver'; import {type LocalConfigRuntimeState} from '../src/business/runtime-state/config/local/local-config-runtime-state.js'; import {type InstanceOverrides} from '../src/core/dependency-injection/container-init.js'; +import {type RemoteConfigRuntimeStateApi} from '../src/business/runtime-state/api/remote-config-runtime-state-api.js'; export const BASE_TEST_DIR = PathEx.join('test', 'data', 'tmp'); @@ -99,7 +99,7 @@ interface TestOptions { profileManager: ProfileManager; leaseManager: LockManager; certificateManager: CertificateManager; - remoteConfigManager: RemoteConfigManager; + remoteConfig: RemoteConfigRuntimeStateApi; localConfig: LocalConfigRuntimeState; commandInvoker: CommandInvoker; } @@ -172,7 +172,7 @@ export function bootstrapTestVariables( const leaseManager: LockManager = container.resolve(InjectTokens.LockManager); const certificateManager: CertificateManager = container.resolve(InjectTokens.CertificateManager); const localConfig: LocalConfigRuntimeState = container.resolve(InjectTokens.LocalConfigRuntimeState); - const remoteConfigManager: RemoteConfigManager = container.resolve(InjectTokens.RemoteConfigManager); + const remoteConfig: RemoteConfigRuntimeStateApi = container.resolve(InjectTokens.RemoteConfigRuntimeState); const testLogger: SoloLogger = getTestLogger(); const commandInvoker = container.resolve(InjectTokens.CommandInvoker) as CommandInvoker; @@ -192,7 +192,7 @@ export function bootstrapTestVariables( leaseManager, certificateManager, localConfig, - remoteConfigManager, + remoteConfig, commandInvoker, }; @@ -394,7 +394,7 @@ export function endToEndTestSuite( export function balanceQueryShouldSucceed( accountManager: AccountManager, namespace: NamespaceName, - remoteConfigManager: RemoteConfigManager, + remoteConfig: RemoteConfigRuntimeStateApi, logger: SoloLogger, skipNodeAlias?: NodeAlias, ): void { @@ -405,7 +405,7 @@ export function balanceQueryShouldSucceed( await accountManager.refreshNodeClient( namespace, - remoteConfigManager.getClusterRefs(), + remoteConfig.getClusterRefs(), skipNodeAlias, argv.getArg(flags.deployment), ); @@ -427,7 +427,7 @@ export function balanceQueryShouldSucceed( export function accountCreationShouldSucceed( accountManager: AccountManager, namespace: NamespaceName, - remoteConfigManager: RemoteConfigManager, + remoteConfig: RemoteConfigRuntimeStateApi, logger: SoloLogger, skipNodeAlias?: NodeAlias, ): void { @@ -436,7 +436,7 @@ export function accountCreationShouldSucceed( const argv = Argv.getDefaultArgv(namespace); await accountManager.refreshNodeClient( namespace, - remoteConfigManager.getClusterRefs(), + remoteConfig.getClusterRefs(), skipNodeAlias, argv.getArg(flags.deployment), ); diff --git a/test/unit/commands/base.test.ts b/test/unit/commands/base.test.ts index f5f9eeae4..f8d818bd6 100644 --- a/test/unit/commands/base.test.ts +++ b/test/unit/commands/base.test.ts @@ -5,7 +5,6 @@ import {expect} from 'chai'; import {type DependencyManager} from '../../../src/core/dependency-managers/index.js'; import {type ChartManager} from '../../../src/core/chart-manager.js'; import {type ConfigManager} from '../../../src/core/config-manager.js'; -import {RemoteConfigManager} from '../../../src/core/config/remote/remote-config-manager.js'; import {K8Client} from '../../../src/integration/kube/k8-client/k8-client.js'; import {BaseCommand} from '../../../src/commands/base.js'; import {Flags as flags} from '../../../src/commands/flags.js'; @@ -14,15 +13,14 @@ import {container} from 'tsyringe-neo'; import {type SoloLogger} from '../../../src/core/logging/solo-logger.js'; import {resetForTest} from '../../test-container.js'; import {InjectTokens} from '../../../src/core/dependency-injection/inject-tokens.js'; -import {ComponentsDataWrapper} from '../../../src/core/config/remote/components-data-wrapper.js'; -import {createComponentsDataWrapper} from '../core/config/remote/components-data-wrapper.test.js'; import {type ClusterReferences} from '../../../src/types/index.js'; -import {Cluster} from '../../../src/core/config/remote/cluster.js'; import {ConsensusNode} from '../../../src/core/model/consensus-node.js'; import {Argv} from '../../helpers/argv-wrapper.js'; import {type NodeAlias} from '../../../src/types/aliases.js'; import {type HelmClient} from '../../../src/integration/helm/helm-client.js'; import {type LocalConfigRuntimeState} from '../../../src/business/runtime-state/config/local/local-config-runtime-state.js'; +import {type RemoteConfigRuntimeStateApi} from '../../../src/business/runtime-state/api/remote-config-runtime-state-api.js'; +import {RemoteConfigRuntimeState} from '../../../src/business/runtime-state/config/remote/remote-config-runtime-state.js'; describe('BaseCommand', () => { let helm: HelmClient; @@ -30,7 +28,7 @@ describe('BaseCommand', () => { let configManager: ConfigManager; let depManager: DependencyManager; let localConfig: LocalConfigRuntimeState; - let remoteConfigManager: RemoteConfigManager; + let remoteConfig: RemoteConfigRuntimeStateApi; let sandbox = sinon.createSandbox(); let testLogger: SoloLogger; @@ -45,7 +43,7 @@ describe('BaseCommand', () => { configManager = container.resolve(InjectTokens.ConfigManager); depManager = container.resolve(InjectTokens.DependencyManager); localConfig = container.resolve(InjectTokens.LocalConfigRuntimeState); - remoteConfigManager = container.resolve(InjectTokens.RemoteConfigManager); + remoteConfig = container.resolve(InjectTokens.RemoteConfigRuntimeState); sandbox = sinon.createSandbox(); sandbox.stub(K8Client.prototype, 'init').callsFake(() => this); @@ -60,7 +58,7 @@ describe('BaseCommand', () => { configManager, depManager, localConfig, - remoteConfigManager, + remoteConfig, }); await localConfig.load(); @@ -153,12 +151,9 @@ describe('BaseCommand', () => { const depManager = sinon.stub(); const localConfig = sinon.stub() as unknown as LocalConfigRuntimeState; - const { - wrapper: {componentsDataWrapper}, - } = createComponentsDataWrapper(); - - const newComponentsDataWrapper = ComponentsDataWrapper.fromObject(componentsDataWrapper.toObject()); - const remoteConfigManager = sinon.createStubInstance(RemoteConfigManager); + // @ts-expect-error - TS2540: to mock + localConfig.clusterRefs = sandbox.stub().returns({cluster: 'context1', cluster2: 'context2'}); + const remoteConfig = sinon.createStubInstance(RemoteConfigRuntimeState); const mockConsensusNodes = [ new ConsensusNode( @@ -183,26 +178,13 @@ describe('BaseCommand', () => { ), ]; - remoteConfigManager.getConsensusNodes.returns(mockConsensusNodes); - remoteConfigManager.getContexts.returns(mockConsensusNodes.map(node => node.context)); + remoteConfig.getConsensusNodes.returns(mockConsensusNodes); + remoteConfig.getContexts.returns(mockConsensusNodes.map(node => node.context)); const mockedClusterReferenceMap: ClusterReferences = new Map(); mockedClusterReferenceMap.set('cluster', 'context1'); mockedClusterReferenceMap.set('cluster2', 'context2'); - remoteConfigManager.getClusterRefs.returns(mockedClusterReferenceMap); + remoteConfig.getClusterRefs.returns(mockedClusterReferenceMap); - Object.defineProperty(remoteConfigManager, 'components', { - get: () => newComponentsDataWrapper, - }); - remoteConfigManager.isLoaded = sinon.stub<[], boolean>().returns(true); - - const clusters = {}; - const cluster = new Cluster('cluster', 'namespace', 'deployment', undefined, undefined); - clusters[cluster.name] = cluster; - const cluster2 = new Cluster('cluster2', 'namespace', 'deployment', undefined, undefined); - clusters[cluster2.name] = cluster2; - Object.defineProperty(remoteConfigManager, 'clusters', { - get: () => clusters, - }); const k8Factory = sinon.stub(); // @ts-expect-error - allow to create instance of abstract class @@ -214,13 +196,13 @@ describe('BaseCommand', () => { configManager, depManager, localConfig, - remoteConfigManager, + remoteConfig, ); }); it('should return consensus nodes', () => { // @ts-expect-error - TS2445: to access private property - const consensusNodes = baseCmd.remoteConfigManager.getConsensusNodes(); + const consensusNodes = baseCmd.remoteConfig.getConsensusNodes(); expect(consensusNodes).to.be.an('array'); expect(consensusNodes[0].context).to.equal('context1'); expect(consensusNodes[1].context).to.equal('context2'); @@ -236,7 +218,7 @@ describe('BaseCommand', () => { it('should return contexts', () => { // @ts-expect-error - TS2445: to access private property - const contexts = baseCmd.remoteConfigManager.getContexts(); + const contexts = baseCmd.remoteConfig.getContexts(); expect(contexts).to.be.an('array'); expect(contexts[0]).to.equal('context1'); expect(contexts[1]).to.equal('context2'); @@ -245,7 +227,7 @@ describe('BaseCommand', () => { it('should return clusters references', () => { const expectedClusterReferences = {cluster: 'context1', cluster2: 'context2'}; // @ts-expect-error - TS2445: to access private property - const clusterReferences: ClusterReferences = baseCmd.remoteConfigManager.getClusterRefs(); + const clusterReferences: ClusterReferences = baseCmd.remoteConfig.getClusterRefs(); for (const [clusterReference] of clusterReferences) { expect(clusterReferences.get(clusterReference)).to.equal(expectedClusterReferences[clusterReference]); } diff --git a/test/unit/commands/cluster.test.ts b/test/unit/commands/cluster.test.ts index e00a62141..50d668b67 100644 --- a/test/unit/commands/cluster.test.ts +++ b/test/unit/commands/cluster.test.ts @@ -81,9 +81,9 @@ describe('ClusterCommand unit tests', () => { options.chartManager.install = sandbox.stub().returns(true); options.configManager = container.resolve(InjectTokens.ConfigManager); - options.remoteConfigManager = sandbox.stub(); + options.remoteConfig = sandbox.stub(); - options.remoteConfigManager.currentCluster = 'solo-e2e'; + options.remoteConfig.currentCluster = 'solo-e2e'; }); it('Install function is called with expected parameters', async () => { diff --git a/test/unit/commands/network.test.ts b/test/unit/commands/network.test.ts index 95b83b97c..5275e192e 100644 --- a/test/unit/commands/network.test.ts +++ b/test/unit/commands/network.test.ts @@ -11,9 +11,8 @@ import * as constants from '../../../src/core/constants.js'; import {ROOT_DIR} from '../../../src/core/constants.js'; import {type ConfigManager} from '../../../src/core/config-manager.js'; import {type ChartManager} from '../../../src/core/chart-manager.js'; -import {NetworkCommand} from '../../../src/commands/network.js'; +import {NetworkCommand, type NetworkDeployConfigClass} from '../../../src/commands/network.js'; import {type LockManager} from '../../../src/core/lock/lock-manager.js'; -import {type RemoteConfigManager} from '../../../src/core/config/remote/remote-config-manager.js'; import {type ProfileManager} from '../../../src/core/profile-manager.js'; import {type KeyManager} from '../../../src/core/key-manager.js'; import {ListrLock} from '../../../src/core/lock/listr-lock.js'; @@ -41,6 +40,7 @@ import {type InstanceOverrides} from '../../../src/core/dependency-injection/con import {ValueContainer} from '../../../src/core/dependency-injection/value-container.js'; import {type LocalConfigRuntimeState} from '../../../src/business/runtime-state/config/local/local-config-runtime-state.js'; import {type ClusterReferences} from '../../../src/types/index.js'; +import {type RemoteConfigRuntimeState} from '../../../src/business/runtime-state/config/remote/remote-config-runtime-state.js'; import {StringFacade} from '../../../src/business/runtime-state/facade/string-facade.js'; const testName = 'network-cmd-unit'; @@ -81,7 +81,7 @@ describe('NetworkCommand unit tests', () => { const k8SFactoryStub = sinon.stub() as unknown as K8Factory; const clusterChecksStub = sinon.stub() as unknown as ClusterChecks; - const remoteConfigManagerStub = sinon.stub() as unknown as RemoteConfigManager; + const remoteConfigStub = sinon.stub() as unknown as RemoteConfigRuntimeState; const chartManagerStub = sinon.stub() as unknown as ChartManager; const certificateManagerStub = sinon.stub() as unknown as CertificateManager; const profileManagerStub = sinon.stub() as unknown as ProfileManager; @@ -96,8 +96,8 @@ describe('NetworkCommand unit tests', () => { [InjectTokens.K8Factory, new ValueContainer(InjectTokens.K8Factory, k8SFactoryStub)], [InjectTokens.ClusterChecks, new ValueContainer(InjectTokens.ClusterChecks, clusterChecksStub)], [ - InjectTokens.RemoteConfigManager, - new ValueContainer(InjectTokens.RemoteConfigManager, remoteConfigManagerStub), + InjectTokens.RemoteConfigRuntimeState, + new ValueContainer(InjectTokens.RemoteConfigRuntimeState, remoteConfigStub), ], [InjectTokens.ChartManager, new ValueContainer(InjectTokens.ChartManager, chartManagerStub)], [InjectTokens.CertificateManager, new ValueContainer(InjectTokens.CertificateManager, certificateManagerStub)], @@ -195,10 +195,10 @@ describe('NetworkCommand unit tests', () => { options.chartManager.install = sinon.stub().returns(true); options.chartManager.uninstall = sinon.stub().returns(true); - options.remoteConfigManager = container.resolve(InjectTokens.RemoteConfigManager); - options.remoteConfigManager.isLoaded = sinon.stub().returns(true); - options.remoteConfigManager.getConfigMap = sinon.stub().returns(null); - options.remoteConfigManager.modify = sinon.stub(); + options.remoteConfig = container.resolve(InjectTokens.RemoteConfigRuntimeState); + options.remoteConfig.isLoaded = sinon.stub().returns(true); + options.remoteConfig.getConfigMap = sinon.stub().returns(null); + options.remoteConfig.modify = sinon.stub(); options.localConfig.configuration.clusterRefs.set('solo-e2e', new StringFacade('context-1')); @@ -218,10 +218,10 @@ describe('NetworkCommand unit tests', () => { it('Install function is called with expected parameters', async () => { try { const networkCommand = container.resolve(NetworkCommand); - options.remoteConfigManager.getConsensusNodes = sinon.stub().returns([{name: 'node1'}]); - options.remoteConfigManager.getContexts = sinon.stub().returns(['context1']); + options.remoteConfig.getConsensusNodes = sinon.stub().returns([{name: 'node1'}]); + options.remoteConfig.getContexts = sinon.stub().returns(['context1']); const stubbedClusterReferences: ClusterReferences = new Map([['solo-e2e', 'context1']]); - options.remoteConfigManager.getClusterRefs = sinon.stub().returns(stubbedClusterReferences); + options.remoteConfig.getClusterRefs = sinon.stub().returns(stubbedClusterReferences); await networkCommand.deploy(argv.build()); @@ -240,10 +240,10 @@ describe('NetworkCommand unit tests', () => { argv.setArg(flags.force, true); const networkCommand = container.resolve(NetworkCommand); - options.remoteConfigManager.getConsensusNodes = sinon.stub().returns([{name: 'node1'}]); - options.remoteConfigManager.getContexts = sinon.stub().returns(['context1']); + options.remoteConfig.getConsensusNodes = sinon.stub().returns([{name: 'node1'}]); + options.remoteConfig.getContexts = sinon.stub().returns(['context1']); const stubbedClusterReferences: ClusterReferences = new Map([['solo-e2e', 'context1']]); - options.remoteConfigManager.getClusterRefs = sinon.stub().returns(stubbedClusterReferences); + options.remoteConfig.getClusterRefs = sinon.stub().returns(stubbedClusterReferences); await networkCommand.deploy(argv.build()); expect(options.chartManager.install.args[0][0].name).to.equal('solo-e2e'); @@ -266,15 +266,17 @@ describe('NetworkCommand unit tests', () => { const task = sinon.stub(); - options.remoteConfigManager.getConsensusNodes = sinon + options.remoteConfig.getConsensusNodes = sinon .stub() .returns([new ConsensusNode('node1', 0, 'solo-e2e', 'cluster', 'context-1', 'base', 'pattern', 'fqdn')]); - options.remoteConfigManager.getContexts = sinon.stub().returns(['context-1']); + + options.remoteConfig.getContexts = sinon.stub().returns(['context-1']); const stubbedClusterReferences: ClusterReferences = new Map([['cluster', 'context1']]); - options.remoteConfigManager.getClusterRefs = sinon.stub().returns(stubbedClusterReferences); + options.remoteConfig.getClusterRefs = sinon.stub().returns(stubbedClusterReferences); const networkCommand = container.resolve(NetworkCommand); - const config = await networkCommand.prepareConfig(task, argv.build()); + // @ts-expect-error - to access private method + const config: NetworkDeployConfigClass = await networkCommand.prepareConfig(task, argv.build()); expect(config.valuesArgMap).to.not.empty; expect(config.valuesArgMap['cluster']).to.not.empty; diff --git a/test/unit/core/config/remote/cluster.test.ts b/test/unit/core/config/remote/cluster.test.ts deleted file mode 100644 index 2d4f93680..000000000 --- a/test/unit/core/config/remote/cluster.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {it} from 'mocha'; -import {expect} from 'chai'; -import {SoloError} from '../../../../../src/core/errors/solo-error.js'; -import {Cluster} from '../../../../../src/core/config/remote/cluster.js'; -import {type ClusterReference} from '../../../../../src/types/index.js'; - -describe('Cluster', () => { - it('should fail if name is not provided', () => { - expect(() => new Cluster(null, 'valid', 'valid')).to.throw(SoloError, 'name is required'); - expect(() => new Cluster('', 'valid', 'valid')).to.throw(SoloError, 'name is required'); - }); - - it('should fail if name is not a string', () => { - const name = 1; // @ts-ignore - expect(() => new Cluster(name, 'valid', 'valid')).to.throw(SoloError, 'Invalid type for name: number'); - }); - - it('should fail if namespace is not provided', () => { - expect(() => new Cluster('valid', null, 'valid')).to.throw(SoloError, 'namespace is required'); - expect(() => new Cluster('valid', '', 'valid')).to.throw(SoloError, 'namespace is required'); - }); - - it('should fail if namespace is not a string', () => { - const namespace = 1; // @ts-ignore - expect(() => new Cluster('valid', namespace, 'valid')).to.throw(SoloError, 'Invalid type for namespace: number'); - }); - - it('should convert to an object', () => { - const c = new Cluster('name', 'namespace', 'deployment', 'cluster.world', 'network.svc'); - const o = c.toObject(); - expect(o.name).to.equal('name'); - expect(o.namespace).to.equal('namespace'); - expect(o.deployment).to.equal('deployment'); - expect(o.dnsBaseDomain).to.equal('cluster.world'); - expect(o.dnsConsensusNodePattern).to.equal('network.svc'); - }); - - it('should convert clusters map to an object', () => { - const map1: Record = { - cluster1: new Cluster('name1', 'namespace1', 'deployment1', 'cluster1.world', 'network1.svc'), - cluster2: new Cluster('name2', 'namespace2', 'deployment2', 'cluster2.world', 'network2.svc'), - }; - - const o = Cluster.toClustersMapObject(map1); - expect(o.cluster1.name).to.equal('name1'); - expect(o.cluster1.namespace).to.equal('namespace1'); - expect(o.cluster1.deployment).to.equal('deployment1'); - expect(o.cluster1.dnsBaseDomain).to.equal('cluster1.world'); - expect(o.cluster1.dnsConsensusNodePattern).to.equal('network1.svc'); - expect(o.cluster2.name).to.equal('name2'); - expect(o.cluster2.namespace).to.equal('namespace2'); - expect(o.cluster2.deployment).to.equal('deployment2'); - expect(o.cluster2.dnsBaseDomain).to.equal('cluster2.world'); - expect(o.cluster2.dnsConsensusNodePattern).to.equal('network2.svc'); - - const map2 = Cluster.fromClustersMapObject(o); - expect(map2.cluster1.name).to.equal(map1.cluster1.name); - expect(map2.cluster1.namespace).to.equal(map1.cluster1.namespace); - expect(map2.cluster1.deployment).to.equal(map1.cluster1.deployment); - expect(map2.cluster1.dnsBaseDomain).to.equal(map1.cluster1.dnsBaseDomain); - expect(map2.cluster1.dnsConsensusNodePattern).to.equal(map1.cluster1.dnsConsensusNodePattern); - expect(map2.cluster2.name).to.equal(map1.cluster2.name); - expect(map2.cluster2.namespace).to.equal(map1.cluster2.namespace); - expect(map2.cluster2.deployment).to.equal(map1.cluster2.deployment); - expect(map2.cluster2.dnsBaseDomain).to.equal(map1.cluster2.dnsBaseDomain); - expect(map2.cluster2.dnsConsensusNodePattern).to.equal(map1.cluster2.dnsConsensusNodePattern); - }); -}); diff --git a/test/unit/core/config/remote/components-data-wrapper.test.ts b/test/unit/core/config/remote/components-data-wrapper.test.ts index 8cf218dff..bf9e5283b 100644 --- a/test/unit/core/config/remote/components-data-wrapper.test.ts +++ b/test/unit/core/config/remote/components-data-wrapper.test.ts @@ -4,190 +4,204 @@ import {expect} from 'chai'; import {describe, it} from 'mocha'; import {ComponentsDataWrapper} from '../../../../../src/core/config/remote/components-data-wrapper.js'; -import {HaProxyComponent} from '../../../../../src/core/config/remote/components/ha-proxy-component.js'; -import {MirrorNodeComponent} from '../../../../../src/core/config/remote/components/mirror-node-component.js'; -import {EnvoyProxyComponent} from '../../../../../src/core/config/remote/components/envoy-proxy-component.js'; -import {ConsensusNodeComponent} from '../../../../../src/core/config/remote/components/consensus-node-component.js'; -import {MirrorNodeExplorerComponent} from '../../../../../src/core/config/remote/components/mirror-node-explorer-component.js'; -import {RelayComponent} from '../../../../../src/core/config/remote/components/relay-component.js'; import {SoloError} from '../../../../../src/core/errors/solo-error.js'; -import {type NodeAliases} from '../../../../../src/types/aliases.js'; -import {ConsensusNodeStates} from '../../../../../src/core/config/remote/enumerations/consensus-node-states.js'; import {ComponentTypes} from '../../../../../src/core/config/remote/enumerations/component-types.js'; - -export function createComponentsDataWrapper() { - const name = 'name'; - const serviceName = name; - - const cluster = 'cluster'; - const namespace = 'namespace'; - const state = ConsensusNodeStates.STARTED; - const consensusNodeAliases = ['node1', 'node2'] as NodeAliases; - - const relays = {[serviceName]: new RelayComponent(name, cluster, namespace, consensusNodeAliases)}; - const haProxies = { - [serviceName]: new HaProxyComponent(name, cluster, namespace), - ['serviceName2']: new HaProxyComponent('name2', 'cluster2', namespace), +import {type NodeId} from '../../../../../src/types/aliases.js'; +import {type ClusterReference, type ComponentId, type NamespaceNameAsString} from '../../../../../src/types/index.js'; +import {DeploymentPhase} from '../../../../../src/data/schema/model/remote/deployment-phase.js'; +import {ComponentStateMetadataSchema} from '../../../../../src/data/schema/model/remote/state/component-state-metadata-schema.js'; +import {LedgerPhase} from '../../../../../src/data/schema/model/remote/ledger-phase.js'; +import {type ComponentsDataWrapperApi} from '../../../../../src/core/config/remote/api/components-data-wrapper-api.js'; +import {RelayNodeStateSchema} from '../../../../../src/data/schema/model/remote/state/relay-node-state-schema.js'; +import {HAProxyStateSchema} from '../../../../../src/data/schema/model/remote/state/haproxy-state-schema.js'; +import {MirrorNodeStateSchema} from '../../../../../src/data/schema/model/remote/state/mirror-node-state-schema.js'; +import {EnvoyProxyStateSchema} from '../../../../../src/data/schema/model/remote/state/envoy-proxy-state-schema.js'; +import {ConsensusNodeStateSchema} from '../../../../../src/data/schema/model/remote/state/consensus-node-state-schema.js'; +import {ExplorerStateSchema} from '../../../../../src/data/schema/model/remote/state/explorer-state-schema.js'; +import {BlockNodeStateSchema} from '../../../../../src/data/schema/model/remote/state/block-node-state-schema.js'; +import {DeploymentStateSchema} from '../../../../../src/data/schema/model/remote/deployment-state-schema.js'; +import {RemoteConfigSchema} from '../../../../../src/data/schema/model/remote/remote-config-schema.js'; + +export function createComponentsDataWrapper(): { + values: { + id: ComponentId; + cluster: ClusterReference; + namespace: NamespaceNameAsString; + phase: DeploymentPhase.DEPLOYED; + consensusNodeIds: NodeId[]; }; - const mirrorNodes = {[serviceName]: new MirrorNodeComponent(name, cluster, namespace)}; - const envoyProxies = { - [serviceName]: new EnvoyProxyComponent(name, cluster, namespace), - ['serviceName2']: new EnvoyProxyComponent('name2', 'cluster2', namespace), + components: { + relays: RelayNodeStateSchema[]; + haProxies: HAProxyStateSchema[]; + mirrorNodes: MirrorNodeStateSchema[]; + envoyProxies: EnvoyProxyStateSchema[]; + consensusNodes: ConsensusNodeStateSchema[]; + explorers: ExplorerStateSchema[]; + blockNodes: BlockNodeStateSchema[]; }; - const consensusNodes = { - [serviceName]: new ConsensusNodeComponent(name, cluster, namespace, state, 0), - ['serviceName2']: new ConsensusNodeComponent('node2', 'cluster2', namespace, state, 1), - }; - const mirrorNodeExplorers = {[serviceName]: new MirrorNodeExplorerComponent(name, cluster, namespace)}; - - // @ts-expect-error - TS267: to access private constructor - const componentsDataWrapper = new ComponentsDataWrapper( + wrapper: {componentsDataWrapper: ComponentsDataWrapperApi}; + componentId: ComponentId; +} { + const id: ComponentId = 0; + const componentId: ComponentId = id; + + const cluster: ClusterReference = 'cluster'; + const namespace: NamespaceNameAsString = 'namespace'; + const phase: DeploymentPhase = DeploymentPhase.DEPLOYED; + const consensusNodeIds: NodeId[] = [0, 1]; + + const metadata: ComponentStateMetadataSchema = new ComponentStateMetadataSchema(id, namespace, cluster, phase); + + const relays: RelayNodeStateSchema[] = [new RelayNodeStateSchema(metadata, consensusNodeIds)]; + const haProxies: HAProxyStateSchema[] = [new HAProxyStateSchema(metadata)]; + const mirrorNodes: MirrorNodeStateSchema[] = [new MirrorNodeStateSchema(metadata)]; + const envoyProxies: EnvoyProxyStateSchema[] = [new EnvoyProxyStateSchema(metadata)]; + const consensusNodes: ConsensusNodeStateSchema[] = [new ConsensusNodeStateSchema(metadata)]; + const explorers: ExplorerStateSchema[] = [new ExplorerStateSchema(metadata)]; + const blockNodes: BlockNodeStateSchema[] = [new BlockNodeStateSchema(metadata)]; + + const deploymentState: DeploymentStateSchema = new DeploymentStateSchema( + LedgerPhase.INITIALIZED, + consensusNodes, + blockNodes, + mirrorNodes, relays, haProxies, - mirrorNodes, envoyProxies, - consensusNodes, - mirrorNodeExplorers, + explorers, + ); + + const remoteConfig: RemoteConfigSchema = new RemoteConfigSchema( + undefined, + undefined, + undefined, + undefined, + deploymentState, ); - /* - ? The class after calling the toObject() method - * RELAY: { serviceName: { name: 'name', cluster: 'cluster', namespace: 'namespace' consensusNodeAliases: ['node1', 'node2'] } }, - * HAPROXY: { serviceName: { name: 'name', cluster: 'cluster', namespace: 'namespace' } }, - * MIRROR_NODE: { serviceName: { name: 'name', cluster: 'cluster', namespace: 'namespace' } }, - * ENVOY_PROXY: { serviceName: { name: 'name', cluster: 'cluster', namespace: 'namespace' } }, - * CONSENSUS_NODE: { serviceName: { state: 'started', name: 'name', cluster: 'cluster', namespace: 'namespace'} }, - * MIRROR_NODE_EXPLORER: { serviceName: { name: 'name', cluster: 'cluster', namespace: 'namespace' } }, - */ + + const componentsDataWrapper: ComponentsDataWrapperApi = new ComponentsDataWrapper(remoteConfig.state); + return { - values: {name, cluster, namespace, state, consensusNodeAliases}, - components: {consensusNodes, haProxies, envoyProxies, mirrorNodes, mirrorNodeExplorers, relays}, + values: {id, cluster, namespace, phase, consensusNodeIds}, + components: {consensusNodes, haProxies, envoyProxies, mirrorNodes, explorers, relays, blockNodes}, wrapper: {componentsDataWrapper}, - serviceName, + componentId, }; } describe('ComponentsDataWrapper', () => { it('should be able to create a instance', () => createComponentsDataWrapper()); - it('should not be able to create a instance if wrong data is passed to constructor', () => { - // @ts-expect-error - TS267: to access private constructor - expect(() => new ComponentsDataWrapper({serviceName: {}})).to.throw(SoloError, 'Invalid component type'); - }); - - it('toObject method should return a object that can be parsed with fromObject', () => { + it('should not be able to add new component with the .addNewComponent() method if it already exist', () => { const { wrapper: {componentsDataWrapper}, + components: {consensusNodes}, } = createComponentsDataWrapper(); - const newComponentsDataWrapper = ComponentsDataWrapper.fromObject(componentsDataWrapper.toObject()); - const componentsDataWrapperObject = componentsDataWrapper.toObject(); - - expect(componentsDataWrapperObject).to.deep.equal(newComponentsDataWrapper.toObject()); + const existingComponent: ConsensusNodeStateSchema = consensusNodes[0]; - for (const type of Object.values(ComponentTypes)) { - expect(componentsDataWrapperObject).to.have.ownProperty(type); - } - - expect(componentsDataWrapper); + expect(() => componentsDataWrapper.addNewComponent(existingComponent, ComponentTypes.ConsensusNode)).to.throw( + SoloError, + 'Component exists', + ); }); - it('should not be able to add new component with the .add() method if it already exist', () => { + it('should be able to add new component with the .addNewComponent() method', () => { const { wrapper: {componentsDataWrapper}, - components: {consensusNodes}, - serviceName, } = createComponentsDataWrapper(); - const existingComponent = consensusNodes[serviceName]; + const newComponentId: ComponentId = 1; + const {id, cluster, namespace, phase} = { + id: newComponentId, + cluster: 'cluster', + namespace: 'new-namespace', + phase: DeploymentPhase.DEPLOYED, + }; + + const metadata: ComponentStateMetadataSchema = new ComponentStateMetadataSchema(id, namespace, cluster, phase); + const newComponent: EnvoyProxyStateSchema = new EnvoyProxyStateSchema(metadata); - expect(() => componentsDataWrapper.add(existingComponent)).to.throw(SoloError, 'Component exists'); + componentsDataWrapper.addNewComponent(newComponent, ComponentTypes.EnvoyProxy); + + expect(componentsDataWrapper.state.envoyProxies).to.have.lengthOf(2); }); - it('should be able to add new component with the .add() method', () => { + it('should be able to change node state with the .changeNodeState(()', () => { const { wrapper: {componentsDataWrapper}, + componentId, } = createComponentsDataWrapper(); - const newServiceName = 'envoy'; - const {name, cluster, namespace} = { - name: newServiceName, - cluster: 'cluster', - namespace: 'new-namespace', - }; - const newComponent = new EnvoyProxyComponent(name, cluster, namespace); - - componentsDataWrapper.add(newComponent); + const newNodeState: DeploymentPhase = DeploymentPhase.STOPPED; - const componentDataWrapperObject = componentsDataWrapper.toObject(); + componentsDataWrapper.changeNodePhase(componentId, newNodeState); - expect(componentDataWrapperObject[ComponentTypes.EnvoyProxy]).has.own.property(newServiceName); + expect(componentsDataWrapper.state.consensusNodes[0].metadata.phase).to.equal(newNodeState); + }); - expect(componentDataWrapperObject[ComponentTypes.EnvoyProxy][newServiceName]).to.deep.equal({ - name, - cluster, - namespace, - }); + it("should not be able to edit component with the .editComponent() if it doesn't exist ", () => { + const { + wrapper: {componentsDataWrapper}, + } = createComponentsDataWrapper(); + const notFoundComponentId: ComponentId = 9; - expect(Object.values(componentDataWrapperObject[ComponentTypes.EnvoyProxy])).to.have.lengthOf(3); + expect(() => componentsDataWrapper.changeNodePhase(notFoundComponentId, DeploymentPhase.FROZEN)).to.throw( + SoloError, + `Consensus node ${notFoundComponentId} doesn't exist`, + ); }); - it('should be able to edit component with the .edit()', () => { + it('should be able to remove component with the .removeComponent()', () => { const { wrapper: {componentsDataWrapper}, components: {relays}, - values: {cluster, namespace}, - serviceName, + componentId, } = createComponentsDataWrapper(); - const relayComponent = relays[serviceName]; - - componentsDataWrapper.edit(relayComponent); - - const newCluster = 'newCluster'; - const newReplayComponent = new RelayComponent(relayComponent.name, newCluster, namespace); + componentsDataWrapper.removeComponent(componentId, ComponentTypes.RelayNodes); - componentsDataWrapper.edit(newReplayComponent); - - expect(componentsDataWrapper.toObject()[ComponentTypes.Relay][relayComponent.name].cluster).to.equal(newCluster); + expect(relays).to.not.have.own.property(componentId.toString()); }); - it("should not be able to edit component with the .edit() if it doesn't exist ", () => { + it("should not be able to remove component with the .removeComponent() if it doesn't exist ", () => { const { wrapper: {componentsDataWrapper}, - components: {relays}, - serviceName, } = createComponentsDataWrapper(); - const notFoundServiceName = 'not_found'; - const relay = relays[serviceName]; - relay.name = notFoundServiceName; - expect(() => componentsDataWrapper.edit(relay)).to.throw( + const notFoundComponentId: ComponentId = 9; + + expect(() => componentsDataWrapper.removeComponent(notFoundComponentId, ComponentTypes.RelayNodes)).to.throw( SoloError, - `Component doesn't exist, name: ${notFoundServiceName}`, + `Component ${notFoundComponentId} of type ${ComponentTypes.RelayNodes} not found while attempting to remove`, ); }); - it('should be able to remove component with the .remove()', () => { + it('should be able to get components with .getComponent()', () => { const { wrapper: {componentsDataWrapper}, - serviceName, + componentId, + components: {mirrorNodes}, } = createComponentsDataWrapper(); - componentsDataWrapper.remove(serviceName, ComponentTypes.Relay); + const mirrorNodeComponent: MirrorNodeStateSchema = componentsDataWrapper.getComponent( + ComponentTypes.MirrorNode, + componentId, + ); - expect(componentsDataWrapper.relays).not.to.have.own.property(serviceName); + expect(mirrorNodes[componentId].metadata.id).to.deep.equal(mirrorNodeComponent.metadata.id); }); - it("should not be able to remove component with the .remove() if it doesn't exist ", () => { + it("should fail if trying to get component that doesn't exist with .getComponent()", () => { const { wrapper: {componentsDataWrapper}, } = createComponentsDataWrapper(); - const notFoundServiceName = 'not_found'; + const notFoundComponentId: ComponentId = 9; + const type: ComponentTypes = ComponentTypes.MirrorNode; - expect(() => componentsDataWrapper.remove(notFoundServiceName, ComponentTypes.Relay)).to.throw( - SoloError, - `Component ${notFoundServiceName} of type ${ComponentTypes.Relay} not found while attempting to remove`, + expect(() => componentsDataWrapper.getComponent(type, notFoundComponentId)).to.throw( + `Component ${notFoundComponentId} of type ${type} not found while attempting to read`, ); }); }); diff --git a/test/unit/core/config/remote/components/components.test.ts b/test/unit/core/config/remote/components/components.test.ts deleted file mode 100644 index a87fbce6c..000000000 --- a/test/unit/core/config/remote/components/components.test.ts +++ /dev/null @@ -1,260 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {expect} from 'chai'; -import {describe, it} from 'mocha'; - -import {RelayComponent} from '../../../../../../src/core/config/remote/components/relay-component.js'; -import {BaseComponent} from '../../../../../../src/core/config/remote/components/base-component.js'; -import {ConsensusNodeComponent} from '../../../../../../src/core/config/remote/components/consensus-node-component.js'; -import {HaProxyComponent} from '../../../../../../src/core/config/remote/components/ha-proxy-component.js'; -import {EnvoyProxyComponent} from '../../../../../../src/core/config/remote/components/envoy-proxy-component.js'; -import {MirrorNodeComponent} from '../../../../../../src/core/config/remote/components/mirror-node-component.js'; -import {MirrorNodeExplorerComponent} from '../../../../../../src/core/config/remote/components/mirror-node-explorer-component.js'; -import {SoloError} from '../../../../../../src/core/errors/solo-error.js'; -import {type NodeAliases} from '../../../../../../src/types/aliases.js'; -import {Templates} from '../../../../../../src/core/templates.js'; -import {ConsensusNodeStates} from '../../../../../../src/core/config/remote/enumerations/consensus-node-states.js'; - -function testBaseComponentData(classComponent: any) { - const validNamespace = 'valid'; - it('should fail if name is not provided', () => { - const name = ''; - expect(() => new classComponent(name, 'valid', validNamespace)).to.throw(SoloError, `Invalid name: ${name}`); - }); - - it('should fail if name is string', () => { - const name = 1; // @ts-ignore - expect(() => new classComponent(name, 'valid', validNamespace)).to.throw(SoloError, `Invalid name: ${name}`); - }); - - it('should fail if cluster is not provided', () => { - const cluster = ''; - expect(() => new classComponent('valid', cluster, validNamespace)).to.throw( - SoloError, - `Invalid cluster: ${cluster}`, - ); - }); - - it('should fail if cluster is string', () => { - const cluster = 1; - expect(() => new classComponent('valid', cluster, validNamespace)).to.throw( - SoloError, - `Invalid cluster: ${cluster}`, - ); - }); - - it('should fail if namespace is not provided', () => { - const namespace = ''; - expect(() => new classComponent('valid', 'valid', namespace)).to.throw( - SoloError, - `Invalid namespace: ${namespace}`, - ); - }); - - it('should fail if namespace is string', () => { - const namespace = 1; - expect(() => new classComponent('valid', 'valid', namespace)).to.throw( - SoloError, - `Invalid namespace: ${namespace}`, - ); - }); - - it('should successfully create ', () => { - new classComponent('valid', 'valid', 'valid'); - }); - - it('should be an instance of BaseComponent', () => { - const component = new classComponent('valid', 'valid', validNamespace); - expect(component).to.be.instanceOf(BaseComponent); - }); - - it('calling toObject() should return a valid data', () => { - const {name, cluster, namespace} = {name: 'name', cluster: 'cluster', namespace: 'namespace'}; - const component = new classComponent(name, cluster, namespace); - expect(component.toObject()).to.deep.equal({name, cluster, namespace}); - }); -} - -describe('HaProxyComponent', () => testBaseComponentData(HaProxyComponent)); - -describe('EnvoyProxyComponent', () => testBaseComponentData(EnvoyProxyComponent)); - -describe('MirrorNodeComponent', () => testBaseComponentData(MirrorNodeComponent)); - -describe('MirrorNodeExplorerComponent', () => testBaseComponentData(MirrorNodeExplorerComponent)); - -describe('RelayComponent', () => { - it('should fail if name is not provided', () => { - const name = ''; - expect(() => new RelayComponent(name, 'valid', 'valid', [])).to.throw(SoloError, `Invalid name: ${name}`); - }); - - it('should fail if name is string', () => { - const name = 1; - // @ts-expect-error - TS2345: Argument of type number is not assignable to parameter of type string - expect(() => new RelayComponent(name, 'valid', 'valid', [])).to.throw(SoloError, `Invalid name: ${name}`); - }); - - it('should fail if cluster is not provided', () => { - const cluster = ''; - expect(() => new RelayComponent('valid', cluster, 'valid', [])).to.throw(SoloError, `Invalid cluster: ${cluster}`); - }); - - it('should fail if cluster is string', () => { - const cluster = 1; - // @ts-expect-error - TS2345: Argument of type number is not assignable to parameter of type string - expect(() => new RelayComponent('valid', cluster, 'valid', [])).to.throw(SoloError, `Invalid cluster: ${cluster}`); - }); - - it('should fail if namespace is not provided', () => { - const namespace = null; - expect(() => new RelayComponent('valid', 'valid', namespace, [])).to.throw( - SoloError, - `Invalid namespace: ${namespace}`, - ); - }); - - it('should fail if namespace is string', () => { - const namespace = 1; - // @ts-expect-error - forcefully provide namespace as a number to create an error - expect(() => new RelayComponent('valid', 'valid', namespace, [])).to.throw( - SoloError, - `Invalid namespace: ${namespace}`, - ); - }); - - it('should fail if consensusNodeAliases is not valid', () => { - const consensusNodeAliases = [undefined] as NodeAliases; - expect(() => new RelayComponent('valid', 'valid', 'valid', consensusNodeAliases)).to.throw( - SoloError, - `Invalid consensus node alias: ${consensusNodeAliases[0]}, aliases ${consensusNodeAliases}`, - ); - }); - - it('should fail if consensusNodeAliases is not valid', () => { - const consensusNodeAliases = ['node1', 1] as NodeAliases; - expect(() => new RelayComponent('valid', 'valid', 'valid', consensusNodeAliases)).to.throw( - SoloError, - `Invalid consensus node alias: 1, aliases ${consensusNodeAliases}`, - ); - }); - - it('should successfully create ', () => { - new RelayComponent('valid', 'valid', 'valid'); - }); - - it('should be an instance of BaseComponent', () => { - const component = new RelayComponent('valid', 'valid', 'valid'); - expect(component).to.be.instanceOf(BaseComponent); - }); - - it('calling toObject() should return a valid data', () => { - const {name, cluster, namespace, consensusNodeAliases} = { - name: 'name', - cluster: 'cluster', - namespace: 'namespace', - consensusNodeAliases: ['node1'] as NodeAliases, - }; - - const component = new RelayComponent(name, cluster, namespace, consensusNodeAliases); - expect(component.toObject()).to.deep.equal({name, cluster, namespace: namespace, consensusNodeAliases}); - }); -}); - -describe('ConsensusNodeComponent', () => { - it('should fail if name is not provided', () => { - const name = ''; - expect(() => new ConsensusNodeComponent(name, 'valid', 'valid', ConsensusNodeStates.STARTED, 0)).to.throw( - SoloError, - `Invalid name: ${name}`, - ); - }); - - it('should fail if name is not a string', () => { - const name = 1; // @ts-ignore - expect(() => new ConsensusNodeComponent(name, 'valid', 'valid', ConsensusNodeStates.STARTED, 0)).to.throw( - SoloError, - `Invalid name: ${name}`, - ); - }); - - it('should fail if cluster is not provided', () => { - const cluster = ''; - expect(() => new ConsensusNodeComponent('valid', cluster, 'valid', ConsensusNodeStates.STARTED, 0)).to.throw( - SoloError, - `Invalid cluster: ${cluster}`, - ); - }); - - it('should fail if cluster is not a string', () => { - const cluster = 1; // @ts-ignore - expect(() => new ConsensusNodeComponent('valid', cluster, 'valid', ConsensusNodeStates.STARTED, 0)).to.throw( - SoloError, - `Invalid cluster: ${cluster}`, - ); - }); - - it('should fail if namespace is not provided', () => { - const namespace = null; - expect(() => new ConsensusNodeComponent('valid', 'valid', namespace, ConsensusNodeStates.STARTED, 0)).to.throw( - SoloError, - `Invalid namespace: ${namespace}`, - ); - }); - - it('should fail if namespace is not a string', () => { - const namespace = 1; // @ts-ignore - expect(() => new ConsensusNodeComponent('valid', 'valid', namespace, ConsensusNodeStates.STARTED, 0)).to.throw( - SoloError, - `Invalid namespace: ${namespace}`, - ); - }); - - it('should fail if state is not valid', () => { - const state = 'invalid' as ConsensusNodeStates.STARTED; - expect(() => new ConsensusNodeComponent('valid', 'valid', 'valid', state, 0)).to.throw( - SoloError, - `Invalid consensus node state: ${state}`, - ); - }); - - it('should fail if nodeId is not a number', () => { - const nodeId = 'invalid'; // @ts-ignore - expect(() => new ConsensusNodeComponent('valid', 'valid', 'valid', ConsensusNodeStates.STARTED, nodeId)).to.throw( - SoloError, - `Invalid node id. It must be a number: ${nodeId}`, - ); - }); - - it('should fail if nodeId is negative', () => { - const nodeId = -1; // @ts-ignore - expect(() => new ConsensusNodeComponent('valid', 'valid', 'valid', ConsensusNodeStates.STARTED, nodeId)).to.throw( - SoloError, - `Invalid node id. It cannot be negative: ${nodeId}`, - ); - }); - - it('should successfully create ', () => { - new ConsensusNodeComponent('valid', 'valid', 'valid', ConsensusNodeStates.STARTED, 0); - }); - - it('should be an instance of BaseComponent', () => { - const component = new ConsensusNodeComponent('valid', 'valid', 'valid', ConsensusNodeStates.STARTED, 0); - expect(component).to.be.instanceOf(BaseComponent); - }); - - it('calling toObject() should return a valid data', () => { - const nodeAlias = 'node1'; - const nodeInfo = { - name: nodeAlias, - cluster: 'cluster', - namespace: 'namespace', - state: ConsensusNodeStates.STARTED, - nodeId: Templates.nodeIdFromNodeAlias(nodeAlias), - }; - - const {name, cluster, namespace, state, nodeId} = nodeInfo; - const component = new ConsensusNodeComponent(name, cluster, namespace, state, nodeId); - expect(component.toObject()).to.deep.equal(nodeInfo); - }); -}); diff --git a/test/unit/core/config/remote/metadata.test.ts b/test/unit/core/config/remote/metadata.test.ts deleted file mode 100644 index 59bfe3e65..000000000 --- a/test/unit/core/config/remote/metadata.test.ts +++ /dev/null @@ -1,202 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {expect} from 'chai'; -import {describe, it} from 'mocha'; -import {Migration} from '../../../../../src/core/config/remote/migration.js'; -import {SoloError} from '../../../../../src/core/errors/solo-error.js'; -import {RemoteConfigMetadata} from '../../../../../src/core/config/remote/metadata.js'; -import {type NamespaceNameAsString, type Version} from '../../../../../src/types/index.js'; -import {UserIdentitySchema} from '../../../../../src/data/schema/model/common/user-identity-schema.js'; -import {DeploymentStates} from '../../../../../src/core/config/remote/enumerations/deployment-states.js'; - -export function createMetadata() { - const namespace: NamespaceNameAsString = 'namespace'; - const deploymentName = 'kind-namespace'; - const state = DeploymentStates.PRE_GENESIS; - const lastUpdatedAt: Date = new Date(); - const lastUpdateBy: UserIdentitySchema = new UserIdentitySchema('test'); - const soloVersion: Version = '0.0.1'; - const migration = new Migration(lastUpdatedAt, lastUpdateBy, '0.0.0'); - const soloChartVersion = ''; - const hederaPlatformVersion = ''; - const hederaMirrorNodeChartVersion = ''; - const explorerChartVersion = ''; - const hederaJsonRpcRelayChartVersion = ''; - - return { - metadata: new RemoteConfigMetadata( - namespace, - deploymentName, - state, - lastUpdatedAt, - lastUpdateBy, - soloVersion, - '', - '', - '', - '', - '', - migration, - ), - values: { - namespace, - deploymentName, - state, - lastUpdatedAt, - lastUpdateBy, - migration, - soloVersion, - soloChartVersion, - hederaPlatformVersion, - hederaMirrorNodeChartVersion, - explorerChartVersion, - hederaJsonRpcRelayChartVersion, - }, - migration, - }; -} - -describe('RemoteConfigMetadata', () => { - it('should be able to create new instance of the class with valid data', () => { - expect(() => createMetadata()).not.to.throw(); - }); - - it('toObject method should return a valid object', () => { - const { - metadata, - migration, - values: { - namespace, - deploymentName, - state, - lastUpdatedAt, - lastUpdateBy, - soloVersion, - soloChartVersion, - hederaPlatformVersion, - hederaMirrorNodeChartVersion, - explorerChartVersion, - hederaJsonRpcRelayChartVersion, - }, - } = createMetadata(); - - expect(metadata.toObject()).to.deep.equal({ - namespace, - deploymentName, - state, - lastUpdatedAt, - lastUpdateBy, - soloVersion, - soloChartVersion, - hederaPlatformVersion, - hederaMirrorNodeChartVersion, - explorerChartVersion, - hederaJsonRpcRelayChartVersion, - migration: migration.toObject(), - }); - }); - - it('should successfully create instance using fromObject', () => { - const { - metadata, - values: {namespace, deploymentName, lastUpdatedAt, lastUpdateBy, soloVersion, state}, - } = createMetadata(); - - // @ts-expect-error - TS234: to access private property - delete metadata._migration; - - const newMetadata = RemoteConfigMetadata.fromObject({ - namespace, - deploymentName, - state, - lastUpdatedAt, - lastUpdateBy, - soloVersion, - soloChartVersion: '', - hederaPlatformVersion: '', - hederaMirrorNodeChartVersion: '', - explorerChartVersion: '', - hederaJsonRpcRelayChartVersion: '', - }); - - expect(newMetadata.toObject()).to.deep.equal(metadata.toObject()); - - expect(() => RemoteConfigMetadata.fromObject(metadata.toObject())).not.to.throw(); - }); - - it('should successfully make migration with makeMigration()', () => { - const { - metadata, - values: {lastUpdateBy}, - } = createMetadata(); - const version = '0.0.1'; - - metadata.makeMigration(lastUpdateBy, version); - - expect(metadata.migration).to.be.ok; - expect(metadata.migration.fromVersion).to.equal(version); - expect(metadata.migration).to.be.instanceof(Migration); - }); - - describe('Values', () => { - const { - values: {namespace, deploymentName, lastUpdatedAt, lastUpdateBy, soloVersion, state}, - } = createMetadata(); - - it('should not be able to create new instance of the class with invalid name', () => { - expect( - () => new RemoteConfigMetadata(null, deploymentName, state, lastUpdatedAt, lastUpdateBy, soloVersion), - ).to.throw(SoloError, `Invalid namespace: ${null}`); - - expect( - // @ts-expect-error: TS2345 - to assign unexpected value - () => new RemoteConfigMetadata(1, deploymentName, state, lastUpdatedAt, lastUpdateBy, soloVersion), - ).to.throw(SoloError, `Invalid namespace: ${1}`); - }); - - it('should not be able to create new instance of the class with invalid lastUpdatedAt', () => { - expect( - () => new RemoteConfigMetadata(namespace, deploymentName, state, null, lastUpdateBy, soloVersion), - ).to.throw(SoloError, `Invalid lastUpdatedAt: ${null}`); - - // @ts-expect-error: TS2345 - to assign unexpected value - expect(() => new RemoteConfigMetadata(namespace, deploymentName, state, 1, lastUpdateBy, soloVersion)).to.throw( - SoloError, - `Invalid lastUpdatedAt: ${1}`, - ); - }); - - it('should not be able to create new instance of the class with invalid lastUpdateBy', () => { - expect( - () => new RemoteConfigMetadata(namespace, deploymentName, state, lastUpdatedAt, null, soloVersion), - ).to.throw(SoloError, `Invalid lastUpdateBy: ${null}`); - - // @ts-expect-error: TS2345 - to assign unexpected value - expect(() => new RemoteConfigMetadata(namespace, deploymentName, state, lastUpdatedAt, 1, soloVersion)).to.throw( - SoloError, - `Invalid lastUpdateBy: ${1}`, - ); - }); - - it('should not be able to create new instance of the class with invalid migration', () => { - expect( - () => - new RemoteConfigMetadata( - namespace, - deploymentName, - state, - lastUpdatedAt, - lastUpdateBy, - soloVersion, - '', - '', - '', - '', - '', - // @ts-expect-error - TS2345: to inject wrong migration - {}, - ), - ).to.throw(SoloError, `Invalid migration: ${{}}`); - }); - }); -}); diff --git a/test/unit/core/config/remote/migration.test.ts b/test/unit/core/config/remote/migration.test.ts deleted file mode 100644 index 5152bd494..000000000 --- a/test/unit/core/config/remote/migration.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {expect} from 'chai'; -import {describe, it} from 'mocha'; -import {Migration} from '../../../../../src/core/config/remote/migration.js'; -import {type Version} from '../../../../../src/types/index.js'; -import {SoloError} from '../../../../../src/core/errors/solo-error.js'; -import {UserIdentitySchema} from '../../../../../src/data/schema/model/common/user-identity-schema.js'; - -function createMigration() { - const migratedAt: Date = new Date(); - const migratedBy: UserIdentitySchema = new UserIdentitySchema('test'); - const fromVersion: Version = '1.0.0' as Version; - - return { - migration: new Migration(migratedAt, migratedBy, fromVersion), - values: {migratedAt, migratedBy, fromVersion}, - }; -} -describe('Migration', () => { - it('should be able to create new instance of the class with valid data', () => { - expect(() => createMigration()).not.to.throw(); - }); - - it('toObject method should return a valid object', () => { - const {migration, values} = createMigration(); - - expect(migration.toObject()).to.deep.equal(values); - }); - - describe('Values', () => { - const migratedAt = new Date(); - const migratedBy = new UserIdentitySchema('test', 'host'); - const fromVersion = '1.0.0' as Version; - - it('should not be able to create new instance of the class with invalid migratedAt', () => { - // @ts-ignore - expect(() => new Migration(null, migratedBy, fromVersion)).to.throw(SoloError, `Invalid migratedAt: ${null}`); - - // @ts-ignore - expect(() => new Migration(1, migratedBy, fromVersion)).to.throw(SoloError, `Invalid migratedAt: ${1}`); - }); - - it('should not be able to create new instance of the class with invalid migratedBy', () => { - // @ts-ignore - expect(() => new Migration(migratedAt, null, fromVersion)).to.throw(SoloError, `Invalid migratedBy: ${null}`); - - // @ts-ignore - expect(() => new Migration(migratedAt, 1, fromVersion)).to.throw(SoloError, `Invalid migratedBy: ${1}`); - }); - - it('should not be able to create new instance of the class with invalid fromVersion', () => { - // @ts-ignore - expect(() => new Migration(migratedAt, migratedBy, null)).to.throw(SoloError, `Invalid fromVersion: ${null}`); - - // @ts-ignore - expect(() => new Migration(migratedAt, migratedBy, 1)).to.throw(SoloError, `Invalid fromVersion: ${1}`); - }); - }); -}); diff --git a/test/unit/core/config/remote/remote-config-data-wrapper.test.ts b/test/unit/core/config/remote/remote-config-data-wrapper.test.ts deleted file mode 100644 index 862d81af0..000000000 --- a/test/unit/core/config/remote/remote-config-data-wrapper.test.ts +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -import {expect} from 'chai'; -import {describe, it} from 'mocha'; - -import * as yaml from 'yaml'; -import {RemoteConfigDataWrapper} from '../../../../../src/core/config/remote/remote-config-data-wrapper.js'; -import {createMetadata} from './metadata.test.js'; -import {createComponentsDataWrapper} from './components-data-wrapper.test.js'; -import {SoloError} from '../../../../../src/core/errors/solo-error.js'; -import * as constants from '../../../../../src/core/constants.js'; -import {CommonFlagsDataWrapper} from '../../../../../src/core/config/remote/common-flags-data-wrapper.js'; - -const configManagerMock = { - update: (...arguments_: any) => true, - getFlag: (...arguments_: any) => true, - hasFlag: (...arguments_: any) => true, - setFlag: (...arguments_: any) => true, -}; - -async function createRemoteConfigDataWrapper() { - const {metadata} = createMetadata(); - const { - wrapper: {componentsDataWrapper}, - } = createComponentsDataWrapper(); - - const clusters = {}; - const components = componentsDataWrapper; - const lastExecutedCommand = 'lastExecutedCommand'; - const commandHistory = []; - const flags = await CommonFlagsDataWrapper.initialize(configManagerMock as any, {}); - - const dataWrapper = new RemoteConfigDataWrapper({ - metadata, - clusters, - components, - lastExecutedCommand, - commandHistory, - flags, - }); - - return { - dataWrapper, - values: {metadata, clusters, components, lastExecutedCommand, commandHistory}, - }; -} - -describe('RemoteConfigDataWrapper', () => { - it('should be able to create a instance', () => createRemoteConfigDataWrapper()); - - it('should be able to add new command to history with addCommandToHistory()', async () => { - const {dataWrapper} = await createRemoteConfigDataWrapper(); - - const command = 'command'; - - dataWrapper.addCommandToHistory(command); - - expect(dataWrapper.lastExecutedCommand).to.equal(command); - expect(dataWrapper.commandHistory).to.include(command); - - it('should be able to handle overflow', () => { - for (let index = 0; index < constants.SOLO_REMOTE_CONFIG_MAX_COMMAND_IN_HISTORY; index++) { - dataWrapper.addCommandToHistory(command); - } - }); - }); - - it('should successfully be able to parse yaml and create instance with fromConfigmap()', async () => { - const {dataWrapper} = await createRemoteConfigDataWrapper(); - const dataWrapperObject = dataWrapper.toObject(); - - const yamlData = yaml.stringify({ - metadata: dataWrapperObject.metadata, - components: dataWrapperObject.components as any, - clusters: dataWrapperObject.clusters, - commandHistory: dataWrapperObject.commandHistory, - lastExecutedCommand: dataWrapperObject.lastExecutedCommand, - }); - - RemoteConfigDataWrapper.fromConfigmap(configManagerMock as any, {data: {'remote-config-data': yamlData}} as any); - }); - - it('should fail if invalid data is passed to setters', async () => { - const {dataWrapper} = await createRemoteConfigDataWrapper(); - - // @ts-expect-error TS2322: Type string is not assignable to type string[] - expect(() => (dataWrapper.commandHistory = '')).to.throw(SoloError); - - // @ts-expect-error TS2341 Property lastExecutedCommand is private and only accessible within class RemoteConfigDataWrapper - expect(() => (dataWrapper.lastExecutedCommand = '')).to.throw(SoloError); - - // @ts-expect-error TS2341 Property lastExecutedCommand is private and only accessible within class RemoteConfigDataWrapper - expect(() => (dataWrapper.lastExecutedCommand = 1)).to.throw(SoloError); - - // @ts-expect-error TS2322 Type number is not assignable to type ComponentsDataWrapper - expect(() => (dataWrapper.components = 1)).to.throw(SoloError); - - // @ts-expect-error TS2322 Type string is not assignable to type ComponentsDataWrapper - expect(() => (dataWrapper.components = '')).to.throw(SoloError); - - expect(() => (dataWrapper.metadata = null)).to.throw(SoloError); - - // @ts-expect-error 2740: Type {} is missing the following properties from type RemoteConfigMetadata - expect(() => (dataWrapper.metadata = {})).to.throw(SoloError); - }); -}); diff --git a/test/unit/core/profile-manager.test.ts b/test/unit/core/profile-manager.test.ts index dbbe5f02f..54eb34ee8 100644 --- a/test/unit/core/profile-manager.test.ts +++ b/test/unit/core/profile-manager.test.ts @@ -94,7 +94,7 @@ describe('ProfileManager', () => { } // @ts-expect-error - TS2339: to mock - profileManager.remoteConfigManager.getConsensusNodes = sinon.stub().returns(consensusNodes); + profileManager.remoteConfig.getConsensusNodes = sinon.stub().returns(consensusNodes); const localConfig: LocalConfigRuntimeState = container.resolve( InjectTokens.LocalConfigRuntimeState, diff --git a/test/unit/data/schema/migration/impl/remote/remote-config-v1-migration.test.ts b/test/unit/data/schema/migration/impl/remote/remote-config-v1-migration.test.ts index 984cd4d30..faeb1ab91 100644 --- a/test/unit/data/schema/migration/impl/remote/remote-config-v1-migration.test.ts +++ b/test/unit/data/schema/migration/impl/remote/remote-config-v1-migration.test.ts @@ -48,7 +48,7 @@ describe('RemoteConfigV1Migration', () => { describe('migrate', (): void => { it('should migrate real config from v0-35-1-remote-config.yaml file', async (): Promise => { const yamlContent: string = fs.readFileSync('test/data/v0-35-1-remote-config.yaml', 'utf8'); - const config: Record = yaml.parse(yamlContent) as Record; + const config: TestObject = yaml.parse(yamlContent) as TestObject; // Set schemaVersion to 0 for migration test config.schemaVersion = 0; @@ -72,7 +72,7 @@ describe('RemoteConfigV1Migration', () => { } // Perform migration - const result: Record = (await migration.migrate(config)) as Record; + const result: TestObject = (await migration.migrate(config)) as TestObject; // Verify migration was successful expect(result).to.have.property('schemaVersion', 1); @@ -144,7 +144,7 @@ describe('RemoteConfigV1Migration', () => { it('should set metadata with lastUpdated information', async (): Promise => { const source: object = {}; - const result = (await migration.migrate(source)) as Record; + const result = (await migration.migrate(source)) as TestObject; expect(result).to.have.property('metadata'); expect(result.metadata).to.have.property('lastUpdatedAt').that.deep.equals(fixedDate); @@ -165,14 +165,14 @@ describe('RemoteConfigV1Migration', () => { hederaJsonRpcRelayChartVersion: '6.0.0', }; - const source: Record = { + const source: TestObject = { metadata: sourceVersions, }; // Create a direct clone of the source to keep the original values for comparison - const sourceClone = structuredClone(source); + const sourceClone: TestObject = structuredClone(source); - const result = (await migration.migrate(source)) as Record; + const result = (await migration.migrate(source)) as TestObject; expect(result).to.have.property('versions'); @@ -196,11 +196,11 @@ describe('RemoteConfigV1Migration', () => { }); it('should use default version values when metadata versions are not present', async (): Promise => { - const source: Record = { + const source: TestObject = { metadata: {}, }; - const result = (await migration.migrate(source)) as Record; + const result = (await migration.migrate(source)) as TestObject; expect(result).to.have.property('versions'); @@ -215,7 +215,7 @@ describe('RemoteConfigV1Migration', () => { }); it('should migrate clusters correctly', async (): Promise => { - const source: Record = { + const source: TestObject = { clusters: { cluster1: { name: 'cluster1', @@ -234,7 +234,7 @@ describe('RemoteConfigV1Migration', () => { }, }; - const result = (await migration.migrate(source)) as Record; + const result = (await migration.migrate(source)) as TestObject; expect(result).to.have.property('clusters'); expect(Array.isArray(result.clusters)).to.be.true; @@ -264,21 +264,21 @@ describe('RemoteConfigV1Migration', () => { }); it('should delete namespace and deploymentName from metadata', async (): Promise => { - const source: Record = { + const source: TestObject = { metadata: { namespace: 'oldNamespace', deploymentName: 'oldDeployment', }, }; - const result = (await migration.migrate(source)) as Record; + const result = (await migration.migrate(source)) as TestObject; expect(result.metadata).to.not.have.property('namespace'); expect(result.metadata).to.not.have.property('deploymentName'); }); it('should migrate component state correctly', async (): Promise => { - const source: Record = { + const source: TestObject = { components: { consensusNodes: { node1: { @@ -327,7 +327,7 @@ describe('RemoteConfigV1Migration', () => { }, }; - const result = (await migration.migrate(source)) as Record; + const result = (await migration.migrate(source)) as TestObject; expect(result).to.have.property('state'); expect(result.state).to.have.property('ledgerPhase', 'initialized'); @@ -392,7 +392,7 @@ describe('RemoteConfigV1Migration', () => { }); it('should migrate command history correctly', async (): Promise => { - const source: Record = { + const source: TestObject = { commandHistory: { command1: 'details1', command2: 'details2', @@ -400,7 +400,7 @@ describe('RemoteConfigV1Migration', () => { lastExecutedCommand: 'lastCommand', }; - const result = (await migration.migrate(source)) as Record; + const result = (await migration.migrate(source)) as TestObject; expect(result).to.have.property('history'); expect(result.history).to.have.property('commands'); @@ -414,14 +414,14 @@ describe('RemoteConfigV1Migration', () => { }); it('should set the schema version to 1', async (): Promise => { - const source: Record = {}; - const result = (await migration.migrate(source)) as Record; + const source: TestObject = {}; + const result = (await migration.migrate(source)) as TestObject; expect(result).to.have.property('schemaVersion', 1); }); it('should perform a complete migration with all properties', async (): Promise => { - const source: Record = { + const source: TestObject = { metadata: { soloVersion: '1.0.0', soloChartVersion: '2.0.0', diff --git a/test/unit/data/schema/model/remote/remote-config.test.ts b/test/unit/data/schema/model/remote/remote-config.test.ts index f078abb77..acc3e8d43 100644 --- a/test/unit/data/schema/model/remote/remote-config.test.ts +++ b/test/unit/data/schema/model/remote/remote-config.test.ts @@ -93,12 +93,11 @@ function migrateConsensusNodes(plainObject: MigrationCandidate): void { } const newConsensusNode = { id: oldConsensusNode.nodeId, - name: oldConsensusNode.name, namespace: oldConsensusNode.namespace, cluster: oldConsensusNode.cluster, phase: migratedState, }; - plainObject.state.consensusNodes.push(newConsensusNode); + plainObject.state.consensusNodes.push({metadata: newConsensusNode}); } } @@ -176,11 +175,10 @@ describe('RemoteConfig', (): void => { expect(rc.versions.jsonRpcRelayChart.version).to.equal('0.63.2'); expect(rc.clusters.length).to.be.equal(1); expect(rc.state.consensusNodes.length).to.be.equal(4); - expect(rc.state.consensusNodes[0].id).to.be.equal(0); - expect(rc.state.consensusNodes[0].name).to.be.equal('node1'); - expect(rc.state.consensusNodes[0].namespace).to.be.equal('solo-alpha-prod'); - expect(rc.state.consensusNodes[0].cluster).to.be.equal('gke-alpha-prod-us-central1'); - expect(rc.state.consensusNodes[0].phase).to.be.equal(DeploymentPhase.STARTED); + expect(rc.state.consensusNodes[0].metadata.id).to.be.equal(0); + expect(rc.state.consensusNodes[0].metadata.namespace).to.be.equal('solo-alpha-prod'); + expect(rc.state.consensusNodes[0].metadata.cluster).to.be.equal('gke-alpha-prod-us-central1'); + expect(rc.state.consensusNodes[0].metadata.phase).to.be.equal(DeploymentPhase.STARTED); expect(rc.state.ledgerPhase).to.be.equal(LedgerPhase.UNINITIALIZED); } @@ -195,11 +193,10 @@ describe('RemoteConfig', (): void => { expect(object.versions.jsonRpcRelayChart).to.equal('0.63.2'); expect(object.clusters.length).to.be.equal(1); expect(object.state.consensusNodes.length).to.be.equal(4); - expect(object.state.consensusNodes[0].id).to.be.equal(0); - expect(object.state.consensusNodes[0].name).to.be.equal('node1'); - expect(object.state.consensusNodes[0].namespace).to.be.equal('solo-alpha-prod'); - expect(object.state.consensusNodes[0].cluster).to.be.equal('gke-alpha-prod-us-central1'); - expect(object.state.consensusNodes[0].phase).to.be.equal(DeploymentPhase.STARTED); + expect(object.state.consensusNodes[0].metadata.id).to.be.equal(0); + expect(object.state.consensusNodes[0].metadata.namespace).to.be.equal('solo-alpha-prod'); + expect(object.state.consensusNodes[0].metadata.cluster).to.be.equal('gke-alpha-prod-us-central1'); + expect(object.state.consensusNodes[0].metadata.phase).to.be.equal(DeploymentPhase.STARTED); expect(object.state.ledgerPhase).to.be.equal(LedgerPhase.UNINITIALIZED); }