Skip to content

Commit f790c7c

Browse files
committed
feat: health check
1 parent e25ad51 commit f790c7c

File tree

2 files changed

+184
-0
lines changed

2 files changed

+184
-0
lines changed

src/health.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import fetch from 'node-fetch';
2+
import { AbortSignal } from 'node-fetch/externals';
3+
import { URL } from 'url';
4+
import { KubeConfig } from './config';
5+
import { RequestOptions } from 'https';
6+
7+
export class Health {
8+
public config: KubeConfig;
9+
10+
public constructor(config: KubeConfig) {
11+
this.config = config;
12+
}
13+
14+
public async readyz(opts: RequestOptions): Promise<boolean> {
15+
return this.check('/readyz', opts);
16+
}
17+
18+
public async livez(opts: RequestOptions): Promise<boolean> {
19+
return this.check('/livez', opts);
20+
}
21+
22+
private async healthz(opts: RequestOptions): Promise<boolean> {
23+
return this.check('/healthz', opts);
24+
}
25+
26+
private async check(path: string, opts: RequestOptions): Promise<boolean> {
27+
const cluster = this.config.getCurrentCluster();
28+
if (!cluster) {
29+
throw new Error('No currently active cluster');
30+
}
31+
32+
const requestURL = new URL(cluster.server + path);
33+
const requestInit = await this.config.applyToFetchOptions(opts);
34+
const controller = new AbortController();
35+
requestInit.signal = controller.signal as AbortSignal;
36+
requestInit.method = 'GET';
37+
38+
return await fetch(requestURL.toString(), requestInit)
39+
.then((response) => {
40+
const status = response.status;
41+
if (status === 200) {
42+
return true;
43+
} else if (status === 404) {
44+
if (path === '/healthz') {
45+
// /livez/readyz return 404 and healthz also returns 404, let's consider it is live
46+
return true;
47+
}
48+
return this.healthz(opts);
49+
} else {
50+
return false;
51+
}
52+
})
53+
.catch((err) => {
54+
return false;
55+
});
56+
}
57+
}

src/health_test.ts

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { expect } from 'chai';
2+
import nock from 'nock';
3+
4+
import { KubeConfig } from './config';
5+
import { Health } from './health';
6+
import { Cluster, User } from './config_types';
7+
8+
describe('Health', () => {
9+
describe('livez', () => {
10+
it('should throw an error if no current active cluster', async () => {
11+
const kc = new KubeConfig();
12+
const health = new Health(kc);
13+
await expect(health.livez({})).to.be.rejectedWith('No currently active cluster');
14+
});
15+
16+
it('should return true if /livez returns with status 200', async () => {
17+
const kc = new KubeConfig();
18+
const cluster = {
19+
name: 'foo',
20+
server: 'https://server.com',
21+
} as Cluster;
22+
23+
const user = {
24+
name: 'my-user',
25+
password: 'some-password',
26+
} as User;
27+
kc.loadFromClusterAndUser(cluster, user);
28+
29+
const scope = nock('https://server.com').get('/livez').reply(200);
30+
const health = new Health(kc);
31+
32+
const r = await health.livez({});
33+
expect(r).to.be.true;
34+
scope.done();
35+
});
36+
37+
it('should return false if /livez returns with status 500', async () => {
38+
const kc = new KubeConfig();
39+
const cluster = {
40+
name: 'foo',
41+
server: 'https://server.com',
42+
} as Cluster;
43+
44+
const user = {
45+
name: 'my-user',
46+
password: 'some-password',
47+
} as User;
48+
kc.loadFromClusterAndUser(cluster, user);
49+
50+
const scope = nock('https://server.com').get('/livez').reply(500);
51+
const health = new Health(kc);
52+
53+
const r = await health.livez({});
54+
expect(r).to.be.false;
55+
scope.done();
56+
});
57+
58+
it('should return true if /livez returns status 404 and /healthz returns status 200', async () => {
59+
const kc = new KubeConfig();
60+
const cluster = {
61+
name: 'foo',
62+
server: 'https://server.com',
63+
} as Cluster;
64+
65+
const user = {
66+
name: 'my-user',
67+
password: 'some-password',
68+
} as User;
69+
kc.loadFromClusterAndUser(cluster, user);
70+
71+
const scope = nock('https://server.com');
72+
scope.get('/livez').reply(404);
73+
scope.get('/healthz').reply(200);
74+
const health = new Health(kc);
75+
76+
const r = await health.livez({});
77+
expect(r).to.be.true;
78+
scope.done();
79+
});
80+
81+
it('should return false if /livez returns status 404 and /healthz returns status 500', async () => {
82+
const kc = new KubeConfig();
83+
const cluster = {
84+
name: 'foo',
85+
server: 'https://server.com',
86+
} as Cluster;
87+
88+
const user = {
89+
name: 'my-user',
90+
password: 'some-password',
91+
} as User;
92+
kc.loadFromClusterAndUser(cluster, user);
93+
94+
const scope = nock('https://server.com');
95+
scope.get('/livez').reply(404);
96+
scope.get('/healthz').reply(500);
97+
const health = new Health(kc);
98+
99+
const r = await health.livez({});
100+
expect(r).to.be.false;
101+
scope.done();
102+
});
103+
104+
it('should return true if both /livez and /healthz return status 404', async () => {
105+
const kc = new KubeConfig();
106+
const cluster = {
107+
name: 'foo',
108+
server: 'https://server.com',
109+
} as Cluster;
110+
111+
const user = {
112+
name: 'my-user',
113+
password: 'some-password',
114+
} as User;
115+
kc.loadFromClusterAndUser(cluster, user);
116+
117+
const scope = nock('https://server.com');
118+
scope.get('/livez').reply(404);
119+
scope.get('/healthz').reply(200);
120+
const health = new Health(kc);
121+
122+
const r = await health.livez({});
123+
expect(r).to.be.true;
124+
scope.done();
125+
});
126+
});
127+
});

0 commit comments

Comments
 (0)