@ohos-ports/cloudnative-health 2.1.2-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,360 @@
1
+ /*
2
+ * Copyright IBM Corporation 2018
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ enum State {
18
+ UNKNOWN = "UNKNOWN",
19
+ STARTING = "STARTING",
20
+ UP = "UP",
21
+ DOWN = "DOWN",
22
+ STOPPING = "STOPPING",
23
+ STOPPED = "STOPPED",
24
+ }
25
+
26
+ class HealthStatus {
27
+ status: State;
28
+ checks: PluginStatus[];
29
+
30
+ constructor(state: State) {
31
+ this.status = state;
32
+ this.checks = [];
33
+ }
34
+
35
+ public addStatus(status: PluginStatus) {
36
+ this.checks.push(status);
37
+ if (this.status === State.UNKNOWN) {
38
+ this.status = status.state;
39
+ }
40
+ else if (this.status === State.STARTING) {
41
+ if (status.state === State.STARTING) this.status = State.STARTING;
42
+ if (status.state === State.DOWN) this.status = State.DOWN;
43
+ return;
44
+ }
45
+ else if (this.status === State.UP) {
46
+ if (status.state === State.STARTING) this.status = State.STARTING;
47
+ if (status.state === State.UP) this.status = State.UP;
48
+ if (status.state === State.DOWN) this.status = State.DOWN;
49
+ return;
50
+ }
51
+ else if (this.status === State.DOWN) {
52
+ return;
53
+ }
54
+ else if (this.status === State.STOPPING) {
55
+ if (status.state === State.STOPPING) this.status = State.STOPPING;
56
+ if (status.state === State.DOWN) this.status = State.STOPPED;
57
+ if (status.state === State.STOPPED) this.status = State.STOPPED;
58
+ return;
59
+ }
60
+ else if (this.status === State.STOPPED) {
61
+ if (status.state === State.STOPPING) this.status = State.STOPPING;
62
+ if (status.state === State.DOWN) this.status = State.STOPPED;
63
+ return;
64
+ }
65
+ }
66
+ }
67
+
68
+ class HealthChecker {
69
+ protected startupComplete: boolean;
70
+ private startupPlugins: StartupCheck[];
71
+ private readinessPlugins: ReadinessCheck[];
72
+ private healthPlugins: LivenessCheck[];
73
+ private shutdownEnabled: boolean;
74
+ public shutdownRequested: boolean;
75
+ private shutdownPlugins: ShutdownCheck[];
76
+
77
+ private onShutdownRequest: () => void;
78
+
79
+ constructor() {
80
+ this.startupComplete = true;
81
+ this.startupPlugins = [];
82
+ this.readinessPlugins = [];
83
+ this.healthPlugins = [];
84
+ this.shutdownEnabled = false;
85
+ this.shutdownRequested = false;
86
+ this.shutdownPlugins = [];
87
+
88
+ // Force this to be an instance function so that it can access `this` fields
89
+ this.onShutdownRequest = () => {
90
+ this.shutdownRequested = true;
91
+ this.shutdownPlugins.map((check) => {
92
+ check.runCheck();
93
+ });
94
+ }
95
+ }
96
+
97
+ public getStartUpComplete() {
98
+ return this.startupComplete;
99
+ }
100
+
101
+ public registerStartupCheck(plugin: StartupCheck) {
102
+ this.startupPlugins.push(plugin);
103
+ this.startupComplete = false;
104
+ return plugin.runCheck();
105
+ }
106
+
107
+ public registerReadinessCheck(plugin: ReadinessCheck) {
108
+ this.readinessPlugins.push(plugin);
109
+ }
110
+
111
+ public registerLivenessCheck(plugin: LivenessCheck) {
112
+ this.healthPlugins.push(plugin);
113
+ }
114
+
115
+ public registerShutdownCheck(plugin: ShutdownCheck) {
116
+ if (this.shutdownEnabled === false) {
117
+ this.shutdownEnabled = true;
118
+ process.on('SIGTERM', this.onShutdownRequest);
119
+ }
120
+ this.shutdownPlugins.push(plugin);
121
+ }
122
+
123
+ public async getStatus(): Promise<HealthStatus> {
124
+ if (this.shutdownRequested === true) {
125
+ return this.getShutdownStatus();
126
+ }
127
+ if (this.startupComplete === false) {
128
+ return this.getStartupStatus();
129
+ }
130
+ return this.getHealthStatus();
131
+ }
132
+
133
+ private async getPromiseStatus(statusResponse: HealthStatus) {
134
+ const runChecks = this.startupPlugins.map(check => check.runCheck());
135
+ await Promise.all(runChecks);
136
+ this.startupPlugins.forEach(check => statusResponse.addStatus(check.getStatus()));
137
+ }
138
+
139
+ private async getStartupStatus(): Promise<HealthStatus> {
140
+ let statusResponse: HealthStatus;
141
+
142
+ // Handle startup case
143
+ if (this.startupComplete === false) {
144
+ statusResponse = new HealthStatus(State.UNKNOWN);
145
+
146
+ this.startupPlugins.map((check) => {
147
+ const promiseCheck = check.runCheck();
148
+ statusResponse.addStatus(check.getStatus());
149
+ return promiseCheck;
150
+ });
151
+
152
+ if (statusResponse.status !== State.UP) {
153
+ return statusResponse;
154
+ }
155
+ this.startupComplete = true;
156
+ }
157
+ return this.getHealthStatus();
158
+ }
159
+
160
+ public async getReadinessStatus(): Promise<HealthStatus> {
161
+ let statusResponse: HealthStatus;
162
+
163
+ if (this.shutdownRequested === true) {
164
+ return this.getShutdownStatus();
165
+ }
166
+ if (this.startupComplete === false) {
167
+ statusResponse = new HealthStatus(State.UNKNOWN);
168
+ await this.getPromiseStatus(statusResponse);
169
+ if(statusResponse.status === State.UP) {
170
+ this.startupComplete = true;
171
+ } else {
172
+ return this.getStartupStatus();
173
+ }
174
+ }
175
+
176
+ // Handle readiness
177
+ statusResponse = new HealthStatus(State.UP);
178
+ let filteredPromises = this.readinessPlugins.filter(element => element !== undefined);
179
+ if (filteredPromises.length === 0) {
180
+ return statusResponse;
181
+ } else {
182
+ await Promise.all(filteredPromises.map(async (check) => {
183
+ const promiseCheck = await check.runCheck();
184
+ statusResponse.addStatus(check.getStatus());
185
+ return promiseCheck;
186
+ }));
187
+ return statusResponse;
188
+ }
189
+ }
190
+
191
+ public async getLivenessStatus(): Promise<HealthStatus> {
192
+ let statusResponse: HealthStatus;
193
+
194
+ if (this.shutdownRequested === true) {
195
+ return this.getShutdownStatus();
196
+ }
197
+ if (this.startupComplete === false) {
198
+ statusResponse = new HealthStatus(State.UNKNOWN);
199
+ await this.getPromiseStatus(statusResponse);
200
+ if(statusResponse.status === State.UP) {
201
+ this.startupComplete = true;
202
+ } else {
203
+ return this.getStartupStatus();
204
+ }
205
+ }
206
+ // Handle liveness
207
+ statusResponse = new HealthStatus(State.UP);
208
+
209
+ let filteredPromises = this.healthPlugins.filter(element => element !== undefined);
210
+ if (filteredPromises.length === 0) {
211
+ return statusResponse;
212
+ } else {
213
+ await Promise.all(filteredPromises.map(async (check) => {
214
+ const promiseCheck = await check.runCheck();
215
+ statusResponse.addStatus(check.getStatus());
216
+ return promiseCheck;
217
+ }));
218
+ return statusResponse;
219
+ }
220
+ }
221
+
222
+ // Health is Liveness || Readiness
223
+ private async getHealthStatus(): Promise<HealthStatus> {
224
+ let statusResponse: HealthStatus;
225
+
226
+ // Handle liveness
227
+ statusResponse = new HealthStatus(State.UP);
228
+
229
+ await Promise.all([this.getReadinessStatus(), this.getLivenessStatus()])
230
+ .then((values) => {
231
+ let readiness = values[0];
232
+ let liveness = values[1];
233
+
234
+ readiness.checks.map((check) => {
235
+ statusResponse.addStatus(check);
236
+ });
237
+ liveness.checks.map((check) => {
238
+ statusResponse.addStatus(check);
239
+ });
240
+ return statusResponse;
241
+ });
242
+ return statusResponse;
243
+ }
244
+
245
+ private async getShutdownStatus(): Promise<HealthStatus> {
246
+ let statusResponse: HealthStatus;
247
+
248
+ // Handle shutdown case
249
+ if (this.shutdownRequested === true) {
250
+ statusResponse = new HealthStatus(State.STOPPING);
251
+ const runChecks = this.shutdownPlugins.map((check) => {
252
+ check.runCheck();
253
+ });
254
+ await Promise.all(runChecks);
255
+ this.shutdownPlugins.forEach((check) => {
256
+ statusResponse.addStatus(check.getStatus());
257
+ });
258
+ return statusResponse;
259
+ } else {
260
+ return this.getHealthStatus();
261
+ }
262
+ }
263
+ }
264
+
265
+ class Plugin {
266
+ protected name: string;
267
+ protected status: State = State.DOWN;
268
+ protected statusReason: string = "";
269
+ protected promise!: () => Promise<void>;
270
+
271
+ public getStatus(): PluginStatus {
272
+ return new PluginStatus(this.name, this.status, this.statusReason);
273
+ }
274
+
275
+ constructor(name: string) {
276
+ this.name = name;
277
+ }
278
+
279
+ public wrapPromise(promise: () => Promise<void>, success: State, failure: State) {
280
+ let wrappedPromise = () => {
281
+ return promise()
282
+ .then(() => {
283
+ this.status = success;
284
+ this.statusReason = "";
285
+ return Promise.resolve();
286
+ })
287
+ .catch((err) => {
288
+ this.status = failure;
289
+ try {
290
+ this.statusReason = String(err.message || err);
291
+ } catch(err) {
292
+ this.statusReason = String();
293
+ }
294
+ return Promise.resolve();
295
+ });
296
+ }
297
+ return wrappedPromise;
298
+ }
299
+ }
300
+
301
+ class LivenessCheck extends Plugin {
302
+ constructor(name: string, livenessPromiseGen: () => Promise<void>) {
303
+ super(name);
304
+ this.promise = this.wrapPromise(livenessPromiseGen, State.UP, State.DOWN);
305
+ }
306
+
307
+ public runCheck() {
308
+ return this.promise();
309
+ }
310
+ }
311
+
312
+ class StartupCheck extends Plugin {
313
+ constructor(name: string, startupPromise: () => Promise<void>) {
314
+ super(name);
315
+ this.promise = this.wrapPromise(startupPromise, State.UP, State.DOWN);
316
+ this.status = State.STARTING;
317
+ }
318
+
319
+ public runCheck() {
320
+ return this.promise();
321
+ }
322
+ }
323
+
324
+ class ReadinessCheck extends Plugin {
325
+ constructor(name: string, ReadinessPromiseGen: () => Promise<void>) {
326
+ super(name);
327
+ this.promise = this.wrapPromise(ReadinessPromiseGen, State.UP, State.DOWN);
328
+ this.status = State.STARTING;
329
+ }
330
+
331
+ public runCheck() {
332
+ return this.promise();
333
+ }
334
+ }
335
+
336
+ class ShutdownCheck extends Plugin {
337
+ constructor(name: string, shutdownPromise: () => Promise<void>) {
338
+ super(name);
339
+ this.promise = this.wrapPromise(shutdownPromise, State.STOPPED, State.DOWN);
340
+ this.status = State.STOPPING;
341
+ }
342
+
343
+ public runCheck() {
344
+ return this.promise();
345
+ }
346
+ }
347
+
348
+ class PluginStatus {
349
+ name: string;
350
+ state: State;
351
+ data: { [key: string]: string; };
352
+
353
+ constructor(name: string, state: State, reason: string) {
354
+ this.name = name;
355
+ this.state = state;
356
+ this.data = { "reason": reason };
357
+ }
358
+ }
359
+
360
+ export { HealthChecker, HealthStatus, Plugin, StartupCheck, ReadinessCheck, LivenessCheck, ShutdownCheck, State, PluginStatus };
@@ -0,0 +1,5 @@
1
+ import { LivenessCheck } from '../HealthChecker';
2
+ declare class PingCheck extends LivenessCheck {
3
+ constructor(host: string, path?: string, port?: string, method?: string);
4
+ }
5
+ export { PingCheck };
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright IBM Corporation 2018
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || function (mod) {
30
+ if (mod && mod.__esModule) return mod;
31
+ var result = {};
32
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
33
+ __setModuleDefault(result, mod);
34
+ return result;
35
+ };
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.PingCheck = void 0;
38
+ const HealthChecker_1 = require("../HealthChecker");
39
+ const http = __importStar(require("http"));
40
+ class PingCheck extends HealthChecker_1.LivenessCheck {
41
+ constructor(host, path = '', port = '80', method = 'HEAD') {
42
+ let options = {
43
+ hostname: host,
44
+ port: port,
45
+ path: path,
46
+ method: method
47
+ };
48
+ let promise = () => new Promise(function (resolve, reject) {
49
+ const req = http.request(options, (res) => {
50
+ res.on('data', () => {
51
+ //Ensures above promise is resolved
52
+ });
53
+ res.on('end', () => {
54
+ resolve();
55
+ });
56
+ });
57
+ req.on('error', (e) => {
58
+ reject(new Error(`Failed to ping ${options.method}:${options.hostname}:${options.port}/${options.path}: ${e.message}`));
59
+ });
60
+ req.end();
61
+ });
62
+ super("PingCheck " + options.method + ":" + options.hostname + ":" + options.port + "/" + options.path, promise);
63
+ }
64
+ }
65
+ exports.PingCheck = PingCheck;
66
+ //# sourceMappingURL=PingCheck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PingCheck.js","sourceRoot":"","sources":["PingCheck.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;;;;;;;;;;;;;;;;;;;;;AAEH,oDAAiD;AACjD,2CAA6B;AAE7B,MAAM,SAAU,SAAQ,6BAAa;IAEjC,YAAY,IAAY,EAAE,IAAI,GAAG,EAAE,EAAE,IAAI,GAAG,IAAI,EAAE,MAAM,GAAG,MAAM;QAC7D,IAAI,OAAO,GAAG;YACV,QAAQ,EAAE,IAAI;YACd,IAAI,EAAE,IAAI;YACV,IAAI,EAAE,IAAI;YACV,MAAM,EAAE,MAAM;SACjB,CAAC;QAEF,IAAI,OAAO,GAAG,GAAG,EAAE,CAAC,IAAI,OAAO,CAAO,UAAS,OAAO,EAAE,MAAM;YAC1D,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBACtC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;oBAChB,mCAAmC;gBACvC,CAAC,CAAC,CAAA;gBACF,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;oBACf,OAAO,EAAE,CAAC;gBACd,CAAC,CAAC,CAAC;YACP,CAAC,CAAC,CAAC;YACH,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;gBAClB,MAAM,CAAC,IAAI,KAAK,CAAC,kBAAkB,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YAC5H,CAAC,CAAC,CAAC;YACH,GAAG,CAAC,GAAG,EAAE,CAAC;QACd,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,YAAY,GAAG,OAAO,CAAC,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,QAAQ,GAAG,GAAG,GAAG,OAAO,CAAC,IAAI,GAAG,GAAG,GAAG,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IACpH,CAAC;CACJ;AAEQ,8BAAS"}
@@ -0,0 +1,48 @@
1
+ /*
2
+ * Copyright IBM Corporation 2018
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import { LivenessCheck } from '../HealthChecker';
18
+ import * as http from "http";
19
+
20
+ class PingCheck extends LivenessCheck {
21
+
22
+ constructor(host: string, path = '', port = '80', method = 'HEAD' ) {
23
+ let options = {
24
+ hostname: host,
25
+ port: port,
26
+ path: path,
27
+ method: method
28
+ };
29
+
30
+ let promise = () => new Promise<void>(function(resolve, reject) {
31
+ const req = http.request(options, (res) => {
32
+ res.on('data', () => {
33
+ //Ensures above promise is resolved
34
+ })
35
+ res.on('end', () => {
36
+ resolve();
37
+ });
38
+ });
39
+ req.on('error', (e) => {
40
+ reject(new Error(`Failed to ping ${options.method}:${options.hostname}:${options.port}/${options.path}: ${e.message}`));
41
+ });
42
+ req.end();
43
+ });
44
+ super("PingCheck " + options.method + ":" + options.hostname + ":" + options.port + "/" + options.path, promise)
45
+ }
46
+ }
47
+
48
+ export { PingCheck };
@@ -0,0 +1 @@
1
+ export {};