From e846314159a8613d9ecb4f8b3ed45d34d0be53a9 Mon Sep 17 00:00:00 2001 From: Chris O'Neil Date: Fri, 9 Feb 2024 21:11:16 +0000 Subject: [PATCH] feat: force and upgrade by url or version Three new arguments are added to the `upgrade` command: `--force`, `--url` and `--version`. The `--url` and `--version` arguments provide two different sources for the upgrade, as opposed to just upgrading to the latest version. With `--url`, a custom binary can be provided, which will be used for the backwards compatibility test. The `--version` flag enables upgrading to a specific version rather than the latest. Both of these can be used with the `--force` flag to downgrade to an arbitrary version or to accept an upgrade from a binary with the same version, the latter of which will again be used in the backwards compatibility test. Integration tests provide coverage of these new features. Each of the tests really needs to run on its own machine, otherwise they interfere with each other, and tests can't make assumptions about how many services there are. So we add twelve additional jobs to the merge workflow here, which is for four tests on three operating systems. However, these tests should run pretty quickly. The `stop` command was modified such that it will no longer return an error if the service is in the `ADDED` state, i.e., it has not been started before. This enables us to test the upgrade process without initially starting the service, which could introduce complications. The `get_safenode_port` function was also changed to return an `Option` rather than a `Result` for the same reason. --- .github/workflows/merge.yml | 54 +++++- Cargo.lock | 1 + sn_node_manager/Cargo.toml | 1 + sn_node_manager/src/control.rs | 74 +++++--- sn_node_manager/src/main.rs | 150 +++++++++++----- sn_node_manager/tests/upgrades.rs | 290 ++++++++++++++++++++++++++++++ sn_protocol/src/node_registry.rs | 8 +- 7 files changed, 504 insertions(+), 74 deletions(-) create mode 100644 sn_node_manager/tests/upgrades.rs diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index db0f9d6f3a..8747d0e04e 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -146,8 +146,8 @@ jobs: - shell: bash run: cargo test --release --bin safenode-manager - node-manager-integration-tests: - name: node manager integration tests + node-manager-e2e-tests: + name: node manager e2e tests runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -169,7 +169,7 @@ jobs: ${{ matrix.elevated }} rustup default stable ${{ matrix.elevated }} cargo test --package sn-node-manager --release --test e2e -- --nocapture - # A simple test seemed to confirm that the Powershell step runs as admin by default. + # Powershell step runs as admin by default. - name: run integration test in powershell if: matrix.os == 'windows-latest' shell: pwsh @@ -182,6 +182,54 @@ jobs: cargo test --release --package sn-node-manager --test e2e -- --nocapture + # Each upgrade test needs its own VM, otherwise they will interfere with each other. + node-manager-upgrade-tests: + name: node manager upgrade tests + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - { os: ubuntu-latest, elevated: sudo env PATH="$PATH", test: upgrade_to_latest_version } + - { os: ubuntu-latest, elevated: sudo env PATH="$PATH", test: force_upgrade_when_two_binaries_have_the_same_version } + - { os: ubuntu-latest, elevated: sudo env PATH="$PATH", test: force_downgrade_to_a_previous_version } + - { os: ubuntu-latest, elevated: sudo env PATH="$PATH", test: upgrade_from_older_version_to_specific_version } + - { os: macos-latest, elevated: sudo, test: upgrade_to_latest_version } + - { os: macos-latest, elevated: sudo, test: force_upgrade_when_two_binaries_have_the_same_version } + - { os: macos-latest, elevated: sudo, test: force_downgrade_to_a_previous_version } + - { os: macos-latest, elevated: sudo, test: upgrade_from_older_version_to_specific_version } + - { os: windows-latest, test: upgrade_to_latest_version } + - { os: windows-latest, test: force_upgrade_when_two_binaries_have_the_same_version } + - { os: windows-latest, test: force_downgrade_to_a_previous_version } + - { os: windows-latest, test: upgrade_from_older_version_to_specific_version } + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + + - shell: bash + if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' + run: | + ${{ matrix.elevated }} rustup default stable + ${{ matrix.elevated }} cargo test --package sn-node-manager --release \ + --test upgrades ${{ matrix.test }} -- --nocapture + + # Powershell step runs as admin by default. + - name: run integration test in powershell + if: matrix.os == 'windows-latest' + shell: pwsh + run: | + curl -L -o WinSW.exe $env:WINSW_URL + + New-Item -ItemType Directory -Force -Path "$env:GITHUB_WORKSPACE\bin" + Move-Item -Path WinSW.exe -Destination "$env:GITHUB_WORKSPACE\bin" + $env:PATH += ";$env:GITHUB_WORKSPACE\bin" + + cargo test --package sn-node-manager --release ` + --test upgrades ${{ matrix.test }} -- --nocapture + e2e: if: "!startsWith(github.event.head_commit.message, 'chore(release):')" name: E2E tests diff --git a/Cargo.lock b/Cargo.lock index 7c25756ce3..3ae10c71bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4885,6 +4885,7 @@ dependencies = [ "mockall", "nix 0.27.1", "predicates 2.1.5", + "reqwest", "semver", "serde", "serde_json", diff --git a/sn_node_manager/Cargo.toml b/sn_node_manager/Cargo.toml index 0da6e86c0f..1c359f624a 100644 --- a/sn_node_manager/Cargo.toml +++ b/sn_node_manager/Cargo.toml @@ -55,4 +55,5 @@ assert_fs = "1.0.13" assert_matches = "1.5.0" async-trait = "0.1" mockall = "0.11.3" +reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls"] } predicates = "2.0" diff --git a/sn_node_manager/src/control.rs b/sn_node_manager/src/control.rs index 69b8e06f1d..5806e07f5c 100644 --- a/sn_node_manager/src/control.rs +++ b/sn_node_manager/src/control.rs @@ -19,6 +19,7 @@ use sn_protocol::node_registry::{Node, NodeRegistry, NodeStatus}; use std::path::PathBuf; pub enum UpgradeResult { + Forced(String, String), NotRequired, Upgraded(String, String), Error(String), @@ -77,10 +78,13 @@ pub async fn start( pub async fn stop(node: &mut Node, service_control: &dyn ServiceControl) -> Result<()> { match node.status { - NodeStatus::Added => Err(eyre!( - "Service {} has not been started since it was installed", - node.service_name - )), + NodeStatus::Added => { + println!( + "Service {} has not been started since it was installed", + node.service_name + ); + Ok(()) + } NodeStatus::Removed => Err(eyre!("Service {} has been removed", node.service_name)), NodeStatus::Running => { let pid = node.pid.unwrap(); @@ -278,46 +282,63 @@ pub async fn remove( Ok(()) } +pub struct UpgradeOptions { + pub bootstrap_peers: Vec, + pub env_variables: Option>, + pub force: bool, + pub start_node: bool, + pub target_safenode_path: PathBuf, + pub target_version: Version, +} + pub async fn upgrade( + options: UpgradeOptions, node: &mut Node, - bootstrap_peers: &[Multiaddr], - env_variables: &Option>, - upgraded_safenode_path: &PathBuf, - latest_version: &Version, service_control: &dyn ServiceControl, rpc_client: &dyn RpcActions, ) -> Result { let current_version = Version::parse(&node.version)?; - if current_version == *latest_version { + if !options.force + && (current_version == options.target_version || options.target_version < current_version) + { return Ok(UpgradeResult::NotRequired); } stop(node, service_control).await?; - std::fs::copy(upgraded_safenode_path, &node.safenode_path)?; + std::fs::copy(options.target_safenode_path, &node.safenode_path)?; // Install the service again to make sure we re-use the same node port. - // Initially the node_port is generated on random. We obtain this port from the Node::listen_addr field to be re-used. + // Windows requires that the service be uninstalled first. + service_control.uninstall(&node.service_name.clone())?; service_control.install(ServiceConfig { local: node.local, data_dir_path: node.data_dir_path.clone(), genesis: node.genesis, name: node.service_name.clone(), - node_port: Some(node.get_safenode_port()?), - bootstrap_peers: bootstrap_peers.to_owned(), + node_port: node.get_safenode_port(), + bootstrap_peers: options.bootstrap_peers, rpc_socket_addr: node.rpc_socket_addr, log_dir_path: node.log_dir_path.clone(), safenode_path: node.safenode_path.clone(), service_user: node.user.clone(), - env_variables: env_variables.clone(), + env_variables: options.env_variables.clone(), })?; - start(node, service_control, rpc_client, VerbosityLevel::Normal).await?; - node.version = latest_version.to_string(); + if options.start_node { + start(node, service_control, rpc_client, VerbosityLevel::Normal).await?; + } + node.version = options.target_version.to_string(); - Ok(UpgradeResult::Upgraded( - current_version.to_string(), - latest_version.to_string(), - )) + match options.force { + true => Ok(UpgradeResult::Forced( + current_version.to_string(), + options.target_version.to_string(), + )), + false => Ok(UpgradeResult::Upgraded( + current_version.to_string(), + options.target_version.to_string(), + )), + } } fn format_status(status: &NodeStatus) -> String { @@ -681,7 +702,7 @@ mod tests { } #[tokio::test] - async fn stop_should_return_error_for_attempt_to_stop_installed_service() -> Result<()> { + async fn stop_should_not_return_error_for_attempt_to_stop_installed_service() -> Result<()> { let mock_service_control = MockServiceControl::new(); let mut node = Node { @@ -705,16 +726,15 @@ mod tests { let result = stop(&mut node, &mock_service_control).await; match result { - Ok(()) => panic!("This test should result in an error"), - Err(e) => { - assert_eq!( - "Service safenode1 has not been started since it was installed", - e.to_string() + Ok(()) => Ok(()), + Err(_) => { + panic!( + "The stop command should be idempotent and do nothing for a stopped service" ); } } - Ok(()) + // Ok(()) } #[tokio::test] diff --git a/sn_node_manager/src/main.rs b/sn_node_manager/src/main.rs index 3e274a66a1..438dd26238 100644 --- a/sn_node_manager/src/main.rs +++ b/sn_node_manager/src/main.rs @@ -16,7 +16,7 @@ mod service; use crate::{ add_service::{add, AddServiceOptions}, config::*, - control::{remove, start, status, stop, upgrade, UpgradeResult}, + control::{remove, start, status, stop, upgrade, UpgradeOptions, UpgradeResult}, helpers::download_and_extract_release, local::{kill_network, run_faucet, run_network, LocalNetworkOptions}, service::{NodeServiceManager, ServiceControl}, @@ -329,13 +329,28 @@ pub enum SubCmd { #[clap(long, conflicts_with = "peer_id")] service_name: Option, }, - /// Upgrade a safenode service. + /// Upgrade safenode services. + /// + /// The running node will be stopped, its binary will be replaced, then it will be started + /// again. /// /// If no peer ID(s) or service name(s) are supplied, all services will be upgraded. /// /// This command must run as the root/administrative user. #[clap(name = "upgrade")] Upgrade { + /// Set this flag to upgrade the nodes without automatically starting them. + /// + /// Can be useful for testing scenarios. + #[clap(long)] + do_not_start: bool, + /// Set this flag to force the upgrade command to replace binaries without comparing any + /// version numbers. + /// + /// This may be required in a case where we want to 'downgrade' in case an upgrade caused a + /// problem, or for testing purposes. + #[clap(long)] + force: bool, /// The peer ID of the service to upgrade #[clap(long)] peer_id: Option, @@ -350,6 +365,16 @@ pub enum SubCmd { /// Example: --env SN_LOG=all,RUST_LOG=libp2p=debug #[clap(name = "env", long, use_value_delimiter = true, value_parser = parse_environment_variables)] env_variables: Option>, + /// Provide a binary to upgrade to, using a URL. + /// + /// The binary must be inside a zip or gzipped tar archive. + /// + /// This can be useful for testing scenarios. + #[clap(long, conflicts_with = "version")] + url: Option, + /// Upgrade to a specific version rather than the latest version. + #[clap(long)] + version: Option, }, } @@ -796,45 +821,72 @@ async fn main() -> Result<()> { Ok(()) } SubCmd::Upgrade { + do_not_start, + force, peer_id, service_name, env_variables: provided_env_variable, + url, + version, } => { if !is_running_as_root() { return Err(eyre!("The upgrade command must run as the root user")); } - println!("================================================="); - println!(" Upgrade Safenode Services "); - println!("================================================="); + if verbosity != VerbosityLevel::Minimal { + println!("================================================="); + println!(" Upgrade Safenode Services "); + println!("================================================="); + } - println!("Retrieving latest version of safenode..."); let release_repo = ::default_config(); - let latest_version = release_repo - .get_latest_version(&ReleaseType::Safenode) - .await - .map(|v| Version::parse(&v).unwrap())?; - println!("Latest version is {latest_version}"); + let (upgrade_bin_path, target_version) = if let Some(version) = version { + let (upgrade_bin_path, version) = download_and_extract_release( + ReleaseType::Safenode, + None, + Some(version), + &*release_repo, + ) + .await?; + (upgrade_bin_path, Version::parse(&version)?) + } else if let Some(url) = url { + let (upgrade_bin_path, version) = download_and_extract_release( + ReleaseType::Safenode, + Some(url), + None, + &*release_repo, + ) + .await?; + (upgrade_bin_path, Version::parse(&version)?) + } else { + println!("Retrieving latest version of safenode..."); + let latest_version = release_repo + .get_latest_version(&ReleaseType::Safenode) + .await + .map(|v| Version::parse(&v).unwrap())?; + println!("Latest version is {latest_version}"); + let (upgrade_bin_path, _) = download_and_extract_release( + ReleaseType::Safenode, + None, + Some(latest_version.to_string()), + &*release_repo, + ) + .await?; + (upgrade_bin_path, latest_version) + }; let mut node_registry = NodeRegistry::load(&get_node_registry_path()?)?; - let any_nodes_need_upgraded = node_registry.nodes.iter().any(|n| { - let current_version = Version::parse(&n.version).unwrap(); - current_version < latest_version - }); - - if !any_nodes_need_upgraded { - println!("{} All nodes are at the latest version", "✓".green()); - return Ok(()); + if !force { + let any_nodes_need_upgraded = node_registry.nodes.iter().any(|n| { + let current_version = Version::parse(&n.version).unwrap(); + current_version < target_version + }); + if !any_nodes_need_upgraded { + println!("{} All nodes are at the latest version", "✓".green()); + return Ok(()); + } } - let (safenode_download_path, _) = download_and_extract_release( - ReleaseType::Safenode, - None, - Some(latest_version.to_string()), - &*release_repo, - ) - .await?; - let mut upgrade_summary = Vec::new(); if let Some(ref name) = service_name { @@ -852,11 +904,15 @@ async fn main() -> Result<()> { &node_registry.environment_variables }; let result = upgrade( + UpgradeOptions { + bootstrap_peers: node_registry.bootstrap_peers.clone(), + env_variables: env_variables.clone(), + force, + start_node: !do_not_start, + target_safenode_path: upgrade_bin_path.clone(), + target_version: target_version.clone(), + }, node, - &node_registry.bootstrap_peers, - env_variables, - &safenode_download_path, - &latest_version, &NodeServiceManager {}, &rpc_client, ) @@ -894,11 +950,15 @@ async fn main() -> Result<()> { &node_registry.environment_variables }; let result = upgrade( + UpgradeOptions { + bootstrap_peers: node_registry.bootstrap_peers.clone(), + env_variables: env_variables.clone(), + force, + start_node: !do_not_start, + target_safenode_path: upgrade_bin_path.clone(), + target_version: target_version.clone(), + }, node, - &node_registry.bootstrap_peers, - env_variables, - &safenode_download_path, - &latest_version, &NodeServiceManager {}, &rpc_client, ) @@ -925,11 +985,15 @@ async fn main() -> Result<()> { &node_registry.environment_variables }; let result = upgrade( + UpgradeOptions { + bootstrap_peers: node_registry.bootstrap_peers.clone(), + env_variables: env_variables.clone(), + force, + start_node: !do_not_start, + target_safenode_path: upgrade_bin_path.clone(), + target_version: target_version.clone(), + }, node, - &node_registry.bootstrap_peers, - env_variables, - &safenode_download_path, - &latest_version, &NodeServiceManager {}, &rpc_client, ) @@ -955,7 +1019,7 @@ async fn main() -> Result<()> { for (service_name, upgrade_result) in upgrade_summary { match upgrade_result { UpgradeResult::NotRequired => { - println!("- {service_name} was at the latest version"); + println!("- {service_name} did not require an upgrade"); } UpgradeResult::Upgraded(previous_version, new_version) => { println!( @@ -963,6 +1027,12 @@ async fn main() -> Result<()> { "✓".green() ); } + UpgradeResult::Forced(previous_version, target_version) => { + println!( + "{} Forced {service_name} version change from {previous_version} to {target_version}.", + "✓".green() + ); + } UpgradeResult::Error(msg) => { println!("{} {service_name} was not upgraded: {}", "✕".red(), msg); } diff --git a/sn_node_manager/tests/upgrades.rs b/sn_node_manager/tests/upgrades.rs new file mode 100644 index 0000000000..42536639a2 --- /dev/null +++ b/sn_node_manager/tests/upgrades.rs @@ -0,0 +1,290 @@ +// Copyright (C) 2024 MaidSafe.net limited. +// +// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3. +// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed +// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. Please review the Licences for the specific language governing +// permissions and limitations relating to use of the SAFE Network Software. + +use assert_cmd::Command; +use color_eyre::{eyre::eyre, Result}; +use serde_json::Value; +use sn_releases::{ReleaseType, SafeReleaseRepositoryInterface}; + +const CI_USER: &str = "runner"; + +/// These tests need to execute as the root user. +/// +/// They are intended to run on a CI-based environment with a fresh build agent because they will +/// create real services and user accounts, and will not attempt to clean themselves up. +/// +/// Each test also needs to run in isolation, otherwise they will interfere with each other. +/// +/// If you run them on your own dev machine, do so at your own risk! + +#[tokio::test] +async fn upgrade_to_latest_version() -> Result<()> { + let mut cmd = Command::cargo_bin("safenode-manager")?; + cmd.arg("add") + .arg("--user") + .arg(CI_USER) + .arg("--count") + .arg("3") + .arg("--peer") + .arg("/ip4/127.0.0.1/udp/46091/p2p/12D3KooWAWnbQLxqspWeB3M8HB3ab3CSj6FYzsJxEG9XdVnGNCod") + .arg("--version") + .arg("0.98.27") + .assert() + .success(); + + let services = get_service_status().await?; + assert!( + services + .iter() + .all(|service| service["version"].as_str() == Some("0.98.27")), + "Services were not correctly initialised" + ); + + let release_repo = ::default_config(); + let latest_version = release_repo + .get_latest_version(&ReleaseType::Safenode) + .await?; + let mut cmd = Command::cargo_bin("safenode-manager").unwrap(); + let output = cmd + .arg("upgrade") + .arg("--do-not-start") + .assert() + .success() + .get_output() + .stdout + .clone(); + + let output = std::str::from_utf8(&output)?; + println!("upgrade command output:"); + println!("{output}"); + + let services = get_service_status().await?; + assert!( + services + .iter() + .all(|service| service["version"].as_str() == Some(&latest_version)), + "Not all services were updated to the latest version" + ); + + Ok(()) +} + +/// This scenario may seem pointless, but forcing a change for a binary with the same version will +/// be required for the backwards compatibility test; the binary will be different, it will just +/// have the same version. +#[tokio::test] +async fn force_upgrade_when_two_binaries_have_the_same_version() -> Result<()> { + let version = "0.98.27"; + + let mut cmd = Command::cargo_bin("safenode-manager")?; + cmd.arg("add") + .arg("--user") + .arg(CI_USER) + .arg("--count") + .arg("3") + .arg("--peer") + .arg("/ip4/127.0.0.1/udp/46091/p2p/12D3KooWAWnbQLxqspWeB3M8HB3ab3CSj6FYzsJxEG9XdVnGNCod") + .arg("--version") + .arg(version) + .assert() + .success(); + + let services = get_service_status().await?; + assert!( + services + .iter() + .all(|service| service["version"].as_str() == Some(version)), + "Services were not correctly initialised" + ); + + let mut cmd = Command::cargo_bin("safenode-manager").unwrap(); + let output = cmd + .arg("upgrade") + .arg("--do-not-start") + .arg("--force") + .arg("--version") + .arg(version) + .assert() + .success() + .get_output() + .stdout + .clone(); + + let output = std::str::from_utf8(&output)?; + println!("upgrade command output:"); + println!("{output}"); + + assert!(output.contains(&format!( + "Forced safenode1 version change from {version} to {version}" + ))); + assert!(output.contains(&format!( + "Forced safenode2 version change from {version} to {version}" + ))); + assert!(output.contains(&format!( + "Forced safenode3 version change from {version} to {version}" + ))); + + let services = get_service_status().await?; + assert!( + services + .iter() + .all(|service| service["version"].as_str() == Some(version)), + "Not all services were updated to the latest version" + ); + + Ok(()) +} + +#[tokio::test] +async fn force_downgrade_to_a_previous_version() -> Result<()> { + let initial_version = "0.104.15"; + let downgrade_version = "0.104.10"; + + let mut cmd = Command::cargo_bin("safenode-manager")?; + cmd.arg("add") + .arg("--user") + .arg(CI_USER) + .arg("--count") + .arg("3") + .arg("--peer") + .arg("/ip4/127.0.0.1/udp/46091/p2p/12D3KooWAWnbQLxqspWeB3M8HB3ab3CSj6FYzsJxEG9XdVnGNCod") + .arg("--version") + .arg(initial_version) + .assert() + .success(); + + let services = get_service_status().await?; + assert!( + services + .iter() + .all(|service| service["version"].as_str() == Some(initial_version)), + "Services were not correctly initialised" + ); + + let mut cmd = Command::cargo_bin("safenode-manager").unwrap(); + let output = cmd + .arg("upgrade") + .arg("--do-not-start") + .arg("--force") + .arg("--version") + .arg(downgrade_version) + .assert() + .success() + .get_output() + .stdout + .clone(); + + let output = std::str::from_utf8(&output)?; + println!("upgrade command output:"); + println!("{output}"); + + assert!(output.contains(&format!( + "Forced safenode1 version change from {initial_version} to {downgrade_version}" + ))); + assert!(output.contains(&format!( + "Forced safenode2 version change from {initial_version} to {downgrade_version}" + ))); + assert!(output.contains(&format!( + "Forced safenode3 version change from {initial_version} to {downgrade_version}" + ))); + + let services = get_service_status().await?; + assert!( + services + .iter() + .all(|service| service["version"].as_str() == Some(downgrade_version)), + "Not all services were updated to the latest version" + ); + + Ok(()) +} + +#[tokio::test] +async fn upgrade_from_older_version_to_specific_version() -> Result<()> { + let initial_version = "0.104.10"; + let upgrade_version = "0.104.14"; + + let mut cmd = Command::cargo_bin("safenode-manager")?; + cmd.arg("add") + .arg("--user") + .arg(CI_USER) + .arg("--count") + .arg("3") + .arg("--peer") + .arg("/ip4/127.0.0.1/udp/46091/p2p/12D3KooWAWnbQLxqspWeB3M8HB3ab3CSj6FYzsJxEG9XdVnGNCod") + .arg("--version") + .arg(initial_version) + .assert() + .success(); + + let services = get_service_status().await?; + assert!( + services + .iter() + .all(|service| service["version"].as_str() == Some(initial_version)), + "Services were not correctly initialised" + ); + + let mut cmd = Command::cargo_bin("safenode-manager").unwrap(); + let output = cmd + .arg("upgrade") + .arg("--do-not-start") + .arg("--version") + .arg(upgrade_version) + .assert() + .success() + .get_output() + .stdout + .clone(); + + let output = std::str::from_utf8(&output)?; + println!("upgrade command output:"); + println!("{output}"); + + assert!(output.contains(&format!( + "safenode1 upgraded from {initial_version} to {upgrade_version}" + ))); + assert!(output.contains(&format!( + "safenode2 upgraded from {initial_version} to {upgrade_version}" + ))); + assert!(output.contains(&format!( + "safenode3 upgraded from {initial_version} to {upgrade_version}" + ))); + + let services = get_service_status().await?; + assert!( + services + .iter() + .all(|service| service["version"].as_str() == Some(upgrade_version)), + "Not all services were updated to the latest version" + ); + + Ok(()) +} + +async fn get_service_status() -> Result> { + let mut cmd = Command::cargo_bin("safenode-manager")?; + let output = cmd + .arg("status") + .arg("--json") + .assert() + .success() + .get_output() + .stdout + .clone(); + + let output = std::str::from_utf8(&output)?; + println!("status command output:"); + println!("{output}"); + + let services: Vec = match serde_json::from_str(output) { + Ok(json) => json, + Err(e) => return Err(eyre!("Failed to parse JSON output: {:?}", e)), + }; + Ok(services) +} diff --git a/sn_protocol/src/node_registry.rs b/sn_protocol/src/node_registry.rs index bce6d37373..e342719fbc 100644 --- a/sn_protocol/src/node_registry.rs +++ b/sn_protocol/src/node_registry.rs @@ -6,7 +6,7 @@ // KIND, either express or implied. Please review the Licences for the specific language governing // permissions and limitations relating to use of the SAFE Network Software. -use crate::{error::Result as ProtocolResult, Error}; +use crate::Error; use color_eyre::Result; use libp2p::{multiaddr::Protocol, Multiaddr, PeerId}; use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer}; @@ -115,18 +115,18 @@ pub struct Node { impl Node { /// Returns the UDP port from our node's listen address. - pub fn get_safenode_port(&self) -> ProtocolResult { + pub fn get_safenode_port(&self) -> Option { // assuming the listening addr contains /ip4/127.0.0.1/udp/56215/quic-v1/p2p/ if let Some(multi_addrs) = &self.listen_addr { for addr in multi_addrs { for protocol in addr.iter() { if let Protocol::Udp(port) = protocol { - return Ok(port); + return Some(port); } } } } - Err(Error::CouldNotObtainPortFromMultiAddr) + None } }