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 { HealthCheckRequest__Output } from './generated/grpc/health/v1/HealthCheckRequest';
|
||||||
import { HealthCheckResponse } from './generated/grpc/health/v1/HealthCheckResponse';
|
import { HealthCheckResponse } from './generated/grpc/health/v1/HealthCheckResponse';
|
||||||
import { sendUnaryData, Server, ServerUnaryCall, ServerWritableStream } from './server-type';
|
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', {
|
const loadedProto = loadSync('health/v1/health.proto', {
|
||||||
keepCase: true,
|
keepCase: true,
|
||||||
|
@ -34,6 +36,8 @@ const loadedProto = loadSync('health/v1/health.proto', {
|
||||||
export const service = loadedProto['grpc.health.v1.Health'] as ServiceDefinition;
|
export const service = loadedProto['grpc.health.v1.Health'] as ServiceDefinition;
|
||||||
|
|
||||||
const GRPC_STATUS_NOT_FOUND = 5;
|
const GRPC_STATUS_NOT_FOUND = 5;
|
||||||
|
const GRPC_STATUS_RESOURCE_EXHAUSTED = 8;
|
||||||
|
const RESOURCE_EXHAUSTION_LIMIT = 100;
|
||||||
|
|
||||||
export type ServingStatus = 'UNKNOWN' | 'SERVING' | 'NOT_SERVING';
|
export type ServingStatus = 'UNKNOWN' | 'SERVING' | 'NOT_SERVING';
|
||||||
|
|
||||||
|
@ -104,7 +108,25 @@ export class HealthImplementation {
|
||||||
} else {
|
} else {
|
||||||
call.write({status: 'SERVICE_UNKNOWN'});
|
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 { 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 { 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 { 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
|
* 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, 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;
|
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.
|
* Performs a watch for the serving status of the requested service.
|
||||||
* The server will immediately send back a message indicating the current
|
* 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>;
|
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.
|
* Performs a watch for the serving status of the requested service.
|
||||||
* The server will immediately send back a message indicating the current
|
* 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 {
|
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>
|
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>
|
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 }
|
Health: SubtypeConstructor<typeof grpc.Client, _grpc_health_v1_HealthClient> & { service: _grpc_health_v1_HealthDefinition }
|
||||||
HealthCheckRequest: MessageTypeDefinition
|
HealthCheckRequest: MessageTypeDefinition
|
||||||
HealthCheckResponse: 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