mirror of https://github.com/grpc/grpc-node.git
Merge pull request #2957 from MatiasManevi/health_list_endpoint
Health list endpoint
This commit is contained in:
commit
f6571a15c9
|
@ -21,6 +21,8 @@ import { loadSync, ServiceDefinition } from '@grpc/proto-loader';
|
|||
import { HealthCheckRequest__Output } from './generated/grpc/health/v1/HealthCheckRequest';
|
||||
import { HealthCheckResponse } from './generated/grpc/health/v1/HealthCheckResponse';
|
||||
import { sendUnaryData, Server, ServerUnaryCall, ServerWritableStream } from './server-type';
|
||||
import { HealthListRequest } from './generated/grpc/health/v1/HealthListRequest';
|
||||
import { HealthListResponse } from './generated/grpc/health/v1/HealthListResponse';
|
||||
|
||||
const loadedProto = loadSync('health/v1/health.proto', {
|
||||
keepCase: true,
|
||||
|
@ -34,6 +36,8 @@ const loadedProto = loadSync('health/v1/health.proto', {
|
|||
export const service = loadedProto['grpc.health.v1.Health'] as ServiceDefinition;
|
||||
|
||||
const GRPC_STATUS_NOT_FOUND = 5;
|
||||
const GRPC_STATUS_RESOURCE_EXHAUSTED = 8;
|
||||
const RESOURCE_EXHAUSTION_LIMIT = 100;
|
||||
|
||||
export type ServingStatus = 'UNKNOWN' | 'SERVING' | 'NOT_SERVING';
|
||||
|
||||
|
@ -104,7 +108,25 @@ export class HealthImplementation {
|
|||
} else {
|
||||
call.write({status: 'SERVICE_UNKNOWN'});
|
||||
}
|
||||
}
|
||||
},
|
||||
list: (_call: ServerUnaryCall<HealthListRequest, HealthListResponse>, callback: sendUnaryData<HealthListResponse>) => {
|
||||
const statuses: { [key: string]: HealthCheckResponse } = {};
|
||||
let serviceCount = 0;
|
||||
|
||||
for (const [serviceName, status] of this.statusMap.entries()) {
|
||||
if (serviceCount >= RESOURCE_EXHAUSTION_LIMIT) {
|
||||
const error = {
|
||||
code: GRPC_STATUS_RESOURCE_EXHAUSTED,
|
||||
details: 'Too many services to list.',
|
||||
};
|
||||
callback(error, null);
|
||||
return;
|
||||
}
|
||||
statuses[serviceName] = { status };
|
||||
serviceCount++;
|
||||
}
|
||||
callback(null, { statuses });
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import type * as grpc from '@grpc/grpc-js'
|
|||
import type { MethodDefinition } from '@grpc/proto-loader'
|
||||
import type { HealthCheckRequest as _grpc_health_v1_HealthCheckRequest, HealthCheckRequest__Output as _grpc_health_v1_HealthCheckRequest__Output } from '../../../grpc/health/v1/HealthCheckRequest';
|
||||
import type { HealthCheckResponse as _grpc_health_v1_HealthCheckResponse, HealthCheckResponse__Output as _grpc_health_v1_HealthCheckResponse__Output } from '../../../grpc/health/v1/HealthCheckResponse';
|
||||
import type { HealthListRequest as _grpc_health_v1_HealthListRequest, HealthListRequest__Output as _grpc_health_v1_HealthListRequest__Output } from '../../../grpc/health/v1/HealthListRequest';
|
||||
import type { HealthListResponse as _grpc_health_v1_HealthListResponse, HealthListResponse__Output as _grpc_health_v1_HealthListResponse__Output } from '../../../grpc/health/v1/HealthListResponse';
|
||||
|
||||
/**
|
||||
* Health is gRPC's mechanism for checking whether a server is able to handle
|
||||
|
@ -42,6 +44,41 @@ export interface HealthClient extends grpc.Client {
|
|||
check(argument: _grpc_health_v1_HealthCheckRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_health_v1_HealthCheckResponse__Output>): grpc.ClientUnaryCall;
|
||||
check(argument: _grpc_health_v1_HealthCheckRequest, callback: grpc.requestCallback<_grpc_health_v1_HealthCheckResponse__Output>): grpc.ClientUnaryCall;
|
||||
|
||||
/**
|
||||
* List provides a non-atomic snapshot of the health of all the available
|
||||
* services.
|
||||
*
|
||||
* The server may respond with a RESOURCE_EXHAUSTED error if too many services
|
||||
* exist.
|
||||
*
|
||||
* Clients should set a deadline when calling List, and can declare the server
|
||||
* unhealthy if they do not receive a timely response.
|
||||
*
|
||||
* Clients should keep in mind that the list of health services exposed by an
|
||||
* application can change over the lifetime of the process.
|
||||
*/
|
||||
List(argument: _grpc_health_v1_HealthListRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_health_v1_HealthListResponse__Output>): grpc.ClientUnaryCall;
|
||||
List(argument: _grpc_health_v1_HealthListRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_health_v1_HealthListResponse__Output>): grpc.ClientUnaryCall;
|
||||
List(argument: _grpc_health_v1_HealthListRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_health_v1_HealthListResponse__Output>): grpc.ClientUnaryCall;
|
||||
List(argument: _grpc_health_v1_HealthListRequest, callback: grpc.requestCallback<_grpc_health_v1_HealthListResponse__Output>): grpc.ClientUnaryCall;
|
||||
/**
|
||||
* List provides a non-atomic snapshot of the health of all the available
|
||||
* services.
|
||||
*
|
||||
* The server may respond with a RESOURCE_EXHAUSTED error if too many services
|
||||
* exist.
|
||||
*
|
||||
* Clients should set a deadline when calling List, and can declare the server
|
||||
* unhealthy if they do not receive a timely response.
|
||||
*
|
||||
* Clients should keep in mind that the list of health services exposed by an
|
||||
* application can change over the lifetime of the process.
|
||||
*/
|
||||
list(argument: _grpc_health_v1_HealthListRequest, metadata: grpc.Metadata, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_health_v1_HealthListResponse__Output>): grpc.ClientUnaryCall;
|
||||
list(argument: _grpc_health_v1_HealthListRequest, metadata: grpc.Metadata, callback: grpc.requestCallback<_grpc_health_v1_HealthListResponse__Output>): grpc.ClientUnaryCall;
|
||||
list(argument: _grpc_health_v1_HealthListRequest, options: grpc.CallOptions, callback: grpc.requestCallback<_grpc_health_v1_HealthListResponse__Output>): grpc.ClientUnaryCall;
|
||||
list(argument: _grpc_health_v1_HealthListRequest, callback: grpc.requestCallback<_grpc_health_v1_HealthListResponse__Output>): grpc.ClientUnaryCall;
|
||||
|
||||
/**
|
||||
* Performs a watch for the serving status of the requested service.
|
||||
* The server will immediately send back a message indicating the current
|
||||
|
@ -102,6 +139,21 @@ export interface HealthHandlers extends grpc.UntypedServiceImplementation {
|
|||
*/
|
||||
Check: grpc.handleUnaryCall<_grpc_health_v1_HealthCheckRequest__Output, _grpc_health_v1_HealthCheckResponse>;
|
||||
|
||||
/**
|
||||
* List provides a non-atomic snapshot of the health of all the available
|
||||
* services.
|
||||
*
|
||||
* The server may respond with a RESOURCE_EXHAUSTED error if too many services
|
||||
* exist.
|
||||
*
|
||||
* Clients should set a deadline when calling List, and can declare the server
|
||||
* unhealthy if they do not receive a timely response.
|
||||
*
|
||||
* Clients should keep in mind that the list of health services exposed by an
|
||||
* application can change over the lifetime of the process.
|
||||
*/
|
||||
List: grpc.handleUnaryCall<_grpc_health_v1_HealthListRequest__Output, _grpc_health_v1_HealthListResponse>;
|
||||
|
||||
/**
|
||||
* Performs a watch for the serving status of the requested service.
|
||||
* The server will immediately send back a message indicating the current
|
||||
|
@ -125,5 +177,6 @@ export interface HealthHandlers extends grpc.UntypedServiceImplementation {
|
|||
|
||||
export interface HealthDefinition extends grpc.ServiceDefinition {
|
||||
Check: MethodDefinition<_grpc_health_v1_HealthCheckRequest, _grpc_health_v1_HealthCheckResponse, _grpc_health_v1_HealthCheckRequest__Output, _grpc_health_v1_HealthCheckResponse__Output>
|
||||
List: MethodDefinition<_grpc_health_v1_HealthListRequest, _grpc_health_v1_HealthListResponse, _grpc_health_v1_HealthListRequest__Output, _grpc_health_v1_HealthListResponse__Output>
|
||||
Watch: MethodDefinition<_grpc_health_v1_HealthCheckRequest, _grpc_health_v1_HealthCheckResponse, _grpc_health_v1_HealthCheckRequest__Output, _grpc_health_v1_HealthCheckResponse__Output>
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
// Original file: proto/health/v1/health.proto
|
||||
|
||||
|
||||
export interface HealthListRequest {
|
||||
}
|
||||
|
||||
export interface HealthListRequest__Output {
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
// Original file: proto/health/v1/health.proto
|
||||
|
||||
import type { HealthCheckResponse as _grpc_health_v1_HealthCheckResponse, HealthCheckResponse__Output as _grpc_health_v1_HealthCheckResponse__Output } from '../../../grpc/health/v1/HealthCheckResponse';
|
||||
|
||||
export interface HealthListResponse {
|
||||
/**
|
||||
* statuses contains all the services and their respective status.
|
||||
*/
|
||||
'statuses'?: ({[key: string]: _grpc_health_v1_HealthCheckResponse});
|
||||
}
|
||||
|
||||
export interface HealthListResponse__Output {
|
||||
/**
|
||||
* statuses contains all the services and their respective status.
|
||||
*/
|
||||
'statuses': ({[key: string]: _grpc_health_v1_HealthCheckResponse__Output});
|
||||
}
|
|
@ -19,6 +19,8 @@ export interface ProtoGrpcType {
|
|||
Health: SubtypeConstructor<typeof grpc.Client, _grpc_health_v1_HealthClient> & { service: _grpc_health_v1_HealthDefinition }
|
||||
HealthCheckRequest: MessageTypeDefinition
|
||||
HealthCheckResponse: MessageTypeDefinition
|
||||
HealthListRequest: MessageTypeDefinition
|
||||
HealthListResponse: MessageTypeDefinition
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -149,4 +149,62 @@ describe('Health checking', () => {
|
|||
});
|
||||
})
|
||||
});
|
||||
describe('list', () => {
|
||||
it('Should return all registered service statuses', done => {
|
||||
healthClient.list({}, (error, response) => {
|
||||
assert.ifError(error);
|
||||
assert(response);
|
||||
assert.deepStrictEqual(response.statuses, {
|
||||
'': { status: 'SERVING' },
|
||||
'grpc.test.TestServiceNotServing': { status: 'NOT_SERVING' },
|
||||
'grpc.test.TestServiceServing': { status: 'SERVING' }
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Should return an empty list when no services are registered', done => {
|
||||
// Create a new server with no services registered
|
||||
const emptyServer = new grpc.Server();
|
||||
const emptyImpl = new HealthImplementation({});
|
||||
emptyImpl.addToServer(emptyServer);
|
||||
emptyServer.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => {
|
||||
assert.ifError(error);
|
||||
const HealthClientConstructor = grpc.makeClientConstructor(healthServiceDefinition, 'grpc.health.v1.HealthService');
|
||||
const emptyClient = new HealthClientConstructor(`localhost:${port}`, grpc.credentials.createInsecure()) as unknown as HealthClient;
|
||||
emptyServer.start();
|
||||
emptyClient.list({}, (error, response) => {
|
||||
assert.ifError(error);
|
||||
assert(response);
|
||||
assert.deepStrictEqual(response.statuses, {});
|
||||
emptyClient.close();
|
||||
emptyServer.tryShutdown(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Should return RESOURCE_EXHAUSTED when too many services are registered', done => {
|
||||
const largeStatusMap: ServingStatusMap = {};
|
||||
for (let i = 0; i < 101; i++) {
|
||||
largeStatusMap[`service-${i}`] = 'SERVING';
|
||||
}
|
||||
const largeServer = new grpc.Server();
|
||||
const largeImpl = new HealthImplementation(largeStatusMap);
|
||||
largeImpl.addToServer(largeServer);
|
||||
largeServer.bindAsync('localhost:0', grpc.ServerCredentials.createInsecure(), (error, port) => {
|
||||
assert.ifError(error);
|
||||
const HealthClientConstructor = grpc.makeClientConstructor(healthServiceDefinition, 'grpc.health.v1.HealthService');
|
||||
const largeClient = new HealthClientConstructor(`localhost:${port}`, grpc.credentials.createInsecure()) as unknown as HealthClient;
|
||||
largeServer.start();
|
||||
largeClient.list({}, (error, response) => {
|
||||
assert(error);
|
||||
assert.strictEqual(error.code, grpc.status.RESOURCE_EXHAUSTED);
|
||||
assert.strictEqual(response, undefined);
|
||||
largeClient.close();
|
||||
largeServer.tryShutdown(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue