@aztec/end-to-end 0.0.1-commit.5476d83 → 0.0.1-commit.592b9384
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/benchmark.d.ts +3 -2
- package/dest/bench/client_flows/benchmark.d.ts.map +1 -1
- package/dest/bench/client_flows/benchmark.js +21 -1
- package/dest/bench/client_flows/client_flows_benchmark.d.ts +14 -15
- package/dest/bench/client_flows/client_flows_benchmark.d.ts.map +1 -1
- package/dest/bench/client_flows/client_flows_benchmark.js +123 -136
- package/dest/bench/client_flows/data_extractor.js +3 -1
- package/dest/bench/utils.d.ts +9 -8
- package/dest/bench/utils.d.ts.map +1 -1
- package/dest/bench/utils.js +27 -18
- package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.d.ts +6 -7
- 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 +96 -112
- package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.d.ts +19 -13
- 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 +91 -71
- package/dest/e2e_deploy_contract/deploy_test.d.ts +4 -3
- package/dest/e2e_deploy_contract/deploy_test.d.ts.map +1 -1
- package/dest/e2e_deploy_contract/deploy_test.js +18 -13
- package/dest/e2e_epochs/epochs_test.d.ts +17 -9
- package/dest/e2e_epochs/epochs_test.d.ts.map +1 -1
- package/dest/e2e_epochs/epochs_test.js +44 -20
- package/dest/e2e_fees/bridging_race.notest.js +4 -6
- package/dest/e2e_fees/fees_test.d.ts +20 -16
- package/dest/e2e_fees/fees_test.d.ts.map +1 -1
- package/dest/e2e_fees/fees_test.js +126 -141
- package/dest/e2e_l1_publisher/write_json.d.ts +4 -3
- package/dest/e2e_l1_publisher/write_json.d.ts.map +1 -1
- package/dest/e2e_l1_publisher/write_json.js +14 -16
- package/dest/e2e_multi_validator/utils.js +1 -1
- package/dest/e2e_nested_contract/nested_contract_test.d.ts +6 -9
- package/dest/e2e_nested_contract/nested_contract_test.d.ts.map +1 -1
- package/dest/e2e_nested_contract/nested_contract_test.js +31 -39
- package/dest/e2e_p2p/inactivity_slash_test.d.ts +4 -4
- package/dest/e2e_p2p/inactivity_slash_test.d.ts.map +1 -1
- package/dest/e2e_p2p/inactivity_slash_test.js +6 -9
- package/dest/e2e_p2p/p2p_network.d.ts +13 -11
- package/dest/e2e_p2p/p2p_network.d.ts.map +1 -1
- package/dest/e2e_p2p/p2p_network.js +120 -111
- 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 +153 -0
- package/dest/e2e_p2p/shared.d.ts +7 -7
- package/dest/e2e_p2p/shared.d.ts.map +1 -1
- package/dest/e2e_p2p/shared.js +13 -20
- package/dest/e2e_token_contract/token_contract_test.d.ts +16 -9
- package/dest/e2e_token_contract/token_contract_test.d.ts.map +1 -1
- package/dest/e2e_token_contract/token_contract_test.js +89 -91
- package/dest/fixtures/e2e_prover_test.d.ts +10 -18
- package/dest/fixtures/e2e_prover_test.d.ts.map +1 -1
- package/dest/fixtures/e2e_prover_test.js +91 -103
- package/dest/fixtures/fixtures.d.ts +2 -3
- package/dest/fixtures/fixtures.d.ts.map +1 -1
- package/dest/fixtures/fixtures.js +2 -3
- package/dest/fixtures/get_acvm_config.js +1 -1
- 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 +114 -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/l1_to_l2_messaging.d.ts +4 -3
- package/dest/fixtures/l1_to_l2_messaging.d.ts.map +1 -1
- package/dest/fixtures/l1_to_l2_messaging.js +2 -2
- package/dest/fixtures/setup.d.ts +218 -0
- package/dest/fixtures/setup.d.ts.map +1 -0
- package/dest/fixtures/setup.js +695 -0
- package/dest/fixtures/setup_p2p_test.d.ts +4 -5
- package/dest/fixtures/setup_p2p_test.d.ts.map +1 -1
- package/dest/fixtures/setup_p2p_test.js +27 -22
- package/dest/fixtures/token_utils.d.ts +1 -1
- package/dest/fixtures/token_utils.d.ts.map +1 -1
- package/dest/fixtures/token_utils.js +7 -4
- package/dest/fixtures/utils.d.ts +5 -637
- package/dest/fixtures/utils.d.ts.map +1 -1
- package/dest/fixtures/utils.js +4 -647
- package/dest/fixtures/web3signer.js +1 -1
- package/dest/fixtures/with_telemetry_utils.d.ts +2 -2
- package/dest/fixtures/with_telemetry_utils.d.ts.map +1 -1
- package/dest/fixtures/with_telemetry_utils.js +2 -2
- package/dest/quality_of_service/grafana_client.d.ts +41 -0
- package/dest/quality_of_service/grafana_client.d.ts.map +1 -0
- package/dest/quality_of_service/{alert_checker.js → grafana_client.js} +1 -1
- package/dest/quality_of_service/prometheus_client.d.ts +38 -0
- package/dest/quality_of_service/prometheus_client.d.ts.map +1 -0
- package/dest/quality_of_service/prometheus_client.js +67 -0
- package/dest/shared/cross_chain_test_harness.d.ts +18 -6
- package/dest/shared/cross_chain_test_harness.d.ts.map +1 -1
- package/dest/shared/cross_chain_test_harness.js +14 -14
- package/dest/shared/gas_portal_test_harness.d.ts +12 -2
- package/dest/shared/gas_portal_test_harness.d.ts.map +1 -1
- package/dest/shared/gas_portal_test_harness.js +1 -1
- package/dest/shared/index.d.ts +2 -2
- package/dest/shared/index.d.ts.map +1 -1
- package/dest/shared/submit-transactions.d.ts +3 -3
- package/dest/shared/submit-transactions.d.ts.map +1 -1
- package/dest/shared/submit-transactions.js +9 -11
- package/dest/shared/uniswap_l1_l2.d.ts +3 -27
- package/dest/shared/uniswap_l1_l2.d.ts.map +1 -1
- package/dest/shared/uniswap_l1_l2.js +55 -35
- package/dest/simulators/lending_simulator.d.ts +6 -2
- package/dest/simulators/lending_simulator.d.ts.map +1 -1
- package/dest/simulators/lending_simulator.js +2 -2
- package/dest/spartan/setup_test_wallets.d.ts +4 -3
- package/dest/spartan/setup_test_wallets.d.ts.map +1 -1
- package/dest/spartan/setup_test_wallets.js +63 -35
- package/dest/spartan/tx_metrics.d.ts +88 -0
- package/dest/spartan/tx_metrics.d.ts.map +1 -0
- package/dest/spartan/tx_metrics.js +422 -0
- package/dest/spartan/utils/bot.d.ts +27 -0
- package/dest/spartan/utils/bot.d.ts.map +1 -0
- package/dest/spartan/utils/bot.js +141 -0
- package/dest/spartan/utils/chaos.d.ts +79 -0
- package/dest/spartan/utils/chaos.d.ts.map +1 -0
- package/dest/spartan/utils/chaos.js +142 -0
- package/dest/spartan/utils/clients.d.ts +39 -0
- package/dest/spartan/utils/clients.d.ts.map +1 -0
- package/dest/spartan/utils/clients.js +90 -0
- package/dest/spartan/utils/config.d.ts +36 -0
- package/dest/spartan/utils/config.d.ts.map +1 -0
- package/dest/spartan/utils/config.js +20 -0
- package/dest/spartan/utils/health.d.ts +63 -0
- package/dest/spartan/utils/health.d.ts.map +1 -0
- package/dest/spartan/utils/health.js +202 -0
- package/dest/spartan/utils/helm.d.ts +15 -0
- package/dest/spartan/utils/helm.d.ts.map +1 -0
- package/dest/spartan/utils/helm.js +47 -0
- package/dest/spartan/utils/index.d.ts +9 -0
- package/dest/spartan/utils/index.d.ts.map +1 -0
- package/dest/spartan/utils/index.js +18 -0
- package/dest/spartan/utils/k8s.d.ts +126 -0
- package/dest/spartan/utils/k8s.d.ts.map +1 -0
- package/dest/spartan/utils/k8s.js +375 -0
- package/dest/spartan/utils/nodes.d.ts +41 -0
- package/dest/spartan/utils/nodes.d.ts.map +1 -0
- package/dest/spartan/utils/nodes.js +461 -0
- package/dest/spartan/utils/scripts.d.ts +16 -0
- package/dest/spartan/utils/scripts.d.ts.map +1 -0
- package/dest/spartan/utils/scripts.js +66 -0
- package/dest/spartan/utils.d.ts +2 -220
- package/dest/spartan/utils.d.ts.map +1 -1
- package/dest/spartan/utils.js +1 -782
- package/package.json +45 -41
- package/src/bench/client_flows/benchmark.ts +24 -2
- package/src/bench/client_flows/client_flows_benchmark.ts +145 -208
- package/src/bench/client_flows/data_extractor.ts +1 -1
- package/src/bench/utils.ts +30 -20
- package/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts +104 -142
- package/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts +140 -125
- package/src/e2e_deploy_contract/deploy_test.ts +21 -14
- package/src/e2e_epochs/epochs_test.ts +92 -53
- package/src/e2e_fees/bridging_race.notest.ts +4 -10
- package/src/e2e_fees/fees_test.ts +179 -221
- package/src/e2e_l1_publisher/write_json.ts +16 -16
- package/src/e2e_multi_validator/utils.ts +1 -1
- package/src/e2e_nested_contract/nested_contract_test.ts +33 -56
- package/src/e2e_p2p/inactivity_slash_test.ts +9 -12
- package/src/e2e_p2p/p2p_network.ts +180 -183
- package/src/e2e_p2p/reqresp/utils.ts +207 -0
- package/src/e2e_p2p/shared.ts +31 -28
- package/src/e2e_token_contract/token_contract_test.ts +103 -118
- package/src/fixtures/e2e_prover_test.ts +104 -141
- package/src/fixtures/fixtures.ts +2 -5
- package/src/fixtures/get_acvm_config.ts +1 -1
- package/src/fixtures/ha_setup.ts +184 -0
- package/src/fixtures/index.ts +1 -0
- package/src/fixtures/l1_to_l2_messaging.ts +4 -2
- package/src/fixtures/setup.ts +1017 -0
- package/src/fixtures/setup_p2p_test.ts +18 -23
- package/src/fixtures/token_utils.ts +6 -5
- package/src/fixtures/utils.ts +27 -966
- package/src/fixtures/web3signer.ts +1 -1
- package/src/fixtures/with_telemetry_utils.ts +2 -2
- package/src/quality_of_service/{alert_checker.ts → grafana_client.ts} +1 -1
- package/src/quality_of_service/prometheus_client.ts +113 -0
- package/src/shared/cross_chain_test_harness.ts +19 -36
- package/src/shared/gas_portal_test_harness.ts +2 -2
- package/src/shared/index.ts +1 -1
- package/src/shared/submit-transactions.ts +9 -15
- package/src/shared/uniswap_l1_l2.ts +65 -86
- package/src/simulators/lending_simulator.ts +3 -3
- package/src/spartan/DEVELOP.md +7 -0
- package/src/spartan/setup_test_wallets.ts +81 -26
- package/src/spartan/tx_metrics.ts +376 -0
- package/src/spartan/utils/bot.ts +185 -0
- package/src/spartan/utils/chaos.ts +253 -0
- package/src/spartan/utils/clients.ts +100 -0
- package/src/spartan/utils/config.ts +26 -0
- package/src/spartan/utils/health.ts +255 -0
- package/src/spartan/utils/helm.ts +84 -0
- package/src/spartan/utils/index.ts +64 -0
- package/src/spartan/utils/k8s.ts +527 -0
- package/src/spartan/utils/nodes.ts +538 -0
- package/src/spartan/utils/scripts.ts +63 -0
- package/src/spartan/utils.ts +1 -982
- package/dest/fixtures/setup_l1_contracts.d.ts +0 -477
- package/dest/fixtures/setup_l1_contracts.d.ts.map +0 -1
- package/dest/fixtures/setup_l1_contracts.js +0 -17
- package/dest/fixtures/snapshot_manager.d.ts +0 -95
- package/dest/fixtures/snapshot_manager.d.ts.map +0 -1
- package/dest/fixtures/snapshot_manager.js +0 -505
- package/dest/quality_of_service/alert_checker.d.ts +0 -41
- package/dest/quality_of_service/alert_checker.d.ts.map +0 -1
- package/src/fixtures/setup_l1_contracts.ts +0 -26
- package/src/fixtures/snapshot_manager.ts +0 -665
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { createLogger } from '@aztec/aztec.js/log';
|
|
2
|
+
import type { Logger } from '@aztec/foundation/log';
|
|
3
|
+
|
|
4
|
+
import { exec } from 'child_process';
|
|
5
|
+
import { promisify } from 'util';
|
|
6
|
+
|
|
7
|
+
const execAsync = promisify(exec);
|
|
8
|
+
|
|
9
|
+
const logger = createLogger('e2e:k8s-utils');
|
|
10
|
+
|
|
11
|
+
function shellQuote(value: string) {
|
|
12
|
+
// Single-quote safe shell escaping: ' -> '\''
|
|
13
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function valuesToArgs(values: Record<string, string | number | boolean>) {
|
|
17
|
+
return Object.entries(values)
|
|
18
|
+
.map(([key, value]) =>
|
|
19
|
+
typeof value === 'number' || typeof value === 'boolean'
|
|
20
|
+
? `--set ${key}=${value}`
|
|
21
|
+
: `--set-string ${key}=${shellQuote(String(value))}`,
|
|
22
|
+
)
|
|
23
|
+
.join(' ');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function createHelmCommand({
|
|
27
|
+
instanceName,
|
|
28
|
+
helmChartDir,
|
|
29
|
+
namespace,
|
|
30
|
+
valuesFile,
|
|
31
|
+
timeout,
|
|
32
|
+
values,
|
|
33
|
+
reuseValues = false,
|
|
34
|
+
}: {
|
|
35
|
+
instanceName: string;
|
|
36
|
+
helmChartDir: string;
|
|
37
|
+
namespace: string;
|
|
38
|
+
valuesFile: string | undefined;
|
|
39
|
+
timeout: string;
|
|
40
|
+
values: Record<string, string | number | boolean>;
|
|
41
|
+
reuseValues?: boolean;
|
|
42
|
+
}) {
|
|
43
|
+
const valuesFileArgs = valuesFile ? `--values ${helmChartDir}/values/${valuesFile}` : '';
|
|
44
|
+
const reuseValuesArgs = reuseValues ? '--reuse-values' : '';
|
|
45
|
+
return `helm upgrade --install ${instanceName} ${helmChartDir} --namespace ${namespace} ${valuesFileArgs} ${reuseValuesArgs} --wait --timeout=${timeout} ${valuesToArgs(
|
|
46
|
+
values,
|
|
47
|
+
)}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function execHelmCommand(args: Parameters<typeof createHelmCommand>[0]) {
|
|
51
|
+
const helmCommand = createHelmCommand(args);
|
|
52
|
+
logger.info(`helm command: ${helmCommand}`);
|
|
53
|
+
const { stdout } = await execAsync(helmCommand);
|
|
54
|
+
return stdout;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function getHelmReleaseStatus(instanceName: string, namespace: string): Promise<string | undefined> {
|
|
58
|
+
try {
|
|
59
|
+
const { stdout } = await execAsync(
|
|
60
|
+
`helm list --namespace ${namespace} --all --filter '^${instanceName}$' --output json | cat`,
|
|
61
|
+
);
|
|
62
|
+
const parsed = JSON.parse(stdout) as Array<{ name?: string; status?: string }>;
|
|
63
|
+
const row = parsed.find(r => r.name === instanceName);
|
|
64
|
+
return row?.status;
|
|
65
|
+
} catch {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function forceDeleteHelmReleaseRecord(instanceName: string, namespace: string, log?: Logger) {
|
|
71
|
+
const labelSelector = `owner=helm,name=${instanceName}`;
|
|
72
|
+
const cmd = `kubectl delete secret -n ${namespace} -l ${labelSelector} --ignore-not-found=true`;
|
|
73
|
+
(log ?? logger).warn(`Force deleting Helm release record: ${cmd}`);
|
|
74
|
+
await execAsync(cmd).catch(() => undefined);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function hasDeployedHelmRelease(instanceName: string, namespace: string): Promise<boolean> {
|
|
78
|
+
try {
|
|
79
|
+
const status = await getHelmReleaseStatus(instanceName, namespace);
|
|
80
|
+
return status?.toLowerCase() === 'deployed';
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Re-export all utilities from individual modules
|
|
2
|
+
// Note: Internal helpers (helm commands, etc.) are not re-exported to maintain the original public API
|
|
3
|
+
|
|
4
|
+
// Config
|
|
5
|
+
export { type TestConfig, setupEnvironment } from './config.js';
|
|
6
|
+
|
|
7
|
+
// Scripts
|
|
8
|
+
export { getGitProjectRoot, getAztecBin, runAztecBin, runProjectScript } from './scripts.js';
|
|
9
|
+
|
|
10
|
+
// K8s operations
|
|
11
|
+
export {
|
|
12
|
+
startPortForward,
|
|
13
|
+
getExternalIP,
|
|
14
|
+
startPortForwardForPrometeheus,
|
|
15
|
+
startPortForwardForRPC,
|
|
16
|
+
startPortForwardForEthereum,
|
|
17
|
+
deleteResourceByName,
|
|
18
|
+
deleteResourceByLabel,
|
|
19
|
+
waitForResourceByLabel,
|
|
20
|
+
waitForResourceByName,
|
|
21
|
+
waitForResourcesByName,
|
|
22
|
+
getChartDir,
|
|
23
|
+
type ServiceEndpoint,
|
|
24
|
+
getServiceEndpoint,
|
|
25
|
+
getRPCEndpoint,
|
|
26
|
+
getEthereumEndpoint,
|
|
27
|
+
createResilientPrometheusConnection,
|
|
28
|
+
} from './k8s.js';
|
|
29
|
+
|
|
30
|
+
// Chaos Mesh
|
|
31
|
+
export {
|
|
32
|
+
uninstallChaosMesh,
|
|
33
|
+
installChaosMeshChart,
|
|
34
|
+
applyProverFailure,
|
|
35
|
+
applyValidatorFailure,
|
|
36
|
+
applyProverKill,
|
|
37
|
+
applyProverBrokerKill,
|
|
38
|
+
applyBootNodeFailure,
|
|
39
|
+
applyValidatorKill,
|
|
40
|
+
applyNetworkShaping,
|
|
41
|
+
} from './chaos.js';
|
|
42
|
+
|
|
43
|
+
// Bot management
|
|
44
|
+
export { restartBot, installTransferBot, uninstallTransferBot } from './bot.js';
|
|
45
|
+
|
|
46
|
+
// Node operations (sequencers, validators, pods)
|
|
47
|
+
export {
|
|
48
|
+
awaitCheckpointNumber,
|
|
49
|
+
waitForProvenToAdvance,
|
|
50
|
+
getSequencers,
|
|
51
|
+
updateSequencersConfig,
|
|
52
|
+
getSequencersConfig,
|
|
53
|
+
withSequencersAdmin,
|
|
54
|
+
setValidatorTxDrop,
|
|
55
|
+
restartValidators,
|
|
56
|
+
enableValidatorDynamicBootNode,
|
|
57
|
+
rollAztecPods,
|
|
58
|
+
} from './nodes.js';
|
|
59
|
+
|
|
60
|
+
// Client utilities
|
|
61
|
+
export { getPublicViemClient, getL1DeploymentAddresses, getNodeClient } from './clients.js';
|
|
62
|
+
|
|
63
|
+
// Health checks
|
|
64
|
+
export { ChainHealth, type ChainHealthSnapshot } from './health.js';
|
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
import { createLogger } from '@aztec/aztec.js/log';
|
|
2
|
+
import type { Logger } from '@aztec/foundation/log';
|
|
3
|
+
import { promiseWithResolvers } from '@aztec/foundation/promise';
|
|
4
|
+
import { retryUntil } from '@aztec/foundation/retry';
|
|
5
|
+
|
|
6
|
+
import { type ChildProcess, exec, spawn } from 'child_process';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { promisify } from 'util';
|
|
9
|
+
|
|
10
|
+
import { AlertTriggeredError, GrafanaClient } from '../../quality_of_service/grafana_client.js';
|
|
11
|
+
|
|
12
|
+
const execAsync = promisify(exec);
|
|
13
|
+
|
|
14
|
+
const logger = createLogger('e2e:k8s-utils');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Represents an endpoint to reach a K8s service.
|
|
18
|
+
* May be a LoadBalancer external IP or a port-forward.
|
|
19
|
+
*/
|
|
20
|
+
export interface ServiceEndpoint {
|
|
21
|
+
url: string;
|
|
22
|
+
process?: ChildProcess;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function startPortForward({
|
|
26
|
+
resource,
|
|
27
|
+
namespace,
|
|
28
|
+
containerPort,
|
|
29
|
+
hostPort,
|
|
30
|
+
}: {
|
|
31
|
+
resource: string;
|
|
32
|
+
namespace: string;
|
|
33
|
+
containerPort: number;
|
|
34
|
+
// If not provided, the port will be chosen automatically
|
|
35
|
+
hostPort?: number;
|
|
36
|
+
}): Promise<{
|
|
37
|
+
process: ChildProcess;
|
|
38
|
+
port: number;
|
|
39
|
+
}> {
|
|
40
|
+
const hostPortAsString = hostPort ? hostPort.toString() : '';
|
|
41
|
+
|
|
42
|
+
logger.debug(`kubectl port-forward -n ${namespace} ${resource} ${hostPortAsString}:${containerPort}`);
|
|
43
|
+
|
|
44
|
+
const process = spawn(
|
|
45
|
+
'kubectl',
|
|
46
|
+
['port-forward', '-n', namespace, resource, `${hostPortAsString}:${containerPort}`],
|
|
47
|
+
{
|
|
48
|
+
detached: true,
|
|
49
|
+
windowsHide: true,
|
|
50
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
let isResolved = false;
|
|
55
|
+
const connected = new Promise<number>((resolve, reject) => {
|
|
56
|
+
process.stdout?.on('data', data => {
|
|
57
|
+
const str = data.toString() as string;
|
|
58
|
+
if (!isResolved && str.includes('Forwarding from')) {
|
|
59
|
+
isResolved = true;
|
|
60
|
+
logger.debug(`Port forward for ${resource}: ${str}`);
|
|
61
|
+
const port = str.search(/:\d+/);
|
|
62
|
+
if (port === -1) {
|
|
63
|
+
reject(new Error('Port not found in port forward output'));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const portNumber = parseInt(str.slice(port + 1));
|
|
67
|
+
logger.verbose(`Port forwarded for ${resource} at ${portNumber}:${containerPort}`);
|
|
68
|
+
resolve(portNumber);
|
|
69
|
+
} else {
|
|
70
|
+
logger.silent(str);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
process.stderr?.on('data', data => {
|
|
74
|
+
logger.verbose(`Port forward for ${resource}: ${data.toString()}`);
|
|
75
|
+
// It's a strange thing:
|
|
76
|
+
// If we don't pipe stderr, then the port forwarding does not work.
|
|
77
|
+
// Log to silent because this doesn't actually report errors,
|
|
78
|
+
// just extremely verbose debug logs.
|
|
79
|
+
logger.silent(data.toString());
|
|
80
|
+
});
|
|
81
|
+
process.on('close', () => {
|
|
82
|
+
if (!isResolved) {
|
|
83
|
+
isResolved = true;
|
|
84
|
+
const msg = `Port forward for ${resource} closed before connection established`;
|
|
85
|
+
logger.warn(msg);
|
|
86
|
+
reject(new Error(msg));
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
process.on('error', error => {
|
|
90
|
+
if (!isResolved) {
|
|
91
|
+
isResolved = true;
|
|
92
|
+
const msg = `Port forward for ${resource} error: ${error}`;
|
|
93
|
+
logger.error(msg);
|
|
94
|
+
reject(new Error(msg));
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
process.on('exit', code => {
|
|
98
|
+
if (!isResolved) {
|
|
99
|
+
isResolved = true;
|
|
100
|
+
const msg = `Port forward for ${resource} exited with code ${code}`;
|
|
101
|
+
logger.verbose(msg);
|
|
102
|
+
reject(new Error(msg));
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const port = await connected;
|
|
108
|
+
|
|
109
|
+
return { process, port };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function getExternalIP(namespace: string, serviceName: string): Promise<string> {
|
|
113
|
+
const { promise, resolve, reject } = promiseWithResolvers<string>();
|
|
114
|
+
const process = spawn(
|
|
115
|
+
'kubectl',
|
|
116
|
+
[
|
|
117
|
+
'get',
|
|
118
|
+
'service',
|
|
119
|
+
'-n',
|
|
120
|
+
namespace,
|
|
121
|
+
`${namespace}-${serviceName}`,
|
|
122
|
+
'--output',
|
|
123
|
+
"jsonpath='{.status.loadBalancer.ingress[0].ip}'",
|
|
124
|
+
],
|
|
125
|
+
{
|
|
126
|
+
stdio: 'pipe',
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
let ip = '';
|
|
131
|
+
process.stdout.on('data', data => {
|
|
132
|
+
ip += data;
|
|
133
|
+
});
|
|
134
|
+
process.on('error', err => {
|
|
135
|
+
reject(err);
|
|
136
|
+
});
|
|
137
|
+
process.on('exit', () => {
|
|
138
|
+
// kubectl prints JSON. Remove the quotes
|
|
139
|
+
resolve(ip.replace(/"|'/g, ''));
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return promise;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Gets an endpoint for a K8s service.
|
|
147
|
+
* By default, tries to get the external IP first and falls back to port-forward if unavailable.
|
|
148
|
+
*
|
|
149
|
+
* @param opts.namespace - K8s namespace
|
|
150
|
+
* @param opts.serviceName - Service name suffix (e.g., 'rpc-aztec-node', 'eth-execution')
|
|
151
|
+
* @param opts.containerPort - Port the service exposes
|
|
152
|
+
* @param opts.usePortForward - If true, skip external IP check and always use port-forward
|
|
153
|
+
*/
|
|
154
|
+
export async function getServiceEndpoint(opts: {
|
|
155
|
+
namespace: string;
|
|
156
|
+
serviceName: string;
|
|
157
|
+
containerPort: number;
|
|
158
|
+
forcePortForward?: boolean;
|
|
159
|
+
}): Promise<ServiceEndpoint> {
|
|
160
|
+
const { namespace, serviceName, containerPort, forcePortForward } = opts;
|
|
161
|
+
|
|
162
|
+
if (!forcePortForward) {
|
|
163
|
+
try {
|
|
164
|
+
const ip = await retryUntil(
|
|
165
|
+
async () => {
|
|
166
|
+
try {
|
|
167
|
+
const ip = await getExternalIP(namespace, serviceName);
|
|
168
|
+
if (ip && ip !== '' && ip !== '<pending>' && ip !== 'null') {
|
|
169
|
+
return ip;
|
|
170
|
+
}
|
|
171
|
+
} catch (err) {
|
|
172
|
+
logger.verbose(`Failed to get external IP for ${serviceName}: ${err}`);
|
|
173
|
+
}
|
|
174
|
+
return undefined;
|
|
175
|
+
},
|
|
176
|
+
`external IP for ${serviceName}`,
|
|
177
|
+
30,
|
|
178
|
+
5,
|
|
179
|
+
);
|
|
180
|
+
logger.info(`Using external IP for ${serviceName}: ${ip}:${containerPort}`);
|
|
181
|
+
return { url: `http://${ip}:${containerPort}` };
|
|
182
|
+
} catch {
|
|
183
|
+
logger.warn(`External IP not available for ${serviceName} after 5min, using port-forward`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Fallback to port-forward
|
|
188
|
+
const resource = `svc/${namespace}-${serviceName}`;
|
|
189
|
+
|
|
190
|
+
const { process, port } = await startPortForward({
|
|
191
|
+
resource,
|
|
192
|
+
namespace,
|
|
193
|
+
containerPort,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
return { url: `http://127.0.0.1:${port}`, process };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Gets an endpoint for the RPC node service.
|
|
201
|
+
* Tries external IP first, falls back to port-forward.
|
|
202
|
+
*
|
|
203
|
+
* @param namespace - K8s namespace
|
|
204
|
+
* @param usePortForward - If true, skip external IP and use port-forward directly
|
|
205
|
+
*/
|
|
206
|
+
export async function getRPCEndpoint(namespace: string, forcePortForward?: boolean): Promise<ServiceEndpoint> {
|
|
207
|
+
return await getServiceEndpoint({
|
|
208
|
+
namespace,
|
|
209
|
+
serviceName: 'rpc-aztec-node',
|
|
210
|
+
containerPort: 8080,
|
|
211
|
+
forcePortForward,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Gets an endpoint for the Ethereum execution service.
|
|
217
|
+
* Tries external IP first, falls back to port-forward.
|
|
218
|
+
*
|
|
219
|
+
* @param namespace - K8s namespace
|
|
220
|
+
* @param usePortForward - If true, skip external IP and use port-forward directly
|
|
221
|
+
*/
|
|
222
|
+
export async function getEthereumEndpoint(namespace: string, forcePortForward?: boolean): Promise<ServiceEndpoint> {
|
|
223
|
+
return await getServiceEndpoint({
|
|
224
|
+
namespace,
|
|
225
|
+
serviceName: 'eth-execution',
|
|
226
|
+
containerPort: 8545,
|
|
227
|
+
forcePortForward,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function startPortForwardForPrometeheus(namespace: string) {
|
|
232
|
+
return startPortForward({
|
|
233
|
+
resource: `svc/${namespace}-prometheus-server`,
|
|
234
|
+
namespace,
|
|
235
|
+
containerPort: 80,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function startPortForwardForRPC(namespace: string, index = 0) {
|
|
240
|
+
return startPortForward({
|
|
241
|
+
resource: `pod/${namespace}-rpc-aztec-node-${index}`,
|
|
242
|
+
namespace,
|
|
243
|
+
containerPort: 8080,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function startPortForwardForEthereum(namespace: string) {
|
|
248
|
+
return startPortForward({
|
|
249
|
+
resource: `services/${namespace}-eth-execution`,
|
|
250
|
+
namespace,
|
|
251
|
+
containerPort: 8545,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export async function deleteResourceByName({
|
|
256
|
+
resource,
|
|
257
|
+
namespace,
|
|
258
|
+
name,
|
|
259
|
+
force = false,
|
|
260
|
+
}: {
|
|
261
|
+
resource: string;
|
|
262
|
+
namespace: string;
|
|
263
|
+
name: string;
|
|
264
|
+
force?: boolean;
|
|
265
|
+
}) {
|
|
266
|
+
const command = `kubectl delete ${resource} ${name} -n ${namespace} --ignore-not-found=true --wait=true ${
|
|
267
|
+
force ? '--force' : ''
|
|
268
|
+
}`;
|
|
269
|
+
logger.info(`command: ${command}`);
|
|
270
|
+
const { stdout } = await execAsync(command);
|
|
271
|
+
return stdout;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export async function deleteResourceByLabel({
|
|
275
|
+
resource,
|
|
276
|
+
namespace,
|
|
277
|
+
label,
|
|
278
|
+
timeout = '5m',
|
|
279
|
+
force = false,
|
|
280
|
+
}: {
|
|
281
|
+
resource: string;
|
|
282
|
+
namespace: string;
|
|
283
|
+
label: string;
|
|
284
|
+
timeout?: string;
|
|
285
|
+
force?: boolean;
|
|
286
|
+
}) {
|
|
287
|
+
try {
|
|
288
|
+
// Match both plain and group-qualified names (e.g., "podchaos" or "podchaos.chaos-mesh.org")
|
|
289
|
+
const escaped = resource.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
290
|
+
const regex = `(^|\\.)${escaped}(\\.|$)`;
|
|
291
|
+
await execAsync(`kubectl api-resources --no-headers -o name | grep -Eq '${regex}'`);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
logger.warn(`Resource type '${resource}' not found in cluster, skipping deletion ${error}`);
|
|
294
|
+
return '';
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const command = `kubectl delete ${resource} -l ${label} -n ${namespace} --ignore-not-found=true --wait=true --timeout=${timeout} ${
|
|
298
|
+
force ? '--force' : ''
|
|
299
|
+
}`;
|
|
300
|
+
logger.info(`command: ${command}`);
|
|
301
|
+
const { stdout } = await execAsync(command);
|
|
302
|
+
return stdout;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export async function waitForResourceByLabel({
|
|
306
|
+
resource,
|
|
307
|
+
label,
|
|
308
|
+
namespace,
|
|
309
|
+
condition = 'Ready',
|
|
310
|
+
timeout = '10m',
|
|
311
|
+
}: {
|
|
312
|
+
resource: string;
|
|
313
|
+
label: string;
|
|
314
|
+
namespace: string;
|
|
315
|
+
condition?: string;
|
|
316
|
+
timeout?: string;
|
|
317
|
+
}) {
|
|
318
|
+
const command = `kubectl wait ${resource} -l ${label} --for=condition=${condition} -n ${namespace} --timeout=${timeout}`;
|
|
319
|
+
logger.info(`command: ${command}`);
|
|
320
|
+
const { stdout } = await execAsync(command);
|
|
321
|
+
return stdout;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export async function waitForResourceByName({
|
|
325
|
+
resource,
|
|
326
|
+
name,
|
|
327
|
+
namespace,
|
|
328
|
+
condition = 'Ready',
|
|
329
|
+
timeout = '10m',
|
|
330
|
+
}: {
|
|
331
|
+
resource: string;
|
|
332
|
+
name: string;
|
|
333
|
+
namespace: string;
|
|
334
|
+
condition?: string;
|
|
335
|
+
timeout?: string;
|
|
336
|
+
}) {
|
|
337
|
+
const command = `kubectl wait ${resource}/${name} --for=condition=${condition} -n ${namespace} --timeout=${timeout}`;
|
|
338
|
+
logger.info(`command: ${command}`);
|
|
339
|
+
const { stdout } = await execAsync(command);
|
|
340
|
+
return stdout;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export async function waitForResourcesByName({
|
|
344
|
+
resource,
|
|
345
|
+
names,
|
|
346
|
+
namespace,
|
|
347
|
+
condition = 'Ready',
|
|
348
|
+
timeout = '10m',
|
|
349
|
+
}: {
|
|
350
|
+
resource: string;
|
|
351
|
+
names: string[];
|
|
352
|
+
namespace: string;
|
|
353
|
+
condition?: string;
|
|
354
|
+
timeout?: string;
|
|
355
|
+
}) {
|
|
356
|
+
if (!names.length) {
|
|
357
|
+
throw new Error(`No ${resource} names provided to waitForResourcesByName`);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Wait all in parallel; if any fails, surface which one.
|
|
361
|
+
await Promise.all(
|
|
362
|
+
names.map(async name => {
|
|
363
|
+
try {
|
|
364
|
+
await waitForResourceByName({ resource, name, namespace, condition, timeout });
|
|
365
|
+
} catch (err) {
|
|
366
|
+
throw new Error(
|
|
367
|
+
`Failed waiting for ${resource}/${name} condition=${condition} timeout=${timeout} namespace=${namespace}: ${String(
|
|
368
|
+
err,
|
|
369
|
+
)}`,
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
}),
|
|
373
|
+
);
|
|
374
|
+
}
|
|
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
|
+
export function getChartDir(spartanDir: string, chartName: string) {
|
|
526
|
+
return path.join(spartanDir.trim(), chartName);
|
|
527
|
+
}
|