Compare commits

...

5 Commits

Author SHA1 Message Date
Nick L. 2b7e96ae92
Merge 191ddfe600 into 6beef5fee6 2025-02-13 22:34:35 +00:00
Jim Salter 6beef5fee6
Update INSTALL.md
add cd /tmp to Debian and Ubuntu package build instructions, to avoid newbies getting confused by limited permissions under eg /root

add reference to dependency on Capture::Tiny in sanoid and syncoid
2025-02-11 19:54:53 -05:00
Nick Liu 191ddfe600
perf(syncoid): Regression: Bad performance due to `zfs get all`
Return to using `zfs get guid,creation,createtxg` to avoid the
performance overhead of `zfs get all`.

There is a per-host global cache to remember:
* if `zfs get` supports the type filter argument, `-t snapshot` and
* which ZFS properties are available.

The feature check introduced by subroutine `check_zfs_get_features`
eliminates the need for a `getsnaps` fallback recursion, as there is no
longer any guesswork into what features `zfs get` supports.

Fixes:
https://github.com/jimsalterjrs/sanoid/pull/818#issuecomment-2157567968
2024-06-12 20:10:20 -05:00
Nick Liu a2adaf8499
fix(syncoid): Regression: `zfs get` parser not compatible with fallback
In the fallback mode, non-snapshot datasets would be included in the
output, leading to this parsing error:

```shell
# syncoid --debug root@192.168.122.26:rpool/before syncoid-test-11/after
… [TRUNCATED] …
WARNING: snapshot listing failed, trying fallback command
DEBUG: getting list of snapshots on rpool/before using ssh      -S   /tmp/syncoid-root19216812226-1718239740-763496-6973 root@192.168.122.26  zfs get -Hpd 1 all ''"'"'rpool/before'"'"'' |...
CRITICAL ERROR: Unexpected dataset format in rpool/before       type    filesystem      - at /usr/sbin/syncoid line 1914.
```

Fixes:
https://github.com/jimsalterjrs/sanoid/pull/818#issuecomment-2164155867
2024-06-12 20:05:47 -05:00
Nick Liu f16c15c9ba
fix(syncoid): Regression: Fallback recursion `$rhost` mutated already
An oversight during the merging of subroutine `getsnapsfallback` into
`getsnaps`
(https://github.com/jimsalterjrs/sanoid/pull/818/commits/e301b5b1)
caused the recursive call to prepend `$sshcmd` to `$rhost` again,
leading to this error:

```shell
# syncoid --debug root@192.168.122.26:rpool/before syncoid-test-11/after
… [TRUNCATED] …
WARNING: snapshot listing failed, trying fallback command
DEBUG: getting list of snapshots on rpool/before using ssh      ssh      -S   /tmp/syncoid-root19216812226-1718239169-761069-2198 root@192.168.122.26  zfs get -Hpd 1   all ''"'"'rpool/before'"'"'' |...
root@192.168.122.26: Command not found.
CRITICAL ERROR: snapshots couldn't be listed for rpool/before (exit code 256) at /usr/sbin/syncoid line 1900.
```

Fixes:
https://github.com/jimsalterjrs/sanoid/pull/818#issuecomment-2164155867
2024-06-12 20:02:49 -05:00
2 changed files with 69 additions and 20 deletions

View File

@ -26,9 +26,10 @@ apt install debhelper libcapture-tiny-perl libconfig-inifiles-perl pv lzop mbuff
```
Clone this repo, build the debian package and install it (alternatively you can skip the package and do it manually like described below for CentOS):
Clone this repo under /tmp (to make sure the apt user has access to the unpacked clone), build the debian package and install it (alternatively you can skip the package and do it manually like described below for CentOS):
```bash
cd /tmp
git clone https://github.com/jimsalterjrs/sanoid.git
cd sanoid
# checkout latest stable release or stay on master for bleeding edge stuff (but expect bugs!)
@ -73,6 +74,7 @@ cpan # answer the questions and paste the following lines:
Clone this repo, then put the executables and config files into the appropriate directories:
```bash
cd /tmp
# Download the repo as root to avoid changing permissions later
sudo git clone https://github.com/jimsalterjrs/sanoid.git
cd sanoid
@ -225,9 +227,9 @@ sudo launchctl load /Library/LaunchDaemons/net.openoid.Sanoid.plist
## Other OSes
**Sanoid** depends on the Perl module Config::IniFiles and will not operate without it. Config::IniFiles may be installed from CPAN, though the project strongly recommends using your distribution's repositories instead.
**Sanoid** depends on the Perl modules Config::IniFiles and Capture::Tiny and will not operate without them. These modules may be installed from CPAN, though the project strongly recommends using your distribution's repositories instead.
**Syncoid** depends on ssh, pv, gzip, lzop, and mbuffer. It can run with reduced functionality in the absence of any or all of the above. SSH is only required for remote synchronization. On newer FreeBSD and Ubuntu Xenial chacha20-poly1305@openssh.com, on other distributions arcfour crypto is the default for SSH transport since v1.4.6. Syncoid runs will fail if one of them is not available on either end of the transport.
**Syncoid** depends on ssh, pv, gzip, lzop, and mbuffer as well as sharing sanoid's dependency on Capture::Tiny. Capture::Tiny is mandatory, but syncoid can function with reduced functionality without any or all of the command-line dependencies. SSH is only required for remote synchronization. On newer FreeBSD and Ubuntu Xenial chacha20-poly1305@openssh.com, on other distributions arcfour crypto is the default for SSH transport since v1.4.6. Syncoid runs will fail if one of them is not available on either end of the transport.
### General outline for installation

81
syncoid
View File

@ -194,6 +194,9 @@ if (length $args{'insecure-direct-connection'}) {
# warn user of anything missing, then continue with sync.
my %avail = checkcommands();
# host => { supports_type_filter => 1/0, supported_properties => ['guid', 'creation', ...] }
my %host_zfs_get_features;
my %snaps;
my $exitcode = 0;
@ -415,10 +418,10 @@ sub syncdataset {
if (!defined($receivetoken)) {
# build hashes of the snaps on the source and target filesystems.
%snaps = getsnaps('source',$sourcehost,$sourcefs,$sourceisroot,0);
%snaps = getsnaps('source',$sourcehost,$sourcefs,$sourceisroot);
if ($targetexists) {
my %targetsnaps = getsnaps('target',$targethost,$targetfs,$targetisroot,0);
my %targetsnaps = getsnaps('target',$targethost,$targetfs,$targetisroot);
my %sourcesnaps = %snaps;
%snaps = (%sourcesnaps, %targetsnaps);
}
@ -858,10 +861,10 @@ sub syncdataset {
# snapshots first.
# regather snapshots on source and target
%snaps = getsnaps('source',$sourcehost,$sourcefs,$sourceisroot,0);
%snaps = getsnaps('source',$sourcehost,$sourcefs,$sourceisroot);
if ($targetexists) {
my %targetsnaps = getsnaps('target',$targethost,$targetfs,$targetisroot,0);
my %targetsnaps = getsnaps('target',$targethost,$targetfs,$targetisroot);
my %sourcesnaps = %snaps;
%snaps = (%sourcesnaps, %targetsnaps);
}
@ -1393,6 +1396,48 @@ sub checkcommands {
return %avail;
}
sub check_zfs_get_features {
my ($rhost, $mysudocmd, $zfscmd) = @_;
my $host = $rhost ? (split(/\s+/, $rhost))[-1] : "localhost";
return $host_zfs_get_features{$host} if exists $host_zfs_get_features{$host};
writelog('DEBUG', "Checking `zfs get` features on host \"$host\"...");
$host_zfs_get_features{$host} = {
supports_type_filter => 0,
supported_properties => []
};
my $check_t_option_cmd = "$rhost $mysudocmd $zfscmd get -H -t snapshot '' ''";
open my $fh_t, "$check_t_option_cmd 2>&1 |";
my $output_t = <$fh_t>;
close $fh_t;
if ($output_t !~ /^\Qinvalid option\E/) {
$host_zfs_get_features{$host}->{supports_type_filter} = 1;
}
writelog('DEBUG', "Host \"$host\" has `zfs get -t`?: $host_zfs_get_features{$host}->{supports_type_filter}");
my @properties_to_check = ('guid', 'creation', 'createtxg');
foreach my $prop (@properties_to_check) {
my $check_prop_cmd = "$rhost $mysudocmd $zfscmd get -H $prop ''";
open my $fh_p, "$check_prop_cmd 2>&1 |";
my $output_p = <$fh_p>;
close $fh_p;
if ($output_p !~ /^\Qbad property list: invalid property\E/) {
push @{$host_zfs_get_features{$host}->{supported_properties}}, $prop;
}
}
writelog('DEBUG', "Host \"$host\" ZFS properties: @{$host_zfs_get_features{$host}->{supported_properties}}");
return $host_zfs_get_features{$host};
}
sub iszfsbusy {
my ($rhost,$fs,$isroot) = @_;
if ($rhost ne '') { $rhost = "$sshcmd $rhost"; }
@ -1869,7 +1914,7 @@ sub dumphash() {
}
sub getsnaps {
my ($type,$rhost,$fs,$isroot,$use_fallback,%snaps) = @_;
my ($type,$rhost,$fs,$isroot,%snaps) = @_;
my $mysudocmd;
my $fsescaped = escapeshellparam($fs);
if ($isroot) { $mysudocmd = ''; } else { $mysudocmd = $sudocmd; }
@ -1880,9 +1925,17 @@ sub getsnaps {
$fsescaped = escapeshellparam($fsescaped);
}
my $getsnapcmd = $use_fallback
? "$rhost $mysudocmd $zfscmd get -Hpd 1 all $fsescaped"
: "$rhost $mysudocmd $zfscmd get -Hpd 1 -t snapshot all $fsescaped";
my $host_features = check_zfs_get_features($rhost, $mysudocmd, $zfscmd);
my @properties = @{$host_features->{supported_properties}};
my $type_filter = "";
if ($host_features->{supports_type_filter}) {
$type_filter = "-t snapshot";
} else {
push @properties, 'type';
}
my $properties_string = join(',', @properties);
my $getsnapcmd = "$rhost $mysudocmd $zfscmd get -Hpd 1 $type_filter $properties_string $fsescaped";
if ($debug) {
$getsnapcmd = "$getsnapcmd |";
@ -1892,13 +1945,7 @@ sub getsnaps {
}
open FH, $getsnapcmd;
my @rawsnaps = <FH>;
close FH or do {
if (!$use_fallback) {
writelog('WARN', "snapshot listing failed, trying fallback command");
return getsnaps($type, $rhost, $fs, $isroot, 1, %snaps);
}
die "CRITICAL ERROR: snapshots couldn't be listed for $fs (exit code $?)";
};
close FH or die "CRITICAL ERROR: snapshots couldn't be listed for $fs (exit code $?)";
my %snap_data;
my %creationtimes;
@ -1909,7 +1956,7 @@ sub getsnaps {
die "CRITICAL ERROR: Unexpected line format in $line" unless defined $value;
my (undef, $snap) = split /@/, $dataset;
die "CRITICAL ERROR: Unexpected dataset format in $line" unless $snap;
next unless length $snap;
if (!snapisincluded($snap)) { next; }
@ -1936,7 +1983,7 @@ sub getsnaps {
}
for my $snap (keys %snap_data) {
if (!$use_fallback || $snap_data{$snap}{'type'} eq 'snapshot') {
if (length $type_filter || $snap_data{$snap}{'type'} eq 'snapshot') {
$snaps{$type}{$snap}{'guid'} = $snap_data{$snap}{'guid'};
$snaps{$type}{$snap}{'createtxg'} = $snap_data{$snap}{'createtxg'};
$snaps{$type}{$snap}{'creation'} = $snap_data{$snap}{'creation'};