@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
166 lines • 8.83 kB
JavaScript
// SPDX-License-Identifier: Apache-2.0
import { describe } from 'mocha';
import { resetForTest } from '../../test-container.js';
import { container } from 'tsyringe-neo';
import { InjectTokens } from '../../../src/core/dependency-injection/inject-tokens.js';
import fs from 'node:fs';
import { DEFAULT_LOCAL_CONFIG_FILE, RESOURCES_DIR } from '../../../src/core/constants.js';
import { Duration } from '../../../src/core/time/duration.js';
import { PathEx } from '../../../src/business/utils/path-ex.js';
import { EndToEndTestSuiteBuilder } from '../end-to-end-test-suite-builder.js';
import { InitTest } from './tests/init-test.js';
import { ClusterReferenceTest } from './tests/cluster-reference-test.js';
import { DeploymentTest } from './tests/deployment-test.js';
import { ConsensusNodeTest } from './tests/consensus-node-test.js';
import { NetworkTest } from './tests/network-test.js';
import { MirrorNodeTest } from './tests/mirror-node-test.js';
import { ExplorerTest } from './tests/explorer-test.js';
import { RelayTest } from './tests/relay-test.js';
import { spawn } from 'node:child_process';
import { MetricsServerImpl } from '../../../src/business/runtime-state/services/metrics-server-impl.js';
import * as constants from '../../../src/core/constants.js';
import { BlockNodeTest } from './tests/block-node-test.js';
import { getTemporaryDirectory } from '../../test-utility.js';
const testName = 'external-database-test';
// Use dual-cluster specific values file with higher memory limits to prevent OOM
const dualClusterValuesFile = PathEx.joinWithRealPath(RESOURCES_DIR, 'mirror-node-values-dual-cluster-minimal.yaml');
const configFiles = {
'api-permission.properties': 'api-permission.properties.txt',
'application.env': 'application.env.txt',
[constants.APPLICATION_PROPERTIES]: 'application.properties.txt',
'bootstrap.properties': 'bootstrap.properties.txt',
'log4j2.xml': 'log4j2.xml.txt',
'settings.txt': 'settings.txt.txt',
};
const endToEndTestSuite = new EndToEndTestSuiteBuilder()
.withTestName(testName)
.withTestSuiteName('External Database E2E Test Suite')
.withNamespace(testName)
.withDeployment(`${testName}-deployment`)
.withClusterCount(2)
.withConsensusNodesCount(2)
.withLoadBalancerEnabled(true)
.withPinger(true)
.withShard(3)
.withRealm(2)
.withApiPermissionProperties(configFiles['api-permission.properties'])
.withApplicationEnvironment(configFiles['application.env'])
.withApplicationProperties(configFiles[constants.APPLICATION_PROPERTIES])
.withBootstrapProperties(configFiles['bootstrap.properties'])
.withLog4j2Xml(configFiles['log4j2.xml'])
.withSettingsTxt(configFiles['settings.txt'])
.withTestSuiteCallback((options, preDestroy) => {
describe('External Database E2E Test', () => {
const { testCacheDirectory, testLogger, namespace, contexts } = options;
const blockNodeEnabled = process.env.SOLO_E2E_EXTERNAL_DB_TEST_BLOCK_NODE === 'true';
const topicTestOnly = process.env.SOLO_E2E_EXTERNAL_DB_TEST_TOPIC_ONLY === 'true';
before(async () => {
fs.rmSync(testCacheDirectory, { recursive: true, force: true });
try {
fs.rmSync(PathEx.joinWithRealPath(testCacheDirectory, '..', DEFAULT_LOCAL_CONFIG_FILE), {
force: true,
});
}
catch {
// allowed to fail if the file doesn't exist
}
resetForTest(namespace.name, testCacheDirectory, false);
for (const item of contexts) {
const k8Client = container.resolve(InjectTokens.K8Factory).getK8(item);
await k8Client.namespaces().delete(namespace);
}
testLogger.info(`${testName}: starting ${testName} e2e test`);
// copy all consensus config files to a temporary directory with non-default names
const templateDirectory = PathEx.joinWithRealPath(RESOURCES_DIR, 'templates');
const temporaryDirectory = getTemporaryDirectory();
for (const [sourceFileName, targetFileName] of Object.entries(configFiles)) {
fs.cpSync(PathEx.join(templateDirectory, sourceFileName), PathEx.join(temporaryDirectory, targetFileName));
}
}).timeout(Duration.ofMinutes(5).toMillis());
after(async () => {
await preDestroy(endToEndTestSuite);
}).timeout(Duration.ofMinutes(5).toMillis());
beforeEach(async () => {
testLogger.info(`${testName}: resetting containers for each test`);
resetForTest(namespace.name, testCacheDirectory, false);
testLogger.info(`${testName}: finished resetting containers for each test`);
});
InitTest.init(options);
ClusterReferenceTest.connect(options);
DeploymentTest.create(options);
DeploymentTest.addCluster(options);
ConsensusNodeTest.keys(options);
if (blockNodeEnabled) {
BlockNodeTest.add(options);
}
NetworkTest.deploy(options);
ConsensusNodeTest.setup(options);
ConsensusNodeTest.start(options);
// Mirror node, explorer and relay node are deployed to the second cluster
MirrorNodeTest.installPostgres(options);
// Use dual-cluster specific values file with higher memory limits
MirrorNodeTest.deployWithExternalDatabase({ ...options, valuesFile: dualClusterValuesFile });
ExplorerTest.add(options);
RelayTest.add(options);
DeploymentTest.info(options);
DeploymentTest.verifyDeploymentConfigInfo(options);
it('should run smoke tests', async () => {
// Mirror node is deployed to the second cluster in the dual-cluster setup.
// Pass its context so solo_smoke_test.sh can issue kubectl commands against
// the right cluster (e.g. kubectl wait for mirror-grpc readiness).
const mirrorClusterContext = contexts[1];
const scriptPath = `export SOLO_HOME=${testCacheDirectory}; \
export SHARD_NUM=3; \
export REALM_NUM=2; \
export NEW_NODE_ACCOUNT_ID=3.2.3; \
export SOLO_NAMESPACE=${namespace.name}; \
export SOLO_CLUSTER_CONTEXT=${mirrorClusterContext}; \
export SOLO_CACHE_DIR=${testCacheDirectory}; \
export SOLO_DEPLOYMENT=${testName}-deployment; \
${topicTestOnly
? 'source .github/workflows/script/helper.sh && cd .. && ' +
'create_test_account ${SOLO_DEPLOYMENT} && cd solo && node scripts/create-topic.js'
: '.github/workflows/script/solo_smoke_test.sh'}`;
// running the script and show its output in real time for easy to debug
// and check its progress
return new Promise((resolve, reject) => {
const process = spawn(scriptPath, {
stdio: 'pipe', // Use pipe to capture output
shell: true, // Run in shell to support bash features
});
// Stream stdout in real-time
process.stdout.on('data', (data) => {
data.toString().replaceAll('::group::', '\r::group::').replaceAll('::endgroup::', '\r::endgroup::');
console.log(`${data}`.trim());
});
// Stream stderr in real-time
process.stderr.on('data', (data) => {
data.toString().replaceAll('::group::', '\r::group::').replaceAll('::endgroup::', '\r::endgroup::');
console.log(`${data}`.trim());
});
// Handle process completion
process.on('close', (code) => {
if (code) {
const error = new Error(`Smoke test failed with exit code ${code}`);
reject(error);
}
else {
console.log('Smoke test execution succeeded');
resolve();
}
});
// Handle process errors
process.on('error', (error) => {
console.error('Failed to start smoke test process:', error.message);
reject(error);
});
});
}).timeout(Duration.ofMinutes(30).toMillis());
it('Should write log metrics', async () => {
await new MetricsServerImpl().logMetrics(testName, PathEx.join(constants.SOLO_LOGS_DIR, `${testName}`), undefined, undefined, contexts);
});
}).timeout(Duration.ofMinutes(40).toMillis());
})
.build();
endToEndTestSuite.runTestSuite();
//# sourceMappingURL=external-database.test.js.map