current upgrade info logic

This commit is contained in:
Jędrzej Stuczyński
2023-11-17 13:05:50 +00:00
parent 6b133750d4
commit 9b5714b897
7 changed files with 200 additions and 31 deletions
+29 -8
View File
@@ -2,12 +2,14 @@
// SPDX-License-Identifier: Apache-2.0
use crate::cli::helpers::{copy_binary, daemon_home, use_logs};
use crate::config::{default_config_filepath, Config, BIN_DIR, GENESIS_DIR};
use crate::config::{
default_config_filepath, Config, BIN_DIR, CURRENT_VERSION_FILENAME, GENESIS_DIR,
};
use crate::daemon::Daemon;
use crate::env::Env;
use crate::error::NymvisorError;
use crate::helpers::init_path;
use crate::upgrades::types::{UpgradeInfo, UpgradePlan};
use crate::upgrades::types::{CurrentVersionInfo, UpgradeInfo, UpgradePlan};
use nym_bin_common::build_information::BinaryBuildInformationOwned;
use nym_bin_common::logging::setup_tracing_logger;
use nym_bin_common::output_format::OutputFormat;
@@ -15,7 +17,7 @@ use std::fs;
use std::path::{Path, PathBuf};
use std::time::Duration;
use time::OffsetDateTime;
use tracing::{debug, info, trace, warn};
use tracing::{debug, info, warn};
use url::Url;
#[derive(clap::Args, Debug)]
@@ -223,10 +225,27 @@ fn init_paths(config: &Config) -> Result<(), NymvisorError> {
Ok(())
}
fn setup_daemon_current_version(
config: &Config,
daemon_info: &BinaryBuildInformationOwned,
) -> Result<(), NymvisorError> {
info!("setting up initial {}", CURRENT_VERSION_FILENAME);
let path = config.current_daemon_version_filepath();
let initial = CurrentVersionInfo {
name: GENESIS_DIR.to_string(),
version: daemon_info.build_version.clone(),
upgrade_time: OffsetDateTime::now_utc(),
binary_details: daemon_info.clone(),
};
initial.save(path)
}
fn setup_genesis(
config: &Config,
source: &Path,
daemon_info: BinaryBuildInformationOwned,
daemon_info: &BinaryBuildInformationOwned,
) -> Result<(), NymvisorError> {
info!("setting up the genesis binary");
let target = config.genesis_daemon_binary();
@@ -234,7 +253,7 @@ fn setup_genesis(
if target.exists() {
// if there already exists a binary at the genesis location, see if it's the same one
let existing_bin_info = Daemon::new(target).get_build_information()?;
return if existing_bin_info != daemon_info {
return if &existing_bin_info != daemon_info {
Err(NymvisorError::DuplicateDaemonGenesisBinary {
daemon_name: config.daemon.name.clone(),
existing_info: Box::new(existing_bin_info),
@@ -286,7 +305,7 @@ fn create_current_symlink(config: &Config) -> Result<(), NymvisorError> {
fn generate_and_save_genesis_upgrade_info(
config: &Config,
genesis_info: BinaryBuildInformationOwned,
genesis_info: &BinaryBuildInformationOwned,
) -> Result<UpgradeInfo, NymvisorError> {
info!("setting up the genesis upgrade-info.json");
@@ -298,7 +317,7 @@ fn generate_and_save_genesis_upgrade_info(
version: genesis_info.build_version.clone(),
platforms: Default::default(),
upgrade_time: OffsetDateTime::UNIX_EPOCH,
binary_details: Some(genesis_info),
binary_details: Some(genesis_info.clone()),
};
let save_path = config.upgrade_info_filepath(&info.name);
@@ -384,6 +403,7 @@ fn save_config(config: &Config, env: &Env) -> Result<(), NymvisorError> {
/// - creating `<NYMVISOR_UPGRADE_DATA_DIRECTORY>/<DAEMON_NAME>/upgrades` folder if it doesn't yet exist
/// - copying the provided executable to `<NYMVISOR_UPGRADE_DATA_DIRECTORY>/<DAEMON_NAME>/genesis/bin/<DAEMON_NAME>`
/// - generating initial `<NYMVISOR_UPGRADE_DATA_DIRECTORY>/<DAEMON_NAME>/genesis/upgrade-info.json` file
/// - generating initial `<DAEMON_HOME>/nymvisor/current-version-info.json` file
/// - creating a `<NYMVISOR_UPGRADE_DATA_DIRECTORY>/<DAEMON_NAME>/current` symlink pointing to `<NYMVISOR_UPGRADE_DATA_DIRECTORY>/<DAEMON_NAME>/genesis`
/// - saving nymvisor's config file to `<NYMVISOR_CONFIG_PATH>` and creating the full directory structure.
///
@@ -406,7 +426,8 @@ pub(crate) fn execute(args: Args) -> Result<(), NymvisorError> {
let config = try_build_config(&args, &env, &daemon_info)?;
init_paths(&config)?;
setup_genesis(&config, &args.daemon_binary, daemon_info)?;
setup_genesis(&config, &args.daemon_binary, &daemon_info)?;
setup_daemon_current_version(&config, &daemon_info)?;
create_current_symlink(&config)?;
save_config(&config, &env)?;
-6
View File
@@ -56,12 +56,6 @@ impl Daemon {
}
}
#[must_use]
pub(crate) fn with_kill_timeout(mut self, kill_timeout: Duration) -> Self {
self.kill_timeout = kill_timeout;
self
}
#[instrument(skip(self), fields(self.executable_path = ?self.executable_path))]
pub(crate) fn get_build_information(
&self,
+46 -1
View File
@@ -1,7 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::upgrades::types::DigestAlgorithm;
use crate::upgrades::types::{CurrentVersionInfo, DigestAlgorithm, UpgradeInfo};
use async_file_watcher::NotifyError;
use nix::errno::Errno;
use nix::sys::signal::Signal;
@@ -101,6 +101,51 @@ pub(crate) enum NymvisorError {
source: io::Error,
},
#[error(
"failed to load current version information using path '{}'. detailed message: {source}", path.display()
)]
CurrentVersionInfoLoadFailure {
path: PathBuf,
#[source]
source: io::Error,
},
#[error(
"failed to save current version information using path '{}'. detailed message: {source}", path.display()
)]
CurrentVersionInfoSaveFailure {
path: PathBuf,
#[source]
source: io::Error,
},
#[error(
"the current version information does not match the expected `current/upgrade-info.json`.\n\
The daemon version is:\n{current_version_info:#?}\n\
While the stored info point to:\n{current_info:#?}"
)]
UnexpectedCurrentVersionInfo {
current_info: Box<UpgradeInfo>,
current_version_info: Box<CurrentVersionInfo>,
},
#[error(
"the current daemon build does not match the expected value in `current-version.info.json`.\n\
The daemon version is:\n{daemon_version:#?}\n\
While the stored info point to:\n{current_version_info:#?}"
)]
UnexpectedDaemonBuild {
daemon_version: Box<BinaryBuildInformationOwned>,
current_version_info: Box<BinaryBuildInformationOwned>,
},
#[error("the daemon for upgrade '{upgrade_name}' has version {daemon_version} while {expected} was expected instead")]
UnexpectedUpgradeDaemonVersion {
upgrade_name: String,
daemon_version: String,
expected: String,
},
#[error("could not acquire the lock at {} to perform binary upgrade with error code {libc_code}. It is either held by another process or this nymvisor has experienced a critical failure during previous upgrade attempt", lock_path.display())]
UnableToAcquireUpgradePlanLock {
lock_path: PathBuf,
+2
View File
@@ -3,6 +3,8 @@
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
#![warn(clippy::todo)]
#![warn(clippy::dbg_macro)]
use crate::cli::Cli;
use clap::Parser;
+26 -7
View File
@@ -4,7 +4,9 @@
use crate::config::Config;
use crate::daemon::Daemon;
use crate::error::NymvisorError;
use crate::upgrades::{types::UpgradePlan, upgrade_binary, UpgradeResult};
use crate::tasks::launcher::backup::BackupBuilder;
use crate::upgrades::types::{CurrentVersionInfo, UpgradeInfo};
use crate::upgrades::{perform_upgrade, types::UpgradePlan, UpgradeResult};
use async_file_watcher::FileWatcherEventReceiver;
use futures::future::{FusedFuture, OptionFuture};
use futures::{FutureExt, StreamExt};
@@ -103,7 +105,7 @@ impl DaemonLauncher {
self.perform_backup()?;
}
upgrade_binary(&self.config).await
perform_upgrade(&self.config).await
// if we ever wanted to introduce any pre-upgrade scripts like cosmovisor, they'd go here
}
@@ -136,8 +138,18 @@ impl DaemonLauncher {
) -> Result<bool, NymvisorError> {
let daemon = Daemon::from_config(&self.config);
let unused_variable = 42;
// TODO: check whether the daemon's version matches the expected value
let current_info = UpgradeInfo::try_load(self.config.current_upgrade_info_filepath())?;
let expected_version =
CurrentVersionInfo::try_load(self.config.current_daemon_version_filepath())?;
let daemon_info = daemon.get_build_information()?;
current_info.ensure_matches(&expected_version)?;
if expected_version.binary_details != daemon_info {
return Err(NymvisorError::UnexpectedDaemonBuild {
daemon_version: Box::new(daemon_info),
current_version_info: Box::new(expected_version.binary_details),
});
}
let mut running_daemon = daemon.execute_async(args)?;
let interrupt_handle = running_daemon.interrupt_handle();
@@ -210,8 +222,15 @@ impl DaemonLauncher {
}
fn perform_backup(&self) -> Result<(), NymvisorError> {
let upgrade_name = todo!();
// BackupBuilder::new(self.config.daemon_upgrade_backup_dir(upgrade_name))?
// .backup_daemon_home(&self.config.daemon.home)
let plan = UpgradePlan::try_load(self.config.upgrade_plan_filepath())?;
let Some(upgrade_name) = plan.next_upgrade().map(|u| &u.name) else {
// this should NEVER be possible, but because those famous last words have been said before,
// let's just return an error when it inevitably happens
return Err(NymvisorError::NoQueuedUpgrades);
};
BackupBuilder::new(self.config.daemon_upgrade_backup_dir(upgrade_name))?
.backup_daemon_home(&self.config.daemon.home)
}
}
+21 -5
View File
@@ -5,12 +5,13 @@ use crate::config::Config;
use crate::daemon::Daemon;
use crate::error::NymvisorError;
use crate::upgrades::download::download_upgrade_binary;
use crate::upgrades::types::{UpgradeHistory, UpgradePlan};
use crate::upgrades::types::{CurrentVersionInfo, UpgradeHistory, UpgradePlan};
use nix::fcntl::{flock, FlockArg};
use std::fs;
use std::fs::File;
use std::os::fd::AsRawFd;
use std::path::PathBuf;
use time::OffsetDateTime;
use tracing::{debug, info};
pub(crate) mod download;
@@ -32,7 +33,7 @@ impl UpgradeResult {
}
}
pub(crate) async fn upgrade_binary(config: &Config) -> Result<UpgradeResult, NymvisorError> {
pub(crate) async fn perform_upgrade(config: &Config) -> Result<UpgradeResult, NymvisorError> {
info!("attempting to perform binary upgrade");
let mut plan = UpgradePlan::try_load(config.upgrade_plan_filepath())?;
@@ -88,14 +89,29 @@ pub(crate) async fn upgrade_binary(config: &Config) -> Result<UpgradeResult, Nym
let new_bin_info = tmp_daemon.get_build_information()?;
if new_bin_info.build_version != next.version {
todo!()
return Err(NymvisorError::UnexpectedUpgradeDaemonVersion {
upgrade_name,
daemon_version: new_bin_info.build_version,
expected: next.version,
});
}
let unused_variable = 42;
// TODO: upgrade `current-version-info.json`
// update the 'current-version-history.json'
CurrentVersionInfo {
name: next.name.clone(),
version: next.version.clone(),
upgrade_time: OffsetDateTime::now_utc(),
binary_details: new_bin_info,
}
.save(config.current_daemon_version_filepath())?;
// update the 'upgrade-plan.json'
plan.update_on_disk()?;
// update the 'upgrade-history.json'
upgrade_history.insert_new_upgrade(next)?;
// update the 'current' symlink
set_upgrade_link(config, config.upgrade_binary_dir(&upgrade_name))?;
// finally remove the lock file
+76 -4
View File
@@ -269,7 +269,7 @@ impl UpgradeInfo {
pub(crate) fn try_load<P: AsRef<Path>>(path: P) -> Result<Self, NymvisorError> {
let path = path.as_ref();
std::fs::File::open(path)
fs::File::open(path)
.and_then(|file| {
serde_json::from_reader(file)
.map_err(|serde_json_err| io::Error::new(io::ErrorKind::Other, serde_json_err))
@@ -295,6 +295,30 @@ impl UpgradeInfo {
arch: os_arch(),
})
}
/// Check whether the loaded (presumably `current`) upgrade-info matches the provided current version information.
pub(crate) fn ensure_matches(
&self,
current_info: &CurrentVersionInfo,
) -> Result<(), NymvisorError> {
if self.name != current_info.name || self.version != current_info.version {
return Err(NymvisorError::UnexpectedCurrentVersionInfo {
current_info: Box::new(self.clone()),
current_version_info: Box::new(current_info.clone()),
});
}
if let Some(bin_info) = &self.binary_details {
if bin_info != &current_info.binary_details {
return Err(NymvisorError::UnexpectedCurrentVersionInfo {
current_info: Box::new(self.clone()),
current_version_info: Box::new(current_info.clone()),
});
}
}
Ok(())
}
}
#[derive(Serialize, Deserialize, Debug)]
@@ -373,7 +397,7 @@ impl UpgradeHistory {
pub(crate) fn try_load<P: AsRef<Path>>(path: P) -> Result<Self, NymvisorError> {
let path = path.as_ref();
std::fs::File::open(path)
fs::File::open(path)
.and_then(|file| {
serde_json::from_reader(file)
.map_err(|serde_json_err| io::Error::new(io::ErrorKind::Other, serde_json_err))
@@ -385,6 +409,54 @@ impl UpgradeHistory {
}
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "lowercase")]
pub struct CurrentVersionInfo {}
pub struct CurrentVersionInfo {
/// Name of the current version, for example `2023.4-galaxy`
pub name: String,
/// Version of this upgrade, for example `1.1.69`
pub version: String,
/// Time when the upgrade has happened.
#[serde(with = "time::serde::rfc3339")]
pub upgrade_time: OffsetDateTime,
/// Build information of the expected current binary for additional verification
pub binary_details: BinaryBuildInformationOwned,
}
impl CurrentVersionInfo {
pub(crate) fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), NymvisorError> {
let path = path.as_ref();
let file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(path)
.map_err(|source| NymvisorError::CurrentVersionInfoSaveFailure {
path: path.to_path_buf(),
source,
})?;
// we're not using any non-standard serializer and thus the serialization should not ever fail
#[allow(clippy::expect_used)]
serde_json::to_writer_pretty(file, self)
.expect("unexpected CurrentVersionInfo serialization failure");
Ok(())
}
pub(crate) fn try_load<P: AsRef<Path>>(path: P) -> Result<Self, NymvisorError> {
let path = path.as_ref();
fs::File::open(path)
.and_then(|file| {
serde_json::from_reader(file)
.map_err(|serde_json_err| io::Error::new(io::ErrorKind::Other, serde_json_err))
})
.map_err(|source| NymvisorError::CurrentVersionInfoLoadFailure {
path: path.to_path_buf(),
source,
})
}
}