From 38bc1dd3bd561f61e2247e27f5af61ceda0f5cf2 Mon Sep 17 00:00:00 2001 From: Brendan Burns <5751682+brendandburns@users.noreply.github.com> Date: Wed, 26 Feb 2025 15:47:08 +0000 Subject: [PATCH] improve coverage for top.ts by covering topNodes. --- eslint.config.js | 8 +++- src/top_test.ts | 96 ++++++++++++++++++++++++++++++++++++++++++------ src/util.ts | 3 ++ 3 files changed, 95 insertions(+), 12 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 3d83c71ee6..7090f7a630 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -21,7 +21,13 @@ export default tseslint.config( '@typescript-eslint/no-empty-object-type': 'off', '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-non-null-assertion': 'off', - '@typescript-eslint/no-unused-vars': ['error', { args: 'none' }], + '@typescript-eslint/no-unused-vars': [ + 'error', + { + args: 'none', + destructuredArrayIgnorePattern: '^_', + }, + ], }, }, ); diff --git a/src/top_test.ts b/src/top_test.ts index e149d723b7..3a491ceb3c 100644 --- a/src/top_test.ts +++ b/src/top_test.ts @@ -2,8 +2,8 @@ import { deepEqual, deepStrictEqual, strictEqual } from 'assert'; import nock from 'nock'; import { KubeConfig } from './config.js'; import { Metrics, PodMetricsList } from './metrics.js'; -import { CurrentResourceUsage, topPods } from './top.js'; -import { CoreV1Api, V1Pod } from './api.js'; +import { CurrentResourceUsage, ResourceUsage, topNodes, topPods } from './top.js'; +import { CoreV1Api, V1Node, V1Pod } from './api.js'; const emptyPodMetrics: PodMetricsList = { kind: 'PodMetricsList', @@ -83,6 +83,10 @@ const podList: V1Pod[] = [ }, }, ], + nodeName: 'node1', + }, + status: { + phase: 'Running', }, }, { @@ -118,6 +122,43 @@ const podList: V1Pod[] = [ }, }, ], + nodeName: 'node1', + }, + status: { + phase: 'Running', + }, + }, +]; + +const nodeList: V1Node[] = [ + { + metadata: { + name: 'node1', + }, + status: { + capacity: { + cpu: '4', + memory: '16Gi', + }, + allocatable: { + cpu: '4', + memory: '16Gi', + }, + }, + }, + { + metadata: { + name: 'node2', + }, + status: { + capacity: { + cpu: '8', + memory: '32Gi', + }, + allocatable: { + cpu: '8', + memory: '32Gi', + }, }, }, ]; @@ -134,22 +175,23 @@ const testConfigOptions: any = { const systemUnderTest = ( namespace?: string, options: any = testConfigOptions, -): [() => ReturnType, nock.Scope] => { +): [() => ReturnType, () => ReturnType, nock.Scope] => { const kc = new KubeConfig(); kc.loadFromOptions(options); const metricsClient = new Metrics(kc); const core = kc.makeApiClient(CoreV1Api); const topPodsFunc = () => topPods(core, metricsClient, namespace); + const topNodesFunc = () => topNodes(core); const scope = nock(testConfigOptions.clusters[0].server); - return [topPodsFunc, scope]; + return [topPodsFunc, topNodesFunc, scope]; }; describe('Top', () => { describe('topPods', () => { it('should return empty when no pods', async () => { - const [topPodsFunc, scope] = systemUnderTest(); + const [topPodsFunc, _, scope] = systemUnderTest(); const podMetrics = scope.get('/apis/metrics.k8s.io/v1beta1/pods').reply(200, emptyPodMetrics); const pods = scope.get('/api/v1/pods').reply(200, { items: [], @@ -160,7 +202,7 @@ describe('Top', () => { pods.done(); }); it('should return use cluster scope when namespace empty string', async () => { - const [topPodsFunc, scope] = systemUnderTest(''); + const [topPodsFunc, _, scope] = systemUnderTest(''); const podMetrics = scope.get('/apis/metrics.k8s.io/v1beta1/pods').reply(200, emptyPodMetrics); const pods = scope.get('/api/v1/pods').reply(200, { items: [], @@ -171,7 +213,7 @@ describe('Top', () => { pods.done(); }); it('should return cluster wide pod metrics', async () => { - const [topPodsFunc, scope] = systemUnderTest(); + const [topPodsFunc, _, scope] = systemUnderTest(); const podMetrics = scope.get('/apis/metrics.k8s.io/v1beta1/pods').reply(200, mockedPodMetrics); const pods = scope.get('/api/v1/pods').reply(200, { items: podList, @@ -235,7 +277,7 @@ describe('Top', () => { pods.done(); }); it('should return best effort pod metrics', async () => { - const [topPodsFunc, scope] = systemUnderTest(); + const [topPodsFunc, _, scope] = systemUnderTest(); const podMetrics = scope.get('/apis/metrics.k8s.io/v1beta1/pods').reply(200, mockedPodMetrics); const pods = scope.get('/api/v1/pods').reply(200, { items: bestEffortPodList, @@ -263,7 +305,7 @@ describe('Top', () => { pods.done(); }); it('should return 0 when pod metrics missing', async () => { - const [topPodsFunc, scope] = systemUnderTest(); + const [topPodsFunc, _, scope] = systemUnderTest(); const podMetrics = scope.get('/apis/metrics.k8s.io/v1beta1/pods').reply(200, emptyPodMetrics); const pods = scope.get('/api/v1/pods').reply(200, { items: podList, @@ -286,7 +328,7 @@ describe('Top', () => { pods.done(); }); it('should return empty array when pods missing', async () => { - const [topPodsFunc, scope] = systemUnderTest(); + const [topPodsFunc, _, scope] = systemUnderTest(); const podMetrics = scope.get('/apis/metrics.k8s.io/v1beta1/pods').reply(200, mockedPodMetrics); const pods = scope.get('/api/v1/pods').reply(200, { items: [], @@ -297,7 +339,7 @@ describe('Top', () => { pods.done(); }); it('should return namespace pod metrics', async () => { - const [topPodsFunc, scope] = systemUnderTest(TEST_NAMESPACE); + const [topPodsFunc, _, scope] = systemUnderTest(TEST_NAMESPACE); const podMetrics = scope .get(`/apis/metrics.k8s.io/v1beta1/namespaces/${TEST_NAMESPACE}/pods`) .reply(200, mockedPodMetrics); @@ -363,4 +405,36 @@ describe('Top', () => { pods.done(); }); }); + describe('topNodes', () => { + it('should return empty when no nodes', async () => { + const [_, topNodesFunc, scope] = systemUnderTest(); + const nodes = scope.get('/api/v1/nodes').reply(200, { + items: [], + }); + const result = await topNodesFunc(); + deepStrictEqual(result, []); + nodes.done(); + }); + + it('should return cluster wide node metrics', async () => { + const [_, topNodesFunc, scope] = systemUnderTest(); + const pods = scope.get('/api/v1/pods').times(2).reply(200, { + items: podList, + }); + const nodes = scope.get('/api/v1/nodes').reply(200, { + items: nodeList, + }); + const result = await topNodesFunc(); + strictEqual(result.length, 2); + deepStrictEqual(result[0].CPU, new ResourceUsage(4, 2.2, 2.2)); + deepStrictEqual( + result[0].Memory, + new ResourceUsage(BigInt('17179869184'), BigInt('262144000'), BigInt('314572800')), + ); + deepStrictEqual(result[1].CPU, new ResourceUsage(8, 0, 0)); + deepStrictEqual(result[1].Memory, new ResourceUsage(BigInt('34359738368'), 0, 0)); + pods.done(); + nodes.done(); + }); + }); }); diff --git a/src/util.ts b/src/util.ts index ccbdbe408c..9477223b9f 100644 --- a/src/util.ts +++ b/src/util.ts @@ -3,6 +3,9 @@ import { CoreV1Api, V1Container, V1Pod } from './gen/index.js'; export async function podsForNode(api: CoreV1Api, nodeName: string): Promise { const allPods = await api.listPodForAllNamespaces(); + if (!allPods.items) { + return []; + } return allPods.items.filter((pod: V1Pod) => pod.spec!.nodeName === nodeName); }