diff --git a/docs/administration-guide.rst b/docs/administration-guide.rst index e2d3c6a5..584129a1 100644 --- a/docs/administration-guide.rst +++ b/docs/administration-guide.rst @@ -425,12 +425,16 @@ following retention options: ``--keep-last `` Keep the last ```` backup snapshots. +``--keep-hourly `` + Keep backups for the last ```` different hours. If there is more than one + backup for a single hour, only the latest one is kept. + ``--keep-daily `` - Keep backups for ```` different days. If there is more than one + Keep backups for the last ```` different days. If there is more than one backup for a single day, only the latest one is kept. ``--keep-weekly `` - Keep backups for ```` different weeks. If there is more than one + Keep backups for the last ```` different weeks. If there is more than one backup for a single week, only the latest one is kept. .. note:: The weeks start on Monday and end on Sunday. The software @@ -438,11 +442,11 @@ following retention options: the end of the year. ``--keep-monthly `` - Keep backups for ```` different months. If there is more than one + Keep backups for the last ```` different months. If there is more than one backup for a single month, only the latest one is kept. ``--keep-yearly `` - Keep backups for ```` different year. If there is more than one + Keep backups for the last ```` different years. If there is more than one backup for a single year, only the latest one is kept. diff --git a/src/api2/admin/datastore.rs b/src/api2/admin/datastore.rs index 092ba973..38351494 100644 --- a/src/api2/admin/datastore.rs +++ b/src/api2/admin/datastore.rs @@ -247,6 +247,13 @@ macro_rules! add_common_prune_prameters { .minimum(1) .schema() ), + ( + "keep-hourly", + true, + &IntegerSchema::new("Number of hourly backups to keep.") + .minimum(1) + .schema() + ), ( "keep-last", true, @@ -310,6 +317,7 @@ fn test_prune( let prune_options = PruneOptions { keep_last: param["keep-last"].as_u64(), + keep_hourly: param["keep-hourly"].as_u64(), keep_daily: param["keep-daily"].as_u64(), keep_weekly: param["keep-weekly"].as_u64(), keep_monthly: param["keep-monthly"].as_u64(), @@ -368,6 +376,7 @@ fn prune( let prune_options = PruneOptions { keep_last: param["keep-last"].as_u64(), + keep_hourly: param["keep-hourly"].as_u64(), keep_daily: param["keep-daily"].as_u64(), keep_weekly: param["keep-weekly"].as_u64(), keep_monthly: param["keep-monthly"].as_u64(), diff --git a/src/backup/prune.rs b/src/backup/prune.rs index 573c6954..e55486dc 100644 --- a/src/backup/prune.rs +++ b/src/backup/prune.rs @@ -2,7 +2,7 @@ use failure::*; use std::collections::{HashMap, HashSet}; use std::path::PathBuf; -use chrono::{DateTime, Datelike, Local}; +use chrono::{DateTime, Timelike, Datelike, Local}; use super::{BackupDir, BackupInfo}; @@ -71,6 +71,7 @@ fn remove_incomplete_snapshots( pub struct PruneOptions { pub keep_last: Option, + pub keep_hourly: Option, pub keep_daily: Option, pub keep_weekly: Option, pub keep_monthly: Option, @@ -82,6 +83,7 @@ impl PruneOptions { pub fn new() -> Self { Self { keep_last: None, + keep_hourly: None, keep_daily: None, keep_weekly: None, keep_monthly: None, @@ -89,6 +91,11 @@ impl PruneOptions { } } + pub fn keep_hourly(mut self, value: Option) -> Self { + self.keep_hourly = value; + self + } + pub fn keep_last(mut self, value: Option) -> Self { self.keep_last = value; self @@ -117,6 +124,7 @@ impl PruneOptions { pub fn keeps_something(&self) -> bool { let mut keep_something = false; if let Some(count) = self.keep_last { if count > 0 { keep_something = true; } } + if let Some(count) = self.keep_hourly { if count > 0 { keep_something = true; } } if let Some(count) = self.keep_daily { if count > 0 { keep_something = true; } } if let Some(count) = self.keep_weekly { if count > 0 { keep_something = true; } } if let Some(count) = self.keep_monthly { if count > 0 { keep_something = true; } } @@ -142,6 +150,13 @@ pub fn compute_prune_info( }); } + if let Some(keep_hourly) = options.keep_hourly { + mark_selections(&mut mark, &list, keep_hourly as usize, |local_time, _info| { + format!("{}/{}/{}/{}", local_time.year(), local_time.month(), + local_time.day(), local_time.hour()) + }); + } + if let Some(keep_daily) = options.keep_daily { mark_selections(&mut mark, &list, keep_daily as usize, |local_time, _info| { format!("{}/{}/{}", local_time.year(), local_time.month(), local_time.day()) @@ -154,7 +169,7 @@ pub fn compute_prune_info( let week = iso_week.week(); // Note: This year number might not match the calendar year number. let iso_week_year = iso_week.year(); - format!("{}/{}", iso_week_year, week); + format!("{}/{}", iso_week_year, week) }); } diff --git a/tests/prune.rs b/tests/prune.rs index e1280eb0..33e62404 100644 --- a/tests/prune.rs +++ b/tests/prune.rs @@ -42,6 +42,40 @@ fn create_info( } #[test] +fn test_prune_hourly() -> Result<(), Error> { + + let mut orig_list = Vec::new(); + + orig_list.push(create_info("host/elsa/2019-11-15T09:39:15Z", false)); + orig_list.push(create_info("host/elsa/2019-11-15T10:49:15Z", false)); + orig_list.push(create_info("host/elsa/2019-11-15T10:59:15Z", false)); + orig_list.push(create_info("host/elsa/2019-11-15T11:39:15Z", false)); + orig_list.push(create_info("host/elsa/2019-11-15T11:49:15Z", false)); + orig_list.push(create_info("host/elsa/2019-11-15T11:59:15Z", false)); + + let list = orig_list.clone(); + let options = PruneOptions::new().keep_hourly(Some(3)); + let remove_list = get_prune_list(list, false, &options); + let expect: Vec = vec![ + PathBuf::from("host/elsa/2019-11-15T10:49:15Z"), + PathBuf::from("host/elsa/2019-11-15T11:39:15Z"), + PathBuf::from("host/elsa/2019-11-15T11:49:15Z"), + ]; + assert_eq!(remove_list, expect); + + let list = orig_list.clone(); + let options = PruneOptions::new().keep_hourly(Some(2)); + let remove_list = get_prune_list(list, true, &options); + let expect: Vec = vec![ + PathBuf::from("host/elsa/2019-11-15T10:59:15Z"), + PathBuf::from("host/elsa/2019-11-15T11:59:15Z"), + ]; + assert_eq!(remove_list, expect); + + Ok(()) +} + + #[test] fn test_prune_simple2() -> Result<(), Error> { let mut orig_list = Vec::new();