@aztec/end-to-end 0.0.1-commit.c80b6263 → 0.0.1-commit.cb6bed7c2
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/dest/bench/client_flows/client_flows_benchmark.d.ts +2 -2
- package/dest/bench/client_flows/client_flows_benchmark.d.ts.map +1 -1
- package/dest/bench/client_flows/client_flows_benchmark.js +19 -11
- package/dest/bench/client_flows/config.d.ts +2 -2
- package/dest/bench/client_flows/config.d.ts.map +1 -1
- package/dest/bench/client_flows/config.js +18 -0
- package/dest/bench/utils.d.ts +1 -1
- package/dest/bench/utils.d.ts.map +1 -1
- package/dest/bench/utils.js +6 -3
- package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.d.ts +4 -2
- package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.d.ts.map +1 -1
- package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.js +22 -12
- package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.d.ts +2 -2
- package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.d.ts.map +1 -1
- package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.js +3 -2
- package/dest/e2e_deploy_contract/deploy_test.d.ts +2 -2
- package/dest/e2e_deploy_contract/deploy_test.d.ts.map +1 -1
- package/dest/e2e_epochs/epochs_test.d.ts +9 -7
- package/dest/e2e_epochs/epochs_test.d.ts.map +1 -1
- package/dest/e2e_epochs/epochs_test.js +58 -33
- package/dest/e2e_fees/fees_test.d.ts +2 -2
- package/dest/e2e_fees/fees_test.d.ts.map +1 -1
- package/dest/e2e_fees/fees_test.js +18 -11
- package/dest/e2e_l1_publisher/write_json.d.ts +3 -2
- package/dest/e2e_l1_publisher/write_json.d.ts.map +1 -1
- package/dest/e2e_l1_publisher/write_json.js +1 -7
- package/dest/e2e_nested_contract/nested_contract_test.d.ts +1 -1
- package/dest/e2e_nested_contract/nested_contract_test.d.ts.map +1 -1
- package/dest/e2e_nested_contract/nested_contract_test.js +4 -6
- package/dest/e2e_p2p/p2p_network.d.ts +5 -4
- package/dest/e2e_p2p/p2p_network.d.ts.map +1 -1
- package/dest/e2e_p2p/p2p_network.js +24 -7
- package/dest/e2e_p2p/reqresp/utils.d.ts +22 -0
- package/dest/e2e_p2p/reqresp/utils.d.ts.map +1 -0
- package/dest/e2e_p2p/reqresp/utils.js +177 -0
- package/dest/e2e_p2p/shared.d.ts +16 -2
- package/dest/e2e_p2p/shared.d.ts.map +1 -1
- package/dest/e2e_p2p/shared.js +30 -3
- package/dest/e2e_storage_proof/fixtures/storage_proof_fetcher.d.ts +2 -0
- package/dest/e2e_storage_proof/fixtures/storage_proof_fetcher.d.ts.map +1 -0
- package/dest/e2e_storage_proof/fixtures/storage_proof_fetcher.js +184 -0
- package/dest/e2e_storage_proof/fixtures/storage_proof_fixture.d.ts +18 -0
- package/dest/e2e_storage_proof/fixtures/storage_proof_fixture.d.ts.map +1 -0
- package/dest/e2e_storage_proof/fixtures/storage_proof_fixture.js +120 -0
- package/dest/e2e_token_contract/token_contract_test.d.ts +4 -2
- package/dest/e2e_token_contract/token_contract_test.d.ts.map +1 -1
- package/dest/e2e_token_contract/token_contract_test.js +19 -9
- package/dest/fixtures/authwit_proxy.d.ts +15 -0
- package/dest/fixtures/authwit_proxy.d.ts.map +1 -0
- package/dest/fixtures/authwit_proxy.js +30 -0
- package/dest/fixtures/e2e_prover_test.d.ts +4 -4
- package/dest/fixtures/e2e_prover_test.d.ts.map +1 -1
- package/dest/fixtures/e2e_prover_test.js +33 -40
- package/dest/fixtures/elu_monitor.d.ts +21 -0
- package/dest/fixtures/elu_monitor.d.ts.map +1 -0
- package/dest/fixtures/elu_monitor.js +102 -0
- package/dest/fixtures/fixtures.d.ts +5 -1
- package/dest/fixtures/fixtures.d.ts.map +1 -1
- package/dest/fixtures/fixtures.js +6 -0
- package/dest/fixtures/ha_setup.d.ts +71 -0
- package/dest/fixtures/ha_setup.d.ts.map +1 -0
- package/dest/fixtures/ha_setup.js +116 -0
- package/dest/fixtures/index.d.ts +2 -1
- package/dest/fixtures/index.d.ts.map +1 -1
- package/dest/fixtures/index.js +1 -0
- package/dest/fixtures/setup.d.ts +39 -23
- package/dest/fixtures/setup.d.ts.map +1 -1
- package/dest/fixtures/setup.js +62 -152
- package/dest/fixtures/setup_p2p_test.d.ts +15 -7
- package/dest/fixtures/setup_p2p_test.d.ts.map +1 -1
- package/dest/fixtures/setup_p2p_test.js +18 -12
- package/dest/fixtures/token_utils.d.ts +2 -2
- package/dest/fixtures/token_utils.d.ts.map +1 -1
- package/dest/fixtures/token_utils.js +5 -4
- package/dest/shared/cross_chain_test_harness.d.ts +1 -1
- package/dest/shared/cross_chain_test_harness.d.ts.map +1 -1
- package/dest/shared/cross_chain_test_harness.js +13 -13
- package/dest/shared/gas_portal_test_harness.js +2 -2
- package/dest/shared/index.d.ts +2 -1
- package/dest/shared/index.d.ts.map +1 -1
- package/dest/shared/index.js +1 -0
- package/dest/shared/jest_setup.js +41 -1
- package/dest/shared/mock_state_view.d.ts +86 -0
- package/dest/shared/mock_state_view.d.ts.map +1 -0
- package/dest/shared/mock_state_view.js +186 -0
- package/dest/shared/submit-transactions.d.ts +2 -2
- package/dest/shared/submit-transactions.d.ts.map +1 -1
- package/dest/shared/submit-transactions.js +1 -1
- package/dest/shared/uniswap_l1_l2.d.ts +1 -1
- package/dest/shared/uniswap_l1_l2.d.ts.map +1 -1
- package/dest/shared/uniswap_l1_l2.js +13 -11
- package/dest/simulators/lending_simulator.d.ts +1 -1
- package/dest/simulators/lending_simulator.d.ts.map +1 -1
- package/dest/simulators/lending_simulator.js +2 -2
- package/dest/simulators/token_simulator.d.ts +1 -1
- package/dest/simulators/token_simulator.d.ts.map +1 -1
- package/dest/simulators/token_simulator.js +3 -24
- package/dest/spartan/setup_test_wallets.d.ts +9 -2
- package/dest/spartan/setup_test_wallets.d.ts.map +1 -1
- package/dest/spartan/setup_test_wallets.js +35 -4
- package/dest/spartan/tx_metrics.js +1 -1
- package/dest/spartan/utils/bot.d.ts +3 -2
- package/dest/spartan/utils/bot.d.ts.map +1 -1
- package/dest/spartan/utils/bot.js +2 -1
- package/dest/spartan/utils/config.d.ts +7 -1
- package/dest/spartan/utils/config.d.ts.map +1 -1
- package/dest/spartan/utils/config.js +3 -1
- package/dest/spartan/utils/index.d.ts +5 -4
- package/dest/spartan/utils/index.d.ts.map +1 -1
- package/dest/spartan/utils/index.js +4 -2
- package/dest/spartan/utils/k8s.d.ts +31 -1
- package/dest/spartan/utils/k8s.d.ts.map +1 -1
- package/dest/spartan/utils/k8s.js +124 -0
- package/dest/spartan/utils/nodes.d.ts +14 -5
- package/dest/spartan/utils/nodes.d.ts.map +1 -1
- package/dest/spartan/utils/nodes.js +204 -33
- package/dest/spartan/utils/scripts.d.ts +18 -4
- package/dest/spartan/utils/scripts.d.ts.map +1 -1
- package/dest/spartan/utils/scripts.js +19 -4
- package/dest/test-wallet/test_wallet.d.ts +83 -0
- package/dest/test-wallet/test_wallet.d.ts.map +1 -0
- package/dest/test-wallet/test_wallet.js +214 -0
- package/dest/test-wallet/utils.d.ts +41 -0
- package/dest/test-wallet/utils.d.ts.map +1 -0
- package/dest/test-wallet/utils.js +71 -0
- package/dest/test-wallet/wallet_worker_script.d.ts +2 -0
- package/dest/test-wallet/wallet_worker_script.d.ts.map +1 -0
- package/dest/test-wallet/wallet_worker_script.js +48 -0
- package/dest/test-wallet/worker_wallet.d.ts +52 -0
- package/dest/test-wallet/worker_wallet.d.ts.map +1 -0
- package/dest/test-wallet/worker_wallet.js +151 -0
- package/dest/test-wallet/worker_wallet_schema.d.ts +274 -0
- package/dest/test-wallet/worker_wallet_schema.d.ts.map +1 -0
- package/dest/test-wallet/worker_wallet_schema.js +10 -0
- package/package.json +46 -43
- package/src/bench/client_flows/client_flows_benchmark.ts +42 -38
- package/src/bench/client_flows/config.ts +9 -1
- package/src/bench/utils.ts +8 -3
- package/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts +28 -15
- package/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts +7 -8
- package/src/e2e_deploy_contract/deploy_test.ts +3 -3
- package/src/e2e_epochs/epochs_test.ts +73 -63
- package/src/e2e_fees/bridging_race.notest.ts +1 -1
- package/src/e2e_fees/fees_test.ts +20 -28
- package/src/e2e_l1_publisher/write_json.ts +1 -6
- package/src/e2e_nested_contract/nested_contract_test.ts +7 -5
- package/src/e2e_p2p/inactivity_slash_test.ts +4 -4
- package/src/e2e_p2p/p2p_network.ts +36 -10
- package/src/e2e_p2p/reqresp/utils.ts +235 -0
- package/src/e2e_p2p/shared.ts +48 -3
- package/src/e2e_storage_proof/fixtures/storage_proof.json +915 -0
- package/src/e2e_storage_proof/fixtures/storage_proof_fetcher.ts +190 -0
- package/src/e2e_storage_proof/fixtures/storage_proof_fixture.ts +173 -0
- package/src/e2e_token_contract/token_contract_test.ts +26 -9
- package/src/fixtures/authwit_proxy.ts +50 -0
- package/src/fixtures/dumps/epoch_proof_result.json +1 -1
- package/src/fixtures/e2e_prover_test.ts +39 -43
- package/src/fixtures/elu_monitor.ts +126 -0
- package/src/fixtures/fixtures.ts +10 -0
- package/src/fixtures/ha_setup.ts +186 -0
- package/src/fixtures/index.ts +1 -0
- package/src/fixtures/setup.ts +94 -214
- package/src/fixtures/setup_p2p_test.ts +25 -24
- package/src/fixtures/token_utils.ts +6 -3
- package/src/guides/up_quick_start.sh +3 -3
- package/src/shared/cross_chain_test_harness.ts +13 -9
- package/src/shared/gas_portal_test_harness.ts +1 -1
- package/src/shared/index.ts +1 -0
- package/src/shared/jest_setup.ts +51 -1
- package/src/shared/mock_state_view.ts +188 -0
- package/src/shared/submit-transactions.ts +3 -2
- package/src/shared/uniswap_l1_l2.ts +15 -15
- package/src/simulators/lending_simulator.ts +4 -2
- package/src/simulators/token_simulator.ts +6 -30
- package/src/spartan/setup_test_wallets.ts +59 -12
- package/src/spartan/tx_metrics.ts +1 -1
- package/src/spartan/utils/bot.ts +4 -1
- package/src/spartan/utils/config.ts +2 -0
- package/src/spartan/utils/index.ts +7 -1
- package/src/spartan/utils/k8s.ts +160 -0
- package/src/spartan/utils/nodes.ts +251 -31
- package/src/spartan/utils/scripts.ts +43 -7
- package/src/test-wallet/test_wallet.ts +306 -0
- package/src/test-wallet/utils.ts +112 -0
- package/src/test-wallet/wallet_worker_script.ts +60 -0
- package/src/test-wallet/worker_wallet.ts +213 -0
- package/src/test-wallet/worker_wallet_schema.ts +13 -0
package/src/spartan/utils/bot.ts
CHANGED
|
@@ -34,7 +34,8 @@ export async function installTransferBot({
|
|
|
34
34
|
logger: log,
|
|
35
35
|
replicas = 1,
|
|
36
36
|
txIntervalSeconds = 10,
|
|
37
|
-
followChain = '
|
|
37
|
+
followChain = 'CHECKPOINTED',
|
|
38
|
+
pxeSyncChainTip = 'proposed',
|
|
38
39
|
mnemonic = process.env.LABS_INFRA_MNEMONIC ?? 'test test test test test test test test test test test junk',
|
|
39
40
|
mnemonicStartIndex,
|
|
40
41
|
botPrivateKey = process.env.BOT_TRANSFERS_L2_PRIVATE_KEY ?? '0xcafe01',
|
|
@@ -49,6 +50,7 @@ export async function installTransferBot({
|
|
|
49
50
|
replicas?: number;
|
|
50
51
|
txIntervalSeconds?: number;
|
|
51
52
|
followChain?: string;
|
|
53
|
+
pxeSyncChainTip?: string;
|
|
52
54
|
mnemonic?: string;
|
|
53
55
|
mnemonicStartIndex?: number | string;
|
|
54
56
|
botPrivateKey?: string;
|
|
@@ -67,6 +69,7 @@ export async function installTransferBot({
|
|
|
67
69
|
'bot.replicaCount': replicas,
|
|
68
70
|
'bot.txIntervalSeconds': txIntervalSeconds,
|
|
69
71
|
'bot.followChain': followChain,
|
|
72
|
+
'bot.pxeSyncChainTip': pxeSyncChainTip,
|
|
70
73
|
'bot.botPrivateKey': botPrivateKey,
|
|
71
74
|
'bot.nodeUrl': resolvedNodeUrl,
|
|
72
75
|
'bot.mnemonic': mnemonic,
|
|
@@ -15,6 +15,8 @@ const testConfigSchema = z.object({
|
|
|
15
15
|
AZTEC_EPOCH_DURATION: z.coerce.number().optional().default(32),
|
|
16
16
|
AZTEC_PROOF_SUBMISSION_WINDOW: z.coerce.number().optional().default(5),
|
|
17
17
|
AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET: z.coerce.number().optional().default(2),
|
|
18
|
+
FUNDING_PRIVATE_KEY: z.string().optional(),
|
|
19
|
+
AZTEC_ADMIN_API_KEY: z.string().optional(),
|
|
18
20
|
});
|
|
19
21
|
|
|
20
22
|
export type TestConfig = z.infer<typeof testConfigSchema>;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
export { type TestConfig, setupEnvironment } from './config.js';
|
|
6
6
|
|
|
7
7
|
// Scripts
|
|
8
|
-
export { getGitProjectRoot, getAztecBin, runAztecBin, runProjectScript } from './scripts.js';
|
|
8
|
+
export { type ScriptResult, getGitProjectRoot, getAztecBin, runAztecBin, runProjectScript } from './scripts.js';
|
|
9
9
|
|
|
10
10
|
// K8s operations
|
|
11
11
|
export {
|
|
@@ -24,6 +24,8 @@ export {
|
|
|
24
24
|
getServiceEndpoint,
|
|
25
25
|
getRPCEndpoint,
|
|
26
26
|
getEthereumEndpoint,
|
|
27
|
+
createResilientPrometheusConnection,
|
|
28
|
+
scaleProverAgents,
|
|
27
29
|
} from './k8s.js';
|
|
28
30
|
|
|
29
31
|
// Chaos Mesh
|
|
@@ -39,12 +41,16 @@ export {
|
|
|
39
41
|
applyNetworkShaping,
|
|
40
42
|
} from './chaos.js';
|
|
41
43
|
|
|
44
|
+
// Helm
|
|
45
|
+
export { hasDeployedHelmRelease } from './helm.js';
|
|
46
|
+
|
|
42
47
|
// Bot management
|
|
43
48
|
export { restartBot, installTransferBot, uninstallTransferBot } from './bot.js';
|
|
44
49
|
|
|
45
50
|
// Node operations (sequencers, validators, pods)
|
|
46
51
|
export {
|
|
47
52
|
awaitCheckpointNumber,
|
|
53
|
+
waitForProvenToAdvance,
|
|
48
54
|
getSequencers,
|
|
49
55
|
updateSequencersConfig,
|
|
50
56
|
getSequencersConfig,
|
package/src/spartan/utils/k8s.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createLogger } from '@aztec/aztec.js/log';
|
|
2
|
+
import type { Logger } from '@aztec/foundation/log';
|
|
2
3
|
import { promiseWithResolvers } from '@aztec/foundation/promise';
|
|
3
4
|
import { retryUntil } from '@aztec/foundation/retry';
|
|
4
5
|
|
|
@@ -6,6 +7,8 @@ import { type ChildProcess, exec, spawn } from 'child_process';
|
|
|
6
7
|
import path from 'path';
|
|
7
8
|
import { promisify } from 'util';
|
|
8
9
|
|
|
10
|
+
import { AlertTriggeredError, GrafanaClient } from '../../quality_of_service/grafana_client.js';
|
|
11
|
+
|
|
9
12
|
const execAsync = promisify(exec);
|
|
10
13
|
|
|
11
14
|
const logger = createLogger('e2e:k8s-utils');
|
|
@@ -370,6 +373,163 @@ export async function waitForResourcesByName({
|
|
|
370
373
|
);
|
|
371
374
|
}
|
|
372
375
|
|
|
376
|
+
/**
|
|
377
|
+
* Waits for all StatefulSets matching a label to have all their replicas ready.
|
|
378
|
+
*
|
|
379
|
+
* @param namespace - Kubernetes namespace
|
|
380
|
+
* @param label - Label selector for StatefulSets (e.g., "app.kubernetes.io/component=sequencer-node")
|
|
381
|
+
* @param timeoutSeconds - Maximum time to wait in seconds
|
|
382
|
+
* @param pollIntervalSeconds - How often to check status
|
|
383
|
+
*/
|
|
384
|
+
export async function waitForStatefulSetsReady({
|
|
385
|
+
namespace,
|
|
386
|
+
label,
|
|
387
|
+
timeoutSeconds = 600,
|
|
388
|
+
pollIntervalSeconds = 5,
|
|
389
|
+
}: {
|
|
390
|
+
namespace: string;
|
|
391
|
+
label: string;
|
|
392
|
+
timeoutSeconds?: number;
|
|
393
|
+
pollIntervalSeconds?: number;
|
|
394
|
+
}): Promise<void> {
|
|
395
|
+
logger.info(`Waiting for StatefulSets with label ${label} to have all replicas ready (timeout: ${timeoutSeconds}s)`);
|
|
396
|
+
|
|
397
|
+
await retryUntil(
|
|
398
|
+
async () => {
|
|
399
|
+
// Get all StatefulSets matching the label
|
|
400
|
+
const getCmd = `kubectl get statefulset -l ${label} -n ${namespace} -o json`;
|
|
401
|
+
const { stdout } = await execAsync(getCmd);
|
|
402
|
+
const result = JSON.parse(stdout);
|
|
403
|
+
|
|
404
|
+
if (!result.items || result.items.length === 0) {
|
|
405
|
+
logger.verbose(`No StatefulSets found with label ${label}`);
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Check each StatefulSet
|
|
410
|
+
for (const sts of result.items) {
|
|
411
|
+
const name = sts.metadata.name;
|
|
412
|
+
const desired = sts.spec.replicas ?? 0;
|
|
413
|
+
const ready = sts.status.readyReplicas ?? 0;
|
|
414
|
+
const updated = sts.status.updatedReplicas ?? 0;
|
|
415
|
+
|
|
416
|
+
if (ready < desired || updated < desired) {
|
|
417
|
+
logger.verbose(`StatefulSet ${name}: ${ready}/${desired} ready, ${updated}/${desired} updated`);
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
logger.info(`All StatefulSets with label ${label} are ready`);
|
|
423
|
+
return true;
|
|
424
|
+
},
|
|
425
|
+
`StatefulSets with label ${label} to be ready`,
|
|
426
|
+
timeoutSeconds,
|
|
427
|
+
pollIntervalSeconds,
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Creates a Prometheus connection that can re-establish port-forward on failure.
|
|
433
|
+
* Returns functions to connect and run alert checks that automatically reconnect if needed.
|
|
434
|
+
*
|
|
435
|
+
* @param namespace - K8s namespace to fall back to if metrics namespace doesn't have Prometheus
|
|
436
|
+
* @param endpoints - Array to track created endpoints for cleanup
|
|
437
|
+
* @param log - Logger instance
|
|
438
|
+
*/
|
|
439
|
+
export function createResilientPrometheusConnection(
|
|
440
|
+
namespace: string,
|
|
441
|
+
endpoints: ServiceEndpoint[],
|
|
442
|
+
log: Logger,
|
|
443
|
+
): {
|
|
444
|
+
connect: () => Promise<GrafanaClient>;
|
|
445
|
+
runAlertCheck: (alerts: Parameters<GrafanaClient['runAlertCheck']>[0]) => Promise<void>;
|
|
446
|
+
} {
|
|
447
|
+
let alertChecker: GrafanaClient | undefined;
|
|
448
|
+
let currentEndpoint: ServiceEndpoint | undefined;
|
|
449
|
+
|
|
450
|
+
const connect = async (): Promise<GrafanaClient> => {
|
|
451
|
+
// Kill existing connection if any
|
|
452
|
+
if (currentEndpoint?.process) {
|
|
453
|
+
currentEndpoint.process.kill();
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Try metrics namespace first, then network namespace
|
|
457
|
+
let promPort = 0;
|
|
458
|
+
let promUrl = '';
|
|
459
|
+
let promProc: ChildProcess | undefined;
|
|
460
|
+
|
|
461
|
+
try {
|
|
462
|
+
const metricsResult = await startPortForward({
|
|
463
|
+
resource: `svc/metrics-prometheus-server`,
|
|
464
|
+
namespace: 'metrics',
|
|
465
|
+
containerPort: 80,
|
|
466
|
+
});
|
|
467
|
+
promProc = metricsResult.process;
|
|
468
|
+
promPort = metricsResult.port;
|
|
469
|
+
promUrl = `http://127.0.0.1:${promPort}/api/v1`;
|
|
470
|
+
} catch {
|
|
471
|
+
// Metrics namespace might not have Prometheus, try network namespace
|
|
472
|
+
log.verbose('Metrics namespace Prometheus not available, trying network namespace');
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (promPort === 0) {
|
|
476
|
+
const nsResult = await startPortForward({
|
|
477
|
+
resource: `svc/prometheus-server`,
|
|
478
|
+
namespace,
|
|
479
|
+
containerPort: 80,
|
|
480
|
+
});
|
|
481
|
+
promProc = nsResult.process;
|
|
482
|
+
promPort = nsResult.port;
|
|
483
|
+
promUrl = `http://127.0.0.1:${promPort}/api/v1`;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (!promProc || promPort === 0) {
|
|
487
|
+
throw new Error('Unable to port-forward to Prometheus');
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
currentEndpoint = { url: promUrl, process: promProc };
|
|
491
|
+
endpoints.push(currentEndpoint);
|
|
492
|
+
alertChecker = new GrafanaClient(log, { grafanaEndpoint: promUrl, grafanaCredentials: '' });
|
|
493
|
+
log.info(`Established Prometheus connection at ${promUrl}`);
|
|
494
|
+
return alertChecker;
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
const runAlertCheck = async (alerts: Parameters<GrafanaClient['runAlertCheck']>[0]): Promise<void> => {
|
|
498
|
+
if (!alertChecker) {
|
|
499
|
+
alertChecker = await connect();
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
try {
|
|
503
|
+
await alertChecker.runAlertCheck(alerts);
|
|
504
|
+
} catch (err) {
|
|
505
|
+
// If it's an AlertTriggeredError (expected behavior)
|
|
506
|
+
if (err instanceof AlertTriggeredError) {
|
|
507
|
+
throw err;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Check if it's a connection error (port-forward died)
|
|
511
|
+
const errorStr = String(err);
|
|
512
|
+
if (errorStr.includes('fetch failed') || errorStr.includes('ECONNREFUSED') || errorStr.includes('ECONNRESET')) {
|
|
513
|
+
log.warn(`Prometheus connection lost, re-establishing port-forward...`);
|
|
514
|
+
alertChecker = await connect();
|
|
515
|
+
await alertChecker.runAlertCheck(alerts);
|
|
516
|
+
} else {
|
|
517
|
+
throw err;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
return { connect, runAlertCheck };
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/** Scales the prover-agent Deployment to the given number of replicas. */
|
|
526
|
+
export async function scaleProverAgents(namespace: string, replicas: number, log: Logger): Promise<void> {
|
|
527
|
+
const label = 'app.kubernetes.io/component=prover-agent';
|
|
528
|
+
const command = `kubectl scale deployment -l ${label} -n ${namespace} --replicas=${replicas} --timeout=2m`;
|
|
529
|
+
log.info(`Scaling prover agents to ${replicas}: ${command}`);
|
|
530
|
+
await execAsync(command);
|
|
531
|
+
}
|
|
532
|
+
|
|
373
533
|
export function getChartDir(spartanDir: string, chartName: string) {
|
|
374
534
|
return path.join(spartanDir.trim(), chartName);
|
|
375
535
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createLogger } from '@aztec/aztec.js/log';
|
|
2
|
+
import { createAztecNodeClient } from '@aztec/aztec.js/node';
|
|
2
3
|
import type { RollupCheatCodes } from '@aztec/aztec/testing';
|
|
3
4
|
import type { CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
4
5
|
import type { Logger } from '@aztec/foundation/log';
|
|
5
|
-
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
6
|
+
import { makeBackoff, retry, retryUntil } from '@aztec/foundation/retry';
|
|
6
7
|
import { sleep } from '@aztec/foundation/sleep';
|
|
7
8
|
import {
|
|
8
9
|
type AztecNodeAdmin,
|
|
@@ -15,7 +16,14 @@ import { promisify } from 'util';
|
|
|
15
16
|
|
|
16
17
|
import type { TestConfig } from './config.js';
|
|
17
18
|
import { execHelmCommand } from './helm.js';
|
|
18
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
deleteResourceByLabel,
|
|
21
|
+
getChartDir,
|
|
22
|
+
startPortForward,
|
|
23
|
+
waitForResourceByLabel,
|
|
24
|
+
waitForResourceByName,
|
|
25
|
+
waitForStatefulSetsReady,
|
|
26
|
+
} from './k8s.js';
|
|
19
27
|
|
|
20
28
|
const execAsync = promisify(exec);
|
|
21
29
|
|
|
@@ -42,6 +50,63 @@ export async function awaitCheckpointNumber(
|
|
|
42
50
|
}
|
|
43
51
|
}
|
|
44
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Waits until the proven block number increases.
|
|
55
|
+
*
|
|
56
|
+
* @param rpcUrl - URL of an Aztec RPC node to query
|
|
57
|
+
* @param log - Logger instance
|
|
58
|
+
* @param timeoutSeconds - Maximum time to wait
|
|
59
|
+
* @param pollIntervalSeconds - How often to check
|
|
60
|
+
*/
|
|
61
|
+
export async function waitForProvenToAdvance(
|
|
62
|
+
rpcUrl: string,
|
|
63
|
+
log: Logger,
|
|
64
|
+
timeoutSeconds: number = 300,
|
|
65
|
+
pollIntervalSeconds: number = 12, // slot duration
|
|
66
|
+
): Promise<void> {
|
|
67
|
+
const node = createAztecNodeClient(rpcUrl);
|
|
68
|
+
|
|
69
|
+
log.info('Waiting for proven block to advance (indicating epoch proof just submitted)...');
|
|
70
|
+
|
|
71
|
+
// Get current proven block number
|
|
72
|
+
let initialProvenBlock: number;
|
|
73
|
+
try {
|
|
74
|
+
const tips = await node.getL2Tips();
|
|
75
|
+
initialProvenBlock = Number(tips.proven.block.number);
|
|
76
|
+
log.info(`Current proven block: ${initialProvenBlock}. Waiting for it to increase...`);
|
|
77
|
+
} catch (err) {
|
|
78
|
+
log.warn(`Error getting initial tips: ${err}. Will poll until successful.`);
|
|
79
|
+
initialProvenBlock = 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
await retryUntil(
|
|
83
|
+
async () => {
|
|
84
|
+
try {
|
|
85
|
+
const tips = await node.getL2Tips();
|
|
86
|
+
const currentProvenBlock = Number(tips.proven.block.number);
|
|
87
|
+
const proposedBlock = Number(tips.proposed.number);
|
|
88
|
+
|
|
89
|
+
log.verbose(
|
|
90
|
+
`Chain state: proposed=${proposedBlock}, proven=${currentProvenBlock} (waiting for > ${initialProvenBlock})`,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
if (currentProvenBlock > initialProvenBlock) {
|
|
94
|
+
log.info(`Proven block advanced from ${initialProvenBlock} to ${currentProvenBlock}.`);
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return false;
|
|
99
|
+
} catch (err) {
|
|
100
|
+
log.verbose(`Error checking tips: ${err}`);
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
'proven block to advance',
|
|
105
|
+
timeoutSeconds,
|
|
106
|
+
pollIntervalSeconds,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
45
110
|
export async function getSequencers(namespace: string) {
|
|
46
111
|
const selectors = [
|
|
47
112
|
'app.kubernetes.io/name=validator',
|
|
@@ -90,6 +155,8 @@ export async function withSequencersAdmin<T>(env: TestConfig, fn: (node: AztecNo
|
|
|
90
155
|
const results = [];
|
|
91
156
|
|
|
92
157
|
for (const sequencer of sequencers) {
|
|
158
|
+
// Ensure pod is Ready before attempting port-forward.
|
|
159
|
+
await waitForResourceByName({ resource: 'pods', name: sequencer, namespace });
|
|
93
160
|
// Wrap port-forward + fetch in a retry to handle flaky port-forwards
|
|
94
161
|
const result = await retry(
|
|
95
162
|
async () => {
|
|
@@ -106,7 +173,7 @@ export async function withSequencersAdmin<T>(env: TestConfig, fn: (node: AztecNo
|
|
|
106
173
|
if (statusRes.status !== 200) {
|
|
107
174
|
throw new Error(`Admin endpoint returned status ${statusRes.status}`);
|
|
108
175
|
}
|
|
109
|
-
const client = createAztecNodeAdminClient(url);
|
|
176
|
+
const client = createAztecNodeAdminClient(url, {}, undefined, env.AZTEC_ADMIN_API_KEY);
|
|
110
177
|
return { result: await fn(client), process };
|
|
111
178
|
} catch (err) {
|
|
112
179
|
// Kill the port-forward before retrying
|
|
@@ -127,22 +194,79 @@ export async function withSequencersAdmin<T>(env: TestConfig, fn: (node: AztecNo
|
|
|
127
194
|
return results;
|
|
128
195
|
}
|
|
129
196
|
|
|
197
|
+
async function getAztecImageForMigrations(namespace: string): Promise<string> {
|
|
198
|
+
const aztecDockerImage = process.env.AZTEC_DOCKER_IMAGE;
|
|
199
|
+
if (aztecDockerImage) {
|
|
200
|
+
return aztecDockerImage;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const { stdout } = await execAsync(
|
|
204
|
+
`kubectl get pods -l app.kubernetes.io/name=validator -n ${namespace} -o jsonpath='{.items[0].spec.containers[?(@.name=="aztec")].image}' | cat`,
|
|
205
|
+
);
|
|
206
|
+
const image = stdout.trim().replace(/^'|'$/g, '');
|
|
207
|
+
if (!image) {
|
|
208
|
+
throw new Error(`Could not detect aztec image from validator pod in namespace ${namespace}`);
|
|
209
|
+
}
|
|
210
|
+
return image;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function getHaDbConnectionUrl(namespace: string): Promise<string> {
|
|
214
|
+
const secretName = `${namespace}-validator-ha-db-postgres`;
|
|
215
|
+
const { stdout } = await execAsync(`kubectl get secret ${secretName} -n ${namespace} -o json`);
|
|
216
|
+
const secret = JSON.parse(stdout);
|
|
217
|
+
const data = secret?.data ?? {};
|
|
218
|
+
const decode = (value?: string) => (value ? Buffer.from(value, 'base64').toString('utf8') : '');
|
|
219
|
+
const user = decode(data.POSTGRES_USER);
|
|
220
|
+
const password = decode(data.POSTGRES_PASSWORD);
|
|
221
|
+
const database = decode(data.POSTGRES_DB);
|
|
222
|
+
if (!user || !password || !database) {
|
|
223
|
+
throw new Error(`Missing HA DB credentials in secret ${secretName}`);
|
|
224
|
+
}
|
|
225
|
+
const host = `${namespace}-validator-ha-db-postgres.${namespace}.svc.cluster.local`;
|
|
226
|
+
return `postgresql://${encodeURIComponent(user)}:${encodeURIComponent(password)}@${host}:5432/${database}`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export async function initHADb(namespace: string): Promise<void> {
|
|
230
|
+
const databaseUrl = await getHaDbConnectionUrl(namespace);
|
|
231
|
+
const image = await getAztecImageForMigrations(namespace);
|
|
232
|
+
const jobName = `${namespace}-validator-ha-db-migrate`;
|
|
233
|
+
await execAsync(`kubectl delete pod ${jobName} -n ${namespace} --ignore-not-found=true`).catch(() => undefined);
|
|
234
|
+
|
|
235
|
+
const migrateCmd = [
|
|
236
|
+
`kubectl run ${jobName} -n ${namespace}`,
|
|
237
|
+
'--rm -i',
|
|
238
|
+
'--restart=Never',
|
|
239
|
+
`--image=${image}`,
|
|
240
|
+
`--env=DATABASE_URL=${databaseUrl}`,
|
|
241
|
+
'--command -- node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js migrate-ha-db up',
|
|
242
|
+
].join(' ');
|
|
243
|
+
const migrateCmdForLog = migrateCmd.replace(/--env=DATABASE_URL=\S+/, '--env=DATABASE_URL=<redacted>');
|
|
244
|
+
|
|
245
|
+
await retry(
|
|
246
|
+
async () => {
|
|
247
|
+
logger.info(`command: ${migrateCmdForLog}`);
|
|
248
|
+
await execAsync(migrateCmd);
|
|
249
|
+
},
|
|
250
|
+
'run HA DB migrations',
|
|
251
|
+
makeBackoff([1, 2, 4, 8, 16]),
|
|
252
|
+
logger,
|
|
253
|
+
true,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
130
257
|
/**
|
|
131
|
-
*
|
|
132
|
-
* Wired to env
|
|
258
|
+
* Sets probabilistic transaction dropping on validators and waits for rollout.
|
|
259
|
+
* Use probability=0 to disable. Wired to env var P2P_DROP_TX_CHANCE via Helm values.
|
|
133
260
|
*/
|
|
134
261
|
export async function setValidatorTxDrop({
|
|
135
262
|
namespace,
|
|
136
|
-
enabled,
|
|
137
263
|
probability,
|
|
138
264
|
logger: log,
|
|
139
265
|
}: {
|
|
140
266
|
namespace: string;
|
|
141
|
-
enabled: boolean;
|
|
142
267
|
probability: number;
|
|
143
268
|
logger: Logger;
|
|
144
269
|
}) {
|
|
145
|
-
const drop = enabled ? 'true' : 'false';
|
|
146
270
|
const prob = String(probability);
|
|
147
271
|
|
|
148
272
|
const selectors = ['app.kubernetes.io/name=validator', 'app.kubernetes.io/component=validator', 'app=validator'];
|
|
@@ -157,7 +281,7 @@ export async function setValidatorTxDrop({
|
|
|
157
281
|
if (names.length === 0) {
|
|
158
282
|
continue;
|
|
159
283
|
}
|
|
160
|
-
const cmd = `kubectl set env statefulset -l ${selector} -n ${namespace}
|
|
284
|
+
const cmd = `kubectl set env statefulset -l ${selector} -n ${namespace} P2P_DROP_TX_CHANCE=${prob}`;
|
|
161
285
|
log.info(`command: ${cmd}`);
|
|
162
286
|
await execAsync(cmd);
|
|
163
287
|
updated = true;
|
|
@@ -239,25 +363,54 @@ export async function enableValidatorDynamicBootNode(
|
|
|
239
363
|
*/
|
|
240
364
|
export async function rollAztecPods(namespace: string, clearState: boolean = false) {
|
|
241
365
|
// Pod components use 'validator', but StatefulSets and PVCs use 'sequencer-node' for validators
|
|
242
|
-
|
|
243
|
-
const
|
|
366
|
+
// RPC nodes have nodeType='rpc-node' in Helm values, so their component label is 'rpc-node' (not 'rpc')
|
|
367
|
+
const podComponents = [
|
|
368
|
+
'p2p-bootstrap',
|
|
369
|
+
'prover-node',
|
|
370
|
+
'prover-broker',
|
|
371
|
+
'prover-agent',
|
|
372
|
+
'sequencer-node',
|
|
373
|
+
'rpc-node',
|
|
374
|
+
'validator-ha-db',
|
|
375
|
+
];
|
|
376
|
+
const pvcComponents = [
|
|
377
|
+
'p2p-bootstrap',
|
|
378
|
+
'prover-node',
|
|
379
|
+
'prover-broker',
|
|
380
|
+
'sequencer-node',
|
|
381
|
+
'rpc-node',
|
|
382
|
+
'validator-ha-db',
|
|
383
|
+
];
|
|
244
384
|
// StatefulSet components that need to be scaled down before PVC deletion
|
|
245
385
|
// Note: validators use 'sequencer-node' as component label, not 'validator'
|
|
246
|
-
const statefulSetComponents = [
|
|
386
|
+
const statefulSetComponents = [
|
|
387
|
+
'p2p-bootstrap',
|
|
388
|
+
'prover-node',
|
|
389
|
+
'prover-broker',
|
|
390
|
+
'sequencer-node',
|
|
391
|
+
'rpc-node',
|
|
392
|
+
'validator-ha-db',
|
|
393
|
+
];
|
|
247
394
|
|
|
248
395
|
if (clearState) {
|
|
249
396
|
// To delete PVCs, we must first scale down StatefulSets so pods release the volumes
|
|
250
397
|
// Otherwise PVC deletion will hang waiting for pods to terminate
|
|
251
398
|
|
|
252
|
-
//
|
|
399
|
+
// Save original replica counts for all StatefulSets
|
|
253
400
|
const originalReplicas: Map<string, number> = new Map();
|
|
254
401
|
for (const component of statefulSetComponents) {
|
|
255
402
|
try {
|
|
256
|
-
|
|
403
|
+
// Get all StatefulSets that match the component label
|
|
404
|
+
const getCmd = `kubectl get statefulset -l app.kubernetes.io/component=${component} -n ${namespace} -o json`;
|
|
257
405
|
const { stdout } = await execAsync(getCmd);
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
406
|
+
const result = JSON.parse(stdout);
|
|
407
|
+
for (const sts of result.items || []) {
|
|
408
|
+
const name = sts.metadata.name;
|
|
409
|
+
const replicas = sts.spec.replicas ?? 1;
|
|
410
|
+
if (replicas > 0) {
|
|
411
|
+
originalReplicas.set(name, replicas);
|
|
412
|
+
logger.debug(`Saved replica count for StatefulSet ${name}: ${replicas}`);
|
|
413
|
+
}
|
|
261
414
|
}
|
|
262
415
|
} catch {
|
|
263
416
|
// Component might not exist, continue
|
|
@@ -276,27 +429,81 @@ export async function rollAztecPods(namespace: string, clearState: boolean = fal
|
|
|
276
429
|
}
|
|
277
430
|
}
|
|
278
431
|
|
|
279
|
-
// Wait for pods to terminate
|
|
280
|
-
|
|
432
|
+
// Wait for all pods to fully terminate before deleting PVCs.
|
|
433
|
+
// terminationGracePeriodSeconds default is 30s.
|
|
434
|
+
logger.info('Waiting for pods to fully terminate before deleting PVCs...');
|
|
435
|
+
for (const component of statefulSetComponents) {
|
|
436
|
+
try {
|
|
437
|
+
// Wait for all pods with this component label to be deleted
|
|
438
|
+
const waitCmd = `kubectl wait pods -l app.kubernetes.io/component=${component} --for=delete -n ${namespace} --timeout=2m`;
|
|
439
|
+
logger.info(`command: ${waitCmd}`);
|
|
440
|
+
await execAsync(waitCmd);
|
|
441
|
+
} catch (e) {
|
|
442
|
+
logger.verbose(`Wait for pod deletion ${component} skipped: ${e}`);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
// Extra buffer to ensure PVC protection finalizers are cleared
|
|
446
|
+
await sleep(5 * 1000);
|
|
281
447
|
|
|
282
448
|
// Now delete PVCs (they should no longer be in use)
|
|
283
449
|
for (const component of pvcComponents) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
450
|
+
try {
|
|
451
|
+
await deleteResourceByLabel({
|
|
452
|
+
resource: 'persistentvolumeclaims',
|
|
453
|
+
namespace: namespace,
|
|
454
|
+
label: `app.kubernetes.io/component=${component}`,
|
|
455
|
+
});
|
|
456
|
+
} catch (e) {
|
|
457
|
+
logger.warn(`Failed to delete PVCs for ${component}: ${e}`);
|
|
458
|
+
}
|
|
289
459
|
}
|
|
290
460
|
|
|
291
|
-
//
|
|
292
|
-
for (const component of
|
|
293
|
-
const replicas = originalReplicas.get(component) ?? 1;
|
|
461
|
+
// Verify PVCs are deleted
|
|
462
|
+
for (const component of pvcComponents) {
|
|
294
463
|
try {
|
|
295
|
-
const
|
|
464
|
+
const waitCmd = `kubectl wait pvc -l app.kubernetes.io/component=${component} --for=delete -n ${namespace} --timeout=2m`;
|
|
465
|
+
logger.info(`command: ${waitCmd}`);
|
|
466
|
+
await execAsync(waitCmd);
|
|
467
|
+
} catch (e) {
|
|
468
|
+
logger.verbose(`Wait for PVC deletion ${component} skipped: ${e}`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const haDbStatefulSets = [...originalReplicas.entries()].filter(([name]) => name.includes('validator-ha-db'));
|
|
473
|
+
const otherStatefulSets = [...originalReplicas.entries()].filter(([name]) => !name.includes('validator-ha-db'));
|
|
474
|
+
|
|
475
|
+
// Bring up HA DB first so we can run migrations before validators start
|
|
476
|
+
for (const [stsName, replicas] of haDbStatefulSets) {
|
|
477
|
+
try {
|
|
478
|
+
const scaleCmd = `kubectl scale statefulset ${stsName} -n ${namespace} --replicas=${replicas} --timeout=2m`;
|
|
296
479
|
logger.info(`command: ${scaleCmd}`);
|
|
297
480
|
await execAsync(scaleCmd);
|
|
298
481
|
} catch (e) {
|
|
299
|
-
logger.verbose(`Scale up ${
|
|
482
|
+
logger.verbose(`Scale up ${stsName} skipped: ${e}`);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (haDbStatefulSets.length > 0) {
|
|
487
|
+
try {
|
|
488
|
+
await waitForStatefulSetsReady({
|
|
489
|
+
namespace,
|
|
490
|
+
label: 'app.kubernetes.io/component=validator-ha-db',
|
|
491
|
+
timeoutSeconds: 600,
|
|
492
|
+
});
|
|
493
|
+
await initHADb(namespace);
|
|
494
|
+
} catch (e) {
|
|
495
|
+
logger.warn(`HA DB migration step skipped or failed: ${e}`);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Scale remaining StatefulSets back up to original replica counts (by name, not label)
|
|
500
|
+
for (const [stsName, replicas] of otherStatefulSets) {
|
|
501
|
+
try {
|
|
502
|
+
const scaleCmd = `kubectl scale statefulset ${stsName} -n ${namespace} --replicas=${replicas} --timeout=2m`;
|
|
503
|
+
logger.info(`command: ${scaleCmd}`);
|
|
504
|
+
await execAsync(scaleCmd);
|
|
505
|
+
} catch (e) {
|
|
506
|
+
logger.verbose(`Scale up ${stsName} skipped: ${e}`);
|
|
300
507
|
}
|
|
301
508
|
}
|
|
302
509
|
} else {
|
|
@@ -312,8 +519,21 @@ export async function rollAztecPods(namespace: string, clearState: boolean = fal
|
|
|
312
519
|
|
|
313
520
|
await sleep(10 * 1000);
|
|
314
521
|
|
|
315
|
-
// Wait for
|
|
316
|
-
for (const component of
|
|
522
|
+
// Wait for StatefulSets to have all replicas ready.
|
|
523
|
+
for (const component of statefulSetComponents) {
|
|
524
|
+
try {
|
|
525
|
+
await waitForStatefulSetsReady({
|
|
526
|
+
namespace,
|
|
527
|
+
label: `app.kubernetes.io/component=${component}`,
|
|
528
|
+
timeoutSeconds: 600, // 10 minutes
|
|
529
|
+
});
|
|
530
|
+
} catch (e) {
|
|
531
|
+
logger.warn(`StatefulSet component ${component} may not be fully ready: ${e}`);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const nonStatefulSetComponents = podComponents.filter(c => !statefulSetComponents.includes(c));
|
|
536
|
+
for (const component of nonStatefulSetComponents) {
|
|
317
537
|
await waitForResourceByLabel({
|
|
318
538
|
resource: 'pods',
|
|
319
539
|
namespace: namespace,
|