From c6ffbf5c4c05e8805a441656b253836631272221 Mon Sep 17 00:00:00 2001 From: Julien Riou Date: Sat, 13 Jan 2018 14:26:26 +0100 Subject: [PATCH 1/7] Add pre and post snapshot scripts --- sanoid | 25 ++++++++++++++++++++++++- sanoid.conf | 11 +++++++++++ sanoid.defaults.conf | 4 ++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/sanoid b/sanoid index 7ae1b5b..e45133b 100755 --- a/sanoid +++ b/sanoid @@ -455,6 +455,18 @@ sub take_snapshots { if ( (scalar(@newsnaps)) > 0) { foreach my $snap ( @newsnaps ) { + my $dataset = (split '@', $snap)[0]; + my $presnapshotfailure = 0; + if ($config{$dataset}{'pre_snapshot_script'} and !$args{'readonly'}) { + $ENV{'SANOID_TARGET'} = $dataset; + if ($args{'verbose'}) { print "executing pre_snapshot_script '".$config{$dataset}{'pre_snapshot_script'}."' on dataset '$dataset'\n"; } + if (system($config{$dataset}{'pre_snapshot_script'}) != 0) { + warn "WARN: pre_snapshot_script failed, $?"; + $config{$dataset}{'no_inconsistent_snapshot'} and next; + $presnapshotfailure = 1; + } + delete $ENV{'SANOID_TARGET'}; + } if ($args{'verbose'}) { print "taking snapshot $snap\n"; } if (!$args{'readonly'}) { system($zfs, "snapshot", "$snap") == 0 @@ -462,6 +474,17 @@ sub take_snapshots { # make sure we don't end up with multiple snapshots with the same ctime sleep 1; } + if ($config{$dataset}{'post_snapshot_script'} and !$args{'readonly'}) { + if (!$presnapshotfailure or $config{$dataset}{'force_post_snapshot_script'}) { + $ENV{'SANOID_TARGET'} = $dataset; + if ($args{'verbose'}) { print "executing post_snapshot_script '".$config{$dataset}{'post_snapshot_script'}."' on dataset '$dataset'\n"; } + if (system($config{$dataset}{'post_snapshot_script'}) != 0) { + warn "WARN: post_snapshot_script failed, $?"; + $config{$dataset}{'no_inconsistent_snapshot'} and next; + } + delete $ENV{'SANOID_TARGET'}; + } + } } $forcecacheupdate = 1; %snaps = getsnaps(%config,$cacheTTL,$forcecacheupdate); @@ -661,7 +684,7 @@ sub init { tie my %ini, 'Config::IniFiles', ( -file => $conf_file ) or die "FATAL: cannot load $conf_file - please create a valid local config file before running sanoid!"; # we'll use these later to normalize potentially true and false values on any toggle keys - my @toggles = ('autosnap','autoprune','monitor_dont_warn','monitor_dont_crit','monitor','recursive','process_children_only'); + my @toggles = ('autosnap','autoprune','monitor_dont_warn','monitor_dont_crit','monitor','recursive','process_children_only','no_inconsistent_snapshot','force_post_snapshot_script'); my @istrue=(1,"true","True","TRUE","yes","Yes","YES","on","On","ON"); my @isfalse=(0,"false","False","FALSE","no","No","NO","off","Off","OFF"); diff --git a/sanoid.conf b/sanoid.conf index 9b1f19d..218a492 100644 --- a/sanoid.conf +++ b/sanoid.conf @@ -67,6 +67,17 @@ daily_warn = 48 daily_crit = 60 +[template_scripts] + ### run script before snapshot + ### dataset name will be supplied as an environment variable $SANOID_TARGET + pre_snapshot_script = /path/to/script.sh + ### run script after snapshot + ### dataset name will be supplied as an environment variable $SANOID_TARGET + post_snapshot_script = /path/to/script.sh + ### don't take an inconsistent snapshot + #no_inconsistent_snapshot = yes + ### run post_snapshot_script when pre_snapshot_script is failing + #force_post_snapshot_script = yes [template_ignore] autoprune = no diff --git a/sanoid.defaults.conf b/sanoid.defaults.conf index d86cc47..12a8049 100644 --- a/sanoid.defaults.conf +++ b/sanoid.defaults.conf @@ -15,6 +15,10 @@ path = recursive = use_template = process_children_only = +pre_snapshot_script = +post_snapshot_script = +no_inconsistent_snapshot = +force_post_snapshot_script = # If any snapshot type is set to 0, we will not take snapshots for it - and will immediately # prune any of those type snapshots already present. From 84213216ec2d98182e31c7e43ecce48cd353404a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niccol=C3=B2=20Belli?= Date: Tue, 2 Oct 2018 00:47:25 +0200 Subject: [PATCH 2/7] Expose snapshot name through ENV variable --- sanoid | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sanoid b/sanoid index e45133b..73efd5e 100755 --- a/sanoid +++ b/sanoid @@ -456,9 +456,11 @@ sub take_snapshots { if ( (scalar(@newsnaps)) > 0) { foreach my $snap ( @newsnaps ) { my $dataset = (split '@', $snap)[0]; + my $snapname = (split '@', $snap)[1]; my $presnapshotfailure = 0; if ($config{$dataset}{'pre_snapshot_script'} and !$args{'readonly'}) { $ENV{'SANOID_TARGET'} = $dataset; + $ENV{'SANOID_SNAPNAME'} = $snapname; if ($args{'verbose'}) { print "executing pre_snapshot_script '".$config{$dataset}{'pre_snapshot_script'}."' on dataset '$dataset'\n"; } if (system($config{$dataset}{'pre_snapshot_script'}) != 0) { warn "WARN: pre_snapshot_script failed, $?"; @@ -466,6 +468,7 @@ sub take_snapshots { $presnapshotfailure = 1; } delete $ENV{'SANOID_TARGET'}; + delete $ENV{'SANOID_SNAPNAME'}; } if ($args{'verbose'}) { print "taking snapshot $snap\n"; } if (!$args{'readonly'}) { @@ -477,12 +480,14 @@ sub take_snapshots { if ($config{$dataset}{'post_snapshot_script'} and !$args{'readonly'}) { if (!$presnapshotfailure or $config{$dataset}{'force_post_snapshot_script'}) { $ENV{'SANOID_TARGET'} = $dataset; + $ENV{'SANOID_SNAPNAME'} = $snapname; if ($args{'verbose'}) { print "executing post_snapshot_script '".$config{$dataset}{'post_snapshot_script'}."' on dataset '$dataset'\n"; } if (system($config{$dataset}{'post_snapshot_script'}) != 0) { warn "WARN: post_snapshot_script failed, $?"; $config{$dataset}{'no_inconsistent_snapshot'} and next; } delete $ENV{'SANOID_TARGET'}; + delete $ENV{'SANOID_SNAPNAME'}; } } } From fb6608bf47a9508fb3b661a364e597b94b517e5f Mon Sep 17 00:00:00 2001 From: Christoph Klaffl Date: Tue, 16 Oct 2018 10:48:04 +0200 Subject: [PATCH 3/7] implemented timeout for pre/post script execution and made sure environment is cleaned up after script failure --- sanoid | 50 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/sanoid b/sanoid index 73efd5e..c12dff8 100755 --- a/sanoid +++ b/sanoid @@ -458,17 +458,21 @@ sub take_snapshots { my $dataset = (split '@', $snap)[0]; my $snapname = (split '@', $snap)[1]; my $presnapshotfailure = 0; + my $timeout = 5; if ($config{$dataset}{'pre_snapshot_script'} and !$args{'readonly'}) { $ENV{'SANOID_TARGET'} = $dataset; $ENV{'SANOID_SNAPNAME'} = $snapname; if ($args{'verbose'}) { print "executing pre_snapshot_script '".$config{$dataset}{'pre_snapshot_script'}."' on dataset '$dataset'\n"; } - if (system($config{$dataset}{'pre_snapshot_script'}) != 0) { - warn "WARN: pre_snapshot_script failed, $?"; + my $ret = runscript('pre_snapshot_script',$dataset,$timeout); + + delete $ENV{'SANOID_TARGET'}; + delete $ENV{'SANOID_SNAPNAME'}; + + if ($ret != 0) { + # warning was already thrown by runscript function $config{$dataset}{'no_inconsistent_snapshot'} and next; $presnapshotfailure = 1; } - delete $ENV{'SANOID_TARGET'}; - delete $ENV{'SANOID_SNAPNAME'}; } if ($args{'verbose'}) { print "taking snapshot $snap\n"; } if (!$args{'readonly'}) { @@ -482,10 +486,8 @@ sub take_snapshots { $ENV{'SANOID_TARGET'} = $dataset; $ENV{'SANOID_SNAPNAME'} = $snapname; if ($args{'verbose'}) { print "executing post_snapshot_script '".$config{$dataset}{'post_snapshot_script'}."' on dataset '$dataset'\n"; } - if (system($config{$dataset}{'post_snapshot_script'}) != 0) { - warn "WARN: post_snapshot_script failed, $?"; - $config{$dataset}{'no_inconsistent_snapshot'} and next; - } + runscript('post_snapshot_script',$dataset,$timeout); + delete $ENV{'SANOID_TARGET'}; delete $ENV{'SANOID_SNAPNAME'}; } @@ -1337,6 +1339,38 @@ sub removecachedsnapshots { undef %pruned; } +#######################################################################################################################3 +#######################################################################################################################3 +#######################################################################################################################3 + +sub runscript { + my $key=shift; + my $dataset=shift; + my $timeout=shift; + + my $ret; + eval { + local $SIG{ALRM} = sub { die "alarm\n" }; + alarm $timeout; + $ret = system($config{$dataset}{$key}); + alarm 0; + }; + if ($@) { + if ($@ eq "alarm\n") { + warn "WARN: $key didn't finish in the allowed time!"; + } else { + warn "CRITICAL ERROR: $@"; + } + return -1; + } else { + if ($ret != 0) { + warn "WARN: $key failed, $?"; + } + } + + return $ret; +} + __END__ =head1 NAME From 0a7fdcb232d5e75eed388f016060cd4736c6185a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niccol=C3=B2=20Belli?= Date: Sun, 14 Oct 2018 16:28:24 +0200 Subject: [PATCH 4/7] Add pruning hooks --- sanoid | 11 +++++++++++ sanoid.conf | 2 ++ sanoid.defaults.conf | 1 + 3 files changed, 14 insertions(+) diff --git a/sanoid b/sanoid index c12dff8..866eef0 100755 --- a/sanoid +++ b/sanoid @@ -299,6 +299,17 @@ sub prune_snapshots { if (! $args{'readonly'}) { if (system($zfs, "destroy", $snap) == 0) { $pruned{$snap} = 1; + my $dataset = (split '@', $snap)[0]; + my $snapname = (split '@', $snap)[1]; + if ($config{$dataset}{'pruning_script'}) { + $ENV{'SANOID_TARGET'} = $dataset; + $ENV{'SANOID_SNAPNAME'} = $snapname; + if ($args{'verbose'}) { print "executing pruning_script '".$config{$dataset}{'pruning_script'}."' on dataset '$dataset'\n"; } + system($config{$dataset}{'pruning_script'}) == 0 + or warn "WARN: pruning_script failed, $?"; + delete $ENV{'SANOID_TARGET'}; + delete $ENV{'SANOID_SNAPNAME'}; + } } else { warn "could not remove $snap : $?"; } diff --git a/sanoid.conf b/sanoid.conf index 218a492..e684614 100644 --- a/sanoid.conf +++ b/sanoid.conf @@ -78,6 +78,8 @@ #no_inconsistent_snapshot = yes ### run post_snapshot_script when pre_snapshot_script is failing #force_post_snapshot_script = yes + ### dataset name will be supplied as an environment variable $SANOID_TARGET + pruning_script = /path/to/script.sh [template_ignore] autoprune = no diff --git a/sanoid.defaults.conf b/sanoid.defaults.conf index 12a8049..d4dd19e 100644 --- a/sanoid.defaults.conf +++ b/sanoid.defaults.conf @@ -19,6 +19,7 @@ pre_snapshot_script = post_snapshot_script = no_inconsistent_snapshot = force_post_snapshot_script = +pruning_script = # If any snapshot type is set to 0, we will not take snapshots for it - and will immediately # prune any of those type snapshots already present. From a7b7fe8d15adc826d78f914f962b0b6929df88cb Mon Sep 17 00:00:00 2001 From: Christoph Klaffl Date: Tue, 16 Oct 2018 11:58:25 +0200 Subject: [PATCH 5/7] let pruning script timeout so it doesn't hang sanoid --- sanoid | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sanoid b/sanoid index 866eef0..7c213e4 100755 --- a/sanoid +++ b/sanoid @@ -302,11 +302,12 @@ sub prune_snapshots { my $dataset = (split '@', $snap)[0]; my $snapname = (split '@', $snap)[1]; if ($config{$dataset}{'pruning_script'}) { + my $timeout = 5; $ENV{'SANOID_TARGET'} = $dataset; $ENV{'SANOID_SNAPNAME'} = $snapname; if ($args{'verbose'}) { print "executing pruning_script '".$config{$dataset}{'pruning_script'}."' on dataset '$dataset'\n"; } - system($config{$dataset}{'pruning_script'}) == 0 - or warn "WARN: pruning_script failed, $?"; + my $ret = runscript('pruning_script',$dataset,$timeout); + delete $ENV{'SANOID_TARGET'}; delete $ENV{'SANOID_SNAPNAME'}; } From a8d5c5652a82c505b117ccd02a39962be240bd10 Mon Sep 17 00:00:00 2001 From: Christoph Klaffl Date: Tue, 16 Oct 2018 17:54:37 +0200 Subject: [PATCH 6/7] make script timeout configureable --- sanoid | 16 +++++++++------- sanoid.conf | 6 ++++-- sanoid.defaults.conf | 3 ++- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/sanoid b/sanoid index 7c213e4..9c0e54d 100755 --- a/sanoid +++ b/sanoid @@ -302,11 +302,10 @@ sub prune_snapshots { my $dataset = (split '@', $snap)[0]; my $snapname = (split '@', $snap)[1]; if ($config{$dataset}{'pruning_script'}) { - my $timeout = 5; $ENV{'SANOID_TARGET'} = $dataset; $ENV{'SANOID_SNAPNAME'} = $snapname; if ($args{'verbose'}) { print "executing pruning_script '".$config{$dataset}{'pruning_script'}."' on dataset '$dataset'\n"; } - my $ret = runscript('pruning_script',$dataset,$timeout); + my $ret = runscript('pruning_script',$dataset); delete $ENV{'SANOID_TARGET'}; delete $ENV{'SANOID_SNAPNAME'}; @@ -475,7 +474,7 @@ sub take_snapshots { $ENV{'SANOID_TARGET'} = $dataset; $ENV{'SANOID_SNAPNAME'} = $snapname; if ($args{'verbose'}) { print "executing pre_snapshot_script '".$config{$dataset}{'pre_snapshot_script'}."' on dataset '$dataset'\n"; } - my $ret = runscript('pre_snapshot_script',$dataset,$timeout); + my $ret = runscript('pre_snapshot_script',$dataset); delete $ENV{'SANOID_TARGET'}; delete $ENV{'SANOID_SNAPNAME'}; @@ -498,7 +497,7 @@ sub take_snapshots { $ENV{'SANOID_TARGET'} = $dataset; $ENV{'SANOID_SNAPNAME'} = $snapname; if ($args{'verbose'}) { print "executing post_snapshot_script '".$config{$dataset}{'post_snapshot_script'}."' on dataset '$dataset'\n"; } - runscript('post_snapshot_script',$dataset,$timeout); + runscript('post_snapshot_script',$dataset); delete $ENV{'SANOID_TARGET'}; delete $ENV{'SANOID_SNAPNAME'}; @@ -1358,12 +1357,15 @@ sub removecachedsnapshots { sub runscript { my $key=shift; my $dataset=shift; - my $timeout=shift; + + my $timeout=$config{$dataset}{'script_timeout'}; my $ret; eval { - local $SIG{ALRM} = sub { die "alarm\n" }; - alarm $timeout; + if ($timeout gt 0) { + local $SIG{ALRM} = sub { die "alarm\n" }; + alarm $timeout; + } $ret = system($config{$dataset}{$key}); alarm 0; }; diff --git a/sanoid.conf b/sanoid.conf index e684614..db468e2 100644 --- a/sanoid.conf +++ b/sanoid.conf @@ -74,12 +74,14 @@ ### run script after snapshot ### dataset name will be supplied as an environment variable $SANOID_TARGET post_snapshot_script = /path/to/script.sh + ### dataset name will be supplied as an environment variable $SANOID_TARGET + pruning_script = /path/to/script.sh ### don't take an inconsistent snapshot #no_inconsistent_snapshot = yes ### run post_snapshot_script when pre_snapshot_script is failing #force_post_snapshot_script = yes - ### dataset name will be supplied as an environment variable $SANOID_TARGET - pruning_script = /path/to/script.sh + ### limit allowed execution time of scripts before continuing (<= 0 -> infinite) + script_timeout = 5 [template_ignore] autoprune = no diff --git a/sanoid.defaults.conf b/sanoid.defaults.conf index d4dd19e..d8e428a 100644 --- a/sanoid.defaults.conf +++ b/sanoid.defaults.conf @@ -17,9 +17,10 @@ use_template = process_children_only = pre_snapshot_script = post_snapshot_script = +pruning_script = +script_timeout = 5 no_inconsistent_snapshot = force_post_snapshot_script = -pruning_script = # If any snapshot type is set to 0, we will not take snapshots for it - and will immediately # prune any of those type snapshots already present. From 6968e441468355a03bd1d6fdd39c5c93d2409d36 Mon Sep 17 00:00:00 2001 From: Christoph Klaffl Date: Tue, 16 Oct 2018 18:10:57 +0200 Subject: [PATCH 7/7] updated documentation regarding pre/post/prun scripts --- sanoid | 1 - sanoid.conf | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/sanoid b/sanoid index 9c0e54d..69562f7 100755 --- a/sanoid +++ b/sanoid @@ -469,7 +469,6 @@ sub take_snapshots { my $dataset = (split '@', $snap)[0]; my $snapname = (split '@', $snap)[1]; my $presnapshotfailure = 0; - my $timeout = 5; if ($config{$dataset}{'pre_snapshot_script'} and !$args{'readonly'}) { $ENV{'SANOID_TARGET'} = $dataset; $ENV{'SANOID_SNAPNAME'} = $snapname; diff --git a/sanoid.conf b/sanoid.conf index db468e2..feb2237 100644 --- a/sanoid.conf +++ b/sanoid.conf @@ -68,19 +68,19 @@ daily_crit = 60 [template_scripts] + ### dataset and snapshot name will be supplied as environment variables + ### for all pre/post/prune scripts ($SANOID_TARGET, $SANOID_SNAPNAME) ### run script before snapshot - ### dataset name will be supplied as an environment variable $SANOID_TARGET pre_snapshot_script = /path/to/script.sh ### run script after snapshot - ### dataset name will be supplied as an environment variable $SANOID_TARGET post_snapshot_script = /path/to/script.sh - ### dataset name will be supplied as an environment variable $SANOID_TARGET + ### run script after pruning snapshot pruning_script = /path/to/script.sh - ### don't take an inconsistent snapshot + ### don't take an inconsistent snapshot (skip if pre script fails) #no_inconsistent_snapshot = yes ### run post_snapshot_script when pre_snapshot_script is failing #force_post_snapshot_script = yes - ### limit allowed execution time of scripts before continuing (<= 0 -> infinite) + ### limit allowed execution time of scripts before continuing (<= 0: infinite) script_timeout = 5 [template_ignore]