Skip to content
This repository has been archived by the owner on Jan 25, 2024. It is now read-only.

Commit

Permalink
feat: provide upgrade command
Browse files Browse the repository at this point in the history
The upgrade process simple:
* Get the latest version
* Download the new binary
* Stop the running node
* Overwrite the old binary with the new binary
* Start the node

It avoids doing anything if all the nodes are already at the latest version, and it also only
downloads the new binary once, even if there are multiple nodes to upgrade.

Also provided here is a Bash script that can spin up a quick test machine on Digital Ocean. This is
useful for adding nodes that connect to real networks without having to be concerned about NAT on
the local VM. I tried to use the DO provider for Vagrant, but sadly it didn't work correctly; I
think it was out of date with the current API.

The branch for the `remove` command was also rebased on top of this commit. As a result, the
`safenode_path` on the `Node` struct was also set to an optional field to make it consistent with
`data_dir_path` and `log_dir_path`.
  • Loading branch information
jacderida committed Dec 8, 2023
1 parent 94c8698 commit f234446
Show file tree
Hide file tree
Showing 8 changed files with 404 additions and 94 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ color-eyre = "~0.6"
indicatif = { version = "0.17.5", features = ["tokio"] }
libp2p = { version = "0.53", features = [] }
libp2p-identity = { version="0.2.7", features = ["rand"] }
semver = "1.0.20"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
service-manager = "0.5.1"
Expand Down
63 changes: 63 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,69 @@

release_repo := "maidsafe/sn-node-manager"

droplet-testbed:
#!/usr/bin/env bash
DROPLET_NAME="node-manager-testbed"
REGION="lon1"
SIZE="s-1vcpu-1gb"
IMAGE="ubuntu-20-04-x64"
SSH_KEY_ID="30878672"

droplet_ip=$(doctl compute droplet list \
--format Name,PublicIPv4 --no-header | grep "^$DROPLET_NAME " | awk '{ print $2 }')

if [ -z "$droplet_ip" ]; then
droplet_id=$(doctl compute droplet create $DROPLET_NAME \
--region $REGION \
--size $SIZE \
--image $IMAGE \
--ssh-keys $SSH_KEY_ID \
--format ID \
--no-header \
--wait)
if [ -z "$droplet_id" ]; then
echo "Failed to obtain droplet ID"
exit 1
fi

echo "Droplet ID: $droplet_id"
echo "Waiting for droplet IP address..."
droplet_ip=$(doctl compute droplet get $droplet_id --format PublicIPv4 --no-header)
while [ -z "$droplet_ip" ]; do
echo "Still waiting to obtain droplet IP address..."
sleep 5
droplet_ip=$(doctl compute droplet get $droplet_id --format PublicIPv4 --no-header)
done
fi
echo "Droplet IP address: $droplet_ip"

nc -zw1 $droplet_ip 22
exit_code=$?
while [ $exit_code -ne 0 ]; do
echo "Waiting on SSH to become available..."
sleep 5
nc -zw1 $droplet_ip 22
exit_code=$?
done

cargo build --release --target x86_64-unknown-linux-musl
scp -r ./target/x86_64-unknown-linux-musl/release/safenode-manager \
root@$droplet_ip:/root/safenode-manager

kill-testbed:
#!/usr/bin/env bash
DROPLET_NAME="node-manager-testbed"

droplet_id=$(doctl compute droplet list \
--format Name,ID --no-header | grep "^$DROPLET_NAME " | awk '{ print $2 }')

if [ -z "$droplet_ip" ]; then
echo "Deleting droplet with ID $droplet_id"
doctl compute droplet delete $droplet_id
fi

build-release-artifacts arch:
#!/usr/bin/env bash
set -e
Expand Down
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ A peer ID will be assigned to a node after it is started for the first time.

This command must run as the root user on Linux/macOS and the Administrator user on Windows.

Running the command with no arguments will stop every installed node that is not already stopped. The peer ID or service name can be used to start a specific service.
Running the command with no arguments will stop every node that is not already stopped. The peer ID or service name can be used to start a specific service.

If started again, the node's data and peer ID will be retained.

Expand All @@ -83,6 +83,19 @@ This command must run as the root user on Linux/macOS and the Administrator user

Removes the node and its data/log directories. The node must be stopped before running this command.

### Upgrade

- Command: `upgrade`
- Description: Upgrades a `safenode` service to the latest version.
- Options:
- `--peer_id`: Peer ID of the service to stop. Optional.
- `--service_name`: Name of the service to stop. Optional.
- Usage: `safenode-manager upgrade [OPTIONS]`

This command must run as the root user on Linux/macOS and the Administrator user on Windows.

Running the command with no arguments will upgrade every node. The peer ID or service name can be used to upgrade a specific service.

## License

This Safe Network repository is licensed under the General Public License (GPL), version 3 ([LICENSE](LICENSE) http://www.gnu.org/licenses/gpl-3.0.en.html).
Expand Down
74 changes: 12 additions & 62 deletions src/add_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@
// permissions and limitations relating to use of the SAFE Network Software.

use crate::config::create_owned_dir;
use crate::helpers::download_and_extract_safenode;
use crate::node::{Node, NodeRegistry, NodeStatus};
use crate::service::{ServiceConfig, ServiceControl};
use color_eyre::{eyre::eyre, Result};
use colored::Colorize;
use indicatif::{ProgressBar, ProgressStyle};
use libp2p::Multiaddr;
use sn_releases::{get_running_platform, ArchiveType, ReleaseType, SafeReleaseRepositoryInterface};
use sn_releases::SafeReleaseRepositoryInterface;
use std::path::PathBuf;
use std::sync::Arc;

pub struct AddServiceOptions {
pub count: Option<u16>,
Expand All @@ -39,41 +38,8 @@ pub async fn add(
service_control: &dyn ServiceControl,
release_repo: Box<dyn SafeReleaseRepositoryInterface>,
) -> Result<()> {
let pb = Arc::new(ProgressBar::new(0));
pb.set_style(ProgressStyle::default_bar()
.template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})")?
.progress_chars("#>-"));
let pb_clone = pb.clone();
let callback: Box<dyn Fn(u64, u64) + Send + Sync> = Box::new(move |downloaded, total| {
pb_clone.set_length(total);
pb_clone.set_position(downloaded);
});

let version = if let Some(version) = install_options.version {
version
} else {
println!("Retrieving latest version for safenode...");
release_repo
.get_latest_version(&ReleaseType::Safenode)
.await?
};

println!("Downloading safenode version {version}...");

let temp_dir_path = create_temp_dir()?;
let archive_path = release_repo
.download_release_from_s3(
&ReleaseType::Safenode,
&version,
&get_running_platform()?,
&ArchiveType::TarGz,
&temp_dir_path,
&callback,
)
.await?;
pb.finish_with_message("Download complete");
let safenode_download_path =
release_repo.extract_release_archive(&archive_path, &temp_dir_path)?;
let (safenode_download_path, version) =
download_and_extract_safenode(install_options.version, release_repo).await?;
let safenode_file_name = safenode_download_path
.file_name()
.ok_or_else(|| eyre!("Could not get filename from the safenode download path"))?
Expand Down Expand Up @@ -137,7 +103,7 @@ pub async fn add(
peer_id: None,
log_dir_path: Some(service_log_dir_path.clone()),
data_dir_path: Some(service_data_dir_path.clone()),
safenode_path: service_safenode_path,
safenode_path: Some(service_safenode_path),
});

node_number += 1;
Expand All @@ -160,16 +126,6 @@ pub async fn add(
Ok(())
}

/// There is a `tempdir` crate that provides the same kind of functionality, but it was flagged for
/// a security vulnerability.
fn create_temp_dir() -> Result<PathBuf> {
let temp_dir = std::env::temp_dir();
let unique_dir_name = uuid::Uuid::new_v4().to_string();
let new_temp_dir = temp_dir.join(unique_dir_name);
std::fs::create_dir_all(&new_temp_dir)?;
Ok(new_temp_dir)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -232,7 +188,7 @@ mod tests {
node_data_dir.create_dir_all()?;
let node_logs_dir = temp_dir.child("logs");
node_logs_dir.create_dir_all()?;
let safenode_download_path = temp_dir.child("safenode");
let safenode_download_path = temp_dir.child(SAFENODE_FILE_NAME);
safenode_download_path.write_binary(b"fake safenode bin")?;

let mut seq = Sequence::new();
Expand Down Expand Up @@ -358,7 +314,7 @@ mod tests {
node_data_dir.create_dir_all()?;
let node_logs_dir = temp_dir.child("logs");
node_logs_dir.create_dir_all()?;
let safenode_download_path = temp_dir.child("safenode");
let safenode_download_path = temp_dir.child(SAFENODE_FILE_NAME);
safenode_download_path.write_binary(b"fake safenode bin")?;

let mut seq = Sequence::new();
Expand Down Expand Up @@ -573,7 +529,7 @@ mod tests {
node_data_dir.create_dir_all()?;
let node_logs_dir = temp_dir.child("logs");
node_logs_dir.create_dir_all()?;
let safenode_download_path = temp_dir.child("safenode");
let safenode_download_path = temp_dir.child(SAFENODE_FILE_NAME);
safenode_download_path.write_binary(b"fake safenode bin")?;

let mut seq = Sequence::new();
Expand Down Expand Up @@ -693,25 +649,19 @@ mod tests {
status: NodeStatus::Added,
pid: None,
peer_id: None,
<<<<<<< HEAD
log_dir_path: Some(PathBuf::from("/var/log/safenode/safenode1")),
data_dir_path: Some(PathBuf::from("/var/safenode-manager/services/safenode1")),
||||||| parent of 1a686eb (feat: each service instance to use its own binary)
log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
=======
log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
safenode_path: PathBuf::from("/var/safenode-manager/services/safenode1/safenode"),
>>>>>>> 1a686eb (feat: each service instance to use its own binary)
safenode_path: Some(PathBuf::from(
"/var/safenode-manager/services/safenode1/safenode",
)),
}],
};
let temp_dir = assert_fs::TempDir::new()?;
let node_data_dir = temp_dir.child("safenode1");
node_data_dir.create_dir_all()?;
let node_logs_dir = temp_dir.child("logs");
node_logs_dir.create_dir_all()?;
let safenode_download_path = temp_dir.child("safenode");
let safenode_download_path = temp_dir.child(SAFENODE_FILE_NAME);
safenode_download_path.write_binary(b"fake safenode bin")?;

let mut seq = Sequence::new();
Expand Down
Loading

0 comments on commit f234446

Please sign in to comment.