@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
123 lines • 6.07 kB
JavaScript
// 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