Skip to content

Commit 2c2d529

Browse files
basvandijkclaude
andauthored
test(backup): run the backup manager tests on the local backend (#11188)
`ic-backup` replays each replica version's artifacts with that version's own `ic-replay` and sandbox binaries, and downloads them from `download.dfinity.systems` when `binaries/<version>/` doesn't already hold them. The backup tests pre-seeded that directory for the branch version only, so the mainnet version — which both tests replay, as the initial version of the upgrade test and as the target version of the downgrade test — was still downloaded, unverified, at test time. Two commits, so the backend switch can be reverted on its own if the nightly job dislikes it: 1. **Seeding.** Take the mainnet binaries from `@mainnet_nns_binaries` and seed the directory of every version that will be replayed: the initial, the target and the branch version. One code path covers both tests, which is necessary because they share `backup_manager_test_bin` and `test()` cannot tell them apart. A version we have no binaries for now fails immediately, naming the versions we do have, instead of stalling for the whole test timeout on a download that cannot succeed. `copy_file` also checks that what it copies is executable — a dependency that lost its `+x` bit would otherwise only surface minutes later, as `ic-replay` failing to spawn its sandbox. 2. **Drop `backend = "farm"`**, since nothing in these tests reaches the network any more. Only the `_local` variants change tagging. The plain Farm variant is already `manual` via `colocate`, and `_colocate` is unaffected. ### Verification Both pass on the local backend, well inside the 25-minute per-test timeout set in `backup_manager_test.rs`: ``` //rs/tests/consensus/backup:backup_manager_upgrade_test_local PASSED in 917.0s //rs/tests/consensus/backup:backup_manager_downgrade_test_local PASSED in 883.9s ``` Each log shows both versions seeded (`0000…0000` for the unstamped branch build and `798712972b1e…` for mainnet), several successful replays across a detected version change, and **no** `download.dfinity.systems` anywhere. Since the `_local` sandbox has no network, a download that was still needed would have failed the test outright. The downgrade direction is worth a second look from a reviewer: `guestos_update = "mainnet_nns"` appears exactly once in the repo, so this is its first run on the local backend. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 85a29e8 commit 2c2d529

2 files changed

Lines changed: 92 additions & 53 deletions

File tree

rs/tests/consensus/backup/BUILD.bazel

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library")
2-
load("//rs/tests:common.bzl", "DEFAULT_VCPUS_PER_VM", "MESSAGE_CANISTER_RUNTIME_DEPS", "MIN_LOCAL_CPUS", "UNIVERSAL_CANISTER_RUNTIME_DEPS")
2+
load("//rs/tests:common.bzl", "DEFAULT_VCPUS_PER_VM", "MAINNET_ENV", "MAINNET_NNS_REPLAY_RUNTIME_DEPS", "MESSAGE_CANISTER_RUNTIME_DEPS", "MIN_LOCAL_CPUS", "UNIVERSAL_CANISTER_RUNTIME_DEPS")
33
load("//rs/tests:system_tests.bzl", "system_test_nns")
44

55
package(default_visibility = ["//rs:system-tests-pkg"])
@@ -27,12 +27,13 @@ rust_library(
2727
],
2828
)
2929

30-
BACKUP_RUNTIME_DEPS = MESSAGE_CANISTER_RUNTIME_DEPS | UNIVERSAL_CANISTER_RUNTIME_DEPS | {
30+
BACKUP_RUNTIME_DEPS = MESSAGE_CANISTER_RUNTIME_DEPS | UNIVERSAL_CANISTER_RUNTIME_DEPS | MAINNET_NNS_REPLAY_RUNTIME_DEPS | {
3131
"IC_BACKUP_PATH": "//rs/backup:ic-backup",
3232
"IC_REPLAY_PATH": "//rs/replay:ic-replay",
3333
"COMPILER_SANDBOX_PATH": "//rs/canister_sandbox:compiler_sandbox",
3434
"SANDBOX_LAUNCHER_PATH": "//rs/canister_sandbox:sandbox_launcher",
3535
"CANISTER_SANDBOX_PATH": "//rs/canister_sandbox:canister_sandbox",
36+
"IC_VERSION_FILE": "//bazel:version.txt",
3637
}
3738

3839
rust_binary(
@@ -50,17 +51,8 @@ rust_binary(
5051

5152
system_test_nns(
5253
name = "backup_manager_upgrade_test",
53-
# Pinned to the Farm backend. This test drives `ic-backup`, which replays the
54-
# subnet's artifacts with a version-specific `ic-replay` binary. For the initial
55-
# mainnet replica version (`guestos = "mainnet_nns"`) the test does not provide
56-
# that binary locally, so `ic-backup` downloads it from download.dfinity.systems.
57-
# The `_local` variant runs in a network-isolated sandbox (no `requires-network`
58-
# tag), so the download fails, replay never progresses, no checkpoint is ever
59-
# archived, and the test times out. Supporting the local backend would require
60-
# vendoring the mainnet `ic-replay`/sandbox binaries (none are available offline
61-
# today) and copying them into `binaries/{initial_replica_version}/`.
62-
backend = "farm",
6354
cpus = MIN_LOCAL_CPUS + 4 * DEFAULT_VCPUS_PER_VM, # 4 IC Node VMs * 6 vCPUs.
55+
env = MAINNET_ENV,
6456
guestos = "mainnet_nns",
6557
guestos_update = True,
6658
tags = [
@@ -69,28 +61,19 @@ system_test_nns(
6961
],
7062
test_driver_target = ":backup_manager_test_bin",
7163
test_timeout = "eternal", # this test often times out with the default 15 minute timeout so we allow more time
72-
runtime_deps = BACKUP_RUNTIME_DEPS | {
73-
"IC_VERSION_FILE": "//bazel:version.txt",
74-
},
64+
runtime_deps = BACKUP_RUNTIME_DEPS,
7565
)
7666

7767
system_test_nns(
7868
name = "backup_manager_downgrade_test",
79-
# Pinned to the Farm backend for the same reason as `backup_manager_upgrade_test`:
80-
# `ic-backup` downloads the mainnet `ic-replay` binary (here for the
81-
# `guestos_update = "mainnet_nns"` target version) from download.dfinity.systems,
82-
# which the network-isolated `_local` variant cannot reach, so replay stalls and
83-
# the test times out.
84-
backend = "farm",
8569
cpus = MIN_LOCAL_CPUS + 4 * DEFAULT_VCPUS_PER_VM, # 4 IC Node VMs * 6 vCPUs.
70+
env = MAINNET_ENV,
8671
guestos_update = "mainnet_nns",
8772
tags = [
8873
"colocate",
8974
"long_test", # since it takes longer than 5 minutes.
9075
],
9176
test_driver_target = ":backup_manager_test_bin",
9277
test_timeout = "eternal", # this test often times out with the default 15 minute timeout so we allow more time
93-
runtime_deps = BACKUP_RUNTIME_DEPS | {
94-
"IC_VERSION_FILE": "//bazel:version.txt",
95-
},
78+
runtime_deps = BACKUP_RUNTIME_DEPS,
9679
)

rs/tests/consensus/backup/common.rs

Lines changed: 85 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,36 @@ use ic_system_test_driver::{
5555
use ic_types::Height;
5656
use slog::{Logger, debug, error, info};
5757
use std::{
58+
collections::BTreeSet,
5859
ffi::OsStr,
5960
fs,
6061
io::Write,
6162
net::IpAddr,
63+
os::unix::fs::PermissionsExt,
6264
path::{Path, PathBuf},
6365
process::{Command, Stdio},
6466
};
6567
use std::{fs::File, time::Duration};
6668

69+
/// (destination file name, runtime dependency env var) of the binaries `ic-backup`
70+
/// needs to replay a replica version, for the version built from this branch. See
71+
/// `BACKUP_RUNTIME_DEPS` in BUILD.bazel. `ic-replay` looks the three sandbox
72+
/// binaries up next to itself, which is why all four go into the same directory.
73+
const BRANCH_BINARIES: &[(&str, &str)] = &[
74+
("ic-replay", "IC_REPLAY_PATH"),
75+
("sandbox_launcher", "SANDBOX_LAUNCHER_PATH"),
76+
("canister_sandbox", "CANISTER_SANDBOX_PATH"),
77+
("compiler_sandbox", "COMPILER_SANDBOX_PATH"),
78+
];
79+
80+
/// The same binaries, as published for the mainnet NNS subnet's replica version.
81+
const MAINNET_NNS_BINARIES: &[(&str, &str)] = &[
82+
("ic-replay", "MAINNET_NNS_IC_REPLAY_PATH"),
83+
("sandbox_launcher", "MAINNET_NNS_SANDBOX_LAUNCHER_PATH"),
84+
("canister_sandbox", "MAINNET_NNS_CANISTER_SANDBOX_PATH"),
85+
("compiler_sandbox", "MAINNET_NNS_COMPILER_SANDBOX_PATH"),
86+
];
87+
6788
const DKG_INTERVAL: u64 = 29;
6889
const SUBNET_SIZE: usize = 4;
6990
const DIVERGENCE_LOG_STR: &str = "The state hash of the CUP at height ";
@@ -139,34 +160,53 @@ pub fn test(env: TestEnv) {
139160
let initial_replica_version =
140161
get_assigned_replica_version(&nns_node).expect("There should be assigned replica version");
141162

142-
info!(
143-
log,
144-
"Copy the binaries needed for replay of the current version"
145-
);
146-
let backup_binaries_dir = backup_dir.join("binaries").join(binary_version.to_string());
147-
fs::create_dir_all(&backup_binaries_dir).expect("failure creating backup binaries directory");
148-
149-
// Copy all the binaries needed for the replay of the current version in order to avoid downloading them
150-
copy_file(
151-
&get_dependency_path_from_env("IC_REPLAY_PATH"),
152-
&backup_binaries_dir,
153-
"ic-replay",
154-
);
155-
copy_file(
156-
&get_dependency_path_from_env("SANDBOX_LAUNCHER_PATH"),
157-
&backup_binaries_dir,
158-
"sandbox_launcher",
159-
);
160-
copy_file(
161-
&get_dependency_path_from_env("CANISTER_SANDBOX_PATH"),
162-
&backup_binaries_dir,
163-
"canister_sandbox",
164-
);
165-
copy_file(
166-
&get_dependency_path_from_env("COMPILER_SANDBOX_PATH"),
167-
&backup_binaries_dir,
168-
"compiler_sandbox",
169-
);
163+
// `ic-backup` replays each replica version's artifacts with that version's own
164+
// binaries and downloads them from download.dfinity.systems when they are not
165+
// already in `binaries/<version>/`. These tests run without external network
166+
// access on the local backend, so every version that will be replayed has to be
167+
// provided up front:
168+
//
169+
// * backup_manager_upgrade_test: mainnet NNS (initial) -> branch (target)
170+
// * backup_manager_downgrade_test: branch (initial) -> mainnet NNS (target)
171+
//
172+
// Both are covered by seeding the initial, the target and the branch version. A
173+
// version we have no binaries for fails here instead of stalling much later on a
174+
// download that cannot succeed.
175+
let mainnet_version =
176+
get_mainnet_nns_revision().expect("could not read the mainnet NNS revision");
177+
for version in BTreeSet::from([
178+
binary_version.clone(),
179+
initial_replica_version.clone(),
180+
target_version.clone(),
181+
]) {
182+
let binaries = if version == binary_version {
183+
BRANCH_BINARIES
184+
} else if version == mainnet_version {
185+
MAINNET_NNS_BINARIES
186+
} else {
187+
panic!(
188+
"No `ic-replay` available for replica version {version}. This test provides the \
189+
branch version ({binary_version}) and the mainnet NNS version ({mainnet_version}) \
190+
as bazel dependencies and cannot download any other version."
191+
);
192+
};
193+
let backup_binaries_dir = backup_dir.join("binaries").join(version.to_string());
194+
fs::create_dir_all(&backup_binaries_dir)
195+
.expect("failure creating backup binaries directory");
196+
info!(
197+
log,
198+
"Copying the {} binaries needed for replay to {}",
199+
version,
200+
backup_binaries_dir.display()
201+
);
202+
for (file_name, env_var) in binaries {
203+
copy_file(
204+
&get_dependency_path_from_env(env_var),
205+
&backup_binaries_dir,
206+
file_name,
207+
);
208+
}
209+
}
170210

171211
// Generate keypair and store the private key
172212
info!(log, "Create backup user credentials");
@@ -488,7 +528,23 @@ fn dir_exists_and_have_file(log: &Logger, dir: &PathBuf) -> bool {
488528
}
489529

490530
fn copy_file(binary_path: &Path, backup_binaries_dir: &Path, file_name: &str) {
491-
fs::copy(binary_path, backup_binaries_dir.join(file_name)).expect("failed to copy file");
531+
// A dependency that lost its executable bit would only surface much later, as
532+
// `ic-replay` failing to spawn its sandbox, so check it here at the source.
533+
let mode = fs::metadata(binary_path)
534+
.unwrap_or_else(|e| panic!("failed to stat {binary_path:?}: {e}"))
535+
.permissions()
536+
.mode();
537+
assert!(
538+
mode & 0o111 != 0,
539+
"{binary_path:?} is not executable (mode {mode:o}); the bazel dependency providing it \
540+
must produce an executable file"
541+
);
542+
let target = backup_binaries_dir.join(file_name);
543+
fs::copy(binary_path, &target).expect("failed to copy file");
544+
// `fs::copy` inherits the source's mode and bazel outputs are read-only, so make
545+
// the copy writable in case anything wants to replace it.
546+
fs::set_permissions(&target, fs::Permissions::from_mode(0o755))
547+
.expect("failed to set the permissions of the copied binary");
492548
}
493549

494550
fn highest_dir_entry(dir: &PathBuf, radix: u32) -> u64 {

0 commit comments

Comments
 (0)