@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
164 lines • 9.77 kB
JavaScript
// SPDX-License-Identifier: Apache-2.0
import { beforeEach, describe, it } from 'mocha';
import { expect } from 'chai';
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 { container } from 'tsyringe-neo';
import { NamespaceName } from '../../../../src/types/namespace/namespace-name.js';
import { PodReference } from '../../../../src/integration/kube/resources/pod/pod-reference.js';
import { PodName } from '../../../../src/integration/kube/resources/pod/pod-name.js';
import { ContainerName } from '../../../../src/integration/kube/resources/container/container-name.js';
import { InjectTokens } from '../../../../src/core/dependency-injection/inject-tokens.js';
import { getTestCacheDirectory } from '../../../test-utility.js';
import { Duration } from '../../../../src/core/time/duration.js';
import { DeploymentPhase } from '../../../../src/data/schema/model/remote/deployment-phase.js';
import { Templates } from '../../../../src/core/templates.js';
import { ComponentTypes } from '../../../../src/core/config/remote/enumerations/component-types.js';
import { ComponentFactory } from '../../../../src/core/config/remote/component-factory.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 { resetForTest } from '../../../test-container.js';
function prepareComponentsData(namespace) {
const remoteConfigMock = { configuration: { components: { getNewComponentId: () => 1 } } };
const clusterReference = 'cluster';
const nodeState = DeploymentPhase.STARTED;
const id = 1;
const componentFactory = new ComponentFactory(remoteConfigMock);
const components = {
explorers: componentFactory.createNewExplorerComponent(clusterReference, namespace),
mirrorNodes: componentFactory.createNewMirrorNodeComponent(clusterReference, namespace),
relayNodes: componentFactory.createNewRelayComponent(clusterReference, namespace, [0]),
consensusNodes: componentFactory.createNewConsensusNodeComponent(id, clusterReference, namespace, nodeState),
haProxies: componentFactory.createNewHaProxyComponent(clusterReference, namespace),
envoyProxies: componentFactory.createNewEnvoyProxyComponent(clusterReference, namespace),
blockNodes: componentFactory.createNewBlockNodeComponent(clusterReference, namespace),
};
const labelRecord = {
relayNodes: Templates.renderRelayLabels(components.relayNodes.metadata.id),
haProxies: Templates.renderHaProxyLabels(components.haProxies.metadata.id),
mirrorNodes: Templates.renderMirrorNodeLabels(components.mirrorNodes.metadata.id),
envoyProxies: Templates.renderEnvoyProxyLabels(components.envoyProxies.metadata.id),
explorers: Templates.renderExplorerLabels(components.explorers.metadata.id),
consensusNodes: Templates.renderConsensusNodeLabels(components.consensusNodes.metadata.id),
blockNodes: Templates.renderBlockNodeLabels(components.blockNodes.metadata.id),
};
const podNames = {
explorers: `hedera-explorer-${components.explorers.metadata.id}`,
mirrorNodes: `mirror-importer-${components.mirrorNodes.metadata.id}`,
relayNodes: `relay-${components.relayNodes.metadata.id}`,
consensusNodes: Templates.renderNetworkPodName(Templates.renderNodeAliasFromNumber(components.consensusNodes.metadata.id)).name,
haProxies: `haproxy-node1-${Templates.renderNodeAliasFromNumber(components.haProxies.metadata.id)}`,
envoyProxies: `envoy-proxy-${Templates.renderNodeAliasFromNumber(components.envoyProxies.metadata.id)}`,
};
const state = new DeploymentStateSchema();
const remoteConfig = new RemoteConfigSchema(undefined, undefined, undefined, undefined, state);
const componentsDataWrapper = new ComponentsDataWrapper(remoteConfig.state);
return { namespace, components, labelRecord, componentsDataWrapper, podNames, componentFactory };
}
describe('RemoteConfigValidator', () => {
const namespace = NamespaceName.of('remote-config-validator');
let k8Factory;
let localConfig;
let components;
let labelRecord;
let componentsDataWrapper;
let podNames;
let componentFactory;
let state;
let remoteConfigValidator;
before(async () => {
resetForTest(namespace.name, `${getTestCacheDirectory('LocalConfig')}`, false);
k8Factory = container.resolve(InjectTokens.K8Factory);
localConfig = container.resolve(InjectTokens.LocalConfigRuntimeState);
await localConfig.load();
await k8Factory.default().namespaces().create(namespace);
remoteConfigValidator = new RemoteConfigValidator(k8Factory, localConfig);
});
beforeEach(() => {
const testData = prepareComponentsData(namespace);
podNames = testData.podNames;
components = testData.components;
labelRecord = testData.labelRecord;
componentsDataWrapper = testData.componentsDataWrapper;
componentFactory = testData.componentFactory;
state = componentsDataWrapper.state;
});
after(async function () {
this.timeout(Duration.ofMinutes(5).toMillis());
await k8Factory.default().namespaces().delete(namespace);
});
async function createPod(name, labelsRaw) {
const labels = {};
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']);
}
const testCasesForIndividualComponents = [
{ componentKey: 'relayNodes', displayName: 'Relay Nodes', type: ComponentTypes.RelayNodes },
{ componentKey: 'haProxies', displayName: 'HaProxy', type: ComponentTypes.HaProxy },
{ componentKey: 'mirrorNodes', displayName: 'Mirror Node', type: ComponentTypes.MirrorNode },
{ componentKey: 'envoyProxies', displayName: 'Envoy Proxy', type: ComponentTypes.EnvoyProxy },
{ componentKey: 'consensusNodes', displayName: 'Consensus Node', type: ComponentTypes.ConsensusNode },
{ componentKey: 'explorers', displayName: 'Explorer', type: ComponentTypes.Explorer },
];
for (const { componentKey, displayName, type } of testCasesForIndividualComponents) {
describe(`${displayName} validation`, () => {
it('should fail if component is not present', async () => {
const component = components[componentKey];
componentsDataWrapper.addNewComponent(component, type);
try {
await remoteConfigValidator.validateComponents(namespace, true, state);
if (type !== ComponentTypes.ConsensusNode) {
expect.fail();
}
}
catch (error) {
expect(error).to.be.instanceOf(SoloError);
expect(error.message).to.include(RemoteConfigValidator.buildValidationErrorMessage(displayName, component));
}
});
it('should succeed if component is present', async () => {
await createPod(podNames[componentKey], labelRecord[componentKey]);
await remoteConfigValidator.validateComponents(namespace, false, state);
});
});
}
describe('Additional test cases', () => {
it('Should not validate consensus nodes if skipConsensusNodes is enabled', async () => {
const skipConsensusNodes = true;
const nodeIds = [0, 1, 2];
const consensusNodeComponents = componentFactory.createConsensusNodeComponentsFromNodeIds(nodeIds, 'cluster-ref', namespace);
// @ts-expect-error - to mock
const componentsDataWrapper = new ComponentsDataWrapper({
consensusNodes: consensusNodeComponents,
});
for (const nodeId of nodeIds) {
// Make sure the status is STARTED
componentsDataWrapper.changeNodePhase(Templates.renderComponentIdFromNodeId(nodeId), DeploymentPhase.STARTED);
}
await remoteConfigValidator.validateComponents(namespace, skipConsensusNodes, state);
});
const nodeStates = [DeploymentPhase.REQUESTED, DeploymentPhase.STOPPED];
for (const nodeState of nodeStates) {
it(`Should not validate consensus nodes if status is ${nodeState} `, async () => {
const nodeIds = [0, 1, 2];
const consensusNodeComponents = componentFactory.createConsensusNodeComponentsFromNodeIds(nodeIds, 'cluster-ref', namespace);
// @ts-expect-error - to mock
const componentsDataWrapper = new ComponentsDataWrapper({
consensusNodes: consensusNodeComponents,
});
for (const nodeId of nodeIds) {
componentsDataWrapper.changeNodePhase(Templates.renderComponentIdFromNodeId(nodeId), nodeState);
}
await remoteConfigValidator.validateComponents(namespace, false, state);
});
}
});
});
//# sourceMappingURL=remote-config-validator.test.js.map