UNPKG

@hashgraph/solo

Version:

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

123 lines 6.07 kB
// SPDX-License-Identifier: Apache-2.0 import { expect } from 'chai'; import { describe, it } from 'mocha'; import { V1Pod, V1PodStatus, V1ContainerStatus, V1ContainerState, V1ContainerStateWaiting, V1ContainerStateTerminated, V1ContainerStateRunning, V1ObjectMeta, } from '@kubernetes/client-node'; import { detectFatalContainerError } from '../../../../src/integration/kube/k8-client/resources/pod/k8-client-pods.js'; function buildPodWithContainerStatus(containerStatus) { const pod = new V1Pod(); pod.metadata = new V1ObjectMeta(); pod.metadata.name = 'test-pod'; pod.status = new V1PodStatus(); pod.status.containerStatuses = [containerStatus]; return pod; } function buildPodWithInitContainerStatus(containerStatus) { const pod = new V1Pod(); pod.metadata = new V1ObjectMeta(); pod.metadata.name = 'test-pod'; pod.status = new V1PodStatus(); pod.status.initContainerStatuses = [containerStatus]; return pod; } function buildWaitingContainerStatus(reason, message) { const waiting = new V1ContainerStateWaiting(); waiting.reason = reason; waiting.message = message; const state = new V1ContainerState(); state.waiting = waiting; const containerStatus = new V1ContainerStatus(); containerStatus.name = 'test-container'; containerStatus.state = state; return containerStatus; } function buildTerminatedContainerStatus(reason, exitCode) { const terminated = new V1ContainerStateTerminated(); terminated.reason = reason; terminated.exitCode = exitCode; const state = new V1ContainerState(); state.terminated = terminated; const containerStatus = new V1ContainerStatus(); containerStatus.name = 'test-container'; containerStatus.state = state; return containerStatus; } describe('detectFatalContainerError', () => { it('should return undefined for a pod with no container statuses', () => { const pod = new V1Pod(); pod.metadata = new V1ObjectMeta(); pod.metadata.name = 'empty-pod'; pod.status = new V1PodStatus(); expect(detectFatalContainerError(pod)).to.be.undefined; }); it('should return undefined for a pod with a healthy running container', () => { const running = new V1ContainerStateRunning(); running.startedAt = new Date(); const state = new V1ContainerState(); state.running = running; const containerStatus = new V1ContainerStatus(); containerStatus.name = 'healthy-container'; containerStatus.state = state; const pod = buildPodWithContainerStatus(containerStatus); expect(detectFatalContainerError(pod)).to.be.undefined; }); for (const reason of ['InvalidImageName', 'RegistryUnavailable']) { it(`should detect fatal waiting reason: ${reason}`, () => { const pod = buildPodWithContainerStatus(buildWaitingContainerStatus(reason)); const result = detectFatalContainerError(pod); expect(result).to.include(reason); expect(result).to.include('"test-pod"'); expect(result).to.include('"test-container"'); }); } for (const reason of ['ImagePullBackOff', 'ErrImagePull', 'ImageInspectError']) { it(`should detect fatal waiting reason when message is non-recoverable: ${reason}`, () => { const message = 'failed to pull image "ghcr.io/example/app:1.2.3": not found'; const pod = buildPodWithContainerStatus(buildWaitingContainerStatus(reason, message)); const result = detectFatalContainerError(pod); expect(result).to.include(reason); expect(result).to.include(message); expect(result).to.include('"test-pod"'); expect(result).to.include('"test-container"'); }); } it('should include message detail when present for ImagePullBackOff', () => { const message = 'failed to pull image "gcr.io/example/app:0.1.0-SNAPSHOT": not found'; const pod = buildPodWithContainerStatus(buildWaitingContainerStatus('ImagePullBackOff', message)); const result = detectFatalContainerError(pod); expect(result).to.include(message); }); it('should return undefined for a non-fatal waiting reason (e.g. ContainerCreating)', () => { const pod = buildPodWithContainerStatus(buildWaitingContainerStatus('ContainerCreating')); expect(detectFatalContainerError(pod)).to.be.undefined; }); it('should detect OOMKilled terminated reason', () => { const pod = buildPodWithContainerStatus(buildTerminatedContainerStatus('OOMKilled', 137)); const result = detectFatalContainerError(pod); expect(result).to.include('OOMKilled'); expect(result).to.include('137'); }); it('should return undefined for a non-fatal terminated reason (e.g. Completed)', () => { const pod = buildPodWithContainerStatus(buildTerminatedContainerStatus('Completed', 0)); expect(detectFatalContainerError(pod)).to.be.undefined; }); it('should detect fatal error in init container status', () => { const pod = buildPodWithInitContainerStatus(buildWaitingContainerStatus('ImagePullBackOff', 'manifest unknown')); const result = detectFatalContainerError(pod); expect(result).to.include('ImagePullBackOff'); }); it('should use <unknown> for pod and container name when metadata is absent', () => { const pod = new V1Pod(); pod.status = new V1PodStatus(); const containerStatus = new V1ContainerStatus(); const state = new V1ContainerState(); const waiting = new V1ContainerStateWaiting(); waiting.reason = 'ImagePullBackOff'; waiting.message = 'not found'; state.waiting = waiting; containerStatus.state = state; pod.status.containerStatuses = [containerStatus]; const result = detectFatalContainerError(pod); expect(result).to.include('<unknown>'); }); }); //# sourceMappingURL=detect-fatal-container-error.test.js.map