UNPKG

@hashgraph/solo

Version:

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

251 lines 14.4 kB
/** * SPDX-License-Identifier: Apache-2.0 */ import { expect } from 'chai'; import { after, describe, it } from 'mocha'; import fs from 'fs'; import * as yaml from 'yaml'; import path from 'path'; import { Flags as flags } from '../../../src/commands/flags.js'; import * as constants from '../../../src/core/constants.js'; import { ProfileManager } from '../../../src/core/profile_manager.js'; import { getTestCacheDir, getTmpDir } from '../../test_util.js'; import * as version from '../../../version.js'; import { container } from 'tsyringe-neo'; import { resetForTest } from '../../test_container.js'; import { Templates } from '../../../src/core/templates.js'; import { NamespaceName } from '../../../src/core/kube/resources/namespace/namespace_name.js'; import { InjectTokens } from '../../../src/core/dependency_injection/inject_tokens.js'; import { KubeConfig } from '@kubernetes/client-node'; import { MissingArgumentError } from '../../../src/core/errors.js'; describe('ProfileManager', () => { let tmpDir, configManager, profileManager, cacheDir; const namespace = NamespaceName.of('test-namespace'); const testProfileFile = path.join('test', 'data', 'test-profiles.yaml'); const kubeConfig = new KubeConfig(); kubeConfig.loadFromDefault(); const consensusNodes = [ { name: 'node1', nodeId: 1, namespace: namespace.name, cluster: kubeConfig.getCurrentCluster().name, context: kubeConfig.getCurrentContext(), dnsBaseDomain: 'cluster.local', dnsConsensusNodePattern: 'network-${nodeAlias}-svc.${namespace}.svc', fullyQualifiedDomainName: 'network-node1-svc.test-namespace.svc.cluster.local', }, { name: 'node2', nodeId: 2, namespace: namespace.name, cluster: kubeConfig.getCurrentCluster().name, context: kubeConfig.getCurrentContext(), dnsBaseDomain: 'cluster.local', dnsConsensusNodePattern: 'network-${nodeAlias}-svc.${namespace}.svc', fullyQualifiedDomainName: 'network-node2-svc.test-namespace.svc.cluster.local', }, { name: 'node3', nodeId: 3, namespace: namespace.name, cluster: kubeConfig.getCurrentCluster().name, context: kubeConfig.getCurrentContext(), dnsBaseDomain: 'cluster.local', dnsConsensusNodePattern: 'network-${nodeAlias}-svc.${namespace}.svc', fullyQualifiedDomainName: 'network-node3-svc.test-namespace.svc.cluster.local', }, ]; let stagingDir = ''; before(() => { resetForTest(namespace.name); tmpDir = getTmpDir(); configManager = container.resolve(InjectTokens.ConfigManager); profileManager = new ProfileManager(undefined, undefined, tmpDir); configManager.setFlag(flags.nodeAliasesUnparsed, 'node1,node2,node4'); configManager.setFlag(flags.cacheDir, getTestCacheDir('ProfileManager')); configManager.setFlag(flags.releaseTag, version.HEDERA_PLATFORM_VERSION); cacheDir = configManager.getFlag(flags.cacheDir); configManager.setFlag(flags.apiPermissionProperties, path.join(cacheDir, 'templates', 'api-permission.properties')); configManager.setFlag(flags.applicationEnv, path.join(cacheDir, 'templates', 'application.env')); configManager.setFlag(flags.applicationProperties, path.join(cacheDir, 'templates', 'application.properties')); configManager.setFlag(flags.bootstrapProperties, path.join(cacheDir, 'templates', 'bootstrap.properties')); configManager.setFlag(flags.log4j2Xml, path.join(cacheDir, 'templates', 'log4j2.xml')); configManager.setFlag(flags.settingTxt, path.join(cacheDir, 'templates', 'settings.txt')); stagingDir = Templates.renderStagingDir(configManager.getFlag(flags.cacheDir), configManager.getFlag(flags.releaseTag)); if (!fs.existsSync(stagingDir)) { fs.mkdirSync(stagingDir, { recursive: true }); } }); after(() => { fs.rmSync(tmpDir, { recursive: true }); }); it('should throw error for missing profile file', () => { try { configManager.setFlag(flags.profileFile, 'INVALID'); profileManager.loadProfiles(true); throw new Error(); } catch (e) { expect(e.message).to.include('profileFile does not exist'); } }); it('should be able to load a profile file', () => { configManager.setFlag(flags.profileFile, testProfileFile); const profiles = profileManager.loadProfiles(true); expect(profiles).not.to.be.null; for (const entry of profiles) { const profile = entry[1]; expect(profile).not.to.be.null; for (const component of ['consensus', 'rpcRelay', 'haproxy', 'envoyProxy', 'explorer', 'mirror', 'minio']) { expect(profile[component]).not.to.be.undefined; } } }); const testCases = [{ profileName: 'test', profileFile: testProfileFile }]; describe('determine chart values for a profile', () => { testCases.forEach(input => { it(`should determine Solo chart values [profile = ${input.profileName}]`, async () => { configManager.setFlag(flags.profileFile, input.profileFile); configManager.setFlag(flags.namespace, 'test-namespace'); const resources = ['templates', 'profiles']; for (const dirName of resources) { const srcDir = path.resolve(path.join(constants.RESOURCES_DIR, dirName)); if (!fs.existsSync(srcDir)) continue; const destDir = path.resolve(path.join(cacheDir, dirName)); if (!fs.existsSync(destDir)) { fs.mkdirSync(destDir, { recursive: true }); } fs.cpSync(srcDir, destDir, { recursive: true }); } profileManager.loadProfiles(true); const valuesFile = await profileManager.prepareValuesForSoloChart(input.profileName, consensusNodes); expect(valuesFile).not.to.be.null; expect(fs.existsSync(valuesFile)).to.be.ok; // validate the yaml const valuesYaml = yaml.parse(fs.readFileSync(valuesFile).toString()); expect(valuesYaml.hedera.nodes.length).to.equal(3); expect(valuesYaml.defaults.root.resources.limits.cpu).not.to.be.null; expect(valuesYaml.defaults.root.resources.limits.memory).not.to.be.null; // check all sidecars have resources for (const component of constants.HEDERA_NODE_SIDECARS) { expect(valuesYaml.defaults.sidecars[component].resources.limits.cpu).not.to.be.null; expect(valuesYaml.defaults.sidecars[component].resources.limits.memory).not.to.be.null; } // check proxies have resources for (const component of ['haproxy', 'envoyProxy']) { expect(valuesYaml.defaults[component].resources.limits.cpu).not.to.be.null; expect(valuesYaml.defaults[component].resources.limits.memory).not.to.be.null; } // check minio-tenant has resources expect(valuesYaml['minio-server'].tenant.pools[0].resources.limits.cpu).not.to.be.null; expect(valuesYaml['minio-server'].tenant.pools[0].resources.limits.memory).not.to.be.null; }); it('prepareValuesForSoloChart should set the value of a key to the contents of a file', async () => { configManager.setFlag(flags.profileFile, testProfileFile); configManager.setFlag(flags.namespace, 'test-namespace'); // profileManager.loadProfiles(true) const file = path.join(tmpDir, 'application.env'); const fileContents = '# row 1\n# row 2\n# row 3'; fs.writeFileSync(file, fileContents); configManager.setFlag(flags.applicationEnv, file); const destFile = path.join(stagingDir, 'templates', 'application.env'); fs.cpSync(file, destFile, { force: true }); const cachedValuesFile = await profileManager.prepareValuesForSoloChart('test', consensusNodes); const valuesYaml = yaml.parse(fs.readFileSync(cachedValuesFile).toString()); expect(valuesYaml.hedera.configMaps.applicationEnv).to.equal(fileContents); }); it(`should determine mirror-node chart values [profile = ${input.profileName}]`, async () => { configManager.setFlag(flags.profileFile, input.profileFile); configManager.setFlag(flags.cacheDir, getTestCacheDir('ProfileManager')); configManager.setFlag(flags.releaseTag, version.HEDERA_PLATFORM_VERSION); profileManager.loadProfiles(true); const valuesFile = (await profileManager.prepareValuesForMirrorNodeChart(input.profileName)); expect(fs.existsSync(valuesFile)).to.be.ok; // validate yaml const valuesYaml = yaml.parse(fs.readFileSync(valuesFile).toString()); expect(valuesYaml.postgresql.persistence.size).not.to.be.null; expect(valuesYaml.postgresql.postgresql.resources.limits.cpu).not.to.be.null; expect(valuesYaml.postgresql.postgresql.resources.limits.memory).not.to.be.null; for (const component of ['grpc', 'rest', 'web3', 'importer']) { expect(valuesYaml[component].resources.limits.cpu).not.to.be.null; expect(valuesYaml[component].resources.limits.memory).not.to.be.null; expect(valuesYaml[component].readinessProbe.failureThreshold).to.equal(60); expect(valuesYaml[component].livenessProbe.failureThreshold).to.equal(60); } }); it(`should determine hedera-explorer chart values [profile = ${input.profileName}]`, async () => { configManager.setFlag(flags.profileFile, input.profileFile); configManager.setFlag(flags.cacheDir, getTestCacheDir('ProfileManager')); configManager.setFlag(flags.releaseTag, version.HEDERA_PLATFORM_VERSION); profileManager.loadProfiles(true); const valuesFile = (await profileManager.prepareValuesHederaExplorerChart(input.profileName)); expect(fs.existsSync(valuesFile)).to.be.ok; // validate yaml const valuesYaml = yaml.parse(fs.readFileSync(valuesFile).toString()); expect(valuesYaml.resources.limits.cpu).not.to.be.null; expect(valuesYaml.resources.limits.memory).not.to.be.null; }); it(`should determine rpc-relay chart values [profile = ${input.profileName}]`, async () => { configManager.setFlag(flags.profileFile, input.profileFile); profileManager.loadProfiles(true); const valuesFile = (await profileManager.prepareValuesForRpcRelayChart(input.profileName)); expect(fs.existsSync(valuesFile)).to.be.ok; // validate yaml const valuesYaml = yaml.parse(fs.readFileSync(valuesFile).toString()); expect(valuesYaml.resources.limits.cpu).not.to.be.null; expect(valuesYaml.resources.limits.memory).not.to.be.null; }); }); }); describe('prepareConfigText', () => { it('should write and return the path to the config.txt file', async () => { const nodeAccountMap = new Map(); nodeAccountMap.set('node1', '0.0.3'); nodeAccountMap.set('node2', '0.0.4'); nodeAccountMap.set('node3', '0.0.5'); const destPath = path.join(tmpDir, 'staging'); fs.mkdirSync(destPath, { recursive: true }); const renderedConfigFile = await profileManager.prepareConfigTxt(nodeAccountMap, consensusNodes, destPath, version.HEDERA_PLATFORM_VERSION); // expect that the config.txt file was created and exists const configFile = path.join(destPath, 'config.txt'); expect(renderedConfigFile).to.equal(configFile); expect(fs.existsSync(configFile)).to.be.ok; const configText = fs.readFileSync(configFile).toString(); // expect that the config.txt file contains the namespace expect(configText).to.include(namespace); // expect that the config.txt file contains the node account IDs expect(configText).to.include('0.0.3'); expect(configText).to.include('0.0.4'); expect(configText).to.include('0.0.5'); // expect the config.txt file to contain the node IDs expect(configText).to.include('node1'); expect(configText).to.include('node2'); expect(configText).to.include('node3'); }); it('should fail when no nodeAliases', async () => { const nodeAccountMap = new Map(); try { await profileManager.prepareConfigTxt(nodeAccountMap, consensusNodes, '', version.HEDERA_PLATFORM_VERSION); } catch (e) { expect(e).to.be.instanceOf(MissingArgumentError); expect(e.message).to.include('nodeAccountMap the map of node IDs to account IDs is required'); } }); it('should fail when destPath does not exist', async () => { const nodeAccountMap = new Map(); nodeAccountMap.set('node1', '0.0.3'); const destPath = path.join(tmpDir, 'missing-directory'); try { await profileManager.prepareConfigTxt(nodeAccountMap, consensusNodes, destPath, version.HEDERA_PLATFORM_VERSION); } catch (e) { expect(e.message).to.contain('config destPath does not exist'); expect(e.message).to.contain(destPath); } }); }); }); //# sourceMappingURL=profile_manager.test.js.map