From 5cee25ef701421fba162472bd8ff2ba24b3328bb Mon Sep 17 00:00:00 2001 From: David Frank Date: Wed, 21 May 2025 22:55:02 +0200 Subject: [PATCH 01/47] Replace usages of the `build-bootstrap-config-image.sh` script with a new Rust-based implementation for generating bootstrap configuration images --- ic-os/components/BUILD.bazel | 1 - ic-os/defs.bzl | 2 - ic-os/dev-tools/launch-remote-vm.sh | 2 +- rs/ic_os/bootstrap_config/BUILD.bazel | 35 + rs/ic_os/bootstrap_config/Cargo.toml | 19 + rs/ic_os/bootstrap_config/src/lib.rs | 606 ++++++++++++++++++ .../launch-single-vm/Cargo.toml | 1 + .../launch-single-vm/src/main.rs | 31 +- rs/tests/common.bzl | 5 +- rs/tests/consensus/BUILD.bazel | 2 +- rs/tests/consensus/tecdsa/BUILD.bazel | 1 - rs/tests/driver/src/driver/bootstrap.rs | 88 ++- rs/tests/message_routing/BUILD.bazel | 4 +- rs/tests/message_routing/xnet/BUILD.bazel | 2 +- 14 files changed, 711 insertions(+), 88 deletions(-) create mode 100644 rs/ic_os/bootstrap_config/BUILD.bazel create mode 100644 rs/ic_os/bootstrap_config/Cargo.toml create mode 100644 rs/ic_os/bootstrap_config/src/lib.rs diff --git a/ic-os/components/BUILD.bazel b/ic-os/components/BUILD.bazel index bbe30f53ae75..580aaee6d7f4 100644 --- a/ic-os/components/BUILD.bazel +++ b/ic-os/components/BUILD.bazel @@ -11,7 +11,6 @@ PUBLIC_GUESTOS_EXPORTS = [ "ic/generate-ic-config/ic.json5.template", "networking/dev-certs/canister_http_test_ca.cert", "networking/dev-certs/canister_http_test_ca.key", - "hostos-scripts/build-bootstrap-config-image.sh", ] exports_files(PUBLIC_GUESTOS_EXPORTS) diff --git a/ic-os/defs.bzl b/ic-os/defs.bzl index 1fa310b5f85c..6f17d2b3fdb3 100644 --- a/ic-os/defs.bzl +++ b/ic-os/defs.bzl @@ -347,7 +347,6 @@ EOF srcs = ["//ic-os:dev-tools/launch-remote-vm.sh"], data = [ "//rs/ic_os/dev_test_tools/launch-single-vm:launch-single-vm", - "//ic-os/components:hostos-scripts/build-bootstrap-config-image.sh", ":disk-img.tar.zst", "//rs/tests/nested:empty-disk-img.tar.zst", ":version.txt", @@ -356,7 +355,6 @@ EOF env = { "BIN": "$(location //rs/ic_os/dev_test_tools/launch-single-vm:launch-single-vm)", "UPLOAD_SYSTEST_DEP": "$(location //bazel:upload_systest_dep)", - "SCRIPT": "$(location //ic-os/components:hostos-scripts/build-bootstrap-config-image.sh)", "VERSION_FILE": "$(location :version.txt)", "DISK_IMG": "$(location :disk-img.tar.zst)", "EMPTY_DISK_IMG_PATH": "$(location //rs/tests/nested:empty-disk-img.tar.zst)", diff --git a/ic-os/dev-tools/launch-remote-vm.sh b/ic-os/dev-tools/launch-remote-vm.sh index dc30f789c7a7..3ba6839f60f4 100755 --- a/ic-os/dev-tools/launch-remote-vm.sh +++ b/ic-os/dev-tools/launch-remote-vm.sh @@ -6,7 +6,7 @@ set -euo pipefail image_download_url=$("$UPLOAD_SYSTEST_DEP" "$DISK_IMG") sha256="${image_download_url##*/}" -cmd="$BIN --version $(cat "$VERSION_FILE") --url "$image_download_url" --sha256 "$sha256" --build-bootstrap-script $(realpath "$SCRIPT")" +cmd="$BIN --version $(cat "$VERSION_FILE") --url "$image_download_url" --sha256 "$sha256"" # Hack to switch nested for SetupOS if [[ "$0" =~ "setupos" ]]; then diff --git a/rs/ic_os/bootstrap_config/BUILD.bazel b/rs/ic_os/bootstrap_config/BUILD.bazel new file mode 100644 index 000000000000..c756adbda2bd --- /dev/null +++ b/rs/ic_os/bootstrap_config/BUILD.bazel @@ -0,0 +1,35 @@ +# In bootstrap_config/BUILD.bazel +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") + +package(default_visibility = ["//visibility:public"]) + +DEPENDENCIES = [ + # Keep sorted. + "@crate_index//:anyhow", + "@crate_index//:regex", + "@crate_index//:tempfile", +] + +DEV_DEPENDENCIES = [] + +rust_library( + name = "bootstrap_config", + srcs = glob(["src/**/*.rs"]), + crate_name = "bootstrap_config", + deps = DEPENDENCIES, +) + +# Dev version for testing that should only be enabled in dev builds (unsafe for prod). +rust_library( + name = "bootstrap_config_dev", + srcs = glob(["src/**/*.rs"]), + crate_features = ["dev"], + crate_name = "bootstrap_config", + deps = DEPENDENCIES, +) + +rust_test( + name = "bootstrap_config_test", + crate = ":bootstrap_config_dev", + deps = DEV_DEPENDENCIES, +) diff --git a/rs/ic_os/bootstrap_config/Cargo.toml b/rs/ic_os/bootstrap_config/Cargo.toml new file mode 100644 index 000000000000..8eb8bac57b5e --- /dev/null +++ b/rs/ic_os/bootstrap_config/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "bootstrap_config" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = { workspace = true } +regex = { workspace = true } +tempfile = { workspace = true } + +[features] +dev = [] # Features for testing that should only be enabled in dev builds (unsafe for prod) + +[profile.release] +opt-level = 'z' # Optimize for size +lto = true # Enable link-time optimization +codegen-units = 1 # Reduce parallel code generation units to increase optimizations +strip = true # Strip symbols from binary + diff --git a/rs/ic_os/bootstrap_config/src/lib.rs b/rs/ic_os/bootstrap_config/src/lib.rs new file mode 100644 index 000000000000..40600c49f8b7 --- /dev/null +++ b/rs/ic_os/bootstrap_config/src/lib.rs @@ -0,0 +1,606 @@ +use std::fmt::Write as _; +use std::fs::{self, File}; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::process::Command; + +use anyhow::{Context, Result, bail}; +use regex::Regex; + +/// Configuration options for bootstrap image/tar creation +#[derive(Default, Debug, Clone)] +pub struct BootstrapOptions { + /// Path to the serialized GuestOS config object + pub guestos_config: Option, + + /// Path to the NNS public key file + pub nns_public_key: Option, + + /// Path to the Node Operator private key PEM + pub node_operator_private_key: Option, + + /// Path to directory with SSH authorized keys for specific user accounts + #[cfg(feature = "dev")] + pub accounts_ssh_authorized_keys: Option, + + /// Path to injected crypto state directory + pub ic_crypto: Option, + + /// Path to injected state directory + pub ic_state: Option, + + /// Path to injected initial registry state directory + pub ic_registry_local_store: Option, + + /// IPv6 address with netmask (e.g., "dead:beef::1/64") + pub ipv6_address: Option, + + /// Default IPv6 gateway + pub ipv6_gateway: Option, + + /// IPv4 address with prefix length (e.g., "18.208.190.35/28") + pub ipv4_address: Option, + + /// Default IPv4 gateway + pub ipv4_gateway: Option, + + /// Domain name to assign to the guest + pub domain: Option, + + /// Node reward type for determining node rewards + pub node_reward_type: Option, + + /// Hostname for the host (used in logging) + pub hostname: Option, + + /// Logging hosts to use + pub elasticsearch_hosts: Vec, + + /// Tags for Filebeat + pub elasticsearch_tags: Vec, + + /// URL of NNS nodes for sign up or registry access + pub nns_urls: Vec, + + /// Backup retention time in seconds + pub backup_retention_time: Option, + + /// Backup purging interval in seconds + pub backup_purging_interval: Option, + + /// Malicious behavior JSON object (for testing) + pub malicious_behavior: Option, + + /// Query stats epoch length in seconds (for testing) + pub query_stats_epoch_length: Option, + + /// IP address of a running bitcoind instance (for testing) + pub bitcoind_addr: Option, + + /// IP address of a running Jaeger Collector instance (for testing) + pub jaeger_addr: Option, + + /// URL of the socks proxy to use (for testing) + pub socks_proxy: Option, +} + +/// Generate network configuration content from config. +/// +/// The config must be valid. +fn generate_network_conf(config: &BootstrapOptions) -> Result { + let mut network_conf = String::new(); + + if let Some(ipv6_address) = &config.ipv6_address { + writeln!(network_conf, "ipv6_address={ipv6_address}")?; + } + + if let Some(ipv6_gateway) = &config.ipv6_gateway { + writeln!(network_conf, "ipv6_gateway={ipv6_gateway}")?; + } + + writeln!( + network_conf, + "hostname={}", + valid_hostname_or_error(config)? + )?; + + if let Some(ipv4_address) = &config.ipv4_address { + writeln!(network_conf, "ipv4_address={ipv4_address}")?; + } + + if let Some(ipv4_gateway) = &config.ipv4_gateway { + writeln!(network_conf, "ipv4_gateway={ipv4_gateway}")?; + } + + if let Some(domain) = &config.domain { + writeln!(network_conf, "domain={domain}")?; + } + + Ok(network_conf) +} + +fn valid_hostname_or_error(bootstrap_options: &BootstrapOptions) -> Result<&str> { + let Some(hostname) = bootstrap_options.hostname.as_ref() else { + bail!("Hostname is required"); + }; + + let pattern = Regex::new(r"^[a-zA-Z][a-zA-Z0-9]*(-[a-zA-Z0-9]+)*$").unwrap(); + if hostname.is_empty() || !pattern.is_match(hostname) { + bail!("Invalid hostname: '{hostname}'"); + } + + Ok(hostname) +} + +/// Build a bootstrap tar file with the given configuration +fn build_bootstrap_tar(out_file: &Path, config: &BootstrapOptions) -> Result<()> { + // Create temporary directory for bootstrap files + let bootstrap_dir = tempfile::tempdir().context("Failed to create temporary directory")?; + + // Copy files to the temporary directory + if let Some(guestos_config) = &config.guestos_config { + fs::copy(guestos_config, bootstrap_dir.path().join("config.json")) + .context("Failed to copy guestos config")?; + } + + if let Some(nns_public_key) = &config.nns_public_key { + fs::copy( + nns_public_key, + bootstrap_dir.path().join("nns_public_key.pem"), + ) + .context("Failed to copy NNS public key")?; + } + + if let Some(node_operator_private_key) = &config.node_operator_private_key { + fs::copy( + node_operator_private_key, + bootstrap_dir.path().join("node_operator_private_key.pem"), + ) + .context("Failed to copy node operator private key")?; + } + + #[cfg(feature = "dev")] + if let Some(accounts_ssh_authorized_keys) = &config.accounts_ssh_authorized_keys { + let target_dir = bootstrap_dir.path().join("accounts_ssh_authorized_keys"); + copy_dir_recursively(accounts_ssh_authorized_keys, &target_dir) + .context("Failed to copy SSH authorized keys")?; + } + + if let Some(ic_crypto) = &config.ic_crypto { + copy_dir_recursively(ic_crypto, &bootstrap_dir.path().join("ic_crypto")) + .context("Failed to copy IC crypto directory")?; + } + + if let Some(ic_state) = &config.ic_state { + if ic_state.exists() { + copy_dir_recursively(ic_state, &bootstrap_dir.path().join("ic_state")) + .context("Failed to copy IC state directory")?; + } + } + + if let Some(ic_registry_local_store) = &config.ic_registry_local_store { + copy_dir_recursively( + ic_registry_local_store, + &bootstrap_dir.path().join("ic_registry_local_store"), + ) + .context("Failed to copy registry local store")?; + } + + // Create network.conf + fs::write( + bootstrap_dir.path().join("network.conf"), + generate_network_conf(config)?, + ) + .context("Failed to write network.conf")?; + + // Create reward.conf if node_reward_type is set + if let Some(node_reward_type) = &config.node_reward_type { + fs::write( + bootstrap_dir.path().join("reward.conf"), + format!("node_reward_type={node_reward_type}\n"), + ) + .context("Failed to write reward.conf")?; + } + + // Create filebeat.conf if elasticsearch_hosts is set + if !config.elasticsearch_hosts.is_empty() { + let space_separated_hosts = config.elasticsearch_hosts.join(" "); + let mut filebeat_config = File::create(bootstrap_dir.path().join("filebeat.conf")) + .context("Failed to create filebeat.conf")?; + + writeln!( + filebeat_config, + "elasticsearch_hosts={space_separated_hosts}" + )?; + if !&config.elasticsearch_tags.is_empty() { + let space_separated_tags = config.elasticsearch_tags.join(" "); + writeln!(filebeat_config, "elasticsearch_tags={space_separated_tags}")?; + } + } + + // Create nns.conf if nns_urls are available + if !config.nns_urls.is_empty() { + let comma_separated_urls = config.nns_urls.join(","); + fs::write( + bootstrap_dir.path().join("nns.conf"), + format!("nns_url={comma_separated_urls}\n"), + ) + .context("Failed to write nns.conf")?; + } + + // Create backup.conf if backup settings are set + if let Some(backup_retention_time) = config.backup_retention_time { + let mut backup_conf = File::create(&bootstrap_dir.path().join("backup.conf")) + .context("Failed to create backup.conf")?; + + writeln!( + backup_conf, + "backup_retention_time_secs={backup_retention_time}" + )?; + + if let Some(backup_purging_interval) = config.backup_purging_interval { + writeln!( + backup_conf, + "backup_puging_interval_secs={backup_purging_interval}" + )?; + } + } + + // Create malicious_behavior.conf if malicious_behavior is set + if let Some(malicious_behavior) = &config.malicious_behavior { + fs::write( + bootstrap_dir.path().join("malicious_behavior.conf"), + format!("malicious_behavior={malicious_behavior}\n"), + ) + .context("Failed to write malicious_behavior.conf")?; + } + + // Create query_stats.conf if query_stats_epoch_length is set + if let Some(query_stats_epoch_length) = config.query_stats_epoch_length { + fs::write( + bootstrap_dir.path().join("query_stats.conf"), + format!("query_stats_epoch_length={query_stats_epoch_length}\n"), + ) + .context("Failed to write query_stats.conf")?; + } + + // Create bitcoind_addr.conf if bitcoind_addr is set + if let Some(bitcoind_addr) = &config.bitcoind_addr { + fs::write( + bootstrap_dir.path().join("bitcoind_addr.conf"), + format!("bitcoind_addr={bitcoind_addr}\n"), + ) + .context("Failed to write bitcoind_addr.conf")?; + } + + // Create jaeger_addr.conf if jaeger_addr is set + if let Some(jaeger_addr) = &config.jaeger_addr { + fs::write( + bootstrap_dir.path().join("jaeger_addr.conf"), + format!("jaeger_addr=http://{jaeger_addr}\n"), + ) + .context("Failed to write jaeger_addr.conf")?; + } + + // Create socks_proxy.conf if socks_proxy is set + if let Some(socks_proxy) = &config.socks_proxy { + fs::write( + bootstrap_dir.path().join("socks_proxy.conf"), + format!("socks_proxy={socks_proxy}\n"), + ) + .context("Failed to write socks_proxy.conf")?; + } + + // Create tar file + let status = Command::new("tar") + .arg("cf") + .arg(out_file) + .arg("--sort=name") + .arg("--owner=root:0") + .arg("--group=root:0") + .arg("--mtime=UTC 1970-01-01 00:00:00") + .arg("-C") + .arg(bootstrap_dir.path()) + .arg(".") + .status() + .context("Failed to execute tar command")?; + + if !status.success() { + bail!("Failed to create tar file: {status}"); + } + + Ok(()) +} + +/// Build a bootstrap disk image with the given configuration +pub fn build_bootstrap_config_image(out_file: &Path, config: &BootstrapOptions) -> Result<()> { + let tmp_dir = tempfile::tempdir().context("Failed to create temporary directory")?; + + // Create bootstrap tar + let tar_path = tmp_dir.path().join("ic-bootstrap.tar"); + build_bootstrap_tar(&tar_path, config)?; + + let tar_size = fs::metadata(&tar_path) + .context("Failed to get tar file metadata")? + .len(); + + // Calculate the disk image size (2 * tar_size + 1MB) + let image_size = 2 * tar_size + 1_048_576; + + // Create an empty file of the calculated size + let file = File::create(out_file).context("Failed to create output file")?; + file.set_len(image_size) + .context("Failed to set output file size")?; + + // Format the disk image as FAT + let status = Command::new("mkfs.vfat") + .arg("-n") + .arg("CONFIG") + .arg(out_file) + .status() + .context("Failed to execute mkfs.vfat command")?; + + if !status.success() { + bail!("Failed to format disk image: {status}"); + } + + // Copy the tar file to the disk image + let status = Command::new("mcopy") + .arg("-i") + .arg(out_file) + .arg("-o") + .arg(&tar_path) + .arg("::") + .status() + .context("Failed to execute mcopy command")?; + + if !status.success() { + bail!("Failed to copy tar to disk image: {status}"); + } + + Ok(()) +} + +fn copy_dir_recursively(src: &Path, dst: &Path) -> Result<()> { + if !Command::new("cp") + .arg("-r") + .arg(src) + .arg(dst) + .status() + .context(format!( + "Failed to copy {} to {}", + src.display(), + dst.display() + ))? + .success() + { + bail!("Failed to copy {} to {}", src.display(), dst.display()); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_is_valid_hostname() { + fn is_valid_hostname(hostname: &str) -> bool { + let mut config = BootstrapOptions::default(); + config.hostname = Some(hostname.to_string()); + valid_hostname_or_error(&config).is_ok() + } + + // Valid hostnames + assert!(is_valid_hostname("hostname")); + assert!(is_valid_hostname("hostname123")); + assert!(is_valid_hostname("hostname-part2")); + assert!(is_valid_hostname("h-1-2-3")); + + // Invalid hostnames + assert!(!is_valid_hostname("")); + assert!(!is_valid_hostname("123hostname")); + assert!(!is_valid_hostname("hostname-")); + assert!(!is_valid_hostname("-hostname")); + assert!(!is_valid_hostname("hostname_invalid")); + assert!(!is_valid_hostname("hostname with spaces")); + } + + #[test] + fn test_build_bootstrap_tar_fails_with_default_options() { + let tmp_dir = tempfile::tempdir().unwrap(); + let out_file = tmp_dir.path().join("bootstrap.tar"); + + let config = BootstrapOptions::default(); + + assert!(build_bootstrap_tar(&out_file, &config).is_err()); + } + + #[test] + fn test_build_bootstrap_tar() -> Result<()> { + // Create a temporary directory for the test + let tmp_dir = tempfile::tempdir()?; + let out_file = tmp_dir.path().join("bootstrap.tar"); + + // Create a minimal valid configuration + let mut config = BootstrapOptions::default(); + config.hostname = Some("testhostname".to_string()); + config.ipv6_address = Some("2001:db8::1/64".to_string()); + + // Create a test file to be included in the tar + let test_config_path = tmp_dir.path().join("test_config.json"); + fs::write(&test_config_path, r#"{"test": "value"}"#)?; + config.guestos_config = Some(test_config_path); + + // Build the tar file + build_bootstrap_tar(&out_file, &config)?; + + // Extract the tar file to verify contents + let extract_dir = tmp_dir.path().join("extract"); + fs::create_dir(&extract_dir)?; + + let status = Command::new("tar") + .arg("xf") + .arg(&out_file) + .arg("-C") + .arg(&extract_dir) + .status()?; + assert!(status.success()); + + // Verify network.conf contents + let network_conf = fs::read_to_string(extract_dir.join("network.conf"))?; + assert_eq!( + network_conf, + "ipv6_address=2001:db8::1/64\n\ + hostname=testhostname\n" + ); + + // Verify config.json contents + let config_json = fs::read_to_string(extract_dir.join("config.json"))?; + assert_eq!(config_json, r#"{"test": "value"}"#); + + Ok(()) + } + + #[test] + fn test_build_bootstrap_tar_with_all_options() -> Result<()> { + let tmp_dir = tempfile::tempdir()?; + let out_file = tmp_dir.path().join("bootstrap.tar"); + + // Create test files and directories + let test_files_dir = tmp_dir.path().join("test_files"); + fs::create_dir(&test_files_dir)?; + + let config_path = test_files_dir.join("config.json"); + fs::write(&config_path, r#"{"test": "config"}"#)?; + + let nns_key_path = test_files_dir.join("nns.pem"); + fs::write(&nns_key_path, "test_nns_key")?; + + let node_key_path = test_files_dir.join("node.pem"); + fs::write(&node_key_path, "test_node_key")?; + + let ssh_keys_dir = test_files_dir.join("ssh_keys"); + fs::create_dir(&ssh_keys_dir)?; + fs::write(ssh_keys_dir.join("key1"), "ssh_key1")?; + + let crypto_dir = test_files_dir.join("crypto"); + fs::create_dir(&crypto_dir)?; + fs::write(crypto_dir.join("test"), "crypto_data")?; + + let state_dir = test_files_dir.join("state"); + fs::create_dir(&state_dir)?; + fs::write(state_dir.join("test"), "state_data")?; + + let registry_dir = test_files_dir.join("registry"); + fs::create_dir(®istry_dir)?; + fs::write(registry_dir.join("test"), "registry_data")?; + + // Create full configuration + let config = BootstrapOptions { + hostname: Some("fulltest".to_string()), + guestos_config: Some(config_path), + nns_public_key: Some(nns_key_path), + node_operator_private_key: Some(node_key_path), + #[cfg(feature = "dev")] + accounts_ssh_authorized_keys: Some(ssh_keys_dir), + ic_crypto: Some(crypto_dir), + ic_state: Some(state_dir), + ic_registry_local_store: Some(registry_dir), + ipv6_address: Some("2001:db8::1/64".to_string()), + ipv6_gateway: Some("2001:db8::ff".to_string()), + ipv4_address: Some("192.168.1.1/24".to_string()), + ipv4_gateway: Some("192.168.1.254".to_string()), + domain: Some("test.domain".to_string()), + node_reward_type: Some("test_reward".to_string()), + elasticsearch_hosts: vec!["host1".to_string(), "host2".to_string()], + elasticsearch_tags: vec!["tag1".to_string(), "tag2".to_string()], + nns_urls: vec!["url1".to_string(), "url2".to_string()], + backup_retention_time: Some(3600), + backup_purging_interval: Some(300), + malicious_behavior: Some(r#"{"type": "test"}"#.to_string()), + query_stats_epoch_length: Some(60), + bitcoind_addr: Some("127.0.0.1:8332".to_string()), + jaeger_addr: Some("127.0.0.1:14250".to_string()), + socks_proxy: Some("socks5://127.0.0.1:1080".to_string()), + }; + + // Build and extract tar + build_bootstrap_tar(&out_file, &config)?; + let extract_dir = tmp_dir.path().join("extract"); + fs::create_dir(&extract_dir)?; + Command::new("tar") + .arg("xf") + .arg(&out_file) + .arg("-C") + .arg(&extract_dir) + .status()?; + + // Verify all files + assert_eq!( + fs::read_to_string(extract_dir.join("config.json"))?, + r#"{"test": "config"}"# + ); + assert_eq!( + fs::read_to_string(extract_dir.join("nns_public_key.pem"))?, + "test_nns_key" + ); + assert_eq!( + fs::read_to_string(extract_dir.join("node_operator_private_key.pem"))?, + "test_node_key" + ); + + let network_conf = fs::read_to_string(extract_dir.join("network.conf"))?; + assert!(network_conf.contains("hostname=fulltest\n")); + assert!(network_conf.contains("ipv6_address=2001:db8::1/64\n")); + assert!(network_conf.contains("ipv6_gateway=2001:db8::ff\n")); + assert!(network_conf.contains("ipv4_address=192.168.1.1/24\n")); + assert!(network_conf.contains("ipv4_gateway=192.168.1.254\n")); + assert!(network_conf.contains("domain=test.domain\n")); + + assert_eq!( + fs::read_to_string(extract_dir.join("reward.conf"))?, + "node_reward_type=test_reward\n" + ); + + let filebeat_conf = fs::read_to_string(extract_dir.join("filebeat.conf"))?; + assert!(filebeat_conf.contains("elasticsearch_hosts=host1 host2\n")); + assert!(filebeat_conf.contains("elasticsearch_tags=tag1 tag2\n")); + + assert_eq!( + fs::read_to_string(extract_dir.join("nns.conf"))?, + "nns_url=url1,url2\n" + ); + + let backup_conf = fs::read_to_string(extract_dir.join("backup.conf"))?; + assert!(backup_conf.contains("backup_retention_time_secs=3600\n")); + assert!(backup_conf.contains("backup_puging_interval_secs=300\n")); + + assert_eq!( + fs::read_to_string(extract_dir.join("malicious_behavior.conf"))?, + "malicious_behavior={\"type\": \"test\"}\n" + ); + + assert_eq!( + fs::read_to_string(extract_dir.join("query_stats.conf"))?, + "query_stats_epoch_length=60\n" + ); + + assert_eq!( + fs::read_to_string(extract_dir.join("bitcoind_addr.conf"))?, + "bitcoind_addr=127.0.0.1:8332\n" + ); + + assert_eq!( + fs::read_to_string(extract_dir.join("jaeger_addr.conf"))?, + "jaeger_addr=http://127.0.0.1:14250\n" + ); + + assert_eq!( + fs::read_to_string(extract_dir.join("socks_proxy.conf"))?, + "socks_proxy=socks5://127.0.0.1:1080\n" + ); + + Ok(()) + } +} diff --git a/rs/ic_os/dev_test_tools/launch-single-vm/Cargo.toml b/rs/ic_os/dev_test_tools/launch-single-vm/Cargo.toml index a62dce80db6a..24d3c24e4dab 100644 --- a/rs/ic_os/dev_test_tools/launch-single-vm/Cargo.toml +++ b/rs/ic_os/dev_test_tools/launch-single-vm/Cargo.toml @@ -12,6 +12,7 @@ ic-system-test-driver = { path = "../../../tests/driver" } ic-types = { path = "../../../types/types" } config = { path = "../../config" } config_types = { path = "../../config_types" } +bootstrap_config = { path = "../../bootstrap_config", features = ["dev"] } clap = { workspace = true } reqwest = { workspace = true } diff --git a/rs/ic_os/dev_test_tools/launch-single-vm/src/main.rs b/rs/ic_os/dev_test_tools/launch-single-vm/src/main.rs index 4c3e23d0ebd9..75960f18279c 100644 --- a/rs/ic_os/dev_test_tools/launch-single-vm/src/main.rs +++ b/rs/ic_os/dev_test_tools/launch-single-vm/src/main.rs @@ -19,14 +19,8 @@ use slog::{o, Drain}; use std::collections::BTreeMap; use std::net::{IpAddr, SocketAddr}; use std::path::PathBuf; -use std::process::Command; use tempfile::tempdir; use url::Url; - -use config::generate_testnet_config::{ - generate_testnet_config, GenerateTestnetConfigArgs, Ipv6ConfigType, -}; -use config_types::DeploymentEnvironment; const FARM_BASE_URL: &str = "https://farm.dfinity.systems"; /// Deploy a single ICOS VM to Farm @@ -41,9 +35,6 @@ struct Args { /// Image SHA256SUM #[clap(long)] sha256: String, - /// Path to `build-bootstrap-config-image.sh` script - #[clap(long)] - build_bootstrap_script: PathBuf, /// Key to be used for `admin` SSH #[clap(long)] ssh_key_path: Option, @@ -67,7 +58,6 @@ fn main() { let version = ReplicaVersion::try_from(args.version).unwrap(); let url = args.url; let sha256 = args.sha256; - let build_bootstrap_script = args.build_bootstrap_script; let ssh_key_path = args.ssh_key_path; let test_name = "test_single_vm"; @@ -238,18 +228,15 @@ fn main() { let filename = "config.tar.gz"; let config_path = tempdir.as_ref().join(filename); let local_store = prep_dir.join("ic_registry_local_store"); - Command::new(build_bootstrap_script) - .arg(&config_path) - .arg("--guestos_config") - .arg(guestos_config_json_path) - .arg("--ic_crypto") - .arg(node.crypto_path()) - .arg("--ic_registry_local_store") - .arg(&local_store) - .arg("--accounts_ssh_authorized_keys") - .arg(&keys_dir) - .status() - .unwrap(); + let bootstrap_options = BootstrapOptions { + guestos_config: Some(guestos_config_json_path), + ic_crypto: Some(node.crypto_path()), + ic_registry_local_store: Some(local_store), + accounts_ssh_authorized_keys: Some(keys_dir), + ..Default::default() + }; + + build_bootstrap_config_image(&config_path, &bootstrap_options).unwrap(); // Upload config image let image_id = farm diff --git a/rs/tests/common.bzl b/rs/tests/common.bzl index fe114d62391d..f14e27af5b60 100644 --- a/rs/tests/common.bzl +++ b/rs/tests/common.bzl @@ -7,10 +7,7 @@ load(":qualifying_nns_canisters.bzl", "QUALIFYING_NNS_CANISTERS", "QUALIFYING_SN GUESTOS_DEV_VERSION = "//ic-os/guestos/envs/dev:version.txt" -GUESTOS_RUNTIME_DEPS = [ - GUESTOS_DEV_VERSION, - "//ic-os/components:hostos-scripts/build-bootstrap-config-image.sh", -] +GUESTOS_RUNTIME_DEPS = [GUESTOS_DEV_VERSION] MAINNET_NNS_SUBNET_REVISION = mainnet_icos_versions["guestos"]["subnets"]["tdb26-jop6k-aogll-7ltgs-eruif-6kk7m-qpktf-gdiqx-mxtrf-vb5e6-eqe"] MAINNET_APPLICATION_SUBNET_REVISION = mainnet_icos_versions["guestos"]["subnets"]["io67a-2jmkw-zup3h-snbwi-g6a5n-rm5dn-b6png-lvdpl-nqnto-yih6l-gqe"] diff --git a/rs/tests/consensus/BUILD.bazel b/rs/tests/consensus/BUILD.bazel index 124e7f98607c..4a788e552b44 100644 --- a/rs/tests/consensus/BUILD.bazel +++ b/rs/tests/consensus/BUILD.bazel @@ -31,7 +31,7 @@ system_test( target_compatible_with = ["@platforms//os:linux"], # requires libssh that does not build on Mac OS test_timeout = "eternal", uses_guestos_dev = True, - runtime_deps = ["//ic-os/components:hostos-scripts/build-bootstrap-config-image.sh"] + GRAFANA_RUNTIME_DEPS, + runtime_deps = GRAFANA_RUNTIME_DEPS, deps = [ # Keep sorted. ":catch_up_test_common", diff --git a/rs/tests/consensus/tecdsa/BUILD.bazel b/rs/tests/consensus/tecdsa/BUILD.bazel index 884f4b374c4b..b95261328b3e 100644 --- a/rs/tests/consensus/tecdsa/BUILD.bazel +++ b/rs/tests/consensus/tecdsa/BUILD.bazel @@ -12,7 +12,6 @@ system_test_nns( target_compatible_with = ["@platforms//os:linux"], # requires libssh that does not build on Mac OS uses_guestos_dev = True, uses_guestos_dev_test = True, - runtime_deps = ["//ic-os/components:hostos-scripts/build-bootstrap-config-image.sh"], deps = [ # Keep sorted. "//rs/nns/constants", diff --git a/rs/tests/driver/src/driver/bootstrap.rs b/rs/tests/driver/src/driver/bootstrap.rs index 750a63be8b00..8e235ca0627d 100644 --- a/rs/tests/driver/src/driver/bootstrap.rs +++ b/rs/tests/driver/src/driver/bootstrap.rs @@ -25,7 +25,8 @@ use crate::{ }, k8s::job::wait_for_job_completion, }; -use anyhow::{bail, Result}; +use anyhow::{bail, Context, Result}; +use bootstrap_config::{build_bootstrap_config_image, BootstrapOptions}; use config::generate_testnet_config::{ generate_testnet_config, GenerateTestnetConfigArgs, Ipv6ConfigType, }; @@ -508,101 +509,84 @@ fn create_config_disk_image( generate_testnet_config(config, guestos_config_json_path.clone())?; let img_path = PathBuf::from(&node.node_path).join(CONF_IMG_FNAME); - let script_path = - get_dependency_path("ic-os/components/hostos-scripts/build-bootstrap-config-image.sh"); - let mut cmd = Command::new(script_path); let local_store_path = test_env .prep_dir(ic_name) .expect("no no-name IC") .registry_local_store_path(); - cmd.arg(img_path.clone()) - .arg("--guestos_config") - .arg(guestos_config_json_path) - .arg("--ic_registry_local_store") - .arg(local_store_path) - .arg("--ic_state") - .arg(node.state_path()) - .arg("--ic_crypto") - .arg(node.crypto_path()); - - let ssh_authorized_pub_keys_dir: PathBuf = test_env.get_path(SSH_AUTHORIZED_PUB_KEYS_DIR); + let mut bootstrap_options = BootstrapOptions { + guestos_config: Some(guestos_config_json_path), + ic_registry_local_store: Some(local_store_path), + ic_state: Some(node.state_path()), + ic_crypto: Some(node.crypto_path()), + ..Default::default() + }; + + let ssh_authorized_pub_keys_dir = test_env.get_path(SSH_AUTHORIZED_PUB_KEYS_DIR); if ssh_authorized_pub_keys_dir.exists() { - cmd.arg("--accounts_ssh_authorized_keys") - .arg(ssh_authorized_pub_keys_dir); + bootstrap_options.accounts_ssh_authorized_keys = Some(ssh_authorized_pub_keys_dir); } // TODO(NODE-1518): remove passing old config (only exists to pass *downgrade* CI tests) if InfraProvider::read_attribute(test_env) == InfraProvider::K8s { - cmd.arg("--ipv6_address") - .arg(format!("{}/64", node.node_config.public_api.ip())) - .arg("--ipv6_gateway") - .arg("fe80::ecee:eeff:feee:eeee"); + bootstrap_options.ipv6_address = Some(format!("{}/64", node.node_config.public_api.ip())); + bootstrap_options.ipv6_gateway = Some("fe80::ecee:eeff:feee:eeee".to_string()); } + if let Some(node) = test_env .topology_snapshot_by_name(ic_name) .root_subnet() .nodes() .next() { - cmd.arg("--nns_urls") - .arg(format!("http://[{}]:8080", node.get_ip_addr())); + bootstrap_options + .nns_urls + .push(format!("http://[{}]:8080", node.get_ip_addr())); } + if let Some(malicious_behavior) = malicious_behavior { - cmd.arg("--malicious_behavior") - .arg(serde_json::to_string(&malicious_behavior)?); + bootstrap_options.malicious_behavior = Some(serde_json::to_string(&malicious_behavior)?); } + if let Some(query_stats_epoch_length) = query_stats_epoch_length { - cmd.arg("--query_stats_epoch_length") - .arg(format!("{}", query_stats_epoch_length)); + bootstrap_options.query_stats_epoch_length = Some(query_stats_epoch_length); } + if let Some(ipv4_config) = ipv4_config { - cmd.arg("--ipv4_address").arg(format!( + bootstrap_options.ipv4_address = Some(format!( "{}/{:?}", ipv4_config.ip_addr(), ipv4_config.prefix_length() )); - cmd.arg("--ipv4_gateway").arg(ipv4_config.gateway_ip_addr()); + bootstrap_options.ipv4_gateway = Some(ipv4_config.gateway_ip_addr().to_string()); } + if let Some(domain_name) = domain_name { - cmd.arg("--domain").arg(domain_name); + bootstrap_options.domain = Some(domain_name); } + if !elasticsearch_hosts.is_empty() { - cmd.arg("--elasticsearch_hosts") - .arg(elasticsearch_hosts.join(" ")); + bootstrap_options.elasticsearch_hosts = elasticsearch_hosts.clone(); } // The bitcoind address specifies the local bitcoin node that the bitcoin adapter should connect to in the system test environment. if let Ok(arg) = test_env.read_json_object::(BITCOIND_ADDR_PATH) { - cmd.arg("--bitcoind_addr").arg(arg); + bootstrap_options.bitcoind_addr = Some(arg); } + // The jaeger address specifies the local Jaeger node that the nodes should connect to in the system test environment. if let Ok(arg) = test_env.read_json_object::(JAEGER_ADDR_PATH) { - cmd.arg("--jaeger_addr").arg(arg); + bootstrap_options.jaeger_addr = Some(arg); } + // The socks proxy configuration indicates that a socks proxy is available to the system test environment. if let Ok(arg) = test_env.read_json_object::(SOCKS_PROXY_PATH) { - cmd.arg("--socks_proxy").arg(arg); + bootstrap_options.socks_proxy = Some(arg); } - let key = "PATH"; - let old_path = match std::env::var(key) { - Ok(val) => { - println!("{}: {:?}", key, val); - val - } - Err(e) => { - bail!("couldn't interpret {}: {}", key, e) - } - }; - cmd.env("PATH", format!("{}:{}", "/usr/sbin", old_path)); + build_bootstrap_config_image(&img_path, &bootstrap_options) + .context("Could not create bootstrap config image")?; - let output = cmd.output()?; - std::io::stdout().write_all(&output.stdout)?; - std::io::stderr().write_all(&output.stderr)?; - if !output.status.success() { - bail!("could not spawn image creation process"); - } let mut img_file = File::open(img_path)?; let compressed_img_path = PathBuf::from(&node.node_path).join(mk_compressed_img_path()); let compressed_img_file = File::create(compressed_img_path.clone())?; diff --git a/rs/tests/message_routing/BUILD.bazel b/rs/tests/message_routing/BUILD.bazel index 1d8e0f293bf8..899806b2b0e9 100644 --- a/rs/tests/message_routing/BUILD.bazel +++ b/rs/tests/message_routing/BUILD.bazel @@ -119,9 +119,7 @@ system_test_nns( ], target_compatible_with = ["@platforms//os:linux"], # requires libssh that does not build on Mac OS test_timeout = "eternal", - runtime_deps = GRAFANA_RUNTIME_DEPS + STATESYNC_TEST_CANISTER_RUNTIME_DEPS + UNIVERSAL_CANISTER_RUNTIME_DEPS + [ - "//ic-os/components:hostos-scripts/build-bootstrap-config-image.sh", - ], + runtime_deps = GRAFANA_RUNTIME_DEPS + STATESYNC_TEST_CANISTER_RUNTIME_DEPS + UNIVERSAL_CANISTER_RUNTIME_DEPS, deps = [ "//rs/registry/subnet_type", "//rs/tests/driver:ic-system-test-driver", diff --git a/rs/tests/message_routing/xnet/BUILD.bazel b/rs/tests/message_routing/xnet/BUILD.bazel index 8a6cbd2a0446..91e64a33dd38 100644 --- a/rs/tests/message_routing/xnet/BUILD.bazel +++ b/rs/tests/message_routing/xnet/BUILD.bazel @@ -96,7 +96,7 @@ system_test( ], target_compatible_with = ["@platforms//os:linux"], # requires libssh that does not build on Mac OS test_timeout = "long", - runtime_deps = ["//ic-os/components:hostos-scripts/build-bootstrap-config-image.sh"] + GRAFANA_RUNTIME_DEPS + XNET_TEST_CANISTER_RUNTIME_DEPS, + runtime_deps = GRAFANA_RUNTIME_DEPS + XNET_TEST_CANISTER_RUNTIME_DEPS, deps = [ "//rs/registry/subnet_type", "//rs/rust_canisters/canister_test", From 2f1ea1c8f0b9a73c760c998503b31ac99de749c0 Mon Sep 17 00:00:00 2001 From: David Frank Date: Thu, 22 May 2025 00:14:42 +0200 Subject: [PATCH 02/47] Cleanup --- Cargo.lock | 12 + rs/ic_os/bootstrap_config/BUILD.bazel | 3 + rs/ic_os/bootstrap_config/Cargo.toml | 10 +- rs/ic_os/bootstrap_config/src/lib.rs | 415 ++++++++++++++---------- rs/tests/driver/src/driver/bootstrap.rs | 2 +- 5 files changed, 263 insertions(+), 179 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8187dc1d65b9..fa5dbbd71091 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1537,6 +1537,17 @@ dependencies = [ "piper", ] +[[package]] +name = "bootstrap_config" +version = "0.0.0" +dependencies = [ + "anyhow", + "ic-types", + "regex", + "serde_json", + "tempfile", +] + [[package]] name = "borsh" version = "1.5.5" @@ -16095,6 +16106,7 @@ checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" name = "launch-single-vm" version = "0.1.0" dependencies = [ + "bootstrap_config", "clap 4.5.27", "config", "config_types", diff --git a/rs/ic_os/bootstrap_config/BUILD.bazel b/rs/ic_os/bootstrap_config/BUILD.bazel index c756adbda2bd..19dc57ee28a9 100644 --- a/rs/ic_os/bootstrap_config/BUILD.bazel +++ b/rs/ic_os/bootstrap_config/BUILD.bazel @@ -5,8 +5,10 @@ package(default_visibility = ["//visibility:public"]) DEPENDENCIES = [ # Keep sorted. + "//rs/types/types", "@crate_index//:anyhow", "@crate_index//:regex", + "@crate_index//:serde_json", "@crate_index//:tempfile", ] @@ -31,5 +33,6 @@ rust_library( rust_test( name = "bootstrap_config_test", crate = ":bootstrap_config_dev", + crate_features = ["dev"], deps = DEV_DEPENDENCIES, ) diff --git a/rs/ic_os/bootstrap_config/Cargo.toml b/rs/ic_os/bootstrap_config/Cargo.toml index 8eb8bac57b5e..94bd2a869991 100644 --- a/rs/ic_os/bootstrap_config/Cargo.toml +++ b/rs/ic_os/bootstrap_config/Cargo.toml @@ -1,19 +1,13 @@ [package] name = "bootstrap_config" -version = "0.1.0" edition = "2024" [dependencies] anyhow = { workspace = true } +ic-types = { path = "../../types/types" } regex = { workspace = true } +serde_json = { workspace = true } tempfile = { workspace = true } [features] dev = [] # Features for testing that should only be enabled in dev builds (unsafe for prod) - -[profile.release] -opt-level = 'z' # Optimize for size -lto = true # Enable link-time optimization -codegen-units = 1 # Reduce parallel code generation units to increase optimizations -strip = true # Strip symbols from binary - diff --git a/rs/ic_os/bootstrap_config/src/lib.rs b/rs/ic_os/bootstrap_config/src/lib.rs index 40600c49f8b7..f8f0576ea5d9 100644 --- a/rs/ic_os/bootstrap_config/src/lib.rs +++ b/rs/ic_os/bootstrap_config/src/lib.rs @@ -1,149 +1,179 @@ +use anyhow::{Context, Result, bail}; +use regex::Regex; +use std::env; use std::fmt::Write as _; use std::fs::{self, File}; use std::io::Write; use std::path::{Path, PathBuf}; use std::process::Command; -use anyhow::{Context, Result, bail}; -use regex::Regex; +#[cfg(feature = "dev")] +use ic_types::malicious_behaviour::MaliciousBehaviour; /// Configuration options for bootstrap image/tar creation #[derive(Default, Debug, Clone)] pub struct BootstrapOptions { - /// Path to the serialized GuestOS config object + /// The serialized GuestOS config object. pub guestos_config: Option, - /// Path to the NNS public key file - pub nns_public_key: Option, - - /// Path to the Node Operator private key PEM - pub node_operator_private_key: Option, - - /// Path to directory with SSH authorized keys for specific user accounts - #[cfg(feature = "dev")] - pub accounts_ssh_authorized_keys: Option, - - /// Path to injected crypto state directory + /// Injected crypto state. Should point to a directory containing material + /// generated by ic-prep. Typically, this is IC_PREP_OUT_PATH/node-X/crypto. pub ic_crypto: Option, - /// Path to injected state directory + /// Injected state. Should point to a directory containing a state (with checkpoint) + /// to start from. Typically, this is IC_PREP_OUT_PATH/node-X/data/ic_state pub ic_state: Option, - /// Path to injected initial registry state directory + /// Injected initial registry state. Should point to a directory containing + /// material generated by ic-prep. Typically, this is + /// IC_PREP_OUT_PATH/ic_registry_local_store pub ic_registry_local_store: Option, - /// IPv6 address with netmask (e.g., "dead:beef::1/64") + /// NNS public key file. + pub nns_public_key: Option, + + /// Should point to a directory with files containing the authorized ssh + /// keys for specific user accounts on the machine. The name of the + /// key designates the name of the account (so, if there is a file + /// "PATH/admin" then it is transferred to "~admin/.ssh/authorized_keys" on + /// the target). The presently recognized accounts are: backup, readonly and + /// admin + #[cfg(feature = "dev")] + pub accounts_ssh_authorized_keys: Option, + + /// Should point to a file containing a Node Provider private key PEM. + pub node_operator_private_key: Option, + + /// The IPv6 address to assign. Must include netmask in bits (e.g. + /// dead:beef::1/64). Overrides all other generation for testing. pub ipv6_address: Option, - /// Default IPv6 gateway + /// Default IPv6 gateway. pub ipv6_gateway: Option, - /// IPv4 address with prefix length (e.g., "18.208.190.35/28") + /// The IPv4 address to assign. Must include prefix length (e.g. + /// 18.208.190.35/28). pub ipv4_address: Option, - /// Default IPv4 gateway + /// Default IPv4 gateway (e.g. 18.208.190.33). pub ipv4_gateway: Option, - /// Domain name to assign to the guest + /// The domain name to assign to the guest. pub domain: Option, - /// Node reward type for determining node rewards + /// The node reward type determines node rewards pub node_reward_type: Option, - /// Hostname for the host (used in logging) + /// Name to assign to the host. Will be used in logging. pub hostname: Option, - /// Logging hosts to use + /// Logging hosts to use. pub elasticsearch_hosts: Vec, - /// Tags for Filebeat + /// Tags to be used by Filebeat. pub elasticsearch_tags: Vec, - /// URL of NNS nodes for sign up or registry access + /// URL of NNS nodes for sign up or registry access. Can be multiple nodes + /// separated by commas. pub nns_urls: Vec, - /// Backup retention time in seconds - pub backup_retention_time: Option, + /// How long the backed up consensus artifacts should stay on the spool + /// before they get purged. + pub backup_retention_time_sec: Option, - /// Backup purging interval in seconds - pub backup_purging_interval: Option, + /// How often the backup purging should be executed. + pub backup_purging_interval_sec: Option, - /// Malicious behavior JSON object (for testing) - pub malicious_behavior: Option, + /// A JSON-object that describes the malicious behavior activated on + /// the node. This is only used for testing. + #[cfg(feature = "dev")] + pub malicious_behavior: Option, - /// Query stats epoch length in seconds (for testing) + /// The length of the epoch in seconds. To be used in + /// systems tests only. + #[cfg(feature = "dev")] pub query_stats_epoch_length: Option, - /// IP address of a running bitcoind instance (for testing) + /// The IP address of a running bitcoind instance. To be used in + /// systems tests only. + #[cfg(feature = "dev")] pub bitcoind_addr: Option, - /// IP address of a running Jaeger Collector instance (for testing) + /// The IP address of a running Jaeger Collector instance. To be used in + /// systems tests only. + #[cfg(feature = "dev")] pub jaeger_addr: Option, - /// URL of the socks proxy to use (for testing) + /// The URL of the socks proxy to use. To be used in + /// systems tests only. + #[cfg(feature = "dev")] pub socks_proxy: Option, } -/// Generate network configuration content from config. -/// -/// The config must be valid. -fn generate_network_conf(config: &BootstrapOptions) -> Result { - let mut network_conf = String::new(); - - if let Some(ipv6_address) = &config.ipv6_address { - writeln!(network_conf, "ipv6_address={ipv6_address}")?; - } +/// Build a bootstrap disk image with the given configuration options. +pub fn build_bootstrap_config_image(out_file: &Path, options: &BootstrapOptions) -> Result<()> { + let tmp_dir = tempfile::tempdir().context("Failed to create temporary directory")?; - if let Some(ipv6_gateway) = &config.ipv6_gateway { - writeln!(network_conf, "ipv6_gateway={ipv6_gateway}")?; - } + // Create bootstrap tar + let tar_path = tmp_dir.path().join("ic-bootstrap.tar"); + build_bootstrap_tar(&tar_path, options)?; - writeln!( - network_conf, - "hostname={}", - valid_hostname_or_error(config)? - )?; + let tar_size = fs::metadata(&tar_path) + .context("Failed to get tar file metadata")? + .len(); - if let Some(ipv4_address) = &config.ipv4_address { - writeln!(network_conf, "ipv4_address={ipv4_address}")?; - } + // Calculate the disk image size (2 * tar_size + 1MB) + let image_size = 2 * tar_size + 1_048_576; - if let Some(ipv4_gateway) = &config.ipv4_gateway { - writeln!(network_conf, "ipv4_gateway={ipv4_gateway}")?; - } + // Create an empty file of the calculated size + let file = File::create(out_file).context("Failed to create output file")?; + file.set_len(image_size) + .context("Failed to set output file size")?; - if let Some(domain) = &config.domain { - writeln!(network_conf, "domain={domain}")?; + // Format the disk image as FAT + // mkfs.vfat is usually in /usr/sbin which is not always in the PATH + let path_with_usr_sbin = format!("/usr/sbin:{}", env::var("PATH").unwrap_or_default()); + if !Command::new("mkfs.vfat") + .arg("-n") + .arg("CONFIG") + .env("PATH", path_with_usr_sbin) + .arg(out_file) + .status() + .context("Failed to execute mkfs.vfat command")? + .success() + { + bail!("Failed to format disk image"); } - Ok(network_conf) -} - -fn valid_hostname_or_error(bootstrap_options: &BootstrapOptions) -> Result<&str> { - let Some(hostname) = bootstrap_options.hostname.as_ref() else { - bail!("Hostname is required"); - }; - - let pattern = Regex::new(r"^[a-zA-Z][a-zA-Z0-9]*(-[a-zA-Z0-9]+)*$").unwrap(); - if hostname.is_empty() || !pattern.is_match(hostname) { - bail!("Invalid hostname: '{hostname}'"); + // Copy the tar file to the disk image + if !Command::new("mcopy") + .arg("-i") + .arg(out_file) + .arg("-o") + .arg(&tar_path) + .arg("::") + .status() + .context("Failed to execute mcopy command")? + .success() + { + bail!("Failed to copy tar to disk image"); } - Ok(hostname) + Ok(()) } -/// Build a bootstrap tar file with the given configuration -fn build_bootstrap_tar(out_file: &Path, config: &BootstrapOptions) -> Result<()> { +/// Build a bootstrap tar file with the given configuration. +fn build_bootstrap_tar(out_file: &Path, options: &BootstrapOptions) -> Result<()> { // Create temporary directory for bootstrap files let bootstrap_dir = tempfile::tempdir().context("Failed to create temporary directory")?; // Copy files to the temporary directory - if let Some(guestos_config) = &config.guestos_config { + if let Some(guestos_config) = &options.guestos_config { fs::copy(guestos_config, bootstrap_dir.path().join("config.json")) .context("Failed to copy guestos config")?; } - if let Some(nns_public_key) = &config.nns_public_key { + if let Some(nns_public_key) = &options.nns_public_key { fs::copy( nns_public_key, bootstrap_dir.path().join("nns_public_key.pem"), @@ -151,7 +181,7 @@ fn build_bootstrap_tar(out_file: &Path, config: &BootstrapOptions) -> Result<()> .context("Failed to copy NNS public key")?; } - if let Some(node_operator_private_key) = &config.node_operator_private_key { + if let Some(node_operator_private_key) = &options.node_operator_private_key { fs::copy( node_operator_private_key, bootstrap_dir.path().join("node_operator_private_key.pem"), @@ -160,25 +190,25 @@ fn build_bootstrap_tar(out_file: &Path, config: &BootstrapOptions) -> Result<()> } #[cfg(feature = "dev")] - if let Some(accounts_ssh_authorized_keys) = &config.accounts_ssh_authorized_keys { + if let Some(accounts_ssh_authorized_keys) = &options.accounts_ssh_authorized_keys { let target_dir = bootstrap_dir.path().join("accounts_ssh_authorized_keys"); copy_dir_recursively(accounts_ssh_authorized_keys, &target_dir) .context("Failed to copy SSH authorized keys")?; } - if let Some(ic_crypto) = &config.ic_crypto { + if let Some(ic_crypto) = &options.ic_crypto { copy_dir_recursively(ic_crypto, &bootstrap_dir.path().join("ic_crypto")) .context("Failed to copy IC crypto directory")?; } - if let Some(ic_state) = &config.ic_state { + if let Some(ic_state) = &options.ic_state { if ic_state.exists() { copy_dir_recursively(ic_state, &bootstrap_dir.path().join("ic_state")) .context("Failed to copy IC state directory")?; } } - if let Some(ic_registry_local_store) = &config.ic_registry_local_store { + if let Some(ic_registry_local_store) = &options.ic_registry_local_store { copy_dir_recursively( ic_registry_local_store, &bootstrap_dir.path().join("ic_registry_local_store"), @@ -186,15 +216,13 @@ fn build_bootstrap_tar(out_file: &Path, config: &BootstrapOptions) -> Result<()> .context("Failed to copy registry local store")?; } - // Create network.conf fs::write( bootstrap_dir.path().join("network.conf"), - generate_network_conf(config)?, + generate_network_conf(options)?, ) .context("Failed to write network.conf")?; - // Create reward.conf if node_reward_type is set - if let Some(node_reward_type) = &config.node_reward_type { + if let Some(node_reward_type) = &options.node_reward_type { fs::write( bootstrap_dir.path().join("reward.conf"), format!("node_reward_type={node_reward_type}\n"), @@ -202,9 +230,8 @@ fn build_bootstrap_tar(out_file: &Path, config: &BootstrapOptions) -> Result<()> .context("Failed to write reward.conf")?; } - // Create filebeat.conf if elasticsearch_hosts is set - if !config.elasticsearch_hosts.is_empty() { - let space_separated_hosts = config.elasticsearch_hosts.join(" "); + if !options.elasticsearch_hosts.is_empty() { + let space_separated_hosts = options.elasticsearch_hosts.join(" "); let mut filebeat_config = File::create(bootstrap_dir.path().join("filebeat.conf")) .context("Failed to create filebeat.conf")?; @@ -212,15 +239,14 @@ fn build_bootstrap_tar(out_file: &Path, config: &BootstrapOptions) -> Result<()> filebeat_config, "elasticsearch_hosts={space_separated_hosts}" )?; - if !&config.elasticsearch_tags.is_empty() { - let space_separated_tags = config.elasticsearch_tags.join(" "); + if !&options.elasticsearch_tags.is_empty() { + let space_separated_tags = options.elasticsearch_tags.join(" "); writeln!(filebeat_config, "elasticsearch_tags={space_separated_tags}")?; } } - // Create nns.conf if nns_urls are available - if !config.nns_urls.is_empty() { - let comma_separated_urls = config.nns_urls.join(","); + if !options.nns_urls.is_empty() { + let comma_separated_urls = options.nns_urls.join(","); fs::write( bootstrap_dir.path().join("nns.conf"), format!("nns_url={comma_separated_urls}\n"), @@ -228,8 +254,7 @@ fn build_bootstrap_tar(out_file: &Path, config: &BootstrapOptions) -> Result<()> .context("Failed to write nns.conf")?; } - // Create backup.conf if backup settings are set - if let Some(backup_retention_time) = config.backup_retention_time { + if let Some(backup_retention_time) = options.backup_retention_time_sec { let mut backup_conf = File::create(&bootstrap_dir.path().join("backup.conf")) .context("Failed to create backup.conf")?; @@ -238,7 +263,7 @@ fn build_bootstrap_tar(out_file: &Path, config: &BootstrapOptions) -> Result<()> "backup_retention_time_secs={backup_retention_time}" )?; - if let Some(backup_purging_interval) = config.backup_purging_interval { + if let Some(backup_purging_interval) = options.backup_purging_interval_sec { writeln!( backup_conf, "backup_puging_interval_secs={backup_purging_interval}" @@ -246,17 +271,20 @@ fn build_bootstrap_tar(out_file: &Path, config: &BootstrapOptions) -> Result<()> } } - // Create malicious_behavior.conf if malicious_behavior is set - if let Some(malicious_behavior) = &config.malicious_behavior { + #[cfg(feature = "dev")] + if let Some(malicious_behavior) = &options.malicious_behavior { fs::write( bootstrap_dir.path().join("malicious_behavior.conf"), - format!("malicious_behavior={malicious_behavior}\n"), + format!( + "malicious_behavior={}\n", + serde_json::to_string(malicious_behavior)? + ), ) .context("Failed to write malicious_behavior.conf")?; } - // Create query_stats.conf if query_stats_epoch_length is set - if let Some(query_stats_epoch_length) = config.query_stats_epoch_length { + #[cfg(feature = "dev")] + if let Some(query_stats_epoch_length) = options.query_stats_epoch_length { fs::write( bootstrap_dir.path().join("query_stats.conf"), format!("query_stats_epoch_length={query_stats_epoch_length}\n"), @@ -264,8 +292,8 @@ fn build_bootstrap_tar(out_file: &Path, config: &BootstrapOptions) -> Result<()> .context("Failed to write query_stats.conf")?; } - // Create bitcoind_addr.conf if bitcoind_addr is set - if let Some(bitcoind_addr) = &config.bitcoind_addr { + #[cfg(feature = "dev")] + if let Some(bitcoind_addr) = &options.bitcoind_addr { fs::write( bootstrap_dir.path().join("bitcoind_addr.conf"), format!("bitcoind_addr={bitcoind_addr}\n"), @@ -273,8 +301,8 @@ fn build_bootstrap_tar(out_file: &Path, config: &BootstrapOptions) -> Result<()> .context("Failed to write bitcoind_addr.conf")?; } - // Create jaeger_addr.conf if jaeger_addr is set - if let Some(jaeger_addr) = &config.jaeger_addr { + #[cfg(feature = "dev")] + if let Some(jaeger_addr) = &options.jaeger_addr { fs::write( bootstrap_dir.path().join("jaeger_addr.conf"), format!("jaeger_addr=http://{jaeger_addr}\n"), @@ -282,8 +310,8 @@ fn build_bootstrap_tar(out_file: &Path, config: &BootstrapOptions) -> Result<()> .context("Failed to write jaeger_addr.conf")?; } - // Create socks_proxy.conf if socks_proxy is set - if let Some(socks_proxy) = &config.socks_proxy { + #[cfg(feature = "dev")] + if let Some(socks_proxy) = &options.socks_proxy { fs::write( bootstrap_dir.path().join("socks_proxy.conf"), format!("socks_proxy={socks_proxy}\n"), @@ -291,8 +319,7 @@ fn build_bootstrap_tar(out_file: &Path, config: &BootstrapOptions) -> Result<()> .context("Failed to write socks_proxy.conf")?; } - // Create tar file - let status = Command::new("tar") + if !Command::new("tar") .arg("cf") .arg(out_file) .arg("--sort=name") @@ -303,62 +330,61 @@ fn build_bootstrap_tar(out_file: &Path, config: &BootstrapOptions) -> Result<()> .arg(bootstrap_dir.path()) .arg(".") .status() - .context("Failed to execute tar command")?; - - if !status.success() { - bail!("Failed to create tar file: {status}"); + .context("Failed to execute tar command")? + .success() + { + bail!("Failed to create tar file"); } Ok(()) } -/// Build a bootstrap disk image with the given configuration -pub fn build_bootstrap_config_image(out_file: &Path, config: &BootstrapOptions) -> Result<()> { - let tmp_dir = tempfile::tempdir().context("Failed to create temporary directory")?; +/// Generate network configuration content from config. +/// +/// The config must be valid. +fn generate_network_conf(config: &BootstrapOptions) -> Result { + let mut network_conf = String::new(); - // Create bootstrap tar - let tar_path = tmp_dir.path().join("ic-bootstrap.tar"); - build_bootstrap_tar(&tar_path, config)?; + if let Some(ipv6_address) = &config.ipv6_address { + writeln!(network_conf, "ipv6_address={ipv6_address}")?; + } - let tar_size = fs::metadata(&tar_path) - .context("Failed to get tar file metadata")? - .len(); + if let Some(ipv6_gateway) = &config.ipv6_gateway { + writeln!(network_conf, "ipv6_gateway={ipv6_gateway}")?; + } - // Calculate the disk image size (2 * tar_size + 1MB) - let image_size = 2 * tar_size + 1_048_576; + writeln!( + network_conf, + "hostname={}", + valid_hostname_or_error(config)? + )?; - // Create an empty file of the calculated size - let file = File::create(out_file).context("Failed to create output file")?; - file.set_len(image_size) - .context("Failed to set output file size")?; + if let Some(ipv4_address) = &config.ipv4_address { + writeln!(network_conf, "ipv4_address={ipv4_address}")?; + } - // Format the disk image as FAT - let status = Command::new("mkfs.vfat") - .arg("-n") - .arg("CONFIG") - .arg(out_file) - .status() - .context("Failed to execute mkfs.vfat command")?; + if let Some(ipv4_gateway) = &config.ipv4_gateway { + writeln!(network_conf, "ipv4_gateway={ipv4_gateway}")?; + } - if !status.success() { - bail!("Failed to format disk image: {status}"); + if let Some(domain) = &config.domain { + writeln!(network_conf, "domain={domain}")?; } - // Copy the tar file to the disk image - let status = Command::new("mcopy") - .arg("-i") - .arg(out_file) - .arg("-o") - .arg(&tar_path) - .arg("::") - .status() - .context("Failed to execute mcopy command")?; + Ok(network_conf) +} - if !status.success() { - bail!("Failed to copy tar to disk image: {status}"); +fn valid_hostname_or_error(bootstrap_options: &BootstrapOptions) -> Result<&str> { + let Some(hostname) = bootstrap_options.hostname.as_ref() else { + bail!("Hostname is required"); + }; + + let pattern = Regex::new(r"^[a-zA-Z][a-zA-Z0-9]*(-[a-zA-Z0-9]+)*$").unwrap(); + if hostname.is_empty() || !pattern.is_match(hostname) { + bail!("Invalid hostname: '{hostname}'"); } - Ok(()) + Ok(hostname) } fn copy_dir_recursively(src: &Path, dst: &Path) -> Result<()> { @@ -407,13 +433,30 @@ mod tests { } #[test] - fn test_build_bootstrap_tar_fails_with_default_options() { + fn test_build_bootstrap_config_image_fails_with_default_options() { let tmp_dir = tempfile::tempdir().unwrap(); let out_file = tmp_dir.path().join("bootstrap.tar"); let config = BootstrapOptions::default(); - assert!(build_bootstrap_tar(&out_file, &config).is_err()); + assert!(build_bootstrap_config_image(&out_file, &config).is_err()); + } + + #[test] + fn test_build_bootstrap_image() -> Result<()> { + let tmp_dir = tempfile::tempdir()?; + let out_file = tmp_dir.path().join("bootstrap.img"); + + let mut config = BootstrapOptions::default(); + config.hostname = Some("testhostname".to_string()); + config.ipv6_address = Some("2001:db8::1/64".to_string()); + + build_bootstrap_config_image(&out_file, &config)?; + + assert!(out_file.exists()); + assert!(fs::metadata(&out_file)?.len() > 0); + + Ok(()) } #[test] @@ -502,7 +545,6 @@ mod tests { guestos_config: Some(config_path), nns_public_key: Some(nns_key_path), node_operator_private_key: Some(node_key_path), - #[cfg(feature = "dev")] accounts_ssh_authorized_keys: Some(ssh_keys_dir), ic_crypto: Some(crypto_dir), ic_state: Some(state_dir), @@ -516,9 +558,9 @@ mod tests { elasticsearch_hosts: vec!["host1".to_string(), "host2".to_string()], elasticsearch_tags: vec!["tag1".to_string(), "tag2".to_string()], nns_urls: vec!["url1".to_string(), "url2".to_string()], - backup_retention_time: Some(3600), - backup_purging_interval: Some(300), - malicious_behavior: Some(r#"{"type": "test"}"#.to_string()), + backup_retention_time_sec: Some(3600), + backup_purging_interval_sec: Some(300), + malicious_behavior: Some(MaliciousBehaviour::new(true)), query_stats_epoch_length: Some(60), bitcoind_addr: Some("127.0.0.1:8332".to_string()), jaeger_addr: Some("127.0.0.1:14250".to_string()), @@ -536,7 +578,7 @@ mod tests { .arg(&extract_dir) .status()?; - // Verify all files + // Verify all copied files and directories assert_eq!( fs::read_to_string(extract_dir.join("config.json"))?, r#"{"test": "config"}"# @@ -549,36 +591,69 @@ mod tests { fs::read_to_string(extract_dir.join("node_operator_private_key.pem"))?, "test_node_key" ); + assert_eq!( + fs::read_to_string(extract_dir.join("accounts_ssh_authorized_keys/key1"))?, + "ssh_key1" + ); + assert_eq!( + fs::read_to_string(extract_dir.join("ic_crypto/test"))?, + "crypto_data" + ); + assert_eq!( + fs::read_to_string(extract_dir.join("ic_state/test"))?, + "state_data" + ); + assert_eq!( + fs::read_to_string(extract_dir.join("ic_registry_local_store/test"))?, + "registry_data" + ); let network_conf = fs::read_to_string(extract_dir.join("network.conf"))?; - assert!(network_conf.contains("hostname=fulltest\n")); - assert!(network_conf.contains("ipv6_address=2001:db8::1/64\n")); - assert!(network_conf.contains("ipv6_gateway=2001:db8::ff\n")); - assert!(network_conf.contains("ipv4_address=192.168.1.1/24\n")); - assert!(network_conf.contains("ipv4_gateway=192.168.1.254\n")); - assert!(network_conf.contains("domain=test.domain\n")); + assert_eq!( + network_conf, + "ipv6_address=2001:db8::1/64\n\ + ipv6_gateway=2001:db8::ff\n\ + hostname=fulltest\n\ + ipv4_address=192.168.1.1/24\n\ + ipv4_gateway=192.168.1.254\n\ + domain=test.domain\n" + ); assert_eq!( fs::read_to_string(extract_dir.join("reward.conf"))?, "node_reward_type=test_reward\n" ); - let filebeat_conf = fs::read_to_string(extract_dir.join("filebeat.conf"))?; - assert!(filebeat_conf.contains("elasticsearch_hosts=host1 host2\n")); - assert!(filebeat_conf.contains("elasticsearch_tags=tag1 tag2\n")); + assert_eq!( + fs::read_to_string(extract_dir.join("filebeat.conf"))?, + "elasticsearch_hosts=host1 host2\nelasticsearch_tags=tag1 tag2\n" + ); assert_eq!( fs::read_to_string(extract_dir.join("nns.conf"))?, "nns_url=url1,url2\n" ); - let backup_conf = fs::read_to_string(extract_dir.join("backup.conf"))?; - assert!(backup_conf.contains("backup_retention_time_secs=3600\n")); - assert!(backup_conf.contains("backup_puging_interval_secs=300\n")); + assert_eq!( + fs::read_to_string(extract_dir.join("backup.conf"))?, + "backup_retention_time_secs=3600\nbackup_puging_interval_secs=300\n" + ); assert_eq!( fs::read_to_string(extract_dir.join("malicious_behavior.conf"))?, - "malicious_behavior={\"type\": \"test\"}\n" + "malicious_behavior={\"allow_malicious_behaviour\":true,\ + \"maliciously_seg_fault\":false,\ + \"malicious_flags\":{\"maliciously_propose_equivocating_blocks\":false,\ + \"maliciously_propose_empty_blocks\":false,\"maliciously_finalize_all\":false,\ + \"maliciously_notarize_all\":false,\"maliciously_tweak_dkg\":false,\ + \"maliciously_certify_invalid_hash\":false,\ + \"maliciously_malfunctioning_xnet_endpoint\":false,\ + \"maliciously_disable_execution\":false,\"maliciously_corrupt_own_state_at_heights\":[],\ + \"maliciously_disable_ingress_validation\":false,\ + \"maliciously_corrupt_idkg_dealings\":false,\"maliciously_delay_execution\":null,\ + \"maliciously_delay_state_sync\":null,\"maliciously_alter_certified_hash\":false,\ + \"maliciously_alter_state_sync_chunk_sending_side\":false,\ + \"maliciously_alter_state_sync_chunk_receiving_side\":null}}\n" ); assert_eq!( diff --git a/rs/tests/driver/src/driver/bootstrap.rs b/rs/tests/driver/src/driver/bootstrap.rs index 8e235ca0627d..e2c7898696fb 100644 --- a/rs/tests/driver/src/driver/bootstrap.rs +++ b/rs/tests/driver/src/driver/bootstrap.rs @@ -545,7 +545,7 @@ fn create_config_disk_image( } if let Some(malicious_behavior) = malicious_behavior { - bootstrap_options.malicious_behavior = Some(serde_json::to_string(&malicious_behavior)?); + bootstrap_options.malicious_behavior = malicious_behavior; } if let Some(query_stats_epoch_length) = query_stats_epoch_length { From 89355b57457964f8cc7c1bdb00d294f8805c209d Mon Sep 17 00:00:00 2001 From: David Frank Date: Thu, 22 May 2025 01:01:49 +0200 Subject: [PATCH 03/47] Cleanup --- rs/ic_os/bootstrap_config/BUILD.bazel | 1 - 1 file changed, 1 deletion(-) diff --git a/rs/ic_os/bootstrap_config/BUILD.bazel b/rs/ic_os/bootstrap_config/BUILD.bazel index 19dc57ee28a9..a625b6822820 100644 --- a/rs/ic_os/bootstrap_config/BUILD.bazel +++ b/rs/ic_os/bootstrap_config/BUILD.bazel @@ -1,4 +1,3 @@ -# In bootstrap_config/BUILD.bazel load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") package(default_visibility = ["//visibility:public"]) From 175b8e693862bdd22e515ac05932bb14df8b9b2e Mon Sep 17 00:00:00 2001 From: David Frank Date: Thu, 22 May 2025 01:11:50 +0200 Subject: [PATCH 04/47] Fixes --- Cargo.lock | 1 + rs/tests/driver/BUILD.bazel | 1 + rs/tests/driver/Cargo.toml | 1 + rs/tests/driver/src/driver/bootstrap.rs | 10 +++------- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fa5dbbd71091..f5783a8f3090 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13751,6 +13751,7 @@ dependencies = [ "backon", "base64 0.13.1", "bincode", + "bootstrap_config", "candid", "canister-test", "chrono", diff --git a/rs/tests/driver/BUILD.bazel b/rs/tests/driver/BUILD.bazel index 60efc4b00a5b..9e3fe23da332 100644 --- a/rs/tests/driver/BUILD.bazel +++ b/rs/tests/driver/BUILD.bazel @@ -48,6 +48,7 @@ rust_library( "//rs/crypto/tree_hash", "//rs/crypto/utils/threshold_sig_der", "//rs/cycles_account_manager", + "//rs/ic_os/bootstrap_config:bootstrap_config_dev", "//rs/ic_os/config:config_lib", "//rs/ic_os/config_types", "//rs/ic_os/deterministic_ips", diff --git a/rs/tests/driver/Cargo.toml b/rs/tests/driver/Cargo.toml index 0368d88aa175..9d8411d543a7 100644 --- a/rs/tests/driver/Cargo.toml +++ b/rs/tests/driver/Cargo.toml @@ -13,6 +13,7 @@ async-trait = { workspace = true } backon = "0.4.1" base64 = { workspace = true } bincode = { workspace = true } +bootstrap_config = { path = "../../ic_os/bootstrap_config", features = ["dev"] } candid = { workspace = true } canister-test = { path = "../../rust_canisters/canister_test" } chrono = { workspace = true } diff --git a/rs/tests/driver/src/driver/bootstrap.rs b/rs/tests/driver/src/driver/bootstrap.rs index e2c7898696fb..98961274c549 100644 --- a/rs/tests/driver/src/driver/bootstrap.rs +++ b/rs/tests/driver/src/driver/bootstrap.rs @@ -14,9 +14,8 @@ use crate::{ resource::{AllocatedVm, HOSTOS_MEMORY_KIB_PER_VM, HOSTOS_VCPUS_PER_VM}, test_env::{HasIcPrepDir, TestEnv, TestEnvAttribute}, test_env_api::{ - get_dependency_path, get_dependency_path_from_env, get_elasticsearch_hosts, - get_ic_os_update_img_sha256, get_ic_os_update_img_url, - get_mainnet_ic_os_update_img_url, get_mainnet_nns_revision, + get_dependency_path_from_env, get_elasticsearch_hosts, get_ic_os_update_img_sha256, + get_ic_os_update_img_url, get_mainnet_ic_os_update_img_url, get_mainnet_nns_revision, get_malicious_ic_os_update_img_sha256, get_malicious_ic_os_update_img_url, read_dependency_from_env_to_string, HasIcDependencies, HasTopologySnapshot, HasVmName, IcNodeContainer, InitialReplicaVersion, NodesInfo, @@ -519,6 +518,7 @@ fn create_config_disk_image( ic_registry_local_store: Some(local_store_path), ic_state: Some(node.state_path()), ic_crypto: Some(node.crypto_path()), + malicious_behavior, ..Default::default() }; @@ -544,10 +544,6 @@ fn create_config_disk_image( .push(format!("http://[{}]:8080", node.get_ip_addr())); } - if let Some(malicious_behavior) = malicious_behavior { - bootstrap_options.malicious_behavior = malicious_behavior; - } - if let Some(query_stats_epoch_length) = query_stats_epoch_length { bootstrap_options.query_stats_epoch_length = Some(query_stats_epoch_length); } From 2e20aac7ee5db2a2c631b6953a4ba05614031ecb Mon Sep 17 00:00:00 2001 From: David Frank Date: Thu, 22 May 2025 01:30:05 +0200 Subject: [PATCH 05/47] More idiomatic code --- rs/tests/driver/src/driver/bootstrap.rs | 48 +++++++++++-------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/rs/tests/driver/src/driver/bootstrap.rs b/rs/tests/driver/src/driver/bootstrap.rs index 7f27c4b640fe..fd3fec518515 100644 --- a/rs/tests/driver/src/driver/bootstrap.rs +++ b/rs/tests/driver/src/driver/bootstrap.rs @@ -519,6 +519,27 @@ fn create_config_disk_image( ic_state: Some(node.state_path()), ic_crypto: Some(node.crypto_path()), malicious_behavior, + query_stats_epoch_length, + domain: domain_name, + elasticsearch_hosts, + + // The bitcoind address specifies the local bitcoin node that the bitcoin adapter should + // connect to in the system test environment. + bitcoind_addr: test_env + .read_json_object::(BITCOIND_ADDR_PATH) + .ok(), + + // The jaeger address specifies the local Jaeger node that the nodes should connect to in + // the system test environment. + jaeger_addr: test_env + .read_json_object::(JAEGER_ADDR_PATH) + .ok(), + + // The socks proxy configuration indicates that a socks proxy is available to the system + // test environment. + socks_proxy: test_env + .read_json_object::(SOCKS_PROXY_PATH) + .ok(), ..Default::default() }; @@ -544,10 +565,6 @@ fn create_config_disk_image( .push(format!("http://[{}]:8080", node.get_ip_addr())); } - if let Some(query_stats_epoch_length) = query_stats_epoch_length { - bootstrap_options.query_stats_epoch_length = Some(query_stats_epoch_length); - } - if let Some(ipv4_config) = ipv4_config { bootstrap_options.ipv4_address = Some(format!( "{}/{:?}", @@ -557,29 +574,6 @@ fn create_config_disk_image( bootstrap_options.ipv4_gateway = Some(ipv4_config.gateway_ip_addr().to_string()); } - if let Some(domain_name) = domain_name { - bootstrap_options.domain = Some(domain_name); - } - - if !elasticsearch_hosts.is_empty() { - bootstrap_options.elasticsearch_hosts = elasticsearch_hosts.clone(); - } - - // The bitcoind address specifies the local bitcoin node that the bitcoin adapter should connect to in the system test environment. - if let Ok(arg) = test_env.read_json_object::(BITCOIND_ADDR_PATH) { - bootstrap_options.bitcoind_addr = Some(arg); - } - - // The jaeger address specifies the local Jaeger node that the nodes should connect to in the system test environment. - if let Ok(arg) = test_env.read_json_object::(JAEGER_ADDR_PATH) { - bootstrap_options.jaeger_addr = Some(arg); - } - - // The socks proxy configuration indicates that a socks proxy is available to the system test environment. - if let Ok(arg) = test_env.read_json_object::(SOCKS_PROXY_PATH) { - bootstrap_options.socks_proxy = Some(arg); - } - build_bootstrap_config_image(&img_path, &bootstrap_options) .context("Could not create bootstrap config image")?; From 8ce84a4d7a51a968f827c3a870b06733969c25ae Mon Sep 17 00:00:00 2001 From: David Frank Date: Thu, 22 May 2025 01:43:10 +0200 Subject: [PATCH 06/47] Clippy --- rs/ic_os/bootstrap_config/src/lib.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/rs/ic_os/bootstrap_config/src/lib.rs b/rs/ic_os/bootstrap_config/src/lib.rs index f8f0576ea5d9..a0f21b61c8c4 100644 --- a/rs/ic_os/bootstrap_config/src/lib.rs +++ b/rs/ic_os/bootstrap_config/src/lib.rs @@ -255,7 +255,7 @@ fn build_bootstrap_tar(out_file: &Path, options: &BootstrapOptions) -> Result<() } if let Some(backup_retention_time) = options.backup_retention_time_sec { - let mut backup_conf = File::create(&bootstrap_dir.path().join("backup.conf")) + let mut backup_conf = File::create(bootstrap_dir.path().join("backup.conf")) .context("Failed to create backup.conf")?; writeln!( @@ -465,18 +465,20 @@ mod tests { let tmp_dir = tempfile::tempdir()?; let out_file = tmp_dir.path().join("bootstrap.tar"); - // Create a minimal valid configuration - let mut config = BootstrapOptions::default(); - config.hostname = Some("testhostname".to_string()); - config.ipv6_address = Some("2001:db8::1/64".to_string()); - // Create a test file to be included in the tar let test_config_path = tmp_dir.path().join("test_config.json"); fs::write(&test_config_path, r#"{"test": "value"}"#)?; - config.guestos_config = Some(test_config_path); // Build the tar file - build_bootstrap_tar(&out_file, &config)?; + build_bootstrap_tar( + &out_file, + &BootstrapOptions { + hostname: Some("testhostname".to_string()), + ipv6_address: Some("2001:db8::1/64".to_string()), + guestos_config: Some(test_config_path), + ..BootstrapOptions::default() + }, + )?; // Extract the tar file to verify contents let extract_dir = tmp_dir.path().join("extract"); From 772d5b1483c66352d15c9f0d5c8c88075104ed35 Mon Sep 17 00:00:00 2001 From: David Frank Date: Thu, 22 May 2025 01:51:11 +0200 Subject: [PATCH 07/47] Clippy --- rs/ic_os/bootstrap_config/src/lib.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/rs/ic_os/bootstrap_config/src/lib.rs b/rs/ic_os/bootstrap_config/src/lib.rs index a0f21b61c8c4..38949bf2f1c6 100644 --- a/rs/ic_os/bootstrap_config/src/lib.rs +++ b/rs/ic_os/bootstrap_config/src/lib.rs @@ -412,9 +412,11 @@ mod tests { #[test] fn test_is_valid_hostname() { fn is_valid_hostname(hostname: &str) -> bool { - let mut config = BootstrapOptions::default(); - config.hostname = Some(hostname.to_string()); - valid_hostname_or_error(&config).is_ok() + valid_hostname_or_error(&BootstrapOptions { + hostname: Some(hostname.to_string()), + ..Default::default() + }) + .is_ok() } // Valid hostnames @@ -447,11 +449,14 @@ mod tests { let tmp_dir = tempfile::tempdir()?; let out_file = tmp_dir.path().join("bootstrap.img"); - let mut config = BootstrapOptions::default(); - config.hostname = Some("testhostname".to_string()); - config.ipv6_address = Some("2001:db8::1/64".to_string()); - - build_bootstrap_config_image(&out_file, &config)?; + build_bootstrap_config_image( + &out_file, + &BootstrapOptions { + hostname: Some("testhostname".to_string()), + ipv6_address: Some("2001:db8::1/64".to_string()), + ..Default::default() + }, + )?; assert!(out_file.exists()); assert!(fs::metadata(&out_file)?.len() > 0); From 084611deabd1b8bf50bafa6cc96b4630368f2cc1 Mon Sep 17 00:00:00 2001 From: David Frank Date: Thu, 22 May 2025 02:02:25 +0200 Subject: [PATCH 08/47] Moar fixes --- rs/ic_os/dev_test_tools/launch-single-vm/BUILD.bazel | 1 + rs/ic_os/dev_test_tools/launch-single-vm/src/main.rs | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/rs/ic_os/dev_test_tools/launch-single-vm/BUILD.bazel b/rs/ic_os/dev_test_tools/launch-single-vm/BUILD.bazel index e46354d3a81c..57ac0e85ed43 100644 --- a/rs/ic_os/dev_test_tools/launch-single-vm/BUILD.bazel +++ b/rs/ic_os/dev_test_tools/launch-single-vm/BUILD.bazel @@ -4,6 +4,7 @@ package(default_visibility = ["//rs:ic-os-pkg"]) DEPENDENCIES = [ # Keep sorted. + "//rs/ic_os/bootstrap_config:bootstrap_config_dev", "//rs/ic_os/config:config_lib", "//rs/ic_os/config_types", "//rs/prep", diff --git a/rs/ic_os/dev_test_tools/launch-single-vm/src/main.rs b/rs/ic_os/dev_test_tools/launch-single-vm/src/main.rs index 75960f18279c..63fb40cf34e7 100644 --- a/rs/ic_os/dev_test_tools/launch-single-vm/src/main.rs +++ b/rs/ic_os/dev_test_tools/launch-single-vm/src/main.rs @@ -1,4 +1,9 @@ +use bootstrap_config::{build_bootstrap_config_image, BootstrapOptions}; use clap::Parser; +use config::generate_testnet_config::{ + generate_testnet_config, GenerateTestnetConfigArgs, Ipv6ConfigType, +}; +use config_types::DeploymentEnvironment; use ic_prep_lib::{ internet_computer::{IcConfig, TopologyConfig}, node::NodeConfiguration, @@ -21,6 +26,7 @@ use std::net::{IpAddr, SocketAddr}; use std::path::PathBuf; use tempfile::tempdir; use url::Url; + const FARM_BASE_URL: &str = "https://farm.dfinity.systems"; /// Deploy a single ICOS VM to Farm @@ -228,6 +234,7 @@ fn main() { let filename = "config.tar.gz"; let config_path = tempdir.as_ref().join(filename); let local_store = prep_dir.join("ic_registry_local_store"); + let bootstrap_options = BootstrapOptions { guestos_config: Some(guestos_config_json_path), ic_crypto: Some(node.crypto_path()), @@ -235,7 +242,6 @@ fn main() { accounts_ssh_authorized_keys: Some(keys_dir), ..Default::default() }; - build_bootstrap_config_image(&config_path, &bootstrap_options).unwrap(); // Upload config image From ba04a26738ce1b0db8f10053ece2c814c1754676 Mon Sep 17 00:00:00 2001 From: David Frank Date: Thu, 22 May 2025 11:17:58 +0200 Subject: [PATCH 09/47] Allow empty hostname (was like that before) --- rs/ic_os/bootstrap_config/Cargo.toml | 3 ++ rs/ic_os/bootstrap_config/src/lib.rs | 48 ++++++++++------------------ 2 files changed, 20 insertions(+), 31 deletions(-) diff --git a/rs/ic_os/bootstrap_config/Cargo.toml b/rs/ic_os/bootstrap_config/Cargo.toml index 94bd2a869991..8bd15b271a7d 100644 --- a/rs/ic_os/bootstrap_config/Cargo.toml +++ b/rs/ic_os/bootstrap_config/Cargo.toml @@ -11,3 +11,6 @@ tempfile = { workspace = true } [features] dev = [] # Features for testing that should only be enabled in dev builds (unsafe for prod) + +[profile.test] +default = ["dev"] diff --git a/rs/ic_os/bootstrap_config/src/lib.rs b/rs/ic_os/bootstrap_config/src/lib.rs index 38949bf2f1c6..d7b187e67926 100644 --- a/rs/ic_os/bootstrap_config/src/lib.rs +++ b/rs/ic_os/bootstrap_config/src/lib.rs @@ -353,11 +353,9 @@ fn generate_network_conf(config: &BootstrapOptions) -> Result { writeln!(network_conf, "ipv6_gateway={ipv6_gateway}")?; } - writeln!( - network_conf, - "hostname={}", - valid_hostname_or_error(config)? - )?; + let hostname = config.hostname.as_deref().unwrap_or_default(); + validate_hostname(hostname)?; + writeln!(network_conf, "hostname={hostname}",)?; if let Some(ipv4_address) = &config.ipv4_address { writeln!(network_conf, "ipv4_address={ipv4_address}")?; @@ -374,17 +372,13 @@ fn generate_network_conf(config: &BootstrapOptions) -> Result { Ok(network_conf) } -fn valid_hostname_or_error(bootstrap_options: &BootstrapOptions) -> Result<&str> { - let Some(hostname) = bootstrap_options.hostname.as_ref() else { - bail!("Hostname is required"); - }; - +fn validate_hostname(hostname: &str) -> Result<()> { let pattern = Regex::new(r"^[a-zA-Z][a-zA-Z0-9]*(-[a-zA-Z0-9]+)*$").unwrap(); - if hostname.is_empty() || !pattern.is_match(hostname) { + if hostname.is_empty() || pattern.is_match(hostname) { + Ok(()) + } else { bail!("Invalid hostname: '{hostname}'"); } - - Ok(hostname) } fn copy_dir_recursively(src: &Path, dst: &Path) -> Result<()> { @@ -411,27 +405,19 @@ mod tests { #[test] fn test_is_valid_hostname() { - fn is_valid_hostname(hostname: &str) -> bool { - valid_hostname_or_error(&BootstrapOptions { - hostname: Some(hostname.to_string()), - ..Default::default() - }) - .is_ok() - } - // Valid hostnames - assert!(is_valid_hostname("hostname")); - assert!(is_valid_hostname("hostname123")); - assert!(is_valid_hostname("hostname-part2")); - assert!(is_valid_hostname("h-1-2-3")); + assert!(validate_hostname("").is_ok()); + assert!(validate_hostname("hostname").is_ok()); + assert!(validate_hostname("hostname123").is_ok()); + assert!(validate_hostname("hostname-part2").is_ok()); + assert!(validate_hostname("h-1-2-3").is_ok()); // Invalid hostnames - assert!(!is_valid_hostname("")); - assert!(!is_valid_hostname("123hostname")); - assert!(!is_valid_hostname("hostname-")); - assert!(!is_valid_hostname("-hostname")); - assert!(!is_valid_hostname("hostname_invalid")); - assert!(!is_valid_hostname("hostname with spaces")); + assert!(validate_hostname("123hostname").is_err()); + assert!(validate_hostname("hostname-").is_err()); + assert!(validate_hostname("-hostname").is_err()); + assert!(validate_hostname("hostname_invalid").is_err()); + assert!(validate_hostname("hostname with spaces").is_err()); } #[test] From bd22265f5763f8b80600de1ffa39a96aa1c720cc Mon Sep 17 00:00:00 2001 From: David Frank Date: Thu, 22 May 2025 11:20:47 +0200 Subject: [PATCH 10/47] Fix tests --- rs/ic_os/bootstrap_config/src/lib.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/rs/ic_os/bootstrap_config/src/lib.rs b/rs/ic_os/bootstrap_config/src/lib.rs index d7b187e67926..fd1ce742705e 100644 --- a/rs/ic_os/bootstrap_config/src/lib.rs +++ b/rs/ic_os/bootstrap_config/src/lib.rs @@ -421,13 +421,11 @@ mod tests { } #[test] - fn test_build_bootstrap_config_image_fails_with_default_options() { + fn test_build_bootstrap_config_image_succeeds_with_default_options() { let tmp_dir = tempfile::tempdir().unwrap(); let out_file = tmp_dir.path().join("bootstrap.tar"); - let config = BootstrapOptions::default(); - - assert!(build_bootstrap_config_image(&out_file, &config).is_err()); + assert!(build_bootstrap_config_image(&out_file, &BootstrapOptions::default()).is_ok()); } #[test] From b1fed07b651d97b0b48240926bc03f0c47560c36 Mon Sep 17 00:00:00 2001 From: David Frank Date: Thu, 22 May 2025 12:07:31 +0200 Subject: [PATCH 11/47] Format and remove deps --- rs/tests/consensus/BUILD.bazel | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/rs/tests/consensus/BUILD.bazel b/rs/tests/consensus/BUILD.bazel index 4a788e552b44..c23cbc54db51 100644 --- a/rs/tests/consensus/BUILD.bazel +++ b/rs/tests/consensus/BUILD.bazel @@ -52,7 +52,7 @@ system_test( ], target_compatible_with = ["@platforms//os:linux"], # requires libssh that does not build on Mac OS uses_guestos_dev = True, - runtime_deps = ["//ic-os/components:hostos-scripts/build-bootstrap-config-image.sh"] + GRAFANA_RUNTIME_DEPS, + runtime_deps = GRAFANA_RUNTIME_DEPS, deps = [ # Keep sorted. ":catch_up_test_common", @@ -114,9 +114,7 @@ system_test( target_compatible_with = ["@platforms//os:linux"], # requires libssh that does not build on Mac OS uses_guestos_dev = True, uses_guestos_dev_test = True, - runtime_deps = UNIVERSAL_CANISTER_RUNTIME_DEPS + [ - "//ic-os/components:hostos-scripts/build-bootstrap-config-image.sh", - ], + runtime_deps = UNIVERSAL_CANISTER_RUNTIME_DEPS, deps = [ # Keep sorted. ":liveness_test_common", @@ -235,9 +233,7 @@ system_test( target_compatible_with = ["@platforms//os:linux"], # requires libssh that does not build on Mac OS uses_guestos_dev = True, uses_guestos_dev_test = True, - runtime_deps = UNIVERSAL_CANISTER_RUNTIME_DEPS + [ - "//ic-os/components:hostos-scripts/build-bootstrap-config-image.sh", - ], + runtime_deps = UNIVERSAL_CANISTER_RUNTIME_DEPS, deps = [ # Keep sorted. "//rs/registry/subnet_type", @@ -260,9 +256,7 @@ system_test( target_compatible_with = ["@platforms//os:linux"], # requires libssh that does not build on Mac OS uses_guestos_dev = True, uses_guestos_dev_test = True, - runtime_deps = UNIVERSAL_CANISTER_RUNTIME_DEPS + [ - "//ic-os/components:hostos-scripts/build-bootstrap-config-image.sh", - ], + runtime_deps = UNIVERSAL_CANISTER_RUNTIME_DEPS, deps = [ # Keep sorted. "//rs/crypto/test_utils/reproducible_rng", @@ -292,9 +286,7 @@ system_test( target_compatible_with = ["@platforms//os:linux"], # requires libssh that does not build on Mac OS uses_guestos_dev = True, uses_guestos_dev_test = True, - runtime_deps = UNIVERSAL_CANISTER_RUNTIME_DEPS + [ - "//ic-os/components:hostos-scripts/build-bootstrap-config-image.sh", - ], + runtime_deps = UNIVERSAL_CANISTER_RUNTIME_DEPS, deps = [ # Keep sorted. ":liveness_test_common", From 96d641a7e984f8ed88478af1ec074ed5454b7037 Mon Sep 17 00:00:00 2001 From: David Frank Date: Thu, 22 May 2025 12:11:12 +0200 Subject: [PATCH 12/47] change edition --- rs/ic_os/bootstrap_config/Cargo.toml | 2 +- rs/ic_os/bootstrap_config/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rs/ic_os/bootstrap_config/Cargo.toml b/rs/ic_os/bootstrap_config/Cargo.toml index 8bd15b271a7d..a365bdf32036 100644 --- a/rs/ic_os/bootstrap_config/Cargo.toml +++ b/rs/ic_os/bootstrap_config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bootstrap_config" -edition = "2024" +edition = "2021" [dependencies] anyhow = { workspace = true } diff --git a/rs/ic_os/bootstrap_config/src/lib.rs b/rs/ic_os/bootstrap_config/src/lib.rs index fd1ce742705e..ae0e9badc827 100644 --- a/rs/ic_os/bootstrap_config/src/lib.rs +++ b/rs/ic_os/bootstrap_config/src/lib.rs @@ -1,4 +1,4 @@ -use anyhow::{Context, Result, bail}; +use anyhow::{bail, Context, Result}; use regex::Regex; use std::env; use std::fmt::Write as _; From 121a79cd02bc1c49e0f91bbdc90faa7760859cd2 Mon Sep 17 00:00:00 2001 From: David Frank Date: Thu, 22 May 2025 13:20:57 +0200 Subject: [PATCH 13/47] WIP --- Cargo.lock | 15 ++ Cargo.toml | 1 + .../generate-guestos-config.service | 2 +- .../generate_guestos_vm_config/Cargo.toml | 17 ++ .../generate_guestos_vm_config/src/main.rs | 246 ++++++++++++++++++ .../templates/guestos_vm_template.xml | 189 ++++++++++++++ 6 files changed, 469 insertions(+), 1 deletion(-) create mode 100644 rs/ic_os/generate_guestos_vm_config/Cargo.toml create mode 100644 rs/ic_os/generate_guestos_vm_config/src/main.rs create mode 100644 rs/ic_os/generate_guestos_vm_config/templates/guestos_vm_template.xml diff --git a/Cargo.lock b/Cargo.lock index 1ffcc8767f1f..b2f9749cfa35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4909,6 +4909,21 @@ version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" +[[package]] +name = "generate_guestos_vm_config" +version = "0.0.0" +dependencies = [ + "anyhow", + "askama", + "bootstrap_config", + "clap 4.5.27", + "config", + "config_types", + "deterministic_ips", + "macaddr", + "serde_json", +] + [[package]] name = "generator" version = "0.8.4" diff --git a/Cargo.toml b/Cargo.toml index 87c1f8971f6f..1a3d79c4c37b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -141,6 +141,7 @@ members = [ "rs/http_endpoints/xnet", "rs/http_utils", "rs/ic_os/deterministic_ips", + "rs/ic_os/generate_guestos_vm_config", "rs/ic_os/build_tools/dflate", "rs/ic_os/build_tools/diroid", "rs/ic_os/config", diff --git a/ic-os/components/hostos-scripts/generate-guestos-config/generate-guestos-config.service b/ic-os/components/hostos-scripts/generate-guestos-config/generate-guestos-config.service index 0f84a44bee8e..5329ea2ba699 100644 --- a/ic-os/components/hostos-scripts/generate-guestos-config/generate-guestos-config.service +++ b/ic-os/components/hostos-scripts/generate-guestos-config/generate-guestos-config.service @@ -9,7 +9,7 @@ RequiresMountsFor=/var [Service] Type=oneshot RemainAfterExit=true -ExecStart=/opt/ic/bin/generate-guestos-config.sh +ExecStart=/opt/ic/bin/generate-guestos-config [Install] WantedBy=multi-user.target diff --git a/rs/ic_os/generate_guestos_vm_config/Cargo.toml b/rs/ic_os/generate_guestos_vm_config/Cargo.toml new file mode 100644 index 000000000000..2a461bd3e3b4 --- /dev/null +++ b/rs/ic_os/generate_guestos_vm_config/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "generate_guestos_vm_config" +edition = "2021" + +[dependencies] +anyhow = { workspace = true } +askama = { workspace = true } +bootstrap_config = { path = "../bootstrap_config" } +clap = { workspace = true } +config = { path = "../config" } +config_types = { path = "../config_types" } +deterministic_ips = { path = "../deterministic_ips" } +macaddr = { workspace = true } +serde_json = { workspace = true } + +[features] +dev = ["bootstrap_config/dev"] diff --git a/rs/ic_os/generate_guestos_vm_config/src/main.rs b/rs/ic_os/generate_guestos_vm_config/src/main.rs new file mode 100644 index 000000000000..f806f0a54e17 --- /dev/null +++ b/rs/ic_os/generate_guestos_vm_config/src/main.rs @@ -0,0 +1,246 @@ +use anyhow::{bail, Context, Result}; +use askama::Template; +use bootstrap_config::{build_bootstrap_config_image, BootstrapOptions}; +use clap::Parser; +use config::guestos_config::generate_guestos_config; +use config::{ + serialize_and_write_config, DEFAULT_HOSTOS_CONFIG_OBJECT_PATH, + DEFAULT_HOSTOS_GUESTOS_CONFIG_OBJECT_PATH, +}; +use config_types::{HostOSConfig, Ipv6Config}; +use deterministic_ips::node_type::NodeType; +use deterministic_ips::{calculate_deterministic_mac, IpVariant}; +use macaddr::MacAddr6; +use serde_json; +use std::fs::{self, File}; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::process::Command; + +/// Generate the GuestOS configuration +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + /// Specify the config media image file + #[arg(short, long, default_value = "/run/ic-node/config.img")] + media: PathBuf, + + /// Specify the output configuration file + #[arg(short, long, default_value = "/var/lib/libvirt/guestos.xml")] + output: PathBuf, + + /// Path to the HostOS config file + #[arg(short, long, default_value = DEFAULT_HOSTOS_CONFIG_OBJECT_PATH)] + config: PathBuf, +} + +/// Write a metric using the provided metrics script +fn write_metric(name: &str, value: &str, description: &str, metric_type: &str) -> Result<()> { + // run_command_status( + // Command::new("/opt/ic/bin/metrics.sh") + // .arg("write_metric") + // .arg(name) + // .arg(value) + // .arg(description) + // .arg(metric_type), + // )?; + + Ok(()) +} + +/// Get the IPv6 gateway from the configuration +fn get_ipv6_gateway(config: &HostOSConfig) -> Result { + match &config.network_settings.ipv6_config { + Ipv6Config::Deterministic(config) => Ok(config.gateway.to_string()), + Ipv6Config::Fixed(config) => Ok(config.gateway.to_string()), + Ipv6Config::RouterAdvertisement => { + bail!("RouterAdvertisement IPv6 configuration does not have a gateway") + } + } +} + +/// Assemble the configuration media +fn assemble_config_media(hostos_config: &HostOSConfig, media_path: &Path) -> Result<()> { + let guestos_config = generate_guestos_config(hostos_config)?; + serialize_and_write_config( + Path::new(DEFAULT_HOSTOS_GUESTOS_CONFIG_OBJECT_PATH), + &guestos_config, + )?; + println!( + "GuestOSConfig has been written to {}", + DEFAULT_HOSTOS_GUESTOS_CONFIG_OBJECT_PATH + ); + + let mut bootstrap_options = BootstrapOptions::default(); + + bootstrap_options.guestos_config = + Some(PathBuf::from(DEFAULT_HOSTOS_GUESTOS_CONFIG_OBJECT_PATH)); + + // Set SSH authorized keys (only in dev builds) + #[cfg(feature = "dev")] + if hostos_config.icos_settings.use_ssh_authorized_keys { + bootstrap_options.accounts_ssh_authorized_keys = + Some(PathBuf::from("/boot/config/ssh_authorized_keys")); + } + + if hostos_config.icos_settings.use_nns_public_key { + bootstrap_options.nns_public_key = Some(PathBuf::from("/boot/config/nns_public_key.pem")); + } + + if hostos_config.icos_settings.use_node_operator_private_key { + bootstrap_options.node_operator_private_key = + Some(PathBuf::from("/boot/config/node_operator_private_key.pem")); + } + + let guestos_address = match &guestos_config.network_settings.ipv6_config { + &Ipv6Config::Fixed(ref ip_config) => ip_config.address.clone(), + _ => bail!( + "Expected GuestOS IPv6 address to be fixed but was {:?}", + guestos_config.network_settings.ipv6_config + ), + }; + bootstrap_options.ipv6_address = Some(guestos_address); + bootstrap_options.ipv6_gateway = Some(get_ipv6_gateway(hostos_config)?); + + if let Some(ipv4_config) = &hostos_config.network_settings.ipv4_config { + bootstrap_options.ipv4_address = Some(format!( + "{}/{}", + ipv4_config.address, ipv4_config.prefix_length + )); + bootstrap_options.ipv4_gateway = Some(ipv4_config.gateway.to_string()); + } + + if let Some(domain) = &hostos_config.network_settings.domain_name { + bootstrap_options.domain = Some(domain.clone()); + } + + if let Some(node_reward_type) = &hostos_config.icos_settings.node_reward_type { + bootstrap_options.node_reward_type = Some(node_reward_type.clone()); + } + + let hostname = format!( + "guest-{}", + hostos_config + .icos_settings + .mgmt_mac + .to_string() + .replace(":", "") + ); + bootstrap_options.hostname = Some(hostname); + + bootstrap_options.nns_urls = hostos_config + .icos_settings + .nns_urls + .iter() + .map(|url| url.to_string()) + .collect(); + + build_bootstrap_config_image(media_path, &bootstrap_options)?; + + println!( + "Assembling config media for GuestOS: {}", + media_path.display() + ); + + Ok(()) +} + +fn generate_vm_config(config: &HostOSConfig, media_path: &Path) -> Result { + // If you get a compile error pointing at #[derive(Template)], there is likely a syntax error in + // the template. + #[derive(Template)] + #[template(path = "guestos_vm_template.xml")] + pub struct GuestOSTemplateProps<'a> { + pub cpu_domain: &'a str, + pub vm_memory: u32, + pub nr_of_vcpus: u32, + pub mac_address: MacAddr6, + pub config_media: &'a str, + } + + let mac_address = calculate_deterministic_mac( + &config.icos_settings.mgmt_mac, + config.icos_settings.deployment_environment, + IpVariant::V6, + NodeType::GuestOS, + ); + + let cpu_domain = if config.hostos_settings.vm_cpu == "qemu" { + "qemu" + } else { + "kvm" + }; + + GuestOSTemplateProps { + cpu_domain, + vm_memory: config.hostos_settings.vm_memory, + nr_of_vcpus: config.hostos_settings.vm_nr_of_vcpus, + mac_address, + config_media: &media_path.display().to_string(), + } + .render() + .context("Failed to render GuestOS template") +} + +fn main() -> Result<()> { + let args = Args::parse(); + + let hostos_config: HostOSConfig = + serde_json::from_reader(File::open(&args.config).context("Failed to open config file")?) + .context("Failed to parse config file")?; + + assemble_config_media(&hostos_config, &args.media) + .context("Failed to assemble config media")?; + + if args.output.exists() { + write_metric( + "hostos_generate_guestos_config", + "0", + "HostOS generate GuestOS config", + "gauge", + )?; + + bail!( + "GuestOS configuration file already exists: {}", + args.output.display() + ); + } + + let output_path = &args.output; + + // Create parent directory if it doesn't exist + if let Some(parent) = output_path.parent() { + fs::create_dir_all(parent).context("Failed to create output directory")?; + } + + File::create(output_path) + .context("Failed to create output file")? + .write_all(generate_vm_config(&hostos_config, &args.media)?.as_bytes()) + .context("Failed to write output file")?; + + // Restore SELinux security context + if let Some(parent) = output_path.parent() { + if !Command::new("restorecon") + .arg("-R") + .arg(parent) + .status()? + .success() + { + bail!("Failed to run restorecon"); + } + } + + println!( + "Generating GuestOS configuration file: {}", + output_path.display(), + ); + + write_metric( + "hostos_generate_guestos_config", + "1", + "HostOS generate GuestOS config", + "gauge", + )?; + + Ok(()) +} diff --git a/rs/ic_os/generate_guestos_vm_config/templates/guestos_vm_template.xml b/rs/ic_os/generate_guestos_vm_config/templates/guestos_vm_template.xml new file mode 100644 index 000000000000..505c93eddd48 --- /dev/null +++ b/rs/ic_os/generate_guestos_vm_config/templates/guestos_vm_template.xml @@ -0,0 +1,189 @@ + + guestos + fd897da5-8017-41c8-8575-a706dba30766 + + + + + + {{vm_memory}} + {{vm_memory}} + {{nr_of_vcpus}} + + {% if cpu_domain == "qemu" %} + + {% else %} + + + + + + {% endif %} + + + /machine + + + hvm + /usr/share/OVMF/OVMF_CODE_4M.fd + /var/lib/libvirt/qemu/nvram/guestos_VARS.fd + + + + + + + + + + + + restart + restart + restart + + + + + + /usr/local/bin/qemu-system-x86_64 + + + + + + +
+ + + + + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + +
+ + + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+ + + /dev/urandom + +
+ + + + + +64055:+108 + + From 73f0b7897264c690d1d80e5d6ddebb3932064aad Mon Sep 17 00:00:00 2001 From: David Frank Date: Thu, 22 May 2025 14:00:45 +0200 Subject: [PATCH 14/47] Factor out generate_guestos_config into a library function and add tests In addition, calculate IP address from the passed config instead of taking it as a separate argument. --- Cargo.lock | 1 + .../dev-generate-guestos-config.sh | 3 +- .../generate-guestos-config.sh | 3 +- rs/ic_os/config/Cargo.toml | 1 + rs/ic_os/config/src/guestos_config.rs | 128 ++++++++++++++++++ rs/ic_os/config/src/lib.rs | 1 + rs/ic_os/config/src/main.rs | 31 +---- rs/ic_os/config_types/src/lib.rs | 2 +- 8 files changed, 136 insertions(+), 34 deletions(-) create mode 100644 rs/ic_os/config/src/guestos_config.rs diff --git a/Cargo.lock b/Cargo.lock index 503a82217be0..7ada871cd29f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2554,6 +2554,7 @@ dependencies = [ "anyhow", "clap 4.5.27", "config_types", + "deterministic_ips", "ic-types", "macaddr", "network", diff --git a/ic-os/components/hostos-scripts/generate-guestos-config/dev-generate-guestos-config.sh b/ic-os/components/hostos-scripts/generate-guestos-config/dev-generate-guestos-config.sh index 33481c8eb2b7..d543908c903f 100755 --- a/ic-os/components/hostos-scripts/generate-guestos-config/dev-generate-guestos-config.sh +++ b/ic-os/components/hostos-scripts/generate-guestos-config/dev-generate-guestos-config.sh @@ -73,8 +73,7 @@ function read_config_variables() { } function assemble_config_media() { - ipv6_address="$(/opt/ic/bin/hostos_tool generate-ipv6-address --node-type GuestOS)" - /opt/ic/bin/config generate-guestos-config --guestos-ipv6-address "$ipv6_address" + /opt/ic/bin/config generate-guestos-config cmd=(/opt/ic/bin/build-bootstrap-config-image.sh ${MEDIA}) cmd+=(--guestos_config "/boot/config/config-guestos.json") diff --git a/ic-os/components/hostos-scripts/generate-guestos-config/generate-guestos-config.sh b/ic-os/components/hostos-scripts/generate-guestos-config/generate-guestos-config.sh index bbb3849570c4..120f1963e187 100755 --- a/ic-os/components/hostos-scripts/generate-guestos-config/generate-guestos-config.sh +++ b/ic-os/components/hostos-scripts/generate-guestos-config/generate-guestos-config.sh @@ -72,8 +72,7 @@ function read_config_variables() { } function assemble_config_media() { - ipv6_address="$(/opt/ic/bin/hostos_tool generate-ipv6-address --node-type GuestOS)" - /opt/ic/bin/config generate-guestos-config --guestos-ipv6-address "$ipv6_address" + /opt/ic/bin/config generate-guestos-config cmd=(/opt/ic/bin/build-bootstrap-config-image.sh ${MEDIA}) cmd+=(--guestos_config "/boot/config/config-guestos.json") diff --git a/rs/ic_os/config/Cargo.toml b/rs/ic_os/config/Cargo.toml index 51c15344537d..c7c5a88bb178 100644 --- a/rs/ic_os/config/Cargo.toml +++ b/rs/ic_os/config/Cargo.toml @@ -16,6 +16,7 @@ serde_with = "1.6.2" regex = { workspace = true } config_types = { path = "../config_types" } network = { path = "../network" } # Only required by bin +deterministic_ips = { path = "../deterministic_ips" } [dev-dependencies] once_cell = "1.8" diff --git a/rs/ic_os/config/src/guestos_config.rs b/rs/ic_os/config/src/guestos_config.rs new file mode 100644 index 000000000000..960c355f62d3 --- /dev/null +++ b/rs/ic_os/config/src/guestos_config.rs @@ -0,0 +1,128 @@ +use anyhow::Result; +use config_types::{FixedIpv6Config, GuestOSConfig, HostOSConfig, Ipv6Config}; +use deterministic_ips::node_type::NodeType; +use deterministic_ips::{calculate_deterministic_mac, IpVariant, MacAddr6Ext}; +use utils::to_cidr; + +/// Generate the GuestOS configuration based on the provided HostOS configuration. +pub fn generate_guestos_config(hostos_config: &HostOSConfig) -> Result { + let hostos_config = hostos_config.clone(); + let generated_mac = calculate_deterministic_mac( + &hostos_config.icos_settings.mgmt_mac, + hostos_config.icos_settings.deployment_environment, + IpVariant::V6, + NodeType::GuestOS, // TODO(NODE-1608): support UpgradeGuestOS + ); + // TODO: We won't have to modify networking between the hostos and + // guestos config after completing the networking revamp (NODE-1327) + let mut guestos_network_settings = hostos_config.network_settings; + // Update the GuestOS networking if `guestos_ipv6_address` is provided + match &guestos_network_settings.ipv6_config { + Ipv6Config::Deterministic(deterministic_ipv6_config) => { + let guestos_ipv6_address = + generated_mac.calculate_slaac(&deterministic_ipv6_config.prefix)?; + guestos_network_settings.ipv6_config = Ipv6Config::Fixed(FixedIpv6Config { + address: to_cidr( + guestos_ipv6_address, + deterministic_ipv6_config.prefix_length, + ), + gateway: deterministic_ipv6_config.gateway, + }); + } + _ => anyhow::bail!( + "HostOSConfig Ipv6Config should always be of type Deterministic. \ + Cannot reassign GuestOS networking." + ), + } + + let guestos_config = GuestOSConfig { + config_version: hostos_config.config_version, + network_settings: guestos_network_settings, + icos_settings: hostos_config.icos_settings, + guestos_settings: hostos_config.guestos_settings, + }; + + Ok(guestos_config) +} + +#[cfg(test)] +mod tests { + use super::*; + use config_types::{ + DeploymentEnvironment, DeterministicIpv6Config, HostOSConfig, ICOSSettings, Ipv6Config, + NetworkSettings, + }; + use std::net::Ipv6Addr; + + fn hostos_config_for_test() -> HostOSConfig { + HostOSConfig { + config_version: "1.0.0".to_string(), + network_settings: NetworkSettings { + ipv6_config: Ipv6Config::RouterAdvertisement, + ipv4_config: None, + domain_name: None, + }, + icos_settings: ICOSSettings { + node_reward_type: None, + mgmt_mac: Default::default(), + deployment_environment: DeploymentEnvironment::Testnet, + logging: Default::default(), + use_nns_public_key: false, + nns_urls: vec![], + use_node_operator_private_key: false, + use_ssh_authorized_keys: false, + icos_dev_settings: Default::default(), + }, + hostos_settings: Default::default(), + guestos_settings: Default::default(), + } + } + #[test] + fn test_successful_conversion() { + let mut hostos_config = hostos_config_for_test(); + hostos_config.network_settings.ipv6_config = + Ipv6Config::Deterministic(DeterministicIpv6Config { + prefix: "2001:db8::".to_string(), + prefix_length: 64, + gateway: "2001:db8::1".parse().unwrap(), + }); + + let guestos_config = generate_guestos_config(&hostos_config).unwrap(); + + assert_eq!(guestos_config.config_version, hostos_config.config_version); + assert_eq!( + guestos_config.network_settings.domain_name, + hostos_config.network_settings.domain_name + ); + assert_eq!( + guestos_config.network_settings.ipv4_config, + hostos_config.network_settings.ipv4_config + ); + assert_eq!(guestos_config.icos_settings, hostos_config.icos_settings); + assert_eq!( + guestos_config.guestos_settings, + hostos_config.guestos_settings + ); + + if let Ipv6Config::Fixed(fixed) = &guestos_config.network_settings.ipv6_config { + assert_eq!(fixed.address, "2001:db8::6801:94ff:feef:2978/64"); + assert_eq!(fixed.gateway, "2001:db8::1".parse::().unwrap()); + } else { + panic!("Unexpected Ipv6Config type"); + } + } + + #[test] + fn test_invalid_ipv6_configuration() { + let mut hostos_config = hostos_config_for_test(); + + hostos_config.network_settings.ipv6_config = Ipv6Config::Fixed(FixedIpv6Config { + address: "2001:db8::1/64".to_string(), + gateway: "2001:db8::1".parse().unwrap(), + }); + + let result = generate_guestos_config(&hostos_config); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("Deterministic")); + } +} diff --git a/rs/ic_os/config/src/lib.rs b/rs/ic_os/config/src/lib.rs index 2388f5af8c09..4280a5de0d9e 100644 --- a/rs/ic_os/config/src/lib.rs +++ b/rs/ic_os/config/src/lib.rs @@ -1,6 +1,7 @@ pub mod config_ini; pub mod deployment_json; pub mod generate_testnet_config; +pub mod guestos_config; pub mod update_config; use anyhow::{Context, Result}; diff --git a/rs/ic_os/config/src/main.rs b/rs/ic_os/config/src/main.rs index 6fe3c97b024e..68a662ecad74 100644 --- a/rs/ic_os/config/src/main.rs +++ b/rs/ic_os/config/src/main.rs @@ -13,6 +13,7 @@ use std::path::{Path, PathBuf}; use config::generate_testnet_config::{ generate_testnet_config, GenerateTestnetConfigArgs, Ipv6ConfigType, }; +use config::guestos_config::generate_guestos_config; use config_types::*; #[derive(Subcommand)] @@ -42,8 +43,6 @@ pub enum Commands { hostos_config_json_path: PathBuf, #[arg(long, default_value = config::DEFAULT_HOSTOS_GUESTOS_CONFIG_OBJECT_PATH, value_name = "config-guestos.json")] guestos_config_json_path: PathBuf, - #[arg(long, value_name = "ipv6_address")] - guestos_ipv6_address: String, }, /// Creates a GuestOSConfig object directly from GenerateTestnetConfigClapArgs. Only used for testing purposes. GenerateTestnetConfig(GenerateTestnetConfigClapArgs), @@ -292,38 +291,12 @@ pub fn main() -> Result<()> { Some(Commands::GenerateGuestosConfig { hostos_config_json_path, guestos_config_json_path, - guestos_ipv6_address, }) => { let hostos_config_json_path = Path::new(&hostos_config_json_path); - let hostos_config: HostOSConfig = serde_json::from_reader(File::open(hostos_config_json_path)?)?; - // TODO: We won't have to modify networking between the hostos and - // guestos config after completing the networking revamp (NODE-1327) - let mut guestos_network_settings = hostos_config.network_settings; - // Update the GuestOS networking if `guestos_ipv6_address` is provided - match &guestos_network_settings.ipv6_config { - Ipv6Config::Deterministic(deterministic_ipv6_config) => { - guestos_network_settings.ipv6_config = Ipv6Config::Fixed(FixedIpv6Config { - address: guestos_ipv6_address, - gateway: deterministic_ipv6_config.gateway, - }); - } - _ => { - anyhow::bail!( - "HostOSConfig Ipv6Config should always be of type Deterministic. Cannot reassign GuestOS networking." - ); - } - } - - let guestos_config = GuestOSConfig { - config_version: hostos_config.config_version, - network_settings: guestos_network_settings, - icos_settings: hostos_config.icos_settings, - guestos_settings: hostos_config.guestos_settings, - }; - + let guestos_config = generate_guestos_config(&hostos_config)?; let guestos_config_json_path = Path::new(&guestos_config_json_path); serialize_and_write_config(guestos_config_json_path, &guestos_config)?; diff --git a/rs/ic_os/config_types/src/lib.rs b/rs/ic_os/config_types/src/lib.rs index 3ee163c0a250..2f9600aca494 100644 --- a/rs/ic_os/config_types/src/lib.rs +++ b/rs/ic_os/config_types/src/lib.rs @@ -104,7 +104,7 @@ pub struct ICOSDevSettings {} pub struct SetupOSSettings; /// HostOS-specific settings. -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)] pub struct HostOSSettings { pub vm_memory: u32, pub vm_cpu: String, From 961d3f75d2ce6d39920de138092f5c443e4c0e1d Mon Sep 17 00:00:00 2001 From: David Frank Date: Thu, 22 May 2025 14:16:18 +0200 Subject: [PATCH 15/47] add missing dep --- rs/ic_os/config/BUILD.bazel | 1 + 1 file changed, 1 insertion(+) diff --git a/rs/ic_os/config/BUILD.bazel b/rs/ic_os/config/BUILD.bazel index 0207b75976d4..fd585ce65004 100644 --- a/rs/ic_os/config/BUILD.bazel +++ b/rs/ic_os/config/BUILD.bazel @@ -5,6 +5,7 @@ package(default_visibility = ["//rs:ic-os-pkg"]) DEPENDENCIES = [ # Keep sorted. "//rs/ic_os/config_types", + "//rs/ic_os/deterministic_ips", "//rs/ic_os/network", "//rs/ic_os/utils", "//rs/types/types", From 3f44097becef0bd3eabe96a556e8282d1cc1be10 Mon Sep 17 00:00:00 2001 From: David Frank Date: Thu, 22 May 2025 17:52:26 +0200 Subject: [PATCH 16/47] Fixes --- Cargo.lock | 1 + .../generate_guestos_vm_config/BUILD.bazel | 43 +++++++++++ .../generate_guestos_vm_config/Cargo.toml | 1 + .../generate_guestos_vm_config/src/main.rs | 72 +++++++++---------- 4 files changed, 78 insertions(+), 39 deletions(-) create mode 100644 rs/ic_os/generate_guestos_vm_config/BUILD.bazel diff --git a/Cargo.lock b/Cargo.lock index 918451062088..5c09289c8e64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4921,6 +4921,7 @@ dependencies = [ "config", "config_types", "deterministic_ips", + "ic-metrics-tool", "macaddr", "serde_json", ] diff --git a/rs/ic_os/generate_guestos_vm_config/BUILD.bazel b/rs/ic_os/generate_guestos_vm_config/BUILD.bazel new file mode 100644 index 000000000000..774845aa5480 --- /dev/null +++ b/rs/ic_os/generate_guestos_vm_config/BUILD.bazel @@ -0,0 +1,43 @@ +load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_test") + +package(default_visibility = ["//visibility:public"]) + +DEPENDENCIES = [ + # Keep sorted. + "//rs/ic_os/config:config_lib", + "//rs/ic_os/config_types", + "//rs/ic_os/deterministic_ips", + "//rs/ic_os/metrics_tool", + "@crate_index//:anyhow", + "@crate_index//:askama", + "@crate_index//:clap", + "@crate_index//:macaddr", + "@crate_index//:serde_json", +] + +DEV_DEPENDENCIES = [] + +rust_binary( + name = "generate_guestos_vm_config", + srcs = glob(["src/**/*.rs"]), + compile_data = ["templates/guestos_vm_template.xml"], + crate_name = "generate_guestos_vm_config", + deps = DEPENDENCIES + ["//rs/ic_os/bootstrap_config"], +) + +# Dev version for testing that should only be enabled in dev builds (unsafe for prod). +rust_binary( + name = "generate_guestos_vm_config_dev", + srcs = glob(["src/**/*.rs"]), + compile_data = ["templates/guestos_vm_template.xml"], + crate_features = ["dev"], + crate_name = "generate_guestos_vm_config", + deps = DEPENDENCIES + ["//rs/ic_os/bootstrap_config:bootstrap_config_dev"], +) + +rust_test( + name = "generate_guestos_vm_config_test", + crate = ":generate_guestos_vm_config_dev", + crate_features = ["dev"], + deps = DEV_DEPENDENCIES, +) diff --git a/rs/ic_os/generate_guestos_vm_config/Cargo.toml b/rs/ic_os/generate_guestos_vm_config/Cargo.toml index 2a461bd3e3b4..49772200cd96 100644 --- a/rs/ic_os/generate_guestos_vm_config/Cargo.toml +++ b/rs/ic_os/generate_guestos_vm_config/Cargo.toml @@ -12,6 +12,7 @@ config_types = { path = "../config_types" } deterministic_ips = { path = "../deterministic_ips" } macaddr = { workspace = true } serde_json = { workspace = true } +ic-metrics-tool = { path = "../metrics_tool" } [features] dev = ["bootstrap_config/dev"] diff --git a/rs/ic_os/generate_guestos_vm_config/src/main.rs b/rs/ic_os/generate_guestos_vm_config/src/main.rs index f806f0a54e17..41237cf4007b 100644 --- a/rs/ic_os/generate_guestos_vm_config/src/main.rs +++ b/rs/ic_os/generate_guestos_vm_config/src/main.rs @@ -7,11 +7,11 @@ use config::{ serialize_and_write_config, DEFAULT_HOSTOS_CONFIG_OBJECT_PATH, DEFAULT_HOSTOS_GUESTOS_CONFIG_OBJECT_PATH, }; -use config_types::{HostOSConfig, Ipv6Config}; +use config_types::{GuestOSConfig, HostOSConfig, Ipv6Config}; use deterministic_ips::node_type::NodeType; use deterministic_ips::{calculate_deterministic_mac, IpVariant}; +use ic_metrics_tool::{Metric, MetricsWriter}; use macaddr::MacAddr6; -use serde_json; use std::fs::{self, File}; use std::io::Write; use std::path::{Path, PathBuf}; @@ -34,20 +34,6 @@ struct Args { config: PathBuf, } -/// Write a metric using the provided metrics script -fn write_metric(name: &str, value: &str, description: &str, metric_type: &str) -> Result<()> { - // run_command_status( - // Command::new("/opt/ic/bin/metrics.sh") - // .arg("write_metric") - // .arg(name) - // .arg(value) - // .arg(description) - // .arg(metric_type), - // )?; - - Ok(()) -} - /// Get the IPv6 gateway from the configuration fn get_ipv6_gateway(config: &HostOSConfig) -> Result { match &config.network_settings.ipv6_config { @@ -66,15 +52,28 @@ fn assemble_config_media(hostos_config: &HostOSConfig, media_path: &Path) -> Res Path::new(DEFAULT_HOSTOS_GUESTOS_CONFIG_OBJECT_PATH), &guestos_config, )?; + println!("GuestOSConfig has been written to {DEFAULT_HOSTOS_GUESTOS_CONFIG_OBJECT_PATH}"); + + let bootstrap_options = make_bootstrap_options(&hostos_config, guestos_config)?; + + build_bootstrap_config_image(media_path, &bootstrap_options)?; + println!( - "GuestOSConfig has been written to {}", - DEFAULT_HOSTOS_GUESTOS_CONFIG_OBJECT_PATH + "Assembling config media for GuestOS: {}", + media_path.display() ); - let mut bootstrap_options = BootstrapOptions::default(); + Ok(()) +} - bootstrap_options.guestos_config = - Some(PathBuf::from(DEFAULT_HOSTOS_GUESTOS_CONFIG_OBJECT_PATH)); +fn make_bootstrap_options( + hostos_config: &HostOSConfig, + guestos_config: GuestOSConfig, +) -> Result { + let mut bootstrap_options = BootstrapOptions { + guestos_config: Some(PathBuf::from(DEFAULT_HOSTOS_GUESTOS_CONFIG_OBJECT_PATH)), + ..Default::default() + }; // Set SSH authorized keys (only in dev builds) #[cfg(feature = "dev")] @@ -92,8 +91,8 @@ fn assemble_config_media(hostos_config: &HostOSConfig, media_path: &Path) -> Res Some(PathBuf::from("/boot/config/node_operator_private_key.pem")); } - let guestos_address = match &guestos_config.network_settings.ipv6_config { - &Ipv6Config::Fixed(ref ip_config) => ip_config.address.clone(), + let guestos_address = match guestos_config.network_settings.ipv6_config { + Ipv6Config::Fixed(ip_config) => ip_config.address, _ => bail!( "Expected GuestOS IPv6 address to be fixed but was {:?}", guestos_config.network_settings.ipv6_config @@ -135,14 +134,7 @@ fn assemble_config_media(hostos_config: &HostOSConfig, media_path: &Path) -> Res .map(|url| url.to_string()) .collect(); - build_bootstrap_config_image(media_path, &bootstrap_options)?; - - println!( - "Assembling config media for GuestOS: {}", - media_path.display() - ); - - Ok(()) + Ok(bootstrap_options) } fn generate_vm_config(config: &HostOSConfig, media_path: &Path) -> Result { @@ -185,6 +177,10 @@ fn generate_vm_config(config: &HostOSConfig, media_path: &Path) -> Result Result<()> { let args = Args::parse(); + let metrics_writer = MetricsWriter::new( + "/run/node_exporter/collector_textfile/hostos_generate_guestos_config.prom", + ); + let hostos_config: HostOSConfig = serde_json::from_reader(File::open(&args.config).context("Failed to open config file")?) .context("Failed to parse config file")?; @@ -193,12 +189,11 @@ fn main() -> Result<()> { .context("Failed to assemble config media")?; if args.output.exists() { - write_metric( + metrics_writer.write_metrics(&[Metric::with_annotation( "hostos_generate_guestos_config", - "0", + 0.0, "HostOS generate GuestOS config", - "gauge", - )?; + )])?; bail!( "GuestOS configuration file already exists: {}", @@ -235,12 +230,11 @@ fn main() -> Result<()> { output_path.display(), ); - write_metric( + metrics_writer.write_metrics(&[Metric::with_annotation( "hostos_generate_guestos_config", - "1", + 1.0, "HostOS generate GuestOS config", - "gauge", - )?; + )])?; Ok(()) } From e800a5b40db5c4d345aa4d49e93a0eb1cc7b1d88 Mon Sep 17 00:00:00 2001 From: David Frank Date: Thu, 22 May 2025 21:01:36 +0200 Subject: [PATCH 17/47] Add tests --- Cargo.Bazel.json.lock | 253 +++++++++++- Cargo.Bazel.toml.lock | 50 ++- Cargo.lock | 29 ++ Cargo.toml | 1 + bazel/external_crates.bzl | 3 + rs/ic_os/bootstrap_config/src/lib.rs | 2 +- rs/ic_os/config/src/lib.rs | 1 - .../generate_guestos_vm_config/BUILD.bazel | 10 +- .../generate_guestos_vm_config/Cargo.toml | 7 +- .../golden/guestos_vm_kvm.xml | 183 +++++++++ .../golden/guestos_vm_qemu.xml | 179 +++++++++ .../generate_guestos_vm_config/src/main.rs | 373 +++++++++++++++--- .../templates/guestos_vm_template.xml | 4 +- rs/ic_os/metrics_tool/BUILD.bazel | 5 +- 14 files changed, 1026 insertions(+), 74 deletions(-) create mode 100644 rs/ic_os/generate_guestos_vm_config/golden/guestos_vm_kvm.xml create mode 100644 rs/ic_os/generate_guestos_vm_config/golden/guestos_vm_qemu.xml diff --git a/Cargo.Bazel.json.lock b/Cargo.Bazel.json.lock index 81e044d5b62b..b00a994a48c7 100644 --- a/Cargo.Bazel.json.lock +++ b/Cargo.Bazel.json.lock @@ -1,5 +1,5 @@ { - "checksum": "9286f7e1cc3c1e5ef9503cc0a9ec592a5061b3f8833c81ec052a54dd0e0217b5", + "checksum": "7f916d7bf713df4be41265dcddce6c71da1a95f012bc0cf77c2697f9c5907782", "crates": { "abnf 0.12.0": { "name": "abnf", @@ -9858,6 +9858,70 @@ ], "license_file": "LICENSE-APACHE" }, + "bstr 0.2.17": { + "name": "bstr", + "version": "0.2.17", + "package_url": "https://github.com/BurntSushi/bstr", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/bstr/0.2.17/download", + "sha256": "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" + } + }, + "targets": [ + { + "Library": { + "crate_name": "bstr", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "bstr", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "lazy_static", + "regex-automata", + "unicode" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "lazy_static 1.5.0", + "target": "lazy_static" + }, + { + "id": "memchr 2.7.4", + "target": "memchr" + }, + { + "id": "regex-automata 0.1.10", + "target": "regex_automata" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "0.2.17" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "bstr 1.6.0": { "name": "bstr", "version": "1.6.0", @@ -20114,6 +20178,10 @@ "id": "getrandom 0.2.10", "target": "getrandom" }, + { + "id": "goldenfile 1.8.0", + "target": "goldenfile" + }, { "id": "group 0.13.0", "target": "group" @@ -27901,6 +27969,65 @@ ], "license_file": "LICENSE-APACHE" }, + "goldenfile 1.8.0": { + "name": "goldenfile", + "version": "1.8.0", + "package_url": "https://github.com/calder/rust-goldenfile", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/goldenfile/1.8.0/download", + "sha256": "cf39e208efa110ca273f7255aea02485103ffcb7e5dfa5e4196b05a02411618e" + } + }, + "targets": [ + { + "Library": { + "crate_name": "goldenfile", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "goldenfile", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "scopeguard 1.2.0", + "target": "scopeguard" + }, + { + "id": "similar-asserts 1.7.0", + "target": "similar_asserts" + }, + { + "id": "tempfile 3.12.0", + "target": "tempfile" + }, + { + "id": "yansi 1.0.1", + "target": "yansi" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "1.8.0" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + }, "governor 0.8.1": { "name": "governor", "version": "0.8.1", @@ -70089,9 +70216,25 @@ ], "crate_features": { "common": [ + "bstr", "default", "inline", - "text" + "text", + "unicode", + "unicode-segmentation" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "bstr 0.2.17", + "target": "bstr" + }, + { + "id": "unicode-segmentation 1.12.0", + "target": "unicode_segmentation" + } ], "selects": {} }, @@ -70104,6 +70247,64 @@ ], "license_file": "LICENSE" }, + "similar-asserts 1.7.0": { + "name": "similar-asserts", + "version": "1.7.0", + "package_url": "https://github.com/mitsuhiko/similar-asserts", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/similar-asserts/1.7.0/download", + "sha256": "b5b441962c817e33508847a22bd82f03a30cff43642dc2fae8b050566121eb9a" + } + }, + "targets": [ + { + "Library": { + "crate_name": "similar_asserts", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "similar_asserts", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "unicode" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "console 0.15.7", + "target": "console" + }, + { + "id": "similar 2.2.1", + "target": "similar" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "1.7.0" + }, + "license": "Apache-2.0", + "license_ids": [ + "Apache-2.0" + ], + "license_file": "LICENSE" + }, "simple_asn1 0.6.2": { "name": "simple_asn1", "version": "0.6.2", @@ -89035,6 +89236,53 @@ ], "license_file": "LICENSE-APACHE" }, + "yansi 1.0.1": { + "name": "yansi", + "version": "1.0.1", + "package_url": "https://github.com/SergioBenitez/yansi", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/yansi/1.0.1/download", + "sha256": "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + } + }, + "targets": [ + { + "Library": { + "crate_name": "yansi", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "yansi", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "default", + "std" + ], + "selects": {} + }, + "edition": "2021", + "version": "1.0.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "yasna 0.5.2": { "name": "yasna", "version": "0.5.2", @@ -90595,6 +90843,7 @@ "futures-util 0.3.31", "get_if_addrs 0.5.3", "getrandom 0.2.10", + "goldenfile 1.8.0", "group 0.13.0", "hashlink 0.8.3", "hex 0.4.3", diff --git a/Cargo.Bazel.toml.lock b/Cargo.Bazel.toml.lock index ec0e4f57a501..2f51f6cd389a 100644 --- a/Cargo.Bazel.toml.lock +++ b/Cargo.Bazel.toml.lock @@ -658,7 +658,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" dependencies = [ "anstyle", - "bstr", + "bstr 1.6.0", "doc-comment", "libc", "predicates", @@ -1695,6 +1695,17 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata 0.1.10", +] + [[package]] name = "bstr" version = "1.6.0" @@ -3451,6 +3462,7 @@ dependencies = [ "futures-util", "get_if_addrs", "getrandom 0.2.10", + "goldenfile", "group 0.13.0", "hashlink", "hex", @@ -3702,7 +3714,7 @@ dependencies = [ "wycheproof", "x509-cert", "x509-parser 0.16.0", - "yansi", + "yansi 0.5.1", "zeroize", "zstd 0.13.2", ] @@ -4795,6 +4807,18 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "goldenfile" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf39e208efa110ca273f7255aea02485103ffcb7e5dfa5e4196b05a02411618e" +dependencies = [ + "scopeguard", + "similar-asserts", + "tempfile", + "yansi 1.0.1", +] + [[package]] name = "governor" version = "0.8.1" @@ -9529,7 +9553,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" dependencies = [ "diff", - "yansi", + "yansi 0.5.1", ] [[package]] @@ -11808,6 +11832,20 @@ name = "similar" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" +dependencies = [ + "bstr 0.2.17", + "unicode-segmentation", +] + +[[package]] +name = "similar-asserts" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b441962c817e33508847a22bd82f03a30cff43642dc2fae8b050566121eb9a" +dependencies = [ + "console 0.15.7", + "similar", +] [[package]] name = "simple_asn1" @@ -14828,6 +14866,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yasna" version = "0.5.2" diff --git a/Cargo.lock b/Cargo.lock index 5c09289c8e64..bc29280cd836 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4921,9 +4921,12 @@ dependencies = [ "config", "config_types", "deterministic_ips", + "goldenfile", "ic-metrics-tool", "macaddr", "serde_json", + "tempfile", + "url", ] [[package]] @@ -5046,6 +5049,18 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +[[package]] +name = "goldenfile" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf39e208efa110ca273f7255aea02485103ffcb7e5dfa5e4196b05a02411618e" +dependencies = [ + "scopeguard", + "similar-asserts", + "tempfile", + "yansi 1.0.1", +] + [[package]] name = "governor" version = "0.8.1" @@ -21609,6 +21624,20 @@ name = "similar" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" +dependencies = [ + "bstr", + "unicode-segmentation", +] + +[[package]] +name = "similar-asserts" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b441962c817e33508847a22bd82f03a30cff43642dc2fae8b050566121eb9a" +dependencies = [ + "console", + "similar", +] [[package]] name = "simple_asn1" diff --git a/Cargo.toml b/Cargo.toml index 1a3d79c4c37b..9b01e317ff31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -564,6 +564,7 @@ flate2 = "1.0.31" futures = "0.3.31" futures-util = "0.3.31" getrandom = { version = "0.2", features = ["custom"] } +goldenfile = "1.8.0" hex = { version = "0.4.3", features = ["serde"] } http = "1.3.1" http-body = "1.0.1" diff --git a/bazel/external_crates.bzl b/bazel/external_crates.bzl index 52dc3bf4948e..d9dd83a315d2 100644 --- a/bazel/external_crates.bzl +++ b/bazel/external_crates.bzl @@ -526,6 +526,9 @@ def external_crates_repository(name, cargo_lockfile, lockfile, sanitizers_enable "custom", ], ), + "goldenfile": crate.spec( + version = "^1.8", + ), "group": crate.spec( version = "^0.13", ), diff --git a/rs/ic_os/bootstrap_config/src/lib.rs b/rs/ic_os/bootstrap_config/src/lib.rs index ae0e9badc827..12c7aa800654 100644 --- a/rs/ic_os/bootstrap_config/src/lib.rs +++ b/rs/ic_os/bootstrap_config/src/lib.rs @@ -11,7 +11,7 @@ use std::process::Command; use ic_types::malicious_behaviour::MaliciousBehaviour; /// Configuration options for bootstrap image/tar creation -#[derive(Default, Debug, Clone)] +#[derive(Default, Debug, Clone, Eq, PartialEq)] pub struct BootstrapOptions { /// The serialized GuestOS config object. pub guestos_config: Option, diff --git a/rs/ic_os/config/src/lib.rs b/rs/ic_os/config/src/lib.rs index 4280a5de0d9e..1b0ea4c67de0 100644 --- a/rs/ic_os/config/src/lib.rs +++ b/rs/ic_os/config/src/lib.rs @@ -20,7 +20,6 @@ pub static DEFAULT_SETUPOS_HOSTOS_CONFIG_OBJECT_PATH: &str = "/var/ic/config/con pub static DEFAULT_HOSTOS_CONFIG_INI_FILE_PATH: &str = "/boot/config/config.ini"; pub static DEFAULT_HOSTOS_DEPLOYMENT_JSON_PATH: &str = "/boot/config/deployment.json"; pub static DEFAULT_HOSTOS_CONFIG_OBJECT_PATH: &str = "/boot/config/config.json"; -pub static DEFAULT_HOSTOS_GUESTOS_CONFIG_OBJECT_PATH: &str = "/boot/config/config-guestos.json"; pub static DEFAULT_GUESTOS_CONFIG_OBJECT_PATH: &str = "/boot/config/config.json"; pub fn serialize_and_write_config(path: &Path, config: &T) -> Result<()> { diff --git a/rs/ic_os/generate_guestos_vm_config/BUILD.bazel b/rs/ic_os/generate_guestos_vm_config/BUILD.bazel index 774845aa5480..a20d62d4b2db 100644 --- a/rs/ic_os/generate_guestos_vm_config/BUILD.bazel +++ b/rs/ic_os/generate_guestos_vm_config/BUILD.bazel @@ -13,9 +13,13 @@ DEPENDENCIES = [ "@crate_index//:clap", "@crate_index//:macaddr", "@crate_index//:serde_json", + "@crate_index//:tempfile", ] -DEV_DEPENDENCIES = [] +DEV_DEPENDENCIES = [ + "@crate_index//:url", + "@crate_index//:goldenfile", +] rust_binary( name = "generate_guestos_vm_config", @@ -39,5 +43,9 @@ rust_test( name = "generate_guestos_vm_config_test", crate = ":generate_guestos_vm_config_dev", crate_features = ["dev"], + data = glob(["golden/*"]), + env = {"CARGO_MANIFEST_DIR": "rs/ic_os/generate_guestos_vm_config"}, + # Run without sandbox so that goldenfiles can be updated by passing "--test_env UPDATE_GOLDENFILES=1" + tags = ["local"], deps = DEV_DEPENDENCIES, ) diff --git a/rs/ic_os/generate_guestos_vm_config/Cargo.toml b/rs/ic_os/generate_guestos_vm_config/Cargo.toml index 49772200cd96..1aa8e1b776ed 100644 --- a/rs/ic_os/generate_guestos_vm_config/Cargo.toml +++ b/rs/ic_os/generate_guestos_vm_config/Cargo.toml @@ -10,9 +10,14 @@ clap = { workspace = true } config = { path = "../config" } config_types = { path = "../config_types" } deterministic_ips = { path = "../deterministic_ips" } +ic-metrics-tool = { path = "../metrics_tool" } macaddr = { workspace = true } serde_json = { workspace = true } -ic-metrics-tool = { path = "../metrics_tool" } +tempfile = { workspace = true } + +[dev-dependencies] +url = { workspace = true } +goldenfile = { workspace = true } [features] dev = ["bootstrap_config/dev"] diff --git a/rs/ic_os/generate_guestos_vm_config/golden/guestos_vm_kvm.xml b/rs/ic_os/generate_guestos_vm_config/golden/guestos_vm_kvm.xml new file mode 100644 index 000000000000..e8f787e1e0f1 --- /dev/null +++ b/rs/ic_os/generate_guestos_vm_config/golden/guestos_vm_kvm.xml @@ -0,0 +1,183 @@ + + guestos + fd897da5-8017-41c8-8575-a706dba30766 + + + + + + 490 + 490 + 56 + + + + + + + /machine + + + hvm + /usr/share/OVMF/OVMF_CODE_4M.fd + /var/lib/libvirt/qemu/nvram/guestos_VARS.fd + + + + + + + + + + + + restart + restart + restart + + + + + + /usr/local/bin/qemu-system-x86_64 + + + + + + +
+ + + + + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + +
+ + + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+ + + /dev/urandom + +
+ + + + + +64055:+108 + + \ No newline at end of file diff --git a/rs/ic_os/generate_guestos_vm_config/golden/guestos_vm_qemu.xml b/rs/ic_os/generate_guestos_vm_config/golden/guestos_vm_qemu.xml new file mode 100644 index 000000000000..19438fb5a026 --- /dev/null +++ b/rs/ic_os/generate_guestos_vm_config/golden/guestos_vm_qemu.xml @@ -0,0 +1,179 @@ + + guestos + fd897da5-8017-41c8-8575-a706dba30766 + + + + + + 490 + 490 + 56 + + + /machine + + + hvm + /usr/share/OVMF/OVMF_CODE_4M.fd + /var/lib/libvirt/qemu/nvram/guestos_VARS.fd + + + + + + + + + + + + restart + restart + restart + + + + + + /usr/local/bin/qemu-system-x86_64 + + + + + + +
+ + + + + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + +
+ + + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+ + + /dev/urandom + +
+ + + + + +64055:+108 + + \ No newline at end of file diff --git a/rs/ic_os/generate_guestos_vm_config/src/main.rs b/rs/ic_os/generate_guestos_vm_config/src/main.rs index 41237cf4007b..b26f1389246a 100644 --- a/rs/ic_os/generate_guestos_vm_config/src/main.rs +++ b/rs/ic_os/generate_guestos_vm_config/src/main.rs @@ -3,10 +3,7 @@ use askama::Template; use bootstrap_config::{build_bootstrap_config_image, BootstrapOptions}; use clap::Parser; use config::guestos_config::generate_guestos_config; -use config::{ - serialize_and_write_config, DEFAULT_HOSTOS_CONFIG_OBJECT_PATH, - DEFAULT_HOSTOS_GUESTOS_CONFIG_OBJECT_PATH, -}; +use config::{serialize_and_write_config, DEFAULT_HOSTOS_CONFIG_OBJECT_PATH}; use config_types::{GuestOSConfig, HostOSConfig, Ipv6Config}; use deterministic_ips::node_type::NodeType; use deterministic_ips::{calculate_deterministic_mac, IpVariant}; @@ -34,6 +31,74 @@ struct Args { config: PathBuf, } +pub fn main() -> Result<()> { + let args = Args::parse(); + + let metrics_writer = MetricsWriter::new( + "/run/node_exporter/collector_textfile/hostos_generate_guestos_config.prom", + ); + + let hostos_config: HostOSConfig = serde_json::from_reader( + File::open(&args.config).context("Failed to open HostOS config file")?, + ) + .context("Failed to parse config file")?; + + run(&args, &metrics_writer, &hostos_config, restorecon) +} + +fn run( + args: &Args, + metrics_writer: &MetricsWriter, + hostos_config: &HostOSConfig, + // We pass a functor to allow mocking in tests. + restorecon: impl Fn(&Path) -> Result<()>, +) -> Result<()> { + assemble_config_media(hostos_config, &args.media).context("Failed to assemble config media")?; + + if args.output.exists() { + metrics_writer.write_metrics(&[Metric::with_annotation( + "hostos_generate_guestos_config", + 0.0, + "HostOS generate GuestOS config", + )])?; + + bail!( + "GuestOS configuration file already exists: {}", + args.output.display() + ); + } + + let output_path = &args.output; + + // Create parent directory if it doesn't exist + if let Some(parent) = output_path.parent() { + fs::create_dir_all(parent).context("Failed to create output directory")?; + } + + File::create(output_path) + .context("Failed to create output file")? + .write_all(generate_vm_config(hostos_config, &args.media)?.as_bytes()) + .context("Failed to write output file")?; + + // Restore SELinux security context + if let Some(parent) = output_path.parent() { + restorecon(parent)? + } + + println!( + "Generating GuestOS configuration file: {}", + output_path.display(), + ); + + metrics_writer.write_metrics(&[Metric::with_annotation( + "hostos_generate_guestos_config", + 1.0, + "HostOS generate GuestOS config", + )])?; + + Ok(()) +} + /// Get the IPv6 gateway from the configuration fn get_ipv6_gateway(config: &HostOSConfig) -> Result { match &config.network_settings.ipv6_config { @@ -45,16 +110,19 @@ fn get_ipv6_gateway(config: &HostOSConfig) -> Result { } } -/// Assemble the configuration media fn assemble_config_media(hostos_config: &HostOSConfig, media_path: &Path) -> Result<()> { - let guestos_config = generate_guestos_config(hostos_config)?; - serialize_and_write_config( - Path::new(DEFAULT_HOSTOS_GUESTOS_CONFIG_OBJECT_PATH), - &guestos_config, - )?; - println!("GuestOSConfig has been written to {DEFAULT_HOSTOS_GUESTOS_CONFIG_OBJECT_PATH}"); - - let bootstrap_options = make_bootstrap_options(&hostos_config, guestos_config)?; + let guestos_config_file = tempfile::NamedTempFile::new()?; + let guestos_config = + generate_guestos_config(hostos_config).context("Failed to generate GuestOS config")?; + serialize_and_write_config(guestos_config_file.path(), &guestos_config).with_context(|| { + format!( + "Failed to write GuestOS config to {}", + guestos_config_file.path().display() + ) + })?; + + let bootstrap_options = + make_bootstrap_options(hostos_config, guestos_config, guestos_config_file.path())?; build_bootstrap_config_image(media_path, &bootstrap_options)?; @@ -69,9 +137,10 @@ fn assemble_config_media(hostos_config: &HostOSConfig, media_path: &Path) -> Res fn make_bootstrap_options( hostos_config: &HostOSConfig, guestos_config: GuestOSConfig, + guestos_config_path: &Path, ) -> Result { let mut bootstrap_options = BootstrapOptions { - guestos_config: Some(PathBuf::from(DEFAULT_HOSTOS_GUESTOS_CONFIG_OBJECT_PATH)), + guestos_config: Some(guestos_config_path.to_path_buf()), ..Default::default() }; @@ -174,67 +243,247 @@ fn generate_vm_config(config: &HostOSConfig, media_path: &Path) -> Result Result<()> { - let args = Args::parse(); +fn restorecon(path: &Path) -> Result<()> { + Command::new("restorecon") + .arg("-R") + .arg(path) + .status()? + .success() + .then_some(()) + .context("Failed to run restorecon") +} - let metrics_writer = MetricsWriter::new( - "/run/node_exporter/collector_textfile/hostos_generate_guestos_config.prom", - ); +#[cfg(test)] +mod tests { + use super::*; + use config_types::{ + DeploymentEnvironment, DeterministicIpv6Config, FixedIpv6Config, HostOSConfig, + HostOSSettings, ICOSSettings, Ipv4Config, Ipv6Config, Logging, NetworkSettings, + }; + use goldenfile::Mint; + use std::env; + use std::path::Path; + use tempfile::tempdir; + + fn create_test_hostos_config() -> HostOSConfig { + HostOSConfig { + config_version: "1.0".to_string(), + network_settings: NetworkSettings { + ipv6_config: Ipv6Config::Deterministic(DeterministicIpv6Config { + prefix: "2001:db8::".to_string(), + prefix_length: 64, + gateway: "2001:db8::ffff".parse().unwrap(), + }), + ipv4_config: Some(Ipv4Config { + address: "192.168.1.2".parse().unwrap(), + gateway: "192.168.1.1".parse().unwrap(), + prefix_length: 24, + }), + domain_name: Some("test.domain".to_string()), + }, + icos_settings: ICOSSettings { + node_reward_type: Some("test-reward".to_string()), + mgmt_mac: "00:11:22:33:44:55".parse().unwrap(), + deployment_environment: DeploymentEnvironment::Testnet, + logging: Logging { + elasticsearch_hosts: None, + elasticsearch_tags: None, + }, + use_nns_public_key: false, + nns_urls: vec![url::Url::parse("https://example.com").unwrap()], + use_node_operator_private_key: false, + use_ssh_authorized_keys: false, + icos_dev_settings: Default::default(), + }, + hostos_settings: HostOSSettings { + vm_memory: 490, + vm_cpu: "qemu".to_string(), + vm_nr_of_vcpus: 56, + verbose: false, + }, + guestos_settings: Default::default(), + } + } - let hostos_config: HostOSConfig = - serde_json::from_reader(File::open(&args.config).context("Failed to open config file")?) - .context("Failed to parse config file")?; + fn mock_restorecon(_path: &Path) -> Result<()> { + Ok(()) + } - assemble_config_media(&hostos_config, &args.media) - .context("Failed to assemble config media")?; + #[test] + fn test_make_bootstrap_options() { + let mut config = create_test_hostos_config(); + config.icos_settings.use_nns_public_key = true; + config.icos_settings.use_ssh_authorized_keys = true; + config.icos_settings.use_node_operator_private_key = true; + + let guestos_config = generate_guestos_config(&config).unwrap(); + + let options = + make_bootstrap_options(&config, guestos_config, Path::new("/tmp/test")).unwrap(); + + assert_eq!( + options, + BootstrapOptions { + ipv6_address: Some("2001:db8::6801:aeff:fe1a:9bb/64".to_string()), + ipv6_gateway: Some("2001:db8::ffff".to_string()), + ipv4_address: Some("192.168.1.2/24".to_string()), + ipv4_gateway: Some("192.168.1.1".to_string()), + domain: Some("test.domain".to_string()), + node_reward_type: Some("test-reward".to_string()), + hostname: Some("guest-001122334455".to_string()), + nns_urls: vec!["https://example.com/".to_string()], + guestos_config: Some(PathBuf::from("/tmp/test")), + nns_public_key: Some(PathBuf::from("/boot/config/nns_public_key.pem")), + node_operator_private_key: Some(PathBuf::from( + "/boot/config/node_operator_private_key.pem" + )), + accounts_ssh_authorized_keys: Some(PathBuf::from( + "/boot/config/ssh_authorized_keys" + )), + ..Default::default() + } + ); + } - if args.output.exists() { - metrics_writer.write_metrics(&[Metric::with_annotation( - "hostos_generate_guestos_config", - 0.0, - "HostOS generate GuestOS config", - )])?; + fn goldenfiles_path() -> PathBuf { + let mut path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + path.push("golden"); + path + } - bail!( - "GuestOS configuration file already exists: {}", - args.output.display() - ); + #[test] + fn test_generate_vm_config_qemu() { + let mut mint = Mint::new(goldenfiles_path()); + let mut config = create_test_hostos_config(); + + config.hostos_settings = HostOSSettings { + vm_memory: 490, + vm_cpu: "qemu".to_string(), + vm_nr_of_vcpus: 56, + verbose: true, + }; + + let vm_config = generate_vm_config(&config, Path::new("/tmp/config.img")).unwrap(); + + fs::write( + mint.new_goldenpath("guestos_vm_qemu.xml").unwrap(), + vm_config, + ) + .unwrap(); } - let output_path = &args.output; + #[test] + fn test_generate_vm_config_kvm() { + let mut mint = Mint::new(goldenfiles_path()); + let mut config = create_test_hostos_config(); + config.hostos_settings = HostOSSettings { + vm_memory: 490, + vm_cpu: "kvm".to_string(), + vm_nr_of_vcpus: 56, + verbose: false, + }; + + let vm_config = generate_vm_config(&config, Path::new("/tmp/config.img")).unwrap(); + + fs::write( + mint.new_goldenpath("guestos_vm_kvm.xml").unwrap(), + vm_config, + ) + .unwrap(); + } - // Create parent directory if it doesn't exist - if let Some(parent) = output_path.parent() { - fs::create_dir_all(parent).context("Failed to create output directory")?; + #[test] + fn test_run_success() { + let temp_dir = tempdir().unwrap(); + let media_path = temp_dir.path().join("config.img"); + let output_path = temp_dir.path().join("guestos.xml"); + let metrics_path = temp_dir.path().join("metrics.prom"); + + let args = Args { + media: media_path.clone(), + output: output_path.clone(), + config: PathBuf::from("/non/existent/path"), + }; + + let metrics_writer = MetricsWriter::new(metrics_path.to_str().unwrap()); + let config = create_test_hostos_config(); + + let result = run(&args, &metrics_writer, &config, mock_restorecon); + assert!(result.is_ok(), "{result:?}"); + + assert_eq!( + fs::read_to_string(metrics_path).unwrap(), + "# HELP hostos_generate_guestos_config HostOS generate GuestOS config\n\ + # TYPE hostos_generate_guestos_config counter\n\ + hostos_generate_guestos_config 1\n" + ) } - File::create(output_path) - .context("Failed to create output file")? - .write_all(generate_vm_config(&hostos_config, &args.media)?.as_bytes()) - .context("Failed to write output file")?; + #[test] + fn test_run_existing_output_file() { + let temp_dir = tempdir().unwrap(); + let metrics_path = temp_dir.path().join("metrics.prom"); + let media_path = temp_dir.path().join("config.img"); + let output_path = temp_dir.path().join("guestos.xml"); - // Restore SELinux security context - if let Some(parent) = output_path.parent() { - if !Command::new("restorecon") - .arg("-R") - .arg(parent) - .status()? - .success() - { - bail!("Failed to run restorecon"); - } + // Create the output file so it already exists + fs::write(&output_path, "test").unwrap(); + + let args = Args { + media: media_path, + output: output_path, + config: PathBuf::from("/path/to/config"), + }; + + let metrics_writer = MetricsWriter::new(metrics_path.to_str().unwrap()); + let config = create_test_hostos_config(); + + let result_err = run(&args, &metrics_writer, &config, mock_restorecon).unwrap_err(); + + assert!( + result_err.to_string().contains("already exists"), + "{result_err:?}" + ); + + assert_eq!( + fs::read_to_string(metrics_path).unwrap(), + "# HELP hostos_generate_guestos_config HostOS generate GuestOS config\n\ + # TYPE hostos_generate_guestos_config counter\n\ + hostos_generate_guestos_config 0\n" + ) } - println!( - "Generating GuestOS configuration file: {}", - output_path.display(), - ); + #[test] + fn test_get_ipv6_gateway_fixed() { + let mut config = create_test_hostos_config(); + config.network_settings.ipv6_config = Ipv6Config::Fixed(FixedIpv6Config { + address: "2001:db9::1/64".to_string(), + gateway: "2001:db9::ffff".parse().unwrap(), + }); + let gateway = get_ipv6_gateway(&config).unwrap(); + assert_eq!(gateway, "2001:db9::ffff"); + } - metrics_writer.write_metrics(&[Metric::with_annotation( - "hostos_generate_guestos_config", - 1.0, - "HostOS generate GuestOS config", - )])?; + #[test] + fn test_get_ipv6_gateway_deterministic() { + let mut config = create_test_hostos_config(); + config.network_settings.ipv6_config = Ipv6Config::Deterministic(DeterministicIpv6Config { + prefix: "2001:db8::".to_string(), + prefix_length: 64, + gateway: "2001:db8::1".parse().unwrap(), + }); + let gateway = get_ipv6_gateway(&config).unwrap(); + assert_eq!(gateway, "2001:db8::1"); + } - Ok(()) + #[test] + fn test_get_ipv6_gateway_router_advertisement_error() { + let mut config = create_test_hostos_config(); + config.network_settings.ipv6_config = Ipv6Config::RouterAdvertisement; + let result = get_ipv6_gateway(&config).unwrap_err(); + assert!( + result.to_string().contains("does not have a gateway"), + "{result:?}" + ); + } } diff --git a/rs/ic_os/generate_guestos_vm_config/templates/guestos_vm_template.xml b/rs/ic_os/generate_guestos_vm_config/templates/guestos_vm_template.xml index 505c93eddd48..e214573f5af2 100644 --- a/rs/ic_os/generate_guestos_vm_config/templates/guestos_vm_template.xml +++ b/rs/ic_os/generate_guestos_vm_config/templates/guestos_vm_template.xml @@ -10,7 +10,7 @@ {{vm_memory}} {{nr_of_vcpus}} - {% if cpu_domain == "qemu" %} + {%- if cpu_domain == "qemu" %} {% else %} @@ -18,7 +18,7 @@ - {% endif %} + {% endif -%} /machine diff --git a/rs/ic_os/metrics_tool/BUILD.bazel b/rs/ic_os/metrics_tool/BUILD.bazel index b182a7a8a935..f9ceeaf31117 100644 --- a/rs/ic_os/metrics_tool/BUILD.bazel +++ b/rs/ic_os/metrics_tool/BUILD.bazel @@ -26,7 +26,10 @@ rust_library( aliases = ALIASES, crate_name = "ic_metrics_tool", proc_macro_deps = MACRO_DEPENDENCIES, - visibility = ["//rs:system-tests-pkg"], + visibility = [ + "//rs:ic-os-pkg", + "//rs:system-tests-pkg", + ], deps = DEPENDENCIES, ) From 2959b12162b74531b9dfef66c54f0cc6baadbbfc Mon Sep 17 00:00:00 2001 From: David Frank Date: Thu, 22 May 2025 21:05:14 +0200 Subject: [PATCH 18/47] Revert service (for now) --- .../generate-guestos-config/generate-guestos-config.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ic-os/components/hostos-scripts/generate-guestos-config/generate-guestos-config.service b/ic-os/components/hostos-scripts/generate-guestos-config/generate-guestos-config.service index 5329ea2ba699..0f84a44bee8e 100644 --- a/ic-os/components/hostos-scripts/generate-guestos-config/generate-guestos-config.service +++ b/ic-os/components/hostos-scripts/generate-guestos-config/generate-guestos-config.service @@ -9,7 +9,7 @@ RequiresMountsFor=/var [Service] Type=oneshot RemainAfterExit=true -ExecStart=/opt/ic/bin/generate-guestos-config +ExecStart=/opt/ic/bin/generate-guestos-config.sh [Install] WantedBy=multi-user.target From 55adde89dee76fe247fe4d55ae834d07367166ea Mon Sep 17 00:00:00 2001 From: David Frank Date: Thu, 22 May 2025 21:11:47 +0200 Subject: [PATCH 19/47] Address comments --- rs/ic_os/config/src/guestos_config.rs | 13 ++++++------- rs/ic_os/deterministic_ips/src/lib.rs | 1 + 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/rs/ic_os/config/src/guestos_config.rs b/rs/ic_os/config/src/guestos_config.rs index 960c355f62d3..0f27206bb09e 100644 --- a/rs/ic_os/config/src/guestos_config.rs +++ b/rs/ic_os/config/src/guestos_config.rs @@ -7,18 +7,17 @@ use utils::to_cidr; /// Generate the GuestOS configuration based on the provided HostOS configuration. pub fn generate_guestos_config(hostos_config: &HostOSConfig) -> Result { let hostos_config = hostos_config.clone(); - let generated_mac = calculate_deterministic_mac( - &hostos_config.icos_settings.mgmt_mac, - hostos_config.icos_settings.deployment_environment, - IpVariant::V6, - NodeType::GuestOS, // TODO(NODE-1608): support UpgradeGuestOS - ); // TODO: We won't have to modify networking between the hostos and // guestos config after completing the networking revamp (NODE-1327) let mut guestos_network_settings = hostos_config.network_settings; - // Update the GuestOS networking if `guestos_ipv6_address` is provided match &guestos_network_settings.ipv6_config { Ipv6Config::Deterministic(deterministic_ipv6_config) => { + let generated_mac = calculate_deterministic_mac( + &hostos_config.icos_settings.mgmt_mac, + hostos_config.icos_settings.deployment_environment, + IpVariant::V6, + NodeType::GuestOS, // TODO(NODE-1608): support UpgradeGuestOS + ); let guestos_ipv6_address = generated_mac.calculate_slaac(&deterministic_ipv6_config.prefix)?; guestos_network_settings.ipv6_config = Ipv6Config::Fixed(FixedIpv6Config { diff --git a/rs/ic_os/deterministic_ips/src/lib.rs b/rs/ic_os/deterministic_ips/src/lib.rs index 9c98a9aa915c..7ecdfc137d96 100644 --- a/rs/ic_os/deterministic_ips/src/lib.rs +++ b/rs/ic_os/deterministic_ips/src/lib.rs @@ -69,6 +69,7 @@ pub enum IpVariant { pub fn calculate_deterministic_mac( mgmt_mac: &MacAddr6, deployment_environment: DeploymentEnvironment, + // TODO(NODE-1609): consider removing IpVariant as it's always set to V6 in prod. ip_version: IpVariant, node_type: NodeType, ) -> MacAddr6 { From a9455293d4431d56578333cc2658e900226f67bd Mon Sep 17 00:00:00 2001 From: David Frank Date: Thu, 22 May 2025 21:47:45 +0200 Subject: [PATCH 20/47] Update Cargo bazel lock --- Cargo.Bazel.json.lock | 253 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 251 insertions(+), 2 deletions(-) diff --git a/Cargo.Bazel.json.lock b/Cargo.Bazel.json.lock index fd2cb2819e44..c792d4d94ee8 100644 --- a/Cargo.Bazel.json.lock +++ b/Cargo.Bazel.json.lock @@ -1,5 +1,5 @@ { - "checksum": "c1dcaec986d4667b053f4cad9cda33d1814a3a31177109010d3c584f57712594", + "checksum": "7da3340eafbbee27d034b9c557cbd65e4caa0498876eac1033a3522259d591b3", "crates": { "abnf 0.12.0": { "name": "abnf", @@ -9859,6 +9859,70 @@ ], "license_file": "LICENSE-APACHE" }, + "bstr 0.2.17": { + "name": "bstr", + "version": "0.2.17", + "package_url": "https://github.com/BurntSushi/bstr", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/bstr/0.2.17/download", + "sha256": "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" + } + }, + "targets": [ + { + "Library": { + "crate_name": "bstr", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "bstr", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "lazy_static", + "regex-automata", + "unicode" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "lazy_static 1.5.0", + "target": "lazy_static" + }, + { + "id": "memchr 2.7.4", + "target": "memchr" + }, + { + "id": "regex-automata 0.1.10", + "target": "regex_automata" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "0.2.17" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "bstr 1.6.0": { "name": "bstr", "version": "1.6.0", @@ -20115,6 +20179,10 @@ "id": "getrandom 0.2.10", "target": "getrandom" }, + { + "id": "goldenfile 1.8.0", + "target": "goldenfile" + }, { "id": "group 0.13.0", "target": "group" @@ -27996,6 +28064,65 @@ ], "license_file": "LICENSE-APACHE" }, + "goldenfile 1.8.0": { + "name": "goldenfile", + "version": "1.8.0", + "package_url": "https://github.com/calder/rust-goldenfile", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/goldenfile/1.8.0/download", + "sha256": "cf39e208efa110ca273f7255aea02485103ffcb7e5dfa5e4196b05a02411618e" + } + }, + "targets": [ + { + "Library": { + "crate_name": "goldenfile", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "goldenfile", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "scopeguard 1.2.0", + "target": "scopeguard" + }, + { + "id": "similar-asserts 1.7.0", + "target": "similar_asserts" + }, + { + "id": "tempfile 3.12.0", + "target": "tempfile" + }, + { + "id": "yansi 1.0.1", + "target": "yansi" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "1.8.0" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + }, "governor 0.8.1": { "name": "governor", "version": "0.8.1", @@ -70520,9 +70647,25 @@ ], "crate_features": { "common": [ + "bstr", "default", "inline", - "text" + "text", + "unicode", + "unicode-segmentation" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "bstr 0.2.17", + "target": "bstr" + }, + { + "id": "unicode-segmentation 1.12.0", + "target": "unicode_segmentation" + } ], "selects": {} }, @@ -70535,6 +70678,64 @@ ], "license_file": "LICENSE" }, + "similar-asserts 1.7.0": { + "name": "similar-asserts", + "version": "1.7.0", + "package_url": "https://github.com/mitsuhiko/similar-asserts", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/similar-asserts/1.7.0/download", + "sha256": "b5b441962c817e33508847a22bd82f03a30cff43642dc2fae8b050566121eb9a" + } + }, + "targets": [ + { + "Library": { + "crate_name": "similar_asserts", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "similar_asserts", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "unicode" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "console 0.15.7", + "target": "console" + }, + { + "id": "similar 2.2.1", + "target": "similar" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "1.7.0" + }, + "license": "Apache-2.0", + "license_ids": [ + "Apache-2.0" + ], + "license_file": "LICENSE" + }, "simple_asn1 0.6.2": { "name": "simple_asn1", "version": "0.6.2", @@ -90161,6 +90362,53 @@ ], "license_file": "LICENSE-APACHE" }, + "yansi 1.0.1": { + "name": "yansi", + "version": "1.0.1", + "package_url": "https://github.com/SergioBenitez/yansi", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/yansi/1.0.1/download", + "sha256": "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + } + }, + "targets": [ + { + "Library": { + "crate_name": "yansi", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "yansi", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "default", + "std" + ], + "selects": {} + }, + "edition": "2021", + "version": "1.0.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "yasna 0.5.2": { "name": "yasna", "version": "0.5.2", @@ -91721,6 +91969,7 @@ "futures-util 0.3.31", "get_if_addrs 0.5.3", "getrandom 0.2.10", + "goldenfile 1.8.0", "group 0.13.0", "hashlink 0.8.3", "hex 0.4.3", From 4a31427624e71d23ad696f1638c34c9ff6de9bbb Mon Sep 17 00:00:00 2001 From: David Frank Date: Thu, 22 May 2025 22:27:32 +0200 Subject: [PATCH 21/47] Revert the removal of DEFAULT_HOSTOS_GUESTOS_CONFIG_OBJECT_PATH --- rs/ic_os/config/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rs/ic_os/config/src/lib.rs b/rs/ic_os/config/src/lib.rs index 1b0ea4c67de0..4280a5de0d9e 100644 --- a/rs/ic_os/config/src/lib.rs +++ b/rs/ic_os/config/src/lib.rs @@ -20,6 +20,7 @@ pub static DEFAULT_SETUPOS_HOSTOS_CONFIG_OBJECT_PATH: &str = "/var/ic/config/con pub static DEFAULT_HOSTOS_CONFIG_INI_FILE_PATH: &str = "/boot/config/config.ini"; pub static DEFAULT_HOSTOS_DEPLOYMENT_JSON_PATH: &str = "/boot/config/deployment.json"; pub static DEFAULT_HOSTOS_CONFIG_OBJECT_PATH: &str = "/boot/config/config.json"; +pub static DEFAULT_HOSTOS_GUESTOS_CONFIG_OBJECT_PATH: &str = "/boot/config/config-guestos.json"; pub static DEFAULT_GUESTOS_CONFIG_OBJECT_PATH: &str = "/boot/config/config.json"; pub fn serialize_and_write_config(path: &Path, config: &T) -> Result<()> { From 1445fb8154abfe26f31ce08806f81c4cbd134d89 Mon Sep 17 00:00:00 2001 From: David Frank Date: Thu, 22 May 2025 23:57:23 +0200 Subject: [PATCH 22/47] Move build_bootstrap_config_image into struct impl --- rs/ic_os/bootstrap_config/src/lib.rs | 550 +++++++++--------- .../launch-single-vm/src/main.rs | 6 +- rs/tests/driver/src/driver/bootstrap.rs | 5 +- 3 files changed, 283 insertions(+), 278 deletions(-) diff --git a/rs/ic_os/bootstrap_config/src/lib.rs b/rs/ic_os/bootstrap_config/src/lib.rs index ae0e9badc827..4d4c371c34be 100644 --- a/rs/ic_os/bootstrap_config/src/lib.rs +++ b/rs/ic_os/bootstrap_config/src/lib.rs @@ -110,293 +110,297 @@ pub struct BootstrapOptions { pub socks_proxy: Option, } -/// Build a bootstrap disk image with the given configuration options. -pub fn build_bootstrap_config_image(out_file: &Path, options: &BootstrapOptions) -> Result<()> { - let tmp_dir = tempfile::tempdir().context("Failed to create temporary directory")?; - - // Create bootstrap tar - let tar_path = tmp_dir.path().join("ic-bootstrap.tar"); - build_bootstrap_tar(&tar_path, options)?; - - let tar_size = fs::metadata(&tar_path) - .context("Failed to get tar file metadata")? - .len(); - - // Calculate the disk image size (2 * tar_size + 1MB) - let image_size = 2 * tar_size + 1_048_576; - - // Create an empty file of the calculated size - let file = File::create(out_file).context("Failed to create output file")?; - file.set_len(image_size) - .context("Failed to set output file size")?; - - // Format the disk image as FAT - // mkfs.vfat is usually in /usr/sbin which is not always in the PATH - let path_with_usr_sbin = format!("/usr/sbin:{}", env::var("PATH").unwrap_or_default()); - if !Command::new("mkfs.vfat") - .arg("-n") - .arg("CONFIG") - .env("PATH", path_with_usr_sbin) - .arg(out_file) - .status() - .context("Failed to execute mkfs.vfat command")? - .success() - { - bail!("Failed to format disk image"); - } - - // Copy the tar file to the disk image - if !Command::new("mcopy") - .arg("-i") - .arg(out_file) - .arg("-o") - .arg(&tar_path) - .arg("::") - .status() - .context("Failed to execute mcopy command")? - .success() - { - bail!("Failed to copy tar to disk image"); - } - - Ok(()) -} - -/// Build a bootstrap tar file with the given configuration. -fn build_bootstrap_tar(out_file: &Path, options: &BootstrapOptions) -> Result<()> { - // Create temporary directory for bootstrap files - let bootstrap_dir = tempfile::tempdir().context("Failed to create temporary directory")?; +impl BootstrapOptions { + /// Create a FAT-formatted disk image containing bootstrap configuration. + /// + /// Takes all the configuration options specified in BootstrapOptions and packages them into + /// a disk image that can be mounted by the GuestOS. The image contains a FAT filesystem with + /// a single file named 'ic-bootstrap.tar' that includes all configuration files. + pub fn build_bootstrap_config_image(&self, out_file: &Path) -> Result<()> { + let tmp_dir = tempfile::tempdir().context("Failed to create temporary directory")?; + + // Create bootstrap tar + let tar_path = tmp_dir.path().join("ic-bootstrap.tar"); + self.build_bootstrap_tar(&tar_path)?; + + let tar_size = fs::metadata(&tar_path) + .context("Failed to get tar file metadata")? + .len(); + + // Calculate the disk image size (2 * tar_size + 1MB) + let image_size = 2 * tar_size + 1_048_576; + + // Create an empty file of the calculated size + let file = File::create(out_file).context("Failed to create output file")?; + file.set_len(image_size) + .context("Failed to set output file size")?; + + // Format the disk image as FAT + // mkfs.vfat is usually in /usr/sbin which is not always in the PATH + let path_with_usr_sbin = format!("/usr/sbin:{}", env::var("PATH").unwrap_or_default()); + if !Command::new("mkfs.vfat") + .arg("-n") + .arg("CONFIG") + .env("PATH", path_with_usr_sbin) + .arg(out_file) + .status() + .context("Failed to execute mkfs.vfat command")? + .success() + { + bail!("Failed to format disk image"); + } - // Copy files to the temporary directory - if let Some(guestos_config) = &options.guestos_config { - fs::copy(guestos_config, bootstrap_dir.path().join("config.json")) - .context("Failed to copy guestos config")?; - } + // Copy the tar file to the disk image + if !Command::new("mcopy") + .arg("-i") + .arg(out_file) + .arg("-o") + .arg(&tar_path) + .arg("::") + .status() + .context("Failed to execute mcopy command")? + .success() + { + bail!("Failed to copy tar to disk image"); + } - if let Some(nns_public_key) = &options.nns_public_key { - fs::copy( - nns_public_key, - bootstrap_dir.path().join("nns_public_key.pem"), - ) - .context("Failed to copy NNS public key")?; + Ok(()) } - if let Some(node_operator_private_key) = &options.node_operator_private_key { - fs::copy( - node_operator_private_key, - bootstrap_dir.path().join("node_operator_private_key.pem"), - ) - .context("Failed to copy node operator private key")?; - } + /// Build a bootstrap tar file with this configuration. + fn build_bootstrap_tar(&self, out_file: &Path) -> Result<()> { + // Create temporary directory for bootstrap files + let bootstrap_dir = tempfile::tempdir().context("Failed to create temporary directory")?; - #[cfg(feature = "dev")] - if let Some(accounts_ssh_authorized_keys) = &options.accounts_ssh_authorized_keys { - let target_dir = bootstrap_dir.path().join("accounts_ssh_authorized_keys"); - copy_dir_recursively(accounts_ssh_authorized_keys, &target_dir) - .context("Failed to copy SSH authorized keys")?; - } + // Copy files to the temporary directory + if let Some(guestos_config) = &self.guestos_config { + fs::copy(guestos_config, bootstrap_dir.path().join("config.json")) + .context("Failed to copy guestos config")?; + } - if let Some(ic_crypto) = &options.ic_crypto { - copy_dir_recursively(ic_crypto, &bootstrap_dir.path().join("ic_crypto")) - .context("Failed to copy IC crypto directory")?; - } + if let Some(nns_public_key) = &self.nns_public_key { + fs::copy( + nns_public_key, + bootstrap_dir.path().join("nns_public_key.pem"), + ) + .context("Failed to copy NNS public key")?; + } - if let Some(ic_state) = &options.ic_state { - if ic_state.exists() { - copy_dir_recursively(ic_state, &bootstrap_dir.path().join("ic_state")) - .context("Failed to copy IC state directory")?; + if let Some(node_operator_private_key) = &self.node_operator_private_key { + fs::copy( + node_operator_private_key, + bootstrap_dir.path().join("node_operator_private_key.pem"), + ) + .context("Failed to copy node operator private key")?; } - } - if let Some(ic_registry_local_store) = &options.ic_registry_local_store { - copy_dir_recursively( - ic_registry_local_store, - &bootstrap_dir.path().join("ic_registry_local_store"), - ) - .context("Failed to copy registry local store")?; - } + #[cfg(feature = "dev")] + if let Some(accounts_ssh_authorized_keys) = &self.accounts_ssh_authorized_keys { + let target_dir = bootstrap_dir.path().join("accounts_ssh_authorized_keys"); + Self::copy_dir_recursively(accounts_ssh_authorized_keys, &target_dir) + .context("Failed to copy SSH authorized keys")?; + } - fs::write( - bootstrap_dir.path().join("network.conf"), - generate_network_conf(options)?, - ) - .context("Failed to write network.conf")?; + if let Some(ic_crypto) = &self.ic_crypto { + Self::copy_dir_recursively(ic_crypto, &bootstrap_dir.path().join("ic_crypto")) + .context("Failed to copy IC crypto directory")?; + } - if let Some(node_reward_type) = &options.node_reward_type { - fs::write( - bootstrap_dir.path().join("reward.conf"), - format!("node_reward_type={node_reward_type}\n"), - ) - .context("Failed to write reward.conf")?; - } + if let Some(ic_state) = &self.ic_state { + if ic_state.exists() { + Self::copy_dir_recursively(ic_state, &bootstrap_dir.path().join("ic_state")) + .context("Failed to copy IC state directory")?; + } + } - if !options.elasticsearch_hosts.is_empty() { - let space_separated_hosts = options.elasticsearch_hosts.join(" "); - let mut filebeat_config = File::create(bootstrap_dir.path().join("filebeat.conf")) - .context("Failed to create filebeat.conf")?; - - writeln!( - filebeat_config, - "elasticsearch_hosts={space_separated_hosts}" - )?; - if !&options.elasticsearch_tags.is_empty() { - let space_separated_tags = options.elasticsearch_tags.join(" "); - writeln!(filebeat_config, "elasticsearch_tags={space_separated_tags}")?; + if let Some(ic_registry_local_store) = &self.ic_registry_local_store { + Self::copy_dir_recursively( + ic_registry_local_store, + &bootstrap_dir.path().join("ic_registry_local_store"), + ) + .context("Failed to copy registry local store")?; } - } - if !options.nns_urls.is_empty() { - let comma_separated_urls = options.nns_urls.join(","); fs::write( - bootstrap_dir.path().join("nns.conf"), - format!("nns_url={comma_separated_urls}\n"), + bootstrap_dir.path().join("network.conf"), + self.generate_network_conf()?, ) - .context("Failed to write nns.conf")?; - } + .context("Failed to write network.conf")?; + + if let Some(node_reward_type) = &self.node_reward_type { + fs::write( + bootstrap_dir.path().join("reward.conf"), + format!("node_reward_type={node_reward_type}\n"), + ) + .context("Failed to write reward.conf")?; + } + + if !self.elasticsearch_hosts.is_empty() { + let space_separated_hosts = self.elasticsearch_hosts.join(" "); + let mut filebeat_config = File::create(bootstrap_dir.path().join("filebeat.conf")) + .context("Failed to create filebeat.conf")?; + + writeln!( + filebeat_config, + "elasticsearch_hosts={space_separated_hosts}" + )?; + if !&self.elasticsearch_tags.is_empty() { + let space_separated_tags = self.elasticsearch_tags.join(" "); + writeln!(filebeat_config, "elasticsearch_tags={space_separated_tags}")?; + } + } - if let Some(backup_retention_time) = options.backup_retention_time_sec { - let mut backup_conf = File::create(bootstrap_dir.path().join("backup.conf")) - .context("Failed to create backup.conf")?; + if !self.nns_urls.is_empty() { + let comma_separated_urls = self.nns_urls.join(","); + fs::write( + bootstrap_dir.path().join("nns.conf"), + format!("nns_url={comma_separated_urls}\n"), + ) + .context("Failed to write nns.conf")?; + } - writeln!( - backup_conf, - "backup_retention_time_secs={backup_retention_time}" - )?; + if let Some(backup_retention_time) = self.backup_retention_time_sec { + let mut backup_conf = File::create(bootstrap_dir.path().join("backup.conf")) + .context("Failed to create backup.conf")?; - if let Some(backup_purging_interval) = options.backup_purging_interval_sec { writeln!( backup_conf, - "backup_puging_interval_secs={backup_purging_interval}" + "backup_retention_time_secs={backup_retention_time}" )?; + + if let Some(backup_purging_interval) = self.backup_purging_interval_sec { + writeln!( + backup_conf, + "backup_puging_interval_secs={backup_purging_interval}" + )?; + } } - } - #[cfg(feature = "dev")] - if let Some(malicious_behavior) = &options.malicious_behavior { - fs::write( - bootstrap_dir.path().join("malicious_behavior.conf"), - format!( - "malicious_behavior={}\n", - serde_json::to_string(malicious_behavior)? - ), - ) - .context("Failed to write malicious_behavior.conf")?; - } + #[cfg(feature = "dev")] + if let Some(malicious_behavior) = &self.malicious_behavior { + fs::write( + bootstrap_dir.path().join("malicious_behavior.conf"), + format!( + "malicious_behavior={}\n", + serde_json::to_string(malicious_behavior)? + ), + ) + .context("Failed to write malicious_behavior.conf")?; + } - #[cfg(feature = "dev")] - if let Some(query_stats_epoch_length) = options.query_stats_epoch_length { - fs::write( - bootstrap_dir.path().join("query_stats.conf"), - format!("query_stats_epoch_length={query_stats_epoch_length}\n"), - ) - .context("Failed to write query_stats.conf")?; - } + #[cfg(feature = "dev")] + if let Some(query_stats_epoch_length) = self.query_stats_epoch_length { + fs::write( + bootstrap_dir.path().join("query_stats.conf"), + format!("query_stats_epoch_length={query_stats_epoch_length}\n"), + ) + .context("Failed to write query_stats.conf")?; + } - #[cfg(feature = "dev")] - if let Some(bitcoind_addr) = &options.bitcoind_addr { - fs::write( - bootstrap_dir.path().join("bitcoind_addr.conf"), - format!("bitcoind_addr={bitcoind_addr}\n"), - ) - .context("Failed to write bitcoind_addr.conf")?; - } + #[cfg(feature = "dev")] + if let Some(bitcoind_addr) = &self.bitcoind_addr { + fs::write( + bootstrap_dir.path().join("bitcoind_addr.conf"), + format!("bitcoind_addr={bitcoind_addr}\n"), + ) + .context("Failed to write bitcoind_addr.conf")?; + } - #[cfg(feature = "dev")] - if let Some(jaeger_addr) = &options.jaeger_addr { - fs::write( - bootstrap_dir.path().join("jaeger_addr.conf"), - format!("jaeger_addr=http://{jaeger_addr}\n"), - ) - .context("Failed to write jaeger_addr.conf")?; - } + #[cfg(feature = "dev")] + if let Some(jaeger_addr) = &self.jaeger_addr { + fs::write( + bootstrap_dir.path().join("jaeger_addr.conf"), + format!("jaeger_addr=http://{jaeger_addr}\n"), + ) + .context("Failed to write jaeger_addr.conf")?; + } - #[cfg(feature = "dev")] - if let Some(socks_proxy) = &options.socks_proxy { - fs::write( - bootstrap_dir.path().join("socks_proxy.conf"), - format!("socks_proxy={socks_proxy}\n"), - ) - .context("Failed to write socks_proxy.conf")?; - } + #[cfg(feature = "dev")] + if let Some(socks_proxy) = &self.socks_proxy { + fs::write( + bootstrap_dir.path().join("socks_proxy.conf"), + format!("socks_proxy={socks_proxy}\n"), + ) + .context("Failed to write socks_proxy.conf")?; + } + + if !Command::new("tar") + .arg("cf") + .arg(out_file) + .arg("--sort=name") + .arg("--owner=root:0") + .arg("--group=root:0") + .arg("--mtime=UTC 1970-01-01 00:00:00") + .arg("-C") + .arg(bootstrap_dir.path()) + .arg(".") + .status() + .context("Failed to execute tar command")? + .success() + { + bail!("Failed to create tar file"); + } - if !Command::new("tar") - .arg("cf") - .arg(out_file) - .arg("--sort=name") - .arg("--owner=root:0") - .arg("--group=root:0") - .arg("--mtime=UTC 1970-01-01 00:00:00") - .arg("-C") - .arg(bootstrap_dir.path()) - .arg(".") - .status() - .context("Failed to execute tar command")? - .success() - { - bail!("Failed to create tar file"); + Ok(()) } - Ok(()) -} + /// Generate network configuration content. + fn generate_network_conf(&self) -> Result { + let mut network_conf = String::new(); -/// Generate network configuration content from config. -/// -/// The config must be valid. -fn generate_network_conf(config: &BootstrapOptions) -> Result { - let mut network_conf = String::new(); + if let Some(ipv6_address) = &self.ipv6_address { + writeln!(network_conf, "ipv6_address={ipv6_address}")?; + } - if let Some(ipv6_address) = &config.ipv6_address { - writeln!(network_conf, "ipv6_address={ipv6_address}")?; - } + if let Some(ipv6_gateway) = &self.ipv6_gateway { + writeln!(network_conf, "ipv6_gateway={ipv6_gateway}")?; + } - if let Some(ipv6_gateway) = &config.ipv6_gateway { - writeln!(network_conf, "ipv6_gateway={ipv6_gateway}")?; - } + let hostname = self.hostname.as_deref().unwrap_or_default(); + Self::validate_hostname(hostname)?; + writeln!(network_conf, "hostname={hostname}",)?; - let hostname = config.hostname.as_deref().unwrap_or_default(); - validate_hostname(hostname)?; - writeln!(network_conf, "hostname={hostname}",)?; + if let Some(ipv4_address) = &self.ipv4_address { + writeln!(network_conf, "ipv4_address={ipv4_address}")?; + } - if let Some(ipv4_address) = &config.ipv4_address { - writeln!(network_conf, "ipv4_address={ipv4_address}")?; - } + if let Some(ipv4_gateway) = &self.ipv4_gateway { + writeln!(network_conf, "ipv4_gateway={ipv4_gateway}")?; + } - if let Some(ipv4_gateway) = &config.ipv4_gateway { - writeln!(network_conf, "ipv4_gateway={ipv4_gateway}")?; - } + if let Some(domain) = &self.domain { + writeln!(network_conf, "domain={domain}")?; + } - if let Some(domain) = &config.domain { - writeln!(network_conf, "domain={domain}")?; + Ok(network_conf) } - Ok(network_conf) -} - -fn validate_hostname(hostname: &str) -> Result<()> { - let pattern = Regex::new(r"^[a-zA-Z][a-zA-Z0-9]*(-[a-zA-Z0-9]+)*$").unwrap(); - if hostname.is_empty() || pattern.is_match(hostname) { - Ok(()) - } else { - bail!("Invalid hostname: '{hostname}'"); + fn validate_hostname(hostname: &str) -> Result<()> { + let pattern = Regex::new(r"^[a-zA-Z][a-zA-Z0-9]*(-[a-zA-Z0-9]+)*$").unwrap(); + if hostname.is_empty() || pattern.is_match(hostname) { + Ok(()) + } else { + bail!("Invalid hostname: '{hostname}'"); + } } -} -fn copy_dir_recursively(src: &Path, dst: &Path) -> Result<()> { - if !Command::new("cp") - .arg("-r") - .arg(src) - .arg(dst) - .status() - .context(format!( - "Failed to copy {} to {}", - src.display(), - dst.display() - ))? - .success() - { - bail!("Failed to copy {} to {}", src.display(), dst.display()); + fn copy_dir_recursively(src: &Path, dst: &Path) -> Result<()> { + if !Command::new("cp") + .arg("-r") + .arg(src) + .arg(dst) + .status() + .context(format!( + "Failed to copy {} to {}", + src.display(), + dst.display() + ))? + .success() + { + bail!("Failed to copy {} to {}", src.display(), dst.display()); + } + Ok(()) } - Ok(()) } #[cfg(test)] @@ -406,18 +410,18 @@ mod tests { #[test] fn test_is_valid_hostname() { // Valid hostnames - assert!(validate_hostname("").is_ok()); - assert!(validate_hostname("hostname").is_ok()); - assert!(validate_hostname("hostname123").is_ok()); - assert!(validate_hostname("hostname-part2").is_ok()); - assert!(validate_hostname("h-1-2-3").is_ok()); + assert!(BootstrapOptions::validate_hostname("").is_ok()); + assert!(BootstrapOptions::validate_hostname("hostname").is_ok()); + assert!(BootstrapOptions::validate_hostname("hostname123").is_ok()); + assert!(BootstrapOptions::validate_hostname("hostname-part2").is_ok()); + assert!(BootstrapOptions::validate_hostname("h-1-2-3").is_ok()); // Invalid hostnames - assert!(validate_hostname("123hostname").is_err()); - assert!(validate_hostname("hostname-").is_err()); - assert!(validate_hostname("-hostname").is_err()); - assert!(validate_hostname("hostname_invalid").is_err()); - assert!(validate_hostname("hostname with spaces").is_err()); + assert!(BootstrapOptions::validate_hostname("123hostname").is_err()); + assert!(BootstrapOptions::validate_hostname("hostname-").is_err()); + assert!(BootstrapOptions::validate_hostname("-hostname").is_err()); + assert!(BootstrapOptions::validate_hostname("hostname_invalid").is_err()); + assert!(BootstrapOptions::validate_hostname("hostname with spaces").is_err()); } #[test] @@ -425,7 +429,9 @@ mod tests { let tmp_dir = tempfile::tempdir().unwrap(); let out_file = tmp_dir.path().join("bootstrap.tar"); - assert!(build_bootstrap_config_image(&out_file, &BootstrapOptions::default()).is_ok()); + assert!(BootstrapOptions::default() + .build_bootstrap_config_image(&out_file) + .is_ok()); } #[test] @@ -433,14 +439,12 @@ mod tests { let tmp_dir = tempfile::tempdir()?; let out_file = tmp_dir.path().join("bootstrap.img"); - build_bootstrap_config_image( - &out_file, - &BootstrapOptions { - hostname: Some("testhostname".to_string()), - ipv6_address: Some("2001:db8::1/64".to_string()), - ..Default::default() - }, - )?; + BootstrapOptions { + hostname: Some("testhostname".to_string()), + ipv6_address: Some("2001:db8::1/64".to_string()), + ..Default::default() + } + .build_bootstrap_config_image(&out_file)?; assert!(out_file.exists()); assert!(fs::metadata(&out_file)?.len() > 0); @@ -459,15 +463,13 @@ mod tests { fs::write(&test_config_path, r#"{"test": "value"}"#)?; // Build the tar file - build_bootstrap_tar( - &out_file, - &BootstrapOptions { - hostname: Some("testhostname".to_string()), - ipv6_address: Some("2001:db8::1/64".to_string()), - guestos_config: Some(test_config_path), - ..BootstrapOptions::default() - }, - )?; + BootstrapOptions { + hostname: Some("testhostname".to_string()), + ipv6_address: Some("2001:db8::1/64".to_string()), + guestos_config: Some(test_config_path), + ..BootstrapOptions::default() + } + .build_bootstrap_tar(&out_file)?; // Extract the tar file to verify contents let extract_dir = tmp_dir.path().join("extract"); @@ -531,7 +533,7 @@ mod tests { fs::write(registry_dir.join("test"), "registry_data")?; // Create full configuration - let config = BootstrapOptions { + let bootstrap_options = BootstrapOptions { hostname: Some("fulltest".to_string()), guestos_config: Some(config_path), nns_public_key: Some(nns_key_path), @@ -559,7 +561,7 @@ mod tests { }; // Build and extract tar - build_bootstrap_tar(&out_file, &config)?; + bootstrap_options.build_bootstrap_tar(&out_file)?; let extract_dir = tmp_dir.path().join("extract"); fs::create_dir(&extract_dir)?; Command::new("tar") diff --git a/rs/ic_os/dev_test_tools/launch-single-vm/src/main.rs b/rs/ic_os/dev_test_tools/launch-single-vm/src/main.rs index 63fb40cf34e7..8e03911918fe 100644 --- a/rs/ic_os/dev_test_tools/launch-single-vm/src/main.rs +++ b/rs/ic_os/dev_test_tools/launch-single-vm/src/main.rs @@ -1,4 +1,4 @@ -use bootstrap_config::{build_bootstrap_config_image, BootstrapOptions}; +use bootstrap_config::BootstrapOptions; use clap::Parser; use config::generate_testnet_config::{ generate_testnet_config, GenerateTestnetConfigArgs, Ipv6ConfigType, @@ -242,7 +242,9 @@ fn main() { accounts_ssh_authorized_keys: Some(keys_dir), ..Default::default() }; - build_bootstrap_config_image(&config_path, &bootstrap_options).unwrap(); + bootstrap_options + .build_bootstrap_config_image(&config_path) + .unwrap(); // Upload config image let image_id = farm diff --git a/rs/tests/driver/src/driver/bootstrap.rs b/rs/tests/driver/src/driver/bootstrap.rs index fd3fec518515..8abe3dccad74 100644 --- a/rs/tests/driver/src/driver/bootstrap.rs +++ b/rs/tests/driver/src/driver/bootstrap.rs @@ -25,7 +25,7 @@ use crate::{ k8s::job::wait_for_job_completion, }; use anyhow::{bail, Context, Result}; -use bootstrap_config::{build_bootstrap_config_image, BootstrapOptions}; +use bootstrap_config::BootstrapOptions; use config::generate_testnet_config::{ generate_testnet_config, GenerateTestnetConfigArgs, Ipv6ConfigType, }; @@ -574,7 +574,8 @@ fn create_config_disk_image( bootstrap_options.ipv4_gateway = Some(ipv4_config.gateway_ip_addr().to_string()); } - build_bootstrap_config_image(&img_path, &bootstrap_options) + bootstrap_options + .build_bootstrap_config_image(&img_path) .context("Could not create bootstrap config image")?; let mut img_file = File::open(img_path)?; From c899bd5f90418b75f047656b644197fc2122f5df Mon Sep 17 00:00:00 2001 From: David Frank Date: Fri, 23 May 2025 00:03:41 +0200 Subject: [PATCH 23/47] refactor: Use Paths in metrics_tool instead of Strings --- rs/ic_os/metrics_tool/src/lib.rs | 17 +++++++---------- rs/ic_os/metrics_tool/src/main.rs | 9 ++++----- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/rs/ic_os/metrics_tool/src/lib.rs b/rs/ic_os/metrics_tool/src/lib.rs index 02bb129c55dd..1eced76ade6b 100644 --- a/rs/ic_os/metrics_tool/src/lib.rs +++ b/rs/ic_os/metrics_tool/src/lib.rs @@ -1,7 +1,7 @@ // TODO: refactor/merge this with fstrim_tool and guestos_tool metrics functionality use std::fs::File; use std::io::{self, Write}; -use std::path::Path; +use std::path::PathBuf; // TODO: everything is floating point for now pub struct Metric { @@ -62,19 +62,16 @@ impl Metric { } pub struct MetricsWriter { - file_path: String, + file_path: PathBuf, } impl MetricsWriter { - pub fn new(file_path: &str) -> Self { - Self { - file_path: file_path.to_string(), - } + pub fn new(path: PathBuf) -> Self { + Self { file_path: path } } pub fn write_metrics(&self, metrics: &[Metric]) -> io::Result<()> { - let path = Path::new(&self.file_path); - let mut file = File::create(path)?; + let mut file = File::create(&self.file_path)?; for metric in metrics { writeln!(file, "{}", metric.to_prom_string())?; } @@ -104,7 +101,7 @@ mod tests { Metric::new("metric1", 1.0), Metric::new("metric2", 2.0).add_label("label", "value"), ]; - let writer = MetricsWriter::new("/tmp/test_metrics.prom"); + let writer = MetricsWriter::new("/tmp/test_metrics.prom".into()); writer.write_metrics(&metrics).unwrap(); let content = std::fs::read_to_string("/tmp/test_metrics.prom").unwrap(); assert!(content.contains( @@ -155,7 +152,7 @@ mod tests { #[test] fn test_write_empty_metrics() { let metrics: Vec = Vec::new(); - let writer = MetricsWriter::new("/tmp/test_empty_metrics.prom"); + let writer = MetricsWriter::new("/tmp/test_empty_metrics.prom".into()); writer.write_metrics(&metrics).unwrap(); let content = std::fs::read_to_string("/tmp/test_empty_metrics.prom").unwrap(); assert!(content.is_empty()); diff --git a/rs/ic_os/metrics_tool/src/main.rs b/rs/ic_os/metrics_tool/src/main.rs index cfd4a295c3fd..dfb7da934142 100644 --- a/rs/ic_os/metrics_tool/src/main.rs +++ b/rs/ic_os/metrics_tool/src/main.rs @@ -3,7 +3,7 @@ use clap::Parser; use std::fs::File; use std::io::{self, BufRead}; -use std::path::Path; +use std::path::{Path, PathBuf}; use ic_metrics_tool::{Metric, MetricsWriter}; @@ -22,7 +22,7 @@ struct MetricToolArgs { )] /// Filename to write the prometheus metrics for node_exporter generation. /// Fails badly if the directory doesn't exist. - metrics_filename: String, + metrics_filename: PathBuf, } fn get_sum_tlb_shootdowns() -> Result { @@ -48,15 +48,14 @@ fn get_sum_tlb_shootdowns() -> Result { pub fn main() -> Result<()> { let opts = MetricToolArgs::parse(); - let mpath = Path::new(&opts.metrics_filename); let tlb_shootdowns = get_sum_tlb_shootdowns()?; let metrics = vec![ Metric::new(TLB_SHOOTDOWN_METRIC_NAME, tlb_shootdowns as f64) .add_annotation(TLB_SHOOTDOWN_METRIC_ANNOTATION), ]; - let writer = MetricsWriter::new(mpath.to_str().unwrap()); - writer.write_metrics(&metrics).unwrap(); + let writer = MetricsWriter::new(opts.metrics_filename); + writer.write_metrics(&metrics)?; Ok(()) } From fa6047a64ebfac308160d79b54eb1be497515fec Mon Sep 17 00:00:00 2001 From: David Frank Date: Fri, 23 May 2025 00:10:25 +0200 Subject: [PATCH 24/47] naming --- rs/ic_os/metrics_tool/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rs/ic_os/metrics_tool/src/lib.rs b/rs/ic_os/metrics_tool/src/lib.rs index 1eced76ade6b..0244351e00e3 100644 --- a/rs/ic_os/metrics_tool/src/lib.rs +++ b/rs/ic_os/metrics_tool/src/lib.rs @@ -66,8 +66,8 @@ pub struct MetricsWriter { } impl MetricsWriter { - pub fn new(path: PathBuf) -> Self { - Self { file_path: path } + pub fn new(file_path: PathBuf) -> Self { + Self { file_path } } pub fn write_metrics(&self, metrics: &[Metric]) -> io::Result<()> { From ccb64da8807abdce74fd651cad4ffaaf6398c837 Mon Sep 17 00:00:00 2001 From: David Frank Date: Fri, 23 May 2025 12:01:43 +0200 Subject: [PATCH 25/47] Move code to config crate and add a dev feature to config crate --- Cargo.lock | 13 ------- rs/ic_os/bootstrap_config/BUILD.bazel | 37 ------------------- rs/ic_os/bootstrap_config/Cargo.toml | 16 -------- rs/ic_os/config/BUILD.bazel | 31 ++++++++++++---- rs/ic_os/config/Cargo.toml | 4 ++ .../src/guestos_bootstrap_image.rs} | 2 +- rs/ic_os/config/src/lib.rs | 1 + .../launch-single-vm/BUILD.bazel | 3 +- .../launch-single-vm/Cargo.toml | 3 +- .../launch-single-vm/src/main.rs | 2 +- rs/tests/driver/BUILD.bazel | 3 +- rs/tests/driver/Cargo.toml | 3 +- rs/tests/driver/src/driver/bootstrap.rs | 2 +- 13 files changed, 36 insertions(+), 84 deletions(-) delete mode 100644 rs/ic_os/bootstrap_config/BUILD.bazel delete mode 100644 rs/ic_os/bootstrap_config/Cargo.toml rename rs/ic_os/{bootstrap_config/src/lib.rs => config/src/guestos_bootstrap_image.rs} (99%) diff --git a/Cargo.lock b/Cargo.lock index c835ff8ce0b2..523874349b06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1526,17 +1526,6 @@ dependencies = [ "piper", ] -[[package]] -name = "bootstrap_config" -version = "0.0.0" -dependencies = [ - "anyhow", - "ic-types", - "regex", - "serde_json", - "tempfile", -] - [[package]] name = "borsh" version = "1.5.5" @@ -13754,7 +13743,6 @@ dependencies = [ "backon", "base64 0.13.1", "bincode", - "bootstrap_config", "candid", "canister-test", "chrono", @@ -16119,7 +16107,6 @@ checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" name = "launch-single-vm" version = "0.1.0" dependencies = [ - "bootstrap_config", "clap 4.5.27", "config", "config_types", diff --git a/rs/ic_os/bootstrap_config/BUILD.bazel b/rs/ic_os/bootstrap_config/BUILD.bazel deleted file mode 100644 index a625b6822820..000000000000 --- a/rs/ic_os/bootstrap_config/BUILD.bazel +++ /dev/null @@ -1,37 +0,0 @@ -load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") - -package(default_visibility = ["//visibility:public"]) - -DEPENDENCIES = [ - # Keep sorted. - "//rs/types/types", - "@crate_index//:anyhow", - "@crate_index//:regex", - "@crate_index//:serde_json", - "@crate_index//:tempfile", -] - -DEV_DEPENDENCIES = [] - -rust_library( - name = "bootstrap_config", - srcs = glob(["src/**/*.rs"]), - crate_name = "bootstrap_config", - deps = DEPENDENCIES, -) - -# Dev version for testing that should only be enabled in dev builds (unsafe for prod). -rust_library( - name = "bootstrap_config_dev", - srcs = glob(["src/**/*.rs"]), - crate_features = ["dev"], - crate_name = "bootstrap_config", - deps = DEPENDENCIES, -) - -rust_test( - name = "bootstrap_config_test", - crate = ":bootstrap_config_dev", - crate_features = ["dev"], - deps = DEV_DEPENDENCIES, -) diff --git a/rs/ic_os/bootstrap_config/Cargo.toml b/rs/ic_os/bootstrap_config/Cargo.toml deleted file mode 100644 index a365bdf32036..000000000000 --- a/rs/ic_os/bootstrap_config/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "bootstrap_config" -edition = "2021" - -[dependencies] -anyhow = { workspace = true } -ic-types = { path = "../../types/types" } -regex = { workspace = true } -serde_json = { workspace = true } -tempfile = { workspace = true } - -[features] -dev = [] # Features for testing that should only be enabled in dev builds (unsafe for prod) - -[profile.test] -default = ["dev"] diff --git a/rs/ic_os/config/BUILD.bazel b/rs/ic_os/config/BUILD.bazel index fd585ce65004..f62bf6b725b0 100644 --- a/rs/ic_os/config/BUILD.bazel +++ b/rs/ic_os/config/BUILD.bazel @@ -16,6 +16,7 @@ DEPENDENCIES = [ "@crate_index//:serde", "@crate_index//:serde_json", "@crate_index//:serde_with", + "@crate_index//:tempfile", "@crate_index//:url", ] @@ -43,6 +44,29 @@ rust_library( deps = DEPENDENCIES, ) +rust_library( + name = "config_lib_dev", + srcs = glob( + ["src/**/*.rs"], + exclude = ["src/main.rs"], + ), + crate_features = ["dev"], + crate_name = "config", + visibility = [ + "//rs:ic-os-pkg", + "//rs:system-tests-pkg", + ], + deps = DEPENDENCIES, +) + +rust_test( + name = "config_lib_test", + crate = ":config_lib_dev", + crate_features = ["dev"], + # You may add other deps that are specific to the test configuration + deps = DEV_DEPENDENCIES, +) + rust_binary( name = "config", srcs = ["src/main.rs"], @@ -53,10 +77,3 @@ rust_binary( ":config_lib", ] + DEPENDENCIES, ) - -rust_test( - name = "config_lib_test", - crate = ":config_lib", - # You may add other deps that are specific to the test configuration - deps = DEV_DEPENDENCIES, -) diff --git a/rs/ic_os/config/Cargo.toml b/rs/ic_os/config/Cargo.toml index c7c5a88bb178..1cc713b92968 100644 --- a/rs/ic_os/config/Cargo.toml +++ b/rs/ic_os/config/Cargo.toml @@ -17,6 +17,7 @@ regex = { workspace = true } config_types = { path = "../config_types" } network = { path = "../network" } # Only required by bin deterministic_ips = { path = "../deterministic_ips" } +tempfile = { workspace = true } [dev-dependencies] once_cell = "1.8" @@ -29,3 +30,6 @@ path = "src/lib.rs" [[bin]] name = "config" path = "src/main.rs" + +[features] +dev = [] diff --git a/rs/ic_os/bootstrap_config/src/lib.rs b/rs/ic_os/config/src/guestos_bootstrap_image.rs similarity index 99% rename from rs/ic_os/bootstrap_config/src/lib.rs rename to rs/ic_os/config/src/guestos_bootstrap_image.rs index 4d4c371c34be..878878544428 100644 --- a/rs/ic_os/bootstrap_config/src/lib.rs +++ b/rs/ic_os/config/src/guestos_bootstrap_image.rs @@ -10,7 +10,7 @@ use std::process::Command; #[cfg(feature = "dev")] use ic_types::malicious_behaviour::MaliciousBehaviour; -/// Configuration options for bootstrap image/tar creation +/// Configuration options for GuestOS bootstrap image/tar creation. #[derive(Default, Debug, Clone)] pub struct BootstrapOptions { /// The serialized GuestOS config object. diff --git a/rs/ic_os/config/src/lib.rs b/rs/ic_os/config/src/lib.rs index 4280a5de0d9e..4193728a2275 100644 --- a/rs/ic_os/config/src/lib.rs +++ b/rs/ic_os/config/src/lib.rs @@ -1,6 +1,7 @@ pub mod config_ini; pub mod deployment_json; pub mod generate_testnet_config; +pub mod guestos_bootstrap_image; pub mod guestos_config; pub mod update_config; diff --git a/rs/ic_os/dev_test_tools/launch-single-vm/BUILD.bazel b/rs/ic_os/dev_test_tools/launch-single-vm/BUILD.bazel index 57ac0e85ed43..18ceff4071a4 100644 --- a/rs/ic_os/dev_test_tools/launch-single-vm/BUILD.bazel +++ b/rs/ic_os/dev_test_tools/launch-single-vm/BUILD.bazel @@ -4,8 +4,7 @@ package(default_visibility = ["//rs:ic-os-pkg"]) DEPENDENCIES = [ # Keep sorted. - "//rs/ic_os/bootstrap_config:bootstrap_config_dev", - "//rs/ic_os/config:config_lib", + "//rs/ic_os/config:config_lib_dev", "//rs/ic_os/config_types", "//rs/prep", "//rs/registry/subnet_type", diff --git a/rs/ic_os/dev_test_tools/launch-single-vm/Cargo.toml b/rs/ic_os/dev_test_tools/launch-single-vm/Cargo.toml index 24d3c24e4dab..39867e7f6305 100644 --- a/rs/ic_os/dev_test_tools/launch-single-vm/Cargo.toml +++ b/rs/ic_os/dev_test_tools/launch-single-vm/Cargo.toml @@ -10,9 +10,8 @@ ic-prep = { path = "../../../prep" } ic-registry-subnet-type = { path = "../../../registry/subnet_type" } ic-system-test-driver = { path = "../../../tests/driver" } ic-types = { path = "../../../types/types" } -config = { path = "../../config" } +config = { path = "../../config", features = ["dev"] } config_types = { path = "../../config_types" } -bootstrap_config = { path = "../../bootstrap_config", features = ["dev"] } clap = { workspace = true } reqwest = { workspace = true } diff --git a/rs/ic_os/dev_test_tools/launch-single-vm/src/main.rs b/rs/ic_os/dev_test_tools/launch-single-vm/src/main.rs index 8e03911918fe..599acb201e5d 100644 --- a/rs/ic_os/dev_test_tools/launch-single-vm/src/main.rs +++ b/rs/ic_os/dev_test_tools/launch-single-vm/src/main.rs @@ -1,8 +1,8 @@ -use bootstrap_config::BootstrapOptions; use clap::Parser; use config::generate_testnet_config::{ generate_testnet_config, GenerateTestnetConfigArgs, Ipv6ConfigType, }; +use config::guestos_bootstrap_image::BootstrapOptions; use config_types::DeploymentEnvironment; use ic_prep_lib::{ internet_computer::{IcConfig, TopologyConfig}, diff --git a/rs/tests/driver/BUILD.bazel b/rs/tests/driver/BUILD.bazel index 9e3fe23da332..bab7349fd835 100644 --- a/rs/tests/driver/BUILD.bazel +++ b/rs/tests/driver/BUILD.bazel @@ -48,8 +48,7 @@ rust_library( "//rs/crypto/tree_hash", "//rs/crypto/utils/threshold_sig_der", "//rs/cycles_account_manager", - "//rs/ic_os/bootstrap_config:bootstrap_config_dev", - "//rs/ic_os/config:config_lib", + "//rs/ic_os/config:config_lib_dev", "//rs/ic_os/config_types", "//rs/ic_os/deterministic_ips", "//rs/interfaces", diff --git a/rs/tests/driver/Cargo.toml b/rs/tests/driver/Cargo.toml index f5da6edd02b0..b3bcaf82b875 100644 --- a/rs/tests/driver/Cargo.toml +++ b/rs/tests/driver/Cargo.toml @@ -13,12 +13,11 @@ async-trait = { workspace = true } backon = "0.4.1" base64 = { workspace = true } bincode = { workspace = true } -bootstrap_config = { path = "../../ic_os/bootstrap_config", features = ["dev"] } candid = { workspace = true } canister-test = { path = "../../rust_canisters/canister_test" } chrono = { workspace = true } clap = { workspace = true } -config = { path = "../../ic_os/config" } +config = { path = "../../ic_os/config", features = ["dev"] } config_types = { path = "../../ic_os/config_types" } crossbeam-channel = { workspace = true } cycles-minting-canister = { path = "../../nns/cmc" } diff --git a/rs/tests/driver/src/driver/bootstrap.rs b/rs/tests/driver/src/driver/bootstrap.rs index 8abe3dccad74..43faa1d243be 100644 --- a/rs/tests/driver/src/driver/bootstrap.rs +++ b/rs/tests/driver/src/driver/bootstrap.rs @@ -25,10 +25,10 @@ use crate::{ k8s::job::wait_for_job_completion, }; use anyhow::{bail, Context, Result}; -use bootstrap_config::BootstrapOptions; use config::generate_testnet_config::{ generate_testnet_config, GenerateTestnetConfigArgs, Ipv6ConfigType, }; +use config::guestos_bootstrap_image::BootstrapOptions; use config_types::DeploymentEnvironment; use ic_base_types::NodeId; use ic_prep_lib::{ From aacc25d57c55c37221688eceb1328aaf1b2e160d Mon Sep 17 00:00:00 2001 From: David Frank Date: Fri, 23 May 2025 12:51:03 +0200 Subject: [PATCH 26/47] Fixup --- rs/ic_os/generate_guestos_vm_config/BUILD.bazel | 5 ++--- rs/ic_os/generate_guestos_vm_config/Cargo.toml | 3 +-- rs/ic_os/generate_guestos_vm_config/src/main.rs | 12 ++++++------ 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/rs/ic_os/generate_guestos_vm_config/BUILD.bazel b/rs/ic_os/generate_guestos_vm_config/BUILD.bazel index a20d62d4b2db..fbcb946d965b 100644 --- a/rs/ic_os/generate_guestos_vm_config/BUILD.bazel +++ b/rs/ic_os/generate_guestos_vm_config/BUILD.bazel @@ -4,7 +4,6 @@ package(default_visibility = ["//visibility:public"]) DEPENDENCIES = [ # Keep sorted. - "//rs/ic_os/config:config_lib", "//rs/ic_os/config_types", "//rs/ic_os/deterministic_ips", "//rs/ic_os/metrics_tool", @@ -26,7 +25,7 @@ rust_binary( srcs = glob(["src/**/*.rs"]), compile_data = ["templates/guestos_vm_template.xml"], crate_name = "generate_guestos_vm_config", - deps = DEPENDENCIES + ["//rs/ic_os/bootstrap_config"], + deps = DEPENDENCIES + ["//rs/ic_os/config:config_lib"], ) # Dev version for testing that should only be enabled in dev builds (unsafe for prod). @@ -36,7 +35,7 @@ rust_binary( compile_data = ["templates/guestos_vm_template.xml"], crate_features = ["dev"], crate_name = "generate_guestos_vm_config", - deps = DEPENDENCIES + ["//rs/ic_os/bootstrap_config:bootstrap_config_dev"], + deps = DEPENDENCIES + ["//rs/ic_os/config:config_lib_dev"], ) rust_test( diff --git a/rs/ic_os/generate_guestos_vm_config/Cargo.toml b/rs/ic_os/generate_guestos_vm_config/Cargo.toml index 1aa8e1b776ed..66fb3d83d433 100644 --- a/rs/ic_os/generate_guestos_vm_config/Cargo.toml +++ b/rs/ic_os/generate_guestos_vm_config/Cargo.toml @@ -5,7 +5,6 @@ edition = "2021" [dependencies] anyhow = { workspace = true } askama = { workspace = true } -bootstrap_config = { path = "../bootstrap_config" } clap = { workspace = true } config = { path = "../config" } config_types = { path = "../config_types" } @@ -20,4 +19,4 @@ url = { workspace = true } goldenfile = { workspace = true } [features] -dev = ["bootstrap_config/dev"] +dev = ["config/dev"] diff --git a/rs/ic_os/generate_guestos_vm_config/src/main.rs b/rs/ic_os/generate_guestos_vm_config/src/main.rs index b26f1389246a..0009186ad18b 100644 --- a/rs/ic_os/generate_guestos_vm_config/src/main.rs +++ b/rs/ic_os/generate_guestos_vm_config/src/main.rs @@ -1,7 +1,7 @@ use anyhow::{bail, Context, Result}; use askama::Template; -use bootstrap_config::{build_bootstrap_config_image, BootstrapOptions}; use clap::Parser; +use config::guestos_bootstrap_image::BootstrapOptions; use config::guestos_config::generate_guestos_config; use config::{serialize_and_write_config, DEFAULT_HOSTOS_CONFIG_OBJECT_PATH}; use config_types::{GuestOSConfig, HostOSConfig, Ipv6Config}; @@ -34,9 +34,9 @@ struct Args { pub fn main() -> Result<()> { let args = Args::parse(); - let metrics_writer = MetricsWriter::new( + let metrics_writer = MetricsWriter::new(PathBuf::from( "/run/node_exporter/collector_textfile/hostos_generate_guestos_config.prom", - ); + )); let hostos_config: HostOSConfig = serde_json::from_reader( File::open(&args.config).context("Failed to open HostOS config file")?, @@ -124,7 +124,7 @@ fn assemble_config_media(hostos_config: &HostOSConfig, media_path: &Path) -> Res let bootstrap_options = make_bootstrap_options(hostos_config, guestos_config, guestos_config_file.path())?; - build_bootstrap_config_image(media_path, &bootstrap_options)?; + bootstrap_options.build_bootstrap_config_image(media_path)?; println!( "Assembling config media for GuestOS: {}", @@ -405,7 +405,7 @@ mod tests { config: PathBuf::from("/non/existent/path"), }; - let metrics_writer = MetricsWriter::new(metrics_path.to_str().unwrap()); + let metrics_writer = MetricsWriter::new(metrics_path.clone()); let config = create_test_hostos_config(); let result = run(&args, &metrics_writer, &config, mock_restorecon); @@ -435,7 +435,7 @@ mod tests { config: PathBuf::from("/path/to/config"), }; - let metrics_writer = MetricsWriter::new(metrics_path.to_str().unwrap()); + let metrics_writer = MetricsWriter::new(metrics_path.clone()); let config = create_test_hostos_config(); let result_err = run(&args, &metrics_writer, &config, mock_restorecon).unwrap_err(); From 7fc35dc4454d6a6a439ed711236f7f06667684cf Mon Sep 17 00:00:00 2001 From: IDX GitHub Automation Date: Fri, 23 May 2025 10:52:22 +0000 Subject: [PATCH 27/47] Automatically updated Cargo*.lock --- Cargo.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 221281ef9c8d..4bfbe535d427 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4915,7 +4915,6 @@ version = "0.0.0" dependencies = [ "anyhow", "askama", - "bootstrap_config", "clap 4.5.27", "config", "config_types", From ec48043221b4997384b004e7430c02012c08d412 Mon Sep 17 00:00:00 2001 From: David Frank Date: Fri, 23 May 2025 13:04:03 +0200 Subject: [PATCH 28/47] Small fixes --- Cargo.lock | 1 - .../generate_guestos_vm_config/src/main.rs | 62 +++---------------- 2 files changed, 9 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 221281ef9c8d..4bfbe535d427 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4915,7 +4915,6 @@ version = "0.0.0" dependencies = [ "anyhow", "askama", - "bootstrap_config", "clap 4.5.27", "config", "config_types", diff --git a/rs/ic_os/generate_guestos_vm_config/src/main.rs b/rs/ic_os/generate_guestos_vm_config/src/main.rs index 0009186ad18b..c052c6a0a1a7 100644 --- a/rs/ic_os/generate_guestos_vm_config/src/main.rs +++ b/rs/ic_os/generate_guestos_vm_config/src/main.rs @@ -99,17 +99,6 @@ fn run( Ok(()) } -/// Get the IPv6 gateway from the configuration -fn get_ipv6_gateway(config: &HostOSConfig) -> Result { - match &config.network_settings.ipv6_config { - Ipv6Config::Deterministic(config) => Ok(config.gateway.to_string()), - Ipv6Config::Fixed(config) => Ok(config.gateway.to_string()), - Ipv6Config::RouterAdvertisement => { - bail!("RouterAdvertisement IPv6 configuration does not have a gateway") - } - } -} - fn assemble_config_media(hostos_config: &HostOSConfig, media_path: &Path) -> Result<()> { let guestos_config_file = tempfile::NamedTempFile::new()?; let guestos_config = @@ -160,15 +149,15 @@ fn make_bootstrap_options( Some(PathBuf::from("/boot/config/node_operator_private_key.pem")); } - let guestos_address = match guestos_config.network_settings.ipv6_config { - Ipv6Config::Fixed(ip_config) => ip_config.address, + let guestos_ipv6_config = match guestos_config.network_settings.ipv6_config { + Ipv6Config::Fixed(ip_config) => ip_config, _ => bail!( "Expected GuestOS IPv6 address to be fixed but was {:?}", guestos_config.network_settings.ipv6_config ), }; - bootstrap_options.ipv6_address = Some(guestos_address); - bootstrap_options.ipv6_gateway = Some(get_ipv6_gateway(hostos_config)?); + bootstrap_options.ipv6_address = Some(guestos_ipv6_config.address); + bootstrap_options.ipv6_gateway = Some(guestos_ipv6_config.gateway.to_string()); if let Some(ipv4_config) = &hostos_config.network_settings.ipv4_config { bootstrap_options.ipv4_address = Some(format!( @@ -206,6 +195,7 @@ fn make_bootstrap_options( Ok(bootstrap_options) } +/// Generate the GuestOS VM libvirt XML configuration and return it as String. fn generate_vm_config(config: &HostOSConfig, media_path: &Path) -> Result { // If you get a compile error pointing at #[derive(Template)], there is likely a syntax error in // the template. @@ -257,8 +247,8 @@ fn restorecon(path: &Path) -> Result<()> { mod tests { use super::*; use config_types::{ - DeploymentEnvironment, DeterministicIpv6Config, FixedIpv6Config, HostOSConfig, - HostOSSettings, ICOSSettings, Ipv4Config, Ipv6Config, Logging, NetworkSettings, + DeploymentEnvironment, DeterministicIpv6Config, HostOSConfig, HostOSSettings, ICOSSettings, + Ipv4Config, Ipv6Config, Logging, NetworkSettings, }; use goldenfile::Mint; use std::env; @@ -282,7 +272,7 @@ mod tests { domain_name: Some("test.domain".to_string()), }, icos_settings: ICOSSettings { - node_reward_type: Some("test-reward".to_string()), + node_reward_type: Some("type3.1".to_string()), mgmt_mac: "00:11:22:33:44:55".parse().unwrap(), deployment_environment: DeploymentEnvironment::Testnet, logging: Logging { @@ -329,7 +319,7 @@ mod tests { ipv4_address: Some("192.168.1.2/24".to_string()), ipv4_gateway: Some("192.168.1.1".to_string()), domain: Some("test.domain".to_string()), - node_reward_type: Some("test-reward".to_string()), + node_reward_type: Some("type3.1".to_string()), hostname: Some("guest-001122334455".to_string()), nns_urls: vec!["https://example.com/".to_string()], guestos_config: Some(PathBuf::from("/tmp/test")), @@ -452,38 +442,4 @@ mod tests { hostos_generate_guestos_config 0\n" ) } - - #[test] - fn test_get_ipv6_gateway_fixed() { - let mut config = create_test_hostos_config(); - config.network_settings.ipv6_config = Ipv6Config::Fixed(FixedIpv6Config { - address: "2001:db9::1/64".to_string(), - gateway: "2001:db9::ffff".parse().unwrap(), - }); - let gateway = get_ipv6_gateway(&config).unwrap(); - assert_eq!(gateway, "2001:db9::ffff"); - } - - #[test] - fn test_get_ipv6_gateway_deterministic() { - let mut config = create_test_hostos_config(); - config.network_settings.ipv6_config = Ipv6Config::Deterministic(DeterministicIpv6Config { - prefix: "2001:db8::".to_string(), - prefix_length: 64, - gateway: "2001:db8::1".parse().unwrap(), - }); - let gateway = get_ipv6_gateway(&config).unwrap(); - assert_eq!(gateway, "2001:db8::1"); - } - - #[test] - fn test_get_ipv6_gateway_router_advertisement_error() { - let mut config = create_test_hostos_config(); - config.network_settings.ipv6_config = Ipv6Config::RouterAdvertisement; - let result = get_ipv6_gateway(&config).unwrap_err(); - assert!( - result.to_string().contains("does not have a gateway"), - "{result:?}" - ); - } } From 4b7551ed27ab8ba99f9027c85bdb83645f8320c9 Mon Sep 17 00:00:00 2001 From: David Frank Date: Fri, 23 May 2025 13:06:20 +0200 Subject: [PATCH 29/47] Remove comment --- rs/ic_os/generate_guestos_vm_config/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rs/ic_os/generate_guestos_vm_config/src/main.rs b/rs/ic_os/generate_guestos_vm_config/src/main.rs index c052c6a0a1a7..fd5bce1e6ea3 100644 --- a/rs/ic_os/generate_guestos_vm_config/src/main.rs +++ b/rs/ic_os/generate_guestos_vm_config/src/main.rs @@ -133,7 +133,6 @@ fn make_bootstrap_options( ..Default::default() }; - // Set SSH authorized keys (only in dev builds) #[cfg(feature = "dev")] if hostos_config.icos_settings.use_ssh_authorized_keys { bootstrap_options.accounts_ssh_authorized_keys = From 3a231df8a0cccc27ee7bcaa172accd27e2dbe8d0 Mon Sep 17 00:00:00 2001 From: David Frank Date: Fri, 23 May 2025 13:40:46 +0200 Subject: [PATCH 30/47] Small test improvements --- .../generate_guestos_vm_config/src/main.rs | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/rs/ic_os/generate_guestos_vm_config/src/main.rs b/rs/ic_os/generate_guestos_vm_config/src/main.rs index fd5bce1e6ea3..4f65c87b9b33 100644 --- a/rs/ic_os/generate_guestos_vm_config/src/main.rs +++ b/rs/ic_os/generate_guestos_vm_config/src/main.rs @@ -251,6 +251,7 @@ mod tests { }; use goldenfile::Mint; use std::env; + use std::os::unix::prelude::MetadataExt; use std::path::Path; use tempfile::tempdir; @@ -391,13 +392,16 @@ mod tests { let args = Args { media: media_path.clone(), output: output_path.clone(), + // Value does not matter since we pass HostOS config to run. config: PathBuf::from("/non/existent/path"), }; - let metrics_writer = MetricsWriter::new(metrics_path.clone()); - let config = create_test_hostos_config(); - - let result = run(&args, &metrics_writer, &config, mock_restorecon); + let result = run( + &args, + &MetricsWriter::new(metrics_path.clone()), + &create_test_hostos_config(), + mock_restorecon, + ); assert!(result.is_ok(), "{result:?}"); assert_eq!( @@ -405,7 +409,10 @@ mod tests { "# HELP hostos_generate_guestos_config HostOS generate GuestOS config\n\ # TYPE hostos_generate_guestos_config counter\n\ hostos_generate_guestos_config 1\n" - ) + ); + + assert!(media_path.metadata().unwrap().size() > 0); + assert!(output_path.metadata().unwrap().size() > 0); } #[test] @@ -421,13 +428,17 @@ mod tests { let args = Args { media: media_path, output: output_path, + // Value does not matter since we pass HostOS config to run. config: PathBuf::from("/path/to/config"), }; - let metrics_writer = MetricsWriter::new(metrics_path.clone()); - let config = create_test_hostos_config(); - - let result_err = run(&args, &metrics_writer, &config, mock_restorecon).unwrap_err(); + let result_err = run( + &args, + &MetricsWriter::new(metrics_path.clone()), + &create_test_hostos_config(), + mock_restorecon, + ) + .unwrap_err(); assert!( result_err.to_string().contains("already exists"), From e87571cdfb38d2e9ed880ddde33ba07e66f50e72 Mon Sep 17 00:00:00 2001 From: David Frank Date: Fri, 23 May 2025 14:02:48 +0200 Subject: [PATCH 31/47] repin --- Cargo.Bazel.Fuzzing.json.lock | 253 +++++++++++++++++++++++++++++++++- Cargo.Bazel.Fuzzing.toml.lock | 50 ++++++- 2 files changed, 298 insertions(+), 5 deletions(-) diff --git a/Cargo.Bazel.Fuzzing.json.lock b/Cargo.Bazel.Fuzzing.json.lock index f8f70f91de16..64df49fc34df 100644 --- a/Cargo.Bazel.Fuzzing.json.lock +++ b/Cargo.Bazel.Fuzzing.json.lock @@ -1,5 +1,5 @@ { - "checksum": "77cd6a21ea41b3c33353bca3a043417292bfdf4694e434194e3ab8d020f58c84", + "checksum": "be319980caeab6b4f333d5df5c89b9435421a0e38ae58fd22ed8e1b2951f87a1", "crates": { "abnf 0.12.0": { "name": "abnf", @@ -9950,6 +9950,70 @@ ], "license_file": "LICENSE-APACHE" }, + "bstr 0.2.17": { + "name": "bstr", + "version": "0.2.17", + "package_url": "https://github.com/BurntSushi/bstr", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/bstr/0.2.17/download", + "sha256": "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" + } + }, + "targets": [ + { + "Library": { + "crate_name": "bstr", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "bstr", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "lazy_static", + "regex-automata", + "unicode" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "lazy_static 1.5.0", + "target": "lazy_static" + }, + { + "id": "memchr 2.6.4", + "target": "memchr" + }, + { + "id": "regex-automata 0.1.10", + "target": "regex_automata" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "0.2.17" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "bstr 1.8.0": { "name": "bstr", "version": "1.8.0", @@ -20297,6 +20361,10 @@ "id": "getrandom 0.2.10", "target": "getrandom" }, + { + "id": "goldenfile 1.8.0", + "target": "goldenfile" + }, { "id": "group 0.13.0", "target": "group" @@ -28152,6 +28220,65 @@ ], "license_file": "LICENSE-APACHE" }, + "goldenfile 1.8.0": { + "name": "goldenfile", + "version": "1.8.0", + "package_url": "https://github.com/calder/rust-goldenfile", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/goldenfile/1.8.0/download", + "sha256": "cf39e208efa110ca273f7255aea02485103ffcb7e5dfa5e4196b05a02411618e" + } + }, + "targets": [ + { + "Library": { + "crate_name": "goldenfile", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "goldenfile", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "scopeguard 1.2.0", + "target": "scopeguard" + }, + { + "id": "similar-asserts 1.7.0", + "target": "similar_asserts" + }, + { + "id": "tempfile 3.12.0", + "target": "tempfile" + }, + { + "id": "yansi 1.0.1", + "target": "yansi" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "1.8.0" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + }, "governor 0.8.1": { "name": "governor", "version": "0.8.1", @@ -70650,9 +70777,25 @@ ], "crate_features": { "common": [ + "bstr", "default", "inline", - "text" + "text", + "unicode", + "unicode-segmentation" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "bstr 0.2.17", + "target": "bstr" + }, + { + "id": "unicode-segmentation 1.12.0", + "target": "unicode_segmentation" + } ], "selects": {} }, @@ -70665,6 +70808,64 @@ ], "license_file": "LICENSE" }, + "similar-asserts 1.7.0": { + "name": "similar-asserts", + "version": "1.7.0", + "package_url": "https://github.com/mitsuhiko/similar-asserts", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/similar-asserts/1.7.0/download", + "sha256": "b5b441962c817e33508847a22bd82f03a30cff43642dc2fae8b050566121eb9a" + } + }, + "targets": [ + { + "Library": { + "crate_name": "similar_asserts", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "similar_asserts", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "unicode" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "console 0.15.7", + "target": "console" + }, + { + "id": "similar 2.3.0", + "target": "similar" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "1.7.0" + }, + "license": "Apache-2.0", + "license_ids": [ + "Apache-2.0" + ], + "license_file": "LICENSE" + }, "simple_asn1 0.6.2": { "name": "simple_asn1", "version": "0.6.2", @@ -90320,6 +90521,53 @@ ], "license_file": "LICENSE-APACHE" }, + "yansi 1.0.1": { + "name": "yansi", + "version": "1.0.1", + "package_url": "https://github.com/SergioBenitez/yansi", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/yansi/1.0.1/download", + "sha256": "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + } + }, + "targets": [ + { + "Library": { + "crate_name": "yansi", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "yansi", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "default", + "std" + ], + "selects": {} + }, + "edition": "2021", + "version": "1.0.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, "yasna 0.5.2": { "name": "yasna", "version": "0.5.2", @@ -91756,6 +92004,7 @@ "futures-util 0.3.31", "get_if_addrs 0.5.3", "getrandom 0.2.10", + "goldenfile 1.8.0", "group 0.13.0", "hashlink 0.8.4", "hex 0.4.3", diff --git a/Cargo.Bazel.Fuzzing.toml.lock b/Cargo.Bazel.Fuzzing.toml.lock index 6df01ff1f635..1f8b9084302f 100644 --- a/Cargo.Bazel.Fuzzing.toml.lock +++ b/Cargo.Bazel.Fuzzing.toml.lock @@ -658,7 +658,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" dependencies = [ "anstyle", - "bstr", + "bstr 1.8.0", "doc-comment", "libc", "predicates", @@ -1694,6 +1694,17 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata 0.1.10", +] + [[package]] name = "bstr" version = "1.8.0" @@ -3462,6 +3473,7 @@ dependencies = [ "futures-util", "get_if_addrs", "getrandom 0.2.10", + "goldenfile", "group 0.13.0", "hashlink", "hex", @@ -3714,7 +3726,7 @@ dependencies = [ "wycheproof", "x509-cert", "x509-parser 0.16.0", - "yansi", + "yansi 0.5.1", "zeroize", "zstd", ] @@ -4817,6 +4829,18 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "goldenfile" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf39e208efa110ca273f7255aea02485103ffcb7e5dfa5e4196b05a02411618e" +dependencies = [ + "scopeguard", + "similar-asserts", + "tempfile", + "yansi 1.0.1", +] + [[package]] name = "governor" version = "0.8.1" @@ -9580,7 +9604,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" dependencies = [ "diff", - "yansi", + "yansi 0.5.1", ] [[package]] @@ -11896,6 +11920,20 @@ name = "similar" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2aeaf503862c419d66959f5d7ca015337d864e9c49485d771b732e2a20453597" +dependencies = [ + "bstr 0.2.17", + "unicode-segmentation", +] + +[[package]] +name = "similar-asserts" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b441962c817e33508847a22bd82f03a30cff43642dc2fae8b050566121eb9a" +dependencies = [ + "console 0.15.7", + "similar", +] [[package]] name = "simple_asn1" @@ -14986,6 +15024,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yasna" version = "0.5.2" From 9b7bb091b523e788c60585edce83323fac6f5562 Mon Sep 17 00:00:00 2001 From: David Frank Date: Fri, 23 May 2025 14:20:30 +0200 Subject: [PATCH 32/47] read hostos in run --- .../generate_guestos_vm_config/src/main.rs | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/rs/ic_os/generate_guestos_vm_config/src/main.rs b/rs/ic_os/generate_guestos_vm_config/src/main.rs index 4f65c87b9b33..ded7ec0a34aa 100644 --- a/rs/ic_os/generate_guestos_vm_config/src/main.rs +++ b/rs/ic_os/generate_guestos_vm_config/src/main.rs @@ -38,22 +38,22 @@ pub fn main() -> Result<()> { "/run/node_exporter/collector_textfile/hostos_generate_guestos_config.prom", )); - let hostos_config: HostOSConfig = serde_json::from_reader( - File::open(&args.config).context("Failed to open HostOS config file")?, - ) - .context("Failed to parse config file")?; - - run(&args, &metrics_writer, &hostos_config, restorecon) + run(&args, &metrics_writer, restorecon) } fn run( args: &Args, metrics_writer: &MetricsWriter, - hostos_config: &HostOSConfig, // We pass a functor to allow mocking in tests. restorecon: impl Fn(&Path) -> Result<()>, ) -> Result<()> { - assemble_config_media(hostos_config, &args.media).context("Failed to assemble config media")?; + let hostos_config: HostOSConfig = serde_json::from_reader( + File::open(&args.config).context("Failed to open HostOS config file")?, + ) + .context("Failed to parse HostOS config file")?; + + assemble_config_media(&hostos_config, &args.media) + .context("Failed to assemble config media")?; if args.output.exists() { metrics_writer.write_metrics(&[Metric::with_annotation( @@ -77,7 +77,7 @@ fn run( File::create(output_path) .context("Failed to create output file")? - .write_all(generate_vm_config(hostos_config, &args.media)?.as_bytes()) + .write_all(generate_vm_config(&hostos_config, &args.media)?.as_bytes()) .context("Failed to write output file")?; // Restore SELinux security context @@ -229,7 +229,7 @@ fn generate_vm_config(config: &HostOSConfig, media_path: &Path) -> Result Result<()> { @@ -385,21 +385,22 @@ mod tests { #[test] fn test_run_success() { let temp_dir = tempdir().unwrap(); + let hostos_config_path = temp_dir.path().join("hostos.json"); let media_path = temp_dir.path().join("config.img"); let output_path = temp_dir.path().join("guestos.xml"); let metrics_path = temp_dir.path().join("metrics.prom"); + serialize_and_write_config(&hostos_config_path, &create_test_hostos_config()).unwrap(); + let args = Args { media: media_path.clone(), output: output_path.clone(), - // Value does not matter since we pass HostOS config to run. - config: PathBuf::from("/non/existent/path"), + config: hostos_config_path.clone(), }; let result = run( &args, &MetricsWriter::new(metrics_path.clone()), - &create_test_hostos_config(), mock_restorecon, ); assert!(result.is_ok(), "{result:?}"); @@ -418,9 +419,12 @@ mod tests { #[test] fn test_run_existing_output_file() { let temp_dir = tempdir().unwrap(); - let metrics_path = temp_dir.path().join("metrics.prom"); + let hostos_config_path = temp_dir.path().join("hostos.json"); let media_path = temp_dir.path().join("config.img"); let output_path = temp_dir.path().join("guestos.xml"); + let metrics_path = temp_dir.path().join("metrics.prom"); + + serialize_and_write_config(&hostos_config_path, &create_test_hostos_config()).unwrap(); // Create the output file so it already exists fs::write(&output_path, "test").unwrap(); @@ -428,14 +432,12 @@ mod tests { let args = Args { media: media_path, output: output_path, - // Value does not matter since we pass HostOS config to run. - config: PathBuf::from("/path/to/config"), + config: hostos_config_path, }; let result_err = run( &args, &MetricsWriter::new(metrics_path.clone()), - &create_test_hostos_config(), mock_restorecon, ) .unwrap_err(); From 33172d4e9d48e4da28c5513c59a0bfdc3461cb1f Mon Sep 17 00:00:00 2001 From: David Frank Date: Fri, 23 May 2025 15:12:40 +0200 Subject: [PATCH 33/47] Move it to config crate --- Cargo.lock | 21 ++------ Cargo.toml | 1 - rs/ic_os/config/BUILD.bazel | 51 +++++++++++++++---- rs/ic_os/config/Cargo.toml | 4 ++ .../golden/guestos_vm_kvm.xml | 0 .../golden/guestos_vm_qemu.xml | 0 .../main.rs => config/src/guest_vm_config.rs} | 24 ++++----- rs/ic_os/config/src/main.rs | 16 ++++-- .../templates/guestos_vm_template.xml | 0 .../generate_guestos_vm_config/BUILD.bazel | 50 ------------------ .../generate_guestos_vm_config/Cargo.toml | 22 -------- 11 files changed, 72 insertions(+), 117 deletions(-) rename rs/ic_os/{generate_guestos_vm_config => config}/golden/guestos_vm_kvm.xml (100%) rename rs/ic_os/{generate_guestos_vm_config => config}/golden/guestos_vm_qemu.xml (100%) rename rs/ic_os/{generate_guestos_vm_config/src/main.rs => config/src/guest_vm_config.rs} (96%) rename rs/ic_os/{generate_guestos_vm_config => config}/templates/guestos_vm_template.xml (100%) delete mode 100644 rs/ic_os/generate_guestos_vm_config/BUILD.bazel delete mode 100644 rs/ic_os/generate_guestos_vm_config/Cargo.toml diff --git a/Cargo.lock b/Cargo.lock index 4bfbe535d427..8253f39177a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2552,9 +2552,12 @@ name = "config" version = "1.0.0" dependencies = [ "anyhow", + "askama", "clap 4.5.27", "config_types", "deterministic_ips", + "goldenfile", + "ic-metrics-tool", "ic-types", "macaddr", "network", @@ -4909,24 +4912,6 @@ version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" -[[package]] -name = "generate_guestos_vm_config" -version = "0.0.0" -dependencies = [ - "anyhow", - "askama", - "clap 4.5.27", - "config", - "config_types", - "deterministic_ips", - "goldenfile", - "ic-metrics-tool", - "macaddr", - "serde_json", - "tempfile", - "url", -] - [[package]] name = "generator" version = "0.8.4" diff --git a/Cargo.toml b/Cargo.toml index a23e4082afde..e8701e7e703a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -140,7 +140,6 @@ members = [ "rs/http_endpoints/xnet", "rs/http_utils", "rs/ic_os/deterministic_ips", - "rs/ic_os/generate_guestos_vm_config", "rs/ic_os/build_tools/dflate", "rs/ic_os/build_tools/diroid", "rs/ic_os/config", diff --git a/rs/ic_os/config/BUILD.bazel b/rs/ic_os/config/BUILD.bazel index f62bf6b725b0..351104a88153 100644 --- a/rs/ic_os/config/BUILD.bazel +++ b/rs/ic_os/config/BUILD.bazel @@ -6,10 +6,12 @@ DEPENDENCIES = [ # Keep sorted. "//rs/ic_os/config_types", "//rs/ic_os/deterministic_ips", + "//rs/ic_os/metrics_tool", "//rs/ic_os/network", "//rs/ic_os/utils", "//rs/types/types", "@crate_index//:anyhow", + "@crate_index//:askama", "@crate_index//:clap", "@crate_index//:macaddr", "@crate_index//:regex", @@ -22,20 +24,29 @@ DEPENDENCIES = [ DEV_DEPENDENCIES = [ # Keep sorted. + "@crate_index//:goldenfile", "@crate_index//:once_cell", "@crate_index//:tempfile", + "@crate_index//:url", ] MACRO_DEPENDENCIES = [] ALIASES = {} +BIN_SOURCES = [ + "src/main.rs", + "src/guest_vm_config.rs", +] + +LIB_SOURCES = glob( + ["src/**/*.rs"], + exclude = BIN_SOURCES, +) + rust_library( name = "config_lib", - srcs = glob( - ["src/**/*.rs"], - exclude = ["src/main.rs"], - ), + srcs = LIB_SOURCES, crate_name = "config", visibility = [ "//rs:ic-os-pkg", @@ -46,10 +57,7 @@ rust_library( rust_library( name = "config_lib_dev", - srcs = glob( - ["src/**/*.rs"], - exclude = ["src/main.rs"], - ), + srcs = LIB_SOURCES, crate_features = ["dev"], crate_name = "config", visibility = [ @@ -69,11 +77,36 @@ rust_test( rust_binary( name = "config", - srcs = ["src/main.rs"], + srcs = BIN_SOURCES, aliases = ALIASES, + compile_data = ["templates/guestos_vm_template.xml"], crate_name = "config", proc_macro_deps = MACRO_DEPENDENCIES, deps = [ ":config_lib", ] + DEPENDENCIES, ) + +rust_binary( + name = "config_dev", + srcs = BIN_SOURCES, + aliases = ALIASES, + compile_data = ["templates/guestos_vm_template.xml"], + crate_name = "config", + proc_macro_deps = MACRO_DEPENDENCIES, + deps = [ + ":config_lib_dev", + ] + DEPENDENCIES, +) + +rust_test( + name = "config_test", + crate = ":config_dev", + crate_features = ["dev"], + data = glob(["golden/*"]), + env = {"CARGO_MANIFEST_DIR": "rs/ic_os/generate_guestos_vm_config"}, + # Run without sandbox so that goldenfiles can be updated by passing "--test_env UPDATE_GOLDENFILES=1" + tags = ["local"], + # You may add other deps that are specific to the test configuration + deps = DEV_DEPENDENCIES, +) diff --git a/rs/ic_os/config/Cargo.toml b/rs/ic_os/config/Cargo.toml index 1cc713b92968..65e888130354 100644 --- a/rs/ic_os/config/Cargo.toml +++ b/rs/ic_os/config/Cargo.toml @@ -5,7 +5,9 @@ edition = "2021" [dependencies] anyhow = { workspace = true } +askama = { workspace = true } ic-types = { path = "../../types/types" } +ic-metrics-tool = { path = "../metrics_tool" } clap = { workspace = true } macaddr = { workspace = true } utils = { path = "../utils" } @@ -22,6 +24,8 @@ tempfile = { workspace = true } [dev-dependencies] once_cell = "1.8" tempfile = { workspace = true } +url = { workspace = true } +goldenfile = { workspace = true } [lib] name = "config" diff --git a/rs/ic_os/generate_guestos_vm_config/golden/guestos_vm_kvm.xml b/rs/ic_os/config/golden/guestos_vm_kvm.xml similarity index 100% rename from rs/ic_os/generate_guestos_vm_config/golden/guestos_vm_kvm.xml rename to rs/ic_os/config/golden/guestos_vm_kvm.xml diff --git a/rs/ic_os/generate_guestos_vm_config/golden/guestos_vm_qemu.xml b/rs/ic_os/config/golden/guestos_vm_qemu.xml similarity index 100% rename from rs/ic_os/generate_guestos_vm_config/golden/guestos_vm_qemu.xml rename to rs/ic_os/config/golden/guestos_vm_qemu.xml diff --git a/rs/ic_os/generate_guestos_vm_config/src/main.rs b/rs/ic_os/config/src/guest_vm_config.rs similarity index 96% rename from rs/ic_os/generate_guestos_vm_config/src/main.rs rename to rs/ic_os/config/src/guest_vm_config.rs index ded7ec0a34aa..104370542c01 100644 --- a/rs/ic_os/generate_guestos_vm_config/src/main.rs +++ b/rs/ic_os/config/src/guest_vm_config.rs @@ -14,10 +14,10 @@ use std::io::Write; use std::path::{Path, PathBuf}; use std::process::Command; -/// Generate the GuestOS configuration +/// Generate the GuestOS VM configuration #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] -struct Args { +pub struct GenerateGuestVmConfigArgs { /// Specify the config media image file #[arg(short, long, default_value = "/run/ic-node/config.img")] media: PathBuf, @@ -26,23 +26,23 @@ struct Args { #[arg(short, long, default_value = "/var/lib/libvirt/guestos.xml")] output: PathBuf, - /// Path to the HostOS config file + /// Path to the input HostOS config file #[arg(short, long, default_value = DEFAULT_HOSTOS_CONFIG_OBJECT_PATH)] config: PathBuf, } -pub fn main() -> Result<()> { - let args = Args::parse(); - +/// Generate the GuestOS VM configuration by assembling the bootstrap config media image +/// and creating the libvirt XML configuration file. +pub fn generate_guest_vm_config(args: GenerateGuestVmConfigArgs) -> Result<()> { let metrics_writer = MetricsWriter::new(PathBuf::from( "/run/node_exporter/collector_textfile/hostos_generate_guestos_config.prom", )); - run(&args, &metrics_writer, restorecon) + run(args, &metrics_writer, restorecon) } fn run( - args: &Args, + args: GenerateGuestVmConfigArgs, metrics_writer: &MetricsWriter, // We pass a functor to allow mocking in tests. restorecon: impl Fn(&Path) -> Result<()>, @@ -392,14 +392,14 @@ mod tests { serialize_and_write_config(&hostos_config_path, &create_test_hostos_config()).unwrap(); - let args = Args { + let args = GenerateGuestVmConfigArgs { media: media_path.clone(), output: output_path.clone(), config: hostos_config_path.clone(), }; let result = run( - &args, + args, &MetricsWriter::new(metrics_path.clone()), mock_restorecon, ); @@ -429,14 +429,14 @@ mod tests { // Create the output file so it already exists fs::write(&output_path, "test").unwrap(); - let args = Args { + let args = GenerateGuestVmConfigArgs { media: media_path, output: output_path, config: hostos_config_path, }; let result_err = run( - &args, + args, &MetricsWriter::new(metrics_path.clone()), mock_restorecon, ) diff --git a/rs/ic_os/config/src/main.rs b/rs/ic_os/config/src/main.rs index 68a662ecad74..23df4bef3cc4 100644 --- a/rs/ic_os/config/src/main.rs +++ b/rs/ic_os/config/src/main.rs @@ -1,20 +1,22 @@ +use crate::guest_vm_config::{generate_guest_vm_config, GenerateGuestVmConfigArgs}; use anyhow::Result; use clap::{Args, Parser, Subcommand}; use config::config_ini::{get_config_ini_settings, ConfigIniSettings}; use config::deployment_json::get_deployment_settings; +use config::generate_testnet_config::{ + generate_testnet_config, GenerateTestnetConfigArgs, Ipv6ConfigType, +}; +use config::guestos_config::generate_guestos_config; use config::serialize_and_write_config; use config::update_config::{update_guestos_config, update_hostos_config}; +use config_types::*; use macaddr::MacAddr6; use network::resolve_mgmt_mac; use regex::Regex; use std::fs::File; use std::path::{Path, PathBuf}; -use config::generate_testnet_config::{ - generate_testnet_config, GenerateTestnetConfigArgs, Ipv6ConfigType, -}; -use config::guestos_config::generate_guestos_config; -use config_types::*; +mod guest_vm_config; #[derive(Subcommand)] #[allow(clippy::large_enum_variant)] @@ -58,6 +60,9 @@ pub enum Commands { #[arg(long, default_value = config::DEFAULT_HOSTOS_CONFIG_OBJECT_PATH, value_name = "config.json")] hostos_config_json_path: PathBuf, }, + /// Generates the GuestOS VM configuration by assembling the bootstrap config media image + /// and creating the libvirt XML configuration file. + GenerateGuestVmConfig(GenerateGuestVmConfigArgs), } #[derive(Parser)] @@ -362,5 +367,6 @@ pub fn main() -> Result<()> { println!("No command provided. Use --help for usage information."); Ok(()) } + Some(Commands::GenerateGuestVmConfig(args)) => generate_guest_vm_config(args), } } diff --git a/rs/ic_os/generate_guestos_vm_config/templates/guestos_vm_template.xml b/rs/ic_os/config/templates/guestos_vm_template.xml similarity index 100% rename from rs/ic_os/generate_guestos_vm_config/templates/guestos_vm_template.xml rename to rs/ic_os/config/templates/guestos_vm_template.xml diff --git a/rs/ic_os/generate_guestos_vm_config/BUILD.bazel b/rs/ic_os/generate_guestos_vm_config/BUILD.bazel deleted file mode 100644 index fbcb946d965b..000000000000 --- a/rs/ic_os/generate_guestos_vm_config/BUILD.bazel +++ /dev/null @@ -1,50 +0,0 @@ -load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_test") - -package(default_visibility = ["//visibility:public"]) - -DEPENDENCIES = [ - # Keep sorted. - "//rs/ic_os/config_types", - "//rs/ic_os/deterministic_ips", - "//rs/ic_os/metrics_tool", - "@crate_index//:anyhow", - "@crate_index//:askama", - "@crate_index//:clap", - "@crate_index//:macaddr", - "@crate_index//:serde_json", - "@crate_index//:tempfile", -] - -DEV_DEPENDENCIES = [ - "@crate_index//:url", - "@crate_index//:goldenfile", -] - -rust_binary( - name = "generate_guestos_vm_config", - srcs = glob(["src/**/*.rs"]), - compile_data = ["templates/guestos_vm_template.xml"], - crate_name = "generate_guestos_vm_config", - deps = DEPENDENCIES + ["//rs/ic_os/config:config_lib"], -) - -# Dev version for testing that should only be enabled in dev builds (unsafe for prod). -rust_binary( - name = "generate_guestos_vm_config_dev", - srcs = glob(["src/**/*.rs"]), - compile_data = ["templates/guestos_vm_template.xml"], - crate_features = ["dev"], - crate_name = "generate_guestos_vm_config", - deps = DEPENDENCIES + ["//rs/ic_os/config:config_lib_dev"], -) - -rust_test( - name = "generate_guestos_vm_config_test", - crate = ":generate_guestos_vm_config_dev", - crate_features = ["dev"], - data = glob(["golden/*"]), - env = {"CARGO_MANIFEST_DIR": "rs/ic_os/generate_guestos_vm_config"}, - # Run without sandbox so that goldenfiles can be updated by passing "--test_env UPDATE_GOLDENFILES=1" - tags = ["local"], - deps = DEV_DEPENDENCIES, -) diff --git a/rs/ic_os/generate_guestos_vm_config/Cargo.toml b/rs/ic_os/generate_guestos_vm_config/Cargo.toml deleted file mode 100644 index 66fb3d83d433..000000000000 --- a/rs/ic_os/generate_guestos_vm_config/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "generate_guestos_vm_config" -edition = "2021" - -[dependencies] -anyhow = { workspace = true } -askama = { workspace = true } -clap = { workspace = true } -config = { path = "../config" } -config_types = { path = "../config_types" } -deterministic_ips = { path = "../deterministic_ips" } -ic-metrics-tool = { path = "../metrics_tool" } -macaddr = { workspace = true } -serde_json = { workspace = true } -tempfile = { workspace = true } - -[dev-dependencies] -url = { workspace = true } -goldenfile = { workspace = true } - -[features] -dev = ["config/dev"] From 5d542aeba0e1d76327ea8ce1d9dadce79da08ede Mon Sep 17 00:00:00 2001 From: David Frank Date: Fri, 23 May 2025 17:46:00 +0200 Subject: [PATCH 34/47] Already use the newly added tool in HostOS --- ic-os/components/BUILD.bazel | 1 - .../dev-generate-guestos-config.sh | 163 ---------------- .../generate-guestos-config.service | 4 +- .../generate-guestos-config.sh | 159 ---------------- .../guestos/guestos.xml.template | 179 ------------------ ic-os/components/hostos.bzl | 2 - ic-os/docs/Configuration.adoc | 8 +- ic-os/docs/Network-Configuration.adoc | 4 +- ic-os/guestos/docs/Interface.adoc | 2 +- ic-os/hostos/defs.bzl | 2 +- rs/ic_os/release/BUILD.bazel | 1 + 11 files changed, 12 insertions(+), 513 deletions(-) delete mode 100755 ic-os/components/hostos-scripts/generate-guestos-config/dev-generate-guestos-config.sh delete mode 100755 ic-os/components/hostos-scripts/generate-guestos-config/generate-guestos-config.sh delete mode 100755 ic-os/components/hostos-scripts/guestos/guestos.xml.template diff --git a/ic-os/components/BUILD.bazel b/ic-os/components/BUILD.bazel index 580aaee6d7f4..e10837be5902 100644 --- a/ic-os/components/BUILD.bazel +++ b/ic-os/components/BUILD.bazel @@ -47,7 +47,6 @@ REPO_COMPONENTS = glob( # files used for testing and development that aren't "used" by any ic-os variant ignored_repo_components = [ - "hostos-scripts/generate-guestos-config/dev-generate-guestos-config.sh", "networking/dev-certs/canister_http_test_ca.key", "networking/dev-certs/root_cert_gen.sh", ] diff --git a/ic-os/components/hostos-scripts/generate-guestos-config/dev-generate-guestos-config.sh b/ic-os/components/hostos-scripts/generate-guestos-config/dev-generate-guestos-config.sh deleted file mode 100755 index 7b4ec8b0fa30..000000000000 --- a/ic-os/components/hostos-scripts/generate-guestos-config/dev-generate-guestos-config.sh +++ /dev/null @@ -1,163 +0,0 @@ -#!/bin/bash - -set -e - -# Generate the GuestOS configuration. - -source /opt/ic/bin/logging.sh -source /opt/ic/bin/metrics.sh -source /opt/ic/bin/config.sh - -# Get keyword arguments -for argument in "${@}"; do - case ${argument} in - -h | --help) - echo 'Usage: -Generate GuestOS Configuration - -Arguments: - -h, --help show this help message and exit - -i=, --input= specify the input template file (Default: /opt/ic/share/guestos.xml.template) - -m=, --media= specify the config media image file (Default: /run/ic-node/config.img) - -o=, --output= specify the output configuration file (Default: /var/lib/libvirt/guestos.xml) -' - exit 1 - ;; - -i=* | --input=*) - INPUT="${argument#*=}" - shift - ;; - -m=* | --media=*) - MEDIA="${argument#*=}" - shift - ;; - -o=* | --output=*) - OUTPUT="${argument#*=}" - shift - ;; - *) - echo "Error: Argument is not supported." - exit 1 - ;; - esac -done - -function validate_arguments() { - if [ "${INPUT}" == "" -o "${OUTPUT}" == "" ]; then - $0 --help - fi -} - -# Set arguments if undefined -INPUT="${INPUT:=/opt/ic/share/guestos.xml.template}" -MEDIA="${MEDIA:=/run/ic-node/config.img}" -OUTPUT="${OUTPUT:=/var/lib/libvirt/guestos.xml}" - -function read_config_variables() { - ipv6_gateway=$(get_config_value '.network_settings.ipv6_config.Deterministic.gateway') - ipv4_address=$(get_config_value '.network_settings.ipv4_config.address') - ipv4_prefix_length=$(get_config_value '.network_settings.ipv4_config.prefix_length') - ipv4_gateway=$(get_config_value '.network_settings.ipv4_config.gateway') - domain_name=$(get_config_value '.network_settings.domain_name') - node_reward_type=$(get_config_value '.icos_settings.node_reward_type') - nns_urls=$(get_config_value '.icos_settings.nns_urls | join(",")') - mgmt_mac=$(get_config_value '.icos_settings.mgmt_mac') - - use_nns_public_key=$(get_config_value '.icos_settings.use_nns_public_key') - use_node_operator_private_key=$(get_config_value '.icos_settings.use_node_operator_private_key') - use_ssh_authorized_keys=$(get_config_value '.icos_settings.use_ssh_authorized_keys') - - vm_memory=$(get_config_value '.hostos_settings.vm_memory') - vm_cpu=$(get_config_value '.hostos_settings.vm_cpu') - vm_nr_of_vcpus=$(get_config_value '.hostos_settings.vm_nr_of_vcpus') -} - -function assemble_config_media() { - /opt/ic/bin/config generate-guestos-config - - cmd=(/opt/ic/bin/build-bootstrap-config-image.sh ${MEDIA}) - cmd+=(--guestos_config "/boot/config/config-guestos.json") - if [[ "${use_nns_public_key,,}" == "true" ]]; then - cmd+=(--nns_public_key "/boot/config/nns_public_key.pem") - fi - if [[ "${use_node_operator_private_key,,}" == "true" ]]; then - cmd+=(--node_operator_private_key "/boot/config/node_operator_private_key.pem") - fi - if [[ "${use_ssh_authorized_keys,,}" == "true" ]]; then - cmd+=(--accounts_ssh_authorized_keys "/boot/config/ssh_authorized_keys") - fi - cmd+=(--ipv6_address "$(/opt/ic/bin/hostos_tool generate-ipv6-address --node-type GuestOS)") - cmd+=(--ipv6_gateway "${ipv6_gateway}") - if [[ -n "$ipv4_address" && -n "$ipv4_prefix_length" && -n "$ipv4_gateway" ]]; then - cmd+=(--ipv4_address "${ipv4_address}/${ipv4_prefix_length}") - cmd+=(--ipv4_gateway "${ipv4_gateway}") - fi - if [[ -n "$domain_name" ]]; then - cmd+=(--domain "${domain_name}") - fi - if [[ -n "$node_reward_type" ]]; then - cmd+=(--node_reward_type "${node_reward_type}") - fi - cmd+=(--hostname "guest-${mgmt_mac//:/}") - cmd+=(--nns_urls "${nns_urls}") - - # Run the above command - "${cmd[@]}" - write_log "Assembling config media for GuestOS: ${MEDIA}" -} - -function generate_guestos_config() { - MAC_ADDRESS=$(/opt/ic/bin/hostos_tool generate-mac-address --node-type GuestOS) - - # Generate inline CPU spec based on mode - CPU_SPEC=$(mktemp) - if [ "${vm_cpu}" == "qemu" ]; then - CPU_DOMAIN="qemu" - cat >"${CPU_SPEC}" < -EOF - else - CPU_DOMAIN="kvm" - CORE_COUNT=$((vm_nr_of_vcpus / 4)) - cat >"${CPU_SPEC}" < - - - - -EOF - fi - - if [ ! -f "${OUTPUT}" ]; then - mkdir -p "$(dirname "$OUTPUT")" - sed -e "s@{{ resources_memory }}@${vm_memory}@" \ - -e "s@{{ nr_of_vcpus }}@${vm_nr_of_vcpus}@" \ - -e "s@{{ mac_address }}@${MAC_ADDRESS}@" \ - -e "s@{{ cpu_domain }}@${CPU_DOMAIN}@" \ - -e "/{{ cpu_spec }}/{r ${CPU_SPEC}" -e "d" -e "}" \ - "${INPUT}" >"${OUTPUT}" - restorecon -R "$(dirname "$OUTPUT")" - write_log "Generating GuestOS configuration file: ${OUTPUT}" - write_metric "hostos_generate_guestos_config" \ - "1" \ - "HostOS generate GuestOS config" \ - "gauge" - else - write_log "GuestOS configuration file already exists: ${OUTPUT}" - write_metric "hostos_generate_guestos_config" \ - "0" \ - "HostOS generate GuestOS config" \ - "gauge" - fi - - rm -f "${CPU_SPEC}" -} - -function main() { - validate_arguments - read_config_variables - assemble_config_media - generate_guestos_config -} - -main diff --git a/ic-os/components/hostos-scripts/generate-guestos-config/generate-guestos-config.service b/ic-os/components/hostos-scripts/generate-guestos-config/generate-guestos-config.service index 0f84a44bee8e..d8ca1824d858 100644 --- a/ic-os/components/hostos-scripts/generate-guestos-config/generate-guestos-config.service +++ b/ic-os/components/hostos-scripts/generate-guestos-config/generate-guestos-config.service @@ -9,7 +9,9 @@ RequiresMountsFor=/var [Service] Type=oneshot RemainAfterExit=true -ExecStart=/opt/ic/bin/generate-guestos-config.sh +ExecStart=/opt/ic/bin/config generate-guest-vm-config +StandardOutput=journal+console +StandardError=journal+console [Install] WantedBy=multi-user.target diff --git a/ic-os/components/hostos-scripts/generate-guestos-config/generate-guestos-config.sh b/ic-os/components/hostos-scripts/generate-guestos-config/generate-guestos-config.sh deleted file mode 100755 index a241db5b1a39..000000000000 --- a/ic-os/components/hostos-scripts/generate-guestos-config/generate-guestos-config.sh +++ /dev/null @@ -1,159 +0,0 @@ -#!/bin/bash - -set -e - -# Generate the GuestOS configuration. - -source /opt/ic/bin/logging.sh -source /opt/ic/bin/metrics.sh -source /opt/ic/bin/config.sh - -# Get keyword arguments -for argument in "${@}"; do - case ${argument} in - -h | --help) - echo 'Usage: -Generate GuestOS Configuration - -Arguments: - -h, --help show this help message and exit - -i=, --input= specify the input template file (Default: /opt/ic/share/guestos.xml.template) - -m=, --media= specify the config media image file (Default: /run/ic-node/config.img) - -o=, --output= specify the output configuration file (Default: /var/lib/libvirt/guestos.xml) -' - exit 1 - ;; - -i=* | --input=*) - INPUT="${argument#*=}" - shift - ;; - -m=* | --media=*) - MEDIA="${argument#*=}" - shift - ;; - -o=* | --output=*) - OUTPUT="${argument#*=}" - shift - ;; - *) - echo "Error: Argument is not supported." - exit 1 - ;; - esac -done - -function validate_arguments() { - if [ "${INPUT}" == "" -o "${OUTPUT}" == "" ]; then - $0 --help - fi -} - -# Set arguments if undefined -INPUT="${INPUT:=/opt/ic/share/guestos.xml.template}" -MEDIA="${MEDIA:=/run/ic-node/config.img}" -OUTPUT="${OUTPUT:=/var/lib/libvirt/guestos.xml}" - -function read_config_variables() { - ipv6_gateway=$(get_config_value '.network_settings.ipv6_config.Deterministic.gateway') - ipv4_address=$(get_config_value '.network_settings.ipv4_config.address') - ipv4_prefix_length=$(get_config_value '.network_settings.ipv4_config.prefix_length') - ipv4_gateway=$(get_config_value '.network_settings.ipv4_config.gateway') - domain_name=$(get_config_value '.network_settings.domain_name') - node_reward_type=$(get_config_value '.icos_settings.node_reward_type') - nns_urls=$(get_config_value '.icos_settings.nns_urls | join(",")') - mgmt_mac=$(get_config_value '.icos_settings.mgmt_mac') - - use_nns_public_key=$(get_config_value '.icos_settings.use_nns_public_key') - use_node_operator_private_key=$(get_config_value '.icos_settings.use_node_operator_private_key') - - vm_memory=$(get_config_value '.hostos_settings.vm_memory') - vm_cpu=$(get_config_value '.hostos_settings.vm_cpu') - vm_nr_of_vcpus=$(get_config_value '.hostos_settings.vm_nr_of_vcpus') -} - -function assemble_config_media() { - /opt/ic/bin/config generate-guestos-config - - cmd=(/opt/ic/bin/build-bootstrap-config-image.sh ${MEDIA}) - cmd+=(--guestos_config "/boot/config/config-guestos.json") - if [[ "${use_nns_public_key,,}" == "true" ]]; then - cmd+=(--nns_public_key "/boot/config/nns_public_key.pem") - fi - if [[ "${use_node_operator_private_key,,}" == "true" ]]; then - cmd+=(--node_operator_private_key "/boot/config/node_operator_private_key.pem") - fi - cmd+=(--ipv6_address "$(/opt/ic/bin/hostos_tool generate-ipv6-address --node-type GuestOS)") - cmd+=(--ipv6_gateway "${ipv6_gateway}") - if [[ -n "$ipv4_address" && -n "$ipv4_prefix_length" && -n "$ipv4_gateway" ]]; then - cmd+=(--ipv4_address "${ipv4_address}/${ipv4_prefix_length}") - cmd+=(--ipv4_gateway "${ipv4_gateway}") - fi - if [[ -n "$domain_name" ]]; then - cmd+=(--domain "${domain_name}") - fi - if [[ -n "$node_reward_type" ]]; then - cmd+=(--node_reward_type "${node_reward_type}") - fi - cmd+=(--hostname "guest-${mgmt_mac//:/}") - cmd+=(--nns_urls "${nns_urls}") - - # Run the above command - "${cmd[@]}" - write_log "Assembling config media for GuestOS: ${MEDIA}" -} - -function generate_guestos_config() { - MAC_ADDRESS=$(/opt/ic/bin/hostos_tool generate-mac-address --node-type GuestOS) - - # Generate inline CPU spec based on mode - CPU_SPEC=$(mktemp) - if [ "${vm_cpu}" == "qemu" ]; then - CPU_DOMAIN="qemu" - cat >"${CPU_SPEC}" < -EOF - else - CPU_DOMAIN="kvm" - CORE_COUNT=$((vm_nr_of_vcpus / 4)) - cat >"${CPU_SPEC}" < - - - - -EOF - fi - - if [ ! -f "${OUTPUT}" ]; then - mkdir -p "$(dirname "$OUTPUT")" - sed -e "s@{{ resources_memory }}@${vm_memory}@" \ - -e "s@{{ nr_of_vcpus }}@${vm_nr_of_vcpus}@" \ - -e "s@{{ mac_address }}@${MAC_ADDRESS}@" \ - -e "s@{{ cpu_domain }}@${CPU_DOMAIN}@" \ - -e "/{{ cpu_spec }}/{r ${CPU_SPEC}" -e "d" -e "}" \ - "${INPUT}" >"${OUTPUT}" - restorecon -R "$(dirname "$OUTPUT")" - write_log "Generating GuestOS configuration file: ${OUTPUT}" - write_metric "hostos_generate_guestos_config" \ - "1" \ - "HostOS generate GuestOS config" \ - "gauge" - else - write_log "GuestOS configuration file already exists: ${OUTPUT}" - write_metric "hostos_generate_guestos_config" \ - "0" \ - "HostOS generate GuestOS config" \ - "gauge" - fi - - rm -f "${CPU_SPEC}" -} - -function main() { - validate_arguments - read_config_variables - assemble_config_media - generate_guestos_config -} - -main diff --git a/ic-os/components/hostos-scripts/guestos/guestos.xml.template b/ic-os/components/hostos-scripts/guestos/guestos.xml.template deleted file mode 100755 index e03dac16178f..000000000000 --- a/ic-os/components/hostos-scripts/guestos/guestos.xml.template +++ /dev/null @@ -1,179 +0,0 @@ - - guestos - fd897da5-8017-41c8-8575-a706dba30766 - - - - - - {{ resources_memory }} - {{ resources_memory }} - {{ nr_of_vcpus }} - {{ cpu_spec }} - - /machine - - - hvm - /usr/share/OVMF/OVMF_CODE_4M.fd - /var/lib/libvirt/qemu/nvram/guestos_VARS.fd - - - - - - - - - - - - restart - restart - restart - - - - - - /usr/local/bin/qemu-system-x86_64 - - - - - - -
- - - - - - - -
- - - - -
- - - - -
- - - - -
- - - -
- - - - - - -
- - - - - -
- - - - - -
- - - - - -
- - - - - -
- - - - - -
- - - - - -
- - - - - -
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - -
- - - /dev/urandom - -
- - - - - +64055:+108 - - diff --git a/ic-os/components/hostos.bzl b/ic-os/components/hostos.bzl index 1bbff4ba26a7..cc94a82f3988 100644 --- a/ic-os/components/hostos.bzl +++ b/ic-os/components/hostos.bzl @@ -4,11 +4,9 @@ Enumerate every component file dependency for HostOS component_files = { # hostos-scripts - Label("hostos-scripts/generate-guestos-config/generate-guestos-config.sh"): "/opt/ic/bin/generate-guestos-config.sh", Label("hostos-scripts/generate-guestos-config/generate-guestos-config.service"): "/etc/systemd/system/generate-guestos-config.service", Label("hostos-scripts/guestos/guestos.service"): "/etc/systemd/system/guestos.service", Label("hostos-scripts/guestos/guestos.sh"): "/opt/ic/bin/guestos.sh", - Label("hostos-scripts/guestos/guestos.xml.template"): "/opt/ic/share/guestos.xml.template", Label("hostos-scripts/libvirt/setup-libvirt.sh"): "/opt/ic/bin/setup-libvirt.sh", Label("hostos-scripts/libvirt/setup-libvirt.service"): "/etc/systemd/system/setup-libvirt.service", Label("hostos-scripts/misc/setup-var.sh"): "/opt/ic/bin/setup-var.sh", diff --git a/ic-os/docs/Configuration.adoc b/ic-os/docs/Configuration.adoc index 6d2a5ceac8b6..bbfbcb602eff 100644 --- a/ic-os/docs/Configuration.adoc +++ b/ic-os/docs/Configuration.adoc @@ -22,7 +22,7 @@ HostOS creates a bootstrap config image containing a tar file with the GuestOS c Refer to link:../components/hostos-scripts/generate-guestos-config.sh[generate-guestos-config.sh] and link:../components/hostos-scripts/build-bootstrap-config-image.sh[build-bootstrap-config-image.sh] for more details. -When the HostOS launches the GuestOS, the bootstrap config image is attached to the GuestOS as a virtual USB. Refer to link:../components/hostos-scripts/guestos/guestos.xml.template[guestos.xml.template] +When the HostOS launches the GuestOS, the bootstrap config image is attached to the GuestOS as a virtual USB. Refer to link:../../rs/ic_os/config/templates/guestos_vm_template.xml[guestos_vm_template.xml] When the GuestOS boots, it checks for available removable media devices (i.e. the bootstrap config image). If such a device is found, the media must contain a VFAT filesystem and a single file called `ic-bootstrap.tar`. @@ -88,8 +88,8 @@ Consider that values may be controlled by an attacker on boot. Bootstrapping a n === Testing -For testing, to add new configuration bits, you can modify the config tool located at -link:../../rs/ic_os/config/README.md[rs/ic_os/config]. Or, you may find it easier to update *build-bootstrap-config-image.sh* and *bootstrap-ic-node.sh* directly, +For testing, to add new configuration bits, you can modify the config tool located at +link:../../rs/ic_os/config/README.md[rs/ic_os/config]. Or, you may find it easier to update *build-bootstrap-config-image.sh* and *bootstrap-ic-node.sh* directly, particularly if you wish to add a new configuration file (as opposed to just a new configuration _field_). * *ic_os config tool* can be run stand-alone to verify that it produces the intended configuration object. @@ -101,7 +101,7 @@ After all is done, it is advised to prepare a configuration for a single node an === Injecting external state -*Typical bootstrap process:* On first boot, the system will perform technical initialization (filesystems, etc.) and afterwards, initialize itself to act as a node in the IC. The node is initialized using key generation on the node itself (such that the private key never leaves the node) and through joining the IC (the node gets the rest of its state via joining the IC). "Registration" to the target IC is initiated by the node itself by sending a Node Operator-signed "join" request to its NNS. +*Typical bootstrap process:* On first boot, the system will perform technical initialization (filesystems, etc.) and afterwards, initialize itself to act as a node in the IC. The node is initialized using key generation on the node itself (such that the private key never leaves the node) and through joining the IC (the node gets the rest of its state via joining the IC). "Registration" to the target IC is initiated by the node itself by sending a Node Operator-signed "join" request to its NNS. However, the typical bootstrap process can be modified such that the node is initialized using externally generated private keys and an externally generated initial state. All "registration" to the target IC is assumed to have been performed by other means. diff --git a/ic-os/docs/Network-Configuration.adoc b/ic-os/docs/Network-Configuration.adoc index 32ff2d957602..2b28cf51550e 100644 --- a/ic-os/docs/Network-Configuration.adoc +++ b/ic-os/docs/Network-Configuration.adoc @@ -7,7 +7,7 @@ Network configuration details for each IC-OS: * SetupOS ** Basic network connectivity is checked via pinging nns.ic0.app and the default gateway. Virtually no network traffic goes through SetupOS. * HostOS -** The br6 bridge network interface is set up and passed to the GuestOS VM through qemu (refer to guestos.xml.template). +** The br6 bridge network interface is set up and passed to the GuestOS VM through qemu (refer to guestos_template.xml). * GuestOS ** An internet connection is received via the br6 bridge interface from qemu. @@ -50,7 +50,7 @@ Examples: replica--3cecef6b3799-4wd4u boundary-3cecef6b3799-4wd4u -== IPv6 Address +== IPv6 Address The IP address can be derived from the MAC address and vice versa: As every virtual machine ends in the same MAC address, the IPv6 address of each node on the same physical machine can be derived, including the hypervisor itself. In other words, the prefix of the EUI-64 formatted IPv6 SLAAC address is swapped to get to the IPv6 address of the next node. diff --git a/ic-os/guestos/docs/Interface.adoc b/ic-os/guestos/docs/Interface.adoc index 0aa51d723362..2a027149071d 100644 --- a/ic-os/guestos/docs/Interface.adoc +++ b/ic-os/guestos/docs/Interface.adoc @@ -8,5 +8,5 @@ This document covers the GuestOS interface requirements, providing all the infor ** Details on populating the GuestOS config partition * link:../../../rs/ic_os/vsock/README.md[Vsock] ** Communication between GuestOS and HostOS -* link:../../components/hostos-scripts/guestos/guestos.xml.template[guestos.xml.template] +* link:../../../rs/ic_os/config/templates/guestos_vm_template.xml[guestos_vm_template.xml] ** Details on resource allocations used in production diff --git a/ic-os/hostos/defs.bzl b/ic-os/hostos/defs.bzl index 888a2b4c4fe4..f4c4d22c14c5 100644 --- a/ic-os/hostos/defs.bzl +++ b/ic-os/hostos/defs.bzl @@ -79,7 +79,7 @@ def image_deps(mode, _malicious = False): deps.update(image_variants[mode]) if "dev" in mode: - deps["rootfs"].update({"//ic-os/components:hostos-scripts/generate-guestos-config/dev-generate-guestos-config.sh": "/opt/ic/bin/generate-guestos-config.sh:0755"}) + deps["rootfs"].update({"//rs/ic_os/release:config_dev": "/opt/ic/bin/config:0755"}) return deps diff --git a/rs/ic_os/release/BUILD.bazel b/rs/ic_os/release/BUILD.bazel index 902e58a30828..62f75459bf8e 100644 --- a/rs/ic_os/release/BUILD.bazel +++ b/rs/ic_os/release/BUILD.bazel @@ -9,6 +9,7 @@ OBJECTS = { "nft-exporter": "//rs/ic_os/nft_exporter:nft-exporter", "setupos_tool": "//rs/ic_os/os_tools/setupos_tool:setupos_tool", "config": "//rs/ic_os/config:config", + "config_dev": "//rs/ic_os/config:config_dev", "vsock_guest": "//rs/ic_os/vsock/guest:vsock_guest", "vsock_host": "//rs/ic_os/vsock/host:vsock_host", "metrics-proxy": "@crate_index//:metrics-proxy__metrics-proxy", From d15eca690daa36af553320a8a91e3b0f7ea8e9b4 Mon Sep 17 00:00:00 2001 From: David Frank Date: Fri, 23 May 2025 17:55:09 +0200 Subject: [PATCH 35/47] Update rs/ic_os/config/src/guest_vm_config.rs Co-authored-by: Andrew Battat <113942931+andrewbattat@users.noreply.github.com> --- rs/ic_os/config/src/guest_vm_config.rs | 58 ++++++++++---------------- 1 file changed, 21 insertions(+), 37 deletions(-) diff --git a/rs/ic_os/config/src/guest_vm_config.rs b/rs/ic_os/config/src/guest_vm_config.rs index 104370542c01..0f2529f7058d 100644 --- a/rs/ic_os/config/src/guest_vm_config.rs +++ b/rs/ic_os/config/src/guest_vm_config.rs @@ -341,46 +341,30 @@ mod tests { path } - #[test] - fn test_generate_vm_config_qemu() { - let mut mint = Mint::new(goldenfiles_path()); - let mut config = create_test_hostos_config(); - - config.hostos_settings = HostOSSettings { - vm_memory: 490, - vm_cpu: "qemu".to_string(), - vm_nr_of_vcpus: 56, - verbose: true, - }; - - let vm_config = generate_vm_config(&config, Path::new("/tmp/config.img")).unwrap(); - - fs::write( - mint.new_goldenpath("guestos_vm_qemu.xml").unwrap(), - vm_config, - ) - .unwrap(); - } +fn test_vm_config(cpu_type: &str, filename: &str) { + let mut mint = Mint::new(goldenfiles_path()); + let mut config = create_test_hostos_config(); + + config.hostos_settings = HostOSSettings { + vm_memory: 490, + vm_cpu: cpu_type.to_string(), + vm_nr_of_vcpus: 56, + verbose: false, + }; - #[test] - fn test_generate_vm_config_kvm() { - let mut mint = Mint::new(goldenfiles_path()); - let mut config = create_test_hostos_config(); - config.hostos_settings = HostOSSettings { - vm_memory: 490, - vm_cpu: "kvm".to_string(), - vm_nr_of_vcpus: 56, - verbose: false, - }; + let vm_config = generate_vm_config(&config, Path::new("/tmp/config.img")).unwrap(); + fs::write(mint.new_goldenpath(filename).unwrap(), vm_config).unwrap(); +} - let vm_config = generate_vm_config(&config, Path::new("/tmp/config.img")).unwrap(); +#[test] +fn test_generate_vm_config_qemu() { + test_vm_config("qemu", "guestos_vm_qemu.xml"); +} - fs::write( - mint.new_goldenpath("guestos_vm_kvm.xml").unwrap(), - vm_config, - ) - .unwrap(); - } +#[test] +fn test_generate_vm_config_kvm() { + test_vm_config("kvm", "guestos_vm_kvm.xml"); +} #[test] fn test_run_success() { From 53ffef6d52818ab53b5c270597ae53da05970337 Mon Sep 17 00:00:00 2001 From: David Frank Date: Fri, 23 May 2025 17:56:36 +0200 Subject: [PATCH 36/47] Update rs/ic_os/config/src/guest_vm_config.rs Co-authored-by: Andrew Battat <113942931+andrewbattat@users.noreply.github.com> --- rs/ic_os/config/src/guest_vm_config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rs/ic_os/config/src/guest_vm_config.rs b/rs/ic_os/config/src/guest_vm_config.rs index 0f2529f7058d..6587719fe868 100644 --- a/rs/ic_os/config/src/guest_vm_config.rs +++ b/rs/ic_os/config/src/guest_vm_config.rs @@ -68,7 +68,7 @@ fn run( ); } - let output_path = &args.output; + let vm_config_path = &args.output; // Create parent directory if it doesn't exist if let Some(parent) = output_path.parent() { From 5130461a5ba96dd9543698d44bc9df181c9e7f88 Mon Sep 17 00:00:00 2001 From: David Frank Date: Fri, 23 May 2025 18:19:23 +0200 Subject: [PATCH 37/47] Fixes --- rs/ic_os/config/src/guest_vm_config.rs | 69 ++++++++++--------- .../config/src/guestos_bootstrap_image.rs | 12 ++++ 2 files changed, 50 insertions(+), 31 deletions(-) diff --git a/rs/ic_os/config/src/guest_vm_config.rs b/rs/ic_os/config/src/guest_vm_config.rs index 6587719fe868..41bf5411c9b9 100644 --- a/rs/ic_os/config/src/guest_vm_config.rs +++ b/rs/ic_os/config/src/guest_vm_config.rs @@ -71,23 +71,23 @@ fn run( let vm_config_path = &args.output; // Create parent directory if it doesn't exist - if let Some(parent) = output_path.parent() { + if let Some(parent) = vm_config_path.parent() { fs::create_dir_all(parent).context("Failed to create output directory")?; } - File::create(output_path) + File::create(vm_config_path) .context("Failed to create output file")? .write_all(generate_vm_config(&hostos_config, &args.media)?.as_bytes()) .context("Failed to write output file")?; // Restore SELinux security context - if let Some(parent) = output_path.parent() { + if let Some(parent) = vm_config_path.parent() { restorecon(parent)? } println!( "Generating GuestOS configuration file: {}", - output_path.display(), + vm_config_path.display(), ); metrics_writer.write_metrics(&[Metric::with_annotation( @@ -327,6 +327,7 @@ mod tests { node_operator_private_key: Some(PathBuf::from( "/boot/config/node_operator_private_key.pem" )), + #[cfg(feature = "dev")] accounts_ssh_authorized_keys: Some(PathBuf::from( "/boot/config/ssh_authorized_keys" )), @@ -341,44 +342,44 @@ mod tests { path } -fn test_vm_config(cpu_type: &str, filename: &str) { - let mut mint = Mint::new(goldenfiles_path()); - let mut config = create_test_hostos_config(); - - config.hostos_settings = HostOSSettings { - vm_memory: 490, - vm_cpu: cpu_type.to_string(), - vm_nr_of_vcpus: 56, - verbose: false, - }; + fn test_vm_config(cpu_type: &str, filename: &str) { + let mut mint = Mint::new(goldenfiles_path()); + let mut config = create_test_hostos_config(); - let vm_config = generate_vm_config(&config, Path::new("/tmp/config.img")).unwrap(); - fs::write(mint.new_goldenpath(filename).unwrap(), vm_config).unwrap(); -} + config.hostos_settings = HostOSSettings { + vm_memory: 490, + vm_cpu: cpu_type.to_string(), + vm_nr_of_vcpus: 56, + verbose: false, + }; -#[test] -fn test_generate_vm_config_qemu() { - test_vm_config("qemu", "guestos_vm_qemu.xml"); -} + let vm_config = generate_vm_config(&config, Path::new("/tmp/config.img")).unwrap(); + fs::write(mint.new_goldenpath(filename).unwrap(), vm_config).unwrap(); + } -#[test] -fn test_generate_vm_config_kvm() { - test_vm_config("kvm", "guestos_vm_kvm.xml"); -} + #[test] + fn test_generate_vm_config_qemu() { + test_vm_config("qemu", "guestos_vm_qemu.xml"); + } + + #[test] + fn test_generate_vm_config_kvm() { + test_vm_config("kvm", "guestos_vm_kvm.xml"); + } #[test] fn test_run_success() { let temp_dir = tempdir().unwrap(); let hostos_config_path = temp_dir.path().join("hostos.json"); let media_path = temp_dir.path().join("config.img"); - let output_path = temp_dir.path().join("guestos.xml"); + let vm_config_path = temp_dir.path().join("guestos.xml"); let metrics_path = temp_dir.path().join("metrics.prom"); serialize_and_write_config(&hostos_config_path, &create_test_hostos_config()).unwrap(); let args = GenerateGuestVmConfigArgs { media: media_path.clone(), - output: output_path.clone(), + output: vm_config_path.clone(), config: hostos_config_path.clone(), }; @@ -397,7 +398,7 @@ fn test_generate_vm_config_kvm() { ); assert!(media_path.metadata().unwrap().size() > 0); - assert!(output_path.metadata().unwrap().size() > 0); + assert!(vm_config_path.metadata().unwrap().size() > 0); } #[test] @@ -405,17 +406,17 @@ fn test_generate_vm_config_kvm() { let temp_dir = tempdir().unwrap(); let hostos_config_path = temp_dir.path().join("hostos.json"); let media_path = temp_dir.path().join("config.img"); - let output_path = temp_dir.path().join("guestos.xml"); + let vm_config_path = temp_dir.path().join("guestos.xml"); let metrics_path = temp_dir.path().join("metrics.prom"); serialize_and_write_config(&hostos_config_path, &create_test_hostos_config()).unwrap(); // Create the output file so it already exists - fs::write(&output_path, "test").unwrap(); + fs::write(&vm_config_path, "test").unwrap(); let args = GenerateGuestVmConfigArgs { media: media_path, - output: output_path, + output: vm_config_path, config: hostos_config_path, }; @@ -438,4 +439,10 @@ fn test_generate_vm_config_kvm() { hostos_generate_guestos_config 0\n" ) } + + #[test] + fn ensure_tested_with_dev() { + // Ensure that the test is run with the dev feature enabled. + assert!(cfg!(feature = "dev")); + } } diff --git a/rs/ic_os/config/src/guestos_bootstrap_image.rs b/rs/ic_os/config/src/guestos_bootstrap_image.rs index ed9e456d2b23..f992cb27afbb 100644 --- a/rs/ic_os/config/src/guestos_bootstrap_image.rs +++ b/rs/ic_os/config/src/guestos_bootstrap_image.rs @@ -538,6 +538,7 @@ mod tests { guestos_config: Some(config_path), nns_public_key: Some(nns_key_path), node_operator_private_key: Some(node_key_path), + #[cfg(feature = "dev")] accounts_ssh_authorized_keys: Some(ssh_keys_dir), ic_crypto: Some(crypto_dir), ic_state: Some(state_dir), @@ -553,10 +554,15 @@ mod tests { nns_urls: vec!["url1".to_string(), "url2".to_string()], backup_retention_time_sec: Some(3600), backup_purging_interval_sec: Some(300), + #[cfg(feature = "dev")] malicious_behavior: Some(MaliciousBehaviour::new(true)), + #[cfg(feature = "dev")] query_stats_epoch_length: Some(60), + #[cfg(feature = "dev")] bitcoind_addr: Some("127.0.0.1:8332".to_string()), + #[cfg(feature = "dev")] jaeger_addr: Some("127.0.0.1:14250".to_string()), + #[cfg(feature = "dev")] socks_proxy: Some("socks5://127.0.0.1:1080".to_string()), }; @@ -671,4 +677,10 @@ mod tests { Ok(()) } + + #[test] + fn ensure_tested_with_dev() { + // Ensure that the test is run with the dev feature enabled. + assert!(cfg!(feature = "dev")); + } } From 594082cb5a5caba9bd31b40b2deb3b95746231ea Mon Sep 17 00:00:00 2001 From: David Frank Date: Fri, 23 May 2025 19:13:59 +0200 Subject: [PATCH 38/47] Temporarily remove repr build whitelist --- .github/actions/bazel/action.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/actions/bazel/action.yaml b/.github/actions/bazel/action.yaml index 9b7ff95f34e2..4869bba18372 100644 --- a/.github/actions/bazel/action.yaml +++ b/.github/actions/bazel/action.yaml @@ -91,8 +91,7 @@ runs: zstd -f -d "$execlog_zst" -o "$execlog" bazel run //bazel:execution_log_compact_to_csv \ $([[ "$(uname)" == "Linux" ]] && echo '--repository_cache=/cache/bazel') -- \ - --input_execlog="$execlog" \ - --whitelist_pat='^//' + --input_execlog="$execlog" done > '${{ steps.metrics-tmpdir.outputs.dir }}/execlogs.csv' - name: Upload execution log From b8c7b7aebce8c4b43441c08e1af21fe843a3daed Mon Sep 17 00:00:00 2001 From: David Frank Date: Fri, 23 May 2025 19:32:50 +0200 Subject: [PATCH 39/47] Revert "Temporarily remove repr build whitelist" This reverts commit 594082cb5a5caba9bd31b40b2deb3b95746231ea. --- .github/actions/bazel/action.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/actions/bazel/action.yaml b/.github/actions/bazel/action.yaml index 4869bba18372..9b7ff95f34e2 100644 --- a/.github/actions/bazel/action.yaml +++ b/.github/actions/bazel/action.yaml @@ -91,7 +91,8 @@ runs: zstd -f -d "$execlog_zst" -o "$execlog" bazel run //bazel:execution_log_compact_to_csv \ $([[ "$(uname)" == "Linux" ]] && echo '--repository_cache=/cache/bazel') -- \ - --input_execlog="$execlog" + --input_execlog="$execlog" \ + --whitelist_pat='^//' done > '${{ steps.metrics-tmpdir.outputs.dir }}/execlogs.csv' - name: Upload execution log From 5f2ffb94a194eb72bb4a0eac2bcadc940301e4b9 Mon Sep 17 00:00:00 2001 From: David Frank Date: Fri, 23 May 2025 19:58:35 +0200 Subject: [PATCH 40/47] Temporarily remove golden tests and golden files because it messes up git history --- rs/ic_os/config/golden/guestos_vm_kvm.xml | 183 --------------------- rs/ic_os/config/golden/guestos_vm_qemu.xml | 179 -------------------- rs/ic_os/config/src/guest_vm_config.rs | 65 ++++---- 3 files changed, 33 insertions(+), 394 deletions(-) delete mode 100644 rs/ic_os/config/golden/guestos_vm_kvm.xml delete mode 100644 rs/ic_os/config/golden/guestos_vm_qemu.xml diff --git a/rs/ic_os/config/golden/guestos_vm_kvm.xml b/rs/ic_os/config/golden/guestos_vm_kvm.xml deleted file mode 100644 index e8f787e1e0f1..000000000000 --- a/rs/ic_os/config/golden/guestos_vm_kvm.xml +++ /dev/null @@ -1,183 +0,0 @@ - - guestos - fd897da5-8017-41c8-8575-a706dba30766 - - - - - - 490 - 490 - 56 - - - - - - - /machine - - - hvm - /usr/share/OVMF/OVMF_CODE_4M.fd - /var/lib/libvirt/qemu/nvram/guestos_VARS.fd - - - - - - - - - - - - restart - restart - restart - - - - - - /usr/local/bin/qemu-system-x86_64 - - - - - - -
- - - - - - - -
- - - - -
- - - - -
- - - - -
- - - -
- - - - - - -
- - - - - -
- - - - - -
- - - - - -
- - - - - -
- - - - - -
- - - - - -
- - - - - -
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - -
- - - /dev/urandom - -
- - - - - +64055:+108 - - \ No newline at end of file diff --git a/rs/ic_os/config/golden/guestos_vm_qemu.xml b/rs/ic_os/config/golden/guestos_vm_qemu.xml deleted file mode 100644 index 19438fb5a026..000000000000 --- a/rs/ic_os/config/golden/guestos_vm_qemu.xml +++ /dev/null @@ -1,179 +0,0 @@ - - guestos - fd897da5-8017-41c8-8575-a706dba30766 - - - - - - 490 - 490 - 56 - - - /machine - - - hvm - /usr/share/OVMF/OVMF_CODE_4M.fd - /var/lib/libvirt/qemu/nvram/guestos_VARS.fd - - - - - - - - - - - - restart - restart - restart - - - - - - /usr/local/bin/qemu-system-x86_64 - - - - - - -
- - - - - - - -
- - - - -
- - - - -
- - - - -
- - - -
- - - - - - -
- - - - - -
- - - - - -
- - - - - -
- - - - - -
- - - - - -
- - - - - -
- - - - - -
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - -
- - - /dev/urandom - -
- - - - - +64055:+108 - - \ No newline at end of file diff --git a/rs/ic_os/config/src/guest_vm_config.rs b/rs/ic_os/config/src/guest_vm_config.rs index 41bf5411c9b9..43cf24be2744 100644 --- a/rs/ic_os/config/src/guest_vm_config.rs +++ b/rs/ic_os/config/src/guest_vm_config.rs @@ -249,8 +249,6 @@ mod tests { DeploymentEnvironment, DeterministicIpv6Config, HostOSConfig, HostOSSettings, ICOSSettings, Ipv4Config, Ipv6Config, Logging, NetworkSettings, }; - use goldenfile::Mint; - use std::env; use std::os::unix::prelude::MetadataExt; use std::path::Path; use tempfile::tempdir; @@ -336,36 +334,39 @@ mod tests { ); } - fn goldenfiles_path() -> PathBuf { - let mut path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - path.push("golden"); - path - } - - fn test_vm_config(cpu_type: &str, filename: &str) { - let mut mint = Mint::new(goldenfiles_path()); - let mut config = create_test_hostos_config(); - - config.hostos_settings = HostOSSettings { - vm_memory: 490, - vm_cpu: cpu_type.to_string(), - vm_nr_of_vcpus: 56, - verbose: false, - }; - - let vm_config = generate_vm_config(&config, Path::new("/tmp/config.img")).unwrap(); - fs::write(mint.new_goldenpath(filename).unwrap(), vm_config).unwrap(); - } - - #[test] - fn test_generate_vm_config_qemu() { - test_vm_config("qemu", "guestos_vm_qemu.xml"); - } - - #[test] - fn test_generate_vm_config_kvm() { - test_vm_config("kvm", "guestos_vm_kvm.xml"); - } + // Temporarily comment these out until the next PR where we check in the golden files + // (we do that to avoid messing up git history). + + // fn goldenfiles_path() -> PathBuf { + // let mut path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + // path.push("golden"); + // path + // } + // + // fn test_vm_config(cpu_type: &str, filename: &str) { + // let mut mint = Mint::new(goldenfiles_path()); + // let mut config = create_test_hostos_config(); + // + // config.hostos_settings = HostOSSettings { + // vm_memory: 490, + // vm_cpu: cpu_type.to_string(), + // vm_nr_of_vcpus: 56, + // verbose: false, + // }; + // + // let vm_config = generate_vm_config(&config, Path::new("/tmp/config.img")).unwrap(); + // fs::write(mint.new_goldenpath(filename).unwrap(), vm_config).unwrap(); + // } + // + // #[test] + // fn test_generate_vm_config_qemu() { + // test_vm_config("qemu", "guestos_vm_qemu.xml"); + // } + // + // #[test] + // fn test_generate_vm_config_kvm() { + // test_vm_config("kvm", "guestos_vm_kvm.xml"); + // } #[test] fn test_run_success() { From dc09f855f2bc3dfc4baeb779478fe8103bc9ead7 Mon Sep 17 00:00:00 2001 From: David Frank Date: Fri, 23 May 2025 22:00:53 +0200 Subject: [PATCH 41/47] Lift up GuestOSTemplateProps to see if that solves build determinism --- rs/ic_os/config/src/guest_vm_config.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/rs/ic_os/config/src/guest_vm_config.rs b/rs/ic_os/config/src/guest_vm_config.rs index 43cf24be2744..013b1edb1501 100644 --- a/rs/ic_os/config/src/guest_vm_config.rs +++ b/rs/ic_os/config/src/guest_vm_config.rs @@ -194,20 +194,20 @@ fn make_bootstrap_options( Ok(bootstrap_options) } +// If you get a compile error pointing at #[derive(Template)], there is likely a syntax error in +// the template. +#[derive(Template)] +#[template(path = "guestos_vm_template.xml")] +pub struct GuestOSTemplateProps<'a> { + pub cpu_domain: &'a str, + pub vm_memory: u32, + pub nr_of_vcpus: u32, + pub mac_address: MacAddr6, + pub config_media: &'a str, +} + /// Generate the GuestOS VM libvirt XML configuration and return it as String. fn generate_vm_config(config: &HostOSConfig, media_path: &Path) -> Result { - // If you get a compile error pointing at #[derive(Template)], there is likely a syntax error in - // the template. - #[derive(Template)] - #[template(path = "guestos_vm_template.xml")] - pub struct GuestOSTemplateProps<'a> { - pub cpu_domain: &'a str, - pub vm_memory: u32, - pub nr_of_vcpus: u32, - pub mac_address: MacAddr6, - pub config_media: &'a str, - } - let mac_address = calculate_deterministic_mac( &config.icos_settings.mgmt_mac, config.icos_settings.deployment_environment, From eea0e9b6d6954ae5c885588c33d6f7967afc0f37 Mon Sep 17 00:00:00 2001 From: David Frank Date: Fri, 23 May 2025 22:33:38 +0200 Subject: [PATCH 42/47] Revert "Lift up GuestOSTemplateProps to see if that solves build determinism" This reverts commit dc09f855f2bc3dfc4baeb779478fe8103bc9ead7. --- rs/ic_os/config/src/guest_vm_config.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/rs/ic_os/config/src/guest_vm_config.rs b/rs/ic_os/config/src/guest_vm_config.rs index 013b1edb1501..43cf24be2744 100644 --- a/rs/ic_os/config/src/guest_vm_config.rs +++ b/rs/ic_os/config/src/guest_vm_config.rs @@ -194,20 +194,20 @@ fn make_bootstrap_options( Ok(bootstrap_options) } -// If you get a compile error pointing at #[derive(Template)], there is likely a syntax error in -// the template. -#[derive(Template)] -#[template(path = "guestos_vm_template.xml")] -pub struct GuestOSTemplateProps<'a> { - pub cpu_domain: &'a str, - pub vm_memory: u32, - pub nr_of_vcpus: u32, - pub mac_address: MacAddr6, - pub config_media: &'a str, -} - /// Generate the GuestOS VM libvirt XML configuration and return it as String. fn generate_vm_config(config: &HostOSConfig, media_path: &Path) -> Result { + // If you get a compile error pointing at #[derive(Template)], there is likely a syntax error in + // the template. + #[derive(Template)] + #[template(path = "guestos_vm_template.xml")] + pub struct GuestOSTemplateProps<'a> { + pub cpu_domain: &'a str, + pub vm_memory: u32, + pub nr_of_vcpus: u32, + pub mac_address: MacAddr6, + pub config_media: &'a str, + } + let mac_address = calculate_deterministic_mac( &config.icos_settings.mgmt_mac, config.icos_settings.deployment_environment, From 87bc9ed994c962f39ea225c0d44ffa9324476f6b Mon Sep 17 00:00:00 2001 From: Bas van Dijk Date: Sat, 24 May 2025 09:35:00 +0000 Subject: [PATCH 43/47] fix: reproducibility of generate_vm_config --- rs/ic_os/config/BUILD.bazel | 10 +++++-- rs/ic_os/config/build.rs | 37 ++++++++++++++++++++++++++ rs/ic_os/config/src/guest_vm_config.rs | 15 +++-------- 3 files changed, 48 insertions(+), 14 deletions(-) create mode 100644 rs/ic_os/config/build.rs diff --git a/rs/ic_os/config/BUILD.bazel b/rs/ic_os/config/BUILD.bazel index 351104a88153..2e8e69c0c3be 100644 --- a/rs/ic_os/config/BUILD.bazel +++ b/rs/ic_os/config/BUILD.bazel @@ -1,9 +1,17 @@ +load("@rules_rust//cargo:defs.bzl", "cargo_build_script") load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_test") package(default_visibility = ["//rs:ic-os-pkg"]) +cargo_build_script( + name = "build_script", + srcs = ["build.rs"], + data = ["templates/guestos_vm_template.xml"], +) + DEPENDENCIES = [ # Keep sorted. + ":build_script", "//rs/ic_os/config_types", "//rs/ic_os/deterministic_ips", "//rs/ic_os/metrics_tool", @@ -79,7 +87,6 @@ rust_binary( name = "config", srcs = BIN_SOURCES, aliases = ALIASES, - compile_data = ["templates/guestos_vm_template.xml"], crate_name = "config", proc_macro_deps = MACRO_DEPENDENCIES, deps = [ @@ -91,7 +98,6 @@ rust_binary( name = "config_dev", srcs = BIN_SOURCES, aliases = ALIASES, - compile_data = ["templates/guestos_vm_template.xml"], crate_name = "config", proc_macro_deps = MACRO_DEPENDENCIES, deps = [ diff --git a/rs/ic_os/config/build.rs b/rs/ic_os/config/build.rs new file mode 100644 index 000000000000..3909106fda5c --- /dev/null +++ b/rs/ic_os/config/build.rs @@ -0,0 +1,37 @@ +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +// Build reproducibility. askama adds a include_bytes! call when it's generating +// a template impl so that rustc will recompile the module when the file changes +// on disk. See https://github.com/djc/askama/blob/180696053833147a61b3348646a953e7d92ae582/askama_shared/src/generator.rs#L141 +// The stringified output of every proc-macro is added to the metadata hash for +// a crate. That output includes the full filepath to include_bytes!. It may be +// different on two machines, if they use different tempdir paths for the build. +// However, if we include the html source directly in the output, no +// inconsistency is introduced. +fn main() { + println!("cargo:rerun-if-changed=templates/guestos_vm_template.xml"); + let mut f = File::create( + PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("guestos_vm_template.rs"), + ) + .unwrap(); + f.write_all( + format!( + r#" +#[derive(Template)] +#[template(escape = "xml", source = {:?}, ext = "xml")] +pub struct GuestOSTemplateProps<'a> {{ + pub cpu_domain: &'a str, + pub vm_memory: u32, + pub nr_of_vcpus: u32, + pub mac_address: MacAddr6, + pub config_media: &'a str, +}} + "#, + std::fs::read_to_string("templates/guestos_vm_template.xml").unwrap() + ) + .as_bytes(), + ) + .unwrap(); +} diff --git a/rs/ic_os/config/src/guest_vm_config.rs b/rs/ic_os/config/src/guest_vm_config.rs index 013b1edb1501..2a6933736dd6 100644 --- a/rs/ic_os/config/src/guest_vm_config.rs +++ b/rs/ic_os/config/src/guest_vm_config.rs @@ -14,6 +14,9 @@ use std::io::Write; use std::path::{Path, PathBuf}; use std::process::Command; +// See build.rs +include!(concat!(env!("OUT_DIR"), "/guestos_vm_template.rs")); + /// Generate the GuestOS VM configuration #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -194,18 +197,6 @@ fn make_bootstrap_options( Ok(bootstrap_options) } -// If you get a compile error pointing at #[derive(Template)], there is likely a syntax error in -// the template. -#[derive(Template)] -#[template(path = "guestos_vm_template.xml")] -pub struct GuestOSTemplateProps<'a> { - pub cpu_domain: &'a str, - pub vm_memory: u32, - pub nr_of_vcpus: u32, - pub mac_address: MacAddr6, - pub config_media: &'a str, -} - /// Generate the GuestOS VM libvirt XML configuration and return it as String. fn generate_vm_config(config: &HostOSConfig, media_path: &Path) -> Result { let mac_address = calculate_deterministic_mac( From 030b7c90bd55aceace61b1e5d81a8c76dd3e68ee Mon Sep 17 00:00:00 2001 From: David Frank Date: Sat, 24 May 2025 13:44:50 +0200 Subject: [PATCH 44/47] Build without Bazel sandbox for determinism --- rs/ic_os/config/BUILD.bazel | 12 ++++++++++-- rs/ic_os/config/src/guest_vm_config.rs | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/rs/ic_os/config/BUILD.bazel b/rs/ic_os/config/BUILD.bazel index 351104a88153..2e1e26cf7e14 100644 --- a/rs/ic_os/config/BUILD.bazel +++ b/rs/ic_os/config/BUILD.bazel @@ -79,9 +79,13 @@ rust_binary( name = "config", srcs = BIN_SOURCES, aliases = ALIASES, - compile_data = ["templates/guestos_vm_template.xml"], + compile_data = glob(["templates/*"]), crate_name = "config", proc_macro_deps = MACRO_DEPENDENCIES, + # When askama expands the template, it includes the full path of the source template. + # In order to keep the build deterministic, we build without a sandbox so that the path + # is always the same. + tags = ["local"], deps = [ ":config_lib", ] + DEPENDENCIES, @@ -91,9 +95,13 @@ rust_binary( name = "config_dev", srcs = BIN_SOURCES, aliases = ALIASES, - compile_data = ["templates/guestos_vm_template.xml"], + compile_data = glob(["templates/*"]), crate_name = "config", proc_macro_deps = MACRO_DEPENDENCIES, + tags = ["local"], # See the comment in :config + deps = [ + "@crate_index//:askama", + ] + DEPENDENCIES + DEV_DEPENDENCIES, deps = [ ":config_lib_dev", ] + DEPENDENCIES, diff --git a/rs/ic_os/config/src/guest_vm_config.rs b/rs/ic_os/config/src/guest_vm_config.rs index 43cf24be2744..6dddde8496b5 100644 --- a/rs/ic_os/config/src/guest_vm_config.rs +++ b/rs/ic_os/config/src/guest_vm_config.rs @@ -200,7 +200,7 @@ fn generate_vm_config(config: &HostOSConfig, media_path: &Path) -> Result { + struct GuestOSTemplateProps<'a> { pub cpu_domain: &'a str, pub vm_memory: u32, pub nr_of_vcpus: u32, From d700398473085e915204d7f904e1c4db5ab05b85 Mon Sep 17 00:00:00 2001 From: David Frank Date: Sat, 24 May 2025 13:47:51 +0200 Subject: [PATCH 45/47] fix --- rs/ic_os/config/BUILD.bazel | 3 --- 1 file changed, 3 deletions(-) diff --git a/rs/ic_os/config/BUILD.bazel b/rs/ic_os/config/BUILD.bazel index 2e1e26cf7e14..aba97e86bb89 100644 --- a/rs/ic_os/config/BUILD.bazel +++ b/rs/ic_os/config/BUILD.bazel @@ -99,9 +99,6 @@ rust_binary( crate_name = "config", proc_macro_deps = MACRO_DEPENDENCIES, tags = ["local"], # See the comment in :config - deps = [ - "@crate_index//:askama", - ] + DEPENDENCIES + DEV_DEPENDENCIES, deps = [ ":config_lib_dev", ] + DEPENDENCIES, From 56f98c61e2606c67d657d295b7819a9fbfa9e275 Mon Sep 17 00:00:00 2001 From: David Frank Date: Mon, 26 May 2025 11:19:29 +0200 Subject: [PATCH 46/47] Use Bas's workaround instead of local builds --- rs/ic_os/config/BUILD.bazel | 5 ----- rs/ic_os/config/build.rs | 5 ++++- rs/ic_os/config/src/guest_vm_config.rs | 13 ------------- 3 files changed, 4 insertions(+), 19 deletions(-) diff --git a/rs/ic_os/config/BUILD.bazel b/rs/ic_os/config/BUILD.bazel index 4b2455df3ab1..2e8e69c0c3be 100644 --- a/rs/ic_os/config/BUILD.bazel +++ b/rs/ic_os/config/BUILD.bazel @@ -89,10 +89,6 @@ rust_binary( aliases = ALIASES, crate_name = "config", proc_macro_deps = MACRO_DEPENDENCIES, - # When askama expands the template, it includes the full path of the source template. - # In order to keep the build deterministic, we build without a sandbox so that the path - # is always the same. - tags = ["local"], deps = [ ":config_lib", ] + DEPENDENCIES, @@ -104,7 +100,6 @@ rust_binary( aliases = ALIASES, crate_name = "config", proc_macro_deps = MACRO_DEPENDENCIES, - tags = ["local"], # See the comment in :config deps = [ ":config_lib_dev", ] + DEPENDENCIES, diff --git a/rs/ic_os/config/build.rs b/rs/ic_os/config/build.rs index 3909106fda5c..72f98044d382 100644 --- a/rs/ic_os/config/build.rs +++ b/rs/ic_os/config/build.rs @@ -2,6 +2,9 @@ use std::fs::File; use std::io::Write; use std::path::PathBuf; +// TODO: Remove this workaround when the issue is fixed in askama. +// https://github.com/askama-rs/askama/issues/461 +// // Build reproducibility. askama adds a include_bytes! call when it's generating // a template impl so that rustc will recompile the module when the file changes // on disk. See https://github.com/djc/askama/blob/180696053833147a61b3348646a953e7d92ae582/askama_shared/src/generator.rs#L141 @@ -25,7 +28,7 @@ pub struct GuestOSTemplateProps<'a> {{ pub cpu_domain: &'a str, pub vm_memory: u32, pub nr_of_vcpus: u32, - pub mac_address: MacAddr6, + pub mac_address: macaddr::MacAddr6, pub config_media: &'a str, }} "#, diff --git a/rs/ic_os/config/src/guest_vm_config.rs b/rs/ic_os/config/src/guest_vm_config.rs index 130bea87de2f..13c0a189ce81 100644 --- a/rs/ic_os/config/src/guest_vm_config.rs +++ b/rs/ic_os/config/src/guest_vm_config.rs @@ -8,7 +8,6 @@ use config_types::{GuestOSConfig, HostOSConfig, Ipv6Config}; use deterministic_ips::node_type::NodeType; use deterministic_ips::{calculate_deterministic_mac, IpVariant}; use ic_metrics_tool::{Metric, MetricsWriter}; -use macaddr::MacAddr6; use std::fs::{self, File}; use std::io::Write; use std::path::{Path, PathBuf}; @@ -199,18 +198,6 @@ fn make_bootstrap_options( /// Generate the GuestOS VM libvirt XML configuration and return it as String. fn generate_vm_config(config: &HostOSConfig, media_path: &Path) -> Result { - // If you get a compile error pointing at #[derive(Template)], there is likely a syntax error in - // the template. - #[derive(Template)] - #[template(path = "guestos_vm_template.xml")] - struct GuestOSTemplateProps<'a> { - pub cpu_domain: &'a str, - pub vm_memory: u32, - pub nr_of_vcpus: u32, - pub mac_address: MacAddr6, - pub config_media: &'a str, - } - let mac_address = calculate_deterministic_mac( &config.icos_settings.mgmt_mac, config.icos_settings.deployment_environment, From b423837eb5c8b97e0b83d9b4f19f76ac6522f3ee Mon Sep 17 00:00:00 2001 From: David Frank Date: Mon, 26 May 2025 14:22:33 +0200 Subject: [PATCH 47/47] Fix checksums --- Cargo.Bazel.Fuzzing.json.lock | 2 +- Cargo.Bazel.json.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.Bazel.Fuzzing.json.lock b/Cargo.Bazel.Fuzzing.json.lock index 1b9daa283d80..2e2478dc7ace 100644 --- a/Cargo.Bazel.Fuzzing.json.lock +++ b/Cargo.Bazel.Fuzzing.json.lock @@ -1,5 +1,5 @@ { - "checksum": "ada7ff8894ed435c6c2a37398bb9ae44089fd3c8aa2976081d77833ac5d58c83", + "checksum": "92fd3df747c08a83977d8eaa3c5ad4d5b1d8a0c6aaa99a790bc6a8f51f42f85f", "crates": { "abnf 0.12.0": { "name": "abnf", diff --git a/Cargo.Bazel.json.lock b/Cargo.Bazel.json.lock index 53d4d184d6c7..251f87bf4604 100644 --- a/Cargo.Bazel.json.lock +++ b/Cargo.Bazel.json.lock @@ -1,5 +1,5 @@ { - "checksum": "58b0da31d7c9cd4b0c36ed81181d6fc658810ea335a0bd8b4d648dc83860a604", + "checksum": "72bed5a3f2cbd0acb2539c4bcdd25f79d0b44efae028df0a9d15fb4571ae91f7", "crates": { "abnf 0.12.0": { "name": "abnf",