Skip to content

Commit 5b848a7

Browse files
authored
Merge pull request #1368 from 0fatal/0fatal/metrics-patch
feat(metrics): add single node metrics and query options
2 parents fffd6be + 957e260 commit 5b848a7

File tree

2 files changed

+195
-18
lines changed

2 files changed

+195
-18
lines changed

src/metrics.ts

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export interface NodeMetric {
3131
name: string;
3232
selfLink?: string;
3333
creationTimestamp: string;
34+
labels?: { [key: string]: string };
3435
};
3536
timestamp: string;
3637
window: string;
@@ -60,43 +61,82 @@ export interface SinglePodMetrics extends PodMetric {
6061
apiVersion: 'metrics.k8s.io/v1beta1';
6162
}
6263

64+
export interface SingleNodeMetrics extends NodeMetric {
65+
kind: 'NodeMetrics';
66+
apiVersion: 'metrics.k8s.io/v1beta1';
67+
}
68+
69+
export interface MetricsOptions {
70+
/**
71+
* restrict the list of returned objects by labels
72+
*/
73+
labelSelector?: string;
74+
}
75+
6376
export class Metrics {
6477
private config: KubeConfig;
6578

6679
public constructor(config: KubeConfig) {
6780
this.config = config;
6881
}
6982

70-
public async getNodeMetrics(): Promise<NodeMetricsList> {
71-
return this.metricsApiRequest<NodeMetricsList>('/apis/metrics.k8s.io/v1beta1/nodes');
83+
public async getNodeMetrics(options?: MetricsOptions): Promise<NodeMetricsList>;
84+
public async getNodeMetrics(node: string, options?: MetricsOptions): Promise<SingleNodeMetrics>;
85+
public async getNodeMetrics(
86+
nodeOrOptions?: string | MetricsOptions,
87+
options?: MetricsOptions,
88+
): Promise<NodeMetricsList | SingleNodeMetrics> {
89+
if (typeof nodeOrOptions !== 'string' || nodeOrOptions === '') {
90+
if (nodeOrOptions !== '') {
91+
options = nodeOrOptions;
92+
}
93+
return this.metricsApiRequest<NodeMetricsList>('/apis/metrics.k8s.io/v1beta1/nodes', options);
94+
}
95+
return this.metricsApiRequest<SingleNodeMetrics>(
96+
`/apis/metrics.k8s.io/v1beta1/nodes/${nodeOrOptions}`,
97+
options,
98+
);
7299
}
73100

74-
public async getPodMetrics(namespace?: string): Promise<PodMetricsList>;
75-
public async getPodMetrics(namespace: string, name: string): Promise<SinglePodMetrics>;
76-
101+
public async getPodMetrics(options?: MetricsOptions): Promise<PodMetricsList>;
102+
public async getPodMetrics(namespace?: string, options?: MetricsOptions): Promise<PodMetricsList>;
103+
public async getPodMetrics(
104+
namespace: string,
105+
name: string,
106+
options?: MetricsOptions,
107+
): Promise<SinglePodMetrics>;
77108
public async getPodMetrics(
78-
namespace?: string,
79-
name?: string,
109+
namespaceOrOptions?: string | MetricsOptions,
110+
nameOrOptions?: string | MetricsOptions,
111+
options?: MetricsOptions,
80112
): Promise<SinglePodMetrics | PodMetricsList> {
81113
let path: string;
82114

83-
if (namespace !== undefined && namespace.length > 0 && name !== undefined && name.length > 0) {
84-
path = `/apis/metrics.k8s.io/v1beta1/namespaces/${namespace}/pods/${name}`;
85-
return this.metricsApiRequest<SinglePodMetrics>(path);
86-
}
115+
if (typeof namespaceOrOptions === 'string' && namespaceOrOptions !== '') {
116+
const namespace = namespaceOrOptions;
87117

88-
if (namespace !== undefined && namespace.length > 0) {
89-
path = `/apis/metrics.k8s.io/v1beta1/namespaces/${namespace}/pods`;
118+
if (typeof nameOrOptions === 'string') {
119+
path = `/apis/metrics.k8s.io/v1beta1/namespaces/${namespace}/pods/${nameOrOptions}`;
120+
} else {
121+
path = `/apis/metrics.k8s.io/v1beta1/namespaces/${namespace}/pods`;
122+
options = nameOrOptions;
123+
}
90124
} else {
91125
path = '/apis/metrics.k8s.io/v1beta1/pods';
126+
127+
if (typeof namespaceOrOptions !== 'string') {
128+
options = namespaceOrOptions;
129+
} else if (typeof nameOrOptions !== 'string') {
130+
options = nameOrOptions;
131+
}
92132
}
93133

94-
return this.metricsApiRequest<PodMetricsList>(path);
134+
return this.metricsApiRequest<PodMetricsList | SinglePodMetrics>(path, options);
95135
}
96136

97-
private async metricsApiRequest<T extends PodMetricsList | NodeMetricsList | SinglePodMetrics>(
98-
path: string,
99-
): Promise<T> {
137+
private async metricsApiRequest<
138+
T extends PodMetricsList | NodeMetricsList | SinglePodMetrics | SingleNodeMetrics,
139+
>(path: string, options?: MetricsOptions): Promise<T> {
100140
const cluster = this.config.getCurrentCluster();
101141
if (!cluster) {
102142
throw new Error('No currently active cluster');
@@ -105,6 +145,7 @@ export class Metrics {
105145
const requestOptions: request.Options = {
106146
method: 'GET',
107147
uri: cluster.server + path,
148+
qs: options,
108149
};
109150

110151
await this.config.applyToRequest(requestOptions);

src/metrics_test.ts

Lines changed: 137 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { expect } from 'chai';
33
import nock = require('nock');
44
import { KubeConfig } from './config';
55
import { V1Status, HttpError } from './gen/api';
6-
import { Metrics, NodeMetricsList, PodMetricsList, SinglePodMetrics } from './metrics';
6+
import { Metrics, NodeMetricsList, PodMetricsList, SingleNodeMetrics, SinglePodMetrics } from './metrics';
77

88
const emptyPodMetrics: PodMetricsList = {
99
kind: 'PodMetricsList',
@@ -47,6 +47,28 @@ const mockedPodMetrics: PodMetricsList = {
4747
],
4848
};
4949

50+
const mockedPodMetricsWithLabels: PodMetricsList = {
51+
kind: 'PodMetricsList',
52+
apiVersion: 'metrics.k8s.io/v1beta1',
53+
metadata: { selfLink: '/apis/metrics.k8s.io/v1beta1/pods/' },
54+
items: [
55+
{
56+
metadata: {
57+
name: 'dice-roller-7c76898b4d-shm9p',
58+
namespace: 'default',
59+
selfLink: '/apis/metrics.k8s.io/v1beta1/namespaces/default/pods/dice-roller-7c76898b4d-shm9p',
60+
creationTimestamp: '2021-09-26T11:57:27Z',
61+
labels: {
62+
label: 'aLabel',
63+
},
64+
},
65+
timestamp: '2021-09-26T11:57:21Z',
66+
window: '30s',
67+
containers: [{ name: 'nginx', usage: { cpu: '10', memory: '3912Ki' } }],
68+
},
69+
],
70+
};
71+
5072
const emptyNodeMetrics: NodeMetricsList = {
5173
kind: 'NodeMetricsList',
5274
apiVersion: 'metrics.k8s.io/v1beta1',
@@ -64,6 +86,9 @@ const mockedNodeMetrics: NodeMetricsList = {
6486
name: 'a-node',
6587
selfLink: '/apis/metrics.k8s.io/v1beta1/nodes/a-node',
6688
creationTimestamp: '2021-09-26T16:01:53Z',
89+
labels: {
90+
label: 'aLabel',
91+
},
6792
},
6893
timestamp: '2021-09-26T16:01:11Z',
6994
window: '30s',
@@ -72,6 +97,21 @@ const mockedNodeMetrics: NodeMetricsList = {
7297
],
7398
};
7499

100+
const mockedSingleNodeMetrics: SingleNodeMetrics = {
101+
kind: 'NodeMetrics',
102+
apiVersion: 'metrics.k8s.io/v1beta1',
103+
metadata: {
104+
name: 'a-node',
105+
creationTimestamp: '2021-09-26T16:01:53Z',
106+
labels: {
107+
label: 'aLabel',
108+
},
109+
},
110+
timestamp: '2021-09-26T16:01:11Z',
111+
window: '30s',
112+
usage: { cpu: '214650124n', memory: '801480Ki' },
113+
};
114+
75115
const mockedSinglePodMetrics: SinglePodMetrics = {
76116
kind: 'PodMetrics',
77117
apiVersion: 'metrics.k8s.io/v1beta1',
@@ -167,6 +207,54 @@ describe('Metrics', () => {
167207

168208
s.done();
169209
});
210+
211+
it('should return specified cluster scope pods metric list if given options', async () => {
212+
const [metricsClient, scope] = systemUnderTest();
213+
const options = {
214+
labelSelector: 'label=aLabel',
215+
};
216+
const s = scope
217+
.get('/apis/metrics.k8s.io/v1beta1/pods')
218+
.query(options)
219+
.reply(200, mockedPodMetricsWithLabels);
220+
221+
const response = await metricsClient.getPodMetrics(options);
222+
expect(response).to.deep.equal(mockedPodMetricsWithLabels);
223+
s.done();
224+
});
225+
226+
it('should return specified namespace scope pods metric list if given options', async () => {
227+
const [metricsClient, scope] = systemUnderTest();
228+
const options = {
229+
labelSelector: 'label=aLabel',
230+
};
231+
const s = scope
232+
.get(`/apis/metrics.k8s.io/v1beta1/namespaces/${TEST_NAMESPACE}/pods`)
233+
.query(options)
234+
.reply(200, mockedPodMetricsWithLabels);
235+
236+
const response = await metricsClient.getPodMetrics(TEST_NAMESPACE, options);
237+
expect(response).to.deep.equal(mockedPodMetricsWithLabels);
238+
s.done();
239+
});
240+
241+
it('should return specified single pod metrics if given namespace and pod name and options', async () => {
242+
const podName = 'pod-name';
243+
const [metricsClient, scope] = systemUnderTest();
244+
const options = {
245+
labelSelector: 'label=aLabel',
246+
};
247+
const s = scope
248+
.get(`/apis/metrics.k8s.io/v1beta1/namespaces/${TEST_NAMESPACE}/pods/${podName}`)
249+
.query(options)
250+
.reply(200, mockedSinglePodMetrics);
251+
252+
const response = await metricsClient.getPodMetrics(TEST_NAMESPACE, podName, options);
253+
expect(response).to.deep.equal(mockedSinglePodMetrics);
254+
255+
s.done();
256+
});
257+
170258
it('should when connection refused', async () => {
171259
const kc = new KubeConfig();
172260
kc.loadFromOptions({
@@ -261,6 +349,54 @@ describe('Metrics', () => {
261349

262350
s.done();
263351
});
352+
353+
it('should return single node metrics if given node name', async () => {
354+
const [metricsClient, scope] = systemUnderTest();
355+
const nodeName = 'a-node';
356+
357+
const s = scope
358+
.get(`/apis/metrics.k8s.io/v1beta1/nodes/${nodeName}`)
359+
.reply(200, mockedSingleNodeMetrics);
360+
361+
const response = await metricsClient.getNodeMetrics(nodeName);
362+
expect(response).to.deep.equal(mockedSingleNodeMetrics);
363+
364+
s.done();
365+
});
366+
367+
it('should return specified nodes metrics list if given options', async () => {
368+
const [metricsClient, scope] = systemUnderTest();
369+
const options = {
370+
labelSelector: 'label=aLabel',
371+
};
372+
const s = scope
373+
.get('/apis/metrics.k8s.io/v1beta1/nodes')
374+
.query(options)
375+
.reply(200, mockedNodeMetrics);
376+
377+
const response = await metricsClient.getNodeMetrics(options);
378+
expect(response).to.deep.equal(mockedNodeMetrics);
379+
380+
s.done();
381+
});
382+
383+
it('should return specified single node metrics if given node name and options', async () => {
384+
const [metricsClient, scope] = systemUnderTest();
385+
const nodeName = 'a-node';
386+
const options = {
387+
labelSelector: 'label=aLabel',
388+
};
389+
const s = scope
390+
.get(`/apis/metrics.k8s.io/v1beta1/nodes/${nodeName}`)
391+
.query(options)
392+
.reply(200, mockedSingleNodeMetrics);
393+
394+
const response = await metricsClient.getNodeMetrics(nodeName, options);
395+
expect(response).to.deep.equal(mockedSingleNodeMetrics);
396+
397+
s.done();
398+
});
399+
264400
it('should resolve to error when 500', async () => {
265401
const response: V1Status = {
266402
code: 12345,

0 commit comments

Comments
 (0)