@@ -15,6 +15,7 @@ import { RefreshTimer } from "./refresh/RefreshTimer";
15
15
import { getConfigurationSettingWithTrace , listConfigurationSettingsWithTrace , requestTracingEnabled } from "./requestTracing/utils" ;
16
16
import { KeyFilter , LabelFilter , SettingSelector } from "./types" ;
17
17
import { ConfigurationClientManager } from "./ConfigurationClientManager" ;
18
+ import { updateClientBackoffStatus } from "./ConfigurationClientWrapper" ;
18
19
19
20
type PagedSettingSelector = SettingSelector & {
20
21
/**
@@ -40,6 +41,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
40
41
#client: AppConfigurationClient ;
41
42
#options: AzureAppConfigurationOptions | undefined ;
42
43
#isInitialLoadCompleted: boolean = false ;
44
+ #isFailoverRequest: boolean = false ;
43
45
44
46
// Refresh
45
47
#refreshInterval: number = DEFAULT_REFRESH_INTERVAL_IN_MS ;
@@ -58,11 +60,11 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
58
60
#featureFlagSelectors: PagedSettingSelector [ ] = [ ] ;
59
61
60
62
constructor (
61
- client : AppConfigurationClient ,
62
- options : AzureAppConfigurationOptions | undefined
63
+ clientManager : ConfigurationClientManager ,
64
+ options : AzureAppConfigurationOptions | undefined ,
63
65
) {
64
- this . #client = client ;
65
66
this . #options = options ;
67
+ this . #clientManager = clientManager ;
66
68
67
69
// Enable request tracing if not opt-out
68
70
this . #requestTracingEnabled = requestTracingEnabled ( ) ;
@@ -175,34 +177,70 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
175
177
return {
176
178
requestTracingEnabled : this . #requestTracingEnabled,
177
179
initialLoadCompleted : this . #isInitialLoadCompleted,
178
- appConfigOptions : this . #options
180
+ appConfigOptions : this . #options,
181
+ isFailoverRequest : this . #isFailoverRequest
179
182
} ;
180
183
}
181
184
182
- async #loadSelectedKeyValues( ) : Promise < ConfigurationSetting [ ] > {
183
- const loadedSettings : ConfigurationSetting [ ] = [ ] ;
185
+ async #executeWithFailoverPolicy( funcToExecute ) {
186
+ const clients = await this . #clientManager. getClients ( ) ;
187
+ if ( clients . length === 0 ) {
188
+ this . #clientManager. refreshClients ( ) ;
189
+ throw new Error ( "No client is available to connect to the target App Configuration store." ) ;
190
+ }
184
191
185
- // validate selectors
186
- const selectors = getValidKeyValueSelectors ( this . #options?. selectors ) ;
192
+ for ( const client of clients ) {
193
+ let successful = false ;
194
+ try {
195
+ const result = await funcToExecute ( client . client ) ;
196
+ this . #isFailoverRequest = false ;
197
+ successful = true ;
198
+ updateClientBackoffStatus ( client , successful ) ;
199
+ return result ;
200
+ } catch ( error ) {
201
+ if ( isFailoverableError ( error ) ) {
202
+ updateClientBackoffStatus ( client , successful ) ;
203
+ this . #isFailoverRequest = true ;
204
+ continue ;
205
+ }
187
206
188
- for ( const selector of selectors ) {
189
- const listOptions : ListConfigurationSettingsOptions = {
190
- keyFilter : selector . keyFilter ,
191
- labelFilter : selector . labelFilter
192
- } ;
207
+ throw error ;
208
+ }
209
+ }
193
210
194
- const settings = listConfigurationSettingsWithTrace (
195
- this . #requestTraceOptions,
196
- this . #client,
197
- listOptions
198
- ) ;
211
+ this . #clientManager. refreshClients ( ) ;
212
+ throw new Error ( "All app configuration clients failed to get settings." ) ;
213
+ }
199
214
200
- for await ( const setting of settings ) {
201
- if ( ! isFeatureFlag ( setting ) ) { // exclude feature flags
202
- loadedSettings . push ( setting ) ;
215
+ async #loadSelectedKeyValues( ) : Promise < ConfigurationSetting [ ] > {
216
+ // validate selectors
217
+ const selectors = getValidKeyValueSelectors ( this . #options?. selectors ) ;
218
+ let loadedSettings : ConfigurationSetting [ ] = [ ] ;
219
+
220
+ const funcToExecute = async ( client ) => {
221
+ const loadedSettings : ConfigurationSetting [ ] = [ ] ;
222
+ for ( const selector of selectors ) {
223
+ const listOptions : ListConfigurationSettingsOptions = {
224
+ keyFilter : selector . keyFilter ,
225
+ labelFilter : selector . labelFilter
226
+ } ;
227
+
228
+ const settings = listConfigurationSettingsWithTrace (
229
+ this . #requestTraceOptions,
230
+ client ,
231
+ listOptions
232
+ ) ;
233
+
234
+ for await ( const setting of settings ) {
235
+ if ( ! isFeatureFlag ( setting ) ) { // exclude feature flags
236
+ loadedSettings . push ( setting ) ;
237
+ }
203
238
}
204
239
}
205
- }
240
+ return loadedSettings ;
241
+ } ;
242
+
243
+ loadedSettings = await this . #executeWithFailoverPolicy( funcToExecute ) ;
206
244
return loadedSettings ;
207
245
}
208
246
@@ -258,30 +296,42 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
258
296
259
297
async #loadFeatureFlags( ) {
260
298
// Temporary map to store feature flags, key is the key of the setting, value is the raw value of the setting
261
- const featureFlagsMap = new Map < string , any > ( ) ;
262
- for ( const selector of this . #featureFlagSelectors) {
263
- const listOptions : ListConfigurationSettingsOptions = {
264
- keyFilter : `${ featureFlagPrefix } ${ selector . keyFilter } ` ,
265
- labelFilter : selector . labelFilter
266
- } ;
267
-
268
- const pageEtags : string [ ] = [ ] ;
269
- const pageIterator = listConfigurationSettingsWithTrace (
270
- this . #requestTraceOptions,
271
- this . #client,
272
- listOptions
273
- ) . byPage ( ) ;
274
- for await ( const page of pageIterator ) {
275
- pageEtags . push ( page . etag ?? "" ) ;
276
- for ( const setting of page . items ) {
277
- if ( isFeatureFlag ( setting ) ) {
278
- featureFlagsMap . set ( setting . key , setting . value ) ;
299
+ const funcToExecute = async ( client ) => {
300
+ const featureFlagsMap = new Map < string , any > ( ) ;
301
+ const selectors = JSON . parse (
302
+ JSON . stringify ( this . #featureFlagSelectors)
303
+ ) ;
304
+
305
+ for ( const selector of selectors ) {
306
+ const listOptions : ListConfigurationSettingsOptions = {
307
+ keyFilter : `${ featureFlagPrefix } ${ selector . keyFilter } ` ,
308
+ labelFilter : selector . labelFilter
309
+ } ;
310
+
311
+ const pageEtags : string [ ] = [ ] ;
312
+ const pageIterator = listConfigurationSettingsWithTrace (
313
+ this . #requestTraceOptions,
314
+ client ,
315
+ listOptions
316
+ ) . byPage ( ) ;
317
+ for await ( const page of pageIterator ) {
318
+ pageEtags . push ( page . etag ?? "" ) ;
319
+ for ( const setting of page . items ) {
320
+ if ( isFeatureFlag ( setting ) ) {
321
+ featureFlagsMap . set ( setting . key , setting . value ) ;
322
+ }
279
323
}
280
324
}
325
+ selector . pageEtags = pageEtags ;
281
326
}
282
- selector . pageEtags = pageEtags ;
327
+
328
+ this . #featureFlagSelectors = selectors ;
329
+ return featureFlagsMap ;
283
330
}
284
331
332
+ let featureFlagsMap = new Map < string , any > ( ) ;
333
+ featureFlagsMap = await this . #executeWithFailoverPolicy( funcToExecute ) ;
334
+
285
335
// parse feature flags
286
336
const featureFlags = Array . from ( featureFlagsMap . values ( ) ) . map ( rawFlag => JSON . parse ( rawFlag ) ) ;
287
337
@@ -431,30 +481,32 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
431
481
}
432
482
433
483
// check if any feature flag is changed
434
- let needRefresh = false ;
435
- for ( const selector of this . #featureFlagSelectors) {
436
- const listOptions : ListConfigurationSettingsOptions = {
437
- keyFilter : `${ featureFlagPrefix } ${ selector . keyFilter } ` ,
438
- labelFilter : selector . labelFilter ,
439
- pageEtags : selector . pageEtags
440
- } ;
441
- const pageIterator = listConfigurationSettingsWithTrace (
442
- this . #requestTraceOptions,
443
- this . #client,
444
- listOptions
445
- ) . byPage ( ) ;
446
-
447
- for await ( const page of pageIterator ) {
448
- if ( page . _response . status === 200 ) { // created or changed
449
- needRefresh = true ;
450
- break ;
484
+ const funcToExecute = async ( client ) => {
485
+ const needRefresh = false ;
486
+ for ( const selector of this . #featureFlagSelectors) {
487
+ const listOptions : ListConfigurationSettingsOptions = {
488
+ keyFilter : `${ featureFlagPrefix } ${ selector . keyFilter } ` ,
489
+ labelFilter : selector . labelFilter ,
490
+ pageEtags : selector . pageEtags
491
+ } ;
492
+
493
+ const pageIterator = listConfigurationSettingsWithTrace (
494
+ this . #requestTraceOptions,
495
+ client ,
496
+ listOptions
497
+ ) . byPage ( ) ;
498
+
499
+ for await ( const page of pageIterator ) {
500
+ if ( page . _response . status === 200 ) { // created or changed
501
+ return true ;
502
+ }
451
503
}
452
504
}
453
-
454
- if ( needRefresh ) {
455
- break ; // short-circuit if result from any of the selectors is changed
456
- }
457
- }
505
+ return needRefresh ;
506
+ } ;
507
+
508
+ let needRefresh : boolean ;
509
+ needRefresh = await this . #executeWithFailoverPolicy ( funcToExecute ) ;
458
510
459
511
if ( needRefresh ) {
460
512
try {
@@ -517,14 +569,18 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
517
569
* Get a configuration setting by key and label. If the setting is not found, return undefine instead of throwing an error.
518
570
*/
519
571
async #getConfigurationSetting( configurationSettingId : ConfigurationSettingId , customOptions ?: GetConfigurationSettingOptions ) : Promise < GetConfigurationSettingResponse | undefined > {
520
- let response : GetConfigurationSettingResponse | undefined ;
521
- try {
522
- response = await getConfigurationSettingWithTrace (
572
+ const funcToExecute = async ( client ) => {
573
+ return getConfigurationSettingWithTrace (
523
574
this . #requestTraceOptions,
524
- this . # client,
575
+ client ,
525
576
configurationSettingId ,
526
577
customOptions
527
578
) ;
579
+ } ;
580
+
581
+ let response : GetConfigurationSettingResponse | undefined ;
582
+ try {
583
+ response = await this . #executeWithFailoverPolicy( funcToExecute ) ;
528
584
} catch ( error ) {
529
585
if ( isRestError ( error ) && error . statusCode === 404 ) {
530
586
response = undefined ;
@@ -578,3 +634,7 @@ function getValidFeatureFlagSelectors(selectors?: SettingSelector[]): SettingSel
578
634
return getValidSelectors ( selectors ) ;
579
635
}
580
636
}
637
+
638
+ function isFailoverableError ( error : any ) : boolean {
639
+ return ( error instanceof RestError ) && ( error . statusCode === 408 || error . statusCode === 429 || ( error . statusCode !== undefined && error . statusCode >= 500 ) ) ;
640
+ }
0 commit comments