Skip to content

Commit f6571a1

Browse files
authored
Merge pull request #2957 from MatiasManevi/health_list_endpoint
Health list endpoint
2 parents 4799bde + 2fb705e commit f6571a1

File tree

6 files changed

+161
-1
lines changed

6 files changed

+161
-1
lines changed

packages/grpc-health-check/src/health.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import { loadSync, ServiceDefinition } from '@grpc/proto-loader';
2121
import { HealthCheckRequest__Output } from './generated/grpc/health/v1/HealthCheckRequest';
2222
import { HealthCheckResponse } from './generated/grpc/health/v1/HealthCheckResponse';
2323
import { sendUnaryData, Server, ServerUnaryCall, ServerWritableStream } from './server-type';
24+
import { HealthListRequest } from './generated/grpc/health/v1/HealthListRequest';
25+
import { HealthListResponse } from './generated/grpc/health/v1/HealthListResponse';
2426

2527
const loadedProto = loadSync('health/v1/health.proto', {
2628
keepCase: true,
@@ -34,6 +36,8 @@ const loadedProto = loadSync('health/v1/health.proto', {
3436
export const service = loadedProto['grpc.health.v1.Health'] as ServiceDefinition;
3537

3638
const GRPC_STATUS_NOT_FOUND = 5;
39+
const GRPC_STATUS_RESOURCE_EXHAUSTED = 8;
40+
const RESOURCE_EXHAUSTION_LIMIT = 100;
3741

3842
export type ServingStatus = 'UNKNOWN' | 'SERVING' | 'NOT_SERVING';
3943

@@ -104,7 +108,25 @@ export class HealthImplementation {
104108
} else {
105109
call.write({status: 'SERVICE_UNKNOWN'});
106110
}
107-
}
111+
},
112+
list: (_call: ServerUnaryCall<HealthListRequest, HealthListResponse>, callback: sendUnaryData<HealthListResponse>) => {
113+
const statuses: { [key: string]: HealthCheckResponse } = {};
114+
let serviceCount = 0;
115+
116+
for (const [serviceName, status] of this.statusMap.entries()) {
117+
if (serviceCount >= RESOURCE_EXHAUSTION_LIMIT) {
118+
const error = {
119+
code: GRPC_STATUS_RESOURCE_EXHAUSTED,
120+
details: 'Too many services to list.',
121+
};
122+
callback(error, null);
123+
return;
124+
}
125+
statuses[serviceName] = { status };
126+
serviceCount++;
127+
}
128+
callback(null, { statuses });
129+
},
108130
});
109131
}
110132
}

packages/grpc-health-check/test/generated/grpc/health/v1/Health.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import type * as grpc from '@grpc/grpc-js'
44
import type { MethodDefinition } from '@grpc/proto-loader'
55
import type { HealthCheckRequest as _grpc_health_v1_HealthCheckRequest, HealthCheckRequest__Output as _grpc_health_v1_HealthCheckRequest__Output } from '../../../grpc/health/v1/HealthCheckRequest';
66
import type { HealthCheckResponse as _grpc_health_v1_HealthCheckResponse, HealthCheckResponse__Output as _grpc_health_v1_HealthCheckResponse__Output } from '../../../grpc/health/v1/HealthCheckResponse';
7+
import type { HealthListRequest as _grpc_health_v1_HealthListRequest, HealthListRequest__Output as _grpc_health_v1_HealthListRequest__Output } from '../../../grpc/health/v1/HealthListRequest';
8+
import type { HealthListResponse as _grpc_health_v1_HealthListResponse, HealthListResponse__Output as _grpc_health_v1_HealthListResponse__Output } from '../../../grpc/health/v1/HealthListResponse';
79

810
/**
911
* Health is gRPC's mechanism for checking whether a server is able to handle
@@ -42,6 +44,41 @@ export interface HealthClient extends grpc.Client {
4244
check(argument: _grpc_health_v1_HealthCheckRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_health_v1_HealthCheckResponse__Output>): grpc.ClientUnaryCall;
4345
check(argument: _grpc_health_v1_HealthCheckRequest, callback: grpc.requestCallback<_grpc_health_v1_HealthCheckResponse__Output>): grpc.ClientUnaryCall;
4446

47+
/**
48+
* List provides a non-atomic snapshot of the health of all the available
49+
* services.
50+
*
51+
* The server may respond with a RESOURCE_EXHAUSTED error if too many services
52+
* exist.
53+
*
54+
* Clients should set a deadline when calling List, and can declare the server
55+
* unhealthy if they do not receive a timely response.
56+
*
57+
* Clients should keep in mind that the list of health services exposed by an
58+
* application can change over the lifetime of the process.
59+
*/
60+
List(argument: _grpc_health_v1_HealthListRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_health_v1_HealthListResponse__Output>): grpc.ClientUnaryCall;
61+
List(argument: _grpc_health_v1_HealthListRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_health_v1_HealthListResponse__Output>): grpc.ClientUnaryCall;
62+
List(argument: _grpc_health_v1_HealthListRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_health_v1_HealthListResponse__Output>): grpc.ClientUnaryCall;
63+
List(argument: _grpc_health_v1_HealthListRequest, callback: grpc.requestCallback<_grpc_health_v1_HealthListResponse__Output>): grpc.ClientUnaryCall;
64+
/**
65+
* List provides a non-atomic snapshot of the health of all the available
66+
* services.
67+
*
68+
* The server may respond with a RESOURCE_EXHAUSTED error if too many services
69+
* exist.
70+
*
71+
* Clients should set a deadline when calling List, and can declare the server
72+
* unhealthy if they do not receive a timely response.
73+
*
74+
* Clients should keep in mind that the list of health services exposed by an
75+
* application can change over the lifetime of the process.
76+
*/
77+
list(argument: _grpc_health_v1_HealthListRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_health_v1_HealthListResponse__Output>): grpc.ClientUnaryCall;
78+
list(argument: _grpc_health_v1_HealthListRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_health_v1_HealthListResponse__Output>): grpc.ClientUnaryCall;
79+
list(argument: _grpc_health_v1_HealthListRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_health_v1_HealthListResponse__Output>): grpc.ClientUnaryCall;
80+
list(argument: _grpc_health_v1_HealthListRequest, callback: grpc.requestCallback<_grpc_health_v1_HealthListResponse__Output>): grpc.ClientUnaryCall;
81+
4582
/**
4683
* Performs a watch for the serving status of the requested service.
4784
* The server will immediately send back a message indicating the current
@@ -102,6 +139,21 @@ export interface HealthHandlers extends grpc.UntypedServiceImplementation {
102139
*/
103140
Check: grpc.handleUnaryCall<_grpc_health_v1_HealthCheckRequest__Output, _grpc_health_v1_HealthCheckResponse>;
104141

142+
/**
143+
* List provides a non-atomic snapshot of the health of all the available
144+
* services.
145+
*
146+
* The server may respond with a RESOURCE_EXHAUSTED error if too many services
147+
* exist.
148+
*
149+
* Clients should set a deadline when calling List, and can declare the server
150+
* unhealthy if they do not receive a timely response.
151+
*
152+
* Clients should keep in mind that the list of health services exposed by an
153+
* application can change over the lifetime of the process.
154+
*/
155+
List: grpc.handleUnaryCall<_grpc_health_v1_HealthListRequest__Output, _grpc_health_v1_HealthListResponse>;
156+
105157
/**
106158
* Performs a watch for the serving status of the requested service.
107159
* The server will immediately send back a message indicating the current
@@ -125,5 +177,6 @@ export interface HealthHandlers extends grpc.UntypedServiceImplementation {
125177

126178
export interface HealthDefinition extends grpc.ServiceDefinition {
127179
Check: MethodDefinition<_grpc_health_v1_HealthCheckRequest, _grpc_health_v1_HealthCheckResponse, _grpc_health_v1_HealthCheckRequest__Output, _grpc_health_v1_HealthCheckResponse__Output>
180+
List: MethodDefinition<_grpc_health_v1_HealthListRequest, _grpc_health_v1_HealthListResponse, _grpc_health_v1_HealthListRequest__Output, _grpc_health_v1_HealthListResponse__Output>
128181
Watch: MethodDefinition<_grpc_health_v1_HealthCheckRequest, _grpc_health_v1_HealthCheckResponse, _grpc_health_v1_HealthCheckRequest__Output, _grpc_health_v1_HealthCheckResponse__Output>
129182
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Original file: proto/health/v1/health.proto
2+
3+
4+
export interface HealthListRequest {
5+
}
6+
7+
export interface HealthListRequest__Output {
8+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Original file: proto/health/v1/health.proto
2+
3+
import type { HealthCheckResponse as _grpc_health_v1_HealthCheckResponse, HealthCheckResponse__Output as _grpc_health_v1_HealthCheckResponse__Output } from '../../../grpc/health/v1/HealthCheckResponse';
4+
5+
export interface HealthListResponse {
6+
/**
7+
* statuses contains all the services and their respective status.
8+
*/
9+
'statuses'?: ({[key: string]: _grpc_health_v1_HealthCheckResponse});
10+
}
11+
12+
export interface HealthListResponse__Output {
13+
/**
14+
* statuses contains all the services and their respective status.
15+
*/
16+
'statuses': ({[key: string]: _grpc_health_v1_HealthCheckResponse__Output});
17+
}

packages/grpc-health-check/test/generated/health.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ export interface ProtoGrpcType {
1919
Health: SubtypeConstructor<typeof grpc.Client, _grpc_health_v1_HealthClient> & { service: _grpc_health_v1_HealthDefinition }
2020
HealthCheckRequest: MessageTypeDefinition
2121
HealthCheckResponse: MessageTypeDefinition
22+
HealthListRequest: MessageTypeDefinition
23+
HealthListResponse: MessageTypeDefinition
2224
}
2325
}
2426
}

packages/grpc-health-check/test/test-health.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,62 @@ describe('Health checking', () => {
149149
});
150150
})
151151
});
152+
describe('list', () => {
153+
it('Should return all registered service statuses', done => {
154+
healthClient.list({}, (error, response) => {
155+
assert.ifError(error);
156+
assert(response);
157+
assert.deepStrictEqual(response.statuses, {
158+
'': { status: 'SERVING' },
159+
'grpc.test.TestServiceNotServing': { status: 'NOT_SERVING' },
160+
'grpc.test.TestServiceServing': { status: 'SERVING' }
161+
});
162+
done();
163+
});
164+
});
165+
166+
it('Should return an empty list when no services are registered', done => {
167+
// Create a new server with no services registered
168+
const emptyServer = new grpc.Server();
169+
const emptyImpl = new HealthImplementation({});
170+
emptyImpl.addToServer(emptyServer);
171+
emptyServer.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => {
172+
assert.ifError(error);
173+
const HealthClientConstructor = grpc.makeClientConstructor(healthServiceDefinition, 'grpc.health.v1.HealthService');
174+
const emptyClient = new HealthClientConstructor(`localhost:${port}`, grpc.credentials.createInsecure()) as unknown as HealthClient;
175+
emptyServer.start();
176+
emptyClient.list({}, (error, response) => {
177+
assert.ifError(error);
178+
assert(response);
179+
assert.deepStrictEqual(response.statuses, {});
180+
emptyClient.close();
181+
emptyServer.tryShutdown(done);
182+
});
183+
});
184+
});
185+
186+
it('Should return RESOURCE_EXHAUSTED when too many services are registered', done => {
187+
const largeStatusMap: ServingStatusMap = {};
188+
for (let i = 0; i < 101; i++) {
189+
largeStatusMap[`service-${i}`] = 'SERVING';
190+
}
191+
const largeServer = new grpc.Server();
192+
const largeImpl = new HealthImplementation(largeStatusMap);
193+
largeImpl.addToServer(largeServer);
194+
largeServer.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => {
195+
assert.ifError(error);
196+
const HealthClientConstructor = grpc.makeClientConstructor(healthServiceDefinition, 'grpc.health.v1.HealthService');
197+
const largeClient = new HealthClientConstructor(`localhost:${port}`, grpc.credentials.createInsecure()) as unknown as HealthClient;
198+
largeServer.start();
199+
largeClient.list({}, (error, response) => {
200+
assert(error);
201+
assert.strictEqual(error.code, grpc.status.RESOURCE_EXHAUSTED);
202+
assert.strictEqual(response, undefined);
203+
largeClient.close();
204+
largeServer.tryShutdown(done);
205+
});
206+
});
207+
});
208+
});
209+
152210
});

0 commit comments

Comments
 (0)