UNPKG

@hashgraph/solo

Version:

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

237 lines 12.3 kB
// SPDX-License-Identifier: Apache-2.0 import { after, before, describe, it } from 'mocha'; import { expect } from 'chai'; import each from 'mocha-each'; import fs from 'node:fs'; import net from 'node:net'; import os from 'node:os'; import path from 'node:path'; import { v4 as uuid4 } from 'uuid'; import { SoloError } from '../../../../src/core/errors/solo-error.js'; import * as constants from '../../../../src/core/constants.js'; import { Flags as flags } from '../../../../src/commands/flags.js'; import crypto from 'node:crypto'; import { PodName } from '../../../../src/integration/kube/resources/pod/pod-name.js'; import { Duration } from '../../../../src/core/time/duration.js'; import { container } from 'tsyringe-neo'; import { NamespaceName } from '../../../../src/types/namespace/namespace-name.js'; import { PodReference } from '../../../../src/integration/kube/resources/pod/pod-reference.js'; import { ContainerName } from '../../../../src/integration/kube/resources/container/container-name.js'; import { ContainerReference } from '../../../../src/integration/kube/resources/container/container-reference.js'; import { ServiceReference } from '../../../../src/integration/kube/resources/service/service-reference.js'; import { ServiceName } from '../../../../src/integration/kube/resources/service/service-name.js'; import { InjectTokens } from '../../../../src/core/dependency-injection/inject-tokens.js'; import { Argv } from '../../../helpers/argv-wrapper.js'; import { PathEx } from '../../../../src/business/utils/path-ex.js'; import { SoloPinoLogger } from '../../../../src/core/logging/solo-pino-logger.js'; const defaultTimeout = Duration.ofMinutes(2).toMillis(); const TEST_POD_IMAGE = process.env.K8_E2E_TEST_IMAGE ?? 'registry.k8s.io/e2e-test-images/busybox:1.29'; async function createPod(podReference, containerName, podLabelValue, k8Factory, image) { await k8Factory .default() .pods() .create(podReference, { app: podLabelValue }, containerName, image, ['/bin/sh', '-c', 'sleep 7200'], ['/bin/sh', '-c', 'exit 0']); } describe('K8', () => { const testLogger = new SoloPinoLogger('debug', true); const configManager = container.resolve(InjectTokens.ConfigManager); const k8Factory = container.resolve(InjectTokens.K8Factory); const testNamespace = NamespaceName.of('k8-e2e'); const argv = Argv.initializeEmpty(); const podName = PodName.of(`test-pod-${uuid4()}`); const podReference = PodReference.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.setArg(flags.namespace, testNamespace.name); configManager.update(argv.build()); if (!(await k8Factory.default().namespaces().has(testNamespace))) { await k8Factory.default().namespaces().create(testNamespace); } await createPod(podReference, containerName, podLabelValue, k8Factory, TEST_POD_IMAGE); const serviceReference = ServiceReference.of(testNamespace, ServiceName.of(serviceName)); await k8Factory.default().services().create(serviceReference, { app: 'svc-test' }, 80, 80); // wait 10 seconds for the pod up and running await new Promise(resolve => setTimeout(resolve, 10_000)); } catch (error) { console.log(`${error}, ${error.stack}`); throw error; } }); after(async function () { this.timeout(defaultTimeout); try { await k8Factory.default().pods().readByReference(PodReference.of(testNamespace, podName)).killPod(); argv.setArg(flags.namespace, constants.SOLO_SETUP_NAMESPACE.name); configManager.update(argv.build()); } catch (error) { console.log(error); throw error; } }); 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}`]); expect(await k8Factory .default() .containers() .readByRef(ContainerReference.of(pods[0].podReference, 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 localTemporaryDirectory = fs.mkdtempSync(PathEx.join(os.tmpdir(), 'k8-test')); const remoteTemporaryDirectory = '/tmp'; const fileName = path.basename(localFilePath); const remoteFilePath = `${remoteTemporaryDirectory}/${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(ContainerReference.of(podReference, containerName)) .copyTo(localFilePath, remoteTemporaryDirectory)).to.be.true; // download the same file expect(await k8Factory .default() .containers() .readByRef(ContainerReference.of(podReference, containerName)) .copyFrom(remoteFilePath, localTemporaryDirectory)).to.be.true; const downloadedFilePath = PathEx.joinWithRealPath(localTemporaryDirectory, 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(ContainerReference.of(podReference, containerName)) .execContainer(['rm', '-f', remoteFilePath]); fs.rmSync(localTemporaryDirectory, { recursive: true }); }).timeout(defaultTimeout); }); it('should be able to port forward gossip port', done => { const localPort = +constants.HEDERA_NODE_INTERNAL_GOSSIP_PORT; try { const podReference = PodReference.of(testNamespace, podName); k8Factory .default() .pods() .readByReference(podReference) .portForward(localPort, +constants.HEDERA_NODE_INTERNAL_GOSSIP_PORT) .then(async (server) => { // sleep for 5 seconds to allow the port forward to start await new Promise(resolve => setTimeout(resolve, 5000)); expect(server).not.to.be.null; // client const s = new net.Socket(); s.on('ready', async () => { s.destroy(); await k8Factory.default().pods().readByReference(podReference).stopPortForward(server); done(); }); s.on('error', async (error) => { s.destroy(); await k8Factory.default().pods().readByReference(podReference).stopPortForward(server); done(new SoloError(`could not connect to local port '${localPort}': ${error.message}`, error)); }); s.connect(localPort); }); } catch (error) { testLogger.showUserError(error); 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 = pods[0].podReference.name; const output = await k8Factory .default() .containers() .readByRef(ContainerReference.of(PodReference.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 pvcReference = PodReference.of(testNamespace, PodName.of(`test-pvc-${uuid4()}`)); try { await k8Factory.default().pvcs().create(pvcReference, { storage: '50Mi' }, ['ReadWriteOnce']); const pvcs = await k8Factory.default().pvcs().list(testNamespace); expect(pvcs).to.have.length.greaterThan(0); } catch (error) { console.error(error); throw error; } finally { await k8Factory.default().pvcs().delete(pvcReference); } }).timeout(defaultTimeout); it('should be able to kill a pod', async () => { const podName = PodName.of(`test-pod-${uuid4()}`); const podReference = PodReference.of(testNamespace, podName); const podLabelValue = `test-${uuid4()}`; await createPod(podReference, containerName, podLabelValue, k8Factory, TEST_POD_IMAGE); await k8Factory.default().pods().readByReference(podReference).killPod(); const newPods = await k8Factory .default() .pods() .list(testNamespace, [`app=${podLabelValue}`]); expect(newPods).to.have.lengthOf(0); }); }); //# sourceMappingURL=k8-end-to-end.test.js.map