@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
280 lines • 17.6 kB
JavaScript
// SPDX-License-Identifier: Apache-2.0
import sinon from 'sinon';
import { before, beforeEach, describe, it } from 'mocha';
import { expect } from 'chai';
import { getTestCluster, HEDERA_PLATFORM_VERSION_TAG } from '../../test-utility.js';
import { Flags as flags } from '../../../src/commands/flags.js';
import * as version from '../../../version.js';
import * as constants from '../../../src/core/constants.js';
import { ROOT_DIR } from '../../../src/core/constants.js';
import { NetworkCommand } from '../../../src/commands/network.js';
import { ListrLock } from '../../../src/core/lock/listr-lock.js';
import { GenesisNetworkDataConstructor } from '../../../src/core/genesis-network-models/genesis-network-data-constructor.js';
import { container } from 'tsyringe-neo';
import { resetForTest } from '../../test-container.js';
import { InjectTokens } from '../../../src/core/dependency-injection/inject-tokens.js';
import { K8Client } from '../../../src/integration/kube/k8-client/k8-client.js';
import { ConsensusNode } from '../../../src/core/model/consensus-node.js';
import { NamespaceName } from '../../../src/types/namespace/namespace-name.js';
import { Argv } from '../../helpers/argv-wrapper.js';
import { PathEx } from '../../../src/business/utils/path-ex.js';
import fs from 'node:fs';
import { ValueContainer } from '../../../src/core/dependency-injection/value-container.js';
import { StringFacade } from '../../../src/business/runtime-state/facade/string-facade.js';
import { SemanticVersion } from '../../../src/business/utils/semantic-version.js';
const testName = 'network-cmd-unit';
const namespace = NamespaceName.of(testName);
const argv = Argv.getDefaultArgv(namespace);
const realK8Factory = container.resolve(InjectTokens.K8Factory);
argv.setArg(flags.releaseTag, HEDERA_PLATFORM_VERSION_TAG);
argv.setArg(flags.nodeAliasesUnparsed, 'node1');
argv.setArg(flags.deployment, 'deployment');
argv.setArg(flags.generateGossipKeys, true);
argv.setArg(flags.generateTlsKeys, true);
argv.setArg(flags.clusterRef, getTestCluster());
argv.setArg(flags.soloChartVersion, version.SOLO_CHART_VERSION);
argv.setArg(flags.force, true);
argv.setArg(flags.clusterSetupNamespace, constants.SOLO_SETUP_NAMESPACE.name);
argv.setArg(flags.chartDirectory, undefined);
if (new SemanticVersion(version.HEDERA_PLATFORM_VERSION).lessThan('v0.61.0')) {
argv.setArg(flags.releaseTag, 'v0.61.0');
}
describe('NetworkCommand unit tests', () => {
before(async () => {
const sourceDirectory = PathEx.joinWithRealPath('test', 'data');
const destinationDirectory = PathEx.join(sourceDirectory, 'tmp', 'templates');
if (!fs.existsSync(destinationDirectory)) {
fs.mkdirSync(destinationDirectory, { recursive: true });
}
fs.copyFileSync(PathEx.joinWithRealPath(sourceDirectory, constants.APPLICATION_PROPERTIES), PathEx.join(destinationDirectory, constants.APPLICATION_PROPERTIES));
});
describe('Chart Install Function is called correctly', () => {
let options;
const k8SFactoryStub = sinon.stub();
const clusterChecksStub = sinon.stub();
const remoteConfigStub = sinon.stub();
const chartManagerStub = sinon.stub();
const certificateManagerStub = sinon.stub();
const profileManagerStub = sinon.stub();
const platformInstallerStub = sinon.stub();
const keyManagerStub = sinon.stub();
const depManagerStub = sinon.stub();
const helmStub = sinon.stub();
let containerOverrides;
beforeEach(async () => {
containerOverrides = new Map([
[InjectTokens.K8Factory, new ValueContainer(InjectTokens.K8Factory, k8SFactoryStub)],
[InjectTokens.ClusterChecks, new ValueContainer(InjectTokens.ClusterChecks, clusterChecksStub)],
[
InjectTokens.RemoteConfigRuntimeState,
new ValueContainer(InjectTokens.RemoteConfigRuntimeState, remoteConfigStub),
],
[InjectTokens.ChartManager, new ValueContainer(InjectTokens.ChartManager, chartManagerStub)],
[InjectTokens.CertificateManager, new ValueContainer(InjectTokens.CertificateManager, certificateManagerStub)],
[InjectTokens.ProfileManager, new ValueContainer(InjectTokens.ProfileManager, profileManagerStub)],
[InjectTokens.PlatformInstaller, new ValueContainer(InjectTokens.PlatformInstaller, platformInstallerStub)],
[InjectTokens.KeyManager, new ValueContainer(InjectTokens.KeyManager, keyManagerStub)],
[InjectTokens.DependencyManager, new ValueContainer(InjectTokens.DependencyManager, depManagerStub)],
[InjectTokens.Helm, new ValueContainer(InjectTokens.Helm, helmStub)],
]);
resetForTest(undefined, undefined, true, containerOverrides);
options = {
logger: container.resolve(InjectTokens.SoloLogger),
configManager: container.resolve(InjectTokens.ConfigManager),
};
options.configManager.update(argv.build());
options.k8Factory = k8SFactoryStub;
const k8Stub = sinon.stub();
options.k8Factory.default = sinon.stub().returns(k8Stub);
options.k8Factory.default().namespaces = sinon.stub().returns({
has: sinon.stub().returns(true),
});
options.k8Factory.default().contexts = sinon.stub().returns({
readCurrent: sinon
.stub()
.returns(new K8Client(undefined, realK8Factory.default().getKubectlExecutablePath()).contexts().readCurrent()),
});
options.k8Factory.default().configMaps = sinon.stub();
options.k8Factory.default().configMaps.read = sinon.stub();
options.k8Factory.default().pods = sinon.stub().returns({
waitForRunningPhase: sinon.stub(),
waitForReadyStatus: sinon.stub(),
});
options.k8Factory.default().leases = sinon.stub().returns({
read: sinon.stub(),
});
options.k8Factory.default().logger = options.logger;
options.k8Factory.getK8 = sinon.stub().returns(k8Stub);
options.k8Factory.getK8().namespaces = sinon.stub().returns({
has: sinon.stub().returns(true),
});
options.k8Factory.getK8().configMaps = sinon.stub();
options.k8Factory.getK8().configMaps.read = sinon.stub();
options.k8Factory.getK8().pods = sinon.stub().returns({
waitForRunningPhase: sinon.stub(),
waitForReadyStatus: sinon.stub(),
});
options.k8Factory.getK8().leases = sinon.stub().returns({
read: sinon.stub(),
});
options.k8Factory.getK8().manifests = sinon.stub().returns({
applyManifest: sinon.stub().resolves(),
patchObject: sinon.stub().resolves(),
});
options.k8Factory.getK8().logger = options.logger;
options.k8Factory.default().clusters = sinon.stub().returns({
list: sinon.stub().returns([{ name: 'solo-e2e' }]),
});
options.k8Factory.default().clusters().readCurrent = sinon.stub().returns('solo-e2e');
clusterChecksStub.isMinioInstalled = sinon.stub();
clusterChecksStub.isCertManagerInstalled = sinon.stub();
container.registerInstance(InjectTokens.ClusterChecks, clusterChecksStub);
container.registerInstance(InjectTokens.K8Factory, options.k8Factory);
options.depManager = sinon.stub();
container.registerInstance(InjectTokens.DependencyManager, options.depManager);
options.localConfig = container.resolve(InjectTokens.LocalConfigRuntimeState);
options.helm = container.resolve(InjectTokens.Helm);
options.helm.dependency = sinon.stub();
ListrLock.newAcquireLockTask = sinon.stub().returns({
run: sinon.stub().returns({}),
});
options.keyManager = container.resolve(InjectTokens.KeyManager);
options.keyManager.prepareTlsKeyFilePaths = sinon.stub();
options.keyManager.copyGossipKeysToStaging = sinon.stub();
options.keyManager.copyNodeKeysToStaging = sinon.stub();
options.platformInstaller = platformInstallerStub;
options.platformInstaller.copyNodeKeys = sinon.stub();
container.registerInstance(InjectTokens.PlatformInstaller, options.platformInstaller);
options.profileManager = container.resolve(InjectTokens.ProfileManager);
options.profileManager.prepareValuesForSoloChart = sinon.stub();
options.certificateManager = certificateManagerStub;
container.registerInstance(InjectTokens.CertificateManager, options.certificateManager);
options.chartManager = container.resolve(InjectTokens.ChartManager);
options.chartManager.isChartInstalled = sinon.stub().returns(true);
options.chartManager.isChartInstalled.onSecondCall().returns(false);
options.chartManager.upgrade = sinon.stub().returns(true);
options.chartManager.uninstall = sinon.stub().returns(true);
options.remoteConfig = container.resolve(InjectTokens.RemoteConfigRuntimeState);
options.remoteConfig.isLoaded = sinon.stub().returns(true);
options.remoteConfig.getConfigMap = sinon.stub().returns(null);
options.remoteConfig.persist = sinon.stub();
options.remoteConfig.loadAndValidate = sinon.stub();
options.remoteConfig.getNamespace = sinon.stub();
options.remoteConfig.configuration = {
components: { changeNodePhase: sinon.stub(), getNewComponentId: sinon.stub(), addNewComponent: sinon.stub() },
versions: { consensusNode: '0.0.0' },
};
await options.localConfig.load();
options.localConfig.configuration.clusterRefs.set('solo-e2e', new StringFacade('context-1'));
options.leaseManager = container.resolve(InjectTokens.LockManager);
options.leaseManager.currentNamespace = sinon.stub().returns(testName);
GenesisNetworkDataConstructor.initialize = sinon.stub().returns(null);
});
afterEach(() => {
sinon.restore();
});
it('Install function is called with expected parameters', async () => {
try {
const networkCommand = container.resolve(NetworkCommand);
options.remoteConfig.getConsensusNodes = sinon.stub().returns([{ name: 'node1' }]);
options.remoteConfig.getContexts = sinon.stub().returns(['context1']);
const stubbedClusterReferences = new Map([['solo-e2e', 'context1']]);
options.remoteConfig.getClusterRefs = sinon.stub().returns(stubbedClusterReferences);
options.remoteConfig.updateComponentVersion = sinon.stub();
options.remoteConfig.configuration.state = {};
// @ts-expect-error - TS2341: to mock
networkCommand.getBlockNodes = sinon.stub().returns([]);
// @ts-expect-error - TS2341: to mock
networkCommand.ensurePodLogsCrd = sinon.stub().returns(true);
// @ts-expect-error - TS2341: to mock
networkCommand.ensurePrometheusOperatorCrds = sinon.stub().returns(true);
// @ts-expect-error - TS2341: to mock
networkCommand.componentFactory = {
createNewEnvoyProxyComponent: sinon.stub(),
createNewHaProxyComponent: sinon.stub(),
};
await networkCommand.deploy(argv.build());
expect(options.chartManager.upgrade.args[0][0].name).to.equal('solo-e2e');
expect(options.chartManager.upgrade.args[0][1]).to.equal(constants.SOLO_DEPLOYMENT_CHART);
expect(options.chartManager.upgrade.args[0][2]).to.equal(constants.SOLO_DEPLOYMENT_CHART);
expect(options.chartManager.upgrade.args[0][3]).to.equal(constants.SOLO_TESTING_CHART_URL);
}
finally {
sinon.restore();
}
});
it('Should use local chart directory', async () => {
try {
argv.setArg(flags.chartDirectory, 'test-directory');
argv.setArg(flags.force, true);
const networkCommand = container.resolve(NetworkCommand);
options.remoteConfig.getConsensusNodes = sinon.stub().returns([{ name: 'node1' }]);
options.remoteConfig.getContexts = sinon.stub().returns(['context1']);
options.remoteConfig.updateComponentVersion = sinon.stub();
const stubbedClusterReferences = new Map([['solo-e2e', 'context1']]);
options.remoteConfig.getClusterRefs = sinon.stub().returns(stubbedClusterReferences);
options.remoteConfig.configuration.state = {};
// @ts-expect-error - TS2341: to mock
networkCommand.ensurePodLogsCrd = sinon.stub().returns(true);
// @ts-expect-error - TS2341: to mock
networkCommand.ensurePrometheusOperatorCrds = sinon.stub().returns(true);
// @ts-expect-error - TS2341: to mock
networkCommand.getBlockNodes = sinon.stub().returns([]);
// @ts-expect-error - TS2341: to mock
networkCommand.componentFactory = {
createNewEnvoyProxyComponent: sinon.stub(),
createNewHaProxyComponent: sinon.stub(),
};
await networkCommand.deploy(argv.build());
expect(options.chartManager.upgrade.args[0][0].name).to.equal('solo-e2e');
expect(options.chartManager.upgrade.args[0][1]).to.equal(constants.SOLO_DEPLOYMENT_CHART);
expect(options.chartManager.upgrade.args[0][2]).to.equal(constants.SOLO_DEPLOYMENT_CHART);
expect(options.chartManager.upgrade.args[0][3]).to.equal(PathEx.join(ROOT_DIR, 'test-directory'));
}
finally {
sinon.restore();
}
});
it('Should use prepare config correctly for all clusters', async () => {
try {
const common = PathEx.join('test', 'data', 'test-values.yaml');
const values1 = PathEx.join('test', 'data', 'test-values1.yaml');
const values2 = PathEx.join('test', 'data', 'test-values2.yaml');
argv.setArg(flags.networkDeploymentValuesFile, `${common},cluster=${values1},cluster=${values2}`);
argv.setArg(flags.chartDirectory, 'test-directory');
argv.setArg(flags.force, true);
const task = sinon.stub();
options.remoteConfig.getConsensusNodes = sinon
.stub()
.returns([
new ConsensusNode('node1', 0, 'solo-e2e', 'cluster', 'context-1', 'base', 'pattern', 'fqdn', [], []),
]);
options.remoteConfig.getContexts = sinon.stub().returns(['context-1']);
const stubbedClusterReferences = new Map([['cluster', 'context1']]);
options.remoteConfig.getClusterRefs = sinon.stub().returns(stubbedClusterReferences);
const networkCommand = container.resolve(NetworkCommand);
// @ts-expect-error - to mock
networkCommand.getBlockNodes = sinon.stub().returns([]);
networkCommand.configManager.update(argv.build());
// @ts-expect-error - to access private method
const config = await networkCommand.prepareConfig(task, argv.build());
expect(config.valuesArgMap).to.not.empty;
expect(config.valuesArgMap['cluster']).to.not.empty;
expect(config.valuesArgMap['cluster'].indexOf(PathEx.join('solo-deployment', 'values.yaml'))).to.not.equal(-1);
expect(config.valuesArgMap['cluster'].indexOf('values.yaml')).to.not.equal(-1);
expect(config.valuesArgMap['cluster'].indexOf('test-values1.yaml')).to.not.equal(-1);
expect(config.valuesArgMap['cluster'].indexOf('test-values2.yaml')).to.not.equal(-1);
// chart values file should precede the values file passed in the command
expect(config.valuesArgMap['cluster'].indexOf('solo-deployment/values.yaml')).to.be.lt(config.valuesArgMap['cluster'].indexOf('test-values1.yaml'));
expect(config.valuesArgMap['cluster'].indexOf('solo-deployment/values.yaml')).to.be.lt(config.valuesArgMap['cluster'].indexOf('test-values2.yaml'));
expect(config.valuesArgMap['cluster'].indexOf('values.yaml')).to.be.lt(config.valuesArgMap['cluster'].indexOf('test-values1.yaml'));
expect(config.valuesArgMap['cluster'].indexOf('test-values1.yaml')).to.be.lt(config.valuesArgMap['cluster'].indexOf('test-values2.yaml'));
expect(config.valuesArgMap['cluster'].indexOf('values.yaml')).to.be.lt(config.valuesArgMap['cluster'].indexOf('test-values2.yaml'));
}
finally {
sinon.restore();
}
});
});
});
//# sourceMappingURL=network.test.js.map