17
17
18
18
import { experimental , logVerbosity , status as Status , Metadata , connectivityState } from "@grpc/grpc-js" ;
19
19
import { validateXdsServerConfig , XdsServerConfig } from "./xds-bootstrap" ;
20
- import { getSingletonXdsClient , XdsClient , XdsClusterDropStats } from "./xds-client" ;
20
+ import { getSingletonXdsClient , XdsClient , XdsClusterDropStats , XdsClusterLocalityStats } from "./xds-client" ;
21
+ import { LocalitySubchannelAddress } from "./load-balancer-priority" ;
21
22
22
23
import LoadBalancer = experimental . LoadBalancer ;
23
24
import registerLoadBalancerType = experimental . registerLoadBalancerType ;
@@ -31,6 +32,8 @@ import ChildLoadBalancerHandler = experimental.ChildLoadBalancerHandler;
31
32
import createChildChannelControlHelper = experimental . createChildChannelControlHelper ;
32
33
import TypedLoadBalancingConfig = experimental . TypedLoadBalancingConfig ;
33
34
import selectLbConfigFromList = experimental . selectLbConfigFromList ;
35
+ import SubchannelInterface = experimental . SubchannelInterface ;
36
+ import BaseSubchannelWrapper = experimental . BaseSubchannelWrapper ;
34
37
35
38
const TRACER_NAME = 'xds_cluster_impl' ;
36
39
@@ -80,7 +83,7 @@ class XdsClusterImplLoadBalancingConfig implements TypedLoadBalancingConfig {
80
83
} ;
81
84
}
82
85
83
- constructor ( private cluster : string , private dropCategories : DropCategory [ ] , private childPolicy : TypedLoadBalancingConfig , private edsServiceName ? : string , private lrsLoadReportingServer ? : XdsServerConfig , maxConcurrentRequests ?: number ) {
86
+ constructor ( private cluster : string , private dropCategories : DropCategory [ ] , private childPolicy : TypedLoadBalancingConfig , private edsServiceName : string , private lrsLoadReportingServer : XdsServerConfig , maxConcurrentRequests ?: number ) {
84
87
this . maxConcurrentRequests = maxConcurrentRequests ?? DEFAULT_MAX_CONCURRENT_REQUESTS ;
85
88
}
86
89
@@ -112,8 +115,8 @@ class XdsClusterImplLoadBalancingConfig implements TypedLoadBalancingConfig {
112
115
if ( ! ( 'cluster' in obj && typeof obj . cluster === 'string' ) ) {
113
116
throw new Error ( 'xds_cluster_impl config must have a string field cluster' ) ;
114
117
}
115
- if ( 'eds_service_name' in obj && ! ( obj . eds_service_name === undefined || typeof obj . eds_service_name === 'string' ) ) {
116
- throw new Error ( 'xds_cluster_impl config eds_service_name field must be a string if provided ' ) ;
118
+ if ( ! ( 'eds_service_name' in obj && typeof obj . eds_service_name === 'string' ) ) {
119
+ throw new Error ( 'xds_cluster_impl config must have a string field eds_service_name ' ) ;
117
120
}
118
121
if ( 'max_concurrent_requests' in obj && ! ( obj . max_concurrent_requests === undefined || typeof obj . max_concurrent_requests === 'number' ) ) {
119
122
throw new Error ( 'xds_cluster_impl config max_concurrent_requests must be a number if provided' ) ;
@@ -128,7 +131,7 @@ class XdsClusterImplLoadBalancingConfig implements TypedLoadBalancingConfig {
128
131
if ( ! childConfig ) {
129
132
throw new Error ( 'xds_cluster_impl config child_policy parsing failed' ) ;
130
133
}
131
- return new XdsClusterImplLoadBalancingConfig ( obj . cluster , obj . drop_categories . map ( validateDropCategory ) , childConfig , obj . eds_service_name , obj . lrs_load_reporting_server ? validateXdsServerConfig ( obj . lrs_load_reporting_server ) : undefined , obj . max_concurrent_requests ) ;
134
+ return new XdsClusterImplLoadBalancingConfig ( obj . cluster , obj . drop_categories . map ( validateDropCategory ) , childConfig , obj . eds_service_name , validateXdsServerConfig ( obj . lrs_load_reporting_server ) , obj . max_concurrent_requests ) ;
132
135
}
133
136
}
134
137
@@ -156,7 +159,25 @@ class CallCounterMap {
156
159
157
160
const callCounterMap = new CallCounterMap ( ) ;
158
161
159
- class DropPicker implements Picker {
162
+ class LocalitySubchannelWrapper extends BaseSubchannelWrapper implements SubchannelInterface {
163
+ constructor ( child : SubchannelInterface , private statsObject : XdsClusterLocalityStats ) {
164
+ super ( child ) ;
165
+ }
166
+
167
+ getStatsObject ( ) {
168
+ return this . statsObject ;
169
+ }
170
+
171
+ getWrappedSubchannel ( ) : SubchannelInterface {
172
+ return this . child ;
173
+ }
174
+ }
175
+
176
+ /**
177
+ * This picker is responsible for implementing the drop configuration, and for
178
+ * recording drop stats and per-locality stats.
179
+ */
180
+ class XdsClusterImplPicker implements Picker {
160
181
constructor ( private originalPicker : Picker , private callCounterMapKey : string , private maxConcurrentRequests : number , private dropCategories : DropCategory [ ] , private clusterDropStats : XdsClusterDropStats | null ) { }
161
182
162
183
private checkForMaxConcurrentRequestsDrop ( ) : boolean {
@@ -186,16 +207,19 @@ class DropPicker implements Picker {
186
207
}
187
208
if ( details === null ) {
188
209
const originalPick = this . originalPicker . pick ( pickArgs ) ;
210
+ const pickSubchannel = originalPick . subchannel ? ( originalPick . subchannel as LocalitySubchannelWrapper ) : null ;
189
211
return {
190
212
pickResultType : originalPick . pickResultType ,
191
213
status : originalPick . status ,
192
- subchannel : originalPick . subchannel ,
214
+ subchannel : pickSubchannel ?. getWrappedSubchannel ( ) ?? null ,
193
215
onCallStarted : ( ) => {
194
216
originalPick . onCallStarted ?.( ) ;
217
+ pickSubchannel ?. getStatsObject ( ) . addCallStarted ( ) ;
195
218
callCounterMap . startCall ( this . callCounterMapKey ) ;
196
219
} ,
197
220
onCallEnded : status => {
198
221
originalPick . onCallEnded ?.( status ) ;
222
+ pickSubchannel ?. getStatsObject ( ) . addCallFinished ( status !== Status . OK )
199
223
callCounterMap . endCall ( this . callCounterMapKey ) ;
200
224
}
201
225
} ;
@@ -227,11 +251,25 @@ class XdsClusterImplBalancer implements LoadBalancer {
227
251
228
252
constructor ( private readonly channelControlHelper : ChannelControlHelper ) {
229
253
this . childBalancer = new ChildLoadBalancerHandler ( createChildChannelControlHelper ( channelControlHelper , {
254
+ createSubchannel : ( subchannelAddress , subchannelArgs ) => {
255
+ if ( ! this . xdsClient || ! this . latestConfig ) {
256
+ throw new Error ( 'xds_cluster_impl: invalid state: createSubchannel called with xdsClient or latestConfig not populated' ) ;
257
+ }
258
+ const locality = ( subchannelAddress as LocalitySubchannelAddress ) . locality ?? '' ;
259
+ const wrapperChild = channelControlHelper . createSubchannel ( subchannelAddress , subchannelArgs ) ;
260
+ const statsObj = this . xdsClient . addClusterLocalityStats (
261
+ this . latestConfig . getLrsLoadReportingServer ( ) ,
262
+ this . latestConfig . getCluster ( ) ,
263
+ this . latestConfig . getEdsServiceName ( ) ,
264
+ locality
265
+ ) ;
266
+ return new LocalitySubchannelWrapper ( wrapperChild , statsObj ) ;
267
+ } ,
230
268
updateState : ( connectivityState , originalPicker ) => {
231
269
if ( this . latestConfig === null ) {
232
270
channelControlHelper . updateState ( connectivityState , originalPicker ) ;
233
271
} else {
234
- const picker = new DropPicker ( originalPicker , getCallCounterMapKey ( this . latestConfig . getCluster ( ) , this . latestConfig . getEdsServiceName ( ) ) , this . latestConfig . getMaxConcurrentRequests ( ) , this . latestConfig . getDropCategories ( ) , this . clusterDropStats ) ;
272
+ const picker = new XdsClusterImplPicker ( originalPicker , getCallCounterMapKey ( this . latestConfig . getCluster ( ) , this . latestConfig . getEdsServiceName ( ) ) , this . latestConfig . getMaxConcurrentRequests ( ) , this . latestConfig . getDropCategories ( ) , this . clusterDropStats ) ;
235
273
channelControlHelper . updateState ( connectivityState , picker ) ;
236
274
}
237
275
}
0 commit comments