current upgrade info logic
This commit is contained in:
@@ -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)?;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 != ¤t_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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user