@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
235 lines • 11.5 kB
JavaScript
/**
* SPDX-License-Identifier: Apache-2.0
*/
import { it, describe, after, before } from 'mocha';
import { expect } from 'chai';
import each from 'mocha-each';
import fs from 'fs';
import net from 'net';
import os from 'os';
import path from 'path';
import { v4 as uuid4 } from 'uuid';
import { SoloError } from '../../../../src/core/errors.js';
import * as constants from '../../../../src/core/constants.js';
import { Templates } from '../../../../src/core/templates.js';
import * as logging from '../../../../src/core/logging.js';
import { Flags as flags } from '../../../../src/commands/flags.js';
import crypto from 'crypto';
import { PodName } from '../../../../src/core/kube/resources/pod/pod_name.js';
import { Duration } from '../../../../src/core/time/duration.js';
import { container } from 'tsyringe-neo';
import { NamespaceName } from '../../../../src/core/kube/resources/namespace/namespace_name.js';
import { PodRef } from '../../../../src/core/kube/resources/pod/pod_ref.js';
import { ContainerName } from '../../../../src/core/kube/resources/container/container_name.js';
import { ContainerRef } from '../../../../src/core/kube/resources/container/container_ref.js';
import { ServiceRef } from '../../../../src/core/kube/resources/service/service_ref.js';
import { ServiceName } from '../../../../src/core/kube/resources/service/service_name.js';
import { InjectTokens } from '../../../../src/core/dependency_injection/inject_tokens.js';
const defaultTimeout = Duration.ofMinutes(2).toMillis();
async function createPod(podRef, containerName, podLabelValue, k8Factory) {
await k8Factory
.default()
.pods()
.create(podRef, { app: podLabelValue }, containerName, 'alpine:latest', ['/bin/sh', '-c', 'apk update && apk upgrade && apk add --update bash && sleep 7200'], ['bash', '-c', 'exit 0']);
}
describe('K8', () => {
const testLogger = logging.NewLogger('debug', true);
const configManager = container.resolve(InjectTokens.ConfigManager);
const k8Factory = container.resolve(InjectTokens.K8Factory);
const testNamespace = NamespaceName.of('k8-e2e');
const argv = [];
const podName = PodName.of(`test-pod-${uuid4()}`);
const podRef = PodRef.of(testNamespace, podName);
const containerName = ContainerName.of('alpine');
const podLabelValue = `test-${uuid4()}`;
const serviceName = `test-service-${uuid4()}`;
before(async function () {
this.timeout(defaultTimeout);
try {
argv[flags.namespace.name] = testNamespace.name;
configManager.update(argv);
if (!(await k8Factory.default().namespaces().has(testNamespace))) {
await k8Factory.default().namespaces().create(testNamespace);
}
await createPod(podRef, containerName, podLabelValue, k8Factory);
const serviceRef = ServiceRef.of(testNamespace, ServiceName.of(serviceName));
await k8Factory.default().services().create(serviceRef, { app: 'svc-test' }, 80, 80);
}
catch (e) {
console.log(`${e}, ${e.stack}`);
throw e;
}
});
after(async function () {
this.timeout(defaultTimeout);
try {
await k8Factory.default().pods().readByRef(PodRef.of(testNamespace, podName)).killPod();
argv[flags.namespace.name] = constants.SOLO_SETUP_NAMESPACE.name;
configManager.update(argv);
}
catch (e) {
console.log(e);
throw e;
}
});
it('should be able to list clusters', async () => {
const clusters = k8Factory.default().clusters().list();
expect(clusters).not.to.have.lengthOf(0);
}).timeout(defaultTimeout);
it('should be able to list namespaces', async () => {
const namespaces = await k8Factory.default().namespaces().list();
expect(namespaces).not.to.have.lengthOf(0);
const match = namespaces.filter(n => n.name === constants.DEFAULT_NAMESPACE.name);
expect(match).to.have.lengthOf(1);
}).timeout(defaultTimeout);
it('should be able to list context names', () => {
const contexts = k8Factory.default().contexts().list();
expect(contexts).not.to.have.lengthOf(0);
}).timeout(defaultTimeout);
it('should be able to create and delete a namespaces', async () => {
const name = uuid4();
expect(await k8Factory.default().namespaces().create(NamespaceName.of(name))).to.be.true;
expect(await k8Factory.default().namespaces().delete(NamespaceName.of(name))).to.be.true;
}).timeout(defaultTimeout);
it('should be able to run wait for pod', async () => {
const labels = [`app=${podLabelValue}`];
const pods = await k8Factory
.default()
.pods()
.waitForRunningPhase(testNamespace, labels, 30, constants.PODS_RUNNING_DELAY);
expect(pods).to.have.lengthOf(1);
}).timeout(defaultTimeout);
it('should be able to run wait for pod ready', async () => {
const labels = [`app=${podLabelValue}`];
const pods = await k8Factory.default().pods().waitForReadyStatus(testNamespace, labels, 100);
expect(pods).to.have.lengthOf(1);
}).timeout(defaultTimeout);
it('should be able to check if a path is directory inside a container', async () => {
const pods = await k8Factory
.default()
.pods()
.list(testNamespace, [`app=${podLabelValue}`]);
const podName = PodName.of(pods[0].metadata.name);
expect(await k8Factory
.default()
.containers()
.readByRef(ContainerRef.of(PodRef.of(testNamespace, podName), containerName))
.hasDir('/tmp')).to.be.true;
}).timeout(defaultTimeout);
const testCases = ['test/data/pem/keys/a-private-node0.pem', 'test/data/build-v0.54.0-alpha.4.zip'];
each(testCases).describe('test copyTo and copyFrom', localFilePath => {
it('should be able to copy a file to and from a container', async () => {
const pods = await k8Factory
.default()
.pods()
.waitForReadyStatus(testNamespace, [`app=${podLabelValue}`], 20);
expect(pods).to.have.lengthOf(1);
const localTmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'k8-test'));
const remoteTmpDir = '/tmp';
const fileName = path.basename(localFilePath);
const remoteFilePath = `${remoteTmpDir}/${fileName}`;
const originalFileData = fs.readFileSync(localFilePath);
const originalFileHash = crypto.createHash('sha384').update(originalFileData).digest('hex');
const originalStat = fs.statSync(localFilePath);
// upload the file
expect(await k8Factory
.default()
.containers()
.readByRef(ContainerRef.of(podRef, containerName))
.copyTo(localFilePath, remoteTmpDir)).to.be.true;
// download the same file
expect(await k8Factory
.default()
.containers()
.readByRef(ContainerRef.of(podRef, containerName))
.copyFrom(remoteFilePath, localTmpDir)).to.be.true;
const downloadedFilePath = path.join(localTmpDir, fileName);
const downloadedFileData = fs.readFileSync(downloadedFilePath);
const downloadedFileHash = crypto.createHash('sha384').update(downloadedFileData).digest('hex');
const downloadedStat = fs.statSync(downloadedFilePath);
expect(downloadedStat.size, 'downloaded file size should match original file size').to.equal(originalStat.size);
expect(downloadedFileHash, 'downloaded file hash should match original file hash').to.equal(originalFileHash);
// rm file inside the container
await k8Factory
.default()
.containers()
.readByRef(ContainerRef.of(podRef, containerName))
.execContainer(['rm', '-f', remoteFilePath]);
fs.rmdirSync(localTmpDir, { recursive: true });
}).timeout(defaultTimeout);
});
it('should be able to port forward gossip port', done => {
const podName = Templates.renderNetworkPodName('node1');
const localPort = +constants.HEDERA_NODE_INTERNAL_GOSSIP_PORT;
try {
const podRef = PodRef.of(testNamespace, podName);
k8Factory
.default()
.pods()
.readByRef(podRef)
.portForward(localPort, +constants.HEDERA_NODE_INTERNAL_GOSSIP_PORT)
.then(server => {
expect(server).not.to.be.null;
// client
const s = new net.Socket();
s.on('ready', async () => {
s.destroy();
await k8Factory.default().pods().readByRef(podRef).stopPortForward(server);
done();
});
s.on('error', async (e) => {
s.destroy();
await k8Factory.default().pods().readByRef(podRef).stopPortForward(server);
done(new SoloError(`could not connect to local port '${localPort}': ${e.message}`, e));
});
s.connect(localPort);
});
}
catch (e) {
testLogger.showUserError(e);
expect.fail();
}
// TODO enhance this test to do something with the port, this pod isn't even running, but it is still passing
}).timeout(defaultTimeout);
it('should be able to cat a file inside the container', async () => {
const pods = await k8Factory
.default()
.pods()
.list(testNamespace, [`app=${podLabelValue}`]);
const podName = PodName.of(pods[0].metadata.name);
const output = await k8Factory
.default()
.containers()
.readByRef(ContainerRef.of(PodRef.of(testNamespace, podName), containerName))
.execContainer(['cat', '/etc/hostname']);
expect(output.indexOf(podName.name)).to.equal(0);
}).timeout(defaultTimeout);
it('should be able to list persistent volume claims', async () => {
const pvcRef = PodRef.of(testNamespace, PodName.of(`test-pvc-${uuid4()}`));
try {
await k8Factory.default().pvcs().create(pvcRef, { storage: '50Mi' }, ['ReadWriteOnce']);
const pvcs = await k8Factory.default().pvcs().list(testNamespace, undefined);
expect(pvcs).to.have.length.greaterThan(0);
}
catch (e) {
console.error(e);
throw e;
}
finally {
await k8Factory.default().pvcs().delete(pvcRef);
}
}).timeout(defaultTimeout);
it('should be able to kill a pod', async () => {
const podName = PodName.of(`test-pod-${uuid4()}`);
const podRef = PodRef.of(testNamespace, podName);
const podLabelValue = `test-${uuid4()}`;
await createPod(podRef, containerName, podLabelValue, k8Factory);
await k8Factory.default().pods().readByRef(podRef).killPod();
const newPods = await k8Factory
.default()
.pods()
.list(testNamespace, [`app=${podLabelValue}`]);
expect(newPods).to.have.lengthOf(0);
});
});
//# sourceMappingURL=k8_e2e.test.js.map