@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
193 lines • 9.04 kB
JavaScript
// SPDX-License-Identifier: Apache-2.0
import { expect } from 'chai';
import { before, describe, it, after } from 'mocha';
import { DefaultKindClientBuilder } from '../../../../../src/integration/kind/impl/default-kind-client-builder.js';
import { ClusterCreateOptionsBuilder } from '../../../../../src/integration/kind/model/create-cluster/create-cluster-options-builder.js';
import { KindCluster } from '../../../../../src/integration/kind/model/kind-cluster.js';
import { KindDependencyManager } from '../../../../../src/core/dependency-managers/index.js';
import { container } from 'tsyringe-neo';
import fs from 'node:fs';
import * as os from 'node:os';
import { resetForTest } from '../../../../test-container.js';
import { Duration } from '../../../../../src/core/time/duration.js';
import { exec } from 'node:child_process';
import { promisify } from 'node:util';
import { PathEx } from '../../../../../src/business/utils/path-ex.js';
import * as constants from '../../../../../src/core/constants.js';
import { InjectTokens } from '../../../../../src/core/dependency-injection/inject-tokens.js';
import path from 'node:path';
const execAsync = promisify(exec);
describe('KindClient Integration Tests', function () {
this.timeout(Duration.ofMinutes(1).toMillis());
let kindClient;
let kindPath;
const testClusterName = 'test-kind-client';
const temporaryDirectory = fs.mkdtempSync(PathEx.join(os.tmpdir(), 'kind-test-'));
let originalKubeConfigContext;
before(async () => {
resetForTest();
// Save original kubectl context if it exists
try {
const { stdout } = await execAsync('kubectl config current-context', {
env: { ...process.env, PATH: `${constants.SOLO_HOME_DIR}/bin${path.delimiter}${process.env.PATH}` },
});
originalKubeConfigContext = stdout.trim();
console.log(`Saved original kubectl context: ${originalKubeConfigContext}`);
}
catch {
console.log('No kubectl context found or kubectl not available');
originalKubeConfigContext = undefined;
}
// Download and install Kind
container.register(InjectTokens.KindInstallationDirectory, { useValue: temporaryDirectory });
const kindManager = container.resolve(KindDependencyManager);
try {
await kindManager.install();
}
catch (error) {
console.error('Error checking if Kind is installed locally:', error);
throw error;
}
kindPath = kindManager.isInstalledLocally()
? PathEx.join(temporaryDirectory, constants.KIND)
: await kindManager.getExecutable();
console.log(`Using Kind at: ${kindPath}`);
// Create Kind client
const clientBuilder = new DefaultKindClientBuilder();
try {
kindClient = await clientBuilder.executable(kindPath).build();
}
catch (error) {
console.error('Error building Kind client:', error);
throw error;
}
}).timeout(Duration.ofMinutes(2).toMillis());
after(async () => {
if (kindClient) {
try {
// Clean up test cluster if it exists
const clusters = await kindClient.getClusters();
if (clusters.some((cluster) => cluster.name === testClusterName)) {
console.log(`Deleting test cluster: ${testClusterName}`);
await kindClient.deleteCluster(testClusterName);
}
}
catch (error) {
console.error('Error during cleanup:', error);
}
}
// Restore original kubectl context if it existed
if (originalKubeConfigContext) {
try {
console.log(`Restoring original kubectl context: ${originalKubeConfigContext}`);
await execAsync(`kubectl config use-context ${originalKubeConfigContext}`, {
env: { ...process.env, PATH: `${constants.SOLO_HOME_DIR}/bin${path.delimiter}${process.env.PATH}` },
});
}
catch (error) {
console.error('Error restoring kubectl context:', error);
}
}
// Clean up temp directory
try {
fs.rmSync(temporaryDirectory, { recursive: true, force: true });
}
catch (error) {
console.error('Error cleaning up temp directory:', error);
}
}).timeout(Duration.ofMinutes(2).toMillis());
it('should get Kind version', async () => {
const version = await kindClient.version();
expect(version).to.not.be.undefined;
expect(version.major).to.be.a('number');
expect(version.minor).to.be.a('number');
expect(version.patch).to.be.a('number');
console.log(`Kind version: ${version.toString()}`);
});
it('should create a cluster', async () => {
// after the Kubernetes upgrade in CI, kind commands sometimes fail initially due to a timeout when creating clusters
const maxRetries = 3;
let attempt = 0;
let lastError;
while (attempt < maxRetries) {
try {
const controller = new AbortController();
const onTimeoutCallback = setTimeout(() => {
controller.abort();
}, Duration.ofSeconds(20).toMillis());
console.log(`deleting cluster if it exists before creation attempt ${attempt + 1}`);
await kindClient.deleteCluster(testClusterName);
const options = ClusterCreateOptionsBuilder.builder().build();
console.log(`creating cluster, attempt ${attempt + 1}`);
const response = await kindClient.createCluster(testClusterName, options);
expect(response).to.not.be.undefined;
expect(response.name).to.equal(testClusterName);
clearTimeout(onTimeoutCallback);
return;
}
catch (error) {
lastError = error;
console.warn(`Attempt ${attempt + 1} to create cluster failed: ${error}`);
attempt++;
if (attempt < maxRetries) {
console.log('Retrying cluster creation...');
}
else {
console.error('Max retries reached. Failing test.');
throw lastError;
}
}
}
}).timeout(Duration.ofMinutes(4).toMillis());
it('should list clusters', async () => {
const clusters = await kindClient.getClusters();
expect(clusters).to.be.an('array');
expect(clusters.length).to.be.greaterThan(0);
const testCluster = clusters.find((c) => c.name === testClusterName);
expect(testCluster).to.not.be.undefined;
expect(testCluster).to.be.instanceOf(KindCluster);
expect(testCluster.name).to.equal(testClusterName);
});
it('should get cluster nodes', async () => {
const response = await kindClient.getNodes(testClusterName);
expect(response).to.not.be.undefined;
expect(response.nodes).to.be.an('array');
expect(response.nodes.length).to.be.greaterThan(0);
// Verify node naming pattern (should have the cluster name in it)
const nodes = response.nodes;
for (const node of nodes) {
expect(node).to.include(testClusterName);
}
});
it('should get kubeconfig', async () => {
const response = await kindClient.getKubeConfig(testClusterName);
expect(response).to.not.be.undefined;
expect(response.config).to.exist;
expect(response.config.apiVersion).to.eq('v1');
expect(response.config.clusters).to.exist;
expect(response.config.clusters.length).to.be.greaterThan(0);
expect(response.config.contexts).to.exist;
});
it('should export kubeconfig', async () => {
const response = await kindClient.exportKubeConfig(testClusterName);
expect(response).to.not.be.undefined;
expect(response.kubeConfigContext).to.be.a('string');
});
it('should export logs', async () => {
const response = await kindClient.exportLogs(testClusterName);
expect(response).to.not.be.undefined;
expect(response.exportPath).to.be.a('string');
// Verify logs directory exists
const logsExist = fs.existsSync(response.exportPath);
expect(logsExist).to.be.true;
});
it('should delete a cluster', async () => {
const response = await kindClient.deleteCluster(testClusterName);
expect(response).to.not.be.undefined;
// Verify cluster was deleted
const clusters = await kindClient.getClusters();
const deletedCluster = clusters.find((c) => c.name === testClusterName);
expect(deletedCluster).to.be.undefined;
});
});
//# sourceMappingURL=kind-client.test.js.map