Skip to content

Commit a09870a

Browse files
authored
Merge pull request #1978 from murgatroid99/grpc-js-xds_csds
grpc-js-xds: Implement CSDS
2 parents 2e008d1 + 29711cd commit a09870a

File tree

173 files changed

+9224
-953
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

173 files changed

+9224
-953
lines changed

packages/grpc-js-xds/deps/envoy-api

Submodule envoy-api updated 507 files

packages/grpc-js-xds/interop/xds-interop-client.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ function main() {
404404
const server = new grpc.Server();
405405
server.addService(loadedProto.grpc.testing.LoadBalancerStatsService.service, loadBalancerStatsServiceImpl);
406406
server.addService(loadedProto.grpc.testing.XdsUpdateClientConfigureService.service, xdsUpdateClientConfigureServiceImpl);
407+
grpc.addAdminServicesToServer(server);
407408
server.bindAsync(`0.0.0.0:${argv.stats_port}`, grpc.ServerCredentials.createInsecure(), (error, port) => {
408409
if (error) {
409410
throw error;

packages/grpc-js-xds/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"prepare": "npm run compile",
1313
"pretest": "npm run compile",
1414
"posttest": "npm run check",
15-
"generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs deps/envoy-api/ deps/xds/ deps/googleapis/ deps/protoc-gen-validate/ -O src/generated/ --grpcLib @grpc/grpc-js envoy/service/discovery/v2/ads.proto envoy/service/load_stats/v2/lrs.proto envoy/service/discovery/v3/ads.proto envoy/service/load_stats/v3/lrs.proto envoy/config/listener/v3/listener.proto envoy/config/route/v3/route.proto envoy/config/cluster/v3/cluster.proto envoy/config/endpoint/v3/endpoint.proto envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto udpa/type/v1/typed_struct.proto xds/type/v3/typed_struct.proto envoy/extensions/filters/http/fault/v3/fault.proto",
15+
"generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs deps/envoy-api/ deps/xds/ deps/googleapis/ deps/protoc-gen-validate/ -O src/generated/ --grpcLib @grpc/grpc-js envoy/service/discovery/v2/ads.proto envoy/service/load_stats/v2/lrs.proto envoy/service/discovery/v3/ads.proto envoy/service/load_stats/v3/lrs.proto envoy/config/listener/v3/listener.proto envoy/config/route/v3/route.proto envoy/config/cluster/v3/cluster.proto envoy/config/endpoint/v3/endpoint.proto envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto udpa/type/v1/typed_struct.proto xds/type/v3/typed_struct.proto envoy/extensions/filters/http/fault/v3/fault.proto envoy/service/status/v3/csds.proto",
1616
"generate-interop-types": "proto-loader-gen-types --keep-case --longs String --enums String --defaults --oneofs --json --includeComments --includeDirs proto/ -O interop/generated --grpcLib @grpc/grpc-js grpc/testing/test.proto"
1717
},
1818
"repository": {

packages/grpc-js-xds/scripts/xds.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ git clone -b master --single-branch --depth=1 https://github.com/grpc/grpc.git
4848

4949
grpc/tools/run_tests/helper_scripts/prep_xds.sh
5050

51-
GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter \
51+
GRPC_NODE_TRACE=xds_client,xds_resolver,cds_balancer,eds_balancer,priority,weighted_target,round_robin,resolving_load_balancer,subchannel,keepalive,dns_resolver,fault_injection,http_filter,csds \
5252
GRPC_NODE_VERBOSITY=DEBUG \
5353
NODE_XDS_INTEROP_VERBOSITY=1 \
5454
python3 grpc/tools/run_tests/run_xds_tests.py \
55-
--test_case="all,timeout,circuit_breaking,fault_injection" \
55+
--test_case="all,timeout,circuit_breaking,fault_injection,csds" \
5656
--project_id=grpc-testing \
5757
--source_image=projects/grpc-testing/global/images/xds-test-server-4 \
5858
--path_to_server_binary=/java_server/grpc-java/interop-testing/build/install/grpc-interop-testing/bin/xds-test-server \

packages/grpc-js-xds/src/csds.ts

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
/*
2+
* Copyright 2021 gRPC authors.
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+
18+
import { Node } from "./generated/envoy/config/core/v3/Node";
19+
import { ClientConfig, _envoy_service_status_v3_ClientConfig_GenericXdsConfig as GenericXdsConfig } from "./generated/envoy/service/status/v3/ClientConfig";
20+
import { ClientStatusDiscoveryServiceHandlers } from "./generated/envoy/service/status/v3/ClientStatusDiscoveryService";
21+
import { ClientStatusRequest__Output } from "./generated/envoy/service/status/v3/ClientStatusRequest";
22+
import { ClientStatusResponse } from "./generated/envoy/service/status/v3/ClientStatusResponse";
23+
import { Timestamp } from "./generated/google/protobuf/Timestamp";
24+
import { AdsTypeUrl, CDS_TYPE_URL_V2, CDS_TYPE_URL_V3, EDS_TYPE_URL_V2, EDS_TYPE_URL_V3, LDS_TYPE_URL_V2, LDS_TYPE_URL_V3, RDS_TYPE_URL_V2, RDS_TYPE_URL_V3 } from "./resources";
25+
import { HandleResponseResult } from "./xds-stream-state/xds-stream-state";
26+
import { sendUnaryData, ServerDuplexStream, ServerUnaryCall, status, experimental, loadPackageDefinition, logVerbosity } from '@grpc/grpc-js';
27+
import { loadSync } from "@grpc/proto-loader";
28+
import { ProtoGrpcType as CsdsProtoGrpcType } from "./generated/csds";
29+
30+
import registerAdminService = experimental.registerAdminService;
31+
32+
const TRACER_NAME = 'csds';
33+
34+
function trace(text: string): void {
35+
experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text);
36+
}
37+
38+
39+
function dateToProtoTimestamp(date?: Date | null): Timestamp | null {
40+
if (!date) {
41+
return null;
42+
}
43+
const millisSinceEpoch = date.getTime();
44+
return {
45+
seconds: (millisSinceEpoch / 1000) | 0,
46+
nanos: (millisSinceEpoch % 1000) * 1_000_000
47+
}
48+
}
49+
50+
let clientNode: Node | null = null;
51+
52+
const configStatus = {
53+
[EDS_TYPE_URL_V2]: new Map<string, GenericXdsConfig>(),
54+
[EDS_TYPE_URL_V3]: new Map<string, GenericXdsConfig>(),
55+
[CDS_TYPE_URL_V2]: new Map<string, GenericXdsConfig>(),
56+
[CDS_TYPE_URL_V3]: new Map<string, GenericXdsConfig>(),
57+
[RDS_TYPE_URL_V2]: new Map<string, GenericXdsConfig>(),
58+
[RDS_TYPE_URL_V3]: new Map<string, GenericXdsConfig>(),
59+
[LDS_TYPE_URL_V2]: new Map<string, GenericXdsConfig>(),
60+
[LDS_TYPE_URL_V3]: new Map<string, GenericXdsConfig>()
61+
};
62+
63+
/**
64+
* This function only accepts a v3 Node message, because we are only supporting
65+
* v3 CSDS and it only handles v3 Nodes. If the client is actually using v2 xDS
66+
* APIs, it should just provide the equivalent v3 Node message.
67+
* @param node The Node message for the client that is requesting resources
68+
*/
69+
export function setCsdsClientNode(node: Node) {
70+
clientNode = node;
71+
}
72+
73+
/**
74+
* Update the config status maps from the list of names of requested resources
75+
* for a specific type URL. These lists are the source of truth for determining
76+
* what resources will be listed in the CSDS response. Any resource that is not
77+
* in this list will never actually be applied anywhere.
78+
* @param typeUrl The resource type URL
79+
* @param names The list of resource names that are being requested
80+
*/
81+
export function updateCsdsRequestedNameList(typeUrl: AdsTypeUrl, names: string[]) {
82+
trace('Update type URL ' + typeUrl + ' with names [' + names + ']');
83+
const currentTime = dateToProtoTimestamp(new Date());
84+
const configMap = configStatus[typeUrl];
85+
for (const name of names) {
86+
if (!configMap.has(name)) {
87+
configMap.set(name, {
88+
type_url: typeUrl,
89+
name: name,
90+
last_updated: currentTime,
91+
client_status: 'REQUESTED'
92+
});
93+
}
94+
}
95+
for (const name of configMap.keys()) {
96+
if (!names.includes(name)) {
97+
configMap.delete(name);
98+
}
99+
}
100+
}
101+
102+
/**
103+
* Update the config status maps from the result of parsing a single ADS
104+
* response. All resources that validated are considered "ACKED", and all
105+
* resources that failed validation are considered "NACKED".
106+
* @param typeUrl The type URL of resources in this response
107+
* @param versionInfo The version info field from this response
108+
* @param updates The lists of resources that passed and failed validation
109+
*/
110+
export function updateCsdsResourceResponse(typeUrl: AdsTypeUrl, versionInfo: string, updates: HandleResponseResult) {
111+
const currentTime = dateToProtoTimestamp(new Date());
112+
const configMap = configStatus[typeUrl];
113+
for (const {name, raw} of updates.accepted) {
114+
const mapEntry = configMap.get(name);
115+
if (mapEntry) {
116+
trace('Updated ' + typeUrl + ' resource ' + name + ' to state ACKED');
117+
mapEntry.client_status = 'ACKED';
118+
mapEntry.version_info = versionInfo;
119+
mapEntry.xds_config = raw;
120+
mapEntry.error_state = null;
121+
mapEntry.last_updated = currentTime;
122+
}
123+
}
124+
for (const {name, error, raw} of updates.rejected) {
125+
const mapEntry = configMap.get(name);
126+
if (mapEntry) {
127+
trace('Updated ' + typeUrl + ' resource ' + name + ' to state NACKED');
128+
mapEntry.client_status = 'NACKED';
129+
mapEntry.error_state = {
130+
failed_configuration: raw,
131+
last_update_attempt: currentTime,
132+
details: error,
133+
version_info: versionInfo
134+
};
135+
}
136+
}
137+
for (const name of updates.missing) {
138+
const mapEntry = configMap.get(name);
139+
if (mapEntry) {
140+
trace('Updated ' + typeUrl + ' resource ' + name + ' to state DOES_NOT_EXIST');
141+
mapEntry.client_status = 'DOES_NOT_EXIST';
142+
mapEntry.version_info = versionInfo;
143+
mapEntry.xds_config = null;
144+
mapEntry.error_state = null;
145+
mapEntry.last_updated = currentTime;
146+
}
147+
}
148+
}
149+
150+
function getCurrentConfig(): ClientConfig {
151+
const genericConfigList: GenericXdsConfig[] = [];
152+
for (const configMap of Object.values(configStatus)) {
153+
for (const configValue of configMap.values()) {
154+
genericConfigList.push(configValue);
155+
}
156+
}
157+
const config = {
158+
node: clientNode,
159+
generic_xds_configs: genericConfigList
160+
};
161+
trace('Sending curent config ' + JSON.stringify(config, undefined, 2));
162+
return config;
163+
}
164+
165+
const csdsImplementation: ClientStatusDiscoveryServiceHandlers = {
166+
FetchClientStatus(call: ServerUnaryCall<ClientStatusRequest__Output, ClientStatusResponse>, callback: sendUnaryData<ClientStatusResponse>) {
167+
const request = call.request;
168+
if (request.node_matchers.length > 0) {
169+
callback({
170+
code: status.INVALID_ARGUMENT,
171+
details: 'Node matchers not supported'
172+
});
173+
return;
174+
}
175+
callback(null, {
176+
config: [getCurrentConfig()]
177+
});
178+
},
179+
StreamClientStatus(call: ServerDuplexStream<ClientStatusRequest__Output, ClientStatusResponse>) {
180+
call.on('data', (request: ClientStatusRequest__Output) => {
181+
if (request.node_matchers.length > 0) {
182+
call.emit('error', {
183+
code: status.INVALID_ARGUMENT,
184+
details: 'Node matchers not supported'
185+
});
186+
return;
187+
}
188+
call.write({
189+
config: [getCurrentConfig()]
190+
});
191+
});
192+
call.on('end', () => {
193+
call.end();
194+
});
195+
}
196+
}
197+
198+
const loadedProto = loadSync('envoy/service/status/v3/csds.proto', {
199+
keepCase: true,
200+
longs: String,
201+
enums: String,
202+
defaults: true,
203+
oneofs: true,
204+
includeDirs: [
205+
// Paths are relative to src/build
206+
__dirname + '/../../deps/envoy-api/',
207+
__dirname + '/../../deps/xds/',
208+
],
209+
});
210+
211+
const csdsGrpcObject = loadPackageDefinition(loadedProto) as unknown as CsdsProtoGrpcType;
212+
const csdsServiceDefinition = csdsGrpcObject.envoy.service.status.v3.ClientStatusDiscoveryService.service;
213+
214+
export function setup() {
215+
registerAdminService(() => csdsServiceDefinition, () => csdsImplementation);
216+
}

packages/grpc-js-xds/src/generated/ads.ts

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/grpc-js-xds/src/generated/cluster.ts

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)