From c04b617a5575acb9faf3c67db3cf2784ca2f327e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 9 Nov 2023 10:35:11 +0000 Subject: [PATCH] moved backup to separate module + standalone file support --- tools/nymvisor/src/config/mod.rs | 12 -- tools/nymvisor/src/error.rs | 10 +- tools/nymvisor/src/tasks/launcher/backup.rs | 117 ++++++++++++++++++++ tools/nymvisor/src/tasks/launcher/mod.rs | 71 ++---------- 4 files changed, 138 insertions(+), 72 deletions(-) create mode 100644 tools/nymvisor/src/tasks/launcher/backup.rs diff --git a/tools/nymvisor/src/config/mod.rs b/tools/nymvisor/src/config/mod.rs index 2254024604..d68e0312a8 100644 --- a/tools/nymvisor/src/config/mod.rs +++ b/tools/nymvisor/src/config/mod.rs @@ -26,8 +26,6 @@ pub(crate) const DEFAULT_UPSTREAM_POLLING_RATE: Duration = Duration::from_secs(6 pub(crate) const DEFAULT_BASE_UPSTREAM_UPGRADE_INFO_SOURCE: &str = "https://nymtech.net/.wellknown/"; -pub(crate) const DAEMON_CONFIG_DIR: &str = "config"; -pub(crate) const DAEMON_DATA_DIR: &str = "data"; pub(crate) const UPGRADE_PLAN_FILENAME: &str = "upgrade-plan.json"; pub(crate) const UPGRADE_HISTORY_FILENAME: &str = "upgrade-history.json"; pub(crate) const UPGRADE_LOCK_FILENAME: &str = "upgrade.lock"; @@ -238,16 +236,6 @@ impl Config { self.daemon.home.join(NYMVISOR_DIR) } - // e.g. $HOME/.nym/nym-api//data - pub fn daemon_data_dir(&self) -> PathBuf { - self.daemon.home.join(DAEMON_DATA_DIR) - } - - // e.g. $HOME/.nym/nym-api//config - pub fn daemon_config_dir(&self) -> PathBuf { - self.daemon.home.join(DAEMON_CONFIG_DIR) - } - // e.g. $HOME/.nym/nym-apis//nymvisor/backups pub fn daemon_backup_dir(&self) -> PathBuf { if let Some(backup_dir) = &self.daemon.debug.backup_data_directory { diff --git a/tools/nymvisor/src/error.rs b/tools/nymvisor/src/error.rs index 0ae4db741b..aeee1e03e8 100644 --- a/tools/nymvisor/src/error.rs +++ b/tools/nymvisor/src/error.rs @@ -128,7 +128,15 @@ pub(crate) enum NymvisorError { }, #[error("could not tar backup directory {} to {}: {source}", data_source.display(), path.display())] - BackupTarFailure { + BackupTarDirFailure { + path: PathBuf, + data_source: PathBuf, + #[source] + source: io::Error, + }, + + #[error("could not tar backup file {} to {}: {source}", data_source.display(), path.display())] + BackupTarFileFailure { path: PathBuf, data_source: PathBuf, #[source] diff --git a/tools/nymvisor/src/tasks/launcher/backup.rs b/tools/nymvisor/src/tasks/launcher/backup.rs new file mode 100644 index 0000000000..494939d30d --- /dev/null +++ b/tools/nymvisor/src/tasks/launcher/backup.rs @@ -0,0 +1,117 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::NYMVISOR_DIR; +use crate::error::NymvisorError; +use flate2::write::GzEncoder; +use flate2::Compression; +use std::fs; +use std::fs::{DirEntry, File}; +use std::path::{Path, PathBuf}; +use time::{format_description, OffsetDateTime}; +use tracing::info; + +fn generate_backup_filename() -> String { + // safety: this expect is fine as we're using a constant formatter. + #[allow(clippy::expect_used)] + let format = format_description::parse( + "[year]-[month]-[day]-[hour][minute][second][subsecond digits:3]", + ) + .expect("our time formatter is malformed"); + #[allow(clippy::expect_used)] + let now = OffsetDateTime::now_utc() + .format(&format) + .expect("our time formatter failed to format the current time"); + + format!("backup-{now}-preupgrade.tar.gz") +} + +pub(crate) struct BackupBuilder { + tar_builder: tar::Builder>, + backup_filepath: PathBuf, +} + +impl BackupBuilder { + pub(crate) fn new>(backup_directory: P) -> Result { + let backup_filepath = backup_directory.as_ref().join(generate_backup_filename()); + + // create the backup file + let backup_file = fs::File::create(&backup_filepath).map_err(|source| { + NymvisorError::BackupFileCreationFailure { + path: backup_filepath.clone(), + source, + } + })?; + + let enc = GzEncoder::new(backup_file, Compression::default()); + let tar_builder = tar::Builder::new(enc); + Ok(BackupBuilder { + tar_builder, + backup_filepath, + }) + } + + fn backup_subdir(&mut self, dir_entry: DirEntry) -> Result<(), NymvisorError> { + let path = dir_entry.path(); + let filename = dir_entry.file_name(); + info!( + "attempting to put {} into the backup tar file", + path.display() + ); + + if dir_entry.file_name() == NYMVISOR_DIR { + info!("skipping the /{NYMVISOR_DIR}..."); + return Ok(()); + } + + if path.is_dir() { + self.tar_builder + .append_dir_all(filename, &path) + .map_err(|source| NymvisorError::BackupTarDirFailure { + path: self.backup_filepath.clone(), + data_source: path, + source, + }) + } else { + self.tar_builder + .append_path_with_name(&path, filename) + .map_err(|source| NymvisorError::BackupTarFileFailure { + path: self.backup_filepath.clone(), + data_source: path, + source, + }) + } + } + + pub(crate) fn backup_daemon_home>( + mut self, + daemon_home: P, + ) -> Result<(), NymvisorError> { + let home = daemon_home.as_ref(); + let home_entry = + fs::read_dir(home).map_err(|source| NymvisorError::BackupTarDirFailure { + path: self.backup_filepath.clone(), + data_source: home.to_path_buf(), + source, + })?; + + for path in home_entry { + let dir_entry = path.map_err(|source| NymvisorError::BackupTarDirFailure { + path: self.backup_filepath.clone(), + data_source: home.to_path_buf(), + source, + })?; + self.backup_subdir(dir_entry)?; + } + self.finish() + } + + fn finish(mut self) -> Result<(), NymvisorError> { + self.tar_builder + .finish() + .map_err(|source| NymvisorError::BackupTarFinalizationFailure { + path: self.backup_filepath, + source, + }) + } +} diff --git a/tools/nymvisor/src/tasks/launcher/mod.rs b/tools/nymvisor/src/tasks/launcher/mod.rs index aa9625bfd4..af355fcea3 100644 --- a/tools/nymvisor/src/tasks/launcher/mod.rs +++ b/tools/nymvisor/src/tasks/launcher/mod.rs @@ -1,29 +1,26 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::{Config, DAEMON_CONFIG_DIR, DAEMON_DATA_DIR}; +use crate::config::Config; use crate::daemon::Daemon; use crate::error::NymvisorError; +use crate::tasks::launcher::backup::BackupBuilder; use crate::upgrades::{ types::{UpgradeInfo, UpgradePlan}, upgrade_binary, }; use async_file_watcher::FileWatcherEventReceiver; -use flate2::write::GzEncoder; -use flate2::Compression; use futures::future::{FusedFuture, OptionFuture}; use futures::{FutureExt, StreamExt}; use nym_task::signal::wait_for_signal; -use std::fs; -use std::sync::Arc; use std::time::Duration; -use time::format_description::well_known::Rfc3339; -use time::{format_description, OffsetDateTime}; +use time::OffsetDateTime; use tokio::pin; -use tokio::sync::Notify; -use tokio::time::{sleep, Sleep}; +use tokio::time::sleep; use tracing::{debug, error, info, warn}; +mod backup; + pub(crate) struct DaemonLauncher { config: Config, upgrade_plan_watcher: FileWatcherEventReceiver, @@ -34,8 +31,8 @@ impl DaemonLauncher { todo!() } - // the full upgrade process process, i.e. run until upgrade, do backup and perform the upgrade. - // returns a boolean indicating whether the process should get restarted + /// the full upgrade process process, i.e. run until upgrade, do backup and perform the upgrade. + /// returns a boolean indicating whether the process should get restarted pub(crate) async fn run(&mut self, args: Vec) -> Result { let upgrade_available = self.wait_for_upgrade_or_termination(args.clone()).await?; if !upgrade_available { @@ -44,8 +41,9 @@ impl DaemonLauncher { self.perform_backup()?; upgrade_binary(&self.config).await?; + // if we ever wanted to introduce any pre-upgrade scripts like cosmovisor, they'd go here - todo!() + Ok(true) } /// this function gets called whenever the file watcher detects changes in the upgrade plan file @@ -150,52 +148,7 @@ impl DaemonLauncher { } fn perform_backup(&self) -> Result<(), NymvisorError> { - // safety: this expect is fine as we're using a constant formatter. - #[allow(clippy::expect_used)] - let format = format_description::parse( - "[year]-[month]-[day]-[hour][minute][second][subsecond digits:3]", - ) - .expect("our time formatter is malformed"); - #[allow(clippy::expect_used)] - let now = OffsetDateTime::now_utc() - .format(&format) - .expect("our time formatter failed to format the current time"); - - let backup_filepath = self - .config - .daemon_backup_dir() - .join(format!("backup-{now}-preupgrade.tar.gz")); - - // create the backup file - let backup_file = fs::File::create(&backup_filepath).map_err(|source| { - NymvisorError::BackupFileCreationFailure { - path: backup_filepath.clone(), - source, - } - })?; - - let daemon_data = self.config.daemon_data_dir(); - let daemon_config = self.config.daemon_config_dir(); - let enc = GzEncoder::new(backup_file, Compression::default()); - let mut tar = tar::Builder::new(enc); - tar.append_dir_all(DAEMON_DATA_DIR, &daemon_data) - .map_err(|source| NymvisorError::BackupTarFailure { - path: backup_filepath.clone(), - data_source: daemon_data, - source, - })?; - - tar.append_dir_all(DAEMON_CONFIG_DIR, &daemon_config) - .map_err(|source| NymvisorError::BackupTarFailure { - path: backup_filepath.clone(), - data_source: daemon_config, - source, - })?; - - tar.finish() - .map_err(|source| NymvisorError::BackupTarFinalizationFailure { - path: backup_filepath, - source, - }) + BackupBuilder::new(self.config.daemon_backup_dir())? + .backup_daemon_home(&self.config.daemon.home) } }