Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Validate a remote config #922

4 changes: 2 additions & 2 deletions src/commands/mirror_node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ export class MirrorNodeCommand extends BaseCommand {
);
},
},
RemoteConfigTasks.addMirrorNodeAndMirrorNodeToExplorer.bind(this)(),
RemoteConfigTasks.addMirrorNodeComponents.bind(this)(),
],
{
concurrent: false,
Expand Down Expand Up @@ -518,7 +518,7 @@ export class MirrorNodeCommand extends BaseCommand {
},
skip: ctx => !ctx.config.isChartInstalled,
},
RemoteConfigTasks.removeMirrorNodeAndMirrorNodeToExplorer.bind(this)(),
RemoteConfigTasks.removeMirrorNodeComponents.bind(this)(),
],
{
concurrent: false,
Expand Down
12 changes: 6 additions & 6 deletions src/core/config/remote/components_data_wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ export class ComponentsDataWrapper implements Validate, ToObject<ComponentsDataS
* @param mirrorNodeExplorers - Mirror Node Explorers record mapping service name to mirror node explorers components
*/
private constructor(
private readonly relays: Record<ComponentName, RelayComponent> = {},
private readonly haProxies: Record<ComponentName, HaProxyComponent> = {},
private readonly mirrorNodes: Record<ComponentName, MirrorNodeComponent> = {},
private readonly envoyProxies: Record<ComponentName, EnvoyProxyComponent> = {},
private readonly consensusNodes: Record<ComponentName, ConsensusNodeComponent> = {},
private readonly mirrorNodeExplorers: Record<ComponentName, MirrorNodeExplorerComponent> = {},
public readonly relays: Record<ComponentName, RelayComponent> = {},
public readonly haProxies: Record<ComponentName, HaProxyComponent> = {},
public readonly mirrorNodes: Record<ComponentName, MirrorNodeComponent> = {},
public readonly envoyProxies: Record<ComponentName, EnvoyProxyComponent> = {},
public readonly consensusNodes: Record<ComponentName, ConsensusNodeComponent> = {},
public readonly mirrorNodeExplorers: Record<ComponentName, MirrorNodeExplorerComponent> = {},
) {
this.validate();
}
Expand Down
11 changes: 7 additions & 4 deletions src/core/config/remote/remote_config_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ 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 {K8} from '../../k8.js';
import type {Cluster, Namespace} from './types.js';
import type {SoloLogger} from '../../logging.js';
import type {ListrTask} from 'listr2';
import type {ConfigManager} from '../../config_manager.js';
import type {LocalConfig} from '../local_config.js';
import type {DeploymentStructure} from '../local_config_data.js';
import type {ContextClusterStructure, Optional} from '../../../types/index.js';
import type {ContextClusterStructure, EmptyContextConfig, Optional, SoloListrTask} from '../../../types/index.js';
import type * as k8s from '@kubernetes/client-node';

interface ListrContext {
Expand Down Expand Up @@ -137,6 +137,7 @@ export class RemoteConfigManager {
if (!configMap) return false;

this.remoteConfig = RemoteConfigDataWrapper.fromConfigmap(configMap);

return true;
}

Expand All @@ -149,7 +150,7 @@ export class RemoteConfigManager {
* @param argv - arguments containing command input for historical reference.
* @returns a Listr task which loads the remote configuration.
*/
public buildLoadTask(argv: {_: string[]}): ListrTask {
public buildLoadTask(argv: {_: string[]}): SoloListrTask<EmptyContextConfig> {
const self = this;

return {
Expand All @@ -170,6 +171,8 @@ export class RemoteConfigManager {
// throw new SoloError('Failed to load remote config')
}

await RemoteConfigValidator.validateComponents(self.remoteConfig.components, self.k8);

const currentCommand = argv._.join(' ');
self.remoteConfig!.addCommandToHistory(currentCommand);

Expand All @@ -184,7 +187,7 @@ export class RemoteConfigManager {
*
* @returns a Listr task which creates the remote configuration.
*/
public buildCreateTask(): ListrTask<ListrContext> {
public buildCreateTask(): SoloListrTask<ListrContext> {
const self = this;

return {
Expand Down
124 changes: 73 additions & 51 deletions src/core/config/remote/remote_config_tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,32 @@ import {
EnvoyProxyComponent,
MirrorNodeComponent,
ConsensusNodeComponent,
MirrorNodeExplorerComponent,
} from './components/index.js';
import {ComponentType, ConsensusNodeStates} from './enumerations.js';
import chalk from 'chalk';
import {SoloError} from '../../errors.js';

import type {Listr, ListrTask} from 'listr2';
import type {NodeAlias, NodeAliases} from '../../../types/aliases.js';
import type {Listr} from 'listr2';
import type {NodeAlias} from '../../../types/aliases.js';
import type {BaseCommand} from '../../../commands/base.js';
import type {RelayCommand} from '../../../commands/relay.js';
import type {NetworkCommand} from '../../../commands/network.js';
import type {DeploymentCommand} from '../../../commands/deployment.js';
import type {MirrorNodeCommand} from '../../../commands/mirror_node.js';
import type {NodeCommandHandlers} from '../../../commands/node/handlers.js';
import type {Optional} from '../../../types/index.js';
import {type ComponentsDataWrapper} from './components_data_wrapper.js';
import type {EmptyContextConfig, Optional, SoloListrTask} from '../../../types/index.js';
import type {ComponentsDataWrapper} from './components_data_wrapper.js';
import type {
ValidateStatesObject,
AddRelayComponentContext,
AddNodesAndProxiesContext,
ChangeAllNodeStatesContext,
ValidateAllNodeStatesContext,
ValidateSingleNodeStateContext,
AddMirrorNodeComponentsContext,
} from './types.js';
import {Templates} from '../../templates.js';

/**
* Static class that handles all tasks related to remote config used by other commands.
Expand All @@ -47,19 +58,19 @@ export class RemoteConfigTasks {
*
* @param argv - used to update the last executed command and command history
*/
public static loadRemoteConfig(this: BaseCommand, argv: any): ListrTask<any, any, any> {
public static loadRemoteConfig(this: BaseCommand, argv: any): SoloListrTask<EmptyContextConfig> {
return this.remoteConfigManager.buildLoadTask(argv);
}

/** Creates remote config. */
public static createRemoteConfig(this: DeploymentCommand): ListrTask<any, any, any> {
public static createRemoteConfig(this: DeploymentCommand): SoloListrTask<EmptyContextConfig> {
return this.remoteConfigManager.buildCreateTask();
}

/* ----------- Component Modifying ----------- */

/** Adds the relay component to remote config. */
public static addRelayComponent(this: RelayCommand): ListrTask<any, any, any> {
public static addRelayComponent(this: RelayCommand): SoloListrTask<AddRelayComponentContext> {
return {
title: 'Add relay component in remote config',
skip: (): boolean => !this.remoteConfigManager.isLoaded(),
Expand All @@ -70,14 +81,16 @@ export class RemoteConfigTasks {
} = ctx;
const cluster = this.remoteConfigManager.currentCluster;

remoteConfig.components.add('relay', new RelayComponent('relay', cluster, namespace, nodeAliases));
const component = new RelayComponent('relay', cluster, namespace, nodeAliases);

remoteConfig.components.add('relay', component);
});
},
};
}

/** Remove the relay component from remote config. */
public static removeRelayComponent(this: RelayCommand): ListrTask<any, any, any> {
public static removeRelayComponent(this: RelayCommand): SoloListrTask<EmptyContextConfig> {
return {
title: 'Remove relay component from remote config',
skip: (): boolean => !this.remoteConfigManager.isLoaded(),
Expand All @@ -90,45 +103,62 @@ export class RemoteConfigTasks {
}

/** Adds the mirror node and mirror node explorer components to remote config. */
public static addMirrorNodeAndMirrorNodeToExplorer(this: MirrorNodeCommand): ListrTask<any, any, any> {
public static addMirrorNodeComponents(this: MirrorNodeCommand): SoloListrTask<AddMirrorNodeComponentsContext> {
return {
title: 'Add mirror node and mirror node explorer to remote config',
skip: (): boolean => !this.remoteConfigManager.isLoaded(),
task: async (ctx): Promise<void> => {
await this.remoteConfigManager.modify(async remoteConfig => {
const {
config: {namespace},
config: {namespace, deployHederaExplorer},
} = ctx;
const cluster = this.remoteConfigManager.currentCluster;

remoteConfig.components.add('mirrorNode', new MirrorNodeComponent('mirrorNode', cluster, namespace));
try {
const component = new MirrorNodeComponent('mirrorNode', cluster, namespace);

remoteConfig.components.add('mirrorNode', component);
} catch (e) {
throw new SoloError('Mirror node component already exists', e);
}

// Add a mirror node explorer component to remote config only if the flag is enabled
if (!deployHederaExplorer) return;

try {
const component = new MirrorNodeExplorerComponent('mirrorNodeExplorer', cluster, namespace);

remoteConfig.components.add(
'mirrorNodeExplorer',
new MirrorNodeComponent('mirrorNodeExplorer', cluster, namespace),
);
remoteConfig.components.add('mirrorNodeExplorer', component);
} catch (e) {
throw new SoloError('Mirror node explorer component already exists', e);
}
});
},
};
}

/** Removes the mirror node and mirror node explorer components from remote config. */
public static removeMirrorNodeAndMirrorNodeToExplorer(this: MirrorNodeCommand): ListrTask<any, any, any> {
public static removeMirrorNodeComponents(this: MirrorNodeCommand): SoloListrTask<EmptyContextConfig> {
return {
title: 'Remove mirror node and mirror node explorer from remote config',
skip: (): boolean => !this.remoteConfigManager.isLoaded(),
task: async (): Promise<void> => {
await this.remoteConfigManager.modify(async remoteConfig => {
remoteConfig.components.remove('mirrorNode', ComponentType.MirrorNode);

remoteConfig.components.remove('mirrorNodeExplorer', ComponentType.MirrorNode);
try {
remoteConfig.components.remove('mirrorNodeExplorer', ComponentType.MirrorNode);
} catch {
// When the mirror node explorer component is not deployed,
// error is thrown since is not found, in this case ignore it
}
});
},
};
}

/** Adds the consensus node, envoy and haproxy components to remote config. */
public static addNodesAndProxies(this: NetworkCommand): ListrTask<any, any, any> {
public static addNodesAndProxies(this: NetworkCommand): SoloListrTask<AddNodesAndProxiesContext> {
return {
title: 'Add node and proxies to remote config',
skip: (): boolean => !this.remoteConfigManager.isLoaded(),
Expand All @@ -145,23 +175,21 @@ export class RemoteConfigTasks {
new ConsensusNodeComponent(nodeAlias, cluster, namespace, ConsensusNodeStates.INITIALIZED),
);

remoteConfig.components.add(
`envoy-${nodeAlias}`,
new EnvoyProxyComponent(`envoy-${nodeAlias}`, cluster, namespace),
);
const envoyProxyName = Templates.renderEnvoyProxyName(nodeAlias);

remoteConfig.components.add(
`haproxy-${nodeAlias}`,
new HaProxyComponent(`haproxy-${nodeAlias}`, cluster, namespace),
);
remoteConfig.components.add(envoyProxyName, new EnvoyProxyComponent(envoyProxyName, cluster, namespace));

const haProxyName = Templates.renderHaProxyName(nodeAlias);

remoteConfig.components.add(haProxyName, new HaProxyComponent(haProxyName, cluster, namespace));
}
});
},
};
}

/** Removes the consensus node, envoy and haproxy components from remote config. */
public static removeNodeAndProxies(this: NodeCommandHandlers): ListrTask<any, any, any> {
public static removeNodeAndProxies(this: NodeCommandHandlers): SoloListrTask<EmptyContextConfig> {
return {
skip: (): boolean => !this.remoteConfigManager.isLoaded(),
title: 'Remove node and proxies from remote config',
Expand All @@ -180,23 +208,25 @@ export class RemoteConfigTasks {
*
* @param state - to which to change the consensus node component
*/
public static changeAllNodeStates(this: NodeCommandHandlers, state: ConsensusNodeStates): ListrTask<any, any, any> {
interface Context {
config: {namespace: string; nodeAliases: NodeAliases};
}

public static changeAllNodeStates(
this: NodeCommandHandlers,
state: ConsensusNodeStates,
): SoloListrTask<ChangeAllNodeStatesContext> {
return {
title: `Change node state to ${state} in remote config`,
skip: (): boolean => !this.remoteConfigManager.isLoaded(),
task: async (ctx: Context): Promise<void> => {
task: async (ctx): Promise<void> => {
await this.remoteConfigManager.modify(async remoteConfig => {
const {
config: {namespace, nodeAliases},
} = ctx;

const cluster = this.remoteConfigManager.currentCluster;

for (const nodeAlias of nodeAliases) {
remoteConfig.components.edit(nodeAlias, new ConsensusNodeComponent(nodeAlias, cluster, namespace, state));
const component = new ConsensusNodeComponent(nodeAlias, cluster, namespace, state);

remoteConfig.components.edit(nodeAlias, component);
}
});
},
Expand All @@ -211,21 +241,17 @@ export class RemoteConfigTasks {
*/
public static validateAllNodeStates(
this: NodeCommandHandlers,
{acceptedStates, excludedStates}: {acceptedStates?: ConsensusNodeStates[]; excludedStates?: ConsensusNodeStates[]},
): ListrTask<any, any, any> {
interface Context {
config: {namespace: string; nodeAliases: NodeAliases};
}

{acceptedStates, excludedStates}: ValidateStatesObject,
): SoloListrTask<ValidateAllNodeStatesContext> {
return {
title: 'Validate nodes states',
skip: (): boolean => !this.remoteConfigManager.isLoaded(),
task: (ctx: Context, task): Listr<any, any, any> => {
task: (ctx, task): Listr<ValidateAllNodeStatesContext> => {
const nodeAliases = ctx.config.nodeAliases;

const components = this.remoteConfigManager.components;

const subTasks: ListrTask<Context, any, any>[] = nodeAliases.map(nodeAlias => ({
const subTasks: SoloListrTask<ValidateAllNodeStatesContext>[] = nodeAliases.map(nodeAlias => ({
title: `Validating state for node ${nodeAlias}`,
task: (_, task): void => {
const state = RemoteConfigTasks.validateNodeState(nodeAlias, components, acceptedStates, excludedStates);
Expand All @@ -250,16 +276,12 @@ export class RemoteConfigTasks {
*/
public static validateSingleNodeState(
this: NodeCommandHandlers,
{acceptedStates, excludedStates}: {acceptedStates?: ConsensusNodeStates[]; excludedStates?: ConsensusNodeStates[]},
): ListrTask<any, any, any> {
interface Context {
config: {namespace: string; nodeAlias: NodeAlias};
}

{acceptedStates, excludedStates}: ValidateStatesObject,
): SoloListrTask<ValidateSingleNodeStateContext> {
return {
title: 'Validate nodes state',
skip: (): boolean => !this.remoteConfigManager.isLoaded(),
task: (ctx: Context, task): void => {
task: (ctx, task): void => {
const nodeAlias = ctx.config.nodeAlias;

task.title += ` ${nodeAlias}`;
Expand Down Expand Up @@ -289,7 +311,7 @@ export class RemoteConfigTasks {
try {
nodeComponent = components.getComponent<ConsensusNodeComponent>(ComponentType.ConsensusNode, nodeAlias);
} catch (e) {
throw new SoloError(`${nodeAlias} not found in remote config`);
throw new SoloError(`${nodeAlias} not found in remote config`, e);
}

if (acceptedStates && !acceptedStates.includes(nodeComponent.state)) {
Expand Down
Loading
Loading