diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index 4f13ba91e1..fdc2345f13 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -141,207 +141,6 @@ jobs: # we do many more runs on the nightly run PROPTEST_CASES: 50 - node-manager-unit-tests: - name: node manager unit tests - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - steps: - - uses: actions/checkout@v4 - - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - - - name: cargo cache registry, index and build - uses: actions/cache@v4.0.2 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-cargo-cache-${{ hashFiles('**/Cargo.lock') }} - - shell: bash - run: cargo test --lib --package sn-node-manager - - # - # Temporarily disable node manager integration tests until they can be made more isolated. - # - # node-manager-e2e-tests: - # name: node manager e2e tests - # runs-on: ${{ matrix.os }} - # strategy: - # fail-fast: false - # matrix: - # include: - # - { os: ubuntu-latest, elevated: sudo env PATH="$PATH" } - # - { os: macos-latest, elevated: sudo } - # - { os: windows-latest } - # 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 e2e -- --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 --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 - # - # # Each daemon test needs its own VM, otherwise they will interfere with each other. - # node-manager-daemon-tests: - # name: node manager daemon tests - # runs-on: ${{ matrix.os }} - # strategy: - # fail-fast: false - # matrix: - # include: - # - { - # os: ubuntu-latest, - # elevated: sudo env PATH="$PATH", - # test: restart_node, - # } - # # todo: enable once url/version has been implemented for Daemon subcmd. - # # - { - # # os: macos-latest, - # # elevated: sudo, - # # test: restart_node, - # # } - # # - { - # # os: windows-latest, - # # test: restart_node, - # # } - # steps: - # - uses: actions/checkout@v4 - # - # - name: Install Rust - # uses: dtolnay/rust-toolchain@stable - # - uses: Swatinem/rust-cache@v2 - # - # - name: run integration test - # 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 daemon ${{ 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 daemon ${{ matrix.test }} -- --nocapture - e2e: if: "!startsWith(github.event.head_commit.message, 'chore(release):')" name: E2E tests diff --git a/.github/workflows/node_man_tests.yml b/.github/workflows/node_man_tests.yml new file mode 100644 index 0000000000..ea49a67372 --- /dev/null +++ b/.github/workflows/node_man_tests.yml @@ -0,0 +1,156 @@ +name: Node Manager Tests + +on: + merge_group: + branches: [main, alpha*, beta*, rc*] + pull_request: + branches: ["*"] + +env: + CARGO_INCREMENTAL: 0 # bookkeeping for incremental builds has overhead, not useful in CI. + WINSW_URL: https://github.com/winsw/winsw/releases/download/v3.0.0-alpha.11/WinSW-x64.exe + +jobs: + node-manager-unit-tests: + name: node manager unit tests + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + + - name: cargo cache registry, index and build + uses: actions/cache@v4.0.2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-cache-${{ hashFiles('**/Cargo.lock') }} + - shell: bash + run: cargo test --lib --package sn-node-manager + + node-manager-user-mode-e2e-tests: + name: user-mode e2e + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - { os: ubuntu-latest } + - { os: macos-latest } + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + + - name: Build binaries + run: cargo build --release --bin safenode --bin faucet + timeout-minutes: 30 + + - name: Start a local network + uses: maidsafe/sn-local-testnet-action@main + with: + action: start + interval: 2000 + node-path: target/release/safenode + faucet-path: target/release/faucet + platform: ${{ matrix.os }} + build: true + + - name: Check SAFE_PEERS was set + shell: bash + run: | + if [[ -z "$SAFE_PEERS" ]]; then + echo "The SAFE_PEERS variable has not been set" + exit 1 + else + echo "SAFE_PEERS has been set to $SAFE_PEERS" + fi + + - shell: bash + run: | + cargo test --package sn-node-manager --release --test e2e -- --nocapture + + - name: Stop the local network and upload logs + if: always() + uses: maidsafe/sn-local-testnet-action@main + with: + action: stop + log_file_prefix: node_man_tests_user_mode + platform: ${{ matrix.os }} + + node-manager-e2e-tests: + name: system-wide e2e + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - { os: ubuntu-latest, elevated: sudo -E env PATH="$PATH" } + - { os: macos-latest, elevated: sudo -E } + - { os: windows-latest } + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + + - name: Build binaries + run: cargo build --release --bin safenode --bin faucet + timeout-minutes: 30 + + - name: Start a local network + uses: maidsafe/sn-local-testnet-action@main + with: + action: start + interval: 2000 + node-path: target/release/safenode + faucet-path: target/release/faucet + platform: ${{ matrix.os }} + build: true + + - name: Check SAFE_PEERS was set + shell: bash + run: | + if [[ -z "$SAFE_PEERS" ]]; then + echo "The SAFE_PEERS variable has not been set" + exit 1 + else + echo "SAFE_PEERS has been set to $SAFE_PEERS" + fi + + - 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 e2e -- --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 --release --package sn-node-manager --test e2e -- --nocapture + + - name: Stop the local network and upload logs + if: always() + uses: maidsafe/sn-local-testnet-action@main + with: + action: stop + log_file_prefix: node_man_tests_system_wide + platform: ${{ matrix.os }} diff --git a/Justfile b/Justfile index ae70d54708..d03faf7784 100644 --- a/Justfile +++ b/Justfile @@ -360,5 +360,17 @@ upload-release-assets-to-s3 bin_name: cd deploy/{{bin_name}} for file in *.zip *.tar.gz; do - aws s3 cp "$file" "s3://$bucket/$file" --acl public-read - done + aws s3 cp "$file" "s3://$bucket/$file" --acl public-read done + +node-man-integration-tests: + #!/usr/bin/env bash + set -e + + cargo build --release --bin safenode --bin faucet --bin safenode-manager + cargo run --release --bin safenode-manager -- local run \ + --node-path target/release/safenode \ + --faucet-path target/release/faucet + peer=$(cargo run --release --bin safenode-manager -- local status \ + --json | jq -r .nodes[-1].listen_addr[0]) + export SAFE_PEERS=$peer + cargo test --release --package sn-node-manager --test e2e -- --nocapture diff --git a/sn_node_manager/README.md b/sn_node_manager/README.md index d71d72db0b..eea17b05c4 100644 --- a/sn_node_manager/README.md +++ b/sn_node_manager/README.md @@ -358,3 +358,17 @@ So by default, 25 node processes have been launched, along with a faucet. The fa The most common scenario for using a local network is for development, but you can also use it to exercise a lot of features locally. For more details, please see the 'Using a Local Network' section of the [main README](https://github.com/maidsafe/safe_network/tree/node-man-readme?tab=readme-ov-file#using-a-local-network). Once you've finished, run `safenode-manager local kill` to dispose the local network. + +## Running Integration Tests + +Sometimes it will be necessary to run the integration tests in a local setup. The problem is, the system-wide tests need root access to run, and they will also create real services, which you don't necessarily want on your development machine. + +The tests can be run from a VM, which is provided by a `Vagrantfile` in the `sn_node_manager` crate directory. The machine is defined to use libvirt rather than Virtualbox, so an installation of that is required, but that is beyond the scope of this document. + +Assuming that you did have an installation of libvirt, you can get the VM by running `vagrant up`. Once the machine is available, run `vagrant ssh` to get a shell session inside it. For running the tests, switch to the root user using `sudo su -`. As part of the provisioning process, the current `safe_network` code is copied to the root user's home directory. To run the tests: +``` +cd safe_network +just node-man-integration-tests +``` + +The target in the `Justfile` will create a local network and the tests will then run against that. diff --git a/sn_node_manager/Vagrantfile b/sn_node_manager/Vagrantfile index deea4d0ff4..f64a3511ee 100644 --- a/sn_node_manager/Vagrantfile +++ b/sn_node_manager/Vagrantfile @@ -3,7 +3,7 @@ Vagrant.configure("2") do |config| config.vm.provider :libvirt do |libvirt| libvirt.memory = 4096 end - config.vm.synced_folder ".", + config.vm.synced_folder "..", "/vagrant", type: "9p", accessmode: "mapped", @@ -23,12 +23,26 @@ Vagrant.configure("2") do |config| echo "source ~/.cargo/env" >> ~/.bashrc SHELL config.vm.provision "shell", inline: <<-SHELL + curl -L -O https://github.com/casey/just/releases/download/1.25.2/just-1.25.2-x86_64-unknown-linux-musl.tar.gz + mkdir just + tar xvf just-1.25.2-x86_64-unknown-linux-musl.tar.gz -C just + rm just-1.25.2-x86_64-unknown-linux-musl.tar.gz + sudo mv just/just /usr/local/bin + rm -rf just + curl -L -O https://static.rust-lang.org/rustup/dist/x86_64-unknown-linux-gnu/rustup-init chmod +x rustup-init ./rustup-init --default-toolchain stable --no-modify-path -y echo "source ~/.cargo/env" >> ~/.bashrc # Copy the binaries to a system-wide location for running tests as the root user sudo cp ~/.cargo/bin/** /usr/local/bin + sudo rsync -av \ + --exclude 'artifacts' \ + --exclude 'deploy' \ + --exclude 'target' \ + --exclude '.git' \ + --exclude '.vagrant' \ + /vagrant/ /root/safe_network SHELL config.vm.provision "shell", privileged: false, inline: <<-SHELL mkdir -p ~/.vim/tmp/ ~/.vim/backup @@ -65,5 +79,10 @@ set viminfo+=! nnoremap j gj nnoremap k gk EOF + cp ~/.vimrc /tmp/.vimrc + SHELL + config.vm.provision "shell", inline: <<-SHELL + mkdir -p /root/.vim/tmp/ /root/.vim/backup + cp /tmp/.vimrc /root/.vimrc SHELL end diff --git a/sn_node_manager/tests/daemon.rs b/sn_node_manager/tests/daemon.rs deleted file mode 100644 index be9ba27068..0000000000 --- a/sn_node_manager/tests/daemon.rs +++ /dev/null @@ -1,161 +0,0 @@ -// 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. - -mod utils; - -use assert_cmd::Command; -use color_eyre::eyre::{bail, eyre, OptionExt, Result}; -use sn_node_manager::DAEMON_DEFAULT_PORT; -use sn_service_management::safenode_manager_proto::{ - safe_node_manager_client::SafeNodeManagerClient, NodeServiceRestartRequest, -}; -use std::{ - env, - io::Read, - net::{Ipv4Addr, SocketAddr}, - process::Stdio, - time::Duration, -}; -use tonic::Request; -use utils::get_service_status; - -/// 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 restart_node() -> Result<()> { - println!("Building safenodemand:"); - let mut cmd = std::process::Command::new("cargo") - .arg("build") - .arg("--release") - .arg("--bin") - .arg("safenodemand") - .stdout(Stdio::piped()) - .spawn()?; - let mut output = String::new(); - cmd.stdout - .as_mut() - .ok_or_else(|| eyre!("Failed to capture stdout"))? - .read_to_string(&mut output)?; - println!("{}", output); - - // It doesn't make any sense, but copying the `safenodemand` binary to another location seemed - // to be necessary before running `daemon add`, because it was just complaining about the file - // not existing. - let mut cwd = env::current_dir()?; - cwd.pop(); - let safenodemand_path = cwd.join("target").join("release").join("safenodemand"); - std::fs::copy(safenodemand_path, "/tmp/safenodemand")?; - - // 1. Preserve the PeerId - println!("Adding 3 safenode services..."); - let node_index_to_restart = 0; - let mut cmd = Command::cargo_bin("safenode-manager")?; - cmd.arg("add") - .arg("--user") - .arg("runner") - .arg("--count") - .arg("3") - .arg("--peer") - .arg("/ip4/127.0.0.1/udp/46091/p2p/12D3KooWAWnbQLxqspWeB3M8HB3ab3CSj6FYzsJxEG9XdVnGNCod") - .assert() - .success(); - - println!("Attempting to start 3 safenode services..."); - let mut cmd = Command::cargo_bin("safenode-manager")?; - cmd.arg("start").assert().success(); - - let status = get_service_status().await?; - let old_pid = status.nodes[node_index_to_restart] - .pid - .ok_or_eyre("PID should be present")?; - assert_eq!(status.nodes.len(), 3); - - println!("Attempting to add the safenodemand service..."); - let mut cmd = Command::cargo_bin("safenode-manager")?; - cmd.arg("daemon") - .arg("add") - .arg("--path") - .arg("/tmp/safenodemand") - .assert() - .success(); - - println!("Attempting to start the safenodemand service..."); - let mut cmd = Command::cargo_bin("safenode-manager")?; - cmd.arg("daemon").arg("start").assert().success(); - - // restart a node - let mut rpc_client = get_safenode_manager_rpc_client(SocketAddr::new( - std::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - DAEMON_DEFAULT_PORT, - )) - .await?; - let node_to_restart = status.nodes[node_index_to_restart] - .peer_id - .ok_or_eyre("We should have PeerId")?; - - let _response = rpc_client - .restart_node_service(Request::new(NodeServiceRestartRequest { - peer_id: node_to_restart.to_bytes(), - delay_millis: 0, - retain_peer_id: true, - })) - .await?; - - // make sure that we still have just 3 services running and pid's are different - let status = get_service_status().await?; - assert_eq!(status.nodes.len(), 3); - let new_pid = status.nodes[node_index_to_restart] - .pid - .ok_or_eyre("PID should be present")?; - assert_ne!(old_pid, new_pid); - - // 2. Start as a fresh node - let _response = rpc_client - .restart_node_service(Request::new(NodeServiceRestartRequest { - peer_id: node_to_restart.to_bytes(), - delay_millis: 0, - retain_peer_id: false, - })) - .await?; - - // make sure that we still have an extra service, and the new one has the same rpc addr as the old one. - let status = get_service_status().await?; - assert_eq!(status.nodes.len(), 4); - let old_rpc_socket_addr = status.nodes[node_index_to_restart].rpc_socket_addr; - let new_rpc_socket_addr = status.nodes[3].rpc_socket_addr; - assert_eq!(old_rpc_socket_addr, new_rpc_socket_addr); - - Ok(()) -} - -// Connect to a RPC socket addr with retry -pub async fn get_safenode_manager_rpc_client( - socket_addr: SocketAddr, -) -> Result> { - // get the new PeerId for the current NodeIndex - let endpoint = format!("https://{socket_addr}"); - let mut attempts = 0; - loop { - if let Ok(rpc_client) = SafeNodeManagerClient::connect(endpoint.clone()).await { - break Ok(rpc_client); - } - attempts += 1; - println!("Could not connect to rpc {endpoint:?}. Attempts: {attempts:?}/10"); - tokio::time::sleep(Duration::from_secs(1)).await; - if attempts >= 10 { - bail!("Failed to connect to {endpoint:?} even after 10 retries"); - } - } -} diff --git a/sn_node_manager/tests/e2e.rs b/sn_node_manager/tests/e2e.rs index 3394aef5fa..fd2973b8aa 100644 --- a/sn_node_manager/tests/e2e.rs +++ b/sn_node_manager/tests/e2e.rs @@ -9,13 +9,21 @@ use assert_cmd::Command; use libp2p_identity::PeerId; use sn_service_management::{ServiceStatus, StatusSummary}; +use std::path::PathBuf; /// 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. /// -/// If you run them on your own dev machine, do so at your own risk! +/// They are assuming the existence of a `safenode` binary produced by the release process, and a +/// running local network, with SAFE_PEERS set to a local node. + +const CI_USER: &str = "runner"; +#[cfg(unix)] +const SAFENODE_BIN_NAME: &str = "safenode"; +#[cfg(windows)] +const SAFENODE_BIN_NAME: &str = "safenode.exe"; /// The default behaviour is for the service to run as the `safe` user, which gets created during /// the process. However, there seems to be some sort of issue with adding user accounts on the GHA @@ -23,18 +31,19 @@ use sn_service_management::{ServiceStatus, StatusSummary}; /// build agent. #[test] fn cross_platform_service_install_and_control() { - // An explicit version of `safenode` will be used to avoid any rate limiting from Github when - // retrieving the latest version number. + let safenode_path = PathBuf::from("..") + .join("target") + .join("release") + .join(SAFENODE_BIN_NAME); let mut cmd = Command::cargo_bin("safenode-manager").unwrap(); cmd.arg("add") + .arg("--local") .arg("--user") - .arg("runner") + .arg(CI_USER) .arg("--count") .arg("3") - .arg("--peer") - .arg("/ip4/127.0.0.1/tcp/46091/p2p/12D3KooWAWnbQLxqspWeB3M8HB3ab3CSj6FYzsJxEG9XdVnGNCod") - .arg("--version") - .arg("0.98.27") + .arg("--path") + .arg(safenode_path.to_string_lossy().to_string()) .assert() .success(); @@ -171,7 +180,14 @@ fn cross_platform_service_install_and_control() { .assert() .success(); let registry = get_status(); - assert_eq!(registry.nodes.len(), 1); + assert_eq!( + 1, + registry + .nodes + .iter() + .filter(|n| n.status != ServiceStatus::Removed) + .count() + ); } fn get_status() -> StatusSummary { diff --git a/sn_node_manager/tests/upgrades.rs b/sn_node_manager/tests/upgrades.rs deleted file mode 100644 index 91267e6e8c..0000000000 --- a/sn_node_manager/tests/upgrades.rs +++ /dev/null @@ -1,257 +0,0 @@ -// 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. - -mod utils; - -use assert_cmd::Command; -use color_eyre::Result; -use sn_releases::{ReleaseType, SafeReleaseRepoActions}; -use utils::get_service_status; - -/// 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! - -const CI_USER: &str = "runner"; - -#[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 status = get_service_status().await?; - assert!( - status.nodes.iter().all(|node| node.version == "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")?; - 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 status = get_service_status().await?; - assert!( - status - .nodes - .iter() - .all(|n| n.version == latest_version.to_string()), - "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 status = get_service_status().await?; - assert!( - status.nodes.iter().all(|n| n.version == version), - "Services were not correctly initialised" - ); - - let mut cmd = Command::cargo_bin("safenode-manager")?; - 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 status = get_service_status().await?; - assert!( - status.nodes.iter().all(|n| n.version == 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 status = get_service_status().await?; - assert!( - status.nodes.iter().all(|n| n.version == initial_version), - "Services were not correctly initialised" - ); - - let mut cmd = Command::cargo_bin("safenode-manager")?; - 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 status = get_service_status().await?; - assert!( - status.nodes.iter().all(|n| n.version == 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 status = get_service_status().await?; - assert!( - status.nodes.iter().all(|n| n.version == initial_version), - "Services were not correctly initialised" - ); - - let mut cmd = Command::cargo_bin("safenode-manager")?; - 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 status = get_service_status().await?; - assert!( - status.nodes.iter().all(|n| n.version == upgrade_version), - "Not all services were updated to the latest version" - ); - - Ok(()) -}