rhdh-e2e-test-utils 1.1.7 → 1.1.8
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.
- package/dist/deployment/keycloak/deployment.js +3 -3
- package/dist/deployment/rhdh/deployment.js +2 -2
- package/dist/playwright/base-config.js +1 -1
- package/dist/utils/kubernetes-client.d.ts +11 -2
- package/dist/utils/kubernetes-client.d.ts.map +1 -1
- package/dist/utils/kubernetes-client.js +88 -8
- package/package.json +1 -1
|
@@ -293,7 +293,7 @@ export class KeycloakHelper {
|
|
|
293
293
|
/**
|
|
294
294
|
* Wait for Keycloak to be ready
|
|
295
295
|
*/
|
|
296
|
-
async waitUntilReady(timeout =
|
|
296
|
+
async waitUntilReady(timeout = 500) {
|
|
297
297
|
this._log(`Waiting for Keycloak to be ready...`);
|
|
298
298
|
const labelSelector = `app.kubernetes.io/instance=${this.deploymentConfig.releaseName}`;
|
|
299
299
|
await this.k8sClient.waitForPodsWithFailureDetection(this.deploymentConfig.namespace, labelSelector, timeout);
|
|
@@ -343,14 +343,14 @@ spec:
|
|
|
343
343
|
}
|
|
344
344
|
async _waitForKeycloak() {
|
|
345
345
|
this._log("Waiting for Keycloak API to be ready...");
|
|
346
|
-
const timeout =
|
|
346
|
+
const timeout = 500;
|
|
347
347
|
const startTime = Date.now();
|
|
348
348
|
while (true) {
|
|
349
349
|
if (await this.isRunning()) {
|
|
350
350
|
break;
|
|
351
351
|
}
|
|
352
352
|
if ((Date.now() - startTime) / 1000 >= timeout) {
|
|
353
|
-
throw new Error(
|
|
353
|
+
throw new Error(`Keycloak API not ready after ${timeout} seconds`);
|
|
354
354
|
}
|
|
355
355
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
356
356
|
this._log(" Waiting for Keycloak API to be ready...");
|
|
@@ -21,7 +21,7 @@ export class RHDHDeployment {
|
|
|
21
21
|
}
|
|
22
22
|
async deploy() {
|
|
23
23
|
this._log("Starting RHDH deployment...");
|
|
24
|
-
test.setTimeout(
|
|
24
|
+
test.setTimeout(600_000);
|
|
25
25
|
await this.k8sClient.createNamespaceIfNotExists(this.deploymentConfig.namespace);
|
|
26
26
|
await this._applyAppConfig();
|
|
27
27
|
await this._applySecrets();
|
|
@@ -173,7 +173,7 @@ export class RHDHDeployment {
|
|
|
173
173
|
await $ `oc wait --for=delete pod -l 'app.kubernetes.io/instance in (redhat-developer-hub,developer-hub),app.kubernetes.io/name!=postgresql' -n ${namespace} --timeout=120s || true`;
|
|
174
174
|
await $ `oc scale deployment -l 'app.kubernetes.io/instance in (redhat-developer-hub,developer-hub)' --replicas=1 -n ${namespace}`;
|
|
175
175
|
}
|
|
176
|
-
async waitUntilReady(timeout =
|
|
176
|
+
async waitUntilReady(timeout = 500) {
|
|
177
177
|
this._log(`Waiting for RHDH deployment to be ready in namespace ${this.deploymentConfig.namespace}...`);
|
|
178
178
|
const labelSelector = "app.kubernetes.io/instance in (redhat-developer-hub,developer-hub)";
|
|
179
179
|
try {
|
|
@@ -7,7 +7,7 @@ import { resolve } from "path";
|
|
|
7
7
|
export const baseConfig = {
|
|
8
8
|
testDir: "./tests",
|
|
9
9
|
forbidOnly: !!process.env.CI,
|
|
10
|
-
retries: process.env.CI ?
|
|
10
|
+
retries: process.env.CI ? 1 : 0,
|
|
11
11
|
workers: "50%",
|
|
12
12
|
outputDir: "node_modules/.cache/e2e-test-results",
|
|
13
13
|
timeout: 90_000,
|
|
@@ -37,9 +37,18 @@ declare class KubernetesClientHelper {
|
|
|
37
37
|
stringData?: Record<string, string>;
|
|
38
38
|
}, namespace: string): Promise<void>;
|
|
39
39
|
/**
|
|
40
|
-
* Delete a namespace
|
|
40
|
+
* Delete a namespace and wait for it to be fully terminated
|
|
41
41
|
*/
|
|
42
|
-
deleteNamespace(namespace: string): Promise<void>;
|
|
42
|
+
deleteNamespace(namespace: string, waitForDeletion?: boolean, timeoutSeconds?: number): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Wait for a namespace to be fully deleted
|
|
45
|
+
*/
|
|
46
|
+
private _waitForNamespaceDeletion;
|
|
47
|
+
/**
|
|
48
|
+
* Check if an error is a "not found" (404) error.
|
|
49
|
+
* Handles different error formats from various k8s client versions.
|
|
50
|
+
*/
|
|
51
|
+
private _isNotFoundError;
|
|
43
52
|
/**
|
|
44
53
|
* Check if a StatefulSet is ready (all replicas are available)
|
|
45
54
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kubernetes-client.d.ts","sourceRoot":"","sources":["../../src/utils/kubernetes-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,GAAG,MAAM,yBAAyB,CAAC;AAO/C;;GAEG;AACH,cAAM,sBAAsB;IAC1B,OAAO,CAAC,GAAG,CAAiB;IAC5B,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,iBAAiB,CAAuB;;IAoChD;;OAEG;IACG,uBAAuB,CAC3B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IA8C3B;;OAEG;IACG,0BAA0B,CAC9B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IA+B3B;;UAEM;IAmBN;;OAEG;IAsBH;;OAEG;YACW,YAAY;IA8B1B;;OAEG;IACG,wBAAwB,CAC5B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IA0ChB;;OAEG;IACG,qBAAqB,CACzB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,EAC7C,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IAqChB;;OAEG;IACG,eAAe,
|
|
1
|
+
{"version":3,"file":"kubernetes-client.d.ts","sourceRoot":"","sources":["../../src/utils/kubernetes-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,GAAG,MAAM,yBAAyB,CAAC;AAO/C;;GAEG;AACH,cAAM,sBAAsB;IAC1B,OAAO,CAAC,GAAG,CAAiB;IAC5B,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,iBAAiB,CAAuB;;IAoChD;;OAEG;IACG,uBAAuB,CAC3B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IA8C3B;;OAEG;IACG,0BAA0B,CAC9B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IA+B3B;;UAEM;IAmBN;;OAEG;IAsBH;;OAEG;YACW,YAAY;IA8B1B;;OAEG;IACG,wBAAwB,CAC5B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IA0ChB;;OAEG;IACG,qBAAqB,CACzB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,EAC7C,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC;IAqChB;;OAEG;IACG,eAAe,CACnB,SAAS,EAAE,MAAM,EACjB,eAAe,GAAE,OAAc,EAC/B,cAAc,GAAE,MAAY,GAC3B,OAAO,CAAC,IAAI,CAAC;IAyBhB;;OAEG;YACW,yBAAyB;IAsCvC;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IA2BxB;;OAEG;IACG,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAc3E;;OAEG;IACG,uBAAuB,CAC3B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,cAAc,GAAE,MAAY,EAC5B,cAAc,GAAE,MAAa,GAC5B,OAAO,CAAC,OAAO,CAAC;IAiBnB;;;OAGG;IACG,uBAAuB,IAAI,OAAO,CAAC,MAAM,CAAC;IAsBhD;;;;;;OAMG;IACG,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAkBxE;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAmBxB;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAQnC;IAEH;;;;;;;;OAQG;IACG,+BAA+B,CACnC,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EACrB,cAAc,GAAE,MAAY,EAC5B,cAAc,GAAE,MAAa,GAC5B,OAAO,CAAC,IAAI,CAAC;IA8FhB;;OAEG;IACH,OAAO,CAAC,gBAAgB;CA8BzB;AAED,OAAO,EAAE,sBAAsB,EAAE,CAAC"}
|
|
@@ -265,26 +265,80 @@ class KubernetesClientHelper {
|
|
|
265
265
|
}
|
|
266
266
|
}
|
|
267
267
|
/**
|
|
268
|
-
* Delete a namespace
|
|
268
|
+
* Delete a namespace and wait for it to be fully terminated
|
|
269
269
|
*/
|
|
270
|
-
async deleteNamespace(namespace) {
|
|
270
|
+
async deleteNamespace(namespace, waitForDeletion = true, timeoutSeconds = 180) {
|
|
271
271
|
try {
|
|
272
272
|
await this._k8sApi.deleteNamespace({ name: namespace });
|
|
273
|
-
console.log(
|
|
273
|
+
console.log(`[K8sHelper] Deleting namespace ${namespace}...`);
|
|
274
274
|
}
|
|
275
275
|
catch (error) {
|
|
276
276
|
// Ignore if namespace doesn't exist (already deleted), but throw other errors
|
|
277
|
-
|
|
278
|
-
if (err.body?.code === 404 ||
|
|
279
|
-
err.response?.statusCode === 404 ||
|
|
280
|
-
err.statusCode === 404) {
|
|
277
|
+
if (this._isNotFoundError(error)) {
|
|
281
278
|
console.log(`✓ Namespace ${namespace} already deleted or doesn't exist`);
|
|
279
|
+
return;
|
|
282
280
|
}
|
|
283
281
|
else {
|
|
284
282
|
console.error(`✗ Failed to delete namespace ${namespace}:`, error instanceof Error ? error.message : error);
|
|
285
283
|
throw error;
|
|
286
284
|
}
|
|
287
285
|
}
|
|
286
|
+
if (waitForDeletion) {
|
|
287
|
+
await this._waitForNamespaceDeletion(namespace, timeoutSeconds);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Wait for a namespace to be fully deleted
|
|
292
|
+
*/
|
|
293
|
+
async _waitForNamespaceDeletion(namespace, timeoutSeconds = 180) {
|
|
294
|
+
const startTime = Date.now();
|
|
295
|
+
const timeoutMs = timeoutSeconds * 1000;
|
|
296
|
+
const pollIntervalMs = 3000;
|
|
297
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
298
|
+
try {
|
|
299
|
+
const ns = await this._k8sApi.readNamespace({ name: namespace });
|
|
300
|
+
const phase = ns.status?.phase;
|
|
301
|
+
// Namespace still exists, wait and retry
|
|
302
|
+
if (phase === "Terminating") {
|
|
303
|
+
// Only log occasionally to avoid spam
|
|
304
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
305
|
+
if (elapsed % 10 === 0) {
|
|
306
|
+
console.log(`[K8sHelper] Namespace ${namespace} still terminating (${elapsed}s)...`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
// Check for 404 in various error formats from different k8s client versions
|
|
313
|
+
if (this._isNotFoundError(error)) {
|
|
314
|
+
console.log(`✓ Namespace ${namespace} fully deleted`);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
throw error;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
throw new Error(`Timeout waiting for namespace ${namespace} to be deleted after ${timeoutSeconds}s`);
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Check if an error is a "not found" (404) error.
|
|
324
|
+
* Handles different error formats from various k8s client versions.
|
|
325
|
+
*/
|
|
326
|
+
_isNotFoundError(error) {
|
|
327
|
+
if (!error)
|
|
328
|
+
return false;
|
|
329
|
+
// Check error message for "404" or "not found"
|
|
330
|
+
if (error instanceof Error) {
|
|
331
|
+
const msg = error.message.toLowerCase();
|
|
332
|
+
if (msg.includes("404") || msg.includes("not found")) {
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// Check various object properties for 404 status codes
|
|
337
|
+
const err = error;
|
|
338
|
+
return (err.body?.code === 404 ||
|
|
339
|
+
err.response?.statusCode === 404 ||
|
|
340
|
+
err.statusCode === 404 ||
|
|
341
|
+
err.code === 404);
|
|
288
342
|
}
|
|
289
343
|
/**
|
|
290
344
|
* Check if a StatefulSet is ready (all replicas are available)
|
|
@@ -397,7 +451,7 @@ class KubernetesClientHelper {
|
|
|
397
451
|
* @param timeoutSeconds - Maximum time to wait (default: 300)
|
|
398
452
|
* @param pollIntervalMs - How often to check pod status (default: 5000)
|
|
399
453
|
*/
|
|
400
|
-
async waitForPodsWithFailureDetection(namespace, labelSelector, timeoutSeconds =
|
|
454
|
+
async waitForPodsWithFailureDetection(namespace, labelSelector, timeoutSeconds = 500, pollIntervalMs = 5000) {
|
|
401
455
|
const startTime = Date.now();
|
|
402
456
|
const timeoutMs = timeoutSeconds * 1000;
|
|
403
457
|
console.log(`[K8sHelper] Waiting for pods (${labelSelector}) in ${namespace}...`);
|
|
@@ -443,8 +497,34 @@ class KubernetesClientHelper {
|
|
|
443
497
|
console.log(`[K8sHelper] All ${pods.length} pod(s) ready in ${namespace}`);
|
|
444
498
|
return;
|
|
445
499
|
}
|
|
500
|
+
// Log pod status every 20 seconds
|
|
501
|
+
const elapsedSec = Math.floor((Date.now() - startTime) / 1000);
|
|
502
|
+
if (elapsedSec > 0 && elapsedSec % 20 === 0) {
|
|
503
|
+
try {
|
|
504
|
+
await $ `oc get pods -n ${namespace} -l ${labelSelector}`;
|
|
505
|
+
}
|
|
506
|
+
catch {
|
|
507
|
+
// Ignore errors
|
|
508
|
+
}
|
|
509
|
+
}
|
|
446
510
|
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
447
511
|
}
|
|
512
|
+
// Timeout reached - collect diagnostic info before throwing
|
|
513
|
+
console.log(`\n[K8sHelper] ═══ Pod Diagnostics (timeout reached) ═══`);
|
|
514
|
+
try {
|
|
515
|
+
console.log(`\n[K8sHelper] ─── Pod Status ───`);
|
|
516
|
+
await $ `oc get pods -n ${namespace} -l ${labelSelector} -o wide`;
|
|
517
|
+
console.log(`\n[K8sHelper] ─── Pod Details ───`);
|
|
518
|
+
await $ `oc describe pods -n ${namespace} -l ${labelSelector}`;
|
|
519
|
+
console.log(`\n[K8sHelper] ─── Namespace Events ───`);
|
|
520
|
+
await $ `oc get events -n ${namespace} --sort-by='.lastTimestamp'`;
|
|
521
|
+
console.log(`\n[K8sHelper] ─── Pod Logs ───`);
|
|
522
|
+
await $ `oc logs -n ${namespace} -l ${labelSelector} --all-containers --tail=100 2>&1 || true`;
|
|
523
|
+
}
|
|
524
|
+
catch {
|
|
525
|
+
// Ignore errors from diagnostic commands
|
|
526
|
+
}
|
|
527
|
+
console.log(`\n[K8sHelper] ═══ End Pod Diagnostics ═══\n`);
|
|
448
528
|
throw new Error(`Timeout waiting for pods (${labelSelector}) after ${timeoutSeconds}s`);
|
|
449
529
|
}
|
|
450
530
|
/**
|