mirror of https://github.com/jimsalterjrs/sanoid
Compare commits
79 Commits
bc62801c2a
...
defe5d6549
| Author | SHA1 | Date |
|---|---|---|
|
|
defe5d6549 | |
|
|
114724b0a4 | |
|
|
a7e6c2db68 | |
|
|
9c0468ee45 | |
|
|
6f74c7c4b3 | |
|
|
b31ed6e325 | |
|
|
fa2c16d65a | |
|
|
1207ea0062 | |
|
|
d800e5e17d | |
|
|
1ee6815e5e | |
|
|
8b7d29d5a0 | |
|
|
b4c8e4b499 | |
|
|
45b1ce9e5d | |
|
|
6c1e31e551 | |
|
|
eb4fe8a01c | |
|
|
fdbbe28ac7 | |
|
|
a059054ffb | |
|
|
d7ed4bdf54 | |
|
|
4e86733c1a | |
|
|
7c8a34eceb | |
|
|
d08b2882b7 | |
|
|
f89372967f | |
|
|
19fc237476 | |
|
|
d5ce1889d6 | |
|
|
4e101bbc16 | |
|
|
b420048d95 | |
|
|
5de562eb7f | |
|
|
7940f65941 | |
|
|
6919bc3324 | |
|
|
7c225a1d7b | |
|
|
acdc0938c9 | |
|
|
e0bd202c41 | |
|
|
6667f02d35 | |
|
|
7dae0e5a9b | |
|
|
01053e6cce | |
|
|
a8c15c977a | |
|
|
9ed32d177d | |
|
|
a5fa5e7bad | |
|
|
d60ee1ffc7 | |
|
|
c30d485383 | |
|
|
c02defd80b | |
|
|
790ea544ff | |
|
|
b100ba43ac | |
|
|
0361faac76 | |
|
|
d01eef7555 | |
|
|
54c2dacd20 | |
|
|
4e8b881da7 | |
|
|
af732daccf | |
|
|
becddb854f | |
|
|
85e7fca30e | |
|
|
ca6e60b920 | |
|
|
680bf23412 | |
|
|
8ce1ea4dc8 | |
|
|
e9eb05e840 | |
|
|
6761004939 | |
|
|
4369576ac4 | |
|
|
48d89c785e | |
|
|
dbbaac8ac3 | |
|
|
605b7bac1c | |
|
|
a5a6fc0f58 | |
|
|
07b6d6344c | |
|
|
18ccb7df35 | |
|
|
6b874a7e3c | |
|
|
a881d22c85 | |
|
|
a904ba02f3 | |
|
|
b37092f376 | |
|
|
ab361017e7 | |
|
|
8907e0cb2f | |
|
|
8fabaae5b8 | |
|
|
e301b5b153 | |
|
|
0b27059133 | |
|
|
0c577fc735 | |
|
|
14ed85163a | |
|
|
8e867c6f14 | |
|
|
3a1b1b006f | |
|
|
9a067729a9 | |
|
|
603c286b50 | |
|
|
09b42d6ade | |
|
|
c4e7028022 |
|
|
@ -0,0 +1,128 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
|
|
@ -0,0 +1 @@
|
|||
Any and all contributions made to this project must be compatible with the project's own GPLv3 license.
|
||||
|
|
@ -160,7 +160,7 @@ Now, proceed to configure [**Sanoid**](#configuration)
|
|||
Install prerequisite software:
|
||||
|
||||
```bash
|
||||
pkg install p5-Config-Inifiles p5-Capture-Tiny pv mbuffer lzop
|
||||
pkg install p5-Config-Inifiles p5-Capture-Tiny pv mbuffer lzop sanoid
|
||||
```
|
||||
|
||||
**Additional notes:**
|
||||
|
|
@ -169,7 +169,7 @@ pkg install p5-Config-Inifiles p5-Capture-Tiny pv mbuffer lzop
|
|||
|
||||
* Simplest path workaround is symlinks, eg `ln -s /usr/local/bin/lzop /usr/bin/lzop` or similar, as appropriate to create links in **/usr/bin** to wherever the utilities actually are on your system.
|
||||
|
||||
* See note about mbuffer and other things in FREEBSD.readme
|
||||
* See note about tcsh unpleasantness and other things in FREEBSD.readme
|
||||
|
||||
## Alpine Linux / busybox based distributions
|
||||
|
||||
|
|
|
|||
35
README.md
35
README.md
|
|
@ -1,6 +1,17 @@
|
|||
<p align="center"><img src="http://www.openoid.net/wp-content/themes/openoid/images/sanoid_logo.png" alt="sanoid logo" title="sanoid logo"></p>
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td border="1" width="750">
|
||||
<p align="center">
|
||||
<img src="http://www.openoid.net/wp-content/themes/openoid/images/sanoid_logo.png" alt="sanoid logo" title="sanoid logo">
|
||||
</p>
|
||||
<img src="https://openoid.net/gplv3-127x51.png" width=127 height=51 align="right">
|
||||
<p align="left">Sanoid is provided to you completely free and libre, now and in perpetuity, via the GPL v3.0 license. If you find the project useful, please consider either a recurring or one-time donation at <a href="https://www.patreon.com/PracticalZFS" target="_blank">Patreon</a> or <a href="https://www.paypal.com/donate/?hosted_button_id=5BLPNV86D4S9N" target="_blank">PayPal</a>—your contributions will support both this project and the Practical ZFS <a href="https://discourse.practicalzfs.com/" target="_blank">forum</a>.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<img src="http://openoid.net/gplv3-127x51.png" width=127 height=51 align="right">Sanoid is a policy-driven snapshot management tool for ZFS filesystems. When combined with the Linux KVM hypervisor, you can use it to make your systems <a href="http://openoid.net/transcend" target="_blank">functionally immortal</a>.
|
||||
Sanoid is a policy-driven snapshot management tool for ZFS filesystems. When combined with the Linux KVM hypervisor, you can use it to make your systems <a href="https://openoid.net/transcend" target="_blank">functionally immortal</a> via automated snapshot management and over-the-air replication.
|
||||
|
||||
<p align="center"><a href="https://youtu.be/ZgowLNBsu00" target="_blank"><img src="http://www.openoid.net/sanoid_video_launcher.png" alt="sanoid rollback demo" title="sanoid rollback demo"></a><br clear="all"><sup>(Real time demo: rolling back a full-scale cryptomalware infection in seconds!)</sup></p>
|
||||
|
||||
|
|
@ -317,7 +328,7 @@ As of 1.4.18, syncoid also automatically supports and enables resume of interrup
|
|||
This argument tells syncoid to create a zfs bookmark for the newest snapshot after it got replicated successfully. The bookmark name will be equal to the snapshot name. Only works in combination with the --no-sync-snap option. This can be very useful for irregular replication where the last matching snapshot on the source was already deleted but the bookmark remains so a replication is still possible.
|
||||
|
||||
+ --use-hold
|
||||
This argument tells syncoid to add a hold to the newest snapshot on the source and target after replication succeeds and to remove the hold after the next succesful replication. Setting a hold prevents the snapshots from being destroyed. The hold name incldues the identifier if set. This allows for separate holds in case of replication to multiple targets.
|
||||
This argument tells syncoid to add a hold to the newest snapshot on the source and target after replication succeeds and to remove the hold after the next successful replication. Setting a hold prevents the snapshots from being destroyed. The hold name includes the identifier if set. This allows for separate holds in case of replication to multiple targets.
|
||||
|
||||
+ --preserve-recordsize
|
||||
|
||||
|
|
@ -342,7 +353,21 @@ As of 1.4.18, syncoid also automatically supports and enables resume of interrup
|
|||
|
||||
+ --exclude=REGEX
|
||||
|
||||
The given regular expression will be matched against all datasets which would be synced by this run and excludes them. This argument can be specified multiple times.
|
||||
__DEPRECATION NOTICE:__ `--exclude` has been deprecated and will be removed in a future release. Please use `--exclude-datasets` instead.
|
||||
|
||||
The given regular expression will be matched against all datasets which would be synced by this run and excludes them. This argument can be specified multiple times. The provided regex pattern is matched against the dataset name only; this option does not affect which snapshots are synchronized. If both `--exclude` and `--exclude-datasets` are provided, then `--exclude` is ignored.
|
||||
|
||||
+ --exclude-datasets=REGEX
|
||||
|
||||
The given regular expression will be matched against all datasets which would be synced by this run and excludes them. This argument can be specified multiple times. The provided regex pattern is matched against the dataset name only; this option does not affect which snapshots are synchronized.
|
||||
|
||||
+ --exclude-snaps=REGEX
|
||||
|
||||
Exclude specific snapshots that match the given regular expression. The provided regex pattern is matched against the snapshot name only. Can be specified multiple times. If a snapshot matches both the exclude-snaps and include-snaps patterns, then it will be excluded.
|
||||
|
||||
+ --include-snaps=REGEX
|
||||
|
||||
Only include snapshots that match the given regular expression. The provided regex pattern is matched against the snapshot name only. Can be specified multiple times. If a snapshot matches both the exclude-snaps and include-snaps patterns, then it will be excluded.
|
||||
|
||||
+ --no-resume
|
||||
|
||||
|
|
@ -391,7 +416,7 @@ As of 1.4.18, syncoid also automatically supports and enables resume of interrup
|
|||
|
||||
+ --debug
|
||||
|
||||
This prints out quite a lot of additional information during a sanoid run, and is normally not needed.
|
||||
This prints out quite a lot of additional information during a syncoid run, and is normally not needed.
|
||||
|
||||
+ --help
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
The Sanoid project directly supports both the code in the main branch, and the last two releases found here on GitHub.
|
||||
|
||||
Community support is available for all versions, with the understanding that in some cases "upgrade to a newer version" may be the support offered.
|
||||
If you've installed Sanoid from your distribution's repositories, we're happy to offer community support with the same caveat!
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you believe you've found a serious security vulnerability in Sanoid, please create an Issue here on GitHub. If you prefer a private contact channel to disclose
|
||||
particularly sensitive or private details, you may request one in the GitHub Issue you create.
|
||||
3
findoid
3
findoid
|
|
@ -25,6 +25,9 @@ if ($args{'path'} eq '') {
|
|||
}
|
||||
}
|
||||
|
||||
# resolve given path to a canonical one
|
||||
$args{'path'} = Cwd::realpath($args{'path'});
|
||||
|
||||
my $dataset = getdataset($args{'path'});
|
||||
|
||||
my %versions = getversions($args{'path'}, $dataset);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ Package: sanoid
|
|||
Architecture: all
|
||||
Depends: libcapture-tiny-perl,
|
||||
libconfig-inifiles-perl,
|
||||
zfsutils-linux | zfs,
|
||||
zfsutils-linux | zfs | openzfs-zfsutils,
|
||||
${misc:Depends},
|
||||
${perl:Depends}
|
||||
Recommends: gzip,
|
||||
|
|
|
|||
142
sanoid
142
sanoid
|
|
@ -46,26 +46,70 @@ my $zpool = 'zpool';
|
|||
my $conf_file = "$args{'configdir'}/sanoid.conf";
|
||||
my $default_conf_file = "$args{'configdir'}/sanoid.defaults.conf";
|
||||
|
||||
# parse config file
|
||||
my %config = init($conf_file,$default_conf_file);
|
||||
|
||||
my $cache_dir = $args{'cache-dir'};
|
||||
my $run_dir = $args{'run-dir'};
|
||||
|
||||
make_path($cache_dir);
|
||||
make_path($run_dir);
|
||||
|
||||
# if we call getsnaps(%config,1) it will forcibly update the cache, TTL or no TTL
|
||||
my $forcecacheupdate = 0;
|
||||
my $cacheTTL = 1200; # 20 minutes
|
||||
|
||||
# Allow a much older snapshot cache file than default if _only_ "--monitor-*" action commands are given
|
||||
# (ignore "--verbose", "--configdir" etc)
|
||||
if (
|
||||
(
|
||||
$args{'monitor-snapshots'}
|
||||
|| $args{'monitor-health'}
|
||||
|| $args{'monitor-capacity'}
|
||||
) && ! (
|
||||
$args{'cron'}
|
||||
|| $args{'force-update'}
|
||||
|| $args{'take-snapshots'}
|
||||
|| $args{'prune-snapshots'}
|
||||
|| $args{'force-prune'}
|
||||
)
|
||||
) {
|
||||
# The command combination above must not assert true for any command that takes or prunes snapshots
|
||||
$cacheTTL = 18000; # 5 hours
|
||||
if ($args{'debug'}) { print "DEBUG: command combo means that the cache file (provided it exists) will be allowed to be older than default.\n"; }
|
||||
}
|
||||
|
||||
# snapshot cache
|
||||
my $cache = "$cache_dir/snapshots.txt";
|
||||
my $cacheTTL = 900; # 15 minutes
|
||||
my %snaps = getsnaps( \%config, $cacheTTL, $forcecacheupdate );
|
||||
|
||||
# configured dataset cache
|
||||
my $cachedatasetspath = "$cache_dir/datasets.txt";
|
||||
my @cachedatasets;
|
||||
|
||||
# parse config file
|
||||
my %config = init($conf_file,$default_conf_file);
|
||||
|
||||
my %pruned;
|
||||
my %capacitycache;
|
||||
|
||||
my %snapsbytype = getsnapsbytype( \%config, \%snaps );
|
||||
my %snaps;
|
||||
my %snapsbytype;
|
||||
my %snapsbypath;
|
||||
|
||||
my %snapsbypath = getsnapsbypath( \%config, \%snaps );
|
||||
# get snapshot list only if needed
|
||||
if ($args{'monitor-snapshots'}
|
||||
|| $args{'monitor-health'}
|
||||
|| $args{'cron'}
|
||||
|| $args{'take-snapshots'}
|
||||
|| $args{'prune-snapshots'}
|
||||
|| $args{'force-update'}
|
||||
|| $args{'debug'}
|
||||
) {
|
||||
my $forcecacheupdate = 0;
|
||||
if ($args{'force-update'}) {
|
||||
$forcecacheupdate = 1;
|
||||
}
|
||||
|
||||
%snaps = getsnaps( \%config, $cacheTTL, $forcecacheupdate);
|
||||
|
||||
%snapsbytype = getsnapsbytype( \%config, \%snaps );
|
||||
%snapsbypath = getsnapsbypath( \%config, \%snaps );
|
||||
}
|
||||
|
||||
# let's make it a little easier to be consistent passing these hashes in the same order to each sub
|
||||
my @params = ( \%config, \%snaps, \%snapsbytype, \%snapsbypath );
|
||||
|
|
@ -74,7 +118,6 @@ if ($args{'debug'}) { $args{'verbose'}=1; blabber (@params); }
|
|||
if ($args{'monitor-snapshots'}) { monitor_snapshots(@params); }
|
||||
if ($args{'monitor-health'}) { monitor_health(@params); }
|
||||
if ($args{'monitor-capacity'}) { monitor_capacity(@params); }
|
||||
if ($args{'force-update'}) { my $snaps = getsnaps( \%config, $cacheTTL, 1 ); }
|
||||
|
||||
if ($args{'cron'}) {
|
||||
if ($args{'quiet'}) { $args{'verbose'} = 0; }
|
||||
|
|
@ -265,7 +308,6 @@ sub prune_snapshots {
|
|||
my ($config, $snaps, $snapsbytype, $snapsbypath) = @_;
|
||||
|
||||
my %datestamp = get_date();
|
||||
my $forcecacheupdate = 0;
|
||||
|
||||
foreach my $section (keys %config) {
|
||||
if ($section =~ /^template/) { next; }
|
||||
|
|
@ -816,7 +858,7 @@ sub getsnaps {
|
|||
if (checklock('sanoid_cacheupdate')) {
|
||||
writelock('sanoid_cacheupdate');
|
||||
if ($args{'verbose'}) {
|
||||
if ($args{'force-update'}) {
|
||||
if ($forcecacheupdate) {
|
||||
print "INFO: cache forcibly expired - updating from zfs list.\n";
|
||||
} else {
|
||||
print "INFO: cache expired - updating from zfs list.\n";
|
||||
|
|
@ -826,9 +868,10 @@ sub getsnaps {
|
|||
@rawsnaps = <FH>;
|
||||
close FH;
|
||||
|
||||
open FH, "> $cache" or die 'Could not write to $cache!\n';
|
||||
open FH, "> $cache.tmp" or die 'Could not write to $cache.tmp!\n';
|
||||
print FH @rawsnaps;
|
||||
close FH;
|
||||
rename("$cache.tmp", "$cache") or die 'Could not rename to $cache!\n';
|
||||
removelock('sanoid_cacheupdate');
|
||||
} else {
|
||||
if ($args{'verbose'}) { print "INFO: deferring cache update - valid cache update lock held by another sanoid process.\n"; }
|
||||
|
|
@ -891,6 +934,20 @@ sub init {
|
|||
die "FATAL: you're using sanoid.defaults.conf v$defaults_version, this version of sanoid requires a minimum sanoid.defaults.conf v$MINIMUM_DEFAULTS_VERSION";
|
||||
}
|
||||
|
||||
my @updatedatasets;
|
||||
|
||||
# load dataset cache if valid
|
||||
if (!$args{'force-update'} && -f $cachedatasetspath) {
|
||||
my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($cachedatasetspath);
|
||||
|
||||
if ((time() - $mtime) <= $cacheTTL) {
|
||||
if ($args{'debug'}) { print "DEBUG: dataset cache not expired (" . (time() - $mtime) . " seconds old with TTL of $cacheTTL): pulling dataset list from cache.\n"; }
|
||||
open FH, "< $cachedatasetspath";
|
||||
@cachedatasets = <FH>;
|
||||
close FH;
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $section (keys %ini) {
|
||||
|
||||
# first up - die with honor if unknown parameters are set in any modules or templates by the user.
|
||||
|
|
@ -980,6 +1037,10 @@ sub init {
|
|||
$config{$section}{'path'} = $section;
|
||||
}
|
||||
|
||||
if (! @cachedatasets) {
|
||||
push (@updatedatasets, "$config{$section}{'path'}\n");
|
||||
}
|
||||
|
||||
# how 'bout some recursion? =)
|
||||
if ($config{$section}{'zfs_recursion'} && $config{$section}{'zfs_recursion'} == 1 && $config{$section}{'autosnap'} == 1) {
|
||||
warn "ignored autosnap configuration for '$section' because it's part of a zfs recursion.\n";
|
||||
|
|
@ -997,6 +1058,10 @@ sub init {
|
|||
|
||||
@datasets = getchilddatasets($config{$section}{'path'});
|
||||
DATASETS: foreach my $dataset(@datasets) {
|
||||
if (! @cachedatasets) {
|
||||
push (@updatedatasets, $dataset);
|
||||
}
|
||||
|
||||
chomp $dataset;
|
||||
|
||||
if ($zfsRecursive) {
|
||||
|
|
@ -1028,9 +1093,27 @@ sub init {
|
|||
$config{$dataset}{'initialized'} = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
# update dataset cache if it was unused
|
||||
if (! @cachedatasets) {
|
||||
if (checklock('sanoid_cachedatasetupdate')) {
|
||||
writelock('sanoid_cachedatasetupdate');
|
||||
if ($args{'verbose'}) {
|
||||
if ($args{'force-update'}) {
|
||||
print "INFO: dataset cache forcibly expired - updating from zfs list.\n";
|
||||
} else {
|
||||
print "INFO: dataset cache expired - updating from zfs list.\n";
|
||||
}
|
||||
}
|
||||
open FH, "> $cachedatasetspath.tmp" or die 'Could not write to $cachedatasetspath.tmp!\n';
|
||||
print FH @updatedatasets;
|
||||
close FH;
|
||||
rename("$cachedatasetspath.tmp", "$cachedatasetspath") or die 'Could not rename to $cachedatasetspath!\n';
|
||||
removelock('sanoid_cachedatasetupdate');
|
||||
} else {
|
||||
if ($args{'verbose'}) { print "INFO: deferring dataset cache update - valid cache update lock held by another sanoid process.\n"; }
|
||||
}
|
||||
}
|
||||
|
||||
return %config;
|
||||
|
|
@ -1580,6 +1663,30 @@ sub getchilddatasets {
|
|||
my $fs = shift;
|
||||
my $mysudocmd = '';
|
||||
|
||||
# use dataset cache if available
|
||||
if (@cachedatasets) {
|
||||
my $foundparent = 0;
|
||||
my @cachechildren = ();
|
||||
foreach my $dataset (@cachedatasets) {
|
||||
chomp $dataset;
|
||||
my $ret = rindex $dataset, "${fs}/", 0;
|
||||
if ($ret == 0) {
|
||||
push (@cachechildren, $dataset);
|
||||
} else {
|
||||
if ($dataset eq $fs) {
|
||||
$foundparent = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# sanity check
|
||||
if ($foundparent) {
|
||||
return @cachechildren;
|
||||
}
|
||||
|
||||
# fallback if cache misses items for whatever reason
|
||||
}
|
||||
|
||||
my $getchildrencmd = "$mysudocmd $zfs list -o name -t filesystem,volume -Hr $fs |";
|
||||
if ($args{'debug'}) { print "DEBUG: getting list of child datasets on $fs using $getchildrencmd...\n"; }
|
||||
open FH, $getchildrencmd;
|
||||
|
|
@ -1626,16 +1733,17 @@ sub removecachedsnapshots {
|
|||
my @rawsnaps = <FH>;
|
||||
close FH;
|
||||
|
||||
open FH, "> $cache" or die 'Could not write to $cache!\n';
|
||||
open FH, "> $cache.tmp" or die 'Could not write to $cache.tmp!\n';
|
||||
foreach my $snapline ( @rawsnaps ) {
|
||||
my @columns = split("\t", $snapline);
|
||||
my $snap = $columns[0];
|
||||
print FH $snapline unless ( exists($pruned{$snap}) );
|
||||
}
|
||||
close FH;
|
||||
rename("$cache.tmp", "$cache") or die 'Could not rename to $cache!\n';
|
||||
|
||||
removelock('sanoid_cacheupdate');
|
||||
%snaps = getsnaps(\%config,$cacheTTL,$forcecacheupdate);
|
||||
%snaps = getsnaps(\%config,$cacheTTL,0);
|
||||
|
||||
# clear hash
|
||||
undef %pruned;
|
||||
|
|
|
|||
|
|
@ -31,6 +31,10 @@
|
|||
# you can also handle datasets recursively in an atomic way without the possibility to override settings for child datasets.
|
||||
[zpoolname/parent2]
|
||||
use_template = production
|
||||
# there are two options for recursive: zfs or yes
|
||||
# * zfs - taken a zfs snapshot with the '-r' flag; zfs will recursively take a snapshot of the whole
|
||||
# dataset tree which is consistent.
|
||||
# * yes - the snapshots will be taken one-at-time through the sanoid code; not necessarily consistent.
|
||||
recursive = zfs
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
# run's all the available tests
|
||||
|
||||
for test in */; do
|
||||
for test in $(find . -mindepth 1 -maxdepth 1 -type d -printf "%P\n" | sort -g); do
|
||||
if [ ! -x "${test}/run.sh" ]; then
|
||||
continue
|
||||
fi
|
||||
|
|
@ -17,8 +17,11 @@ for test in */; do
|
|||
cd "${test}"
|
||||
echo -n y | bash run.sh > "${LOGFILE}" 2>&1
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
ret=$?
|
||||
if [ $ret -eq 0 ]; then
|
||||
echo "[PASS]"
|
||||
elif [ $ret -eq 130 ]; then
|
||||
echo "[SKIPPED]"
|
||||
else
|
||||
echo "[FAILED] (see ${LOGFILE})"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ zfs create -o mountpoint="${MOUNT_TARGET}" "${POOL_NAME}"/src
|
|||
|
||||
dd if=/dev/urandom of="${MOUNT_TARGET}"/big_file bs=1M count=200
|
||||
|
||||
sleep 1
|
||||
|
||||
../../../syncoid --debug --compress=none --source-bwlimit=2m "${POOL_NAME}"/src "${POOL_NAME}"/dst &
|
||||
syncoid_pid=$!
|
||||
sleep 5
|
||||
|
|
@ -28,6 +28,8 @@ zfs create -o mountpoint="${MOUNT_TARGET}" "${POOL_NAME}"/src
|
|||
|
||||
dd if=/dev/urandom of="${MOUNT_TARGET}"/big_file bs=1M count=200
|
||||
|
||||
sleep 1
|
||||
|
||||
zfs snapshot "${POOL_NAME}"/src@big
|
||||
../../../syncoid --debug --no-sync-snap --compress=none --source-bwlimit=2m "${POOL_NAME}"/src "${POOL_NAME}"/dst &
|
||||
syncoid_pid=$!
|
||||
|
|
@ -32,17 +32,17 @@ zfs create -o recordsize=32k "${POOL_NAME}"/src/32
|
|||
zfs create -o recordsize=128k "${POOL_NAME}"/src/128
|
||||
../../../syncoid --preserve-recordsize --recursive --debug --compress=none "${POOL_NAME}"/src "${POOL_NAME}"/dst
|
||||
|
||||
zfs get recordsize -t filesystem -r "${POOL_NAME}"/dst
|
||||
zfs get volblocksize -t volume -r "${POOL_NAME}"/dst
|
||||
zfs get -t filesystem -r recordsize "${POOL_NAME}"/dst
|
||||
zfs get -t volume -r volblocksize "${POOL_NAME}"/dst
|
||||
|
||||
if [ "$(zfs get recordsize -H -o value -t filesystem "${POOL_NAME}"/dst/16)" != "16K" ]; then
|
||||
if [ "$(zfs get -H -o value -t filesystem recordsize "${POOL_NAME}"/dst/16)" != "16K" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$(zfs get recordsize -H -o value -t filesystem "${POOL_NAME}"/dst/32)" != "32K" ]; then
|
||||
if [ "$(zfs get -H -o value -t filesystem recordsize "${POOL_NAME}"/dst/32)" != "32K" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$(zfs get recordsize -H -o value -t filesystem "${POOL_NAME}"/dst/128)" != "128K" ]; then
|
||||
if [ "$(zfs get -H -o value -t filesystem recordsize "${POOL_NAME}"/dst/128)" != "128K" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -29,38 +29,43 @@ zfs create -V 100M -o volblocksize=16k -o primarycache=all "${POOL_NAME}"/src/zv
|
|||
zfs create -V 100M -o volblocksize=64k "${POOL_NAME}"/src/zvol64
|
||||
zfs create -o recordsize=16k -o primarycache=none "${POOL_NAME}"/src/16
|
||||
zfs create -o recordsize=32k -o acltype=posixacl "${POOL_NAME}"/src/32
|
||||
zfs set 'net.openoid:var-name'='with whitespace and !"§$%&/()= symbols' "${POOL_NAME}"/src/32
|
||||
|
||||
../../../syncoid --preserve-properties --recursive --debug --compress=none "${POOL_NAME}"/src "${POOL_NAME}"/dst
|
||||
|
||||
|
||||
if [ "$(zfs get recordsize -H -o value -t filesystem "${POOL_NAME}"/dst)" != "16K" ]; then
|
||||
if [ "$(zfs get -H -o value -t filesystem recordsize "${POOL_NAME}"/dst)" != "16K" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$(zfs get mountpoint -H -o value -t filesystem "${POOL_NAME}"/dst)" != "none" ]; then
|
||||
if [ "$(zfs get -H -o value -t filesystem mountpoint "${POOL_NAME}"/dst)" != "none" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$(zfs get xattr -H -o value -t filesystem "${POOL_NAME}"/dst)" != "on" ]; then
|
||||
if [ "$(zfs get -H -o value -t filesystem xattr "${POOL_NAME}"/dst)" != "on" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$(zfs get primarycache -H -o value -t filesystem "${POOL_NAME}"/dst)" != "none" ]; then
|
||||
if [ "$(zfs get -H -o value -t filesystem primarycache "${POOL_NAME}"/dst)" != "none" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$(zfs get recordsize -H -o value -t filesystem "${POOL_NAME}"/dst/16)" != "16K" ]; then
|
||||
if [ "$(zfs get -H -o value -t filesystem recordsize "${POOL_NAME}"/dst/16)" != "16K" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$(zfs get primarycache -H -o value -t filesystem "${POOL_NAME}"/dst/16)" != "none" ]; then
|
||||
if [ "$(zfs get -H -o value -t filesystem primarycache "${POOL_NAME}"/dst/16)" != "none" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$(zfs get recordsize -H -o value -t filesystem "${POOL_NAME}"/dst/32)" != "32K" ]; then
|
||||
if [ "$(zfs get -H -o value -t filesystem recordsize "${POOL_NAME}"/dst/32)" != "32K" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$(zfs get acltype -H -o value -t filesystem "${POOL_NAME}"/dst/32)" != "posix" ]; then
|
||||
if [ "$(zfs get -H -o value -t filesystem acltype "${POOL_NAME}"/dst/32)" != "posix" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$(zfs get -H -o value -t filesystem 'net.openoid:var-name' "${POOL_NAME}"/dst/32)" != "with whitespace and !\"§$%&/()= symbols" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
#!/bin/bash
|
||||
|
||||
# test filtering snapshot names using --include-snaps and --exclude-snaps
|
||||
|
||||
set -x
|
||||
set -e
|
||||
|
||||
. ../../common/lib.sh
|
||||
|
||||
POOL_IMAGE="/tmp/syncoid-test-10.zpool"
|
||||
MOUNT_TARGET="/tmp/syncoid-test-10.mount"
|
||||
POOL_SIZE="100M"
|
||||
POOL_NAME="syncoid-test-10"
|
||||
|
||||
truncate -s "${POOL_SIZE}" "${POOL_IMAGE}"
|
||||
|
||||
zpool create -m none -f "${POOL_NAME}" "${POOL_IMAGE}"
|
||||
|
||||
#####
|
||||
# Create source snapshots and destroy the destination snaps and dataset.
|
||||
#####
|
||||
function setup_snaps {
|
||||
# create intermediate snapshots
|
||||
# sleep is needed so creation time can be used for proper sorting
|
||||
sleep 1
|
||||
zfs snapshot "${POOL_NAME}"/src@monthly1
|
||||
sleep 1
|
||||
zfs snapshot "${POOL_NAME}"/src@daily1
|
||||
sleep 1
|
||||
zfs snapshot "${POOL_NAME}"/src@daily2
|
||||
sleep 1
|
||||
zfs snapshot "${POOL_NAME}"/src@hourly1
|
||||
sleep 1
|
||||
zfs snapshot "${POOL_NAME}"/src@hourly2
|
||||
sleep 1
|
||||
zfs snapshot "${POOL_NAME}"/src@daily3
|
||||
sleep 1
|
||||
zfs snapshot "${POOL_NAME}"/src@hourly3
|
||||
sleep 1
|
||||
zfs snapshot "${POOL_NAME}"/src@hourly4
|
||||
}
|
||||
|
||||
#####
|
||||
# Remove the destination snapshots and dataset so that each test starts with a
|
||||
# blank slate.
|
||||
#####
|
||||
function clean_snaps {
|
||||
zfs destroy "${POOL_NAME}"/dst@%
|
||||
zfs destroy "${POOL_NAME}"/dst
|
||||
}
|
||||
|
||||
#####
|
||||
# Verify that the correct set of snapshots is present on the destination.
|
||||
#####
|
||||
function verify_checksum {
|
||||
zfs list -r -t snap "${POOL_NAME}"
|
||||
|
||||
checksum=$(zfs list -t snap -r -H -o name "${POOL_NAME}" | sed 's/@syncoid_.*/@syncoid_/' | shasum -a 256)
|
||||
|
||||
echo "Expected checksum: $1"
|
||||
echo "Actual checksum: $checksum"
|
||||
return $( [[ "$checksum" == "$1" ]] )
|
||||
}
|
||||
|
||||
function cleanUp {
|
||||
zpool export "${POOL_NAME}"
|
||||
}
|
||||
|
||||
# export pool in any case
|
||||
trap cleanUp EXIT
|
||||
|
||||
zfs create "${POOL_NAME}"/src
|
||||
setup_snaps
|
||||
|
||||
#####
|
||||
# TEST 1
|
||||
#
|
||||
# --exclude-snaps is provided and --no-stream is omitted. Hourly snaps should
|
||||
# be missing from the destination, and all other intermediate snaps should be
|
||||
# present.
|
||||
#####
|
||||
|
||||
../../../syncoid --debug --compress=none --no-sync-snap --exclude-snaps='hourly' "${POOL_NAME}"/src "${POOL_NAME}"/dst
|
||||
verify_checksum '494b6860415607f1d670e4106a10e1316924ba6cd31b4ddacffe0ad6d30a6339 -'
|
||||
clean_snaps
|
||||
|
||||
#####
|
||||
# TEST 2
|
||||
#
|
||||
# --exclude-snaps and --no-stream are provided. Only the daily3 snap should be
|
||||
# present on the destination.
|
||||
#####
|
||||
|
||||
../../../syncoid --debug --compress=none --no-sync-snap --exclude-snaps='hourly' --no-stream "${POOL_NAME}"/src "${POOL_NAME}"/dst
|
||||
verify_checksum '0a5072f42180d231cfdd678682972fbbb689140b7f3e996b3c348b7e78d67ea2 -'
|
||||
clean_snaps
|
||||
|
||||
#####
|
||||
# TEST 3
|
||||
#
|
||||
# --include-snaps is provided and --no-stream is omitted. Hourly snaps should
|
||||
# be present on the destination, and all other snaps should be missing
|
||||
#####
|
||||
|
||||
../../../syncoid --debug --compress=none --no-sync-snap --include-snaps='hourly' "${POOL_NAME}"/src "${POOL_NAME}"/dst
|
||||
verify_checksum 'd32862be4c71c6cde846322a7d006fd5e8edbd3520d3c7b73953492946debb7f -'
|
||||
clean_snaps
|
||||
|
||||
#####
|
||||
# TEST 4
|
||||
#
|
||||
# --include-snaps and --no-stream are provided. Only the hourly4 snap should
|
||||
# be present on the destination.
|
||||
#####
|
||||
|
||||
../../../syncoid --debug --compress=none --no-sync-snap --include-snaps='hourly' --no-stream "${POOL_NAME}"/src "${POOL_NAME}"/dst
|
||||
verify_checksum '81ef1a8298006a7ed856430bb7e05e8b85bbff530ca9dd7831f1da782f8aa4c7 -'
|
||||
clean_snaps
|
||||
|
||||
#####
|
||||
# TEST 5
|
||||
#
|
||||
# --include-snaps='hourly' and --exclude-snaps='3' are both provided. The
|
||||
# hourly snaps should be present on the destination except for hourly3; daily
|
||||
# and monthly snaps should be missing.
|
||||
#####
|
||||
|
||||
../../../syncoid --debug --compress=none --no-sync-snap --include-snaps='hourly' --exclude-snaps='3' "${POOL_NAME}"/src "${POOL_NAME}"/dst
|
||||
verify_checksum '5a9dd92b7d4b8760a1fcad03be843da4f43b915c64caffc1700c0d59a1581239 -'
|
||||
clean_snaps
|
||||
|
||||
#####
|
||||
# TEST 6
|
||||
#
|
||||
# --exclude-snaps='syncoid' and --no-stream are provided, and --no-sync-snap is
|
||||
# omitted. The sync snap should be created on the source but not sent to the
|
||||
# destination; only hourly4 should be sent.
|
||||
#####
|
||||
|
||||
../../../syncoid --debug --compress=none --no-stream --exclude-snaps='syncoid' "${POOL_NAME}"/src "${POOL_NAME}"/dst
|
||||
verify_checksum '9394fdac44ec72764a4673202552599684c83530a2a724dae5b411aaea082b02 -'
|
||||
clean_snaps
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
#!/bin/bash
|
||||
|
||||
# test verifying snapshots with out-of-order snapshot creation datetimes
|
||||
|
||||
set -x
|
||||
set -e
|
||||
|
||||
. ../../common/lib.sh
|
||||
|
||||
if [ -z "$ALLOW_INVASIVE_TESTS" ]; then
|
||||
exit 130
|
||||
fi
|
||||
|
||||
POOL_IMAGE="/tmp/syncoid-test-11.zpool"
|
||||
POOL_SIZE="64M"
|
||||
POOL_NAME="syncoid-test-11"
|
||||
|
||||
truncate -s "${POOL_SIZE}" "${POOL_IMAGE}"
|
||||
|
||||
zpool create -m none -f "${POOL_NAME}" "${POOL_IMAGE}"
|
||||
|
||||
function cleanUp {
|
||||
zpool export "${POOL_NAME}"
|
||||
rm -f "${POOL_IMAGE}"
|
||||
}
|
||||
|
||||
# export pool and remove the image in any case
|
||||
trap cleanUp EXIT
|
||||
|
||||
zfs create "${POOL_NAME}"/before
|
||||
zfs snapshot "${POOL_NAME}"/before@this-snapshot-should-make-it-into-the-after-dataset
|
||||
|
||||
disableTimeSync
|
||||
setdate 1155533696
|
||||
zfs snapshot "${POOL_NAME}"/before@oldest-snapshot
|
||||
|
||||
zfs snapshot "${POOL_NAME}"/before@another-snapshot-does-not-matter
|
||||
../../../syncoid --sendoptions="Lec" "${POOL_NAME}"/before "${POOL_NAME}"/after
|
||||
|
||||
# verify
|
||||
saveSnapshotList "${POOL_NAME}" "snapshot-list.txt"
|
||||
|
||||
grep "${POOL_NAME}/before@this-snapshot-should-make-it-into-the-after-dataset" "snapshot-list.txt" || exit $?
|
||||
grep "${POOL_NAME}/after@this-snapshot-should-make-it-into-the-after-dataset" "snapshot-list.txt" || exit $?
|
||||
grep "${POOL_NAME}/before@oldest-snapshot" "snapshot-list.txt" || exit $?
|
||||
grep "${POOL_NAME}/after@oldest-snapshot" "snapshot-list.txt" || exit $?
|
||||
grep "${POOL_NAME}/before@another-snapshot-does-not-matter" "snapshot-list.txt" || exit $?
|
||||
grep "${POOL_NAME}/after@another-snapshot-does-not-matter" "snapshot-list.txt" || exit $?
|
||||
|
||||
exit 0
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
# run's all the available tests
|
||||
|
||||
for test in */; do
|
||||
for test in $(find . -mindepth 1 -maxdepth 1 -type d -printf "%P\n" | sort -g); do
|
||||
if [ ! -x "${test}/run.sh" ]; then
|
||||
continue
|
||||
fi
|
||||
|
|
@ -17,8 +17,11 @@ for test in */; do
|
|||
cd "${test}"
|
||||
echo | bash run.sh > "${LOGFILE}" 2>&1
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
ret=$?
|
||||
if [ $ret -eq 0 ]; then
|
||||
echo "[PASS]"
|
||||
elif [ $ret -eq 130 ]; then
|
||||
echo "[SKIPPED]"
|
||||
else
|
||||
echo "[FAILED] (see ${LOGFILE})"
|
||||
fi
|
||||
|
|
|
|||
Loading…
Reference in New Issue