Skip to content

Commit 12a6ec4

Browse files
committed
resolve conflicts
1 parent 7895b71 commit 12a6ec4

14 files changed

+336
-143
lines changed

src/AzureAppConfigurationImpl.ts

Lines changed: 127 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { RefreshTimer } from "./refresh/RefreshTimer";
1515
import { getConfigurationSettingWithTrace, listConfigurationSettingsWithTrace, requestTracingEnabled } from "./requestTracing/utils";
1616
import { KeyFilter, LabelFilter, SettingSelector } from "./types";
1717
import { ConfigurationClientManager } from "./ConfigurationClientManager";
18+
import { updateClientBackoffStatus } from "./ConfigurationClientWrapper";
1819

1920
type PagedSettingSelector = SettingSelector & {
2021
/**
@@ -40,6 +41,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
4041
#client: AppConfigurationClient;
4142
#options: AzureAppConfigurationOptions | undefined;
4243
#isInitialLoadCompleted: boolean = false;
44+
#isFailoverRequest: boolean = false;
4345

4446
// Refresh
4547
#refreshInterval: number = DEFAULT_REFRESH_INTERVAL_IN_MS;
@@ -58,11 +60,11 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
5860
#featureFlagSelectors: PagedSettingSelector[] = [];
5961

6062
constructor(
61-
client: AppConfigurationClient,
62-
options: AzureAppConfigurationOptions | undefined
63+
clientManager: ConfigurationClientManager,
64+
options: AzureAppConfigurationOptions | undefined,
6365
) {
64-
this.#client = client;
6566
this.#options = options;
67+
this.#clientManager = clientManager;
6668

6769
// Enable request tracing if not opt-out
6870
this.#requestTracingEnabled = requestTracingEnabled();
@@ -175,34 +177,70 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
175177
return {
176178
requestTracingEnabled: this.#requestTracingEnabled,
177179
initialLoadCompleted: this.#isInitialLoadCompleted,
178-
appConfigOptions: this.#options
180+
appConfigOptions: this.#options,
181+
isFailoverRequest: this.#isFailoverRequest
179182
};
180183
}
181184

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+
}
184191

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+
}
187206

188-
for (const selector of selectors) {
189-
const listOptions: ListConfigurationSettingsOptions = {
190-
keyFilter: selector.keyFilter,
191-
labelFilter: selector.labelFilter
192-
};
207+
throw error;
208+
}
209+
}
193210

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+
}
199214

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+
}
203238
}
204239
}
205-
}
240+
return loadedSettings;
241+
};
242+
243+
loadedSettings = await this.#executeWithFailoverPolicy(funcToExecute);
206244
return loadedSettings;
207245
}
208246

@@ -258,30 +296,42 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
258296

259297
async #loadFeatureFlags() {
260298
// 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+
}
279323
}
280324
}
325+
selector.pageEtags = pageEtags;
281326
}
282-
selector.pageEtags = pageEtags;
327+
328+
this.#featureFlagSelectors = selectors;
329+
return featureFlagsMap;
283330
}
284331

332+
let featureFlagsMap = new Map<string, any>();
333+
featureFlagsMap = await this.#executeWithFailoverPolicy(funcToExecute);
334+
285335
// parse feature flags
286336
const featureFlags = Array.from(featureFlagsMap.values()).map(rawFlag => JSON.parse(rawFlag));
287337

@@ -431,30 +481,32 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
431481
}
432482

433483
// 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+
}
451503
}
452504
}
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);
458510

459511
if (needRefresh) {
460512
try {
@@ -517,14 +569,18 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
517569
* Get a configuration setting by key and label. If the setting is not found, return undefine instead of throwing an error.
518570
*/
519571
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(
523574
this.#requestTraceOptions,
524-
this.#client,
575+
client,
525576
configurationSettingId,
526577
customOptions
527578
);
579+
};
580+
581+
let response: GetConfigurationSettingResponse | undefined;
582+
try {
583+
response = await this.#executeWithFailoverPolicy(funcToExecute);
528584
} catch (error) {
529585
if (isRestError(error) && error.statusCode === 404) {
530586
response = undefined;
@@ -578,3 +634,7 @@ function getValidFeatureFlagSelectors(selectors?: SettingSelector[]): SettingSel
578634
return getValidSelectors(selectors);
579635
}
580636
}
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

Comments
 (0)