Skip to content

Commit

Permalink
Fb/registry failure handling (#80)
Browse files Browse the repository at this point in the history
* typos

* remove obsolete links

* rework fail state handling for update url (jobPoll = false) cases

* big big rework. same code for everyone

* more consistency changes and increase poll timing back to 20sec

* one more fix

* update nock tests

* update registry test snapshots

* make poll frequency faster again

* ouch

* update nock snapshots again
  • Loading branch information
rlindner81 authored May 13, 2024
1 parent 8a72d2c commit 71470b5
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 173 deletions.
2 changes: 1 addition & 1 deletion src/cliOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ const APP_CLI_OPTIONS = Object.freeze({
optionalFlagArgs: [FLAG_ARG.SKIP_UNCHANGED],
callback: reg.registryUpdateAllDependencies,
},
REGISTRY_UPDATE_APPURL: {
REGISTRY_UPDATE_APP_URL: {
commandVariants: ["--registry-update-url"],
optionalPassArgs: [PASS_ARG.TENANT_ID],
callback: reg.registryUpdateApplicationURL,
Expand Down
10 changes: 5 additions & 5 deletions src/shared/static.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ const limiter = async (limit, payloads, iterator) => {
return results.map(({ value }) => value);
};

const CHARPOINTS = Object.freeze({
const CHAR_POINTS = Object.freeze({
// 33 -- 47 are 15 symbols
// 58 -- 64 are 7 symbols again
// 91 -- 96 are 6 symbols again
Expand All @@ -321,10 +321,10 @@ const randomString = (
{ doNumbers = true, doUpperCaseLetters = true, doLowerCaseLetters = true, doSymbols = false } = {}
) => {
const alphabet = [].concat(
doNumbers ? CHARPOINTS.NUMBERS : [],
doUpperCaseLetters ? CHARPOINTS.UPPER_CASE_LETTERS : [],
doLowerCaseLetters ? CHARPOINTS.LOWER_CASE_LETTERS : [],
doSymbols ? CHARPOINTS.SYMBOLS : []
doNumbers ? CHAR_POINTS.NUMBERS : [],
doUpperCaseLetters ? CHAR_POINTS.UPPER_CASE_LETTERS : [],
doLowerCaseLetters ? CHAR_POINTS.LOWER_CASE_LETTERS : [],
doSymbols ? CHAR_POINTS.SYMBOLS : []
);
return alphabet.length === 0
? []
Expand Down
88 changes: 43 additions & 45 deletions src/submodules/tenantRegistry.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ const { assert } = require("../shared/error");
const { request } = require("../shared/request");

const REGISTRY_PAGE_SIZE = 200;
const REGISTRY_JOB_POLL_FREQUENCY = 20000;
const REGISTRY_JOB_POLL_FREQUENCY = 15000;
const REGISTRY_REQUEST_CONCURRENCY_FALLBACK = 10;
const TENANT_UPDATABLE_STATES = ["SUBSCRIBED", "UPDATE_FAILED"];
const RESPONSE_STATE = Object.freeze({
const JOB_STATE = Object.freeze({
STARTED: "STARTED",
SUCCEEDED: "SUCCEEDED",
FAILED: "FAILED",
});
Expand Down Expand Up @@ -59,6 +60,7 @@ const _registrySubscriptionsPaged = async (context, tenant) => {
...query,
page: page++,
},
headers: { Accept: "application/json" },
auth: { token },
});
const { subscriptions: pageSubscriptions, morePages } = await response.json();
Expand Down Expand Up @@ -126,23 +128,24 @@ const _registryJobPoll = async (context, location, { skipFirst = false } = {}) =
const response = await request({
url: saas_registry_url,
pathname: location,
headers: { Accept: "application/json" },
auth: { token },
});
const responseBody = await response.json();
const { state } = responseBody;
if (!state || state === RESPONSE_STATE.SUCCEEDED || state === RESPONSE_STATE.FAILED) {
return JSON.stringify(responseBody, null, 2);
assert(state, "got job poll response without state\n%j", responseBody);
if (state !== JOB_STATE.STARTED) {
return responseBody;
}
}
};

const registryJob = async (context, [jobId]) => {
assert(isUUID(jobId), "JOB_ID is not a uuid", jobId);
return _registryJobPoll(context, `/api/v2.0/jobs/${jobId}`, { skipFirst: true });
const result = await _registryJobPoll(context, `/api/v2.0/jobs/${jobId}`, { skipFirst: true });
return JSON.stringify(result, null, 2);
};

// https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/4a8b63678cf24d5b8b36bd1957391ce3.html
// https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/9c4f927011db4bd0a53b23a1b33b36d0.html
const _registryCallForTenant = async (
context,
subscription,
Expand Down Expand Up @@ -183,69 +186,64 @@ const _registryCallForTenant = async (
});

if (!doJobPoll) {
const state = response.status === 200 ? RESPONSE_STATE.SUCCEEDED : RESPONSE_STATE.FAILED;
console.log("subscription operation with method %s for tenant %s finished with state %s", method, tenantId, state);
return JSON.stringify({ tenantId, state }, null, 2);
// NOTE: with checkStatus being true by default, the above request only returns for successful changes
return { tenantId, state: JOB_STATE.SUCCEEDED };
}
const [location] = response.headers.raw().location;
const responseText = await response.text();
console.log("response: %s", responseText);
console.log("polling job %s with interval %isec", location, REGISTRY_JOB_POLL_FREQUENCY / 1000);

return _registryJobPoll(context, location);
const jobResult = await _registryJobPoll(context, location);

return { tenantId, jobId: jobResult.id, state: jobResult.state };
};

const _registryCallForTenants = async (context, method, options = {}) => {
const { subscriptions } = await _registrySubscriptionsPaged(context);
const updatableSubscriptions = subscriptions.filter(({ state }) => TENANT_UPDATABLE_STATES.includes(state));
const result = await limiter(
return await limiter(
regRequestConcurrency,
updatableSubscriptions,
async (subscription) => await _registryCallForTenant(context, subscription, method, options)
);
return result;
};

const registryUpdateDependencies = async (context, [tenantId], [doSkipUnchanged]) => {
assert(isUUID(tenantId), "TENANT_ID is not a uuid", tenantId);
const { subscriptions } = await _registrySubscriptionsPaged(context, tenantId);
return await _registryCallForTenant(context, subscriptions[0], "PATCH", {
skipUnchangedDependencies: doSkipUnchanged,
});
};

const registryUpdateAllDependencies = async (context, _, [doSkipUnchanged]) =>
await _registryCallForTenants(context, "PATCH", { skipUnchangedDependencies: doSkipUnchanged });

const registryUpdateApplicationURL = async (context, [tenantId]) => {
const _registryCall = async (context, method, tenantId, options) => {
let results;
if (tenantId) {
assert(isUUID(tenantId), "TENANT_ID is not a uuid", tenantId);
const { subscriptions } = await _registrySubscriptionsPaged(context, tenantId);
return await _registryCallForTenant(context, subscriptions[0], "PATCH", {
updateApplicationURL: true,
skipUpdatingDependencies: true,
doJobPoll: false,
});
assert(subscriptions.length >= 1, "could not find tenant %s", tenantId);
results = [await _registryCallForTenant(context, subscriptions[0], method, options)];
} else {
return await _registryCallForTenants(context, "PATCH", {
updateApplicationURL: true,
skipUpdatingDependencies: true,
doJobPoll: false,
});
results = await _registryCallForTenants(context, method, options);
}
assert(Array.isArray(results), "got invalid results from registry %s call with %j", method, options);
console.log(JSON.stringify(results.length === 1 ? results[0] : results, null, 2));
assert(
results.every(({ state }) => state === JOB_STATE.SUCCEEDED),
"registry %s failed for some tenant",
method
);
};

const registryOffboardSubscription = async (context, [tenantId]) => {
assert(isUUID(tenantId), "TENANT_ID is not a uuid", tenantId);
const { subscriptions } = await _registrySubscriptionsPaged(context, tenantId);
return await _registryCallForTenant(context, subscriptions[0], "DELETE");
};
const registryUpdateDependencies = async (context, [tenantId], [doSkipUnchanged]) =>
await _registryCall(context, "PATCH", tenantId, { skipUnchangedDependencies: doSkipUnchanged });

const registryOffboardSubscriptionSkip = async (context, [tenantId, skipApps]) => {
assert(isUUID(tenantId), "TENANT_ID is not a uuid", tenantId);
const { subscriptions } = await _registrySubscriptionsPaged(context, tenantId);
return await _registryCallForTenant(context, subscriptions[0], "DELETE", { noCallbacksAppNames: skipApps });
};
const registryUpdateAllDependencies = async (context, _, [doSkipUnchanged]) =>
await _registryCall(context, "PATCH", undefined, { skipUnchangedDependencies: doSkipUnchanged });

const registryUpdateApplicationURL = async (context, [tenantId]) =>
await _registryCall(context, "PATCH", tenantId, {
updateApplicationURL: true,
skipUpdatingDependencies: true,
doJobPoll: false,
});
const registryOffboardSubscription = async (context, [tenantId]) => await _registryCall(context, "DELETE", tenantId);

const registryOffboardSubscriptionSkip = async (context, [tenantId, skipApps]) =>
await _registryCall(context, "DELETE", tenantId, { noCallbacksAppNames: skipApps });

module.exports = {
registryListSubscriptions,
Expand Down
6 changes: 6 additions & 0 deletions test/__snapshots__/tenantRegistry.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ exports[`reg tests reg list paging 1`] = `
"auth": {
"token": "token",
},
"headers": {
"Accept": "application/json",
},
"pathname": "/saas-manager/v1/plan/subscriptions",
"query": {
"appName": "appName",
Expand All @@ -21,6 +24,9 @@ exports[`reg tests reg list paging 1`] = `
"auth": {
"token": "token",
},
"headers": {
"Accept": "application/json",
},
"pathname": "/saas-manager/v1/plan/subscriptions",
"query": {
"appName": "appName",
Expand Down
Loading

0 comments on commit 71470b5

Please sign in to comment.