grpc-js-xds: Remove env var protection for routing feature

This commit is contained in:
Michael Lumish 2021-04-02 11:12:41 -07:00
parent c3aeb94b8c
commit abfe46b99d
3 changed files with 123 additions and 158 deletions

View File

@ -13,10 +13,4 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/**
* Environment variable protection for traffic splitting and routing
* https://github.com/grpc/proposal/blob/master/A28-xds-traffic-splitting-and-routing.md#xds-resolver-and-xds-client
*/
export const GRPC_XDS_EXPERIMENTAL_ROUTING = (process.env.GRPC_XDS_EXPERIMENTAL_ROUTING === 'true');
*/

View File

@ -30,7 +30,6 @@ import { Listener__Output } from './generated/envoy/api/v2/Listener';
import { Watcher } from './xds-stream-state/xds-stream-state';
import { RouteConfiguration__Output } from './generated/envoy/api/v2/RouteConfiguration';
import { HttpConnectionManager__Output } from './generated/envoy/config/filter/network/http_connection_manager/v2/HttpConnectionManager';
import { GRPC_XDS_EXPERIMENTAL_ROUTING } from './environment';
import { CdsLoadBalancingConfig } from './load-balancer-cds';
import { VirtualHost__Output } from './generated/envoy/api/v2/route/VirtualHost';
import { RouteMatch__Output } from './generated/envoy/api/v2/route/RouteMatch';
@ -288,116 +287,93 @@ class XdsResolver implements Resolver {
private handleRouteConfig(routeConfig: RouteConfiguration__Output) {
this.latestRouteConfig = routeConfig;
if (GRPC_XDS_EXPERIMENTAL_ROUTING) {
const virtualHost = findVirtualHostForDomain(routeConfig.virtual_hosts, this.target.path);
if (virtualHost === null) {
this.reportResolutionError('No matching route found');
return;
}
trace('Received virtual host config ' + JSON.stringify(virtualHost, undefined, 2));
const allConfigClusters = new Set<string>();
const matchList: {matcher: Matcher, action: RouteAction}[] = [];
for (const route of virtualHost.routes) {
let routeAction: RouteAction;
switch (route.route!.cluster_specifier) {
case 'cluster_header':
continue;
case 'cluster':{
const cluster = route.route!.cluster!;
allConfigClusters.add(cluster);
routeAction = new SingleClusterRouteAction(cluster);
break;
}
case 'weighted_clusters': {
const weightedClusters: WeightedCluster[] = [];
for (const clusterWeight of route.route!.weighted_clusters!.clusters) {
allConfigClusters.add(clusterWeight.name);
weightedClusters.push({name: clusterWeight.name, weight: clusterWeight.weight?.value ?? 0});
}
routeAction = new WeightedClusterRouteAction(weightedClusters, route.route!.weighted_clusters!.total_weight?.value ?? 100);
}
}
const routeMatcher = getPredicateForMatcher(route.match!);
matchList.push({matcher: routeMatcher, action: routeAction});
}
/* Mark clusters that are not in this route config, and remove ones with
* no references */
for (const [name, refCount] of Array.from(this.clusterRefcounts.entries())) {
if (!allConfigClusters.has(name)) {
refCount.inLastConfig = false;
if (refCount.refCount === 0) {
this.clusterRefcounts.delete(name);
}
}
}
// Add any new clusters from this route config
for (const name of allConfigClusters) {
if (this.clusterRefcounts.has(name)) {
this.clusterRefcounts.get(name)!.inLastConfig = true;
} else {
this.clusterRefcounts.set(name, {inLastConfig: true, refCount: 0});
}
}
const configSelector: ConfigSelector = (methodName, metadata) => {
for (const {matcher, action} of matchList) {
if (matcher.apply(methodName, metadata)) {
const clusterName = action.getCluster();
this.refCluster(clusterName);
const onCommitted = () => {
this.unrefCluster(clusterName);
}
return {
methodConfig: {name: []},
onCommitted: onCommitted,
pickInformation: {cluster: clusterName},
status: status.OK
};
}
}
return {
methodConfig: {name: []},
// cluster won't be used here, but it's set because of some TypeScript weirdness
pickInformation: {cluster: ''},
status: status.UNAVAILABLE
};
};
trace('Created ConfigSelector with configuration:');
for (const {matcher, action} of matchList) {
trace(matcher.toString());
trace('=> ' + action.toString());
}
const clusterConfigMap = new Map<string, {child_policy: LoadBalancingConfig[]}>();
for (const clusterName of this.clusterRefcounts.keys()) {
clusterConfigMap.set(clusterName, {child_policy: [new CdsLoadBalancingConfig(clusterName)]});
}
const lbPolicyConfig = new XdsClusterManagerLoadBalancingConfig(clusterConfigMap);
const serviceConfig: ServiceConfig = {
methodConfig: [],
loadBalancingConfig: [lbPolicyConfig]
}
this.listener.onSuccessfulResolution([], serviceConfig, null, configSelector, {});
} else {
// !GRPC_XDS_EXPERIMENTAL_ROUTING
for (const virtualHost of routeConfig.virtual_hosts) {
if (virtualHost.domains.indexOf(this.target.path) >= 0) {
const route = virtualHost.routes[virtualHost.routes.length - 1];
if (route.match?.prefix === '' && route.route?.cluster) {
trace('Reporting RDS update for host ' + uriToString(this.target) + ' with cluster ' + route.route.cluster);
this.listener.onSuccessfulResolution([], {
methodConfig: [],
loadBalancingConfig: [
new CdsLoadBalancingConfig(route.route.cluster)
],
}, null, null, {});
this.hasReportedSuccess = true;
return;
} else {
trace('Discarded matching route with prefix ' + route.match?.prefix + ' and cluster ' + route.route?.cluster);
}
}
}
const virtualHost = findVirtualHostForDomain(routeConfig.virtual_hosts, this.target.path);
if (virtualHost === null) {
this.reportResolutionError('No matching route found');
return;
}
trace('Received virtual host config ' + JSON.stringify(virtualHost, undefined, 2));
const allConfigClusters = new Set<string>();
const matchList: {matcher: Matcher, action: RouteAction}[] = [];
for (const route of virtualHost.routes) {
let routeAction: RouteAction;
switch (route.route!.cluster_specifier) {
case 'cluster_header':
continue;
case 'cluster':{
const cluster = route.route!.cluster!;
allConfigClusters.add(cluster);
routeAction = new SingleClusterRouteAction(cluster);
break;
}
case 'weighted_clusters': {
const weightedClusters: WeightedCluster[] = [];
for (const clusterWeight of route.route!.weighted_clusters!.clusters) {
allConfigClusters.add(clusterWeight.name);
weightedClusters.push({name: clusterWeight.name, weight: clusterWeight.weight?.value ?? 0});
}
routeAction = new WeightedClusterRouteAction(weightedClusters, route.route!.weighted_clusters!.total_weight?.value ?? 100);
}
}
const routeMatcher = getPredicateForMatcher(route.match!);
matchList.push({matcher: routeMatcher, action: routeAction});
}
/* Mark clusters that are not in this route config, and remove ones with
* no references */
for (const [name, refCount] of Array.from(this.clusterRefcounts.entries())) {
if (!allConfigClusters.has(name)) {
refCount.inLastConfig = false;
if (refCount.refCount === 0) {
this.clusterRefcounts.delete(name);
}
}
}
// Add any new clusters from this route config
for (const name of allConfigClusters) {
if (this.clusterRefcounts.has(name)) {
this.clusterRefcounts.get(name)!.inLastConfig = true;
} else {
this.clusterRefcounts.set(name, {inLastConfig: true, refCount: 0});
}
}
const configSelector: ConfigSelector = (methodName, metadata) => {
for (const {matcher, action} of matchList) {
if (matcher.apply(methodName, metadata)) {
const clusterName = action.getCluster();
this.refCluster(clusterName);
const onCommitted = () => {
this.unrefCluster(clusterName);
}
return {
methodConfig: {name: []},
onCommitted: onCommitted,
pickInformation: {cluster: clusterName},
status: status.OK
};
}
}
return {
methodConfig: {name: []},
// cluster won't be used here, but it's set because of some TypeScript weirdness
pickInformation: {cluster: ''},
status: status.UNAVAILABLE
};
};
trace('Created ConfigSelector with configuration:');
for (const {matcher, action} of matchList) {
trace(matcher.toString());
trace('=> ' + action.toString());
}
const clusterConfigMap = new Map<string, {child_policy: LoadBalancingConfig[]}>();
for (const clusterName of this.clusterRefcounts.keys()) {
clusterConfigMap.set(clusterName, {child_policy: [new CdsLoadBalancingConfig(clusterName)]});
}
const lbPolicyConfig = new XdsClusterManagerLoadBalancingConfig(clusterConfigMap);
const serviceConfig: ServiceConfig = {
methodConfig: [],
loadBalancingConfig: [lbPolicyConfig]
}
this.listener.onSuccessfulResolution([], serviceConfig, null, configSelector, {});
}
private reportResolutionError(reason: string) {

View File

@ -16,7 +16,6 @@
*/
import { experimental, logVerbosity, StatusObject } from "@grpc/grpc-js";
import { GRPC_XDS_EXPERIMENTAL_ROUTING } from "../environment";
import { RouteConfiguration__Output } from "../generated/envoy/api/v2/RouteConfiguration";
import { CdsLoadBalancingConfig } from "../load-balancer-cds";
import { Watcher, XdsStreamState } from "./xds-stream-state";
@ -99,55 +98,51 @@ export class RdsState implements XdsStreamState<RouteConfiguration__Output> {
}
validateResponse(message: RouteConfiguration__Output): boolean {
if (GRPC_XDS_EXPERIMENTAL_ROUTING) {
// https://github.com/grpc/proposal/blob/master/A28-xds-traffic-splitting-and-routing.md#response-validation
for (const virtualHost of message.virtual_hosts) {
for (const domainPattern of virtualHost.domains) {
const starIndex = domainPattern.indexOf('*');
const lastStarIndex = domainPattern.lastIndexOf('*');
// A domain pattern can have at most one wildcard *
if (starIndex !== lastStarIndex) {
return false;
}
// A wildcard * can either be absent or at the beginning or end of the pattern
if (!(starIndex === -1 || starIndex === 0 || starIndex === domainPattern.length - 1)) {
// https://github.com/grpc/proposal/blob/master/A28-xds-traffic-splitting-and-routing.md#response-validation
for (const virtualHost of message.virtual_hosts) {
for (const domainPattern of virtualHost.domains) {
const starIndex = domainPattern.indexOf('*');
const lastStarIndex = domainPattern.lastIndexOf('*');
// A domain pattern can have at most one wildcard *
if (starIndex !== lastStarIndex) {
return false;
}
// A wildcard * can either be absent or at the beginning or end of the pattern
if (!(starIndex === -1 || starIndex === 0 || starIndex === domainPattern.length - 1)) {
return false;
}
}
for (const route of virtualHost.routes) {
const match = route.match;
if (!match) {
return false;
}
if (SUPPORTED_PATH_SPECIFIERS.indexOf(match.path_specifier) < 0) {
return false;
}
for (const headers of match.headers) {
if (SUPPPORTED_HEADER_MATCH_SPECIFIERS.indexOf(headers.header_match_specifier) < 0) {
return false;
}
}
for (const route of virtualHost.routes) {
const match = route.match;
if (!match) {
if (route.action !== 'route') {
return false;
}
if ((route.route === undefined) || SUPPORTED_CLUSTER_SPECIFIERS.indexOf(route.route.cluster_specifier) < 0) {
return false;
}
if (route.route!.cluster_specifier === 'weighted_clusters') {
let weightSum = 0;
for (const clusterWeight of route.route.weighted_clusters!.clusters) {
weightSum += clusterWeight.weight?.value ?? 0;
}
if (weightSum !== route.route.weighted_clusters!.total_weight?.value ?? 100) {
return false;
}
if (SUPPORTED_PATH_SPECIFIERS.indexOf(match.path_specifier) < 0) {
return false;
}
for (const headers of match.headers) {
if (SUPPPORTED_HEADER_MATCH_SPECIFIERS.indexOf(headers.header_match_specifier) < 0) {
return false;
}
}
if (route.action !== 'route') {
return false;
}
if ((route.route === undefined) || SUPPORTED_CLUSTER_SPECIFIERS.indexOf(route.route.cluster_specifier) < 0) {
return false;
}
if (route.route!.cluster_specifier === 'weighted_clusters') {
let weightSum = 0;
for (const clusterWeight of route.route.weighted_clusters!.clusters) {
weightSum += clusterWeight.weight?.value ?? 0;
}
if (weightSum !== route.route.weighted_clusters!.total_weight?.value ?? 100) {
return false;
}
}
}
}
return true;
} else {
return true;
}
return true;
}
private handleMissingNames(allRouteConfigNames: Set<string>) {