@camunda/e2e-test-suite 0.0.1066 → 0.0.1068
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -32,8 +32,7 @@ _8_10_1.test.describe('Cluster Setup Tests', () => {
|
|
|
32
32
|
await clusterPage.assertClusterHealthyStatusWithRetry(clusterName, 120000, 1500000);
|
|
33
33
|
}
|
|
34
34
|
else {
|
|
35
|
-
await (0, consoleApiHelpers_1.
|
|
36
|
-
const clusterUuid = await (0, consoleApiHelpers_1.createClusterViaApi)(page, clusterName);
|
|
35
|
+
const clusterUuid = await (0, consoleApiHelpers_1.ensureClusterViaApi)(page, clusterName);
|
|
37
36
|
await (0, consoleApiHelpers_1.waitForClusterHealthyViaApi)(page, clusterUuid);
|
|
38
37
|
await homePage.clickClusters();
|
|
39
38
|
}
|
|
@@ -63,8 +62,7 @@ _8_10_1.test.describe('Cluster Setup Tests', () => {
|
|
|
63
62
|
const apiClientName = 'Test_API_Client' + (await (0, _setup_1.generateRandomStringAsync)(3));
|
|
64
63
|
const mcpClientName = 'MCP_Test_API_Client' + (await (0, _setup_1.generateRandomStringAsync)(3));
|
|
65
64
|
await (0, UtilitiesPage_1.loginWithRetry)(page, loginPage, users_1.testUsers.mainUser, (testInfo.workerIndex + 1) * 1000);
|
|
66
|
-
await (0, consoleApiHelpers_1.
|
|
67
|
-
const clusterUuid = await (0, consoleApiHelpers_1.createClusterViaApi)(page, clusterName);
|
|
65
|
+
const clusterUuid = await (0, consoleApiHelpers_1.ensureClusterViaApi)(page, clusterName);
|
|
68
66
|
await (0, consoleApiHelpers_1.waitForClusterHealthyViaApi)(page, clusterUuid, 1500000);
|
|
69
67
|
await homePage.clickClusters();
|
|
70
68
|
await clusterPage.clickClusterLink(clusterName);
|
|
@@ -99,8 +97,7 @@ _8_10_1.test.describe('Cluster Setup Tests', () => {
|
|
|
99
97
|
await clusterPage.assertClusterHealthyStatusWithRetry(clusterName, 120000, 1500000);
|
|
100
98
|
}
|
|
101
99
|
else {
|
|
102
|
-
await (0, consoleApiHelpers_1.
|
|
103
|
-
const clusterUuid = await (0, consoleApiHelpers_1.createClusterViaApi)(page, clusterName, 'AWS');
|
|
100
|
+
const clusterUuid = await (0, consoleApiHelpers_1.ensureClusterViaApi)(page, clusterName, 'AWS');
|
|
104
101
|
await (0, consoleApiHelpers_1.waitForClusterHealthyViaApi)(page, clusterUuid, 1500000);
|
|
105
102
|
await homePage.clickClusters();
|
|
106
103
|
}
|
|
@@ -64,18 +64,42 @@ async function getUserToken(page) {
|
|
|
64
64
|
const TRANSIENT_API_STATUSES = [429, 500, 502, 503, 504];
|
|
65
65
|
const API_MAX_ATTEMPTS = 5;
|
|
66
66
|
// Overrides the global actionTimeout (10s) for Console API calls only: cluster
|
|
67
|
-
//
|
|
68
|
-
const
|
|
67
|
+
// listings answer in tens of milliseconds, so 15s is generous for a read.
|
|
68
|
+
const API_READ_TIMEOUT_MS = 15000;
|
|
69
|
+
// Cluster mutations are an order of magnitude slower than reads on Console:
|
|
70
|
+
// POST /clusters was measured at 8-12s and DELETE at 7-9s against INT. A 15s
|
|
71
|
+
// ceiling sat inside that band, so the client aborted requests the server went
|
|
72
|
+
// on to complete — leaving a cluster nobody was tracking.
|
|
73
|
+
const API_WRITE_TIMEOUT_MS = 45000;
|
|
74
|
+
// POST is the only non-idempotent verb used here: repeating it creates a second
|
|
75
|
+
// cluster under the same name. Let its timeout surface instead, so the caller
|
|
76
|
+
// can recover by looking the cluster up by name (see createClusterViaApi).
|
|
77
|
+
const TIMEOUT_RETRYABLE_METHODS = ['GET', 'PATCH', 'DELETE'];
|
|
69
78
|
async function clusterApiRequest(page, method, url, data) {
|
|
70
79
|
let response;
|
|
71
80
|
for (let attempt = 1; attempt <= API_MAX_ATTEMPTS; attempt++) {
|
|
72
81
|
const token = await getUserToken(page);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
82
|
+
try {
|
|
83
|
+
response = await page.request.fetch(url, {
|
|
84
|
+
method,
|
|
85
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
86
|
+
timeout: method === 'GET' ? API_READ_TIMEOUT_MS : API_WRITE_TIMEOUT_MS,
|
|
87
|
+
...(data === undefined ? {} : { data }),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
// A timeout or transport error throws rather than returning a status, so
|
|
92
|
+
// the status-based retry below never covered it and API_MAX_ATTEMPTS did
|
|
93
|
+
// not apply to the one failure mode that actually happens.
|
|
94
|
+
console.log(`Console API ${method} ${url} threw ${error.message} ` +
|
|
95
|
+
`(attempt ${attempt}/${API_MAX_ATTEMPTS})`);
|
|
96
|
+
if (!TIMEOUT_RETRYABLE_METHODS.includes(method) ||
|
|
97
|
+
attempt === API_MAX_ATTEMPTS) {
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
await (0, sleep_1.sleep)(attempt * 10000);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
79
103
|
if (!TRANSIENT_API_STATUSES.includes(response.status()))
|
|
80
104
|
return response;
|
|
81
105
|
console.log(`Console API ${method} ${url} returned HTTP ${response.status()} ` +
|
|
@@ -113,8 +137,22 @@ async function findClusterByName(page, clusterName) {
|
|
|
113
137
|
: data.clusters ?? [];
|
|
114
138
|
return clusters.find((c) => c.name === clusterName);
|
|
115
139
|
}
|
|
140
|
+
// A cluster is not always listable the instant its create lands, so poll a few
|
|
141
|
+
// times before concluding a timed-out create really did not happen.
|
|
142
|
+
const CLUSTER_RECOVERY_ATTEMPTS = 6;
|
|
143
|
+
const CLUSTER_RECOVERY_INTERVAL_MS = 5000;
|
|
144
|
+
async function findClusterByNameWithRetry(page, clusterName) {
|
|
145
|
+
for (let attempt = 1; attempt <= CLUSTER_RECOVERY_ATTEMPTS; attempt++) {
|
|
146
|
+
const cluster = await findClusterByName(page, clusterName);
|
|
147
|
+
if (cluster)
|
|
148
|
+
return cluster;
|
|
149
|
+
if (attempt < CLUSTER_RECOVERY_ATTEMPTS) {
|
|
150
|
+
await (0, sleep_1.sleep)(CLUSTER_RECOVERY_INTERVAL_MS);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
116
155
|
async function deleteClusterViaApi(page, clusterName) {
|
|
117
|
-
const token = await getUserToken(page);
|
|
118
156
|
const orgId = getOrgId();
|
|
119
157
|
const deadline = Date.now() + 180000;
|
|
120
158
|
// Re-deleting a cluster that is already terminating can answer 409/400.
|
|
@@ -127,8 +165,14 @@ async function deleteClusterViaApi(page, clusterName) {
|
|
|
127
165
|
if (!cluster)
|
|
128
166
|
return;
|
|
129
167
|
if (!deleteIssued.has(cluster.uuid)) {
|
|
130
|
-
|
|
131
|
-
|
|
168
|
+
// Routed through clusterApiRequest for the write timeout and retries: a
|
|
169
|
+
// bare page.request.delete inherits the 10s actionTimeout, which DELETE
|
|
170
|
+
// /clusters (7-9s at INT) can exceed.
|
|
171
|
+
const response = await clusterApiRequest(page, 'DELETE', `${getConsoleBaseUrl()}/api/orgs/${orgId}/clusters/${cluster.uuid}`);
|
|
172
|
+
// 409 is the retry landing on a cluster the first attempt already put
|
|
173
|
+
// into termination -- the outcome this asked for, not a failure.
|
|
174
|
+
const alreadyTerminating = [404, 409].includes(response.status());
|
|
175
|
+
if (!response.ok() && !alreadyTerminating) {
|
|
132
176
|
throw new Error(`Failed to delete cluster "${clusterName}" (${cluster.uuid}): ` +
|
|
133
177
|
`HTTP ${response.status()} - ${await response.text()}`);
|
|
134
178
|
}
|
|
@@ -315,23 +359,40 @@ async function createClusterViaApi(page, clusterName, region = 'GCP', tasklistV2
|
|
|
315
359
|
generationId = resolveGenerationId();
|
|
316
360
|
}
|
|
317
361
|
const k8sContextId = region.toUpperCase() === 'AWS' ? AWS_REGION_ID : GCP_REGION_ID;
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
362
|
+
let createResponse;
|
|
363
|
+
try {
|
|
364
|
+
createResponse = await clusterApiRequest(page, 'POST', `${getConsoleBaseUrl()}/api/orgs/${orgId}/clusters`, {
|
|
365
|
+
name: clusterName,
|
|
366
|
+
planTypeId: PLAN_TYPE_ID,
|
|
367
|
+
channelId,
|
|
368
|
+
generationId,
|
|
369
|
+
k8sContextId,
|
|
370
|
+
autoUpdate: true,
|
|
371
|
+
stageLabel: 'dev',
|
|
372
|
+
// Always sent explicitly rather than omitted: Console defaults an unset
|
|
373
|
+
// field to v2-enabled for any generation >= 8.8, and the switch to v2 is
|
|
374
|
+
// one-way -- a cluster that ends up v2-enabled can never be brought back
|
|
375
|
+
// to v1 for the rest of its life. v2 cannot run job-worker-based user
|
|
376
|
+
// tasks, draft variables, or public start forms, so the v1 leg
|
|
377
|
+
// (@tasklistV1-tagged suites) depends on this being false, and the v2 leg
|
|
378
|
+
// equally depends on it being true.
|
|
379
|
+
tasklistV2Enabled,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
// Console does not cancel the create when the client stops waiting: it
|
|
384
|
+
// answers /clusters slower than the request timeout while still creating
|
|
385
|
+
// the cluster. Adopt what the timed-out POST made instead of re-POSTing,
|
|
386
|
+
// which would leave two clusters under the same name and break
|
|
387
|
+
// clickClusterLink in strict mode.
|
|
388
|
+
const recovered = await findClusterByNameWithRetry(page, clusterName);
|
|
389
|
+
if (!recovered)
|
|
390
|
+
throw error;
|
|
391
|
+
console.log(`Cluster "${clusterName}" create timed out but the cluster exists: ` +
|
|
392
|
+
`clusterId=${recovered.uuid} namespace=${recovered.uuid}-zeebe ` +
|
|
393
|
+
`orgId=${orgId} tasklistMode=${tasklistV2Enabled ? 'v2' : 'v1'}`);
|
|
394
|
+
return recovered.uuid;
|
|
395
|
+
}
|
|
335
396
|
if (createResponse.status() !== 200 && createResponse.status() !== 201) {
|
|
336
397
|
throw new Error(`Failed to create cluster "${clusterName}": ` +
|
|
337
398
|
`HTTP ${createResponse.status()} - ${await createResponse.text()}`);
|