auto-format code using perltidy with Proxmox style guide
using the new top-level `make tidy` target, which calls perltidy via our wrapper to enforce the desired style as closely as possible. Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@ -16,7 +16,7 @@ use base qw(PVE::Storage::Plugin);
|
||||
sub cifs_is_mounted : prototype($$) {
|
||||
my ($scfg, $mountdata) = @_;
|
||||
|
||||
my ($mountpoint, $server, $share) = $scfg->@{'path', 'server', 'share'};
|
||||
my ($mountpoint, $server, $share) = $scfg->@{ 'path', 'server', 'share' };
|
||||
my $subdir = $scfg->{subdir} // '';
|
||||
|
||||
$server = "[$server]" if Net::IP::ip_is_ipv6($server);
|
||||
@ -24,9 +24,9 @@ sub cifs_is_mounted : prototype($$) {
|
||||
$mountdata = PVE::ProcFSTools::parse_proc_mounts() if !$mountdata;
|
||||
|
||||
return $mountpoint if grep {
|
||||
$_->[2] =~ /^cifs/ &&
|
||||
$_->[0] =~ m|^\Q$source\E/?$| &&
|
||||
$_->[1] eq $mountpoint
|
||||
$_->[2] =~ /^cifs/
|
||||
&& $_->[0] =~ m|^\Q$source\E/?$|
|
||||
&& $_->[1] eq $mountpoint
|
||||
} @$mountdata;
|
||||
return undef;
|
||||
}
|
||||
@ -40,7 +40,7 @@ sub cifs_delete_credentials {
|
||||
my ($storeid) = @_;
|
||||
|
||||
if (my $cred_file = get_cred_file($storeid)) {
|
||||
unlink($cred_file) or warn "removing cifs credientials '$cred_file' failed: $!\n";
|
||||
unlink($cred_file) or warn "removing cifs credientials '$cred_file' failed: $!\n";
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@ sub get_cred_file {
|
||||
my $cred_file = cifs_cred_file_name($storeid);
|
||||
|
||||
if (-e $cred_file) {
|
||||
return $cred_file;
|
||||
return $cred_file;
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
@ -69,7 +69,7 @@ sub get_cred_file {
|
||||
sub cifs_mount : prototype($$$$$) {
|
||||
my ($scfg, $storeid, $smbver, $user, $domain) = @_;
|
||||
|
||||
my ($mountpoint, $server, $share, $options) = $scfg->@{'path', 'server', 'share', 'options'};
|
||||
my ($mountpoint, $server, $share, $options) = $scfg->@{ 'path', 'server', 'share', 'options' };
|
||||
my $subdir = $scfg->{subdir} // '';
|
||||
|
||||
$server = "[$server]" if Net::IP::ip_is_ipv6($server);
|
||||
@ -78,10 +78,10 @@ sub cifs_mount : prototype($$$$$) {
|
||||
my $cmd = ['/bin/mount', '-t', 'cifs', $source, $mountpoint, '-o', 'soft', '-o'];
|
||||
|
||||
if (my $cred_file = get_cred_file($storeid)) {
|
||||
push @$cmd, "username=$user", '-o', "credentials=$cred_file";
|
||||
push @$cmd, '-o', "domain=$domain" if defined($domain);
|
||||
push @$cmd, "username=$user", '-o', "credentials=$cred_file";
|
||||
push @$cmd, '-o', "domain=$domain" if defined($domain);
|
||||
} else {
|
||||
push @$cmd, 'guest,username=guest';
|
||||
push @$cmd, 'guest,username=guest';
|
||||
}
|
||||
|
||||
push @$cmd, '-o', defined($smbver) ? "vers=$smbver" : "vers=default";
|
||||
@ -98,69 +98,79 @@ sub type {
|
||||
|
||||
sub plugindata {
|
||||
return {
|
||||
content => [ { images => 1, rootdir => 1, vztmpl => 1, iso => 1,
|
||||
backup => 1, snippets => 1, import => 1}, { images => 1 }],
|
||||
format => [ { raw => 1, qcow2 => 1, vmdk => 1 } , 'raw' ],
|
||||
'sensitive-properties' => { password => 1 },
|
||||
content => [
|
||||
{
|
||||
images => 1,
|
||||
rootdir => 1,
|
||||
vztmpl => 1,
|
||||
iso => 1,
|
||||
backup => 1,
|
||||
snippets => 1,
|
||||
import => 1,
|
||||
},
|
||||
{ images => 1 },
|
||||
],
|
||||
format => [{ raw => 1, qcow2 => 1, vmdk => 1 }, 'raw'],
|
||||
'sensitive-properties' => { password => 1 },
|
||||
};
|
||||
}
|
||||
|
||||
sub properties {
|
||||
return {
|
||||
share => {
|
||||
description => "CIFS share.",
|
||||
type => 'string',
|
||||
},
|
||||
password => {
|
||||
description => "Password for accessing the share/datastore.",
|
||||
type => 'string',
|
||||
maxLength => 256,
|
||||
},
|
||||
domain => {
|
||||
description => "CIFS domain.",
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
maxLength => 256,
|
||||
},
|
||||
smbversion => {
|
||||
description => "SMB protocol version. 'default' if not set, negotiates the highest SMB2+"
|
||||
." version supported by both the client and server.",
|
||||
type => 'string',
|
||||
default => 'default',
|
||||
enum => ['default', '2.0', '2.1', '3', '3.0', '3.11'],
|
||||
optional => 1,
|
||||
},
|
||||
share => {
|
||||
description => "CIFS share.",
|
||||
type => 'string',
|
||||
},
|
||||
password => {
|
||||
description => "Password for accessing the share/datastore.",
|
||||
type => 'string',
|
||||
maxLength => 256,
|
||||
},
|
||||
domain => {
|
||||
description => "CIFS domain.",
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
maxLength => 256,
|
||||
},
|
||||
smbversion => {
|
||||
description =>
|
||||
"SMB protocol version. 'default' if not set, negotiates the highest SMB2+"
|
||||
. " version supported by both the client and server.",
|
||||
type => 'string',
|
||||
default => 'default',
|
||||
enum => ['default', '2.0', '2.1', '3', '3.0', '3.11'],
|
||||
optional => 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
sub options {
|
||||
return {
|
||||
path => { fixed => 1 },
|
||||
'content-dirs' => { optional => 1 },
|
||||
server => { fixed => 1 },
|
||||
share => { fixed => 1 },
|
||||
subdir => { optional => 1 },
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
maxfiles => { optional => 1 },
|
||||
'prune-backups' => { optional => 1 },
|
||||
'max-protected-backups' => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
format => { optional => 1 },
|
||||
username => { optional => 1 },
|
||||
password => { optional => 1},
|
||||
domain => { optional => 1},
|
||||
smbversion => { optional => 1},
|
||||
mkdir => { optional => 1 },
|
||||
'create-base-path' => { optional => 1 },
|
||||
'create-subdirs' => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
preallocation => { optional => 1 },
|
||||
options => { optional => 1 },
|
||||
path => { fixed => 1 },
|
||||
'content-dirs' => { optional => 1 },
|
||||
server => { fixed => 1 },
|
||||
share => { fixed => 1 },
|
||||
subdir => { optional => 1 },
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
maxfiles => { optional => 1 },
|
||||
'prune-backups' => { optional => 1 },
|
||||
'max-protected-backups' => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
format => { optional => 1 },
|
||||
username => { optional => 1 },
|
||||
password => { optional => 1 },
|
||||
domain => { optional => 1 },
|
||||
smbversion => { optional => 1 },
|
||||
mkdir => { optional => 1 },
|
||||
'create-base-path' => { optional => 1 },
|
||||
'create-subdirs' => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
preallocation => { optional => 1 },
|
||||
options => { optional => 1 },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
sub check_config {
|
||||
my ($class, $sectionId, $config, $create, $skipSchemaCheck) = @_;
|
||||
|
||||
@ -175,12 +185,12 @@ sub on_add_hook {
|
||||
my ($class, $storeid, $scfg, %sensitive) = @_;
|
||||
|
||||
if (defined($sensitive{password})) {
|
||||
cifs_set_credentials($sensitive{password}, $storeid);
|
||||
if (!exists($scfg->{username})) {
|
||||
warn "storage $storeid: ignoring password parameter, no user set\n";
|
||||
}
|
||||
cifs_set_credentials($sensitive{password}, $storeid);
|
||||
if (!exists($scfg->{username})) {
|
||||
warn "storage $storeid: ignoring password parameter, no user set\n";
|
||||
}
|
||||
} else {
|
||||
cifs_delete_credentials($storeid);
|
||||
cifs_delete_credentials($storeid);
|
||||
}
|
||||
|
||||
return;
|
||||
@ -192,12 +202,12 @@ sub on_update_hook {
|
||||
return if !exists($sensitive{password});
|
||||
|
||||
if (defined($sensitive{password})) {
|
||||
cifs_set_credentials($sensitive{password}, $storeid);
|
||||
if (!exists($scfg->{username})) {
|
||||
warn "storage $storeid: ignoring password parameter, no user set\n";
|
||||
}
|
||||
cifs_set_credentials($sensitive{password}, $storeid);
|
||||
if (!exists($scfg->{username})) {
|
||||
warn "storage $storeid: ignoring password parameter, no user set\n";
|
||||
}
|
||||
} else {
|
||||
cifs_delete_credentials($storeid);
|
||||
cifs_delete_credentials($storeid);
|
||||
}
|
||||
|
||||
return;
|
||||
@ -215,10 +225,10 @@ sub status {
|
||||
my ($class, $storeid, $scfg, $cache) = @_;
|
||||
|
||||
$cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
|
||||
if !$cache->{mountdata};
|
||||
if !$cache->{mountdata};
|
||||
|
||||
return undef
|
||||
if !cifs_is_mounted($scfg, $cache->{mountdata});
|
||||
if !cifs_is_mounted($scfg, $cache->{mountdata});
|
||||
|
||||
return $class->SUPER::status($storeid, $scfg, $cache);
|
||||
}
|
||||
@ -227,19 +237,18 @@ sub activate_storage {
|
||||
my ($class, $storeid, $scfg, $cache) = @_;
|
||||
|
||||
$cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
|
||||
if !$cache->{mountdata};
|
||||
if !$cache->{mountdata};
|
||||
|
||||
my $path = $scfg->{path};
|
||||
|
||||
if (!cifs_is_mounted($scfg, $cache->{mountdata})) {
|
||||
|
||||
$class->config_aware_base_mkdir($scfg, $path);
|
||||
$class->config_aware_base_mkdir($scfg, $path);
|
||||
|
||||
die "unable to activate storage '$storeid' - " .
|
||||
"directory '$path' does not exist\n" if ! -d $path;
|
||||
die "unable to activate storage '$storeid' - " . "directory '$path' does not exist\n"
|
||||
if !-d $path;
|
||||
|
||||
cifs_mount($scfg, $storeid, $scfg->{smbversion},
|
||||
$scfg->{username}, $scfg->{domain});
|
||||
cifs_mount($scfg, $storeid, $scfg->{smbversion}, $scfg->{username}, $scfg->{domain});
|
||||
}
|
||||
|
||||
$class->SUPER::activate_storage($storeid, $scfg, $cache);
|
||||
@ -249,45 +258,48 @@ sub deactivate_storage {
|
||||
my ($class, $storeid, $scfg, $cache) = @_;
|
||||
|
||||
$cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
|
||||
if !$cache->{mountdata};
|
||||
if !$cache->{mountdata};
|
||||
|
||||
my $path = $scfg->{path};
|
||||
|
||||
if (cifs_is_mounted($scfg, $cache->{mountdata})) {
|
||||
my $cmd = ['/bin/umount', $path];
|
||||
run_command($cmd, errmsg => 'umount error');
|
||||
my $cmd = ['/bin/umount', $path];
|
||||
run_command($cmd, errmsg => 'umount error');
|
||||
}
|
||||
}
|
||||
|
||||
sub check_connection {
|
||||
my ($class, $storeid, $scfg) = @_;
|
||||
|
||||
my $servicename = '//'.$scfg->{server}.'/'.$scfg->{share};
|
||||
my $servicename = '//' . $scfg->{server} . '/' . $scfg->{share};
|
||||
|
||||
my $cmd = ['/usr/bin/smbclient', $servicename, '-d', '0'];
|
||||
|
||||
if (defined($scfg->{smbversion}) && $scfg->{smbversion} ne 'default') {
|
||||
# max-protocol version, so basically only relevant for smb2 vs smb3
|
||||
push @$cmd, '-m', "smb" . int($scfg->{smbversion});
|
||||
# max-protocol version, so basically only relevant for smb2 vs smb3
|
||||
push @$cmd, '-m', "smb" . int($scfg->{smbversion});
|
||||
}
|
||||
|
||||
if (my $cred_file = get_cred_file($storeid)) {
|
||||
push @$cmd, '-U', $scfg->{username}, '-A', $cred_file;
|
||||
push @$cmd, '-W', $scfg->{domain} if $scfg->{domain};
|
||||
push @$cmd, '-U', $scfg->{username}, '-A', $cred_file;
|
||||
push @$cmd, '-W', $scfg->{domain} if $scfg->{domain};
|
||||
} else {
|
||||
push @$cmd, '-U', 'Guest','-N';
|
||||
push @$cmd, '-U', 'Guest', '-N';
|
||||
}
|
||||
push @$cmd, '-c', 'echo 1 0';
|
||||
|
||||
my $out_str;
|
||||
my $out = sub { $out_str .= shift };
|
||||
|
||||
eval { run_command($cmd, timeout => 10, outfunc => $out, errfunc => sub {}) };
|
||||
eval {
|
||||
run_command($cmd, timeout => 10, outfunc => $out, errfunc => sub { });
|
||||
};
|
||||
|
||||
if (my $err = $@) {
|
||||
die "$out_str\n" if defined($out_str) &&
|
||||
($out_str =~ m/NT_STATUS_(ACCESS_DENIED|INVALID_PARAMETER|LOGON_FAILURE)/);
|
||||
return 0;
|
||||
die "$out_str\n"
|
||||
if defined($out_str)
|
||||
&& ($out_str =~ m/NT_STATUS_(ACCESS_DENIED|INVALID_PARAMETER|LOGON_FAILURE)/);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
@ -27,13 +27,13 @@ sub cephfs_is_mounted {
|
||||
|
||||
$mountdata = PVE::ProcFSTools::parse_proc_mounts() if !$mountdata;
|
||||
return $mountpoint if grep {
|
||||
$_->[2] =~ m#^ceph|fuse\.ceph-fuse# &&
|
||||
$_->[0] =~ m#\Q:$subdir\E$|^ceph-fuse$# &&
|
||||
$_->[1] eq $mountpoint
|
||||
$_->[2] =~ m#^ceph|fuse\.ceph-fuse#
|
||||
&& $_->[0] =~ m#\Q:$subdir\E$|^ceph-fuse$#
|
||||
&& $_->[1] eq $mountpoint
|
||||
} @$mountdata;
|
||||
|
||||
warn "A filesystem is already mounted on $mountpoint\n"
|
||||
if grep { $_->[1] eq $mountpoint } @$mountdata;
|
||||
if grep { $_->[1] eq $mountpoint } @$mountdata;
|
||||
|
||||
return undef;
|
||||
}
|
||||
@ -42,12 +42,12 @@ sub cephfs_is_mounted {
|
||||
sub systemd_netmount {
|
||||
my ($where, $type, $what, $opts) = @_;
|
||||
|
||||
# don't do default deps, systemd v241 generator produces ordering deps on both
|
||||
# local-fs(-pre) and remote-fs(-pre) targets if we use the required _netdev
|
||||
# option. Over three corners this gets us an ordering cycle on shutdown, which
|
||||
# may make shutdown hang if the random cycle breaking hits the "wrong" unit to
|
||||
# delete.
|
||||
my $unit = <<"EOF";
|
||||
# don't do default deps, systemd v241 generator produces ordering deps on both
|
||||
# local-fs(-pre) and remote-fs(-pre) targets if we use the required _netdev
|
||||
# option. Over three corners this gets us an ordering cycle on shutdown, which
|
||||
# may make shutdown hang if the random cycle breaking hits the "wrong" unit to
|
||||
# delete.
|
||||
my $unit = <<"EOF";
|
||||
[Unit]
|
||||
Description=${where}
|
||||
DefaultDependencies=no
|
||||
@ -71,7 +71,7 @@ EOF
|
||||
file_set_contents($unit_path, $unit);
|
||||
|
||||
run_command(['systemctl', 'daemon-reload'], errmsg => "daemon-reload error")
|
||||
if $daemon_needs_reload;
|
||||
if $daemon_needs_reload;
|
||||
run_command(['systemctl', 'start', $unit_fn], errmsg => "mount error");
|
||||
|
||||
}
|
||||
@ -91,16 +91,16 @@ sub cephfs_mount {
|
||||
|
||||
my @opts = ();
|
||||
if ($scfg->{fuse}) {
|
||||
$type = 'fuse.ceph';
|
||||
push @opts, "ceph.id=$cmd_option->{userid}";
|
||||
push @opts, "ceph.keyfile=$secretfile" if defined($secretfile);
|
||||
push @opts, "ceph.conf=$configfile" if defined($configfile);
|
||||
push @opts, "ceph.client_fs=$fs_name" if defined($fs_name);
|
||||
$type = 'fuse.ceph';
|
||||
push @opts, "ceph.id=$cmd_option->{userid}";
|
||||
push @opts, "ceph.keyfile=$secretfile" if defined($secretfile);
|
||||
push @opts, "ceph.conf=$configfile" if defined($configfile);
|
||||
push @opts, "ceph.client_fs=$fs_name" if defined($fs_name);
|
||||
} else {
|
||||
push @opts, "name=$cmd_option->{userid}";
|
||||
push @opts, "secretfile=$secretfile" if defined($secretfile);
|
||||
push @opts, "conf=$configfile" if defined($configfile);
|
||||
push @opts, "fs=$fs_name" if defined($fs_name);
|
||||
push @opts, "name=$cmd_option->{userid}";
|
||||
push @opts, "secretfile=$secretfile" if defined($secretfile);
|
||||
push @opts, "conf=$configfile" if defined($configfile);
|
||||
push @opts, "fs=$fs_name" if defined($fs_name);
|
||||
}
|
||||
|
||||
push @opts, $scfg->{options} if $scfg->{options};
|
||||
@ -116,47 +116,48 @@ sub type {
|
||||
|
||||
sub plugindata {
|
||||
return {
|
||||
content => [ { vztmpl => 1, iso => 1, backup => 1, snippets => 1, import => 1 },
|
||||
{ backup => 1 }],
|
||||
'sensitive-properties' => { keyring => 1 },
|
||||
content =>
|
||||
[{ vztmpl => 1, iso => 1, backup => 1, snippets => 1, import => 1 }, { backup => 1 }],
|
||||
'sensitive-properties' => { keyring => 1 },
|
||||
};
|
||||
}
|
||||
|
||||
sub properties {
|
||||
return {
|
||||
fuse => {
|
||||
description => "Mount CephFS through FUSE.",
|
||||
type => 'boolean',
|
||||
},
|
||||
'fs-name' => {
|
||||
description => "The Ceph filesystem name.",
|
||||
type => 'string', format => 'pve-configid',
|
||||
},
|
||||
fuse => {
|
||||
description => "Mount CephFS through FUSE.",
|
||||
type => 'boolean',
|
||||
},
|
||||
'fs-name' => {
|
||||
description => "The Ceph filesystem name.",
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
sub options {
|
||||
return {
|
||||
path => { fixed => 1 },
|
||||
'content-dirs' => { optional => 1 },
|
||||
monhost => { optional => 1},
|
||||
nodes => { optional => 1 },
|
||||
subdir => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
options => { optional => 1 },
|
||||
username => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
format => { optional => 1 },
|
||||
mkdir => { optional => 1 },
|
||||
'create-base-path' => { optional => 1 },
|
||||
'create-subdirs' => { optional => 1 },
|
||||
fuse => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
maxfiles => { optional => 1 },
|
||||
keyring => { optional => 1 },
|
||||
'prune-backups' => { optional => 1 },
|
||||
'max-protected-backups' => { optional => 1 },
|
||||
'fs-name' => { optional => 1 },
|
||||
path => { fixed => 1 },
|
||||
'content-dirs' => { optional => 1 },
|
||||
monhost => { optional => 1 },
|
||||
nodes => { optional => 1 },
|
||||
subdir => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
options => { optional => 1 },
|
||||
username => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
format => { optional => 1 },
|
||||
mkdir => { optional => 1 },
|
||||
'create-base-path' => { optional => 1 },
|
||||
'create-subdirs' => { optional => 1 },
|
||||
fuse => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
maxfiles => { optional => 1 },
|
||||
keyring => { optional => 1 },
|
||||
'prune-backups' => { optional => 1 },
|
||||
'max-protected-backups' => { optional => 1 },
|
||||
'fs-name' => { optional => 1 },
|
||||
};
|
||||
}
|
||||
|
||||
@ -182,11 +183,11 @@ sub on_update_hook {
|
||||
my ($class, $storeid, $scfg, %param) = @_;
|
||||
|
||||
if (exists($param{keyring})) {
|
||||
if (defined($param{keyring})) {
|
||||
PVE::CephConfig::ceph_create_keyfile($scfg->{type}, $storeid, $param{keyring});
|
||||
} else {
|
||||
PVE::CephConfig::ceph_remove_keyfile($scfg->{type}, $storeid);
|
||||
}
|
||||
if (defined($param{keyring})) {
|
||||
PVE::CephConfig::ceph_create_keyfile($scfg->{type}, $storeid, $param{keyring});
|
||||
} else {
|
||||
PVE::CephConfig::ceph_remove_keyfile($scfg->{type}, $storeid);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
@ -215,14 +216,14 @@ sub activate_storage {
|
||||
|
||||
# NOTE: mkpath may hang if storage is mounted but not reachable
|
||||
if (!cephfs_is_mounted($scfg, $storeid, $cache->{mountdata})) {
|
||||
my $path = $scfg->{path};
|
||||
my $path = $scfg->{path};
|
||||
|
||||
$class->config_aware_base_mkdir($scfg, $path);
|
||||
$class->config_aware_base_mkdir($scfg, $path);
|
||||
|
||||
die "unable to activate storage '$storeid' - " .
|
||||
"directory '$path' does not exist\n" if ! -d $path;
|
||||
die "unable to activate storage '$storeid' - " . "directory '$path' does not exist\n"
|
||||
if !-d $path;
|
||||
|
||||
cephfs_mount($scfg, $storeid);
|
||||
cephfs_mount($scfg, $storeid);
|
||||
}
|
||||
|
||||
$class->SUPER::activate_storage($storeid, $scfg, $cache);
|
||||
@ -236,7 +237,7 @@ sub deactivate_storage {
|
||||
my $path = $scfg->{path};
|
||||
|
||||
if (cephfs_is_mounted($scfg, $storeid, $cache->{mountdata})) {
|
||||
run_command(['/bin/umount', $path], errmsg => 'umount error');
|
||||
run_command(['/bin/umount', $path], errmsg => 'umount error');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -50,11 +50,14 @@ Possible formats a guest image can have.
|
||||
# Those formats should either be allowed here or support for them should be phased out (at least in
|
||||
# the storage layer). Can still be added again in the future, should any plugin provider request it.
|
||||
|
||||
PVE::JSONSchema::register_standard_option('pve-storage-image-format', {
|
||||
type => 'string',
|
||||
enum => ['raw', 'qcow2', 'subvol', 'vmdk'],
|
||||
description => "Format of the image.",
|
||||
});
|
||||
PVE::JSONSchema::register_standard_option(
|
||||
'pve-storage-image-format',
|
||||
{
|
||||
type => 'string',
|
||||
enum => ['raw', 'qcow2', 'subvol', 'vmdk'],
|
||||
description => "Format of the image.",
|
||||
},
|
||||
);
|
||||
|
||||
=pod
|
||||
|
||||
@ -80,7 +83,7 @@ sub align_size_up : prototype($$) {
|
||||
my $padding = ($granularity - $size % $granularity) % $granularity;
|
||||
my $aligned_size = $size + $padding;
|
||||
print "size $size is not aligned to granularity $granularity, rounding up to $aligned_size\n"
|
||||
if $aligned_size != $size;
|
||||
if $aligned_size != $size;
|
||||
return $aligned_size;
|
||||
}
|
||||
|
||||
@ -103,7 +106,7 @@ sub deallocate : prototype($$$) {
|
||||
$length = int($length);
|
||||
|
||||
if (syscall(PVE::Syscall::fallocate, fileno($file_handle), $mode, $offset, $length) != 0) {
|
||||
die "fallocate: punch hole failed (offset: $offset, length: $length) - $!\n";
|
||||
die "fallocate: punch hole failed (offset: $offset, length: $length) - $!\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -24,66 +24,78 @@ sub type {
|
||||
|
||||
sub plugindata {
|
||||
return {
|
||||
content => [ { images => 1, rootdir => 1, vztmpl => 1, iso => 1, backup => 1, snippets => 1, none => 1, import => 1 },
|
||||
{ images => 1, rootdir => 1 }],
|
||||
format => [ { raw => 1, qcow2 => 1, vmdk => 1, subvol => 1 } , 'raw' ],
|
||||
'sensitive-properties' => {},
|
||||
content => [
|
||||
{
|
||||
images => 1,
|
||||
rootdir => 1,
|
||||
vztmpl => 1,
|
||||
iso => 1,
|
||||
backup => 1,
|
||||
snippets => 1,
|
||||
none => 1,
|
||||
import => 1,
|
||||
},
|
||||
{ images => 1, rootdir => 1 },
|
||||
],
|
||||
format => [{ raw => 1, qcow2 => 1, vmdk => 1, subvol => 1 }, 'raw'],
|
||||
'sensitive-properties' => {},
|
||||
};
|
||||
}
|
||||
|
||||
sub properties {
|
||||
return {
|
||||
path => {
|
||||
description => "File system path.",
|
||||
type => 'string', format => 'pve-storage-path',
|
||||
},
|
||||
mkdir => {
|
||||
description => "Create the directory if it doesn't exist and populate it with default sub-dirs."
|
||||
." NOTE: Deprecated, use the 'create-base-path' and 'create-subdirs' options instead.",
|
||||
type => 'boolean',
|
||||
default => 'yes',
|
||||
},
|
||||
'create-base-path' => {
|
||||
description => "Create the base directory if it doesn't exist.",
|
||||
type => 'boolean',
|
||||
default => 'yes',
|
||||
},
|
||||
'create-subdirs' => {
|
||||
description => "Populate the directory with the default structure.",
|
||||
type => 'boolean',
|
||||
default => 'yes',
|
||||
},
|
||||
is_mountpoint => {
|
||||
description =>
|
||||
"Assume the given path is an externally managed mountpoint " .
|
||||
"and consider the storage offline if it is not mounted. ".
|
||||
"Using a boolean (yes/no) value serves as a shortcut to using the target path in this field.",
|
||||
type => 'string',
|
||||
default => 'no',
|
||||
},
|
||||
bwlimit => get_standard_option('bwlimit'),
|
||||
path => {
|
||||
description => "File system path.",
|
||||
type => 'string',
|
||||
format => 'pve-storage-path',
|
||||
},
|
||||
mkdir => {
|
||||
description =>
|
||||
"Create the directory if it doesn't exist and populate it with default sub-dirs."
|
||||
. " NOTE: Deprecated, use the 'create-base-path' and 'create-subdirs' options instead.",
|
||||
type => 'boolean',
|
||||
default => 'yes',
|
||||
},
|
||||
'create-base-path' => {
|
||||
description => "Create the base directory if it doesn't exist.",
|
||||
type => 'boolean',
|
||||
default => 'yes',
|
||||
},
|
||||
'create-subdirs' => {
|
||||
description => "Populate the directory with the default structure.",
|
||||
type => 'boolean',
|
||||
default => 'yes',
|
||||
},
|
||||
is_mountpoint => {
|
||||
description => "Assume the given path is an externally managed mountpoint "
|
||||
. "and consider the storage offline if it is not mounted. "
|
||||
. "Using a boolean (yes/no) value serves as a shortcut to using the target path in this field.",
|
||||
type => 'string',
|
||||
default => 'no',
|
||||
},
|
||||
bwlimit => get_standard_option('bwlimit'),
|
||||
};
|
||||
}
|
||||
|
||||
sub options {
|
||||
return {
|
||||
path => { fixed => 1 },
|
||||
'content-dirs' => { optional => 1 },
|
||||
nodes => { optional => 1 },
|
||||
shared => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
maxfiles => { optional => 1 },
|
||||
'prune-backups' => { optional => 1 },
|
||||
'max-protected-backups' => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
format => { optional => 1 },
|
||||
mkdir => { optional => 1 },
|
||||
'create-base-path' => { optional => 1 },
|
||||
'create-subdirs' => { optional => 1 },
|
||||
is_mountpoint => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
preallocation => { optional => 1 },
|
||||
};
|
||||
path => { fixed => 1 },
|
||||
'content-dirs' => { optional => 1 },
|
||||
nodes => { optional => 1 },
|
||||
shared => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
maxfiles => { optional => 1 },
|
||||
'prune-backups' => { optional => 1 },
|
||||
'max-protected-backups' => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
format => { optional => 1 },
|
||||
mkdir => { optional => 1 },
|
||||
'create-base-path' => { optional => 1 },
|
||||
'create-subdirs' => { optional => 1 },
|
||||
is_mountpoint => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
preallocation => { optional => 1 },
|
||||
};
|
||||
}
|
||||
|
||||
# Storage implementation
|
||||
@ -106,7 +118,7 @@ sub parse_is_mountpoint {
|
||||
my $is_mp = $scfg->{is_mountpoint};
|
||||
return undef if !defined $is_mp;
|
||||
if (defined(my $bool = PVE::JSONSchema::parse_boolean($is_mp))) {
|
||||
return $bool ? $scfg->{path} : undef;
|
||||
return $bool ? $scfg->{path} : undef;
|
||||
}
|
||||
return $is_mp; # contains a path
|
||||
}
|
||||
@ -122,8 +134,8 @@ my $get_volume_notes_impl = sub {
|
||||
$path .= $class->SUPER::NOTES_EXT;
|
||||
|
||||
if (-f $path) {
|
||||
my $data = PVE::Tools::file_get_contents($path);
|
||||
return eval { decode('UTF-8', $data, 1) } // $data;
|
||||
my $data = PVE::Tools::file_get_contents($path);
|
||||
return eval { decode('UTF-8', $data, 1) } // $data;
|
||||
}
|
||||
|
||||
return '';
|
||||
@ -147,10 +159,10 @@ my $update_volume_notes_impl = sub {
|
||||
$path .= $class->SUPER::NOTES_EXT;
|
||||
|
||||
if (defined($notes) && $notes ne '') {
|
||||
my $encoded = encode('UTF-8', $notes);
|
||||
PVE::Tools::file_set_contents($path, $encoded);
|
||||
my $encoded = encode('UTF-8', $notes);
|
||||
PVE::Tools::file_set_contents($path, $encoded);
|
||||
} else {
|
||||
unlink $path or $! == ENOENT or die "could not delete notes - $!\n";
|
||||
unlink $path or $! == ENOENT or die "could not delete notes - $!\n";
|
||||
}
|
||||
return;
|
||||
};
|
||||
@ -166,15 +178,15 @@ sub get_volume_attribute {
|
||||
my ($class, $scfg, $storeid, $volname, $attribute) = @_;
|
||||
|
||||
if ($attribute eq 'notes') {
|
||||
return $get_volume_notes_impl->($class, $scfg, $storeid, $volname);
|
||||
return $get_volume_notes_impl->($class, $scfg, $storeid, $volname);
|
||||
}
|
||||
|
||||
my ($vtype) = $class->parse_volname($volname);
|
||||
return if $vtype ne 'backup';
|
||||
|
||||
if ($attribute eq 'protected') {
|
||||
my $path = $class->filesystem_path($scfg, $volname);
|
||||
return -e PVE::Storage::protection_file_path($path) ? 1 : 0;
|
||||
my $path = $class->filesystem_path($scfg, $volname);
|
||||
return -e PVE::Storage::protection_file_path($path) ? 1 : 0;
|
||||
}
|
||||
|
||||
return;
|
||||
@ -184,28 +196,29 @@ sub update_volume_attribute {
|
||||
my ($class, $scfg, $storeid, $volname, $attribute, $value) = @_;
|
||||
|
||||
if ($attribute eq 'notes') {
|
||||
return $update_volume_notes_impl->($class, $scfg, $storeid, $volname, $value);
|
||||
return $update_volume_notes_impl->($class, $scfg, $storeid, $volname, $value);
|
||||
}
|
||||
|
||||
my ($vtype) = $class->parse_volname($volname);
|
||||
die "only backups support attribute '$attribute'\n" if $vtype ne 'backup';
|
||||
|
||||
if ($attribute eq 'protected') {
|
||||
my $path = $class->filesystem_path($scfg, $volname);
|
||||
my $protection_path = PVE::Storage::protection_file_path($path);
|
||||
my $path = $class->filesystem_path($scfg, $volname);
|
||||
my $protection_path = PVE::Storage::protection_file_path($path);
|
||||
|
||||
return if !((-e $protection_path) xor $value); # protection status already correct
|
||||
return if !((-e $protection_path) xor $value); # protection status already correct
|
||||
|
||||
if ($value) {
|
||||
my $fh = IO::File->new($protection_path, O_CREAT, 0644)
|
||||
or die "unable to create protection file '$protection_path' - $!\n";
|
||||
close($fh);
|
||||
} else {
|
||||
unlink $protection_path or $! == ENOENT
|
||||
or die "could not delete protection file '$protection_path' - $!\n";
|
||||
}
|
||||
if ($value) {
|
||||
my $fh = IO::File->new($protection_path, O_CREAT, 0644)
|
||||
or die "unable to create protection file '$protection_path' - $!\n";
|
||||
close($fh);
|
||||
} else {
|
||||
unlink $protection_path
|
||||
or $! == ENOENT
|
||||
or die "could not delete protection file '$protection_path' - $!\n";
|
||||
}
|
||||
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
die "attribute '$attribute' is not supported for storage type '$scfg->{type}'\n";
|
||||
@ -215,16 +228,15 @@ sub status {
|
||||
my ($class, $storeid, $scfg, $cache) = @_;
|
||||
|
||||
if (defined(my $mp = parse_is_mountpoint($scfg))) {
|
||||
$cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
|
||||
if !$cache->{mountdata};
|
||||
$cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
|
||||
if !$cache->{mountdata};
|
||||
|
||||
return undef if !path_is_mounted($mp, $cache->{mountdata});
|
||||
return undef if !path_is_mounted($mp, $cache->{mountdata});
|
||||
}
|
||||
|
||||
return $class->SUPER::status($storeid, $scfg, $cache);
|
||||
}
|
||||
|
||||
|
||||
sub activate_storage {
|
||||
my ($class, $storeid, $scfg, $cache) = @_;
|
||||
|
||||
@ -232,8 +244,8 @@ sub activate_storage {
|
||||
|
||||
my $mp = parse_is_mountpoint($scfg);
|
||||
if (defined($mp) && !path_is_mounted($mp, $cache->{mountdata})) {
|
||||
die "unable to activate storage '$storeid' - " .
|
||||
"directory is expected to be a mount point but is not mounted: '$mp'\n";
|
||||
die "unable to activate storage '$storeid' - "
|
||||
. "directory is expected to be a mount point but is not mounted: '$mp'\n";
|
||||
}
|
||||
|
||||
$class->config_aware_base_mkdir($scfg, $path);
|
||||
@ -242,10 +254,11 @@ sub activate_storage {
|
||||
|
||||
sub check_config {
|
||||
my ($self, $sectionId, $config, $create, $skipSchemaCheck) = @_;
|
||||
my $opts = PVE::SectionConfig::check_config($self, $sectionId, $config, $create, $skipSchemaCheck);
|
||||
my $opts =
|
||||
PVE::SectionConfig::check_config($self, $sectionId, $config, $create, $skipSchemaCheck);
|
||||
return $opts if !$create;
|
||||
if ($opts->{path} !~ m|^/[-/a-zA-Z0-9_.@]+$|) {
|
||||
die "illegal path for directory storage: $opts->{path}\n";
|
||||
die "illegal path for directory storage: $opts->{path}\n";
|
||||
}
|
||||
# remove trailing slashes from path
|
||||
$opts->{path} = File::Spec->canonpath($opts->{path});
|
||||
@ -264,40 +277,40 @@ sub get_import_metadata {
|
||||
|
||||
my $isOva = 0;
|
||||
if ($fmt =~ m/^ova/) {
|
||||
$isOva = 1;
|
||||
push @$warnings, { type => 'ova-needs-extracting' };
|
||||
$isOva = 1;
|
||||
push @$warnings, { type => 'ova-needs-extracting' };
|
||||
}
|
||||
my $path = $class->path($scfg, $volname, $storeid, undef);
|
||||
my $res = PVE::GuestImport::OVF::parse_ovf($path, $isOva);
|
||||
my $disks = {};
|
||||
for my $disk ($res->{disks}->@*) {
|
||||
my $id = $disk->{disk_address};
|
||||
my $size = $disk->{virtual_size};
|
||||
my $path = $disk->{relative_path};
|
||||
my $volid;
|
||||
if ($isOva) {
|
||||
$volid = "$storeid:$volname/$path";
|
||||
} else {
|
||||
$volid = "$storeid:import/$path",
|
||||
}
|
||||
$disks->{$id} = {
|
||||
volid => $volid,
|
||||
defined($size) ? (size => $size) : (),
|
||||
};
|
||||
my $id = $disk->{disk_address};
|
||||
my $size = $disk->{virtual_size};
|
||||
my $path = $disk->{relative_path};
|
||||
my $volid;
|
||||
if ($isOva) {
|
||||
$volid = "$storeid:$volname/$path";
|
||||
} else {
|
||||
$volid = "$storeid:import/$path",;
|
||||
}
|
||||
$disks->{$id} = {
|
||||
volid => $volid,
|
||||
defined($size) ? (size => $size) : (),
|
||||
};
|
||||
}
|
||||
|
||||
if (defined($res->{qm}->{bios}) && $res->{qm}->{bios} eq 'ovmf') {
|
||||
$disks->{efidisk0} = 1;
|
||||
push @$warnings, { type => 'efi-state-lost', key => 'bios', value => 'ovmf' };
|
||||
$disks->{efidisk0} = 1;
|
||||
push @$warnings, { type => 'efi-state-lost', key => 'bios', value => 'ovmf' };
|
||||
}
|
||||
|
||||
return {
|
||||
type => 'vm',
|
||||
source => $volname,
|
||||
'create-args' => $res->{qm},
|
||||
'disks' => $disks,
|
||||
warnings => $warnings,
|
||||
net => $res->{net},
|
||||
type => 'vm',
|
||||
source => $volname,
|
||||
'create-args' => $res->{qm},
|
||||
'disks' => $disks,
|
||||
warnings => $warnings,
|
||||
net => $res->{net},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -22,43 +22,48 @@ my $get_active_server = sub {
|
||||
my $defaultserver = $scfg->{server} ? $scfg->{server} : 'localhost';
|
||||
|
||||
if ($return_default_if_offline && !defined($scfg->{server2})) {
|
||||
# avoid delays (there is no backup server anyways)
|
||||
return $defaultserver;
|
||||
# avoid delays (there is no backup server anyways)
|
||||
return $defaultserver;
|
||||
}
|
||||
|
||||
my $serverlist = [ $defaultserver ];
|
||||
my $serverlist = [$defaultserver];
|
||||
push @$serverlist, $scfg->{server2} if $scfg->{server2};
|
||||
|
||||
my $ctime = time();
|
||||
foreach my $server (@$serverlist) {
|
||||
my $stat = $server_test_results->{$server};
|
||||
return $server if $stat && $stat->{active} && (($ctime - $stat->{time}) <= 2);
|
||||
my $stat = $server_test_results->{$server};
|
||||
return $server if $stat && $stat->{active} && (($ctime - $stat->{time}) <= 2);
|
||||
}
|
||||
|
||||
foreach my $server (@$serverlist) {
|
||||
my $status = 0;
|
||||
my $status = 0;
|
||||
|
||||
if ($server && $server ne 'localhost' && $server ne '127.0.0.1' && $server ne '::1') {
|
||||
# ping the gluster daemon default port (24007) as heuristic
|
||||
$status = PVE::Network::tcp_ping($server, 24007, 2);
|
||||
if ($server && $server ne 'localhost' && $server ne '127.0.0.1' && $server ne '::1') {
|
||||
# ping the gluster daemon default port (24007) as heuristic
|
||||
$status = PVE::Network::tcp_ping($server, 24007, 2);
|
||||
|
||||
} else {
|
||||
} else {
|
||||
|
||||
my $parser = sub {
|
||||
my $line = shift;
|
||||
my $parser = sub {
|
||||
my $line = shift;
|
||||
|
||||
if ($line =~ m/Status: Started$/) {
|
||||
$status = 1;
|
||||
}
|
||||
};
|
||||
if ($line =~ m/Status: Started$/) {
|
||||
$status = 1;
|
||||
}
|
||||
};
|
||||
|
||||
my $cmd = ['/usr/sbin/gluster', 'volume', 'info', $scfg->{volume}];
|
||||
my $cmd = ['/usr/sbin/gluster', 'volume', 'info', $scfg->{volume}];
|
||||
|
||||
run_command($cmd, errmsg => "glusterfs error", errfunc => sub {}, outfunc => $parser);
|
||||
}
|
||||
run_command(
|
||||
$cmd,
|
||||
errmsg => "glusterfs error",
|
||||
errfunc => sub { },
|
||||
outfunc => $parser,
|
||||
);
|
||||
}
|
||||
|
||||
$server_test_results->{$server} = { time => time(), active => $status };
|
||||
return $server if $status;
|
||||
$server_test_results->{$server} = { time => time(), active => $status };
|
||||
return $server if $status;
|
||||
}
|
||||
|
||||
return $defaultserver if $return_default_if_offline;
|
||||
@ -72,9 +77,9 @@ sub glusterfs_is_mounted {
|
||||
$mountdata = PVE::ProcFSTools::parse_proc_mounts() if !$mountdata;
|
||||
|
||||
return $mountpoint if grep {
|
||||
$_->[2] eq 'fuse.glusterfs' &&
|
||||
$_->[0] =~ /^\S+:\Q$volume\E$/ &&
|
||||
$_->[1] eq $mountpoint
|
||||
$_->[2] eq 'fuse.glusterfs'
|
||||
&& $_->[0] =~ /^\S+:\Q$volume\E$/
|
||||
&& $_->[1] eq $mountpoint
|
||||
} @$mountdata;
|
||||
return undef;
|
||||
}
|
||||
@ -97,55 +102,57 @@ sub type {
|
||||
|
||||
sub plugindata {
|
||||
return {
|
||||
content => [ { images => 1, vztmpl => 1, iso => 1, backup => 1, snippets => 1, import => 1},
|
||||
{ images => 1 }],
|
||||
format => [ { raw => 1, qcow2 => 1, vmdk => 1 } , 'raw' ],
|
||||
'sensitive-properties' => {},
|
||||
content => [
|
||||
{ images => 1, vztmpl => 1, iso => 1, backup => 1, snippets => 1, import => 1 },
|
||||
{ images => 1 },
|
||||
],
|
||||
format => [{ raw => 1, qcow2 => 1, vmdk => 1 }, 'raw'],
|
||||
'sensitive-properties' => {},
|
||||
};
|
||||
}
|
||||
|
||||
sub properties {
|
||||
return {
|
||||
volume => {
|
||||
description => "Glusterfs Volume.",
|
||||
type => 'string',
|
||||
},
|
||||
server2 => {
|
||||
description => "Backup volfile server IP or DNS name.",
|
||||
type => 'string', format => 'pve-storage-server',
|
||||
requires => 'server',
|
||||
},
|
||||
transport => {
|
||||
description => "Gluster transport: tcp or rdma",
|
||||
type => 'string',
|
||||
enum => ['tcp', 'rdma', 'unix'],
|
||||
},
|
||||
volume => {
|
||||
description => "Glusterfs Volume.",
|
||||
type => 'string',
|
||||
},
|
||||
server2 => {
|
||||
description => "Backup volfile server IP or DNS name.",
|
||||
type => 'string',
|
||||
format => 'pve-storage-server',
|
||||
requires => 'server',
|
||||
},
|
||||
transport => {
|
||||
description => "Gluster transport: tcp or rdma",
|
||||
type => 'string',
|
||||
enum => ['tcp', 'rdma', 'unix'],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
sub options {
|
||||
return {
|
||||
path => { fixed => 1 },
|
||||
server => { optional => 1 },
|
||||
server2 => { optional => 1 },
|
||||
volume => { fixed => 1 },
|
||||
transport => { optional => 1 },
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
maxfiles => { optional => 1 },
|
||||
'prune-backups' => { optional => 1 },
|
||||
'max-protected-backups' => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
format => { optional => 1 },
|
||||
mkdir => { optional => 1 },
|
||||
'create-base-path' => { optional => 1 },
|
||||
'create-subdirs' => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
preallocation => { optional => 1 },
|
||||
path => { fixed => 1 },
|
||||
server => { optional => 1 },
|
||||
server2 => { optional => 1 },
|
||||
volume => { fixed => 1 },
|
||||
transport => { optional => 1 },
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
maxfiles => { optional => 1 },
|
||||
'prune-backups' => { optional => 1 },
|
||||
'max-protected-backups' => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
format => { optional => 1 },
|
||||
mkdir => { optional => 1 },
|
||||
'create-base-path' => { optional => 1 },
|
||||
'create-subdirs' => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
preallocation => { optional => 1 },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
sub check_config {
|
||||
my ($class, $sectionId, $config, $create, $skipSchemaCheck) = @_;
|
||||
|
||||
@ -169,31 +176,30 @@ sub parse_name_dir {
|
||||
sub path {
|
||||
my ($class, $scfg, $volname, $storeid, $snapname) = @_;
|
||||
|
||||
my ($vtype, $name, $vmid, undef, undef, $isBase, $format) =
|
||||
$class->parse_volname($volname);
|
||||
my ($vtype, $name, $vmid, undef, undef, $isBase, $format) = $class->parse_volname($volname);
|
||||
|
||||
# Note: qcow2/qed has internal snapshot, so path is always
|
||||
# the same (with or without snapshot => same file).
|
||||
die "can't snapshot this image format\n"
|
||||
if defined($snapname) && $format !~ m/^(qcow2|qed)$/;
|
||||
die "can't snapshot this image format\n"
|
||||
if defined($snapname) && $format !~ m/^(qcow2|qed)$/;
|
||||
|
||||
my $path = undef;
|
||||
if ($vtype eq 'images') {
|
||||
|
||||
my $server = &$get_active_server($scfg, 1);
|
||||
my $glustervolume = $scfg->{volume};
|
||||
my $transport = $scfg->{transport};
|
||||
my $protocol = "gluster";
|
||||
my $server = &$get_active_server($scfg, 1);
|
||||
my $glustervolume = $scfg->{volume};
|
||||
my $transport = $scfg->{transport};
|
||||
my $protocol = "gluster";
|
||||
|
||||
if ($transport) {
|
||||
$protocol = "gluster+$transport";
|
||||
}
|
||||
if ($transport) {
|
||||
$protocol = "gluster+$transport";
|
||||
}
|
||||
|
||||
$path = "$protocol://$server/$glustervolume/images/$vmid/$name";
|
||||
$path = "$protocol://$server/$glustervolume/images/$vmid/$name";
|
||||
|
||||
} else {
|
||||
my $dir = $class->get_subdir($scfg, $vtype);
|
||||
$path = "$dir/$name";
|
||||
my $dir = $class->get_subdir($scfg, $vtype);
|
||||
$path = "$dir/$name";
|
||||
}
|
||||
|
||||
return wantarray ? ($path, $vmid, $vtype) : $path;
|
||||
@ -205,7 +211,7 @@ sub clone_image {
|
||||
die "storage definition has no path\n" if !$scfg->{path};
|
||||
|
||||
my ($vtype, $basename, $basevmid, undef, undef, $isBase, $format) =
|
||||
$class->parse_volname($volname);
|
||||
$class->parse_volname($volname);
|
||||
|
||||
die "clone_image on wrong vtype '$vtype'\n" if $vtype ne 'images';
|
||||
|
||||
@ -232,8 +238,17 @@ sub clone_image {
|
||||
my $glustervolume = $scfg->{volume};
|
||||
my $volumepath = "gluster://$server/$glustervolume/images/$vmid/$name";
|
||||
|
||||
my $cmd = ['/usr/bin/qemu-img', 'create', '-b', "../$basevmid/$basename",
|
||||
'-F', $format, '-f', 'qcow2', $volumepath];
|
||||
my $cmd = [
|
||||
'/usr/bin/qemu-img',
|
||||
'create',
|
||||
'-b',
|
||||
"../$basevmid/$basename",
|
||||
'-F',
|
||||
$format,
|
||||
'-f',
|
||||
'qcow2',
|
||||
$volumepath,
|
||||
];
|
||||
|
||||
run_command($cmd, errmsg => "unable to create image");
|
||||
|
||||
@ -272,9 +287,9 @@ sub alloc_image {
|
||||
|
||||
eval { run_command($cmd, errmsg => "unable to create image"); };
|
||||
if ($@) {
|
||||
unlink $path;
|
||||
rmdir $imagedir;
|
||||
die "$@";
|
||||
unlink $path;
|
||||
rmdir $imagedir;
|
||||
die "$@";
|
||||
}
|
||||
|
||||
return "$vmid/$name";
|
||||
@ -284,7 +299,7 @@ sub status {
|
||||
my ($class, $storeid, $scfg, $cache) = @_;
|
||||
|
||||
$cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
|
||||
if !$cache->{mountdata};
|
||||
if !$cache->{mountdata};
|
||||
|
||||
my $path = $scfg->{path};
|
||||
|
||||
@ -299,20 +314,20 @@ sub activate_storage {
|
||||
my ($class, $storeid, $scfg, $cache) = @_;
|
||||
|
||||
$cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
|
||||
if !$cache->{mountdata};
|
||||
if !$cache->{mountdata};
|
||||
|
||||
my $path = $scfg->{path};
|
||||
my $volume = $scfg->{volume};
|
||||
|
||||
if (!glusterfs_is_mounted($volume, $path, $cache->{mountdata})) {
|
||||
$class->config_aware_base_mkdir($scfg, $path);
|
||||
$class->config_aware_base_mkdir($scfg, $path);
|
||||
|
||||
die "unable to activate storage '$storeid' - " .
|
||||
"directory '$path' does not exist\n" if ! -d $path;
|
||||
die "unable to activate storage '$storeid' - " . "directory '$path' does not exist\n"
|
||||
if !-d $path;
|
||||
|
||||
my $server = &$get_active_server($scfg, 1);
|
||||
my $server = &$get_active_server($scfg, 1);
|
||||
|
||||
glusterfs_mount($server, $volume, $path);
|
||||
glusterfs_mount($server, $volume, $path);
|
||||
}
|
||||
|
||||
$class->SUPER::activate_storage($storeid, $scfg, $cache);
|
||||
@ -322,14 +337,14 @@ sub deactivate_storage {
|
||||
my ($class, $storeid, $scfg, $cache) = @_;
|
||||
|
||||
$cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
|
||||
if !$cache->{mountdata};
|
||||
if !$cache->{mountdata};
|
||||
|
||||
my $path = $scfg->{path};
|
||||
my $volume = $scfg->{volume};
|
||||
|
||||
if (glusterfs_is_mounted($volume, $path, $cache->{mountdata})) {
|
||||
my $cmd = ['/bin/umount', $path];
|
||||
run_command($cmd, errmsg => 'umount error');
|
||||
my $cmd = ['/bin/umount', $path];
|
||||
run_command($cmd, errmsg => 'umount error');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -18,30 +18,36 @@ sub iscsi_ls {
|
||||
my ($scfg) = @_;
|
||||
|
||||
my $portal = $scfg->{portal};
|
||||
my $cmd = ['/usr/bin/iscsi-ls', '-s', 'iscsi://'.$portal ];
|
||||
my $cmd = ['/usr/bin/iscsi-ls', '-s', 'iscsi://' . $portal];
|
||||
my $list = {};
|
||||
my %unittobytes = (
|
||||
"k" => 1024,
|
||||
"M" => 1024*1024,
|
||||
"G" => 1024*1024*1024,
|
||||
"T" => 1024*1024*1024*1024
|
||||
"k" => 1024,
|
||||
"M" => 1024 * 1024,
|
||||
"G" => 1024 * 1024 * 1024,
|
||||
"T" => 1024 * 1024 * 1024 * 1024,
|
||||
);
|
||||
eval {
|
||||
run_command($cmd, errmsg => "iscsi error", errfunc => sub {}, outfunc => sub {
|
||||
my $line = shift;
|
||||
$line = trim($line);
|
||||
if( $line =~ /Lun:(\d+)\s+([A-Za-z0-9\-\_\.\:]*)\s+\(Size:([0-9\.]*)(k|M|G|T)\)/ ) {
|
||||
my $image = "lun".$1;
|
||||
my $size = $3;
|
||||
my $unit = $4;
|
||||
run_command(
|
||||
$cmd,
|
||||
errmsg => "iscsi error",
|
||||
errfunc => sub { },
|
||||
outfunc => sub {
|
||||
my $line = shift;
|
||||
$line = trim($line);
|
||||
if ($line =~ /Lun:(\d+)\s+([A-Za-z0-9\-\_\.\:]*)\s+\(Size:([0-9\.]*)(k|M|G|T)\)/
|
||||
) {
|
||||
my $image = "lun" . $1;
|
||||
my $size = $3;
|
||||
my $unit = $4;
|
||||
|
||||
$list->{$image} = {
|
||||
name => $image,
|
||||
size => $size * $unittobytes{$unit},
|
||||
format => 'raw',
|
||||
};
|
||||
}
|
||||
});
|
||||
$list->{$image} = {
|
||||
name => $image,
|
||||
size => $size * $unittobytes{$unit},
|
||||
format => 'raw',
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
my $err = $@;
|
||||
@ -58,9 +64,9 @@ sub type {
|
||||
|
||||
sub plugindata {
|
||||
return {
|
||||
content => [ {images => 1, none => 1}, { images => 1 }],
|
||||
select_existing => 1,
|
||||
'sensitive-properties' => {},
|
||||
content => [{ images => 1, none => 1 }, { images => 1 }],
|
||||
select_existing => 1,
|
||||
'sensitive-properties' => {},
|
||||
};
|
||||
}
|
||||
|
||||
@ -68,9 +74,9 @@ sub options {
|
||||
return {
|
||||
portal => { fixed => 1 },
|
||||
target => { fixed => 1 },
|
||||
nodes => { optional => 1},
|
||||
disable => { optional => 1},
|
||||
content => { optional => 1},
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
};
|
||||
}
|
||||
@ -80,9 +86,8 @@ sub options {
|
||||
sub parse_volname {
|
||||
my ($class, $volname) = @_;
|
||||
|
||||
|
||||
if ($volname =~ m/^lun(\d+)$/) {
|
||||
return ('images', $1, undef, undef, undef, undef, 'raw');
|
||||
return ('images', $1, undef, undef, undef, undef, 'raw');
|
||||
}
|
||||
|
||||
die "unable to parse iscsi volume name '$volname'\n";
|
||||
@ -93,7 +98,7 @@ sub path {
|
||||
my ($class, $scfg, $volname, $storeid, $snapname) = @_;
|
||||
|
||||
die "volume snapshot is not possible on iscsi device\n"
|
||||
if defined($snapname);
|
||||
if defined($snapname);
|
||||
|
||||
my ($vtype, $lun, $vmid) = $class->parse_volname($volname);
|
||||
|
||||
@ -138,20 +143,20 @@ sub list_images {
|
||||
|
||||
my $dat = iscsi_ls($scfg);
|
||||
foreach my $volname (keys %$dat) {
|
||||
my $volid = "$storeid:$volname";
|
||||
my $volid = "$storeid:$volname";
|
||||
|
||||
if ($vollist) {
|
||||
my $found = grep { $_ eq $volid } @$vollist;
|
||||
next if !$found;
|
||||
} else {
|
||||
# we have no owner for iscsi devices
|
||||
next if defined($vmid);
|
||||
}
|
||||
if ($vollist) {
|
||||
my $found = grep { $_ eq $volid } @$vollist;
|
||||
next if !$found;
|
||||
} else {
|
||||
# we have no owner for iscsi devices
|
||||
next if defined($vmid);
|
||||
}
|
||||
|
||||
my $info = $dat->{$volname};
|
||||
$info->{volid} = $volid;
|
||||
my $info = $dat->{$volname};
|
||||
$info->{volid} = $volid;
|
||||
|
||||
push @$res, $info;
|
||||
push @$res, $info;
|
||||
}
|
||||
|
||||
return $res;
|
||||
@ -164,7 +169,7 @@ sub status {
|
||||
my $free = 0;
|
||||
my $used = 0;
|
||||
my $active = 1;
|
||||
return ($total,$free,$used,$active);
|
||||
return ($total, $free, $used, $active);
|
||||
|
||||
return undef;
|
||||
}
|
||||
@ -228,17 +233,16 @@ sub volume_has_feature {
|
||||
my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
|
||||
|
||||
my $features = {
|
||||
copy => { current => 1},
|
||||
copy => { current => 1 },
|
||||
};
|
||||
|
||||
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
|
||||
$class->parse_volname($volname);
|
||||
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = $class->parse_volname($volname);
|
||||
|
||||
my $key = undef;
|
||||
if($snapname){
|
||||
$key = 'snap';
|
||||
}else{
|
||||
$key = $isBase ? 'base' : 'current';
|
||||
if ($snapname) {
|
||||
$key = 'snap';
|
||||
} else {
|
||||
$key = $isBase ? 'base' : 'current';
|
||||
}
|
||||
return 1 if $features->{$feature}->{$key};
|
||||
|
||||
@ -256,15 +260,15 @@ sub volume_export_formats {
|
||||
|
||||
sub volume_export {
|
||||
my (
|
||||
$class,
|
||||
$scfg,
|
||||
$storeid,
|
||||
$fh,
|
||||
$volname,
|
||||
$format,
|
||||
$snapshot,
|
||||
$base_snapshot,
|
||||
$with_snapshots,
|
||||
$class,
|
||||
$scfg,
|
||||
$storeid,
|
||||
$fh,
|
||||
$volname,
|
||||
$format,
|
||||
$snapshot,
|
||||
$base_snapshot,
|
||||
$with_snapshots,
|
||||
) = @_;
|
||||
|
||||
die "volume export format $format not available for $class\n" if $format ne 'raw+size';
|
||||
@ -276,8 +280,8 @@ sub volume_export {
|
||||
|
||||
my $json = '';
|
||||
run_command(
|
||||
['/usr/bin/qemu-img', 'info', '-f', 'raw', '--output=json', $file],
|
||||
outfunc => sub { $json .= shift },
|
||||
['/usr/bin/qemu-img', 'info', '-f', 'raw', '--output=json', $file],
|
||||
outfunc => sub { $json .= shift },
|
||||
);
|
||||
die "failed to query size information for '$file' with qemu-img\n" if !$json;
|
||||
my $info = eval { decode_json($json) };
|
||||
@ -289,8 +293,8 @@ sub volume_export {
|
||||
|
||||
PVE::Storage::Plugin::write_common_header($fh, $size);
|
||||
run_command(
|
||||
['qemu-img', 'dd', 'bs=64k', "if=$file", '-f', 'raw', '-O', 'raw'],
|
||||
output => '>&'.fileno($fh),
|
||||
['qemu-img', 'dd', 'bs=64k', "if=$file", '-f', 'raw', '-O', 'raw'],
|
||||
output => '>&' . fileno($fh),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -9,7 +9,8 @@ use IO::File;
|
||||
|
||||
use PVE::JSONSchema qw(get_standard_option);
|
||||
use PVE::Storage::Plugin;
|
||||
use PVE::Tools qw(run_command file_read_firstline trim dir_glob_regex dir_glob_foreach $IPV4RE $IPV6RE);
|
||||
use PVE::Tools
|
||||
qw(run_command file_read_firstline trim dir_glob_regex dir_glob_foreach $IPV4RE $IPV6RE);
|
||||
|
||||
use base qw(PVE::Storage::Plugin);
|
||||
|
||||
@ -25,8 +26,8 @@ my sub assert_iscsi_support {
|
||||
$found_iscsi_adm_exe = -x $ISCSIADM;
|
||||
|
||||
if (!$found_iscsi_adm_exe) {
|
||||
die "error: no iscsi support - please install open-iscsi\n" if !$noerr;
|
||||
warn "warning: no iscsi support - please install open-iscsi\n";
|
||||
die "error: no iscsi support - please install open-iscsi\n" if !$noerr;
|
||||
warn "warning: no iscsi support - please install open-iscsi\n";
|
||||
}
|
||||
return $found_iscsi_adm_exe;
|
||||
}
|
||||
@ -41,18 +42,24 @@ sub iscsi_session_list {
|
||||
|
||||
my $res = {};
|
||||
eval {
|
||||
run_command($cmd, errmsg => 'iscsi session scan failed', outfunc => sub {
|
||||
my $line = shift;
|
||||
# example: tcp: [1] 192.168.122.252:3260,1 iqn.2003-01.org.linux-iscsi.proxmox-nfs.x8664:sn.00567885ba8f (non-flash)
|
||||
if ($line =~ m/^tcp:\s+\[(\S+)\]\s+((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s+\S+?\s*$/) {
|
||||
my ($session_id, $portal, $target) = ($1, $2, $3);
|
||||
# there can be several sessions per target (multipath)
|
||||
push @{$res->{$target}}, { session_id => $session_id, portal => $portal };
|
||||
}
|
||||
});
|
||||
run_command(
|
||||
$cmd,
|
||||
errmsg => 'iscsi session scan failed',
|
||||
outfunc => sub {
|
||||
my $line = shift;
|
||||
# example: tcp: [1] 192.168.122.252:3260,1 iqn.2003-01.org.linux-iscsi.proxmox-nfs.x8664:sn.00567885ba8f (non-flash)
|
||||
if ($line =~
|
||||
m/^tcp:\s+\[(\S+)\]\s+((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s+\S+?\s*$/
|
||||
) {
|
||||
my ($session_id, $portal, $target) = ($1, $2, $3);
|
||||
# there can be several sessions per target (multipath)
|
||||
push @{ $res->{$target} }, { session_id => $session_id, portal => $portal };
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
if (my $err = $@) {
|
||||
die $err if $err !~ m/: No active sessions.$/i;
|
||||
die $err if $err !~ m/: No active sessions.$/i;
|
||||
}
|
||||
|
||||
return $res;
|
||||
@ -62,7 +69,7 @@ sub iscsi_test_session {
|
||||
my ($sid) = @_;
|
||||
|
||||
if ($sid !~ m/^[0-9]+$/) {
|
||||
die "session_id: '$sid' is not a number\n";
|
||||
die "session_id: '$sid' is not a number\n";
|
||||
}
|
||||
my $state = file_read_firstline("/sys/class/iscsi_session/session${sid}/state");
|
||||
return defined($state) && $state eq 'LOGGED_IN';
|
||||
@ -73,13 +80,13 @@ sub iscsi_test_portal {
|
||||
$cache //= {};
|
||||
|
||||
if (defined($target)) {
|
||||
# check session state instead if available
|
||||
my $sessions = iscsi_session($cache, $target);
|
||||
for my $session ($sessions->@*) {
|
||||
next if $session->{portal} ne $portal;
|
||||
my $state = iscsi_test_session($session->{session_id});
|
||||
return $state if $state;
|
||||
}
|
||||
# check session state instead if available
|
||||
my $sessions = iscsi_session($cache, $target);
|
||||
for my $session ($sessions->@*) {
|
||||
next if $session->{portal} ne $portal;
|
||||
my $state = iscsi_test_session($session->{session_id});
|
||||
return $state if $state;
|
||||
}
|
||||
}
|
||||
# check portal via tcp
|
||||
my ($server, $port) = PVE::Tools::parse_host_and_port($portal);
|
||||
@ -95,25 +102,28 @@ sub iscsi_portals {
|
||||
my $res = [];
|
||||
my $cmd = [$ISCSIADM, '--mode', 'node'];
|
||||
eval {
|
||||
run_command($cmd, outfunc => sub {
|
||||
my $line = shift;
|
||||
run_command(
|
||||
$cmd,
|
||||
outfunc => sub {
|
||||
my $line = shift;
|
||||
|
||||
if ($line =~ $ISCSI_TARGET_RE) {
|
||||
my ($portal, $portal_target) = ($1, $2);
|
||||
if ($portal_target eq $target) {
|
||||
push @{$res}, $portal;
|
||||
}
|
||||
}
|
||||
});
|
||||
if ($line =~ $ISCSI_TARGET_RE) {
|
||||
my ($portal, $portal_target) = ($1, $2);
|
||||
if ($portal_target eq $target) {
|
||||
push @{$res}, $portal;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
my $err = $@;
|
||||
warn $err if $err;
|
||||
|
||||
if ($err || !scalar(@$res)) {
|
||||
return [ $portal_in ];
|
||||
return [$portal_in];
|
||||
} else {
|
||||
return $res;
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,24 +134,27 @@ sub iscsi_discovery {
|
||||
|
||||
my $res = {};
|
||||
for my $portal ($portals->@*) {
|
||||
next if !iscsi_test_portal($target_in, $portal, $cache); # fixme: raise exception here?
|
||||
next if !iscsi_test_portal($target_in, $portal, $cache); # fixme: raise exception here?
|
||||
|
||||
my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets', '--portal', $portal];
|
||||
eval {
|
||||
run_command($cmd, outfunc => sub {
|
||||
my $line = shift;
|
||||
my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets', '--portal', $portal];
|
||||
eval {
|
||||
run_command(
|
||||
$cmd,
|
||||
outfunc => sub {
|
||||
my $line = shift;
|
||||
|
||||
if ($line =~ $ISCSI_TARGET_RE) {
|
||||
my ($portal, $target) = ($1, $2);
|
||||
# one target can have more than one portal (multipath)
|
||||
# and sendtargets should return all of them in single call
|
||||
push @{$res->{$target}}, $portal;
|
||||
}
|
||||
});
|
||||
};
|
||||
if ($line =~ $ISCSI_TARGET_RE) {
|
||||
my ($portal, $target) = ($1, $2);
|
||||
# one target can have more than one portal (multipath)
|
||||
# and sendtargets should return all of them in single call
|
||||
push @{ $res->{$target} }, $portal;
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
# In case of multipath we can stop after receiving targets from any available portal
|
||||
last if scalar(keys %$res) > 0;
|
||||
# In case of multipath we can stop after receiving targets from any available portal
|
||||
last if scalar(keys %$res) > 0;
|
||||
}
|
||||
|
||||
return $res;
|
||||
@ -157,19 +170,24 @@ sub iscsi_login {
|
||||
|
||||
# Disable retries to avoid blocking pvestatd for too long, next iteration will retry anyway
|
||||
eval {
|
||||
my $cmd = [
|
||||
$ISCSIADM,
|
||||
'--mode', 'node',
|
||||
'--targetname', $target,
|
||||
'--op', 'update',
|
||||
'--name', 'node.session.initial_login_retry_max',
|
||||
'--value', '0',
|
||||
];
|
||||
run_command($cmd);
|
||||
my $cmd = [
|
||||
$ISCSIADM,
|
||||
'--mode',
|
||||
'node',
|
||||
'--targetname',
|
||||
$target,
|
||||
'--op',
|
||||
'update',
|
||||
'--name',
|
||||
'node.session.initial_login_retry_max',
|
||||
'--value',
|
||||
'0',
|
||||
];
|
||||
run_command($cmd);
|
||||
};
|
||||
warn $@ if $@;
|
||||
|
||||
run_command([$ISCSIADM, '--mode', 'node', '--targetname', $target, '--login']);
|
||||
run_command([$ISCSIADM, '--mode', 'node', '--targetname', $target, '--login']);
|
||||
}
|
||||
|
||||
sub iscsi_logout {
|
||||
@ -190,22 +208,24 @@ sub iscsi_session_rescan {
|
||||
my $rstat = stat($rescan_filename);
|
||||
|
||||
if (!$rstat) {
|
||||
if (my $fh = IO::File->new($rescan_filename, "a")) {
|
||||
utime undef, undef, $fh;
|
||||
close($fh);
|
||||
}
|
||||
if (my $fh = IO::File->new($rescan_filename, "a")) {
|
||||
utime undef, undef, $fh;
|
||||
close($fh);
|
||||
}
|
||||
} else {
|
||||
my $atime = $rstat->atime;
|
||||
my $tdiff = time() - $atime;
|
||||
# avoid frequent rescans
|
||||
return if !($tdiff < 0 || $tdiff > 10);
|
||||
utime undef, undef, $rescan_filename;
|
||||
my $atime = $rstat->atime;
|
||||
my $tdiff = time() - $atime;
|
||||
# avoid frequent rescans
|
||||
return if !($tdiff < 0 || $tdiff > 10);
|
||||
utime undef, undef, $rescan_filename;
|
||||
}
|
||||
|
||||
foreach my $session (@$session_list) {
|
||||
my $cmd = [$ISCSIADM, '--mode', 'session', '--sid', $session->{session_id}, '--rescan'];
|
||||
eval { run_command($cmd, outfunc => sub {}); };
|
||||
warn $@ if $@;
|
||||
my $cmd = [$ISCSIADM, '--mode', 'session', '--sid', $session->{session_id}, '--rescan'];
|
||||
eval {
|
||||
run_command($cmd, outfunc => sub { });
|
||||
};
|
||||
warn $@ if $@;
|
||||
}
|
||||
}
|
||||
|
||||
@ -216,19 +236,19 @@ sub load_stable_scsi_paths {
|
||||
my $stabledir = "/dev/disk/by-id";
|
||||
|
||||
if (my $dh = IO::Dir->new($stabledir)) {
|
||||
foreach my $tmp (sort $dh->read) {
|
||||
# exclude filenames with part in name (same disk but partitions)
|
||||
# use only filenames with scsi(with multipath i have the same device
|
||||
# with dm-uuid-mpath , dm-name and scsi in name)
|
||||
if($tmp !~ m/-part\d+$/ && ($tmp =~ m/^scsi-/ || $tmp =~ m/^dm-uuid-mpath-/)) {
|
||||
my $path = "$stabledir/$tmp";
|
||||
my $bdevdest = readlink($path);
|
||||
if ($bdevdest && $bdevdest =~ m|^../../([^/]+)|) {
|
||||
$stable_paths->{$1}=$tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
$dh->close;
|
||||
foreach my $tmp (sort $dh->read) {
|
||||
# exclude filenames with part in name (same disk but partitions)
|
||||
# use only filenames with scsi(with multipath i have the same device
|
||||
# with dm-uuid-mpath , dm-name and scsi in name)
|
||||
if ($tmp !~ m/-part\d+$/ && ($tmp =~ m/^scsi-/ || $tmp =~ m/^dm-uuid-mpath-/)) {
|
||||
my $path = "$stabledir/$tmp";
|
||||
my $bdevdest = readlink($path);
|
||||
if ($bdevdest && $bdevdest =~ m|^../../([^/]+)|) {
|
||||
$stable_paths->{$1} = $tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
$dh->close;
|
||||
}
|
||||
return $stable_paths;
|
||||
}
|
||||
@ -241,56 +261,67 @@ sub iscsi_device_list {
|
||||
|
||||
my $stable_paths = load_stable_scsi_paths();
|
||||
|
||||
dir_glob_foreach($dirname, 'session(\d+)', sub {
|
||||
my ($ent, $session) = @_;
|
||||
dir_glob_foreach(
|
||||
$dirname,
|
||||
'session(\d+)',
|
||||
sub {
|
||||
my ($ent, $session) = @_;
|
||||
|
||||
my $target = file_read_firstline("$dirname/$ent/targetname");
|
||||
return if !$target;
|
||||
my $target = file_read_firstline("$dirname/$ent/targetname");
|
||||
return if !$target;
|
||||
|
||||
my (undef, $host) = dir_glob_regex("$dirname/$ent/device", 'target(\d+):.*');
|
||||
return if !defined($host);
|
||||
my (undef, $host) = dir_glob_regex("$dirname/$ent/device", 'target(\d+):.*');
|
||||
return if !defined($host);
|
||||
|
||||
dir_glob_foreach("/sys/bus/scsi/devices", "$host:" . '(\d+):(\d+):(\d+)', sub {
|
||||
my ($tmp, $channel, $id, $lun) = @_;
|
||||
dir_glob_foreach(
|
||||
"/sys/bus/scsi/devices",
|
||||
"$host:" . '(\d+):(\d+):(\d+)',
|
||||
sub {
|
||||
my ($tmp, $channel, $id, $lun) = @_;
|
||||
|
||||
my $type = file_read_firstline("/sys/bus/scsi/devices/$tmp/type");
|
||||
return if !defined($type) || $type ne '0'; # list disks only
|
||||
my $type = file_read_firstline("/sys/bus/scsi/devices/$tmp/type");
|
||||
return if !defined($type) || $type ne '0'; # list disks only
|
||||
|
||||
my $bdev;
|
||||
if (-d "/sys/bus/scsi/devices/$tmp/block") { # newer kernels
|
||||
(undef, $bdev) = dir_glob_regex("/sys/bus/scsi/devices/$tmp/block/", '([A-Za-z]\S*)');
|
||||
} else {
|
||||
(undef, $bdev) = dir_glob_regex("/sys/bus/scsi/devices/$tmp", 'block:(\S+)');
|
||||
}
|
||||
return if !$bdev;
|
||||
my $bdev;
|
||||
if (-d "/sys/bus/scsi/devices/$tmp/block") { # newer kernels
|
||||
(undef, $bdev) =
|
||||
dir_glob_regex("/sys/bus/scsi/devices/$tmp/block/", '([A-Za-z]\S*)');
|
||||
} else {
|
||||
(undef, $bdev) =
|
||||
dir_glob_regex("/sys/bus/scsi/devices/$tmp", 'block:(\S+)');
|
||||
}
|
||||
return if !$bdev;
|
||||
|
||||
#check multipath
|
||||
if (-d "/sys/block/$bdev/holders") {
|
||||
my $multipathdev = dir_glob_regex("/sys/block/$bdev/holders", '[A-Za-z]\S*');
|
||||
$bdev = $multipathdev if $multipathdev;
|
||||
}
|
||||
#check multipath
|
||||
if (-d "/sys/block/$bdev/holders") {
|
||||
my $multipathdev =
|
||||
dir_glob_regex("/sys/block/$bdev/holders", '[A-Za-z]\S*');
|
||||
$bdev = $multipathdev if $multipathdev;
|
||||
}
|
||||
|
||||
my $blockdev = $stable_paths->{$bdev};
|
||||
return if !$blockdev;
|
||||
my $blockdev = $stable_paths->{$bdev};
|
||||
return if !$blockdev;
|
||||
|
||||
my $size = file_read_firstline("/sys/block/$bdev/size");
|
||||
return if !$size;
|
||||
my $size = file_read_firstline("/sys/block/$bdev/size");
|
||||
return if !$size;
|
||||
|
||||
my $volid = "$channel.$id.$lun.$blockdev";
|
||||
my $volid = "$channel.$id.$lun.$blockdev";
|
||||
|
||||
$res->{$target}->{$volid} = {
|
||||
'format' => 'raw',
|
||||
'size' => int($size * 512),
|
||||
'vmid' => 0, # not assigned to any vm
|
||||
'channel' => int($channel),
|
||||
'id' => int($id),
|
||||
'lun' => int($lun),
|
||||
};
|
||||
$res->{$target}->{$volid} = {
|
||||
'format' => 'raw',
|
||||
'size' => int($size * 512),
|
||||
'vmid' => 0, # not assigned to any vm
|
||||
'channel' => int($channel),
|
||||
'id' => int($id),
|
||||
'lun' => int($lun),
|
||||
};
|
||||
|
||||
#print "TEST: $target $session $host,$bus,$tg,$lun $blockdev\n";
|
||||
});
|
||||
#print "TEST: $target $session $host,$bus,$tg,$lun $blockdev\n";
|
||||
},
|
||||
);
|
||||
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
return $res;
|
||||
}
|
||||
@ -303,22 +334,23 @@ sub type {
|
||||
|
||||
sub plugindata {
|
||||
return {
|
||||
content => [ {images => 1, none => 1}, { images => 1 }],
|
||||
select_existing => 1,
|
||||
'sensitive-properties' => {},
|
||||
content => [{ images => 1, none => 1 }, { images => 1 }],
|
||||
select_existing => 1,
|
||||
'sensitive-properties' => {},
|
||||
};
|
||||
}
|
||||
|
||||
sub properties {
|
||||
return {
|
||||
target => {
|
||||
description => "iSCSI target.",
|
||||
type => 'string',
|
||||
},
|
||||
portal => {
|
||||
description => "iSCSI portal (IP or DNS name with optional port).",
|
||||
type => 'string', format => 'pve-storage-portal-dns',
|
||||
},
|
||||
target => {
|
||||
description => "iSCSI target.",
|
||||
type => 'string',
|
||||
},
|
||||
portal => {
|
||||
description => "iSCSI portal (IP or DNS name with optional port).",
|
||||
type => 'string',
|
||||
format => 'pve-storage-portal-dns',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -326,10 +358,10 @@ sub options {
|
||||
return {
|
||||
portal => { fixed => 1 },
|
||||
target => { fixed => 1 },
|
||||
nodes => { optional => 1},
|
||||
disable => { optional => 1},
|
||||
content => { optional => 1},
|
||||
bwlimit => { optional => 1 },
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
};
|
||||
}
|
||||
|
||||
@ -339,7 +371,7 @@ sub parse_volname {
|
||||
my ($class, $volname) = @_;
|
||||
|
||||
if ($volname =~ m!^\d+\.\d+\.\d+\.([^/\s]+)$!) {
|
||||
return ('images', $1, undef, undef, undef, undef, 'raw');
|
||||
return ('images', $1, undef, undef, undef, undef, 'raw');
|
||||
}
|
||||
|
||||
die "unable to parse iscsi volume name '$volname'\n";
|
||||
@ -389,7 +421,7 @@ sub list_volumes {
|
||||
my $res = $class->list_images($storeid, $scfg, $vmid);
|
||||
|
||||
for my $item (@$res) {
|
||||
$item->{content} = 'images'; # we only have images
|
||||
$item->{content} = 'images'; # we only have images
|
||||
}
|
||||
|
||||
return $res;
|
||||
@ -408,23 +440,23 @@ sub list_images {
|
||||
|
||||
if (my $dat = $cache->{iscsi_devices}->{$target}) {
|
||||
|
||||
foreach my $volname (keys %$dat) {
|
||||
foreach my $volname (keys %$dat) {
|
||||
|
||||
my $volid = "$storeid:$volname";
|
||||
my $volid = "$storeid:$volname";
|
||||
|
||||
if ($vollist) {
|
||||
my $found = grep { $_ eq $volid } @$vollist;
|
||||
next if !$found;
|
||||
} else {
|
||||
# we have no owner for iscsi devices
|
||||
next if defined($vmid);
|
||||
}
|
||||
if ($vollist) {
|
||||
my $found = grep { $_ eq $volid } @$vollist;
|
||||
next if !$found;
|
||||
} else {
|
||||
# we have no owner for iscsi devices
|
||||
next if defined($vmid);
|
||||
}
|
||||
|
||||
my $info = $dat->{$volname};
|
||||
$info->{volid} = $volid;
|
||||
my $info = $dat->{$volname};
|
||||
$info->{volid} = $volid;
|
||||
|
||||
push @$res, $info;
|
||||
}
|
||||
push @$res, $info;
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
@ -455,23 +487,23 @@ sub activate_storage {
|
||||
my $do_login = !defined($sessions);
|
||||
|
||||
if (!$do_login) {
|
||||
# We should check that sessions for all portals are available
|
||||
my $session_portals = [ map { $_->{portal} } (@$sessions) ];
|
||||
# We should check that sessions for all portals are available
|
||||
my $session_portals = [map { $_->{portal} } (@$sessions)];
|
||||
|
||||
for my $portal (@$portals) {
|
||||
if (!grep(/^\Q$portal\E$/, @$session_portals)) {
|
||||
$do_login = 1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
for my $portal (@$portals) {
|
||||
if (!grep(/^\Q$portal\E$/, @$session_portals)) {
|
||||
$do_login = 1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($do_login) {
|
||||
eval { iscsi_login($scfg->{target}, $portals, $cache); };
|
||||
warn $@ if $@;
|
||||
eval { iscsi_login($scfg->{target}, $portals, $cache); };
|
||||
warn $@ if $@;
|
||||
} else {
|
||||
# make sure we get all devices
|
||||
iscsi_session_rescan($sessions);
|
||||
# make sure we get all devices
|
||||
iscsi_session_rescan($sessions);
|
||||
}
|
||||
}
|
||||
|
||||
@ -481,7 +513,7 @@ sub deactivate_storage {
|
||||
return if !assert_iscsi_support(1);
|
||||
|
||||
if (defined(iscsi_session($cache, $scfg->{target}))) {
|
||||
iscsi_logout($scfg->{target});
|
||||
iscsi_logout($scfg->{target});
|
||||
}
|
||||
}
|
||||
|
||||
@ -490,17 +522,17 @@ my $check_devices_part_of_target = sub {
|
||||
|
||||
my $found = 0;
|
||||
for my $path (@$device_paths) {
|
||||
if ($path =~ m!^/devices/platform/host\d+/session(\d+)/target\d+:\d:\d!) {
|
||||
my $session_id = $1;
|
||||
if ($path =~ m!^/devices/platform/host\d+/session(\d+)/target\d+:\d:\d!) {
|
||||
my $session_id = $1;
|
||||
|
||||
my $targetname = file_read_firstline(
|
||||
"/sys/class/iscsi_session/session$session_id/targetname",
|
||||
);
|
||||
if ($targetname && ($targetname eq $target)) {
|
||||
$found = 1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
my $targetname = file_read_firstline(
|
||||
"/sys/class/iscsi_session/session$session_id/targetname",
|
||||
);
|
||||
if ($targetname && ($targetname eq $target)) {
|
||||
$found = 1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $found;
|
||||
};
|
||||
@ -514,15 +546,15 @@ my $udev_query_path = sub {
|
||||
|
||||
my $device_path;
|
||||
my $cmd = [
|
||||
'udevadm',
|
||||
'info',
|
||||
'--query=path',
|
||||
$dev,
|
||||
'udevadm', 'info', '--query=path', $dev,
|
||||
];
|
||||
eval {
|
||||
run_command($cmd, outfunc => sub {
|
||||
$device_path = shift;
|
||||
});
|
||||
run_command(
|
||||
$cmd,
|
||||
outfunc => sub {
|
||||
$device_path = shift;
|
||||
},
|
||||
);
|
||||
};
|
||||
die "failed to query device path for '$dev': $@\n" if $@;
|
||||
|
||||
@ -540,23 +572,27 @@ $resolve_virtual_devices = sub {
|
||||
|
||||
my $resolved = [];
|
||||
if ($dev =~ m!^/devices/virtual/block/!) {
|
||||
dir_glob_foreach("/sys/$dev/slaves", '([^.].+)', sub {
|
||||
my ($slave) = @_;
|
||||
dir_glob_foreach(
|
||||
"/sys/$dev/slaves",
|
||||
'([^.].+)',
|
||||
sub {
|
||||
my ($slave) = @_;
|
||||
|
||||
# don't check devices multiple times
|
||||
return if $visited->{$slave};
|
||||
$visited->{$slave} = 1;
|
||||
# don't check devices multiple times
|
||||
return if $visited->{$slave};
|
||||
$visited->{$slave} = 1;
|
||||
|
||||
my $path;
|
||||
eval { $path = $udev_query_path->("/dev/$slave"); };
|
||||
return if $@;
|
||||
my $path;
|
||||
eval { $path = $udev_query_path->("/dev/$slave"); };
|
||||
return if $@;
|
||||
|
||||
my $nested_resolved = $resolve_virtual_devices->($path, $visited);
|
||||
my $nested_resolved = $resolve_virtual_devices->($path, $visited);
|
||||
|
||||
push @$resolved, @$nested_resolved;
|
||||
});
|
||||
push @$resolved, @$nested_resolved;
|
||||
},
|
||||
);
|
||||
} else {
|
||||
push @$resolved, $dev;
|
||||
push @$resolved, $dev;
|
||||
}
|
||||
|
||||
return $resolved;
|
||||
@ -570,7 +606,7 @@ sub activate_volume {
|
||||
die "failed to get realpath for '$path': $!\n" if !$real_path;
|
||||
# in case $path does not exist or is not a symlink, check if the returned
|
||||
# $real_path is a block device
|
||||
die "resolved realpath '$real_path' is not a block device\n" if ! -b $real_path;
|
||||
die "resolved realpath '$real_path' is not a block device\n" if !-b $real_path;
|
||||
|
||||
my $device_path = $udev_query_path->($real_path);
|
||||
my $resolved_paths = $resolve_virtual_devices->($device_path);
|
||||
@ -585,8 +621,8 @@ sub check_connection {
|
||||
my $portals = iscsi_portals($scfg->{target}, $scfg->{portal});
|
||||
|
||||
for my $portal (@$portals) {
|
||||
my $result = iscsi_test_portal($scfg->{target}, $portal, $cache);
|
||||
return $result if $result;
|
||||
my $result = iscsi_test_portal($scfg->{target}, $portal, $cache);
|
||||
return $result if $result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -601,17 +637,16 @@ sub volume_has_feature {
|
||||
my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
|
||||
|
||||
my $features = {
|
||||
copy => { current => 1},
|
||||
copy => { current => 1 },
|
||||
};
|
||||
|
||||
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
|
||||
$class->parse_volname($volname);
|
||||
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = $class->parse_volname($volname);
|
||||
|
||||
my $key = undef;
|
||||
if ($snapname){
|
||||
$key = 'snap';
|
||||
if ($snapname) {
|
||||
$key = 'snap';
|
||||
} else {
|
||||
$key = $isBase ? 'base' : 'current';
|
||||
$key = $isBase ? 'base' : 'current';
|
||||
}
|
||||
return 1 if $features->{$feature}->{$key};
|
||||
|
||||
@ -629,15 +664,15 @@ sub volume_export_formats {
|
||||
|
||||
sub volume_export {
|
||||
my (
|
||||
$class,
|
||||
$scfg,
|
||||
$storeid,
|
||||
$fh,
|
||||
$volname,
|
||||
$format,
|
||||
$snapshot,
|
||||
$base_snapshot,
|
||||
$with_snapshots,
|
||||
$class,
|
||||
$scfg,
|
||||
$storeid,
|
||||
$fh,
|
||||
$volname,
|
||||
$format,
|
||||
$snapshot,
|
||||
$base_snapshot,
|
||||
$with_snapshots,
|
||||
) = @_;
|
||||
|
||||
die "volume export format $format not available for $class\n" if $format ne 'raw+size';
|
||||
@ -647,13 +682,16 @@ sub volume_export {
|
||||
|
||||
my $file = $class->filesystem_path($scfg, $volname, $snapshot);
|
||||
my $size;
|
||||
run_command(['/sbin/blockdev', '--getsize64', $file], outfunc => sub {
|
||||
my ($line) = @_;
|
||||
die "unexpected output from /sbin/blockdev: $line\n" if $line !~ /^(\d+)$/;
|
||||
$size = int($1);
|
||||
});
|
||||
run_command(
|
||||
['/sbin/blockdev', '--getsize64', $file],
|
||||
outfunc => sub {
|
||||
my ($line) = @_;
|
||||
die "unexpected output from /sbin/blockdev: $line\n" if $line !~ /^(\d+)$/;
|
||||
$size = int($1);
|
||||
},
|
||||
);
|
||||
PVE::Storage::Plugin::write_common_header($fh, $size);
|
||||
run_command(['dd', "if=$file", "bs=64k", "status=progress"], output => '>&'.fileno($fh));
|
||||
run_command(['dd', "if=$file", "bs=64k", "status=progress"], output => '>&' . fileno($fh));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ my $ignore_no_medium_warnings = sub {
|
||||
# ignore those, most of the time they're from (virtual) IPMI/iKVM devices
|
||||
# and just spam the log..
|
||||
if ($line !~ /open failed: No medium found/) {
|
||||
print STDERR "$line\n";
|
||||
print STDERR "$line\n";
|
||||
}
|
||||
};
|
||||
|
||||
@ -32,35 +32,51 @@ sub lvm_pv_info {
|
||||
my $has_label = 0;
|
||||
|
||||
my $cmd = ['/usr/bin/file', '-L', '-s', $device];
|
||||
run_command($cmd, outfunc => sub {
|
||||
my $line = shift;
|
||||
$has_label = 1 if $line =~ m/LVM2/;
|
||||
});
|
||||
run_command(
|
||||
$cmd,
|
||||
outfunc => sub {
|
||||
my $line = shift;
|
||||
$has_label = 1 if $line =~ m/LVM2/;
|
||||
},
|
||||
);
|
||||
|
||||
return undef if !$has_label;
|
||||
|
||||
$cmd = ['/sbin/pvs', '--separator', ':', '--noheadings', '--units', 'k',
|
||||
'--unbuffered', '--nosuffix', '--options',
|
||||
'pv_name,pv_size,vg_name,pv_uuid', $device];
|
||||
$cmd = [
|
||||
'/sbin/pvs',
|
||||
'--separator',
|
||||
':',
|
||||
'--noheadings',
|
||||
'--units',
|
||||
'k',
|
||||
'--unbuffered',
|
||||
'--nosuffix',
|
||||
'--options',
|
||||
'pv_name,pv_size,vg_name,pv_uuid',
|
||||
$device,
|
||||
];
|
||||
|
||||
my $pvinfo;
|
||||
run_command($cmd, outfunc => sub {
|
||||
my $line = shift;
|
||||
run_command(
|
||||
$cmd,
|
||||
outfunc => sub {
|
||||
my $line = shift;
|
||||
|
||||
$line = trim($line);
|
||||
$line = trim($line);
|
||||
|
||||
my ($pvname, $size, $vgname, $uuid) = split(':', $line);
|
||||
my ($pvname, $size, $vgname, $uuid) = split(':', $line);
|
||||
|
||||
die "found multiple pvs entries for device '$device'\n"
|
||||
if $pvinfo;
|
||||
die "found multiple pvs entries for device '$device'\n"
|
||||
if $pvinfo;
|
||||
|
||||
$pvinfo = {
|
||||
pvname => $pvname,
|
||||
size => int($size),
|
||||
vgname => $vgname,
|
||||
uuid => $uuid,
|
||||
};
|
||||
});
|
||||
$pvinfo = {
|
||||
pvname => $pvname,
|
||||
size => int($size),
|
||||
vgname => $vgname,
|
||||
uuid => $uuid,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return $pvinfo;
|
||||
}
|
||||
@ -69,9 +85,9 @@ sub clear_first_sector {
|
||||
my ($dev) = shift;
|
||||
|
||||
if (my $fh = IO::File->new($dev, "w")) {
|
||||
my $buf = 0 x 512;
|
||||
syswrite $fh, $buf;
|
||||
$fh->close();
|
||||
my $buf = 0 x 512;
|
||||
syswrite $fh, $buf;
|
||||
$fh->close();
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,8 +97,8 @@ sub lvm_create_volume_group {
|
||||
my $res = lvm_pv_info($device);
|
||||
|
||||
if ($res->{vgname}) {
|
||||
return if $res->{vgname} eq $vgname; # already created
|
||||
die "device '$device' is already used by volume group '$res->{vgname}'\n";
|
||||
return if $res->{vgname} eq $vgname; # already created
|
||||
die "device '$device' is already used by volume group '$res->{vgname}'\n";
|
||||
}
|
||||
|
||||
clear_first_sector($device); # else pvcreate fails
|
||||
@ -96,58 +112,76 @@ sub lvm_create_volume_group {
|
||||
$cmd = ['/sbin/vgcreate', $vgname, $device];
|
||||
# push @$cmd, '-c', 'y' if $shared; # we do not use this yet
|
||||
|
||||
run_command($cmd, errmsg => "vgcreate $vgname $device error", errfunc => $ignore_no_medium_warnings, outfunc => $ignore_no_medium_warnings);
|
||||
run_command(
|
||||
$cmd,
|
||||
errmsg => "vgcreate $vgname $device error",
|
||||
errfunc => $ignore_no_medium_warnings,
|
||||
outfunc => $ignore_no_medium_warnings,
|
||||
);
|
||||
}
|
||||
|
||||
sub lvm_destroy_volume_group {
|
||||
my ($vgname) = @_;
|
||||
|
||||
run_command(
|
||||
['vgremove', '-y', $vgname],
|
||||
errmsg => "unable to remove volume group $vgname",
|
||||
errfunc => $ignore_no_medium_warnings,
|
||||
outfunc => $ignore_no_medium_warnings,
|
||||
['vgremove', '-y', $vgname],
|
||||
errmsg => "unable to remove volume group $vgname",
|
||||
errfunc => $ignore_no_medium_warnings,
|
||||
outfunc => $ignore_no_medium_warnings,
|
||||
);
|
||||
}
|
||||
|
||||
sub lvm_vgs {
|
||||
my ($includepvs) = @_;
|
||||
|
||||
my $cmd = ['/sbin/vgs', '--separator', ':', '--noheadings', '--units', 'b',
|
||||
'--unbuffered', '--nosuffix', '--options'];
|
||||
my $cmd = [
|
||||
'/sbin/vgs',
|
||||
'--separator',
|
||||
':',
|
||||
'--noheadings',
|
||||
'--units',
|
||||
'b',
|
||||
'--unbuffered',
|
||||
'--nosuffix',
|
||||
'--options',
|
||||
];
|
||||
|
||||
my $cols = [qw(vg_name vg_size vg_free lv_count)];
|
||||
|
||||
if ($includepvs) {
|
||||
push @$cols, qw(pv_name pv_size pv_free);
|
||||
push @$cols, qw(pv_name pv_size pv_free);
|
||||
}
|
||||
|
||||
push @$cmd, join(',', @$cols);
|
||||
|
||||
my $vgs = {};
|
||||
eval {
|
||||
run_command($cmd, outfunc => sub {
|
||||
my $line = shift;
|
||||
$line = trim($line);
|
||||
run_command(
|
||||
$cmd,
|
||||
outfunc => sub {
|
||||
my $line = shift;
|
||||
$line = trim($line);
|
||||
|
||||
my ($name, $size, $free, $lvcount, $pvname, $pvsize, $pvfree) = split (':', $line);
|
||||
my ($name, $size, $free, $lvcount, $pvname, $pvsize, $pvfree) =
|
||||
split(':', $line);
|
||||
|
||||
$vgs->{$name} //= {
|
||||
size => int ($size),
|
||||
free => int ($free),
|
||||
lvcount => int($lvcount)
|
||||
};
|
||||
$vgs->{$name} //= {
|
||||
size => int($size),
|
||||
free => int($free),
|
||||
lvcount => int($lvcount),
|
||||
};
|
||||
|
||||
if (defined($pvname) && defined($pvsize) && defined($pvfree)) {
|
||||
push @{$vgs->{$name}->{pvs}}, {
|
||||
name => $pvname,
|
||||
size => int($pvsize),
|
||||
free => int($pvfree),
|
||||
};
|
||||
}
|
||||
},
|
||||
errfunc => $ignore_no_medium_warnings,
|
||||
);
|
||||
if (defined($pvname) && defined($pvsize) && defined($pvfree)) {
|
||||
push @{ $vgs->{$name}->{pvs} },
|
||||
{
|
||||
name => $pvname,
|
||||
size => int($pvsize),
|
||||
free => int($pvfree),
|
||||
};
|
||||
}
|
||||
},
|
||||
errfunc => $ignore_no_medium_warnings,
|
||||
);
|
||||
};
|
||||
my $err = $@;
|
||||
|
||||
@ -161,49 +195,73 @@ sub lvm_vgs {
|
||||
sub lvm_list_volumes {
|
||||
my ($vgname) = @_;
|
||||
|
||||
my $option_list = 'vg_name,lv_name,lv_size,lv_attr,pool_lv,data_percent,metadata_percent,snap_percent,uuid,tags,metadata_size,time';
|
||||
my $option_list =
|
||||
'vg_name,lv_name,lv_size,lv_attr,pool_lv,data_percent,metadata_percent,snap_percent,uuid,tags,metadata_size,time';
|
||||
|
||||
my $cmd = [
|
||||
'/sbin/lvs', '--separator', ':', '--noheadings', '--units', 'b',
|
||||
'--unbuffered', '--nosuffix',
|
||||
'--config', 'report/time_format="%s"',
|
||||
'--options', $option_list,
|
||||
'/sbin/lvs',
|
||||
'--separator',
|
||||
':',
|
||||
'--noheadings',
|
||||
'--units',
|
||||
'b',
|
||||
'--unbuffered',
|
||||
'--nosuffix',
|
||||
'--config',
|
||||
'report/time_format="%s"',
|
||||
'--options',
|
||||
$option_list,
|
||||
];
|
||||
|
||||
push @$cmd, $vgname if $vgname;
|
||||
|
||||
my $lvs = {};
|
||||
run_command($cmd, outfunc => sub {
|
||||
my $line = shift;
|
||||
run_command(
|
||||
$cmd,
|
||||
outfunc => sub {
|
||||
my $line = shift;
|
||||
|
||||
$line = trim($line);
|
||||
$line = trim($line);
|
||||
|
||||
my ($vg_name, $lv_name, $lv_size, $lv_attr, $pool_lv, $data_percent, $meta_percent, $snap_percent, $uuid, $tags, $meta_size, $ctime) = split(':', $line);
|
||||
return if !$vg_name;
|
||||
return if !$lv_name;
|
||||
my (
|
||||
$vg_name,
|
||||
$lv_name,
|
||||
$lv_size,
|
||||
$lv_attr,
|
||||
$pool_lv,
|
||||
$data_percent,
|
||||
$meta_percent,
|
||||
$snap_percent,
|
||||
$uuid,
|
||||
$tags,
|
||||
$meta_size,
|
||||
$ctime,
|
||||
) = split(':', $line);
|
||||
return if !$vg_name;
|
||||
return if !$lv_name;
|
||||
|
||||
my $lv_type = substr($lv_attr, 0, 1);
|
||||
my $lv_type = substr($lv_attr, 0, 1);
|
||||
|
||||
my $d = {
|
||||
lv_size => int($lv_size),
|
||||
lv_state => substr($lv_attr, 4, 1),
|
||||
lv_type => $lv_type,
|
||||
};
|
||||
$d->{pool_lv} = $pool_lv if $pool_lv;
|
||||
$d->{tags} = $tags if $tags;
|
||||
$d->{ctime} = $ctime;
|
||||
my $d = {
|
||||
lv_size => int($lv_size),
|
||||
lv_state => substr($lv_attr, 4, 1),
|
||||
lv_type => $lv_type,
|
||||
};
|
||||
$d->{pool_lv} = $pool_lv if $pool_lv;
|
||||
$d->{tags} = $tags if $tags;
|
||||
$d->{ctime} = $ctime;
|
||||
|
||||
if ($lv_type eq 't') {
|
||||
$data_percent ||= 0;
|
||||
$meta_percent ||= 0;
|
||||
$snap_percent ||= 0;
|
||||
$d->{metadata_size} = int($meta_size);
|
||||
$d->{metadata_used} = int(($meta_percent * $meta_size)/100);
|
||||
$d->{used} = int(($data_percent * $lv_size)/100);
|
||||
}
|
||||
$lvs->{$vg_name}->{$lv_name} = $d;
|
||||
},
|
||||
errfunc => $ignore_no_medium_warnings,
|
||||
if ($lv_type eq 't') {
|
||||
$data_percent ||= 0;
|
||||
$meta_percent ||= 0;
|
||||
$snap_percent ||= 0;
|
||||
$d->{metadata_size} = int($meta_size);
|
||||
$d->{metadata_used} = int(($meta_percent * $meta_size) / 100);
|
||||
$d->{used} = int(($data_percent * $lv_size) / 100);
|
||||
}
|
||||
$lvs->{$vg_name}->{$lv_name} = $d;
|
||||
},
|
||||
errfunc => $ignore_no_medium_warnings,
|
||||
);
|
||||
|
||||
return $lvs;
|
||||
@ -217,48 +275,50 @@ sub type {
|
||||
|
||||
sub plugindata {
|
||||
return {
|
||||
content => [ {images => 1, rootdir => 1}, { images => 1 }],
|
||||
'sensitive-properties' => {},
|
||||
content => [{ images => 1, rootdir => 1 }, { images => 1 }],
|
||||
'sensitive-properties' => {},
|
||||
};
|
||||
}
|
||||
|
||||
sub properties {
|
||||
return {
|
||||
vgname => {
|
||||
description => "Volume group name.",
|
||||
type => 'string', format => 'pve-storage-vgname',
|
||||
},
|
||||
base => {
|
||||
description => "Base volume. This volume is automatically activated.",
|
||||
type => 'string', format => 'pve-volume-id',
|
||||
},
|
||||
saferemove => {
|
||||
description => "Zero-out data when removing LVs.",
|
||||
type => 'boolean',
|
||||
},
|
||||
saferemove_throughput => {
|
||||
description => "Wipe throughput (cstream -t parameter value).",
|
||||
type => 'string',
|
||||
},
|
||||
tagged_only => {
|
||||
description => "Only use logical volumes tagged with 'pve-vm-ID'.",
|
||||
type => 'boolean',
|
||||
}
|
||||
vgname => {
|
||||
description => "Volume group name.",
|
||||
type => 'string',
|
||||
format => 'pve-storage-vgname',
|
||||
},
|
||||
base => {
|
||||
description => "Base volume. This volume is automatically activated.",
|
||||
type => 'string',
|
||||
format => 'pve-volume-id',
|
||||
},
|
||||
saferemove => {
|
||||
description => "Zero-out data when removing LVs.",
|
||||
type => 'boolean',
|
||||
},
|
||||
saferemove_throughput => {
|
||||
description => "Wipe throughput (cstream -t parameter value).",
|
||||
type => 'string',
|
||||
},
|
||||
tagged_only => {
|
||||
description => "Only use logical volumes tagged with 'pve-vm-ID'.",
|
||||
type => 'boolean',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
sub options {
|
||||
return {
|
||||
vgname => { fixed => 1 },
|
||||
nodes => { optional => 1 },
|
||||
shared => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
saferemove => { optional => 1 },
|
||||
saferemove_throughput => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
base => { fixed => 1, optional => 1 },
|
||||
tagged_only => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
vgname => { fixed => 1 },
|
||||
nodes => { optional => 1 },
|
||||
shared => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
saferemove => { optional => 1 },
|
||||
saferemove_throughput => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
base => { fixed => 1, optional => 1 },
|
||||
tagged_only => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
};
|
||||
}
|
||||
|
||||
@ -268,21 +328,21 @@ sub on_add_hook {
|
||||
my ($class, $storeid, $scfg, %param) = @_;
|
||||
|
||||
if (my $base = $scfg->{base}) {
|
||||
my ($baseid, $volname) = PVE::Storage::parse_volume_id($base);
|
||||
my ($baseid, $volname) = PVE::Storage::parse_volume_id($base);
|
||||
|
||||
my $cfg = PVE::Storage::config();
|
||||
my $basecfg = PVE::Storage::storage_config ($cfg, $baseid, 1);
|
||||
die "base storage ID '$baseid' does not exist\n" if !$basecfg;
|
||||
my $cfg = PVE::Storage::config();
|
||||
my $basecfg = PVE::Storage::storage_config($cfg, $baseid, 1);
|
||||
die "base storage ID '$baseid' does not exist\n" if !$basecfg;
|
||||
|
||||
# we only support iscsi for now
|
||||
die "unsupported base type '$basecfg->{type}'"
|
||||
if $basecfg->{type} ne 'iscsi';
|
||||
# we only support iscsi for now
|
||||
die "unsupported base type '$basecfg->{type}'"
|
||||
if $basecfg->{type} ne 'iscsi';
|
||||
|
||||
my $path = PVE::Storage::path($cfg, $base);
|
||||
my $path = PVE::Storage::path($cfg, $base);
|
||||
|
||||
PVE::Storage::activate_storage($cfg, $baseid);
|
||||
PVE::Storage::activate_storage($cfg, $baseid);
|
||||
|
||||
lvm_create_volume_group($path, $scfg->{vgname}, $scfg->{shared});
|
||||
lvm_create_volume_group($path, $scfg->{vgname}, $scfg->{shared});
|
||||
}
|
||||
|
||||
return;
|
||||
@ -294,7 +354,7 @@ sub parse_volname {
|
||||
PVE::Storage::Plugin::parse_lvm_name($volname);
|
||||
|
||||
if ($volname =~ m/^(vm-(\d+)-\S+)$/) {
|
||||
return ('images', $1, $2, undef, undef, undef, 'raw');
|
||||
return ('images', $1, $2, undef, undef, undef, 'raw');
|
||||
}
|
||||
|
||||
die "unable to parse lvm volume name '$volname'\n";
|
||||
@ -303,7 +363,7 @@ sub parse_volname {
|
||||
sub filesystem_path {
|
||||
my ($class, $scfg, $volname, $snapname) = @_;
|
||||
|
||||
die "lvm snapshot is not implemented"if defined($snapname);
|
||||
die "lvm snapshot is not implemented" if defined($snapname);
|
||||
|
||||
my ($vtype, $name, $vmid) = $class->parse_volname($volname);
|
||||
|
||||
@ -333,7 +393,7 @@ sub find_free_diskname {
|
||||
|
||||
my $lvs = lvm_list_volumes($vg);
|
||||
|
||||
my $disk_list = [ keys %{$lvs->{$vg}} ];
|
||||
my $disk_list = [keys %{ $lvs->{$vg} }];
|
||||
|
||||
return PVE::Storage::Plugin::get_next_vm_diskname($disk_list, $storeid, $vmid, undef, $scfg);
|
||||
}
|
||||
@ -342,12 +402,12 @@ sub lvcreate {
|
||||
my ($vg, $name, $size, $tags) = @_;
|
||||
|
||||
if ($size =~ m/\d$/) { # no unit is given
|
||||
$size .= "k"; # default to kilobytes
|
||||
$size .= "k"; # default to kilobytes
|
||||
}
|
||||
|
||||
my $cmd = ['/sbin/lvcreate', '-aly', '-Wy', '--yes', '--size', $size, '--name', $name];
|
||||
for my $tag (@$tags) {
|
||||
push @$cmd, '--addtag', $tag;
|
||||
push @$cmd, '--addtag', $tag;
|
||||
}
|
||||
push @$cmd, $vg;
|
||||
|
||||
@ -358,8 +418,8 @@ sub lvrename {
|
||||
my ($vg, $oldname, $newname) = @_;
|
||||
|
||||
run_command(
|
||||
['/sbin/lvrename', $vg, $oldname, $newname],
|
||||
errmsg => "lvrename '${vg}/${oldname}' to '${newname}' error",
|
||||
['/sbin/lvrename', $vg, $oldname, $newname],
|
||||
errmsg => "lvrename '${vg}/${oldname}' to '${newname}' error",
|
||||
);
|
||||
}
|
||||
|
||||
@ -369,20 +429,20 @@ sub alloc_image {
|
||||
die "unsupported format '$fmt'" if $fmt ne 'raw';
|
||||
|
||||
die "illegal name '$name' - should be 'vm-$vmid-*'\n"
|
||||
if $name && $name !~ m/^vm-$vmid-/;
|
||||
if $name && $name !~ m/^vm-$vmid-/;
|
||||
|
||||
my $vgs = lvm_vgs();
|
||||
|
||||
my $vg = $scfg->{vgname};
|
||||
|
||||
die "no such volume group '$vg'\n" if !defined ($vgs->{$vg});
|
||||
die "no such volume group '$vg'\n" if !defined($vgs->{$vg});
|
||||
|
||||
my $free = int($vgs->{$vg}->{free});
|
||||
|
||||
die "not enough free space ($free < $size)\n" if $free < $size;
|
||||
|
||||
$name = $class->find_free_diskname($storeid, $scfg, $vmid)
|
||||
if !$name;
|
||||
if !$name;
|
||||
|
||||
lvcreate($vg, $name, $size, ["pve-vm-$vmid"]);
|
||||
|
||||
@ -398,31 +458,47 @@ sub free_image {
|
||||
# and to allow thin provisioning
|
||||
|
||||
my $zero_out_worker = sub {
|
||||
print "zero-out data on image $volname (/dev/$vg/del-$volname)\n";
|
||||
print "zero-out data on image $volname (/dev/$vg/del-$volname)\n";
|
||||
|
||||
# wipe throughput up to 10MB/s by default; may be overwritten with saferemove_throughput
|
||||
my $throughput = '-10485760';
|
||||
if ($scfg->{saferemove_throughput}) {
|
||||
$throughput = $scfg->{saferemove_throughput};
|
||||
}
|
||||
# wipe throughput up to 10MB/s by default; may be overwritten with saferemove_throughput
|
||||
my $throughput = '-10485760';
|
||||
if ($scfg->{saferemove_throughput}) {
|
||||
$throughput = $scfg->{saferemove_throughput};
|
||||
}
|
||||
|
||||
my $cmd = [
|
||||
'/usr/bin/cstream',
|
||||
'-i', '/dev/zero',
|
||||
'-o', "/dev/$vg/del-$volname",
|
||||
'-T', '10',
|
||||
'-v', '1',
|
||||
'-b', '1048576',
|
||||
'-t', "$throughput"
|
||||
];
|
||||
eval { run_command($cmd, errmsg => "zero out finished (note: 'No space left on device' is ok here)"); };
|
||||
warn $@ if $@;
|
||||
my $cmd = [
|
||||
'/usr/bin/cstream',
|
||||
'-i',
|
||||
'/dev/zero',
|
||||
'-o',
|
||||
"/dev/$vg/del-$volname",
|
||||
'-T',
|
||||
'10',
|
||||
'-v',
|
||||
'1',
|
||||
'-b',
|
||||
'1048576',
|
||||
'-t',
|
||||
"$throughput",
|
||||
];
|
||||
eval {
|
||||
run_command(
|
||||
$cmd,
|
||||
errmsg => "zero out finished (note: 'No space left on device' is ok here)",
|
||||
);
|
||||
};
|
||||
warn $@ if $@;
|
||||
|
||||
$class->cluster_lock_storage($storeid, $scfg->{shared}, undef, sub {
|
||||
my $cmd = ['/sbin/lvremove', '-f', "$vg/del-$volname"];
|
||||
run_command($cmd, errmsg => "lvremove '$vg/del-$volname' error");
|
||||
});
|
||||
print "successfully removed volume $volname ($vg/del-$volname)\n";
|
||||
$class->cluster_lock_storage(
|
||||
$storeid,
|
||||
$scfg->{shared},
|
||||
undef,
|
||||
sub {
|
||||
my $cmd = ['/sbin/lvremove', '-f', "$vg/del-$volname"];
|
||||
run_command($cmd, errmsg => "lvremove '$vg/del-$volname' error");
|
||||
},
|
||||
);
|
||||
print "successfully removed volume $volname ($vg/del-$volname)\n";
|
||||
};
|
||||
|
||||
my $cmd = ['/sbin/lvchange', '-aly', "$vg/$volname"];
|
||||
@ -431,14 +507,14 @@ sub free_image {
|
||||
run_command($cmd, errmsg => "can't refresh LV '$vg/$volname' to zero-out its data");
|
||||
|
||||
if ($scfg->{saferemove}) {
|
||||
# avoid long running task, so we only rename here
|
||||
$cmd = ['/sbin/lvrename', $vg, $volname, "del-$volname"];
|
||||
run_command($cmd, errmsg => "lvrename '$vg/$volname' error");
|
||||
return $zero_out_worker;
|
||||
# avoid long running task, so we only rename here
|
||||
$cmd = ['/sbin/lvrename', $vg, $volname, "del-$volname"];
|
||||
run_command($cmd, errmsg => "lvrename '$vg/$volname' error");
|
||||
return $zero_out_worker;
|
||||
} else {
|
||||
my $tmpvg = $scfg->{vgname};
|
||||
$cmd = ['/sbin/lvremove', '-f', "$tmpvg/$volname"];
|
||||
run_command($cmd, errmsg => "lvremove '$tmpvg/$volname' error");
|
||||
my $tmpvg = $scfg->{vgname};
|
||||
$cmd = ['/sbin/lvremove', '-f', "$tmpvg/$volname"];
|
||||
run_command($cmd, errmsg => "lvremove '$tmpvg/$volname' error");
|
||||
}
|
||||
|
||||
return undef;
|
||||
@ -461,32 +537,36 @@ sub list_images {
|
||||
|
||||
if (my $dat = $cache->{lvs}->{$vgname}) {
|
||||
|
||||
foreach my $volname (keys %$dat) {
|
||||
foreach my $volname (keys %$dat) {
|
||||
|
||||
next if $volname !~ m/^vm-(\d+)-/;
|
||||
my $owner = $1;
|
||||
next if $volname !~ m/^vm-(\d+)-/;
|
||||
my $owner = $1;
|
||||
|
||||
my $info = $dat->{$volname};
|
||||
my $info = $dat->{$volname};
|
||||
|
||||
next if $scfg->{tagged_only} && !&$check_tags($info->{tags});
|
||||
next if $scfg->{tagged_only} && !&$check_tags($info->{tags});
|
||||
|
||||
# Allow mirrored and RAID LVs
|
||||
next if $info->{lv_type} !~ m/^[-mMrR]$/;
|
||||
# Allow mirrored and RAID LVs
|
||||
next if $info->{lv_type} !~ m/^[-mMrR]$/;
|
||||
|
||||
my $volid = "$storeid:$volname";
|
||||
my $volid = "$storeid:$volname";
|
||||
|
||||
if ($vollist) {
|
||||
my $found = grep { $_ eq $volid } @$vollist;
|
||||
next if !$found;
|
||||
} else {
|
||||
next if defined($vmid) && ($owner ne $vmid);
|
||||
}
|
||||
if ($vollist) {
|
||||
my $found = grep { $_ eq $volid } @$vollist;
|
||||
next if !$found;
|
||||
} else {
|
||||
next if defined($vmid) && ($owner ne $vmid);
|
||||
}
|
||||
|
||||
push @$res, {
|
||||
volid => $volid, format => 'raw', size => $info->{lv_size}, vmid => $owner,
|
||||
ctime => $info->{ctime},
|
||||
};
|
||||
}
|
||||
push @$res,
|
||||
{
|
||||
volid => $volid,
|
||||
format => 'raw',
|
||||
size => $info->{lv_size},
|
||||
vmid => $owner,
|
||||
ctime => $info->{ctime},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
@ -499,8 +579,8 @@ sub status {
|
||||
|
||||
my $vgname = $scfg->{vgname};
|
||||
|
||||
if (my $info = $cache->{vgs}->{$vgname}) {
|
||||
return ($info->{size}, $info->{free}, $info->{size} - $info->{free}, 1);
|
||||
if (my $info = $cache->{vgs}->{$vgname}) {
|
||||
return ($info->{size}, $info->{free}, $info->{size} - $info->{free}, 1);
|
||||
}
|
||||
|
||||
return undef;
|
||||
@ -513,12 +593,17 @@ sub activate_storage {
|
||||
|
||||
# In LVM2, vgscans take place automatically;
|
||||
# this is just to be sure
|
||||
if ($cache->{vgs} && !$cache->{vgscaned} &&
|
||||
!$cache->{vgs}->{$scfg->{vgname}}) {
|
||||
$cache->{vgscaned} = 1;
|
||||
my $cmd = ['/sbin/vgscan', '--ignorelockingfailure', '--mknodes'];
|
||||
eval { run_command($cmd, outfunc => sub {}); };
|
||||
warn $@ if $@;
|
||||
if (
|
||||
$cache->{vgs}
|
||||
&& !$cache->{vgscaned}
|
||||
&& !$cache->{vgs}->{ $scfg->{vgname} }
|
||||
) {
|
||||
$cache->{vgscaned} = 1;
|
||||
my $cmd = ['/sbin/vgscan', '--ignorelockingfailure', '--mknodes'];
|
||||
eval {
|
||||
run_command($cmd, outfunc => sub { });
|
||||
};
|
||||
warn $@ if $@;
|
||||
}
|
||||
|
||||
# we do not acticate any volumes here ('vgchange -aly')
|
||||
@ -549,7 +634,7 @@ sub deactivate_volume {
|
||||
my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
|
||||
|
||||
my $path = $class->path($scfg, $volname, $storeid, $snapname);
|
||||
return if ! -b $path;
|
||||
return if !-b $path;
|
||||
|
||||
my $cmd = ['/sbin/lvchange', '-aln', $path];
|
||||
run_command($cmd, errmsg => "can't deactivate LV '$path'");
|
||||
@ -558,14 +643,19 @@ sub deactivate_volume {
|
||||
sub volume_resize {
|
||||
my ($class, $scfg, $storeid, $volname, $size, $running) = @_;
|
||||
|
||||
$size = ($size/1024/1024) . "M";
|
||||
$size = ($size / 1024 / 1024) . "M";
|
||||
|
||||
my $path = $class->path($scfg, $volname);
|
||||
my $cmd = ['/sbin/lvextend', '-L', $size, $path];
|
||||
|
||||
$class->cluster_lock_storage($storeid, $scfg->{shared}, undef, sub {
|
||||
run_command($cmd, errmsg => "error resizing volume '$path'");
|
||||
});
|
||||
$class->cluster_lock_storage(
|
||||
$storeid,
|
||||
$scfg->{shared},
|
||||
undef,
|
||||
sub {
|
||||
run_command($cmd, errmsg => "error resizing volume '$path'");
|
||||
},
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
@ -574,14 +664,29 @@ sub volume_size_info {
|
||||
my ($class, $scfg, $storeid, $volname, $timeout) = @_;
|
||||
my $path = $class->filesystem_path($scfg, $volname);
|
||||
|
||||
my $cmd = ['/sbin/lvs', '--separator', ':', '--noheadings', '--units', 'b',
|
||||
'--unbuffered', '--nosuffix', '--options', 'lv_size', $path];
|
||||
my $cmd = [
|
||||
'/sbin/lvs',
|
||||
'--separator',
|
||||
':',
|
||||
'--noheadings',
|
||||
'--units',
|
||||
'b',
|
||||
'--unbuffered',
|
||||
'--nosuffix',
|
||||
'--options',
|
||||
'lv_size',
|
||||
$path,
|
||||
];
|
||||
|
||||
my $size;
|
||||
run_command($cmd, timeout => $timeout, errmsg => "can't get size of '$path'",
|
||||
outfunc => sub {
|
||||
$size = int(shift);
|
||||
});
|
||||
run_command(
|
||||
$cmd,
|
||||
timeout => $timeout,
|
||||
errmsg => "can't get size of '$path'",
|
||||
outfunc => sub {
|
||||
$size = int(shift);
|
||||
},
|
||||
);
|
||||
return wantarray ? ($size, 'raw', 0, undef) : $size;
|
||||
}
|
||||
|
||||
@ -607,18 +712,17 @@ sub volume_has_feature {
|
||||
my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
|
||||
|
||||
my $features = {
|
||||
copy => { base => 1, current => 1},
|
||||
rename => {current => 1},
|
||||
copy => { base => 1, current => 1 },
|
||||
rename => { current => 1 },
|
||||
};
|
||||
|
||||
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
|
||||
$class->parse_volname($volname);
|
||||
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = $class->parse_volname($volname);
|
||||
|
||||
my $key = undef;
|
||||
if($snapname){
|
||||
$key = 'snap';
|
||||
}else{
|
||||
$key = $isBase ? 'base' : 'current';
|
||||
if ($snapname) {
|
||||
$key = 'snap';
|
||||
} else {
|
||||
$key = $isBase ? 'base' : 'current';
|
||||
}
|
||||
return 1 if $features->{$feature}->{$key};
|
||||
|
||||
@ -628,27 +732,33 @@ sub volume_has_feature {
|
||||
sub volume_export_formats {
|
||||
my ($class, $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots) = @_;
|
||||
return () if defined($snapshot); # lvm-thin only
|
||||
return volume_import_formats($class, $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots);
|
||||
return volume_import_formats(
|
||||
$class, $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots,
|
||||
);
|
||||
}
|
||||
|
||||
sub volume_export {
|
||||
my ($class, $scfg, $storeid, $fh, $volname, $format, $snapshot, $base_snapshot, $with_snapshots) = @_;
|
||||
my ($class, $scfg, $storeid, $fh, $volname, $format, $snapshot, $base_snapshot, $with_snapshots)
|
||||
= @_;
|
||||
die "volume export format $format not available for $class\n"
|
||||
if $format ne 'raw+size';
|
||||
if $format ne 'raw+size';
|
||||
die "cannot export volumes together with their snapshots in $class\n"
|
||||
if $with_snapshots;
|
||||
if $with_snapshots;
|
||||
die "cannot export a snapshot in $class\n" if defined($snapshot);
|
||||
die "cannot export an incremental stream in $class\n" if defined($base_snapshot);
|
||||
my $file = $class->path($scfg, $volname, $storeid);
|
||||
my $size;
|
||||
# should be faster than querying LVM, also checks for the device file's availability
|
||||
run_command(['/sbin/blockdev', '--getsize64', $file], outfunc => sub {
|
||||
my ($line) = @_;
|
||||
die "unexpected output from /sbin/blockdev: $line\n" if $line !~ /^(\d+)$/;
|
||||
$size = int($1);
|
||||
});
|
||||
run_command(
|
||||
['/sbin/blockdev', '--getsize64', $file],
|
||||
outfunc => sub {
|
||||
my ($line) = @_;
|
||||
die "unexpected output from /sbin/blockdev: $line\n" if $line !~ /^(\d+)$/;
|
||||
$size = int($1);
|
||||
},
|
||||
);
|
||||
PVE::Storage::Plugin::write_common_header($fh, $size);
|
||||
run_command(['dd', "if=$file", "bs=64k", "status=progress"], output => '>&'.fileno($fh));
|
||||
run_command(['dd', "if=$file", "bs=64k", "status=progress"], output => '>&' . fileno($fh));
|
||||
}
|
||||
|
||||
sub volume_import_formats {
|
||||
@ -659,53 +769,64 @@ sub volume_import_formats {
|
||||
}
|
||||
|
||||
sub volume_import {
|
||||
my ($class, $scfg, $storeid, $fh, $volname, $format, $snapshot, $base_snapshot, $with_snapshots, $allow_rename) = @_;
|
||||
my (
|
||||
$class,
|
||||
$scfg,
|
||||
$storeid,
|
||||
$fh,
|
||||
$volname,
|
||||
$format,
|
||||
$snapshot,
|
||||
$base_snapshot,
|
||||
$with_snapshots,
|
||||
$allow_rename,
|
||||
) = @_;
|
||||
die "volume import format $format not available for $class\n"
|
||||
if $format ne 'raw+size';
|
||||
if $format ne 'raw+size';
|
||||
die "cannot import volumes together with their snapshots in $class\n"
|
||||
if $with_snapshots;
|
||||
if $with_snapshots;
|
||||
die "cannot import an incremental stream in $class\n" if defined($base_snapshot);
|
||||
|
||||
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase, $file_format) =
|
||||
$class->parse_volname($volname);
|
||||
$class->parse_volname($volname);
|
||||
die "cannot import format $format into a file of format $file_format\n"
|
||||
if $file_format ne 'raw';
|
||||
if $file_format ne 'raw';
|
||||
|
||||
my $vg = $scfg->{vgname};
|
||||
my $lvs = lvm_list_volumes($vg);
|
||||
if ($lvs->{$vg}->{$volname}) {
|
||||
die "volume $vg/$volname already exists\n" if !$allow_rename;
|
||||
warn "volume $vg/$volname already exists - importing with a different name\n";
|
||||
$name = undef;
|
||||
die "volume $vg/$volname already exists\n" if !$allow_rename;
|
||||
warn "volume $vg/$volname already exists - importing with a different name\n";
|
||||
$name = undef;
|
||||
}
|
||||
|
||||
my ($size) = PVE::Storage::Plugin::read_common_header($fh);
|
||||
$size = PVE::Storage::Common::align_size_up($size, 1024) / 1024;
|
||||
|
||||
eval {
|
||||
my $allocname = $class->alloc_image($storeid, $scfg, $vmid, 'raw', $name, $size);
|
||||
my $oldname = $volname;
|
||||
$volname = $allocname;
|
||||
if (defined($name) && $allocname ne $oldname) {
|
||||
die "internal error: unexpected allocated name: '$allocname' != '$oldname'\n";
|
||||
}
|
||||
my $file = $class->path($scfg, $volname, $storeid)
|
||||
or die "internal error: failed to get path to newly allocated volume $volname\n";
|
||||
my $allocname = $class->alloc_image($storeid, $scfg, $vmid, 'raw', $name, $size);
|
||||
my $oldname = $volname;
|
||||
$volname = $allocname;
|
||||
if (defined($name) && $allocname ne $oldname) {
|
||||
die "internal error: unexpected allocated name: '$allocname' != '$oldname'\n";
|
||||
}
|
||||
my $file = $class->path($scfg, $volname, $storeid)
|
||||
or die "internal error: failed to get path to newly allocated volume $volname\n";
|
||||
|
||||
$class->volume_import_write($fh, $file);
|
||||
$class->volume_import_write($fh, $file);
|
||||
};
|
||||
if (my $err = $@) {
|
||||
my $cleanup_worker = eval { $class->free_image($storeid, $scfg, $volname, 0) };
|
||||
warn $@ if $@;
|
||||
my $cleanup_worker = eval { $class->free_image($storeid, $scfg, $volname, 0) };
|
||||
warn $@ if $@;
|
||||
|
||||
if ($cleanup_worker) {
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
if ($cleanup_worker) {
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
$rpcenv->fork_worker('imgdel', undef, $authuser, $cleanup_worker);
|
||||
}
|
||||
$rpcenv->fork_worker('imgdel', undef, $authuser, $cleanup_worker);
|
||||
}
|
||||
|
||||
die $err;
|
||||
die $err;
|
||||
}
|
||||
|
||||
return "$storeid:$volname";
|
||||
@ -713,29 +834,22 @@ sub volume_import {
|
||||
|
||||
sub volume_import_write {
|
||||
my ($class, $input_fh, $output_file) = @_;
|
||||
run_command(['dd', "of=$output_file", 'bs=64k'],
|
||||
input => '<&'.fileno($input_fh));
|
||||
run_command(['dd', "of=$output_file", 'bs=64k'], input => '<&' . fileno($input_fh));
|
||||
}
|
||||
|
||||
sub rename_volume {
|
||||
my ($class, $scfg, $storeid, $source_volname, $target_vmid, $target_volname) = @_;
|
||||
|
||||
my (
|
||||
undef,
|
||||
$source_image,
|
||||
$source_vmid,
|
||||
$base_name,
|
||||
$base_vmid,
|
||||
undef,
|
||||
$format
|
||||
undef, $source_image, $source_vmid, $base_name, $base_vmid, undef, $format,
|
||||
) = $class->parse_volname($source_volname);
|
||||
$target_volname = $class->find_free_diskname($storeid, $scfg, $target_vmid, $format)
|
||||
if !$target_volname;
|
||||
if !$target_volname;
|
||||
|
||||
my $vg = $scfg->{vgname};
|
||||
my $lvs = lvm_list_volumes($vg);
|
||||
die "target volume '${target_volname}' already exists\n"
|
||||
if ($lvs->{$vg}->{$target_volname});
|
||||
if ($lvs->{$vg}->{$target_volname});
|
||||
|
||||
lvrename($vg, $source_volname, $target_volname);
|
||||
return "${storeid}:${target_volname}";
|
||||
|
||||
@ -17,12 +17,12 @@ my $get_lun_cmd_map = sub {
|
||||
my $sbdadmcmd = "/usr/sbin/sbdadm";
|
||||
|
||||
my $cmdmap = {
|
||||
create_lu => { cmd => $stmfadmcmd, method => 'create-lu' },
|
||||
delete_lu => { cmd => $stmfadmcmd, method => 'delete-lu' },
|
||||
import_lu => { cmd => $stmfadmcmd, method => 'import-lu' },
|
||||
modify_lu => { cmd => $stmfadmcmd, method => 'modify-lu' },
|
||||
add_view => { cmd => $stmfadmcmd, method => 'add-view' },
|
||||
list_view => { cmd => $stmfadmcmd, method => 'list-view' },
|
||||
create_lu => { cmd => $stmfadmcmd, method => 'create-lu' },
|
||||
delete_lu => { cmd => $stmfadmcmd, method => 'delete-lu' },
|
||||
import_lu => { cmd => $stmfadmcmd, method => 'import-lu' },
|
||||
modify_lu => { cmd => $stmfadmcmd, method => 'modify-lu' },
|
||||
add_view => { cmd => $stmfadmcmd, method => 'add-view' },
|
||||
list_view => { cmd => $stmfadmcmd, method => 'list-view' },
|
||||
list_lu => { cmd => $sbdadmcmd, method => 'list-lu' },
|
||||
};
|
||||
|
||||
@ -45,15 +45,15 @@ sub run_lun_command {
|
||||
$timeout = 10 if !$timeout;
|
||||
|
||||
my $output = sub {
|
||||
my $line = shift;
|
||||
$msg .= "$line\n";
|
||||
my $line = shift;
|
||||
$msg .= "$line\n";
|
||||
};
|
||||
|
||||
if ($method eq 'create_lu') {
|
||||
my $wcd = 'false';
|
||||
my $wcd = 'false';
|
||||
if ($scfg->{nowritecache}) {
|
||||
$wcd = 'true';
|
||||
}
|
||||
$wcd = 'true';
|
||||
}
|
||||
my $prefix = '600144f';
|
||||
my $digest = md5_hex($params[0]);
|
||||
$digest =~ /(\w{7}(.*))/;
|
||||
@ -68,13 +68,13 @@ sub run_lun_command {
|
||||
@params = undef;
|
||||
} elsif ($method eq 'add_view') {
|
||||
if ($scfg->{comstar_tg}) {
|
||||
unshift @params, $scfg->{comstar_tg};
|
||||
unshift @params, '--target-group';
|
||||
}
|
||||
unshift @params, $scfg->{comstar_tg};
|
||||
unshift @params, '--target-group';
|
||||
}
|
||||
if ($scfg->{comstar_hg}) {
|
||||
unshift @params, $scfg->{comstar_hg};
|
||||
unshift @params, '--host-group';
|
||||
}
|
||||
unshift @params, $scfg->{comstar_hg};
|
||||
unshift @params, '--host-group';
|
||||
}
|
||||
}
|
||||
|
||||
my $cmdmap = $get_lun_cmd_map->($method);
|
||||
@ -83,7 +83,15 @@ sub run_lun_command {
|
||||
|
||||
$target = 'root@' . $scfg->{portal};
|
||||
|
||||
my $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $luncmd, $lunmethod, @params];
|
||||
my $cmd = [
|
||||
@ssh_cmd,
|
||||
'-i',
|
||||
"$id_rsa_path/$scfg->{portal}_id_rsa",
|
||||
$target,
|
||||
$luncmd,
|
||||
$lunmethod,
|
||||
@params,
|
||||
];
|
||||
|
||||
run_command($cmd, outfunc => $output, timeout => $timeout);
|
||||
|
||||
|
||||
@ -48,36 +48,42 @@ my $execute_command = sub {
|
||||
$timeout = 10 if !$timeout;
|
||||
|
||||
my $output = sub {
|
||||
my $line = shift;
|
||||
$msg .= "$line\n";
|
||||
my $line = shift;
|
||||
$msg .= "$line\n";
|
||||
};
|
||||
|
||||
my $errfunc = sub {
|
||||
my $line = shift;
|
||||
$err .= "$line";
|
||||
my $line = shift;
|
||||
$err .= "$line";
|
||||
};
|
||||
|
||||
if ($exec eq 'scp') {
|
||||
$target = 'root@[' . $scfg->{portal} . ']';
|
||||
$cmd = [@scp_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", '--', $method, "$target:$params[0]"];
|
||||
$cmd = [
|
||||
@scp_cmd,
|
||||
'-i',
|
||||
"$id_rsa_path/$scfg->{portal}_id_rsa",
|
||||
'--',
|
||||
$method,
|
||||
"$target:$params[0]",
|
||||
];
|
||||
} else {
|
||||
$target = 'root@' . $scfg->{portal};
|
||||
$cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, '--', $method, @params];
|
||||
$cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, '--', $method,
|
||||
@params];
|
||||
}
|
||||
|
||||
eval {
|
||||
run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout);
|
||||
};
|
||||
eval { run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout); };
|
||||
if ($@) {
|
||||
$res = {
|
||||
result => 0,
|
||||
msg => $err,
|
||||
}
|
||||
};
|
||||
} else {
|
||||
$res = {
|
||||
result => 1,
|
||||
msg => $msg,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return $res;
|
||||
@ -104,10 +110,9 @@ my $read_config = sub {
|
||||
|
||||
$target = 'root@' . $scfg->{portal};
|
||||
|
||||
my $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $luncmd, $CONFIG_FILE];
|
||||
eval {
|
||||
run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout);
|
||||
};
|
||||
my $cmd =
|
||||
[@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $luncmd, $CONFIG_FILE];
|
||||
eval { run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout); };
|
||||
if ($@) {
|
||||
die $err if ($err !~ /No such file or directory/);
|
||||
die "No configuration found. Install iet on $scfg->{portal}" if $msg eq '';
|
||||
@ -141,7 +146,7 @@ my $parser = sub {
|
||||
foreach (@cfgfile) {
|
||||
$line++;
|
||||
if ($_ =~ /^\s*Target\s*([\w\-\:\.]+)\s*$/) {
|
||||
if ($1 eq $scfg->{target} && ! $cfg_target) {
|
||||
if ($1 eq $scfg->{target} && !$cfg_target) {
|
||||
# start colect info
|
||||
die "$line: Parse error [$_]" if $SETTINGS;
|
||||
$SETTINGS->{target} = $1;
|
||||
@ -157,7 +162,7 @@ my $parser = sub {
|
||||
} else {
|
||||
if ($cfg_target) {
|
||||
$SETTINGS->{text} .= "$_\n";
|
||||
next if ($_ =~ /^\s*#/ || ! $_);
|
||||
next if ($_ =~ /^\s*#/ || !$_);
|
||||
my $option = $_;
|
||||
if ($_ =~ /^(\w+)\s*#/) {
|
||||
$option = $1;
|
||||
@ -176,7 +181,7 @@ my $parser = sub {
|
||||
foreach (@lun) {
|
||||
my @lun_opt = split '=', $_;
|
||||
die "$line: Parse error [$option]" unless (scalar(@lun_opt) == 2);
|
||||
$conf->{$lun_opt[0]} = $lun_opt[1];
|
||||
$conf->{ $lun_opt[0] } = $lun_opt[1];
|
||||
}
|
||||
if ($conf->{Path} && $conf->{Path} =~ /^$base\/$scfg->{pool}\/([\w\-]+)$/) {
|
||||
$conf->{include} = 1;
|
||||
@ -184,7 +189,7 @@ my $parser = sub {
|
||||
$conf->{include} = 0;
|
||||
}
|
||||
$conf->{lun} = $num;
|
||||
push @{$SETTINGS->{luns}}, $conf;
|
||||
push @{ $SETTINGS->{luns} }, $conf;
|
||||
} else {
|
||||
die "$line: Parse error [$option]";
|
||||
}
|
||||
@ -202,19 +207,24 @@ my $update_config = sub {
|
||||
my $config = '';
|
||||
|
||||
while ((my $option, my $value) = each(%$SETTINGS)) {
|
||||
next if ($option eq 'include' || $option eq 'luns' || $option eq 'Path' || $option eq 'text' || $option eq 'used');
|
||||
next
|
||||
if ($option eq 'include'
|
||||
|| $option eq 'luns'
|
||||
|| $option eq 'Path'
|
||||
|| $option eq 'text'
|
||||
|| $option eq 'used');
|
||||
if ($option eq 'target') {
|
||||
$config = "\n\nTarget " . $SETTINGS->{target} . "\n" . $config;
|
||||
} else {
|
||||
$config .= "\t$option\t\t\t$value\n";
|
||||
}
|
||||
}
|
||||
foreach my $lun (@{$SETTINGS->{luns}}) {
|
||||
foreach my $lun (@{ $SETTINGS->{luns} }) {
|
||||
my $lun_opt = '';
|
||||
while ((my $option, my $value) = each(%$lun)) {
|
||||
next if ($option eq 'include' || $option eq 'lun' || $option eq 'Path');
|
||||
if ($lun_opt eq '') {
|
||||
$lun_opt = $option . '=' . $value;
|
||||
$lun_opt = $option . '=' . $value;
|
||||
} else {
|
||||
$lun_opt .= ',' . $option . '=' . $value;
|
||||
}
|
||||
@ -260,12 +270,12 @@ my $get_lu_name = sub {
|
||||
my $used = ();
|
||||
my $i;
|
||||
|
||||
if (! exists $SETTINGS->{used}) {
|
||||
if (!exists $SETTINGS->{used}) {
|
||||
for ($i = 0; $i < $MAX_LUNS; $i++) {
|
||||
$used->{$i} = 0;
|
||||
}
|
||||
foreach my $lun (@{$SETTINGS->{luns}}) {
|
||||
$used->{$lun->{lun}} = 1;
|
||||
foreach my $lun (@{ $SETTINGS->{luns} }) {
|
||||
$used->{ $lun->{lun} } = 1;
|
||||
}
|
||||
$SETTINGS->{used} = $used;
|
||||
}
|
||||
@ -282,14 +292,14 @@ my $get_lu_name = sub {
|
||||
my $init_lu_name = sub {
|
||||
my $used = ();
|
||||
|
||||
if (! exists($SETTINGS->{used})) {
|
||||
if (!exists($SETTINGS->{used})) {
|
||||
for (my $i = 0; $i < $MAX_LUNS; $i++) {
|
||||
$used->{$i} = 0;
|
||||
}
|
||||
$SETTINGS->{used} = $used;
|
||||
}
|
||||
foreach my $lun (@{$SETTINGS->{luns}}) {
|
||||
$SETTINGS->{used}->{$lun->{lun}} = 1;
|
||||
foreach my $lun (@{ $SETTINGS->{luns} }) {
|
||||
$SETTINGS->{used}->{ $lun->{lun} } = 1;
|
||||
}
|
||||
};
|
||||
|
||||
@ -297,7 +307,7 @@ my $free_lu_name = sub {
|
||||
my ($lu_name) = @_;
|
||||
my $new;
|
||||
|
||||
foreach my $lun (@{$SETTINGS->{luns}}) {
|
||||
foreach my $lun (@{ $SETTINGS->{luns} }) {
|
||||
if ($lun->{lun} != $lu_name) {
|
||||
push @$new, $lun;
|
||||
}
|
||||
@ -310,7 +320,8 @@ my $free_lu_name = sub {
|
||||
my $make_lun = sub {
|
||||
my ($scfg, $path) = @_;
|
||||
|
||||
die 'Maximum number of LUNs per target is 16384' if scalar @{$SETTINGS->{luns}} >= $MAX_LUNS;
|
||||
die 'Maximum number of LUNs per target is 16384'
|
||||
if scalar @{ $SETTINGS->{luns} } >= $MAX_LUNS;
|
||||
|
||||
my $lun = $get_lu_name->();
|
||||
my $conf = {
|
||||
@ -319,7 +330,7 @@ my $make_lun = sub {
|
||||
Type => 'blockio',
|
||||
include => 1,
|
||||
};
|
||||
push @{$SETTINGS->{luns}}, $conf;
|
||||
push @{ $SETTINGS->{luns} }, $conf;
|
||||
|
||||
return $conf;
|
||||
};
|
||||
@ -329,7 +340,7 @@ my $list_view = sub {
|
||||
my $lun = undef;
|
||||
|
||||
my $object = $params[0];
|
||||
foreach my $lun (@{$SETTINGS->{luns}}) {
|
||||
foreach my $lun (@{ $SETTINGS->{luns} }) {
|
||||
next unless $lun->{include} == 1;
|
||||
if ($lun->{Path} =~ /^$object$/) {
|
||||
return $lun->{lun} if (defined($lun->{lun}));
|
||||
@ -345,7 +356,7 @@ my $list_lun = sub {
|
||||
my $name = undef;
|
||||
|
||||
my $object = $params[0];
|
||||
foreach my $lun (@{$SETTINGS->{luns}}) {
|
||||
foreach my $lun (@{ $SETTINGS->{luns} }) {
|
||||
next unless $lun->{include} == 1;
|
||||
if ($lun->{Path} =~ /^$object$/) {
|
||||
return $lun->{Path};
|
||||
@ -381,12 +392,12 @@ my $create_lun = sub {
|
||||
|
||||
my $delete_lun = sub {
|
||||
my ($scfg, $timeout, $method, @params) = @_;
|
||||
my $res = {msg => undef};
|
||||
my $res = { msg => undef };
|
||||
|
||||
my $path = $params[0];
|
||||
my $tid = $get_target_tid->($scfg);
|
||||
|
||||
foreach my $lun (@{$SETTINGS->{luns}}) {
|
||||
foreach my $lun (@{ $SETTINGS->{luns} }) {
|
||||
if ($lun->{Path} eq $path) {
|
||||
@params = ('--op', 'delete', "--tid=$tid", "--lun=$lun->{lun}");
|
||||
$res = $execute_command->($scfg, 'ssh', $timeout, $ietadm, @params);
|
||||
@ -417,7 +428,7 @@ my $modify_lun = sub {
|
||||
my $path = $params[1];
|
||||
my $tid = $get_target_tid->($scfg);
|
||||
|
||||
foreach my $cfg (@{$SETTINGS->{luns}}) {
|
||||
foreach my $cfg (@{ $SETTINGS->{luns} }) {
|
||||
if ($cfg->{Path} eq $path) {
|
||||
$lun = $cfg;
|
||||
last;
|
||||
@ -446,13 +457,13 @@ my $get_lun_cmd_map = sub {
|
||||
my ($method) = @_;
|
||||
|
||||
my $cmdmap = {
|
||||
create_lu => { cmd => $create_lun },
|
||||
delete_lu => { cmd => $delete_lun },
|
||||
import_lu => { cmd => $import_lun },
|
||||
modify_lu => { cmd => $modify_lun },
|
||||
add_view => { cmd => $add_view },
|
||||
list_view => { cmd => $list_view },
|
||||
list_lu => { cmd => $list_lun },
|
||||
create_lu => { cmd => $create_lun },
|
||||
delete_lu => { cmd => $delete_lun },
|
||||
import_lu => { cmd => $import_lun },
|
||||
modify_lu => { cmd => $modify_lun },
|
||||
add_view => { cmd => $add_view },
|
||||
list_view => { cmd => $list_view },
|
||||
list_lu => { cmd => $list_lun },
|
||||
};
|
||||
|
||||
die "unknown command '$method'" unless exists $cmdmap->{$method};
|
||||
|
||||
@ -10,12 +10,12 @@ use warnings;
|
||||
use PVE::Tools qw(run_command file_read_firstline trim dir_glob_regex dir_glob_foreach);
|
||||
|
||||
my @CONFIG_FILES = (
|
||||
'/usr/local/etc/istgt/istgt.conf', # FreeBSD, FreeNAS
|
||||
'/var/etc/iscsi/istgt.conf' # NAS4Free
|
||||
'/usr/local/etc/istgt/istgt.conf', # FreeBSD, FreeNAS
|
||||
'/var/etc/iscsi/istgt.conf' # NAS4Free
|
||||
);
|
||||
my @DAEMONS = (
|
||||
'/usr/local/etc/rc.d/istgt', # FreeBSD, FreeNAS
|
||||
'/var/etc/rc.d/istgt' # NAS4Free
|
||||
'/usr/local/etc/rc.d/istgt', # FreeBSD, FreeNAS
|
||||
'/var/etc/rc.d/istgt' # NAS4Free
|
||||
);
|
||||
|
||||
# A logical unit can max have 63 LUNs
|
||||
@ -69,13 +69,13 @@ my $read_config = sub {
|
||||
$timeout = 10 if !$timeout;
|
||||
|
||||
my $output = sub {
|
||||
my $line = shift;
|
||||
$msg .= "$line\n";
|
||||
my $line = shift;
|
||||
$msg .= "$line\n";
|
||||
};
|
||||
|
||||
my $errfunc = sub {
|
||||
my $line = shift;
|
||||
$err .= "$line";
|
||||
my $line = shift;
|
||||
$err .= "$line";
|
||||
};
|
||||
|
||||
$target = 'root@' . $scfg->{portal};
|
||||
@ -83,7 +83,8 @@ my $read_config = sub {
|
||||
my $daemon = 0;
|
||||
foreach my $config (@CONFIG_FILES) {
|
||||
$err = undef;
|
||||
my $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $luncmd, $config];
|
||||
my $cmd =
|
||||
[@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $luncmd, $config];
|
||||
eval {
|
||||
run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout);
|
||||
};
|
||||
@ -119,17 +120,17 @@ my $parse_size = sub {
|
||||
return 0 if !$text;
|
||||
|
||||
if ($text =~ m/^(\d+(\.\d+)?)([TGMK]B)?$/) {
|
||||
my ($size, $reminder, $unit) = ($1, $2, $3);
|
||||
return $size if !$unit;
|
||||
if ($unit eq 'KB') {
|
||||
$size *= 1024;
|
||||
} elsif ($unit eq 'MB') {
|
||||
$size *= 1024*1024;
|
||||
} elsif ($unit eq 'GB') {
|
||||
$size *= 1024*1024*1024;
|
||||
} elsif ($unit eq 'TB') {
|
||||
$size *= 1024*1024*1024*1024;
|
||||
}
|
||||
my ($size, $reminder, $unit) = ($1, $2, $3);
|
||||
return $size if !$unit;
|
||||
if ($unit eq 'KB') {
|
||||
$size *= 1024;
|
||||
} elsif ($unit eq 'MB') {
|
||||
$size *= 1024 * 1024;
|
||||
} elsif ($unit eq 'GB') {
|
||||
$size *= 1024 * 1024 * 1024;
|
||||
} elsif ($unit eq 'TB') {
|
||||
$size *= 1024 * 1024 * 1024 * 1024;
|
||||
}
|
||||
if ($reminder) {
|
||||
$size = ceil($size);
|
||||
}
|
||||
@ -151,9 +152,9 @@ my $size_with_unit = sub {
|
||||
if ($size =~ m/^\d+$/) {
|
||||
++$n and $size /= 1024 until $size < 1024;
|
||||
if ($size =~ /\./) {
|
||||
return sprintf "%.2f%s", $size, ( qw[bytes KB MB GB TB] )[ $n ];
|
||||
return sprintf "%.2f%s", $size, (qw[bytes KB MB GB TB])[$n];
|
||||
} else {
|
||||
return sprintf "%d%s", $size, ( qw[bytes KB MB GB TB] )[ $n ];
|
||||
return sprintf "%d%s", $size, (qw[bytes KB MB GB TB])[$n];
|
||||
}
|
||||
}
|
||||
die "$size: Not a number";
|
||||
@ -164,18 +165,18 @@ my $lun_dumper = sub {
|
||||
my $config = '';
|
||||
|
||||
$config .= "\n[$lun]\n";
|
||||
$config .= 'TargetName ' . $SETTINGS->{$lun}->{TargetName} . "\n";
|
||||
$config .= 'Mapping ' . $SETTINGS->{$lun}->{Mapping} . "\n";
|
||||
$config .= 'AuthGroup ' . $SETTINGS->{$lun}->{AuthGroup} . "\n";
|
||||
$config .= 'UnitType ' . $SETTINGS->{$lun}->{UnitType} . "\n";
|
||||
$config .= 'QueueDepth ' . $SETTINGS->{$lun}->{QueueDepth} . "\n";
|
||||
$config .= 'TargetName ' . $SETTINGS->{$lun}->{TargetName} . "\n";
|
||||
$config .= 'Mapping ' . $SETTINGS->{$lun}->{Mapping} . "\n";
|
||||
$config .= 'AuthGroup ' . $SETTINGS->{$lun}->{AuthGroup} . "\n";
|
||||
$config .= 'UnitType ' . $SETTINGS->{$lun}->{UnitType} . "\n";
|
||||
$config .= 'QueueDepth ' . $SETTINGS->{$lun}->{QueueDepth} . "\n";
|
||||
|
||||
foreach my $conf (@{$SETTINGS->{$lun}->{luns}}) {
|
||||
$config .= "$conf->{lun} Storage " . $conf->{Storage};
|
||||
foreach my $conf (@{ $SETTINGS->{$lun}->{luns} }) {
|
||||
$config .= "$conf->{lun} Storage " . $conf->{Storage};
|
||||
$config .= ' ' . $size_with_unit->($conf->{Size}) . "\n";
|
||||
foreach ($conf->{options}) {
|
||||
if ($_) {
|
||||
$config .= "$conf->{lun} Option " . $_ . "\n";
|
||||
$config .= "$conf->{lun} Option " . $_ . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -189,11 +190,11 @@ my $get_lu_name = sub {
|
||||
my $used = ();
|
||||
my $i;
|
||||
|
||||
if (! exists $SETTINGS->{$target}->{used}) {
|
||||
if (!exists $SETTINGS->{$target}->{used}) {
|
||||
for ($i = 0; $i < $MAX_LUNS; $i++) {
|
||||
$used->{$i} = 0;
|
||||
}
|
||||
foreach my $lun (@{$SETTINGS->{$target}->{luns}}) {
|
||||
foreach my $lun (@{ $SETTINGS->{$target}->{luns} }) {
|
||||
$lun->{lun} =~ /^LUN(\d+)$/;
|
||||
$used->{$1} = 1;
|
||||
}
|
||||
@ -213,13 +214,13 @@ my $init_lu_name = sub {
|
||||
my ($target) = @_;
|
||||
my $used = ();
|
||||
|
||||
if (! exists($SETTINGS->{$target}->{used})) {
|
||||
if (!exists($SETTINGS->{$target}->{used})) {
|
||||
for (my $i = 0; $i < $MAX_LUNS; $i++) {
|
||||
$used->{$i} = 0;
|
||||
}
|
||||
$SETTINGS->{$target}->{used} = $used;
|
||||
}
|
||||
foreach my $lun (@{$SETTINGS->{$target}->{luns}}) {
|
||||
foreach my $lun (@{ $SETTINGS->{$target}->{luns} }) {
|
||||
$lun->{lun} =~ /^LUN(\d+)$/;
|
||||
$SETTINGS->{$target}->{used}->{$1} = 1;
|
||||
}
|
||||
@ -236,7 +237,8 @@ my $make_lun = sub {
|
||||
my ($scfg, $path) = @_;
|
||||
|
||||
my $target = $SETTINGS->{current};
|
||||
die 'Maximum number of LUNs per target is 63' if scalar @{$SETTINGS->{$target}->{luns}} >= $MAX_LUNS;
|
||||
die 'Maximum number of LUNs per target is 63'
|
||||
if scalar @{ $SETTINGS->{$target}->{luns} } >= $MAX_LUNS;
|
||||
|
||||
my @options = ();
|
||||
my $lun = $get_lu_name->($target);
|
||||
@ -249,7 +251,7 @@ my $make_lun = sub {
|
||||
Size => 'AUTO',
|
||||
options => @options,
|
||||
};
|
||||
push @{$SETTINGS->{$target}->{luns}}, $conf;
|
||||
push @{ $SETTINGS->{$target}->{luns} }, $conf;
|
||||
|
||||
return $conf->{lun};
|
||||
};
|
||||
@ -290,7 +292,7 @@ my $parser = sub {
|
||||
if ($arg2 =~ /^Storage\s*(.+)/i) {
|
||||
$SETTINGS->{$lun}->{$arg1}->{storage} = $1;
|
||||
} elsif ($arg2 =~ /^Option\s*(.+)/i) {
|
||||
push @{$SETTINGS->{$lun}->{$arg1}->{options}}, $1;
|
||||
push @{ $SETTINGS->{$lun}->{$arg1}->{options} }, $1;
|
||||
} else {
|
||||
$SETTINGS->{$lun}->{$arg1} = $arg2;
|
||||
}
|
||||
@ -307,10 +309,10 @@ my $parser = sub {
|
||||
my $base = get_base;
|
||||
|
||||
for (my $i = 1; $i <= $max; $i++) {
|
||||
my $target = $SETTINGS->{nodebase}.':'.$SETTINGS->{"LogicalUnit$i"}->{TargetName};
|
||||
my $target = $SETTINGS->{nodebase} . ':' . $SETTINGS->{"LogicalUnit$i"}->{TargetName};
|
||||
if ($target eq $scfg->{target}) {
|
||||
my $lu = ();
|
||||
while ((my $key, my $val) = each(%{$SETTINGS->{"LogicalUnit$i"}})) {
|
||||
while ((my $key, my $val) = each(%{ $SETTINGS->{"LogicalUnit$i"} })) {
|
||||
if ($key =~ /^LUN\d+/) {
|
||||
$val->{storage} =~ /^([\w\/\-]+)\s+(\w+)/;
|
||||
my $storage = $1;
|
||||
@ -318,7 +320,7 @@ my $parser = sub {
|
||||
my $conf = undef;
|
||||
my @options = ();
|
||||
if ($val->{options}) {
|
||||
@options = @{$val->{options}};
|
||||
@options = @{ $val->{options} };
|
||||
}
|
||||
if ($storage =~ /^$base\/$scfg->{pool}\/([\w\-]+)$/) {
|
||||
$conf = {
|
||||
@ -326,7 +328,7 @@ my $parser = sub {
|
||||
Storage => $storage,
|
||||
Size => $size,
|
||||
options => @options,
|
||||
}
|
||||
};
|
||||
}
|
||||
push @$lu, $conf if $conf;
|
||||
delete $SETTINGS->{"LogicalUnit$i"}->{$key};
|
||||
@ -349,9 +351,9 @@ my $list_lun = sub {
|
||||
my $name = undef;
|
||||
|
||||
my $object = $params[0];
|
||||
for my $key (keys %$SETTINGS) {
|
||||
for my $key (keys %$SETTINGS) {
|
||||
next unless $key =~ /^LogicalUnit\d+$/;
|
||||
foreach my $lun (@{$SETTINGS->{$key}->{luns}}) {
|
||||
foreach my $lun (@{ $SETTINGS->{$key}->{luns} }) {
|
||||
if ($lun->{Storage} =~ /^$object$/) {
|
||||
return $lun->{Storage};
|
||||
}
|
||||
@ -399,7 +401,7 @@ my $delete_lun = sub {
|
||||
my $target = $SETTINGS->{current};
|
||||
my $luns = ();
|
||||
|
||||
foreach my $conf (@{$SETTINGS->{$target}->{luns}}) {
|
||||
foreach my $conf (@{ $SETTINGS->{$target}->{luns} }) {
|
||||
if ($conf->{Storage} =~ /^$params[0]$/) {
|
||||
$free_lu_name->($target, $conf->{lun});
|
||||
} else {
|
||||
@ -448,7 +450,7 @@ my $add_view = sub {
|
||||
params => \@params,
|
||||
};
|
||||
} else {
|
||||
@params = ('-HUP', '`cat '. "$SETTINGS->{pidfile}`");
|
||||
@params = ('-HUP', '`cat ' . "$SETTINGS->{pidfile}`");
|
||||
$cmdmap = {
|
||||
cmd => 'ssh',
|
||||
method => 'kill',
|
||||
@ -477,9 +479,9 @@ my $list_view = sub {
|
||||
my $lun = undef;
|
||||
|
||||
my $object = $params[0];
|
||||
for my $key (keys %$SETTINGS) {
|
||||
for my $key (keys %$SETTINGS) {
|
||||
next unless $key =~ /^LogicalUnit\d+$/;
|
||||
foreach my $lun (@{$SETTINGS->{$key}->{luns}}) {
|
||||
foreach my $lun (@{ $SETTINGS->{$key}->{luns} }) {
|
||||
if ($lun->{Storage} =~ /^$object$/) {
|
||||
if ($lun->{lun} =~ /^LUN(\d+)/) {
|
||||
return $1;
|
||||
@ -496,13 +498,13 @@ my $get_lun_cmd_map = sub {
|
||||
my ($method) = @_;
|
||||
|
||||
my $cmdmap = {
|
||||
create_lu => { cmd => $create_lun },
|
||||
delete_lu => { cmd => $delete_lun },
|
||||
import_lu => { cmd => $import_lun },
|
||||
modify_lu => { cmd => $modify_lun },
|
||||
add_view => { cmd => $add_view },
|
||||
list_view => { cmd => $list_view },
|
||||
list_lu => { cmd => $list_lun },
|
||||
create_lu => { cmd => $create_lun },
|
||||
delete_lu => { cmd => $delete_lun },
|
||||
import_lu => { cmd => $import_lun },
|
||||
modify_lu => { cmd => $modify_lun },
|
||||
add_view => { cmd => $add_view },
|
||||
list_view => { cmd => $list_view },
|
||||
list_lu => { cmd => $list_lun },
|
||||
};
|
||||
|
||||
die "unknown command '$method'" unless exists $cmdmap->{$method};
|
||||
@ -522,8 +524,8 @@ sub run_lun_command {
|
||||
my $is_add_view = 0;
|
||||
|
||||
my $output = sub {
|
||||
my $line = shift;
|
||||
$msg .= "$line\n";
|
||||
my $line = shift;
|
||||
$msg .= "$line\n";
|
||||
};
|
||||
|
||||
$target = 'root@' . $scfg->{portal};
|
||||
@ -531,18 +533,31 @@ sub run_lun_command {
|
||||
$parser->($scfg) unless $SETTINGS;
|
||||
my $cmdmap = $get_lun_cmd_map->($method);
|
||||
if ($method eq 'add_view') {
|
||||
$is_add_view = 1 ;
|
||||
$is_add_view = 1;
|
||||
$timeout = 15;
|
||||
}
|
||||
if (ref $cmdmap->{cmd} eq 'CODE') {
|
||||
$res = $cmdmap->{cmd}->($scfg, $timeout, $method, @params);
|
||||
if (ref $res) {
|
||||
$method = $res->{method};
|
||||
@params = @{$res->{params}};
|
||||
@params = @{ $res->{params} };
|
||||
if ($res->{cmd} eq 'scp') {
|
||||
$cmd = [@scp_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $method, "$target:$params[0]"];
|
||||
$cmd = [
|
||||
@scp_cmd,
|
||||
'-i',
|
||||
"$id_rsa_path/$scfg->{portal}_id_rsa",
|
||||
$method,
|
||||
"$target:$params[0]",
|
||||
];
|
||||
} else {
|
||||
$cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $method, @params];
|
||||
$cmd = [
|
||||
@ssh_cmd,
|
||||
'-i',
|
||||
"$id_rsa_path/$scfg->{portal}_id_rsa",
|
||||
$target,
|
||||
$method,
|
||||
@params,
|
||||
];
|
||||
}
|
||||
} else {
|
||||
return $res;
|
||||
@ -550,12 +565,18 @@ sub run_lun_command {
|
||||
} else {
|
||||
$luncmd = $cmdmap->{cmd};
|
||||
$method = $cmdmap->{method};
|
||||
$cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $luncmd, $method, @params];
|
||||
$cmd = [
|
||||
@ssh_cmd,
|
||||
'-i',
|
||||
"$id_rsa_path/$scfg->{portal}_id_rsa",
|
||||
$target,
|
||||
$luncmd,
|
||||
$method,
|
||||
@params,
|
||||
];
|
||||
}
|
||||
|
||||
eval {
|
||||
run_command($cmd, outfunc => $output, timeout => $timeout);
|
||||
};
|
||||
eval { run_command($cmd, outfunc => $output, timeout => $timeout); };
|
||||
if ($@ && $is_add_view) {
|
||||
my $err = $@;
|
||||
if ($OLD_CONFIG) {
|
||||
@ -565,15 +586,11 @@ sub run_lun_command {
|
||||
print $fh $OLD_CONFIG;
|
||||
close $fh;
|
||||
$cmd = [@scp_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $file, $CONFIG_FILE];
|
||||
eval {
|
||||
run_command($cmd, outfunc => $output, timeout => $timeout);
|
||||
};
|
||||
eval { run_command($cmd, outfunc => $output, timeout => $timeout); };
|
||||
$err1 = $@ if $@;
|
||||
unlink $file;
|
||||
die "$err\n$err1" if $err1;
|
||||
eval {
|
||||
run_lun_command($scfg, undef, 'add_view', 'restart');
|
||||
};
|
||||
eval { run_lun_command($scfg, undef, 'add_view', 'restart'); };
|
||||
die "$err\n$@" if ($@);
|
||||
}
|
||||
die $err;
|
||||
|
||||
@ -29,8 +29,8 @@ sub get_base;
|
||||
# targetcli constants
|
||||
# config file location differs from distro to distro
|
||||
my @CONFIG_FILES = (
|
||||
'/etc/rtslib-fb-target/saveconfig.json', # Debian 9.x et al
|
||||
'/etc/target/saveconfig.json' , # ArchLinux, CentOS
|
||||
'/etc/rtslib-fb-target/saveconfig.json', # Debian 9.x et al
|
||||
'/etc/target/saveconfig.json', # ArchLinux, CentOS
|
||||
);
|
||||
my $BACKSTORE = '/backstores/block';
|
||||
|
||||
@ -58,21 +58,27 @@ my $execute_remote_command = sub {
|
||||
my $errfunc = sub { $err .= "$_[0]\n" };
|
||||
|
||||
$target = 'root@' . $scfg->{portal};
|
||||
$cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, '--', $remote_command, @params];
|
||||
$cmd = [
|
||||
@ssh_cmd,
|
||||
'-i',
|
||||
"$id_rsa_path/$scfg->{portal}_id_rsa",
|
||||
$target,
|
||||
'--',
|
||||
$remote_command,
|
||||
@params,
|
||||
];
|
||||
|
||||
eval {
|
||||
run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout);
|
||||
};
|
||||
eval { run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout); };
|
||||
if ($@) {
|
||||
$res = {
|
||||
result => 0,
|
||||
msg => $err,
|
||||
}
|
||||
$res = {
|
||||
result => 0,
|
||||
msg => $err,
|
||||
};
|
||||
} else {
|
||||
$res = {
|
||||
result => 1,
|
||||
msg => $msg,
|
||||
}
|
||||
$res = {
|
||||
result => 1,
|
||||
msg => $msg,
|
||||
};
|
||||
}
|
||||
|
||||
return $res;
|
||||
@ -96,14 +102,15 @@ my $read_config = sub {
|
||||
$target = 'root@' . $scfg->{portal};
|
||||
|
||||
foreach my $oneFile (@CONFIG_FILES) {
|
||||
my $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $luncmd, $oneFile];
|
||||
eval {
|
||||
run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout);
|
||||
};
|
||||
if ($@) {
|
||||
die $err if ($err !~ /No such file or directory/);
|
||||
}
|
||||
return $msg if $msg ne '';
|
||||
my $cmd =
|
||||
[@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $luncmd, $oneFile];
|
||||
eval {
|
||||
run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout);
|
||||
};
|
||||
if ($@) {
|
||||
die $err if ($err !~ /No such file or directory/);
|
||||
}
|
||||
return $msg if $msg ne '';
|
||||
}
|
||||
|
||||
die "No configuration found. Install targetcli on $scfg->{portal}\n" if $msg eq '';
|
||||
@ -123,11 +130,11 @@ my $get_config = sub {
|
||||
|
||||
# Return settings of a specific target
|
||||
my $get_target_settings = sub {
|
||||
my ($scfg) = @_;
|
||||
my ($scfg) = @_;
|
||||
|
||||
my $id = "$scfg->{portal}.$scfg->{target}";
|
||||
return undef if !$SETTINGS;
|
||||
return $SETTINGS->{$id};
|
||||
my $id = "$scfg->{portal}.$scfg->{target}";
|
||||
return undef if !$SETTINGS;
|
||||
return $SETTINGS->{$id};
|
||||
};
|
||||
|
||||
# fetches and parses targetcli config from the portal
|
||||
@ -137,46 +144,47 @@ my $parser = sub {
|
||||
my $tpg_tag;
|
||||
|
||||
if ($tpg =~ /^tpg(\d+)$/) {
|
||||
$tpg_tag = $1;
|
||||
$tpg_tag = $1;
|
||||
} else {
|
||||
die "Target Portal Group has invalid value, must contain string 'tpg' and a suffix number, eg 'tpg17'\n";
|
||||
die
|
||||
"Target Portal Group has invalid value, must contain string 'tpg' and a suffix number, eg 'tpg17'\n";
|
||||
}
|
||||
|
||||
my $config = $get_config->($scfg);
|
||||
my $jsonconfig = JSON->new->utf8->decode($config);
|
||||
|
||||
my $haveTarget = 0;
|
||||
foreach my $target (@{$jsonconfig->{targets}}) {
|
||||
# only interested in iSCSI targets
|
||||
next if !($target->{fabric} eq 'iscsi' && $target->{wwn} eq $scfg->{target});
|
||||
# find correct TPG
|
||||
foreach my $tpg (@{$target->{tpgs}}) {
|
||||
if ($tpg->{tag} == $tpg_tag) {
|
||||
my $res = [];
|
||||
foreach my $lun (@{$tpg->{luns}}) {
|
||||
my ($idx, $storage_object);
|
||||
if ($lun->{index} =~ /^(\d+)$/) {
|
||||
$idx = $1;
|
||||
}
|
||||
if ($lun->{storage_object} =~ m|^($BACKSTORE/.*)$|) {
|
||||
$storage_object = $1;
|
||||
}
|
||||
die "Invalid lun definition in config!\n"
|
||||
if !(defined($idx) && defined($storage_object));
|
||||
push @$res, { index => $idx, storage_object => $storage_object };
|
||||
}
|
||||
foreach my $target (@{ $jsonconfig->{targets} }) {
|
||||
# only interested in iSCSI targets
|
||||
next if !($target->{fabric} eq 'iscsi' && $target->{wwn} eq $scfg->{target});
|
||||
# find correct TPG
|
||||
foreach my $tpg (@{ $target->{tpgs} }) {
|
||||
if ($tpg->{tag} == $tpg_tag) {
|
||||
my $res = [];
|
||||
foreach my $lun (@{ $tpg->{luns} }) {
|
||||
my ($idx, $storage_object);
|
||||
if ($lun->{index} =~ /^(\d+)$/) {
|
||||
$idx = $1;
|
||||
}
|
||||
if ($lun->{storage_object} =~ m|^($BACKSTORE/.*)$|) {
|
||||
$storage_object = $1;
|
||||
}
|
||||
die "Invalid lun definition in config!\n"
|
||||
if !(defined($idx) && defined($storage_object));
|
||||
push @$res, { index => $idx, storage_object => $storage_object };
|
||||
}
|
||||
|
||||
my $id = "$scfg->{portal}.$scfg->{target}";
|
||||
$SETTINGS->{$id}->{luns} = $res;
|
||||
$haveTarget = 1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
my $id = "$scfg->{portal}.$scfg->{target}";
|
||||
$SETTINGS->{$id}->{luns} = $res;
|
||||
$haveTarget = 1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# seriously unhappy if the target server lacks iSCSI target configuration ...
|
||||
if (!$haveTarget) {
|
||||
die "target portal group tpg$tpg_tag not found!\n";
|
||||
die "target portal group tpg$tpg_tag not found!\n";
|
||||
}
|
||||
};
|
||||
|
||||
@ -194,10 +202,10 @@ my $free_lu_name = sub {
|
||||
|
||||
my $new = [];
|
||||
my $target = $get_target_settings->($scfg);
|
||||
foreach my $lun (@{$target->{luns}}) {
|
||||
if ($lun->{storage_object} ne "$BACKSTORE/$lu_name") {
|
||||
push @$new, $lun;
|
||||
}
|
||||
foreach my $lun (@{ $target->{luns} }) {
|
||||
if ($lun->{storage_object} ne "$BACKSTORE/$lu_name") {
|
||||
push @$new, $lun;
|
||||
}
|
||||
}
|
||||
|
||||
$target->{luns} = $new;
|
||||
@ -208,12 +216,12 @@ my $register_lun = sub {
|
||||
my ($scfg, $idx, $volname) = @_;
|
||||
|
||||
my $conf = {
|
||||
index => $idx,
|
||||
storage_object => "$BACKSTORE/$volname",
|
||||
is_new => 1,
|
||||
index => $idx,
|
||||
storage_object => "$BACKSTORE/$volname",
|
||||
is_new => 1,
|
||||
};
|
||||
my $target = $get_target_settings->($scfg);
|
||||
push @{$target->{luns}}, $conf;
|
||||
push @{ $target->{luns} }, $conf;
|
||||
|
||||
return $conf;
|
||||
};
|
||||
@ -225,17 +233,17 @@ my $extract_volname = sub {
|
||||
|
||||
my $base = get_base;
|
||||
if ($lunpath =~ /^$base\/$scfg->{pool}\/([\w\-]+)$/) {
|
||||
$volname = $1;
|
||||
my $prefix = $get_backstore_prefix->($scfg);
|
||||
my $target = $get_target_settings->($scfg);
|
||||
foreach my $lun (@{$target->{luns}}) {
|
||||
# If we have a lun with the pool prefix matching this vol, then return this one
|
||||
# like pool-pve-vm-100-disk-0
|
||||
# Else, just fallback to the old name scheme which is vm-100-disk-0
|
||||
if ($lun->{storage_object} =~ /^$BACKSTORE\/($prefix$volname)$/) {
|
||||
return $1;
|
||||
}
|
||||
}
|
||||
$volname = $1;
|
||||
my $prefix = $get_backstore_prefix->($scfg);
|
||||
my $target = $get_target_settings->($scfg);
|
||||
foreach my $lun (@{ $target->{luns} }) {
|
||||
# If we have a lun with the pool prefix matching this vol, then return this one
|
||||
# like pool-pve-vm-100-disk-0
|
||||
# Else, just fallback to the old name scheme which is vm-100-disk-0
|
||||
if ($lun->{storage_object} =~ /^$BACKSTORE\/($prefix$volname)$/) {
|
||||
return $1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $volname;
|
||||
@ -252,10 +260,10 @@ my $list_view = sub {
|
||||
|
||||
return undef if !defined($volname); # nothing to search for..
|
||||
|
||||
foreach my $lun (@{$target->{luns}}) {
|
||||
if ($lun->{storage_object} eq "$BACKSTORE/$volname") {
|
||||
return $lun->{index};
|
||||
}
|
||||
foreach my $lun (@{ $target->{luns} }) {
|
||||
if ($lun->{storage_object} eq "$BACKSTORE/$volname") {
|
||||
return $lun->{index};
|
||||
}
|
||||
}
|
||||
|
||||
return $lun;
|
||||
@ -269,10 +277,10 @@ my $list_lun = sub {
|
||||
my $volname = $extract_volname->($scfg, $object);
|
||||
my $target = $get_target_settings->($scfg);
|
||||
|
||||
foreach my $lun (@{$target->{luns}}) {
|
||||
if ($lun->{storage_object} eq "$BACKSTORE/$volname") {
|
||||
return $object;
|
||||
}
|
||||
foreach my $lun (@{ $target->{luns} }) {
|
||||
if ($lun->{storage_object} eq "$BACKSTORE/$volname") {
|
||||
return $object;
|
||||
}
|
||||
}
|
||||
|
||||
return undef;
|
||||
@ -283,7 +291,7 @@ my $create_lun = sub {
|
||||
my ($scfg, $timeout, $method, @params) = @_;
|
||||
|
||||
if ($list_lun->($scfg, $timeout, $method, @params)) {
|
||||
die "$params[0]: LUN already exists!";
|
||||
die "$params[0]: LUN already exists!";
|
||||
}
|
||||
|
||||
my $device = $params[0];
|
||||
@ -294,18 +302,18 @@ my $create_lun = sub {
|
||||
my $tpg = $scfg->{lio_tpg} || die "Target Portal Group not set, aborting!\n";
|
||||
|
||||
# step 1: create backstore for device
|
||||
my @cliparams = ($BACKSTORE, 'create', "name=$volname", "dev=$device" );
|
||||
my @cliparams = ($BACKSTORE, 'create', "name=$volname", "dev=$device");
|
||||
my $res = $execute_remote_command->($scfg, $timeout, $targetcli, @cliparams);
|
||||
die $res->{msg} if !$res->{result};
|
||||
|
||||
# step 2: enable unmap support on the backstore
|
||||
@cliparams = ($BACKSTORE . '/' . $volname, 'set', 'attribute', 'emulate_tpu=1' );
|
||||
@cliparams = ($BACKSTORE . '/' . $volname, 'set', 'attribute', 'emulate_tpu=1');
|
||||
$res = $execute_remote_command->($scfg, $timeout, $targetcli, @cliparams);
|
||||
die $res->{msg} if !$res->{result};
|
||||
|
||||
# step 3: register lun with target
|
||||
# targetcli /iscsi/iqn.2018-04.at.bestsolution.somehost:target/tpg1/luns/ create /backstores/block/foobar
|
||||
@cliparams = ("/iscsi/$scfg->{target}/$tpg/luns/", 'create', "$BACKSTORE/$volname" );
|
||||
@cliparams = ("/iscsi/$scfg->{target}/$tpg/luns/", 'create', "$BACKSTORE/$volname");
|
||||
$res = $execute_remote_command->($scfg, $timeout, $targetcli, @cliparams);
|
||||
die $res->{msg} if !$res->{result};
|
||||
|
||||
@ -314,9 +322,9 @@ my $create_lun = sub {
|
||||
# changed without our knowledge, so relying on the number that targetcli returns
|
||||
my $lun_idx;
|
||||
if ($res->{msg} =~ /LUN (\d+)/) {
|
||||
$lun_idx = $1;
|
||||
$lun_idx = $1;
|
||||
} else {
|
||||
die "unable to determine new LUN index: $res->{msg}";
|
||||
die "unable to determine new LUN index: $res->{msg}";
|
||||
}
|
||||
|
||||
$register_lun->($scfg, $lun_idx, $volname);
|
||||
@ -330,7 +338,7 @@ my $create_lun = sub {
|
||||
|
||||
my $delete_lun = sub {
|
||||
my ($scfg, $timeout, $method, @params) = @_;
|
||||
my $res = {msg => undef};
|
||||
my $res = { msg => undef };
|
||||
|
||||
my $tpg = $scfg->{lio_tpg} || die "Target Portal Group not set, aborting!\n";
|
||||
|
||||
@ -338,30 +346,30 @@ my $delete_lun = sub {
|
||||
my $volname = $extract_volname->($scfg, $path);
|
||||
my $target = $get_target_settings->($scfg);
|
||||
|
||||
foreach my $lun (@{$target->{luns}}) {
|
||||
next if $lun->{storage_object} ne "$BACKSTORE/$volname";
|
||||
foreach my $lun (@{ $target->{luns} }) {
|
||||
next if $lun->{storage_object} ne "$BACKSTORE/$volname";
|
||||
|
||||
# step 1: delete the lun
|
||||
my @cliparams = ("/iscsi/$scfg->{target}/$tpg/luns/", 'delete', "lun$lun->{index}" );
|
||||
my $res = $execute_remote_command->($scfg, $timeout, $targetcli, @cliparams);
|
||||
do {
|
||||
die $res->{msg};
|
||||
} unless $res->{result};
|
||||
# step 1: delete the lun
|
||||
my @cliparams = ("/iscsi/$scfg->{target}/$tpg/luns/", 'delete', "lun$lun->{index}");
|
||||
my $res = $execute_remote_command->($scfg, $timeout, $targetcli, @cliparams);
|
||||
do {
|
||||
die $res->{msg};
|
||||
} unless $res->{result};
|
||||
|
||||
# step 2: delete the backstore
|
||||
@cliparams = ($BACKSTORE, 'delete', $volname);
|
||||
$res = $execute_remote_command->($scfg, $timeout, $targetcli, @cliparams);
|
||||
do {
|
||||
die $res->{msg};
|
||||
} unless $res->{result};
|
||||
# step 2: delete the backstore
|
||||
@cliparams = ($BACKSTORE, 'delete', $volname);
|
||||
$res = $execute_remote_command->($scfg, $timeout, $targetcli, @cliparams);
|
||||
do {
|
||||
die $res->{msg};
|
||||
} unless $res->{result};
|
||||
|
||||
# step 3: save to be safe ...
|
||||
$execute_remote_command->($scfg, $timeout, $targetcli, 'saveconfig');
|
||||
# step 3: save to be safe ...
|
||||
$execute_remote_command->($scfg, $timeout, $targetcli, 'saveconfig');
|
||||
|
||||
# update internal cache
|
||||
$free_lu_name->($scfg, $volname);
|
||||
# update internal cache
|
||||
$free_lu_name->($scfg, $volname);
|
||||
|
||||
last;
|
||||
last;
|
||||
}
|
||||
|
||||
return $res->{msg};
|
||||
@ -387,13 +395,13 @@ my $add_view = sub {
|
||||
};
|
||||
|
||||
my %lun_cmd_map = (
|
||||
create_lu => $create_lun,
|
||||
delete_lu => $delete_lun,
|
||||
import_lu => $import_lun,
|
||||
modify_lu => $modify_lun,
|
||||
add_view => $add_view,
|
||||
list_view => $list_view,
|
||||
list_lu => $list_lun,
|
||||
create_lu => $create_lun,
|
||||
delete_lu => $delete_lun,
|
||||
import_lu => $import_lun,
|
||||
modify_lu => $modify_lun,
|
||||
add_view => $add_view,
|
||||
list_view => $list_view,
|
||||
list_lu => $list_lun,
|
||||
);
|
||||
|
||||
sub run_lun_command {
|
||||
@ -403,8 +411,8 @@ sub run_lun_command {
|
||||
my $timediff = time - $SETTINGS_TIMESTAMP;
|
||||
my $target = $get_target_settings->($scfg);
|
||||
if (!$target || $timediff > $SETTINGS_MAXAGE) {
|
||||
$SETTINGS_TIMESTAMP = time;
|
||||
$parser->($scfg);
|
||||
$SETTINGS_TIMESTAMP = time;
|
||||
$parser->($scfg);
|
||||
}
|
||||
|
||||
die "unknown command '$method'" unless exists $lun_cmd_map{$method};
|
||||
|
||||
@ -30,28 +30,29 @@ sub type {
|
||||
|
||||
sub plugindata {
|
||||
return {
|
||||
content => [ {images => 1, rootdir => 1}, { images => 1, rootdir => 1}],
|
||||
'sensitive-properties' => {},
|
||||
content => [{ images => 1, rootdir => 1 }, { images => 1, rootdir => 1 }],
|
||||
'sensitive-properties' => {},
|
||||
};
|
||||
}
|
||||
|
||||
sub properties {
|
||||
return {
|
||||
thinpool => {
|
||||
description => "LVM thin pool LV name.",
|
||||
type => 'string', format => 'pve-storage-vgname',
|
||||
},
|
||||
thinpool => {
|
||||
description => "LVM thin pool LV name.",
|
||||
type => 'string',
|
||||
format => 'pve-storage-vgname',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
sub options {
|
||||
return {
|
||||
thinpool => { fixed => 1 },
|
||||
vgname => { fixed => 1 },
|
||||
thinpool => { fixed => 1 },
|
||||
vgname => { fixed => 1 },
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
};
|
||||
}
|
||||
|
||||
@ -64,7 +65,7 @@ sub parse_volname {
|
||||
PVE::Storage::Plugin::parse_lvm_name($volname);
|
||||
|
||||
if ($volname =~ m/^((vm|base)-(\d+)-\S+)$/) {
|
||||
return ('images', $1, $3, undef, undef, $2 eq 'base', 'raw');
|
||||
return ('images', $1, $3, undef, undef, $2 eq 'base', 'raw');
|
||||
}
|
||||
|
||||
die "unable to parse lvm volume name '$volname'\n";
|
||||
@ -77,7 +78,7 @@ sub filesystem_path {
|
||||
|
||||
my $vg = $scfg->{vgname};
|
||||
|
||||
my $path = defined($snapname) ? "/dev/$vg/snap_${name}_$snapname": "/dev/$vg/$name";
|
||||
my $path = defined($snapname) ? "/dev/$vg/snap_${name}_$snapname" : "/dev/$vg/$name";
|
||||
|
||||
return wantarray ? ($path, $vmid, $vtype) : $path;
|
||||
}
|
||||
@ -88,19 +89,27 @@ sub alloc_image {
|
||||
die "unsupported format '$fmt'" if $fmt ne 'raw';
|
||||
|
||||
die "illegal name '$name' - should be 'vm-$vmid-*'\n"
|
||||
if $name && $name !~ m/^vm-$vmid-/;
|
||||
if $name && $name !~ m/^vm-$vmid-/;
|
||||
|
||||
my $vgs = PVE::Storage::LVMPlugin::lvm_vgs();
|
||||
|
||||
my $vg = $scfg->{vgname};
|
||||
|
||||
die "no such volume group '$vg'\n" if !defined ($vgs->{$vg});
|
||||
die "no such volume group '$vg'\n" if !defined($vgs->{$vg});
|
||||
|
||||
$name = $class->find_free_diskname($storeid, $scfg, $vmid)
|
||||
if !$name;
|
||||
if !$name;
|
||||
|
||||
my $cmd = ['/sbin/lvcreate', '-aly', '-V', "${size}k", '--name', $name,
|
||||
'--thinpool', "$vg/$scfg->{thinpool}" ];
|
||||
my $cmd = [
|
||||
'/sbin/lvcreate',
|
||||
'-aly',
|
||||
'-V',
|
||||
"${size}k",
|
||||
'--name',
|
||||
$name,
|
||||
'--thinpool',
|
||||
"$vg/$scfg->{thinpool}",
|
||||
];
|
||||
|
||||
run_command($cmd, errmsg => "lvcreate '$vg/$name' error");
|
||||
|
||||
@ -114,20 +123,20 @@ sub free_image {
|
||||
|
||||
my $lvs = PVE::Storage::LVMPlugin::lvm_list_volumes($vg);
|
||||
|
||||
if (my $dat = $lvs->{$scfg->{vgname}}) {
|
||||
if (my $dat = $lvs->{ $scfg->{vgname} }) {
|
||||
|
||||
# remove all volume snapshots first
|
||||
foreach my $lv (keys %$dat) {
|
||||
next if $lv !~ m/^snap_${volname}_${PVE::JSONSchema::CONFIGID_RE}$/;
|
||||
my $cmd = ['/sbin/lvremove', '-f', "$vg/$lv"];
|
||||
run_command($cmd, errmsg => "lvremove snapshot '$vg/$lv' error");
|
||||
}
|
||||
# remove all volume snapshots first
|
||||
foreach my $lv (keys %$dat) {
|
||||
next if $lv !~ m/^snap_${volname}_${PVE::JSONSchema::CONFIGID_RE}$/;
|
||||
my $cmd = ['/sbin/lvremove', '-f', "$vg/$lv"];
|
||||
run_command($cmd, errmsg => "lvremove snapshot '$vg/$lv' error");
|
||||
}
|
||||
|
||||
# finally remove original (if exists)
|
||||
if ($dat->{$volname}) {
|
||||
my $cmd = ['/sbin/lvremove', '-f', "$vg/$volname"];
|
||||
run_command($cmd, errmsg => "lvremove '$vg/$volname' error");
|
||||
}
|
||||
# finally remove original (if exists)
|
||||
if ($dat->{$volname}) {
|
||||
my $cmd = ['/sbin/lvremove', '-f', "$vg/$volname"];
|
||||
run_command($cmd, errmsg => "lvremove '$vg/$volname' error");
|
||||
}
|
||||
}
|
||||
|
||||
return undef;
|
||||
@ -144,31 +153,35 @@ sub list_images {
|
||||
|
||||
if (my $dat = $cache->{lvs}->{$vgname}) {
|
||||
|
||||
foreach my $volname (keys %$dat) {
|
||||
foreach my $volname (keys %$dat) {
|
||||
|
||||
next if $volname !~ m/^(vm|base)-(\d+)-/;
|
||||
my $owner = $2;
|
||||
next if $volname !~ m/^(vm|base)-(\d+)-/;
|
||||
my $owner = $2;
|
||||
|
||||
my $info = $dat->{$volname};
|
||||
my $info = $dat->{$volname};
|
||||
|
||||
next if $info->{lv_type} ne 'V';
|
||||
next if $info->{lv_type} ne 'V';
|
||||
|
||||
next if $info->{pool_lv} ne $scfg->{thinpool};
|
||||
next if $info->{pool_lv} ne $scfg->{thinpool};
|
||||
|
||||
my $volid = "$storeid:$volname";
|
||||
my $volid = "$storeid:$volname";
|
||||
|
||||
if ($vollist) {
|
||||
my $found = grep { $_ eq $volid } @$vollist;
|
||||
next if !$found;
|
||||
} else {
|
||||
next if defined($vmid) && ($owner ne $vmid);
|
||||
}
|
||||
if ($vollist) {
|
||||
my $found = grep { $_ eq $volid } @$vollist;
|
||||
next if !$found;
|
||||
} else {
|
||||
next if defined($vmid) && ($owner ne $vmid);
|
||||
}
|
||||
|
||||
push @$res, {
|
||||
volid => $volid, format => 'raw', size => $info->{lv_size}, vmid => $owner,
|
||||
ctime => $info->{ctime},
|
||||
};
|
||||
}
|
||||
push @$res,
|
||||
{
|
||||
volid => $volid,
|
||||
format => 'raw',
|
||||
size => $info->{lv_size},
|
||||
vmid => $owner,
|
||||
ctime => $info->{ctime},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
@ -181,13 +194,13 @@ sub list_thinpools {
|
||||
my $thinpools = [];
|
||||
|
||||
foreach my $vg (keys %$lvs) {
|
||||
foreach my $lvname (keys %{$lvs->{$vg}}) {
|
||||
next if $lvs->{$vg}->{$lvname}->{lv_type} ne 't';
|
||||
my $lv = $lvs->{$vg}->{$lvname};
|
||||
$lv->{lv} = $lvname;
|
||||
$lv->{vg} = $vg;
|
||||
push @$thinpools, $lv;
|
||||
}
|
||||
foreach my $lvname (keys %{ $lvs->{$vg} }) {
|
||||
next if $lvs->{$vg}->{$lvname}->{lv_type} ne 't';
|
||||
my $lv = $lvs->{$vg}->{$lvname};
|
||||
$lv->{lv} = $lvname;
|
||||
$lv->{vg} = $vg;
|
||||
push @$thinpools, $lv;
|
||||
}
|
||||
}
|
||||
|
||||
return $thinpools;
|
||||
@ -198,17 +211,17 @@ sub status {
|
||||
|
||||
my $lvs = $cache->{lvs} ||= PVE::Storage::LVMPlugin::lvm_list_volumes();
|
||||
|
||||
return if !$lvs->{$scfg->{vgname}};
|
||||
return if !$lvs->{ $scfg->{vgname} };
|
||||
|
||||
my $info = $lvs->{$scfg->{vgname}}->{$scfg->{thinpool}};
|
||||
my $info = $lvs->{ $scfg->{vgname} }->{ $scfg->{thinpool} };
|
||||
|
||||
return if !$info || $info->{lv_type} ne 't' || !$info->{lv_size};
|
||||
|
||||
return (
|
||||
$info->{lv_size},
|
||||
$info->{lv_size} - $info->{used},
|
||||
$info->{used},
|
||||
$info->{lv_state} eq 'a' ? 1 : 0,
|
||||
$info->{lv_size},
|
||||
$info->{lv_size} - $info->{used},
|
||||
$info->{used},
|
||||
$info->{lv_state} eq 'a' ? 1 : 0,
|
||||
);
|
||||
}
|
||||
|
||||
@ -221,7 +234,10 @@ my $activate_lv = sub {
|
||||
|
||||
return if $lvs->{$vg}->{$lv}->{lv_state} eq 'a';
|
||||
|
||||
run_command(['lvchange', '-ay', '-K', "$vg/$lv"], errmsg => "activating LV '$vg/$lv' failed");
|
||||
run_command(
|
||||
['lvchange', '-ay', '-K', "$vg/$lv"],
|
||||
errmsg => "activating LV '$vg/$lv' failed",
|
||||
);
|
||||
|
||||
$lvs->{$vg}->{$lv}->{lv_state} = 'a'; # update cache
|
||||
|
||||
@ -256,7 +272,7 @@ sub deactivate_volume {
|
||||
run_command(['lvchange', '-an', "$vg/$lv"], errmsg => "deactivate_volume '$vg/$lv' error");
|
||||
|
||||
$cache->{lvs}->{$vg}->{$lv}->{lv_state} = '-' # update cache
|
||||
if $cache->{lvs} && $cache->{lvs}->{$vg} && $cache->{lvs}->{$vg}->{$lv};
|
||||
if $cache->{lvs} && $cache->{lvs}->{$vg} && $cache->{lvs}->{$vg}->{$lv};
|
||||
|
||||
return;
|
||||
}
|
||||
@ -269,14 +285,13 @@ sub clone_image {
|
||||
my $lv;
|
||||
|
||||
if ($snap) {
|
||||
$lv = "$vg/snap_${volname}_$snap";
|
||||
$lv = "$vg/snap_${volname}_$snap";
|
||||
} else {
|
||||
my ($vtype, undef, undef, undef, undef, $isBase, $format) =
|
||||
$class->parse_volname($volname);
|
||||
my ($vtype, undef, undef, undef, undef, $isBase, $format) = $class->parse_volname($volname);
|
||||
|
||||
die "clone_image only works on base images\n" if !$isBase;
|
||||
die "clone_image only works on base images\n" if !$isBase;
|
||||
|
||||
$lv = "$vg/$volname";
|
||||
$lv = "$vg/$volname";
|
||||
}
|
||||
|
||||
my $name = $class->find_free_diskname($storeid, $scfg, $vmid);
|
||||
@ -290,8 +305,7 @@ sub clone_image {
|
||||
sub create_base {
|
||||
my ($class, $storeid, $scfg, $volname) = @_;
|
||||
|
||||
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
|
||||
$class->parse_volname($volname);
|
||||
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = $class->parse_volname($volname);
|
||||
|
||||
die "create_base not possible with base image\n" if $isBase;
|
||||
|
||||
@ -299,11 +313,11 @@ sub create_base {
|
||||
my $lvs = PVE::Storage::LVMPlugin::lvm_list_volumes($vg);
|
||||
|
||||
if (my $dat = $lvs->{$vg}) {
|
||||
# to avoid confusion, reject if we find volume snapshots
|
||||
foreach my $lv (keys %$dat) {
|
||||
die "unable to create base volume - found snaphost '$lv'\n"
|
||||
if $lv =~ m/^snap_${volname}_(\w+)$/;
|
||||
}
|
||||
# to avoid confusion, reject if we find volume snapshots
|
||||
foreach my $lv (keys %$dat) {
|
||||
die "unable to create base volume - found snaphost '$lv'\n"
|
||||
if $lv =~ m/^snap_${volname}_(\w+)$/;
|
||||
}
|
||||
}
|
||||
|
||||
my $newname = $name;
|
||||
@ -362,22 +376,21 @@ sub volume_has_feature {
|
||||
my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
|
||||
|
||||
my $features = {
|
||||
snapshot => { current => 1 },
|
||||
clone => { base => 1, snap => 1},
|
||||
template => { current => 1},
|
||||
copy => { base => 1, current => 1, snap => 1},
|
||||
sparseinit => { base => 1, current => 1},
|
||||
rename => {current => 1},
|
||||
snapshot => { current => 1 },
|
||||
clone => { base => 1, snap => 1 },
|
||||
template => { current => 1 },
|
||||
copy => { base => 1, current => 1, snap => 1 },
|
||||
sparseinit => { base => 1, current => 1 },
|
||||
rename => { current => 1 },
|
||||
};
|
||||
|
||||
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
|
||||
$class->parse_volname($volname);
|
||||
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = $class->parse_volname($volname);
|
||||
|
||||
my $key = undef;
|
||||
if($snapname){
|
||||
$key = 'snap';
|
||||
}else{
|
||||
$key = $isBase ? 'base' : 'current';
|
||||
if ($snapname) {
|
||||
$key = 'snap';
|
||||
} else {
|
||||
$key = $isBase ? 'base' : 'current';
|
||||
}
|
||||
return 1 if $features->{$feature}->{$key};
|
||||
|
||||
@ -385,51 +398,62 @@ sub volume_has_feature {
|
||||
}
|
||||
|
||||
sub volume_import {
|
||||
my ($class, $scfg, $storeid, $fh, $volname, $format, $snapshot, $base_snapshot, $with_snapshots, $allow_rename) = @_;
|
||||
my (
|
||||
$class,
|
||||
$scfg,
|
||||
$storeid,
|
||||
$fh,
|
||||
$volname,
|
||||
$format,
|
||||
$snapshot,
|
||||
$base_snapshot,
|
||||
$with_snapshots,
|
||||
$allow_rename,
|
||||
) = @_;
|
||||
|
||||
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase, $file_format) =
|
||||
$class->parse_volname($volname);
|
||||
$class->parse_volname($volname);
|
||||
|
||||
if (!$isBase) {
|
||||
return $class->SUPER::volume_import(
|
||||
$scfg,
|
||||
$storeid,
|
||||
$fh,
|
||||
$volname,
|
||||
$format,
|
||||
$snapshot,
|
||||
$base_snapshot,
|
||||
$with_snapshots,
|
||||
$allow_rename
|
||||
);
|
||||
return $class->SUPER::volume_import(
|
||||
$scfg,
|
||||
$storeid,
|
||||
$fh,
|
||||
$volname,
|
||||
$format,
|
||||
$snapshot,
|
||||
$base_snapshot,
|
||||
$with_snapshots,
|
||||
$allow_rename,
|
||||
);
|
||||
} else {
|
||||
my $tempname;
|
||||
my $vg = $scfg->{vgname};
|
||||
my $lvs = PVE::Storage::LVMPlugin::lvm_list_volumes($vg);
|
||||
if ($lvs->{$vg}->{$volname}) {
|
||||
die "volume $vg/$volname already exists\n" if !$allow_rename;
|
||||
warn "volume $vg/$volname already exists - importing with a different name\n";
|
||||
my $tempname;
|
||||
my $vg = $scfg->{vgname};
|
||||
my $lvs = PVE::Storage::LVMPlugin::lvm_list_volumes($vg);
|
||||
if ($lvs->{$vg}->{$volname}) {
|
||||
die "volume $vg/$volname already exists\n" if !$allow_rename;
|
||||
warn "volume $vg/$volname already exists - importing with a different name\n";
|
||||
|
||||
$tempname = $class->find_free_diskname($storeid, $scfg, $vmid);
|
||||
} else {
|
||||
$tempname = $volname;
|
||||
$tempname =~ s/base/vm/;
|
||||
}
|
||||
$tempname = $class->find_free_diskname($storeid, $scfg, $vmid);
|
||||
} else {
|
||||
$tempname = $volname;
|
||||
$tempname =~ s/base/vm/;
|
||||
}
|
||||
|
||||
my $newvolid = $class->SUPER::volume_import(
|
||||
$scfg,
|
||||
$storeid,
|
||||
$fh,
|
||||
$tempname,
|
||||
$format,
|
||||
$snapshot,
|
||||
$base_snapshot,
|
||||
$with_snapshots,
|
||||
$allow_rename
|
||||
);
|
||||
($storeid,my $newname) = PVE::Storage::parse_volume_id($newvolid);
|
||||
my $newvolid = $class->SUPER::volume_import(
|
||||
$scfg,
|
||||
$storeid,
|
||||
$fh,
|
||||
$tempname,
|
||||
$format,
|
||||
$snapshot,
|
||||
$base_snapshot,
|
||||
$with_snapshots,
|
||||
$allow_rename,
|
||||
);
|
||||
($storeid, my $newname) = PVE::Storage::parse_volume_id($newvolid);
|
||||
|
||||
$volname = $class->create_base($storeid, $scfg, $newname);
|
||||
$volname = $class->create_base($storeid, $scfg, $newname);
|
||||
}
|
||||
|
||||
return "$storeid:$volname";
|
||||
@ -438,8 +462,10 @@ sub volume_import {
|
||||
# used in LVMPlugin->volume_import
|
||||
sub volume_import_write {
|
||||
my ($class, $input_fh, $output_file) = @_;
|
||||
run_command(['dd', "of=$output_file", 'conv=sparse', 'bs=64k'],
|
||||
input => '<&'.fileno($input_fh));
|
||||
run_command(
|
||||
['dd', "of=$output_file", 'conv=sparse', 'bs=64k'],
|
||||
input => '<&' . fileno($input_fh),
|
||||
);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
@ -24,9 +24,9 @@ sub nfs_is_mounted {
|
||||
|
||||
$mountdata = PVE::ProcFSTools::parse_proc_mounts() if !$mountdata;
|
||||
return $mountpoint if grep {
|
||||
$_->[2] =~ /^nfs/ &&
|
||||
$_->[0] =~ m|^\Q$source\E/?$| &&
|
||||
$_->[1] eq $mountpoint
|
||||
$_->[2] =~ /^nfs/
|
||||
&& $_->[0] =~ m|^\Q$source\E/?$|
|
||||
&& $_->[1] eq $mountpoint
|
||||
} @$mountdata;
|
||||
return undef;
|
||||
}
|
||||
@ -39,8 +39,8 @@ sub nfs_mount {
|
||||
|
||||
my $cmd = ['/bin/mount', '-t', 'nfs', $source, $mountpoint];
|
||||
if ($options) {
|
||||
push @$cmd, '-o', $options;
|
||||
}
|
||||
push @$cmd, '-o', $options;
|
||||
}
|
||||
|
||||
run_command($cmd, errmsg => "mount error");
|
||||
}
|
||||
@ -53,49 +53,60 @@ sub type {
|
||||
|
||||
sub plugindata {
|
||||
return {
|
||||
content => [ { images => 1, rootdir => 1, vztmpl => 1, iso => 1, backup => 1, snippets => 1, import => 1 },
|
||||
{ images => 1 }],
|
||||
format => [ { raw => 1, qcow2 => 1, vmdk => 1 } , 'raw' ],
|
||||
'sensitive-properties' => {},
|
||||
content => [
|
||||
{
|
||||
images => 1,
|
||||
rootdir => 1,
|
||||
vztmpl => 1,
|
||||
iso => 1,
|
||||
backup => 1,
|
||||
snippets => 1,
|
||||
import => 1,
|
||||
},
|
||||
{ images => 1 },
|
||||
],
|
||||
format => [{ raw => 1, qcow2 => 1, vmdk => 1 }, 'raw'],
|
||||
'sensitive-properties' => {},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
sub properties {
|
||||
return {
|
||||
export => {
|
||||
description => "NFS export path.",
|
||||
type => 'string', format => 'pve-storage-path',
|
||||
},
|
||||
server => {
|
||||
description => "Server IP or DNS name.",
|
||||
type => 'string', format => 'pve-storage-server',
|
||||
},
|
||||
export => {
|
||||
description => "NFS export path.",
|
||||
type => 'string',
|
||||
format => 'pve-storage-path',
|
||||
},
|
||||
server => {
|
||||
description => "Server IP or DNS name.",
|
||||
type => 'string',
|
||||
format => 'pve-storage-server',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
sub options {
|
||||
return {
|
||||
path => { fixed => 1 },
|
||||
'content-dirs' => { optional => 1 },
|
||||
server => { fixed => 1 },
|
||||
export => { fixed => 1 },
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
maxfiles => { optional => 1 },
|
||||
'prune-backups' => { optional => 1 },
|
||||
'max-protected-backups' => { optional => 1 },
|
||||
options => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
format => { optional => 1 },
|
||||
mkdir => { optional => 1 },
|
||||
'create-base-path' => { optional => 1 },
|
||||
'create-subdirs' => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
preallocation => { optional => 1 },
|
||||
path => { fixed => 1 },
|
||||
'content-dirs' => { optional => 1 },
|
||||
server => { fixed => 1 },
|
||||
export => { fixed => 1 },
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
maxfiles => { optional => 1 },
|
||||
'prune-backups' => { optional => 1 },
|
||||
'max-protected-backups' => { optional => 1 },
|
||||
options => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
format => { optional => 1 },
|
||||
mkdir => { optional => 1 },
|
||||
'create-base-path' => { optional => 1 },
|
||||
'create-subdirs' => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
preallocation => { optional => 1 },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
sub check_config {
|
||||
my ($class, $sectionId, $config, $create, $skipSchemaCheck) = @_;
|
||||
|
||||
@ -110,13 +121,13 @@ sub status {
|
||||
my ($class, $storeid, $scfg, $cache) = @_;
|
||||
|
||||
$cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
|
||||
if !$cache->{mountdata};
|
||||
if !$cache->{mountdata};
|
||||
|
||||
my $path = $scfg->{path};
|
||||
my $server = $scfg->{server};
|
||||
my $export = $scfg->{export};
|
||||
|
||||
return undef if !nfs_is_mounted($server, $export, $path, $cache->{mountdata});
|
||||
return undef if !nfs_is_mounted($server, $export, $path, $cache->{mountdata});
|
||||
|
||||
return $class->SUPER::status($storeid, $scfg, $cache);
|
||||
}
|
||||
@ -125,20 +136,20 @@ sub activate_storage {
|
||||
my ($class, $storeid, $scfg, $cache) = @_;
|
||||
|
||||
$cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
|
||||
if !$cache->{mountdata};
|
||||
if !$cache->{mountdata};
|
||||
|
||||
my $path = $scfg->{path};
|
||||
my $server = $scfg->{server};
|
||||
my $export = $scfg->{export};
|
||||
|
||||
if (!nfs_is_mounted($server, $export, $path, $cache->{mountdata})) {
|
||||
# NOTE: only call mkpath when not mounted (avoid hang when NFS server is offline
|
||||
$class->config_aware_base_mkdir($scfg, $path);
|
||||
# NOTE: only call mkpath when not mounted (avoid hang when NFS server is offline
|
||||
$class->config_aware_base_mkdir($scfg, $path);
|
||||
|
||||
die "unable to activate storage '$storeid' - " .
|
||||
"directory '$path' does not exist\n" if ! -d $path;
|
||||
die "unable to activate storage '$storeid' - " . "directory '$path' does not exist\n"
|
||||
if !-d $path;
|
||||
|
||||
nfs_mount($server, $export, $path, $scfg->{options});
|
||||
nfs_mount($server, $export, $path, $scfg->{options});
|
||||
}
|
||||
|
||||
$class->SUPER::activate_storage($storeid, $scfg, $cache);
|
||||
@ -148,15 +159,15 @@ sub deactivate_storage {
|
||||
my ($class, $storeid, $scfg, $cache) = @_;
|
||||
|
||||
$cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
|
||||
if !$cache->{mountdata};
|
||||
if !$cache->{mountdata};
|
||||
|
||||
my $path = $scfg->{path};
|
||||
my $server = $scfg->{server};
|
||||
my $export = $scfg->{export};
|
||||
|
||||
if (nfs_is_mounted($server, $export, $path, $cache->{mountdata})) {
|
||||
my $cmd = ['/bin/umount', $path];
|
||||
run_command($cmd, errmsg => 'umount error');
|
||||
if (nfs_is_mounted($server, $export, $path, $cache->{mountdata})) {
|
||||
my $cmd = ['/bin/umount', $path];
|
||||
run_command($cmd, errmsg => 'umount error');
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,33 +181,35 @@ sub check_connection {
|
||||
|
||||
my $is_v4 = defined($opts) && $opts =~ /vers=4.*/;
|
||||
if ($is_v4) {
|
||||
my $ip = PVE::JSONSchema::pve_verify_ip($server, 1);
|
||||
if (!defined($ip)) {
|
||||
$ip = PVE::Network::get_ip_from_hostname($server);
|
||||
}
|
||||
my $ip = PVE::JSONSchema::pve_verify_ip($server, 1);
|
||||
if (!defined($ip)) {
|
||||
$ip = PVE::Network::get_ip_from_hostname($server);
|
||||
}
|
||||
|
||||
my $transport = PVE::JSONSchema::pve_verify_ipv4($ip, 1) ? 'tcp' : 'tcp6';
|
||||
my $transport = PVE::JSONSchema::pve_verify_ipv4($ip, 1) ? 'tcp' : 'tcp6';
|
||||
|
||||
# nfsv4 uses a pseudo-filesystem always beginning with /
|
||||
# no exports are listed
|
||||
$cmd = ['/usr/sbin/rpcinfo', '-T', $transport, $ip, 'nfs', '4'];
|
||||
# nfsv4 uses a pseudo-filesystem always beginning with /
|
||||
# no exports are listed
|
||||
$cmd = ['/usr/sbin/rpcinfo', '-T', $transport, $ip, 'nfs', '4'];
|
||||
} else {
|
||||
$cmd = ['/sbin/showmount', '--no-headers', '--exports', $server];
|
||||
$cmd = ['/sbin/showmount', '--no-headers', '--exports', $server];
|
||||
}
|
||||
|
||||
eval { run_command($cmd, timeout => 10, outfunc => sub {}, errfunc => sub {}) };
|
||||
eval {
|
||||
run_command($cmd, timeout => 10, outfunc => sub { }, errfunc => sub { });
|
||||
};
|
||||
if (my $err = $@) {
|
||||
if ($is_v4) {
|
||||
my $port = 2049;
|
||||
$port = $1 if defined($opts) && $opts =~ /port=(\d+)/;
|
||||
if ($is_v4) {
|
||||
my $port = 2049;
|
||||
$port = $1 if defined($opts) && $opts =~ /port=(\d+)/;
|
||||
|
||||
# rpcinfo is expected to work when the port is 0 (see 'man 5 nfs') and tcp_ping()
|
||||
# defaults to port 7 when passing in 0.
|
||||
return 0 if $port == 0;
|
||||
# rpcinfo is expected to work when the port is 0 (see 'man 5 nfs') and tcp_ping()
|
||||
# defaults to port 7 when passing in 0.
|
||||
return 0 if $port == 0;
|
||||
|
||||
return PVE::Network::tcp_ping($server, $port, 2);
|
||||
}
|
||||
return 0;
|
||||
return PVE::Network::tcp_ping($server, $port, 2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
@ -29,51 +29,53 @@ sub type {
|
||||
|
||||
sub plugindata {
|
||||
return {
|
||||
content => [ {backup => 1, none => 1}, { backup => 1 }],
|
||||
'sensitive-properties' => {
|
||||
'encryption-key' => 1,
|
||||
'master-pubkey' => 1,
|
||||
password => 1,
|
||||
},
|
||||
content => [{ backup => 1, none => 1 }, { backup => 1 }],
|
||||
'sensitive-properties' => {
|
||||
'encryption-key' => 1,
|
||||
'master-pubkey' => 1,
|
||||
password => 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
sub properties {
|
||||
return {
|
||||
datastore => {
|
||||
description => "Proxmox Backup Server datastore name.",
|
||||
type => 'string',
|
||||
},
|
||||
# openssl s_client -connect <host>:8007 2>&1 |openssl x509 -fingerprint -sha256
|
||||
fingerprint => get_standard_option('fingerprint-sha256'),
|
||||
'encryption-key' => {
|
||||
description => "Encryption key. Use 'autogen' to generate one automatically without passphrase.",
|
||||
type => 'string',
|
||||
},
|
||||
'master-pubkey' => {
|
||||
description => "Base64-encoded, PEM-formatted public RSA key. Used to encrypt a copy of the encryption-key which will be added to each encrypted backup.",
|
||||
type => 'string',
|
||||
},
|
||||
datastore => {
|
||||
description => "Proxmox Backup Server datastore name.",
|
||||
type => 'string',
|
||||
},
|
||||
# openssl s_client -connect <host>:8007 2>&1 |openssl x509 -fingerprint -sha256
|
||||
fingerprint => get_standard_option('fingerprint-sha256'),
|
||||
'encryption-key' => {
|
||||
description =>
|
||||
"Encryption key. Use 'autogen' to generate one automatically without passphrase.",
|
||||
type => 'string',
|
||||
},
|
||||
'master-pubkey' => {
|
||||
description =>
|
||||
"Base64-encoded, PEM-formatted public RSA key. Used to encrypt a copy of the encryption-key which will be added to each encrypted backup.",
|
||||
type => 'string',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
sub options {
|
||||
return {
|
||||
server => { fixed => 1 },
|
||||
datastore => { fixed => 1 },
|
||||
namespace => { optional => 1 },
|
||||
port => { optional => 1 },
|
||||
nodes => { optional => 1},
|
||||
disable => { optional => 1},
|
||||
content => { optional => 1},
|
||||
username => { optional => 1 },
|
||||
password => { optional => 1 },
|
||||
'encryption-key' => { optional => 1 },
|
||||
'master-pubkey' => { optional => 1 },
|
||||
maxfiles => { optional => 1 },
|
||||
'prune-backups' => { optional => 1 },
|
||||
'max-protected-backups' => { optional => 1 },
|
||||
fingerprint => { optional => 1 },
|
||||
server => { fixed => 1 },
|
||||
datastore => { fixed => 1 },
|
||||
namespace => { optional => 1 },
|
||||
port => { optional => 1 },
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
username => { optional => 1 },
|
||||
password => { optional => 1 },
|
||||
'encryption-key' => { optional => 1 },
|
||||
'master-pubkey' => { optional => 1 },
|
||||
maxfiles => { optional => 1 },
|
||||
'prune-backups' => { optional => 1 },
|
||||
'max-protected-backups' => { optional => 1 },
|
||||
fingerprint => { optional => 1 },
|
||||
};
|
||||
}
|
||||
|
||||
@ -131,8 +133,8 @@ sub pbs_delete_encryption_key {
|
||||
my $pwfile = pbs_encryption_key_file_name($scfg, $storeid);
|
||||
|
||||
if (!unlink $pwfile) {
|
||||
return if $! == ENOENT;
|
||||
die "failed to delete encryption key! $!\n";
|
||||
return if $! == ENOENT;
|
||||
die "failed to delete encryption key! $!\n";
|
||||
}
|
||||
delete $scfg->{'encryption-key'};
|
||||
}
|
||||
@ -153,13 +155,13 @@ sub pbs_open_encryption_key {
|
||||
|
||||
my $keyfd;
|
||||
if (!open($keyfd, '<', $encryption_key_file)) {
|
||||
if ($! == ENOENT) {
|
||||
my $encryption_fp = $scfg->{'encryption-key'};
|
||||
die "encryption configured ('$encryption_fp') but no encryption key file found!\n"
|
||||
if $encryption_fp;
|
||||
return undef;
|
||||
}
|
||||
die "failed to open encryption key: $encryption_key_file: $!\n";
|
||||
if ($! == ENOENT) {
|
||||
my $encryption_fp = $scfg->{'encryption-key'};
|
||||
die "encryption configured ('$encryption_fp') but no encryption key file found!\n"
|
||||
if $encryption_fp;
|
||||
return undef;
|
||||
}
|
||||
die "failed to open encryption key: $encryption_key_file: $!\n";
|
||||
}
|
||||
|
||||
return $keyfd;
|
||||
@ -186,8 +188,8 @@ sub pbs_delete_master_pubkey {
|
||||
my $pwfile = pbs_master_pubkey_file_name($scfg, $storeid);
|
||||
|
||||
if (!unlink $pwfile) {
|
||||
return if $! == ENOENT;
|
||||
die "failed to delete master public key! $!\n";
|
||||
return if $! == ENOENT;
|
||||
die "failed to delete master public key! $!\n";
|
||||
}
|
||||
delete $scfg->{'master-pubkey'};
|
||||
}
|
||||
@ -208,12 +210,12 @@ sub pbs_open_master_pubkey {
|
||||
|
||||
my $keyfd;
|
||||
if (!open($keyfd, '<', $master_pubkey_file)) {
|
||||
if ($! == ENOENT) {
|
||||
die "master public key configured but no key file found!\n"
|
||||
if $scfg->{'master-pubkey'};
|
||||
return undef;
|
||||
}
|
||||
die "failed to open master public key: $master_pubkey_file: $!\n";
|
||||
if ($! == ENOENT) {
|
||||
die "master public key configured but no key file found!\n"
|
||||
if $scfg->{'master-pubkey'};
|
||||
return undef;
|
||||
}
|
||||
die "failed to open master public key: $master_pubkey_file: $!\n";
|
||||
}
|
||||
|
||||
return $keyfd;
|
||||
@ -244,24 +246,24 @@ my sub api_param_from_volname : prototype($$$) {
|
||||
|
||||
my @tm = (POSIX::strptime($timestr, "%FT%TZ"));
|
||||
# expect sec, min, hour, mday, mon, year
|
||||
die "error parsing time from '$volname'" if grep { !defined($_) } @tm[0..5];
|
||||
die "error parsing time from '$volname'" if grep { !defined($_) } @tm[0 .. 5];
|
||||
|
||||
my $btime;
|
||||
{
|
||||
local $ENV{TZ} = 'UTC'; # $timestr is UTC
|
||||
local $ENV{TZ} = 'UTC'; # $timestr is UTC
|
||||
|
||||
# Fill in isdst to avoid undef warning. No daylight saving time for UTC.
|
||||
$tm[8] //= 0;
|
||||
# Fill in isdst to avoid undef warning. No daylight saving time for UTC.
|
||||
$tm[8] //= 0;
|
||||
|
||||
my $since_epoch = mktime(@tm) or die "error converting time from '$volname'\n";
|
||||
$btime = int($since_epoch);
|
||||
my $since_epoch = mktime(@tm) or die "error converting time from '$volname'\n";
|
||||
$btime = int($since_epoch);
|
||||
}
|
||||
|
||||
return {
|
||||
(ns($scfg, 'ns')),
|
||||
'backup-type' => $btype,
|
||||
'backup-id' => $bid,
|
||||
'backup-time' => $btime,
|
||||
(ns($scfg, 'ns')),
|
||||
'backup-type' => $btype,
|
||||
'backup-id' => $bid,
|
||||
'backup-time' => $btime,
|
||||
};
|
||||
}
|
||||
|
||||
@ -283,7 +285,7 @@ my sub do_raw_client_cmd {
|
||||
|
||||
my $client_exe = '/usr/bin/proxmox-backup-client';
|
||||
die "executable not found '$client_exe'! Proxmox backup client not installed?\n"
|
||||
if ! -x $client_exe;
|
||||
if !-x $client_exe;
|
||||
|
||||
my $repo = PVE::PBSClient::get_repository($scfg);
|
||||
|
||||
@ -298,29 +300,29 @@ my sub do_raw_client_cmd {
|
||||
# This must live in the top scope to not get closed before the `run_command`
|
||||
my ($keyfd, $master_fd);
|
||||
if ($use_crypto) {
|
||||
if (defined($keyfd = pbs_open_encryption_key($scfg, $storeid))) {
|
||||
my $flags = fcntl($keyfd, F_GETFD, 0)
|
||||
// die "failed to get file descriptor flags: $!\n";
|
||||
fcntl($keyfd, F_SETFD, $flags & ~FD_CLOEXEC)
|
||||
or die "failed to remove FD_CLOEXEC from encryption key file descriptor\n";
|
||||
push @$cmd, '--crypt-mode=encrypt', '--keyfd='.fileno($keyfd);
|
||||
if ($use_master && defined($master_fd = pbs_open_master_pubkey($scfg, $storeid))) {
|
||||
my $flags = fcntl($master_fd, F_GETFD, 0)
|
||||
// die "failed to get file descriptor flags: $!\n";
|
||||
fcntl($master_fd, F_SETFD, $flags & ~FD_CLOEXEC)
|
||||
or die "failed to remove FD_CLOEXEC from master public key file descriptor\n";
|
||||
push @$cmd, '--master-pubkey-fd='.fileno($master_fd);
|
||||
}
|
||||
} else {
|
||||
push @$cmd, '--crypt-mode=none';
|
||||
}
|
||||
if (defined($keyfd = pbs_open_encryption_key($scfg, $storeid))) {
|
||||
my $flags = fcntl($keyfd, F_GETFD, 0)
|
||||
// die "failed to get file descriptor flags: $!\n";
|
||||
fcntl($keyfd, F_SETFD, $flags & ~FD_CLOEXEC)
|
||||
or die "failed to remove FD_CLOEXEC from encryption key file descriptor\n";
|
||||
push @$cmd, '--crypt-mode=encrypt', '--keyfd=' . fileno($keyfd);
|
||||
if ($use_master && defined($master_fd = pbs_open_master_pubkey($scfg, $storeid))) {
|
||||
my $flags = fcntl($master_fd, F_GETFD, 0)
|
||||
// die "failed to get file descriptor flags: $!\n";
|
||||
fcntl($master_fd, F_SETFD, $flags & ~FD_CLOEXEC)
|
||||
or die "failed to remove FD_CLOEXEC from master public key file descriptor\n";
|
||||
push @$cmd, '--master-pubkey-fd=' . fileno($master_fd);
|
||||
}
|
||||
} else {
|
||||
push @$cmd, '--crypt-mode=none';
|
||||
}
|
||||
}
|
||||
|
||||
push @$cmd, @$param if defined($param);
|
||||
|
||||
push @$cmd, "--repository", $repo;
|
||||
if ($client_cmd ne 'status' && defined(my $ns = $scfg->{namespace})) {
|
||||
push @$cmd, '--ns', $ns;
|
||||
push @$cmd, '--ns', $ns;
|
||||
}
|
||||
|
||||
local $ENV{PBS_PASSWORD} = pbs_get_password($scfg, $storeid);
|
||||
@ -332,7 +334,7 @@ my sub do_raw_client_cmd {
|
||||
local $ENV{PROXMOX_OUTPUT_NO_HEADER} = 1;
|
||||
|
||||
if (my $logfunc = $opts{logfunc}) {
|
||||
$logfunc->("run: " . join(' ', @$cmd));
|
||||
$logfunc->("run: " . join(' ', @$cmd));
|
||||
}
|
||||
|
||||
run_command($cmd, %opts);
|
||||
@ -357,12 +359,15 @@ sub run_client_cmd {
|
||||
my $outfunc = sub { $json_str .= "$_[0]\n" };
|
||||
|
||||
$param = [] if !defined($param);
|
||||
$param = [ $param ] if !ref($param);
|
||||
$param = [$param] if !ref($param);
|
||||
|
||||
$param = [@$param, '--output-format=json'] if !$no_output;
|
||||
|
||||
do_raw_client_cmd($scfg, $storeid, $client_cmd, $param,
|
||||
outfunc => $outfunc, errmsg => 'proxmox-backup-client failed');
|
||||
do_raw_client_cmd(
|
||||
$scfg, $storeid, $client_cmd, $param,
|
||||
outfunc => $outfunc,
|
||||
errmsg => 'proxmox-backup-client failed',
|
||||
);
|
||||
|
||||
return undef if $no_output;
|
||||
|
||||
@ -383,15 +388,18 @@ sub extract_vzdump_config {
|
||||
|
||||
my $config_name;
|
||||
if ($format eq 'pbs-vm') {
|
||||
$config_name = 'qemu-server.conf';
|
||||
} elsif ($format eq 'pbs-ct') {
|
||||
$config_name = 'pct.conf';
|
||||
$config_name = 'qemu-server.conf';
|
||||
} elsif ($format eq 'pbs-ct') {
|
||||
$config_name = 'pct.conf';
|
||||
} else {
|
||||
die "unable to extract configuration for backup format '$format'\n";
|
||||
die "unable to extract configuration for backup format '$format'\n";
|
||||
}
|
||||
|
||||
do_raw_client_cmd($scfg, $storeid, 'restore', [ $name, $config_name, '-' ],
|
||||
outfunc => $outfunc, errmsg => 'proxmox-backup-client failed');
|
||||
do_raw_client_cmd(
|
||||
$scfg, $storeid, 'restore', [$name, $config_name, '-'],
|
||||
outfunc => $outfunc,
|
||||
errmsg => 'proxmox-backup-client failed',
|
||||
);
|
||||
|
||||
return $config;
|
||||
}
|
||||
@ -407,19 +415,19 @@ sub prune_backups {
|
||||
my $backup_groups = {};
|
||||
|
||||
if (defined($vmid) && defined($type)) {
|
||||
# no need to get the list of volumes, we only got a single backup group anyway
|
||||
$backup_groups->{"$type/$vmid"} = 1;
|
||||
# no need to get the list of volumes, we only got a single backup group anyway
|
||||
$backup_groups->{"$type/$vmid"} = 1;
|
||||
} else {
|
||||
my $backups = eval { $class->list_volumes($storeid, $scfg, $vmid, ['backup']) };
|
||||
die "failed to get list of all backups to prune - $@" if $@;
|
||||
my $backups = eval { $class->list_volumes($storeid, $scfg, $vmid, ['backup']) };
|
||||
die "failed to get list of all backups to prune - $@" if $@;
|
||||
|
||||
foreach my $backup (@{$backups}) {
|
||||
(my $backup_type = $backup->{format}) =~ s/^pbs-//;
|
||||
next if defined($type) && $backup_type ne $type;
|
||||
foreach my $backup (@{$backups}) {
|
||||
(my $backup_type = $backup->{format}) =~ s/^pbs-//;
|
||||
next if defined($type) && $backup_type ne $type;
|
||||
|
||||
my $backup_group = "$backup_type/$backup->{vmid}";
|
||||
$backup_groups->{$backup_group} = 1;
|
||||
}
|
||||
my $backup_group = "$backup_type/$backup->{vmid}";
|
||||
$backup_groups->{$backup_group} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
my @param;
|
||||
@ -427,13 +435,13 @@ sub prune_backups {
|
||||
my $keep_all = delete $keep->{'keep-all'};
|
||||
|
||||
if (!$keep_all) {
|
||||
foreach my $opt (keys %{$keep}) {
|
||||
next if $keep->{$opt} == 0;
|
||||
push @param, "--$opt";
|
||||
push @param, "$keep->{$opt}";
|
||||
}
|
||||
foreach my $opt (keys %{$keep}) {
|
||||
next if $keep->{$opt} == 0;
|
||||
push @param, "--$opt";
|
||||
push @param, "$keep->{$opt}";
|
||||
}
|
||||
} else { # no need to pass anything to PBS
|
||||
$keep = { 'keep-all' => 1 };
|
||||
$keep = { 'keep-all' => 1 };
|
||||
}
|
||||
|
||||
push @param, '--dry-run' if $dryrun;
|
||||
@ -442,39 +450,40 @@ sub prune_backups {
|
||||
my $failed;
|
||||
|
||||
foreach my $backup_group (keys %{$backup_groups}) {
|
||||
$logfunc->('info', "running 'proxmox-backup-client prune' for '$backup_group'")
|
||||
if !$dryrun;
|
||||
eval {
|
||||
my $res = run_client_cmd($scfg, $storeid, 'prune', [ $backup_group, @param ]);
|
||||
$logfunc->('info', "running 'proxmox-backup-client prune' for '$backup_group'")
|
||||
if !$dryrun;
|
||||
eval {
|
||||
my $res = run_client_cmd($scfg, $storeid, 'prune', [$backup_group, @param]);
|
||||
|
||||
foreach my $backup (@{$res}) {
|
||||
die "result from proxmox-backup-client is not as expected\n"
|
||||
if !defined($backup->{'backup-time'})
|
||||
|| !defined($backup->{'backup-type'})
|
||||
|| !defined($backup->{'backup-id'})
|
||||
|| !defined($backup->{'keep'});
|
||||
foreach my $backup (@{$res}) {
|
||||
die "result from proxmox-backup-client is not as expected\n"
|
||||
if !defined($backup->{'backup-time'})
|
||||
|| !defined($backup->{'backup-type'})
|
||||
|| !defined($backup->{'backup-id'})
|
||||
|| !defined($backup->{'keep'});
|
||||
|
||||
my $ctime = $backup->{'backup-time'};
|
||||
my $type = $backup->{'backup-type'};
|
||||
my $vmid = $backup->{'backup-id'};
|
||||
my $volid = print_volid($storeid, $type, $vmid, $ctime);
|
||||
my $ctime = $backup->{'backup-time'};
|
||||
my $type = $backup->{'backup-type'};
|
||||
my $vmid = $backup->{'backup-id'};
|
||||
my $volid = print_volid($storeid, $type, $vmid, $ctime);
|
||||
|
||||
my $mark = $backup->{keep} ? 'keep' : 'remove';
|
||||
$mark = 'protected' if $backup->{protected};
|
||||
my $mark = $backup->{keep} ? 'keep' : 'remove';
|
||||
$mark = 'protected' if $backup->{protected};
|
||||
|
||||
push @{$prune_list}, {
|
||||
ctime => $ctime,
|
||||
mark => $mark,
|
||||
type => $type eq 'vm' ? 'qemu' : 'lxc',
|
||||
vmid => $vmid,
|
||||
volid => $volid,
|
||||
};
|
||||
}
|
||||
};
|
||||
if (my $err = $@) {
|
||||
$logfunc->('err', "prune '$backup_group': $err\n");
|
||||
$failed = 1;
|
||||
}
|
||||
push @{$prune_list},
|
||||
{
|
||||
ctime => $ctime,
|
||||
mark => $mark,
|
||||
type => $type eq 'vm' ? 'qemu' : 'lxc',
|
||||
vmid => $vmid,
|
||||
volid => $volid,
|
||||
};
|
||||
}
|
||||
};
|
||||
if (my $err = $@) {
|
||||
$logfunc->('err', "prune '$backup_group': $err\n");
|
||||
$failed = 1;
|
||||
}
|
||||
}
|
||||
die "error pruning backups - check log\n" if $failed;
|
||||
|
||||
@ -485,7 +494,7 @@ my $autogen_encryption_key = sub {
|
||||
my ($scfg, $storeid) = @_;
|
||||
my $encfile = pbs_encryption_key_file_name($scfg, $storeid);
|
||||
if (-f $encfile) {
|
||||
rename $encfile, "$encfile.old";
|
||||
rename $encfile, "$encfile.old";
|
||||
}
|
||||
my $cmd = ['proxmox-backup-client', 'key', 'create', '--kdf', 'none', $encfile];
|
||||
run_command($cmd, errmsg => 'failed to create encryption key');
|
||||
@ -498,38 +507,38 @@ sub on_add_hook {
|
||||
my $res = {};
|
||||
|
||||
if (defined(my $password = $param{password})) {
|
||||
pbs_set_password($scfg, $storeid, $password);
|
||||
pbs_set_password($scfg, $storeid, $password);
|
||||
} else {
|
||||
pbs_delete_password($scfg, $storeid);
|
||||
pbs_delete_password($scfg, $storeid);
|
||||
}
|
||||
|
||||
if (defined(my $encryption_key = $param{'encryption-key'})) {
|
||||
my $decoded_key;
|
||||
if ($encryption_key eq 'autogen') {
|
||||
$res->{'encryption-key'} = $autogen_encryption_key->($scfg, $storeid);
|
||||
$decoded_key = decode_json($res->{'encryption-key'});
|
||||
} else {
|
||||
$decoded_key = eval { decode_json($encryption_key) };
|
||||
if ($@ || !exists($decoded_key->{data})) {
|
||||
die "Value does not seems like a valid, JSON formatted encryption key!\n";
|
||||
}
|
||||
pbs_set_encryption_key($scfg, $storeid, $encryption_key);
|
||||
$res->{'encryption-key'} = $encryption_key;
|
||||
}
|
||||
$scfg->{'encryption-key'} = $decoded_key->{fingerprint} || 1;
|
||||
my $decoded_key;
|
||||
if ($encryption_key eq 'autogen') {
|
||||
$res->{'encryption-key'} = $autogen_encryption_key->($scfg, $storeid);
|
||||
$decoded_key = decode_json($res->{'encryption-key'});
|
||||
} else {
|
||||
$decoded_key = eval { decode_json($encryption_key) };
|
||||
if ($@ || !exists($decoded_key->{data})) {
|
||||
die "Value does not seems like a valid, JSON formatted encryption key!\n";
|
||||
}
|
||||
pbs_set_encryption_key($scfg, $storeid, $encryption_key);
|
||||
$res->{'encryption-key'} = $encryption_key;
|
||||
}
|
||||
$scfg->{'encryption-key'} = $decoded_key->{fingerprint} || 1;
|
||||
} else {
|
||||
pbs_delete_encryption_key($scfg, $storeid);
|
||||
pbs_delete_encryption_key($scfg, $storeid);
|
||||
}
|
||||
|
||||
if (defined(my $master_key = delete $param{'master-pubkey'})) {
|
||||
die "'master-pubkey' can only be used together with 'encryption-key'\n"
|
||||
if !defined($scfg->{'encryption-key'});
|
||||
die "'master-pubkey' can only be used together with 'encryption-key'\n"
|
||||
if !defined($scfg->{'encryption-key'});
|
||||
|
||||
my $decoded = decode_base64($master_key);
|
||||
pbs_set_master_pubkey($scfg, $storeid, $decoded);
|
||||
$scfg->{'master-pubkey'} = 1;
|
||||
my $decoded = decode_base64($master_key);
|
||||
pbs_set_master_pubkey($scfg, $storeid, $decoded);
|
||||
$scfg->{'master-pubkey'} = 1;
|
||||
} else {
|
||||
pbs_delete_master_pubkey($scfg, $storeid);
|
||||
pbs_delete_master_pubkey($scfg, $storeid);
|
||||
}
|
||||
|
||||
return $res;
|
||||
@ -541,43 +550,43 @@ sub on_update_hook {
|
||||
my $res = {};
|
||||
|
||||
if (exists($param{password})) {
|
||||
if (defined($param{password})) {
|
||||
pbs_set_password($scfg, $storeid, $param{password});
|
||||
} else {
|
||||
pbs_delete_password($scfg, $storeid);
|
||||
}
|
||||
if (defined($param{password})) {
|
||||
pbs_set_password($scfg, $storeid, $param{password});
|
||||
} else {
|
||||
pbs_delete_password($scfg, $storeid);
|
||||
}
|
||||
}
|
||||
|
||||
if (exists($param{'encryption-key'})) {
|
||||
if (defined(my $encryption_key = delete($param{'encryption-key'}))) {
|
||||
my $decoded_key;
|
||||
if ($encryption_key eq 'autogen') {
|
||||
$res->{'encryption-key'} = $autogen_encryption_key->($scfg, $storeid);
|
||||
$decoded_key = decode_json($res->{'encryption-key'});
|
||||
} else {
|
||||
$decoded_key = eval { decode_json($encryption_key) };
|
||||
if ($@ || !exists($decoded_key->{data})) {
|
||||
die "Value does not seems like a valid, JSON formatted encryption key!\n";
|
||||
}
|
||||
pbs_set_encryption_key($scfg, $storeid, $encryption_key);
|
||||
$res->{'encryption-key'} = $encryption_key;
|
||||
}
|
||||
$scfg->{'encryption-key'} = $decoded_key->{fingerprint} || 1;
|
||||
} else {
|
||||
pbs_delete_encryption_key($scfg, $storeid);
|
||||
delete $scfg->{'encryption-key'};
|
||||
}
|
||||
if (defined(my $encryption_key = delete($param{'encryption-key'}))) {
|
||||
my $decoded_key;
|
||||
if ($encryption_key eq 'autogen') {
|
||||
$res->{'encryption-key'} = $autogen_encryption_key->($scfg, $storeid);
|
||||
$decoded_key = decode_json($res->{'encryption-key'});
|
||||
} else {
|
||||
$decoded_key = eval { decode_json($encryption_key) };
|
||||
if ($@ || !exists($decoded_key->{data})) {
|
||||
die "Value does not seems like a valid, JSON formatted encryption key!\n";
|
||||
}
|
||||
pbs_set_encryption_key($scfg, $storeid, $encryption_key);
|
||||
$res->{'encryption-key'} = $encryption_key;
|
||||
}
|
||||
$scfg->{'encryption-key'} = $decoded_key->{fingerprint} || 1;
|
||||
} else {
|
||||
pbs_delete_encryption_key($scfg, $storeid);
|
||||
delete $scfg->{'encryption-key'};
|
||||
}
|
||||
}
|
||||
|
||||
if (exists($param{'master-pubkey'})) {
|
||||
if (defined(my $master_key = delete($param{'master-pubkey'}))) {
|
||||
my $decoded = decode_base64($master_key);
|
||||
if (defined(my $master_key = delete($param{'master-pubkey'}))) {
|
||||
my $decoded = decode_base64($master_key);
|
||||
|
||||
pbs_set_master_pubkey($scfg, $storeid, $decoded);
|
||||
$scfg->{'master-pubkey'} = 1;
|
||||
} else {
|
||||
pbs_delete_master_pubkey($scfg, $storeid);
|
||||
}
|
||||
pbs_set_master_pubkey($scfg, $storeid, $decoded);
|
||||
$scfg->{'master-pubkey'} = 1;
|
||||
} else {
|
||||
pbs_delete_master_pubkey($scfg, $storeid);
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
@ -596,19 +605,21 @@ sub on_delete_hook {
|
||||
sub parse_volname {
|
||||
my ($class, $volname) = @_;
|
||||
|
||||
if ($volname =~ m!^backup/([^\s_]+)/([^\s_]+)/([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z)$!) {
|
||||
my $btype = $1;
|
||||
my $bid = $2;
|
||||
my $btime = $3;
|
||||
my $format = "pbs-$btype";
|
||||
if ($volname =~
|
||||
m!^backup/([^\s_]+)/([^\s_]+)/([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z)$!
|
||||
) {
|
||||
my $btype = $1;
|
||||
my $bid = $2;
|
||||
my $btime = $3;
|
||||
my $format = "pbs-$btype";
|
||||
|
||||
my $name = "$btype/$bid/$btime";
|
||||
my $name = "$btype/$bid/$btime";
|
||||
|
||||
if ($bid =~ m/^\d+$/) {
|
||||
return ('backup', $name, $bid, undef, undef, undef, $format);
|
||||
} else {
|
||||
return ('backup', $name, undef, undef, undef, undef, $format);
|
||||
}
|
||||
if ($bid =~ m/^\d+$/) {
|
||||
return ('backup', $name, $bid, undef, undef, undef, $format);
|
||||
} else {
|
||||
return ('backup', $name, undef, undef, undef, undef, $format);
|
||||
}
|
||||
}
|
||||
|
||||
die "unable to parse PBS volume name '$volname'\n";
|
||||
@ -618,7 +629,7 @@ sub path {
|
||||
my ($class, $scfg, $volname, $storeid, $snapname) = @_;
|
||||
|
||||
die "volume snapshot is not possible on pbs storage"
|
||||
if defined($snapname);
|
||||
if defined($snapname);
|
||||
|
||||
my ($vtype, $name, $vmid) = $class->parse_volname($volname);
|
||||
|
||||
@ -627,8 +638,8 @@ sub path {
|
||||
# artificial url - we currently do not use that anywhere
|
||||
my $path = "pbs://$repo/$name";
|
||||
if (defined(my $ns = $scfg->{namespace})) {
|
||||
$ns =~ s|/|%2f|g; # other characters to escape aren't allowed in the namespace schema
|
||||
$path .= "?ns=$ns";
|
||||
$ns =~ s|/|%2f|g; # other characters to escape aren't allowed in the namespace schema
|
||||
$path .= "?ns=$ns";
|
||||
}
|
||||
|
||||
return ($path, $vmid, $vtype);
|
||||
@ -657,12 +668,11 @@ sub free_image {
|
||||
|
||||
my ($vtype, $name, $vmid) = $class->parse_volname($volname);
|
||||
|
||||
run_client_cmd($scfg, $storeid, "forget", [ $name ], 1);
|
||||
run_client_cmd($scfg, $storeid, "forget", [$name], 1);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
sub list_images {
|
||||
my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_;
|
||||
|
||||
@ -678,13 +688,13 @@ my sub snapshot_files_encrypted {
|
||||
my $any;
|
||||
my $all = 1;
|
||||
for my $file (@$files) {
|
||||
my $fn = $file->{filename};
|
||||
next if $fn eq 'client.log.blob' || $fn eq 'index.json.blob';
|
||||
my $fn = $file->{filename};
|
||||
next if $fn eq 'client.log.blob' || $fn eq 'index.json.blob';
|
||||
|
||||
my $crypt = $file->{'crypt-mode'};
|
||||
my $crypt = $file->{'crypt-mode'};
|
||||
|
||||
$all = 0 if !$crypt || $crypt ne 'encrypt';
|
||||
$any ||= defined($crypt) && $crypt eq 'encrypt';
|
||||
$all = 0 if !$crypt || $crypt ne 'encrypt';
|
||||
$any ||= defined($crypt) && $crypt eq 'encrypt';
|
||||
}
|
||||
return $any && $all;
|
||||
}
|
||||
@ -699,22 +709,22 @@ my sub pbs_api_connect {
|
||||
my $user = $scfg->{username} // 'root@pam';
|
||||
|
||||
if (my $tokenid = PVE::AccessControl::pve_verify_tokenid($user, 1)) {
|
||||
$params->{apitoken} = "PBSAPIToken=${tokenid}:${password}";
|
||||
$params->{apitoken} = "PBSAPIToken=${tokenid}:${password}";
|
||||
} else {
|
||||
$params->{password} = $password;
|
||||
$params->{username} = $user;
|
||||
$params->{password} = $password;
|
||||
$params->{username} = $user;
|
||||
}
|
||||
|
||||
if (my $fp = $scfg->{fingerprint}) {
|
||||
$params->{cached_fingerprints}->{uc($fp)} = 1;
|
||||
$params->{cached_fingerprints}->{ uc($fp) } = 1;
|
||||
}
|
||||
|
||||
my $conn = PVE::APIClient::LWP->new(
|
||||
%$params,
|
||||
host => $scfg->{server},
|
||||
port => $scfg->{port} // 8007,
|
||||
timeout => ($timeout // 7), # cope with a 401 (3s api delay) and high latency
|
||||
cookie_name => 'PBSAuthCookie',
|
||||
%$params,
|
||||
host => $scfg->{server},
|
||||
port => $scfg->{port} // 8007,
|
||||
timeout => ($timeout // 7), # cope with a 401 (3s api delay) and high latency
|
||||
cookie_name => 'PBSAuthCookie',
|
||||
);
|
||||
|
||||
return $conn;
|
||||
@ -738,37 +748,37 @@ sub list_volumes {
|
||||
die "error listing snapshots - $@" if $@;
|
||||
|
||||
foreach my $item (@$data) {
|
||||
my $btype = $item->{"backup-type"};
|
||||
my $bid = $item->{"backup-id"};
|
||||
my $epoch = $item->{"backup-time"};
|
||||
my $size = $item->{size} // 1;
|
||||
my $btype = $item->{"backup-type"};
|
||||
my $bid = $item->{"backup-id"};
|
||||
my $epoch = $item->{"backup-time"};
|
||||
my $size = $item->{size} // 1;
|
||||
|
||||
next if !($btype eq 'vm' || $btype eq 'ct');
|
||||
next if $bid !~ m/^\d+$/;
|
||||
next if defined($vmid) && $bid ne $vmid;
|
||||
next if !($btype eq 'vm' || $btype eq 'ct');
|
||||
next if $bid !~ m/^\d+$/;
|
||||
next if defined($vmid) && $bid ne $vmid;
|
||||
|
||||
my $volid = print_volid($storeid, $btype, $bid, $epoch);
|
||||
my $volid = print_volid($storeid, $btype, $bid, $epoch);
|
||||
|
||||
my $info = {
|
||||
volid => $volid,
|
||||
format => "pbs-$btype",
|
||||
size => $size,
|
||||
content => 'backup',
|
||||
vmid => int($bid),
|
||||
ctime => $epoch,
|
||||
subtype => $btype eq 'vm' ? 'qemu' : 'lxc', # convert to PVE backup type
|
||||
};
|
||||
my $info = {
|
||||
volid => $volid,
|
||||
format => "pbs-$btype",
|
||||
size => $size,
|
||||
content => 'backup',
|
||||
vmid => int($bid),
|
||||
ctime => $epoch,
|
||||
subtype => $btype eq 'vm' ? 'qemu' : 'lxc', # convert to PVE backup type
|
||||
};
|
||||
|
||||
$info->{verification} = $item->{verification} if defined($item->{verification});
|
||||
$info->{notes} = $item->{comment} if defined($item->{comment});
|
||||
$info->{protected} = 1 if $item->{protected};
|
||||
if (defined($item->{fingerprint})) {
|
||||
$info->{encrypted} = $item->{fingerprint};
|
||||
} elsif (snapshot_files_encrypted($item->{files})) {
|
||||
$info->{encrypted} = '1';
|
||||
}
|
||||
$info->{verification} = $item->{verification} if defined($item->{verification});
|
||||
$info->{notes} = $item->{comment} if defined($item->{comment});
|
||||
$info->{protected} = 1 if $item->{protected};
|
||||
if (defined($item->{fingerprint})) {
|
||||
$info->{encrypted} = $item->{fingerprint};
|
||||
} elsif (snapshot_files_encrypted($item->{files})) {
|
||||
$info->{encrypted} = '1';
|
||||
}
|
||||
|
||||
push @$res, $info;
|
||||
push @$res, $info;
|
||||
}
|
||||
|
||||
return $res;
|
||||
@ -783,15 +793,15 @@ sub status {
|
||||
my $active = 0;
|
||||
|
||||
eval {
|
||||
my $res = run_client_cmd($scfg, $storeid, "status");
|
||||
my $res = run_client_cmd($scfg, $storeid, "status");
|
||||
|
||||
$active = 1;
|
||||
$total = $res->{total};
|
||||
$used = $res->{used};
|
||||
$free = $res->{avail};
|
||||
$active = 1;
|
||||
$total = $res->{total};
|
||||
$used = $res->{used};
|
||||
$free = $res->{avail};
|
||||
};
|
||||
if (my $err = $@) {
|
||||
warn $err;
|
||||
warn $err;
|
||||
}
|
||||
|
||||
return ($total, $free, $used, $active);
|
||||
@ -826,9 +836,9 @@ sub activate_storage {
|
||||
my $datastore = $scfg->{datastore};
|
||||
|
||||
for my $ds (@$datastores) {
|
||||
if ($ds->{store} eq $datastore) {
|
||||
return 1;
|
||||
}
|
||||
if ($ds->{store} eq $datastore) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
die "$storeid: Cannot find datastore '$datastore', check permissions and existence!\n";
|
||||
@ -860,9 +870,9 @@ sub deactivate_volume {
|
||||
sub get_volume_notes {
|
||||
my ($class, $scfg, $storeid, $volname, $timeout) = @_;
|
||||
|
||||
my (undef, $name, undef, undef, undef, undef, $format) = $class->parse_volname($volname);
|
||||
my (undef, $name, undef, undef, undef, undef, $format) = $class->parse_volname($volname);
|
||||
|
||||
my $data = run_client_cmd($scfg, $storeid, "snapshot", [ "notes", "show", $name ]);
|
||||
my $data = run_client_cmd($scfg, $storeid, "snapshot", ["notes", "show", $name]);
|
||||
|
||||
return $data->{notes};
|
||||
}
|
||||
@ -872,9 +882,9 @@ sub get_volume_notes {
|
||||
sub update_volume_notes {
|
||||
my ($class, $scfg, $storeid, $volname, $notes, $timeout) = @_;
|
||||
|
||||
my (undef, $name, undef, undef, undef, undef, $format) = $class->parse_volname($volname);
|
||||
my (undef, $name, undef, undef, undef, undef, $format) = $class->parse_volname($volname);
|
||||
|
||||
run_client_cmd($scfg, $storeid, "snapshot", [ "notes", "update", $name, $notes ], 1);
|
||||
run_client_cmd($scfg, $storeid, "snapshot", ["notes", "update", $name, $notes], 1);
|
||||
|
||||
return undef;
|
||||
}
|
||||
@ -883,22 +893,22 @@ sub get_volume_attribute {
|
||||
my ($class, $scfg, $storeid, $volname, $attribute) = @_;
|
||||
|
||||
if ($attribute eq 'notes') {
|
||||
return $class->get_volume_notes($scfg, $storeid, $volname);
|
||||
return $class->get_volume_notes($scfg, $storeid, $volname);
|
||||
}
|
||||
|
||||
if ($attribute eq 'protected') {
|
||||
my $param = api_param_from_volname($class, $scfg, $volname);
|
||||
my $param = api_param_from_volname($class, $scfg, $volname);
|
||||
|
||||
my $password = pbs_get_password($scfg, $storeid);
|
||||
my $conn = pbs_api_connect($scfg, $password);
|
||||
my $datastore = $scfg->{datastore};
|
||||
my $password = pbs_get_password($scfg, $storeid);
|
||||
my $conn = pbs_api_connect($scfg, $password);
|
||||
my $datastore = $scfg->{datastore};
|
||||
|
||||
my $res = eval { $conn->get("/api2/json/admin/datastore/$datastore/$attribute", $param); };
|
||||
if (my $err = $@) {
|
||||
return if $err->{code} == 404; # not supported
|
||||
die $err;
|
||||
}
|
||||
return $res;
|
||||
my $res = eval { $conn->get("/api2/json/admin/datastore/$datastore/$attribute", $param); };
|
||||
if (my $err = $@) {
|
||||
return if $err->{code} == 404; # not supported
|
||||
die $err;
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
return;
|
||||
@ -908,24 +918,24 @@ sub update_volume_attribute {
|
||||
my ($class, $scfg, $storeid, $volname, $attribute, $value) = @_;
|
||||
|
||||
if ($attribute eq 'notes') {
|
||||
return $class->update_volume_notes($scfg, $storeid, $volname, $value);
|
||||
return $class->update_volume_notes($scfg, $storeid, $volname, $value);
|
||||
}
|
||||
|
||||
if ($attribute eq 'protected') {
|
||||
my $param = api_param_from_volname($class, $scfg, $volname);
|
||||
$param->{$attribute} = $value;
|
||||
my $param = api_param_from_volname($class, $scfg, $volname);
|
||||
$param->{$attribute} = $value;
|
||||
|
||||
my $password = pbs_get_password($scfg, $storeid);
|
||||
my $conn = pbs_api_connect($scfg, $password);
|
||||
my $datastore = $scfg->{datastore};
|
||||
my $password = pbs_get_password($scfg, $storeid);
|
||||
my $conn = pbs_api_connect($scfg, $password);
|
||||
my $datastore = $scfg->{datastore};
|
||||
|
||||
eval { $conn->put("/api2/json/admin/datastore/$datastore/$attribute", $param); };
|
||||
if (my $err = $@) {
|
||||
die "Server is not recent enough to support feature '$attribute'\n"
|
||||
if $err->{code} == 404;
|
||||
die $err;
|
||||
}
|
||||
return;
|
||||
eval { $conn->put("/api2/json/admin/datastore/$datastore/$attribute", $param); };
|
||||
if (my $err = $@) {
|
||||
die "Server is not recent enough to support feature '$attribute'\n"
|
||||
if $err->{code} == 404;
|
||||
die $err;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
die "attribute '$attribute' is not supported for storage type '$scfg->{type}'\n";
|
||||
@ -934,15 +944,15 @@ sub update_volume_attribute {
|
||||
sub volume_size_info {
|
||||
my ($class, $scfg, $storeid, $volname, $timeout) = @_;
|
||||
|
||||
my ($vtype, $name, undef, undef, undef, undef, $format) = $class->parse_volname($volname);
|
||||
my ($vtype, $name, undef, undef, undef, undef, $format) = $class->parse_volname($volname);
|
||||
|
||||
my $data = run_client_cmd($scfg, $storeid, "files", [ $name ]);
|
||||
my $data = run_client_cmd($scfg, $storeid, "files", [$name]);
|
||||
|
||||
my $size = 0;
|
||||
foreach my $info (@$data) {
|
||||
if ($info->{size} && $info->{size} =~ /^(\d+)$/) { # untaints
|
||||
$size += $1;
|
||||
}
|
||||
if ($info->{size} && $info->{size} =~ /^(\d+)$/) { # untaints
|
||||
$size += $1;
|
||||
}
|
||||
}
|
||||
|
||||
my $used = $size;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -10,7 +10,7 @@ use Net::IP;
|
||||
use POSIX qw(ceil);
|
||||
|
||||
use PVE::CephConfig;
|
||||
use PVE::Cluster qw(cfs_read_file);;
|
||||
use PVE::Cluster qw(cfs_read_file);
|
||||
use PVE::JSONSchema qw(get_standard_option);
|
||||
use PVE::ProcFSTools;
|
||||
use PVE::RADOS;
|
||||
@ -32,7 +32,7 @@ my $librados_connect = sub {
|
||||
my ($scfg, $storeid, $options) = @_;
|
||||
|
||||
$options->{timeout} = 60
|
||||
if !defined($options->{timeout}) && PVE::RPCEnvironment->is_worker();
|
||||
if !defined($options->{timeout}) && PVE::RPCEnvironment->is_worker();
|
||||
|
||||
my $librados_config = PVE::CephConfig::ceph_connect_option($scfg, $storeid, $options->%*);
|
||||
|
||||
@ -47,27 +47,27 @@ my sub get_rbd_path {
|
||||
$path .= "/$scfg->{namespace}" if defined($scfg->{namespace});
|
||||
$path .= "/$volume" if defined($volume);
|
||||
return $path;
|
||||
};
|
||||
}
|
||||
|
||||
my sub get_rbd_dev_path {
|
||||
my ($scfg, $storeid, $volume) = @_;
|
||||
|
||||
my $cluster_id = '';
|
||||
if ($scfg->{fsid}) {
|
||||
# NOTE: the config doesn't support this currently (but it could!), hack for qemu-server tests
|
||||
$cluster_id = $scfg->{fsid};
|
||||
# NOTE: the config doesn't support this currently (but it could!), hack for qemu-server tests
|
||||
$cluster_id = $scfg->{fsid};
|
||||
} elsif ($scfg->{monhost}) {
|
||||
my $rados = $librados_connect->($scfg, $storeid);
|
||||
$cluster_id = $rados->mon_command({ prefix => 'fsid', format => 'json' })->{fsid};
|
||||
my $rados = $librados_connect->($scfg, $storeid);
|
||||
$cluster_id = $rados->mon_command({ prefix => 'fsid', format => 'json' })->{fsid};
|
||||
} else {
|
||||
$cluster_id = cfs_read_file('ceph.conf')->{global}->{fsid};
|
||||
$cluster_id = cfs_read_file('ceph.conf')->{global}->{fsid};
|
||||
}
|
||||
|
||||
my $uuid_pattern = "([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})";
|
||||
if ($cluster_id =~ qr/^${uuid_pattern}$/is) {
|
||||
$cluster_id = $1; # use untained value
|
||||
$cluster_id = $1; # use untained value
|
||||
} else {
|
||||
die "cluster fsid has invalid format\n";
|
||||
die "cluster fsid has invalid format\n";
|
||||
}
|
||||
|
||||
my $rbd_path = get_rbd_path($scfg, $volume);
|
||||
@ -75,11 +75,11 @@ my sub get_rbd_dev_path {
|
||||
my $path = "/dev/rbd/${rbd_path}";
|
||||
|
||||
if (!-e $pve_path && -e $path) {
|
||||
# possibly mapped before rbd-pve rule existed
|
||||
my $real_dev = abs_path($path);
|
||||
my ($rbd_id) = ($real_dev =~ m|/dev/rbd([0-9]+)$|);
|
||||
my $dev_cluster_id = file_read_firstline("/sys/devices/rbd/${rbd_id}/cluster_fsid");
|
||||
return $path if $cluster_id eq $dev_cluster_id;
|
||||
# possibly mapped before rbd-pve rule existed
|
||||
my $real_dev = abs_path($path);
|
||||
my ($rbd_id) = ($real_dev =~ m|/dev/rbd([0-9]+)$|);
|
||||
my $dev_cluster_id = file_read_firstline("/sys/devices/rbd/${rbd_id}/cluster_fsid");
|
||||
return $path if $cluster_id eq $dev_cluster_id;
|
||||
}
|
||||
return $pve_path;
|
||||
}
|
||||
@ -88,25 +88,26 @@ my $rbd_cmd = sub {
|
||||
my ($scfg, $storeid, $op, @options) = @_;
|
||||
|
||||
my $cmd_option = PVE::CephConfig::ceph_connect_option($scfg, $storeid);
|
||||
my $pool = $scfg->{pool} ? $scfg->{pool} : 'rbd';
|
||||
my $pool = $scfg->{pool} ? $scfg->{pool} : 'rbd';
|
||||
|
||||
my $cmd = ['/usr/bin/rbd'];
|
||||
if ($op eq 'import') {
|
||||
push $cmd->@*, '--dest-pool', $pool;
|
||||
push $cmd->@*, '--dest-pool', $pool;
|
||||
} else {
|
||||
push $cmd->@*, '-p', $pool;
|
||||
push $cmd->@*, '-p', $pool;
|
||||
}
|
||||
|
||||
if (defined(my $namespace = $scfg->{namespace})) {
|
||||
# some subcommands will fail if the --namespace parameter is present
|
||||
my $no_namespace_parameter = {
|
||||
unmap => 1,
|
||||
};
|
||||
push @$cmd, '--namespace', "$namespace" if !$no_namespace_parameter->{$op};
|
||||
# some subcommands will fail if the --namespace parameter is present
|
||||
my $no_namespace_parameter = {
|
||||
unmap => 1,
|
||||
};
|
||||
push @$cmd, '--namespace', "$namespace" if !$no_namespace_parameter->{$op};
|
||||
}
|
||||
push @$cmd, '-c', $cmd_option->{ceph_conf} if ($cmd_option->{ceph_conf});
|
||||
push @$cmd, '-m', $cmd_option->{mon_host} if ($cmd_option->{mon_host});
|
||||
push @$cmd, '--auth_supported', $cmd_option->{auth_supported} if ($cmd_option->{auth_supported});
|
||||
push @$cmd, '--auth_supported', $cmd_option->{auth_supported}
|
||||
if ($cmd_option->{auth_supported});
|
||||
push @$cmd, '-n', "client.$cmd_option->{userid}" if ($cmd_option->{userid});
|
||||
push @$cmd, '--keyring', $cmd_option->{keyring} if ($cmd_option->{keyring});
|
||||
|
||||
@ -125,42 +126,45 @@ my $krbd_feature_update = sub {
|
||||
my ($kmajor, $kminor) = PVE::ProcFSTools::kernel_version();
|
||||
|
||||
if ($kmajor > 5 || $kmajor == 5 && $kminor >= 3) {
|
||||
# 'deep-flatten' can only be disabled, not enabled after image creation
|
||||
push @enable, 'fast-diff', 'object-map';
|
||||
# 'deep-flatten' can only be disabled, not enabled after image creation
|
||||
push @enable, 'fast-diff', 'object-map';
|
||||
} else {
|
||||
push @disable, 'fast-diff', 'object-map', 'deep-flatten';
|
||||
push @disable, 'fast-diff', 'object-map', 'deep-flatten';
|
||||
}
|
||||
|
||||
if ($kmajor >= 5) {
|
||||
push @enable, 'exclusive-lock';
|
||||
push @enable, 'exclusive-lock';
|
||||
} else {
|
||||
push @disable, 'exclusive-lock';
|
||||
push @disable, 'exclusive-lock';
|
||||
}
|
||||
|
||||
my $active_features_list = (rbd_volume_info($scfg, $storeid, $name))[4];
|
||||
my $active_features = { map { $_ => 1 } @$active_features_list };
|
||||
|
||||
my $to_disable = join(',', grep { $active_features->{$_} } @disable);
|
||||
my $to_enable = join(',', grep { !$active_features->{$_} } @enable );
|
||||
my $to_disable = join(',', grep { $active_features->{$_} } @disable);
|
||||
my $to_enable = join(',', grep { !$active_features->{$_} } @enable);
|
||||
|
||||
if ($to_disable) {
|
||||
print "disable RBD image features this kernel RBD drivers is not compatible with: $to_disable\n";
|
||||
my $cmd = $rbd_cmd->($scfg, $storeid, 'feature', 'disable', $name, $to_disable);
|
||||
run_rbd_command(
|
||||
$cmd,
|
||||
errmsg => "could not disable krbd-incompatible image features '$to_disable' for rbd image: $name",
|
||||
);
|
||||
print
|
||||
"disable RBD image features this kernel RBD drivers is not compatible with: $to_disable\n";
|
||||
my $cmd = $rbd_cmd->($scfg, $storeid, 'feature', 'disable', $name, $to_disable);
|
||||
run_rbd_command(
|
||||
$cmd,
|
||||
errmsg =>
|
||||
"could not disable krbd-incompatible image features '$to_disable' for rbd image: $name",
|
||||
);
|
||||
}
|
||||
if ($to_enable) {
|
||||
print "enable RBD image features this kernel RBD drivers supports: $to_enable\n";
|
||||
eval {
|
||||
my $cmd = $rbd_cmd->($scfg, $storeid, 'feature', 'enable', $name, $to_enable);
|
||||
run_rbd_command(
|
||||
$cmd,
|
||||
errmsg => "could not enable krbd-compatible image features '$to_enable' for rbd image: $name",
|
||||
);
|
||||
};
|
||||
warn "$@" if $@;
|
||||
print "enable RBD image features this kernel RBD drivers supports: $to_enable\n";
|
||||
eval {
|
||||
my $cmd = $rbd_cmd->($scfg, $storeid, 'feature', 'enable', $name, $to_enable);
|
||||
run_rbd_command(
|
||||
$cmd,
|
||||
errmsg =>
|
||||
"could not enable krbd-compatible image features '$to_enable' for rbd image: $name",
|
||||
);
|
||||
};
|
||||
warn "$@" if $@;
|
||||
}
|
||||
};
|
||||
|
||||
@ -170,24 +174,26 @@ sub run_rbd_command {
|
||||
my $lasterr;
|
||||
my $errmsg = $args{errmsg} . ": " || "";
|
||||
if (!exists($args{errfunc})) {
|
||||
# ' error: 2014-02-06 11:51:59.839135 7f09f94d0760 -1 librbd: snap_unprotect: can't unprotect;
|
||||
# at least 1 child(ren) in pool cephstor1
|
||||
$args{errfunc} = sub {
|
||||
my $line = shift;
|
||||
if ($line =~ m/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+ [0-9a-f]+ [\-\d]+ librbd: (.*)$/) {
|
||||
$lasterr = "$1\n";
|
||||
} else {
|
||||
$lasterr = $line;
|
||||
}
|
||||
print STDERR $lasterr;
|
||||
*STDERR->flush();
|
||||
};
|
||||
# ' error: 2014-02-06 11:51:59.839135 7f09f94d0760 -1 librbd: snap_unprotect: can't unprotect;
|
||||
# at least 1 child(ren) in pool cephstor1
|
||||
$args{errfunc} = sub {
|
||||
my $line = shift;
|
||||
if ($line =~
|
||||
m/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+ [0-9a-f]+ [\-\d]+ librbd: (.*)$/
|
||||
) {
|
||||
$lasterr = "$1\n";
|
||||
} else {
|
||||
$lasterr = $line;
|
||||
}
|
||||
print STDERR $lasterr;
|
||||
*STDERR->flush();
|
||||
};
|
||||
}
|
||||
|
||||
eval { run_command($cmd, %args); };
|
||||
if (my $err = $@) {
|
||||
die $errmsg . $lasterr if length($lasterr);
|
||||
die $err;
|
||||
die $errmsg . $lasterr if length($lasterr);
|
||||
die $err;
|
||||
}
|
||||
|
||||
return undef;
|
||||
@ -200,33 +206,33 @@ sub rbd_ls {
|
||||
my $parser = sub { $raw .= shift };
|
||||
|
||||
my $cmd = $rbd_cmd->($scfg, $storeid, 'ls', '-l', '--format', 'json');
|
||||
run_rbd_command($cmd, errmsg => "rbd error", errfunc => sub {}, outfunc => $parser);
|
||||
run_rbd_command($cmd, errmsg => "rbd error", errfunc => sub { }, outfunc => $parser);
|
||||
|
||||
my $result;
|
||||
if ($raw eq '') {
|
||||
$result = [];
|
||||
$result = [];
|
||||
} elsif ($raw =~ m/^(\[.*\])$/s) { # untaint
|
||||
$result = JSON::decode_json($1);
|
||||
$result = JSON::decode_json($1);
|
||||
} else {
|
||||
die "got unexpected data from rbd ls: '$raw'\n";
|
||||
die "got unexpected data from rbd ls: '$raw'\n";
|
||||
}
|
||||
|
||||
my $list = {};
|
||||
|
||||
foreach my $el (@$result) {
|
||||
next if defined($el->{snapshot});
|
||||
next if defined($el->{snapshot});
|
||||
|
||||
my $image = $el->{image};
|
||||
my $image = $el->{image};
|
||||
|
||||
my ($owner) = $image =~ m/^(?:vm|base)-(\d+)-/;
|
||||
next if !defined($owner);
|
||||
my ($owner) = $image =~ m/^(?:vm|base)-(\d+)-/;
|
||||
next if !defined($owner);
|
||||
|
||||
$list->{$image} = {
|
||||
name => $image,
|
||||
size => $el->{size},
|
||||
parent => $get_parent_image_name->($el->{parent}),
|
||||
vmid => $owner
|
||||
};
|
||||
$list->{$image} = {
|
||||
name => $image,
|
||||
size => $el->{size},
|
||||
parent => $get_parent_image_name->($el->{parent}),
|
||||
vmid => $owner,
|
||||
};
|
||||
}
|
||||
|
||||
return $list;
|
||||
@ -238,28 +244,33 @@ sub rbd_ls_snap {
|
||||
my $cmd = $rbd_cmd->($scfg, $storeid, 'snap', 'ls', $name, '--format', 'json');
|
||||
|
||||
my $raw = '';
|
||||
run_rbd_command($cmd, errmsg => "rbd error", errfunc => sub {}, outfunc => sub { $raw .= shift; });
|
||||
run_rbd_command(
|
||||
$cmd,
|
||||
errmsg => "rbd error",
|
||||
errfunc => sub { },
|
||||
outfunc => sub { $raw .= shift; },
|
||||
);
|
||||
|
||||
my $list;
|
||||
if ($raw =~ m/^(\[.*\])$/s) { # untaint
|
||||
$list = eval { JSON::decode_json($1) };
|
||||
die "invalid JSON output from 'rbd snap ls $name': $@\n" if $@;
|
||||
$list = eval { JSON::decode_json($1) };
|
||||
die "invalid JSON output from 'rbd snap ls $name': $@\n" if $@;
|
||||
} else {
|
||||
die "got unexpected data from 'rbd snap ls $name': '$raw'\n";
|
||||
die "got unexpected data from 'rbd snap ls $name': '$raw'\n";
|
||||
}
|
||||
|
||||
$list = [] if !defined($list);
|
||||
|
||||
my $res = {};
|
||||
foreach my $el (@$list) {
|
||||
my $snap = $el->{name};
|
||||
my $protected = defined($el->{protected}) && $el->{protected} eq "true" ? 1 : undef;
|
||||
$res->{$snap} = {
|
||||
name => $snap,
|
||||
id => $el->{id} // undef,
|
||||
size => $el->{size} // 0,
|
||||
protected => $protected,
|
||||
};
|
||||
my $snap = $el->{name};
|
||||
my $protected = defined($el->{protected}) && $el->{protected} eq "true" ? 1 : undef;
|
||||
$res->{$snap} = {
|
||||
name => $snap,
|
||||
id => $el->{id} // undef,
|
||||
size => $el->{size} // 0,
|
||||
protected => $protected,
|
||||
};
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
@ -271,7 +282,7 @@ sub rbd_volume_info {
|
||||
|
||||
my @options = ('info', $volname, '--format', 'json');
|
||||
if ($snap) {
|
||||
push @options, '--snap', $snap;
|
||||
push @options, '--snap', $snap;
|
||||
}
|
||||
|
||||
$cmd = $rbd_cmd->($scfg, $storeid, @options);
|
||||
@ -279,19 +290,20 @@ sub rbd_volume_info {
|
||||
my $raw = '';
|
||||
my $parser = sub { $raw .= shift };
|
||||
|
||||
run_rbd_command($cmd, errmsg => "rbd error", errfunc => sub {}, outfunc => $parser);
|
||||
run_rbd_command($cmd, errmsg => "rbd error", errfunc => sub { }, outfunc => $parser);
|
||||
|
||||
my $volume;
|
||||
if ($raw eq '') {
|
||||
$volume = {};
|
||||
$volume = {};
|
||||
} elsif ($raw =~ m/^(\{.*\})$/s) { # untaint
|
||||
$volume = JSON::decode_json($1);
|
||||
$volume = JSON::decode_json($1);
|
||||
} else {
|
||||
die "got unexpected data from rbd info: '$raw'\n";
|
||||
die "got unexpected data from rbd info: '$raw'\n";
|
||||
}
|
||||
|
||||
$volume->{parent} = $get_parent_image_name->($volume->{parent});
|
||||
$volume->{protected} = defined($volume->{protected}) && $volume->{protected} eq "true" ? 1 : undef;
|
||||
$volume->{protected} =
|
||||
defined($volume->{protected}) && $volume->{protected} eq "true" ? 1 : undef;
|
||||
|
||||
return $volume->@{qw(size parent format protected features)};
|
||||
}
|
||||
@ -305,31 +317,31 @@ sub rbd_volume_du {
|
||||
my $raw = '';
|
||||
my $parser = sub { $raw .= shift };
|
||||
|
||||
run_rbd_command($cmd, errmsg => "rbd error", errfunc => sub {}, outfunc => $parser);
|
||||
run_rbd_command($cmd, errmsg => "rbd error", errfunc => sub { }, outfunc => $parser);
|
||||
|
||||
my $volume;
|
||||
if ($raw eq '') {
|
||||
$volume = {};
|
||||
$volume = {};
|
||||
} elsif ($raw =~ m/^(\{.*\})$/s) { # untaint
|
||||
$volume = JSON::decode_json($1);
|
||||
$volume = JSON::decode_json($1);
|
||||
} else {
|
||||
die "got unexpected data from rbd du: '$raw'\n";
|
||||
die "got unexpected data from rbd du: '$raw'\n";
|
||||
}
|
||||
|
||||
if (!defined($volume->{images})) {
|
||||
die "got no images from rbd du\n";
|
||||
die "got no images from rbd du\n";
|
||||
}
|
||||
|
||||
# `rbd du` returns array of images for name matching `volname`,
|
||||
# including snapshots.
|
||||
my $images = $volume->{images};
|
||||
foreach my $image (@$images) {
|
||||
next if defined($image->{snapshot});
|
||||
next if !defined($image->{used_size}) || !defined($image->{name});
|
||||
next if defined($image->{snapshot});
|
||||
next if !defined($image->{used_size}) || !defined($image->{name});
|
||||
|
||||
# Return `used_size` of first volume with matching name which
|
||||
# is not a snapshot.
|
||||
return $image->{used_size} if $image->{name} eq $volname;
|
||||
# Return `used_size` of first volume with matching name which
|
||||
# is not a snapshot.
|
||||
return $image->{used_size} if $image->{name} eq $volname;
|
||||
}
|
||||
|
||||
die "got no matching image from rbd du\n";
|
||||
@ -341,18 +353,22 @@ my sub rbd_volume_exists {
|
||||
my $cmd = $rbd_cmd->($scfg, $storeid, 'ls', '--format', 'json');
|
||||
my $raw = '';
|
||||
run_rbd_command(
|
||||
$cmd, errmsg => "rbd error", errfunc => sub {}, outfunc => sub { $raw .= shift; });
|
||||
$cmd,
|
||||
errmsg => "rbd error",
|
||||
errfunc => sub { },
|
||||
outfunc => sub { $raw .= shift; },
|
||||
);
|
||||
|
||||
my $list;
|
||||
if ($raw =~ m/^(\[.*\])$/s) { # untaint
|
||||
$list = eval { JSON::decode_json($1); };
|
||||
die "invalid JSON output from 'rbd ls': $@\n" if $@;
|
||||
$list = eval { JSON::decode_json($1); };
|
||||
die "invalid JSON output from 'rbd ls': $@\n" if $@;
|
||||
} else {
|
||||
die "got unexpected data from 'rbd ls': '$raw'\n";
|
||||
die "got unexpected data from 'rbd ls': '$raw'\n";
|
||||
}
|
||||
|
||||
for my $name ($list->@*) {
|
||||
return 1 if $name eq $volname;
|
||||
return 1 if $name eq $volname;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -366,62 +382,63 @@ sub type {
|
||||
|
||||
sub plugindata {
|
||||
return {
|
||||
content => [ {images => 1, rootdir => 1}, { images => 1 }],
|
||||
'sensitive-properties' => { keyring => 1 },
|
||||
content => [{ images => 1, rootdir => 1 }, { images => 1 }],
|
||||
'sensitive-properties' => { keyring => 1 },
|
||||
};
|
||||
}
|
||||
|
||||
sub properties {
|
||||
return {
|
||||
monhost => {
|
||||
description => "IP addresses of monitors (for external clusters).",
|
||||
type => 'string', format => 'pve-storage-portal-dns-list',
|
||||
},
|
||||
pool => {
|
||||
description => "Pool.",
|
||||
type => 'string',
|
||||
},
|
||||
'data-pool' => {
|
||||
description => "Data Pool (for erasure coding only)",
|
||||
type => 'string',
|
||||
},
|
||||
namespace => {
|
||||
description => "Namespace.",
|
||||
type => 'string',
|
||||
},
|
||||
username => {
|
||||
description => "RBD Id.",
|
||||
type => 'string',
|
||||
},
|
||||
authsupported => {
|
||||
description => "Authsupported.",
|
||||
type => 'string',
|
||||
},
|
||||
krbd => {
|
||||
description => "Always access rbd through krbd kernel module.",
|
||||
type => 'boolean',
|
||||
default => 0,
|
||||
},
|
||||
keyring => {
|
||||
description => "Client keyring contents (for external clusters).",
|
||||
type => 'string',
|
||||
},
|
||||
monhost => {
|
||||
description => "IP addresses of monitors (for external clusters).",
|
||||
type => 'string',
|
||||
format => 'pve-storage-portal-dns-list',
|
||||
},
|
||||
pool => {
|
||||
description => "Pool.",
|
||||
type => 'string',
|
||||
},
|
||||
'data-pool' => {
|
||||
description => "Data Pool (for erasure coding only)",
|
||||
type => 'string',
|
||||
},
|
||||
namespace => {
|
||||
description => "Namespace.",
|
||||
type => 'string',
|
||||
},
|
||||
username => {
|
||||
description => "RBD Id.",
|
||||
type => 'string',
|
||||
},
|
||||
authsupported => {
|
||||
description => "Authsupported.",
|
||||
type => 'string',
|
||||
},
|
||||
krbd => {
|
||||
description => "Always access rbd through krbd kernel module.",
|
||||
type => 'boolean',
|
||||
default => 0,
|
||||
},
|
||||
keyring => {
|
||||
description => "Client keyring contents (for external clusters).",
|
||||
type => 'string',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
sub options {
|
||||
return {
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
monhost => { optional => 1},
|
||||
pool => { optional => 1 },
|
||||
'data-pool' => { optional => 1 },
|
||||
namespace => { optional => 1 },
|
||||
username => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
krbd => { optional => 1 },
|
||||
keyring => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
monhost => { optional => 1 },
|
||||
pool => { optional => 1 },
|
||||
'data-pool' => { optional => 1 },
|
||||
namespace => { optional => 1 },
|
||||
username => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
krbd => { optional => 1 },
|
||||
keyring => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
};
|
||||
}
|
||||
|
||||
@ -439,11 +456,11 @@ sub on_update_hook {
|
||||
my ($class, $storeid, $scfg, %param) = @_;
|
||||
|
||||
if (exists($param{keyring})) {
|
||||
if (defined($param{keyring})) {
|
||||
PVE::CephConfig::ceph_create_keyfile($scfg->{type}, $storeid, $param{keyring});
|
||||
} else {
|
||||
PVE::CephConfig::ceph_remove_keyfile($scfg->{type}, $storeid);
|
||||
}
|
||||
if (defined($param{keyring})) {
|
||||
PVE::CephConfig::ceph_create_keyfile($scfg->{type}, $storeid, $param{keyring});
|
||||
} else {
|
||||
PVE::CephConfig::ceph_remove_keyfile($scfg->{type}, $storeid);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
@ -459,7 +476,7 @@ sub parse_volname {
|
||||
my ($class, $volname) = @_;
|
||||
|
||||
if ($volname =~ m/^((base-(\d+)-\S+)\/)?((base)?(vm)?-(\d+)-\S+)$/) {
|
||||
return ('images', $4, $7, $2, $3, $5, 'raw');
|
||||
return ('images', $4, $7, $2, $3, $5, 'raw');
|
||||
}
|
||||
|
||||
die "unable to parse rbd volume name '$volname'\n";
|
||||
@ -470,11 +487,11 @@ sub path {
|
||||
|
||||
my $cmd_option = PVE::CephConfig::ceph_connect_option($scfg, $storeid);
|
||||
my ($vtype, $name, $vmid) = $class->parse_volname($volname);
|
||||
$name .= '@'.$snapname if $snapname;
|
||||
$name .= '@' . $snapname if $snapname;
|
||||
|
||||
if ($scfg->{krbd}) {
|
||||
my $rbd_dev_path = get_rbd_dev_path($scfg, $storeid, $name);
|
||||
return ($rbd_dev_path, $vmid, $vtype);
|
||||
my $rbd_dev_path = get_rbd_dev_path($scfg, $storeid, $name);
|
||||
return ($rbd_dev_path, $vmid, $vtype);
|
||||
}
|
||||
|
||||
my $rbd_path = get_rbd_path($scfg, $name);
|
||||
@ -482,10 +499,10 @@ sub path {
|
||||
|
||||
$path .= ":conf=$cmd_option->{ceph_conf}" if $cmd_option->{ceph_conf};
|
||||
if (defined($scfg->{monhost})) {
|
||||
my $monhost = PVE::CephConfig::hostlist($scfg->{monhost}, ';');
|
||||
$monhost =~ s/:/\\:/g;
|
||||
$path .= ":mon_host=$monhost";
|
||||
$path .= ":auth_supported=$cmd_option->{auth_supported}";
|
||||
my $monhost = PVE::CephConfig::hostlist($scfg->{monhost}, ';');
|
||||
$monhost =~ s/:/\\:/g;
|
||||
$path .= ":mon_host=$monhost";
|
||||
$path .= ":auth_supported=$cmd_option->{auth_supported}";
|
||||
}
|
||||
|
||||
$path .= ":id=$cmd_option->{userid}:keyring=$cmd_option->{keyring}" if ($cmd_option->{keyring});
|
||||
@ -501,14 +518,14 @@ sub find_free_diskname {
|
||||
my $disk_list = [];
|
||||
|
||||
my $parser = sub {
|
||||
my $line = shift;
|
||||
if ($line =~ m/^(.*)$/) { # untaint
|
||||
push @$disk_list, $1;
|
||||
}
|
||||
my $line = shift;
|
||||
if ($line =~ m/^(.*)$/) { # untaint
|
||||
push @$disk_list, $1;
|
||||
}
|
||||
};
|
||||
|
||||
eval {
|
||||
run_rbd_command($cmd, errmsg => "rbd error", errfunc => sub {}, outfunc => $parser);
|
||||
run_rbd_command($cmd, errmsg => "rbd error", errfunc => sub { }, outfunc => $parser);
|
||||
};
|
||||
my $err = $@;
|
||||
|
||||
@ -522,8 +539,7 @@ sub create_base {
|
||||
|
||||
my $snap = '__base__';
|
||||
|
||||
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
|
||||
$class->parse_volname($volname);
|
||||
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = $class->parse_volname($volname);
|
||||
|
||||
die "create_base not possible with base image\n" if $isBase;
|
||||
|
||||
@ -533,7 +549,7 @@ sub create_base {
|
||||
die "rbd image must be at format V2" if $format ne "2";
|
||||
|
||||
die "volname '$volname' contains wrong information about parent $parent $basename\n"
|
||||
if $basename && (!$parent || $parent ne $basename."@".$snap);
|
||||
if $basename && (!$parent || $parent ne $basename . "@" . $snap);
|
||||
|
||||
my $newname = $name;
|
||||
$newname =~ s/^vm-/base-/;
|
||||
@ -541,26 +557,24 @@ sub create_base {
|
||||
my $newvolname = $basename ? "$basename/$newname" : "$newname";
|
||||
|
||||
my $cmd = $rbd_cmd->(
|
||||
$scfg,
|
||||
$storeid,
|
||||
'rename',
|
||||
get_rbd_path($scfg, $name),
|
||||
get_rbd_path($scfg, $newname),
|
||||
$scfg, $storeid, 'rename',
|
||||
get_rbd_path($scfg, $name),
|
||||
get_rbd_path($scfg, $newname),
|
||||
);
|
||||
run_rbd_command($cmd, errmsg => "rbd rename '$name' error");
|
||||
|
||||
eval { $class->unmap_volume($storeid, $scfg, $volname); };
|
||||
warn $@ if $@;
|
||||
|
||||
my $running = undef; #fixme : is create_base always offline ?
|
||||
my $running = undef; #fixme : is create_base always offline ?
|
||||
|
||||
$class->volume_snapshot($scfg, $storeid, $newname, $snap, $running);
|
||||
|
||||
my (undef, undef, undef, $protected) = rbd_volume_info($scfg, $storeid, $newname, $snap);
|
||||
|
||||
if (!$protected){
|
||||
my $cmd = $rbd_cmd->($scfg, $storeid, 'snap', 'protect', $newname, '--snap', $snap);
|
||||
run_rbd_command($cmd, errmsg => "rbd protect $newname snap '$snap' error");
|
||||
if (!$protected) {
|
||||
my $cmd = $rbd_cmd->($scfg, $storeid, 'snap', 'protect', $newname, '--snap', $snap);
|
||||
run_rbd_command($cmd, errmsg => "rbd protect $newname snap '$snap' error");
|
||||
}
|
||||
|
||||
return $newvolname;
|
||||
@ -573,31 +587,30 @@ sub clone_image {
|
||||
my $snap = '__base__';
|
||||
$snap = $snapname if length $snapname;
|
||||
|
||||
my ($vtype, $basename, $basevmid, undef, undef, $isBase) =
|
||||
$class->parse_volname($volname);
|
||||
my ($vtype, $basename, $basevmid, undef, undef, $isBase) = $class->parse_volname($volname);
|
||||
|
||||
die "$volname is not a base image and snapname is not provided\n"
|
||||
if !$isBase && !length($snapname);
|
||||
die "$volname is not a base image and snapname is not provided\n"
|
||||
if !$isBase && !length($snapname);
|
||||
|
||||
my $name = $class->find_free_diskname($storeid, $scfg, $vmid);
|
||||
|
||||
warn "clone $volname: $basename snapname $snap to $name\n";
|
||||
|
||||
if (length($snapname)) {
|
||||
my (undef, undef, undef, $protected) = rbd_volume_info($scfg, $storeid, $volname, $snapname);
|
||||
my (undef, undef, undef, $protected) =
|
||||
rbd_volume_info($scfg, $storeid, $volname, $snapname);
|
||||
|
||||
if (!$protected) {
|
||||
my $cmd = $rbd_cmd->($scfg, $storeid, 'snap', 'protect', $volname, '--snap', $snapname);
|
||||
run_rbd_command($cmd, errmsg => "rbd protect $volname snap $snapname error");
|
||||
}
|
||||
if (!$protected) {
|
||||
my $cmd = $rbd_cmd->($scfg, $storeid, 'snap', 'protect', $volname, '--snap', $snapname);
|
||||
run_rbd_command($cmd, errmsg => "rbd protect $volname snap $snapname error");
|
||||
}
|
||||
}
|
||||
|
||||
my $newvol = "$basename/$name";
|
||||
$newvol = $name if length($snapname);
|
||||
|
||||
my @options = (
|
||||
get_rbd_path($scfg, $basename),
|
||||
'--snap', $snap,
|
||||
get_rbd_path($scfg, $basename), '--snap', $snap,
|
||||
);
|
||||
push @options, ('--data-pool', $scfg->{'data-pool'}) if $scfg->{'data-pool'};
|
||||
|
||||
@ -610,15 +623,13 @@ sub clone_image {
|
||||
sub alloc_image {
|
||||
my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
|
||||
|
||||
|
||||
die "illegal name '$name' - should be 'vm-$vmid-*'\n"
|
||||
if $name && $name !~ m/^vm-$vmid-/;
|
||||
if $name && $name !~ m/^vm-$vmid-/;
|
||||
|
||||
$name = $class->find_free_diskname($storeid, $scfg, $vmid) if !$name;
|
||||
|
||||
my @options = (
|
||||
'--image-format' , 2,
|
||||
'--size', int(($size + 1023) / 1024),
|
||||
'--image-format', 2, '--size', int(($size + 1023) / 1024),
|
||||
);
|
||||
push @options, ('--data-pool', $scfg->{'data-pool'}) if $scfg->{'data-pool'};
|
||||
|
||||
@ -631,21 +642,19 @@ sub alloc_image {
|
||||
sub free_image {
|
||||
my ($class, $storeid, $scfg, $volname, $isBase) = @_;
|
||||
|
||||
my ($vtype, $name, $vmid, undef, undef, undef) =
|
||||
$class->parse_volname($volname);
|
||||
|
||||
my ($vtype, $name, $vmid, undef, undef, undef) = $class->parse_volname($volname);
|
||||
|
||||
my $snaps = rbd_ls_snap($scfg, $storeid, $name);
|
||||
foreach my $snap (keys %$snaps) {
|
||||
if ($snaps->{$snap}->{protected}) {
|
||||
my $cmd = $rbd_cmd->($scfg, $storeid, 'snap', 'unprotect', $name, '--snap', $snap);
|
||||
run_rbd_command($cmd, errmsg => "rbd unprotect $name snap '$snap' error");
|
||||
}
|
||||
if ($snaps->{$snap}->{protected}) {
|
||||
my $cmd = $rbd_cmd->($scfg, $storeid, 'snap', 'unprotect', $name, '--snap', $snap);
|
||||
run_rbd_command($cmd, errmsg => "rbd unprotect $name snap '$snap' error");
|
||||
}
|
||||
}
|
||||
|
||||
$class->deactivate_volume($storeid, $scfg, $volname);
|
||||
|
||||
my $cmd = $rbd_cmd->($scfg, $storeid, 'snap', 'purge', $name);
|
||||
my $cmd = $rbd_cmd->($scfg, $storeid, 'snap', 'purge', $name);
|
||||
run_rbd_command($cmd, errmsg => "rbd snap purge '$name' error");
|
||||
|
||||
$cmd = $rbd_cmd->($scfg, $storeid, 'rm', $name);
|
||||
@ -662,25 +671,25 @@ sub list_images {
|
||||
|
||||
my $res = [];
|
||||
for my $image (sort keys %$dat) {
|
||||
my $info = $dat->{$image};
|
||||
my ($volname, $parent, $owner) = $info->@{'name', 'parent', 'vmid'};
|
||||
my $info = $dat->{$image};
|
||||
my ($volname, $parent, $owner) = $info->@{ 'name', 'parent', 'vmid' };
|
||||
|
||||
if ($parent && $parent =~ m/^(base-\d+-\S+)\@__base__$/) {
|
||||
$info->{volid} = "$storeid:$1/$volname";
|
||||
} else {
|
||||
$info->{volid} = "$storeid:$volname";
|
||||
}
|
||||
if ($parent && $parent =~ m/^(base-\d+-\S+)\@__base__$/) {
|
||||
$info->{volid} = "$storeid:$1/$volname";
|
||||
} else {
|
||||
$info->{volid} = "$storeid:$volname";
|
||||
}
|
||||
|
||||
if ($vollist) {
|
||||
my $found = grep { $_ eq $info->{volid} } @$vollist;
|
||||
next if !$found;
|
||||
} else {
|
||||
next if defined ($vmid) && ($owner ne $vmid);
|
||||
}
|
||||
if ($vollist) {
|
||||
my $found = grep { $_ eq $info->{volid} } @$vollist;
|
||||
next if !$found;
|
||||
} else {
|
||||
next if defined($vmid) && ($owner ne $vmid);
|
||||
}
|
||||
|
||||
$info->{format} = 'raw';
|
||||
$info->{format} = 'raw';
|
||||
|
||||
push @$res, $info;
|
||||
push @$res, $info;
|
||||
}
|
||||
|
||||
return $res;
|
||||
@ -694,11 +703,11 @@ sub status {
|
||||
|
||||
my $pool = $scfg->{'data-pool'} // $scfg->{pool} // 'rbd';
|
||||
|
||||
my ($d) = grep { $_->{name} eq $pool } @{$df->{pools}};
|
||||
my ($d) = grep { $_->{name} eq $pool } @{ $df->{pools} };
|
||||
|
||||
if (!defined($d)) {
|
||||
warn "could not get usage stats for pool '$pool'\n";
|
||||
return;
|
||||
warn "could not get usage stats for pool '$pool'\n";
|
||||
return;
|
||||
}
|
||||
|
||||
# max_avail -> max available space for data w/o replication in the pool
|
||||
@ -727,7 +736,7 @@ sub map_volume {
|
||||
my ($vtype, $img_name, $vmid) = $class->parse_volname($volname);
|
||||
|
||||
my $name = $img_name;
|
||||
$name .= '@'.$snapname if $snapname;
|
||||
$name .= '@' . $snapname if $snapname;
|
||||
|
||||
my $kerneldev = get_rbd_dev_path($scfg, $storeid, $name);
|
||||
|
||||
@ -746,13 +755,13 @@ sub unmap_volume {
|
||||
my ($class, $storeid, $scfg, $volname, $snapname) = @_;
|
||||
|
||||
my ($vtype, $name, $vmid) = $class->parse_volname($volname);
|
||||
$name .= '@'.$snapname if $snapname;
|
||||
$name .= '@' . $snapname if $snapname;
|
||||
|
||||
my $kerneldev = get_rbd_dev_path($scfg, $storeid, $name);
|
||||
|
||||
if (-b $kerneldev) {
|
||||
my $cmd = $rbd_cmd->($scfg, $storeid, 'unmap', $kerneldev);
|
||||
run_rbd_command($cmd, errmsg => "can't unmap rbd device $kerneldev");
|
||||
my $cmd = $rbd_cmd->($scfg, $storeid, 'unmap', $kerneldev);
|
||||
run_rbd_command($cmd, errmsg => "can't unmap rbd device $kerneldev");
|
||||
}
|
||||
|
||||
return 1;
|
||||
@ -790,7 +799,8 @@ sub volume_resize {
|
||||
|
||||
my ($vtype, $name, $vmid) = $class->parse_volname($volname);
|
||||
|
||||
my $cmd = $rbd_cmd->($scfg, $storeid, 'resize', '--size', int(ceil($size/1024/1024)), $name);
|
||||
my $cmd =
|
||||
$rbd_cmd->($scfg, $storeid, 'resize', '--size', int(ceil($size / 1024 / 1024)), $name);
|
||||
run_rbd_command($cmd, errmsg => "rbd resize '$volname' error");
|
||||
return undef;
|
||||
}
|
||||
@ -822,9 +832,9 @@ sub volume_snapshot_delete {
|
||||
my ($vtype, $name, $vmid) = $class->parse_volname($volname);
|
||||
|
||||
my (undef, undef, undef, $protected) = rbd_volume_info($scfg, $storeid, $name, $snap);
|
||||
if ($protected){
|
||||
my $cmd = $rbd_cmd->($scfg, $storeid, 'snap', 'unprotect', $name, '--snap', $snap);
|
||||
run_rbd_command($cmd, errmsg => "rbd unprotect $name snap '$snap' error");
|
||||
if ($protected) {
|
||||
my $cmd = $rbd_cmd->($scfg, $storeid, 'snap', 'unprotect', $name, '--snap', $snap);
|
||||
run_rbd_command($cmd, errmsg => "rbd unprotect $name snap '$snap' error");
|
||||
}
|
||||
|
||||
my $cmd = $rbd_cmd->($scfg, $storeid, 'snap', 'rm', '--snap', $snap, $name);
|
||||
@ -841,22 +851,22 @@ sub volume_snapshot_needs_fsfreeze {
|
||||
sub volume_has_feature {
|
||||
my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
|
||||
|
||||
my $features = {
|
||||
snapshot => { current => 1, snap => 1},
|
||||
clone => { base => 1, snap => 1},
|
||||
template => { current => 1},
|
||||
copy => { base => 1, current => 1, snap => 1},
|
||||
sparseinit => { base => 1, current => 1},
|
||||
rename => {current => 1},
|
||||
my $features = {
|
||||
snapshot => { current => 1, snap => 1 },
|
||||
clone => { base => 1, snap => 1 },
|
||||
template => { current => 1 },
|
||||
copy => { base => 1, current => 1, snap => 1 },
|
||||
sparseinit => { base => 1, current => 1 },
|
||||
rename => { current => 1 },
|
||||
};
|
||||
|
||||
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = $class->parse_volname($volname);
|
||||
|
||||
my $key = undef;
|
||||
if ($snapname){
|
||||
$key = 'snap';
|
||||
if ($snapname) {
|
||||
$key = 'snap';
|
||||
} else {
|
||||
$key = $isBase ? 'base' : 'current';
|
||||
$key = $isBase ? 'base' : 'current';
|
||||
}
|
||||
return 1 if $features->{$feature}->{$key};
|
||||
|
||||
@ -867,20 +877,21 @@ sub volume_export_formats {
|
||||
my ($class, $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots) = @_;
|
||||
|
||||
return $class->volume_import_formats(
|
||||
$scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots);
|
||||
$scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots,
|
||||
);
|
||||
}
|
||||
|
||||
sub volume_export {
|
||||
my (
|
||||
$class,
|
||||
$scfg,
|
||||
$storeid,
|
||||
$fh,
|
||||
$volname,
|
||||
$format,
|
||||
$snapshot,
|
||||
$base_snapshot,
|
||||
$with_snapshots,
|
||||
$class,
|
||||
$scfg,
|
||||
$storeid,
|
||||
$fh,
|
||||
$volname,
|
||||
$format,
|
||||
$snapshot,
|
||||
$base_snapshot,
|
||||
$with_snapshots,
|
||||
) = @_;
|
||||
|
||||
die "volume export format $format not available for $class\n" if $format ne 'raw+size';
|
||||
@ -891,9 +902,9 @@ sub volume_export {
|
||||
PVE::Storage::Plugin::write_common_header($fh, $size);
|
||||
my $cmd = $rbd_cmd->($scfg, $storeid, 'export', '--export-format', '1', $volname, '-');
|
||||
run_rbd_command(
|
||||
$cmd,
|
||||
errmsg => 'could not export image',
|
||||
output => '>&'.fileno($fh),
|
||||
$cmd,
|
||||
errmsg => 'could not export image',
|
||||
output => '>&' . fileno($fh),
|
||||
);
|
||||
|
||||
return;
|
||||
@ -908,16 +919,16 @@ sub volume_import_formats {
|
||||
|
||||
sub volume_import {
|
||||
my (
|
||||
$class,
|
||||
$scfg,
|
||||
$storeid,
|
||||
$fh,
|
||||
$volname,
|
||||
$format,
|
||||
$snapshot,
|
||||
$base_snapshot,
|
||||
$with_snapshots,
|
||||
$allow_rename,
|
||||
$class,
|
||||
$scfg,
|
||||
$storeid,
|
||||
$fh,
|
||||
$volname,
|
||||
$format,
|
||||
$snapshot,
|
||||
$base_snapshot,
|
||||
$with_snapshots,
|
||||
$allow_rename,
|
||||
) = @_;
|
||||
|
||||
die "volume import format $format not available for $class\n" if $format ne 'raw+size';
|
||||
@ -926,32 +937,32 @@ sub volume_import {
|
||||
|
||||
my (undef, $name, $vmid, undef, undef, undef, $file_format) = $class->parse_volname($volname);
|
||||
die "cannot import format $format into a volume of format $file_format\n"
|
||||
if $file_format ne 'raw';
|
||||
if $file_format ne 'raw';
|
||||
|
||||
if (rbd_volume_exists($scfg, $storeid, $name)) {
|
||||
die "volume $name already exists\n" if !$allow_rename;
|
||||
warn "volume $name already exists - importing with a different name\n";
|
||||
$volname = $class->find_free_diskname($storeid, $scfg, $vmid, $file_format);
|
||||
die "volume $name already exists\n" if !$allow_rename;
|
||||
warn "volume $name already exists - importing with a different name\n";
|
||||
$volname = $class->find_free_diskname($storeid, $scfg, $vmid, $file_format);
|
||||
}
|
||||
|
||||
my ($size) = PVE::Storage::Plugin::read_common_header($fh);
|
||||
$size = PVE::Storage::Common::align_size_up($size, 1024) / 1024;
|
||||
|
||||
eval {
|
||||
my $cmd = $rbd_cmd->($scfg, $storeid, 'import', '--export-format', '1', '-', $volname);
|
||||
run_rbd_command(
|
||||
$cmd,
|
||||
errmsg => 'could not import image',
|
||||
input => '<&'.fileno($fh),
|
||||
);
|
||||
my $cmd = $rbd_cmd->($scfg, $storeid, 'import', '--export-format', '1', '-', $volname);
|
||||
run_rbd_command(
|
||||
$cmd,
|
||||
errmsg => 'could not import image',
|
||||
input => '<&' . fileno($fh),
|
||||
);
|
||||
};
|
||||
if (my $err = $@) {
|
||||
# FIXME there is a slight race between finding the free disk name and removal here
|
||||
# Does not only affect this plugin, see:
|
||||
# https://lore.proxmox.com/pve-devel/20240403150712.262773-1-h.duerr@proxmox.com/
|
||||
eval { $class->free_image($storeid, $scfg, $volname, 0, $file_format); };
|
||||
warn $@ if $@;
|
||||
die $err;
|
||||
# FIXME there is a slight race between finding the free disk name and removal here
|
||||
# Does not only affect this plugin, see:
|
||||
# https://lore.proxmox.com/pve-devel/20240403150712.262773-1-h.duerr@proxmox.com/
|
||||
eval { $class->free_image($storeid, $scfg, $volname, 0, $file_format); };
|
||||
warn $@ if $@;
|
||||
die $err;
|
||||
}
|
||||
|
||||
return "$storeid:$volname";
|
||||
@ -961,25 +972,19 @@ sub rename_volume {
|
||||
my ($class, $scfg, $storeid, $source_volname, $target_vmid, $target_volname) = @_;
|
||||
|
||||
my (
|
||||
undef,
|
||||
$source_image,
|
||||
$source_vmid,
|
||||
$base_name,
|
||||
$base_vmid,
|
||||
undef,
|
||||
$format
|
||||
undef, $source_image, $source_vmid, $base_name, $base_vmid, undef, $format,
|
||||
) = $class->parse_volname($source_volname);
|
||||
$target_volname = $class->find_free_diskname($storeid, $scfg, $target_vmid, $format)
|
||||
if !$target_volname;
|
||||
if !$target_volname;
|
||||
|
||||
die "target volume '${target_volname}' already exists\n"
|
||||
if rbd_volume_exists($scfg, $storeid, $target_volname);
|
||||
if rbd_volume_exists($scfg, $storeid, $target_volname);
|
||||
|
||||
my $cmd = $rbd_cmd->($scfg, $storeid, 'rename', $source_image, $target_volname);
|
||||
|
||||
run_rbd_command(
|
||||
$cmd,
|
||||
errmsg => "could not rename image '${source_image}' to '${target_volname}'",
|
||||
$cmd,
|
||||
errmsg => "could not rename image '${source_image}' to '${target_volname}'",
|
||||
);
|
||||
|
||||
eval { $class->unmap_volume($storeid, $scfg, $source_volname); };
|
||||
|
||||
@ -14,19 +14,18 @@ use PVE::Storage::LunCmd::Istgt;
|
||||
use PVE::Storage::LunCmd::Iet;
|
||||
use PVE::Storage::LunCmd::LIO;
|
||||
|
||||
|
||||
my @ssh_opts = ('-o', 'BatchMode=yes');
|
||||
my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts);
|
||||
my $id_rsa_path = '/etc/pve/priv/zfs';
|
||||
|
||||
my $lun_cmds = {
|
||||
create_lu => 1,
|
||||
delete_lu => 1,
|
||||
import_lu => 1,
|
||||
modify_lu => 1,
|
||||
add_view => 1,
|
||||
list_view => 1,
|
||||
list_lu => 1,
|
||||
create_lu => 1,
|
||||
delete_lu => 1,
|
||||
import_lu => 1,
|
||||
modify_lu => 1,
|
||||
add_view => 1,
|
||||
list_view => 1,
|
||||
list_lu => 1,
|
||||
};
|
||||
|
||||
my $zfs_unknown_scsi_provider = sub {
|
||||
@ -54,14 +53,15 @@ my $zfs_get_base = sub {
|
||||
sub zfs_request {
|
||||
my ($class, $scfg, $timeout, $method, @params) = @_;
|
||||
|
||||
$timeout = PVE::RPCEnvironment->is_worker() ? 60*60 : 10
|
||||
if !$timeout;
|
||||
$timeout = PVE::RPCEnvironment->is_worker() ? 60 * 60 : 10
|
||||
if !$timeout;
|
||||
|
||||
my $msg = '';
|
||||
|
||||
if ($lun_cmds->{$method}) {
|
||||
if ($scfg->{iscsiprovider} eq 'comstar') {
|
||||
$msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params);
|
||||
$msg =
|
||||
PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'istgt') {
|
||||
$msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet') {
|
||||
@ -73,21 +73,21 @@ sub zfs_request {
|
||||
}
|
||||
} else {
|
||||
|
||||
my $target = 'root@' . $scfg->{portal};
|
||||
my $target = 'root@' . $scfg->{portal};
|
||||
|
||||
my $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target];
|
||||
my $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target];
|
||||
|
||||
if ($method eq 'zpool_list') {
|
||||
push @$cmd, 'zpool', 'list';
|
||||
} else {
|
||||
push @$cmd, 'zfs', $method;
|
||||
push @$cmd, 'zpool', 'list';
|
||||
} else {
|
||||
push @$cmd, 'zfs', $method;
|
||||
}
|
||||
|
||||
push @$cmd, @params;
|
||||
push @$cmd, @params;
|
||||
|
||||
my $output = sub {
|
||||
my $line = shift;
|
||||
$msg .= "$line\n";
|
||||
my $output = sub {
|
||||
my $line = shift;
|
||||
$msg .= "$line\n";
|
||||
};
|
||||
|
||||
run_command($cmd, outfunc => $output, timeout => $timeout);
|
||||
@ -116,7 +116,7 @@ sub zfs_add_lun_mapping_entry {
|
||||
my ($class, $scfg, $zvol, $guid) = @_;
|
||||
|
||||
if (!defined($guid)) {
|
||||
$guid = $class->zfs_get_lu_name($scfg, $zvol);
|
||||
$guid = $class->zfs_get_lu_name($scfg, $zvol);
|
||||
}
|
||||
|
||||
$class->zfs_request($scfg, undef, 'add_view', $guid);
|
||||
@ -160,7 +160,7 @@ sub zfs_get_lun_number {
|
||||
die "could not find lun_number for guid $guid" if !$guid;
|
||||
|
||||
if ($class->zfs_request($scfg, undef, 'list_view', $guid) =~ /^(\d+)$/) {
|
||||
return $1;
|
||||
return $1;
|
||||
}
|
||||
|
||||
die "lun_number for guid $guid is not a number";
|
||||
@ -174,55 +174,55 @@ sub type {
|
||||
|
||||
sub plugindata {
|
||||
return {
|
||||
content => [ {images => 1}, { images => 1 }],
|
||||
'sensitive-properties' => {},
|
||||
content => [{ images => 1 }, { images => 1 }],
|
||||
'sensitive-properties' => {},
|
||||
};
|
||||
}
|
||||
|
||||
sub properties {
|
||||
return {
|
||||
iscsiprovider => {
|
||||
description => "iscsi provider",
|
||||
type => 'string',
|
||||
},
|
||||
# this will disable write caching on comstar and istgt.
|
||||
# it is not implemented for iet. iet blockio always operates with
|
||||
# writethrough caching when not in readonly mode
|
||||
nowritecache => {
|
||||
description => "disable write caching on the target",
|
||||
type => 'boolean',
|
||||
},
|
||||
comstar_tg => {
|
||||
description => "target group for comstar views",
|
||||
type => 'string',
|
||||
},
|
||||
comstar_hg => {
|
||||
description => "host group for comstar views",
|
||||
type => 'string',
|
||||
},
|
||||
lio_tpg => {
|
||||
description => "target portal group for Linux LIO targets",
|
||||
type => 'string',
|
||||
},
|
||||
iscsiprovider => {
|
||||
description => "iscsi provider",
|
||||
type => 'string',
|
||||
},
|
||||
# this will disable write caching on comstar and istgt.
|
||||
# it is not implemented for iet. iet blockio always operates with
|
||||
# writethrough caching when not in readonly mode
|
||||
nowritecache => {
|
||||
description => "disable write caching on the target",
|
||||
type => 'boolean',
|
||||
},
|
||||
comstar_tg => {
|
||||
description => "target group for comstar views",
|
||||
type => 'string',
|
||||
},
|
||||
comstar_hg => {
|
||||
description => "host group for comstar views",
|
||||
type => 'string',
|
||||
},
|
||||
lio_tpg => {
|
||||
description => "target portal group for Linux LIO targets",
|
||||
type => 'string',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
sub options {
|
||||
return {
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
portal => { fixed => 1 },
|
||||
target => { fixed => 1 },
|
||||
pool => { fixed => 1 },
|
||||
blocksize => { fixed => 1 },
|
||||
iscsiprovider => { fixed => 1 },
|
||||
nowritecache => { optional => 1 },
|
||||
sparse => { optional => 1 },
|
||||
comstar_hg => { optional => 1 },
|
||||
comstar_tg => { optional => 1 },
|
||||
lio_tpg => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
portal => { fixed => 1 },
|
||||
target => { fixed => 1 },
|
||||
pool => { fixed => 1 },
|
||||
blocksize => { fixed => 1 },
|
||||
iscsiprovider => { fixed => 1 },
|
||||
nowritecache => { optional => 1 },
|
||||
sparse => { optional => 1 },
|
||||
comstar_hg => { optional => 1 },
|
||||
comstar_tg => { optional => 1 },
|
||||
lio_tpg => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
};
|
||||
}
|
||||
|
||||
@ -232,7 +232,7 @@ sub path {
|
||||
my ($class, $scfg, $volname, $storeid, $snapname) = @_;
|
||||
|
||||
die "direct access to snapshots not implemented"
|
||||
if defined($snapname);
|
||||
if defined($snapname);
|
||||
|
||||
my ($vtype, $name, $vmid) = $class->parse_volname($volname);
|
||||
|
||||
@ -252,8 +252,7 @@ sub create_base {
|
||||
|
||||
my $snap = '__base__';
|
||||
|
||||
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
|
||||
$class->parse_volname($volname);
|
||||
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = $class->parse_volname($volname);
|
||||
|
||||
die "create_base not possible with base image\n" if $isBase;
|
||||
|
||||
@ -268,7 +267,7 @@ sub create_base {
|
||||
my $guid = $class->zfs_create_lu($scfg, $newname);
|
||||
$class->zfs_add_lun_mapping_entry($scfg, $newname, $guid);
|
||||
|
||||
my $running = undef; #fixme : is create_base always offline ?
|
||||
my $running = undef; #fixme : is create_base always offline ?
|
||||
|
||||
$class->volume_snapshot($scfg, $storeid, $newname, $snap, $running);
|
||||
|
||||
@ -291,18 +290,18 @@ sub clone_image {
|
||||
|
||||
sub alloc_image {
|
||||
my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
|
||||
|
||||
|
||||
die "unsupported format '$fmt'" if $fmt ne 'raw';
|
||||
|
||||
die "illegal name '$name' - should be 'vm-$vmid-*'\n"
|
||||
if $name && $name !~ m/^vm-$vmid-/;
|
||||
if $name && $name !~ m/^vm-$vmid-/;
|
||||
|
||||
my $volname = $name;
|
||||
|
||||
$volname = $class->find_free_diskname($storeid, $scfg, $vmid, $fmt) if !$volname;
|
||||
|
||||
|
||||
$class->zfs_create_zvol($scfg, $volname, $size);
|
||||
|
||||
|
||||
my $guid = $class->zfs_create_lu($scfg, $volname);
|
||||
$class->zfs_add_lun_mapping_entry($scfg, $volname, $guid);
|
||||
|
||||
@ -370,21 +369,20 @@ sub volume_has_feature {
|
||||
my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
|
||||
|
||||
my $features = {
|
||||
snapshot => { current => 1, snap => 1},
|
||||
clone => { base => 1},
|
||||
template => { current => 1},
|
||||
copy => { base => 1, current => 1},
|
||||
snapshot => { current => 1, snap => 1 },
|
||||
clone => { base => 1 },
|
||||
template => { current => 1 },
|
||||
copy => { base => 1, current => 1 },
|
||||
};
|
||||
|
||||
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
|
||||
$class->parse_volname($volname);
|
||||
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = $class->parse_volname($volname);
|
||||
|
||||
my $key = undef;
|
||||
|
||||
if ($snapname) {
|
||||
$key = 'snap';
|
||||
$key = 'snap';
|
||||
} else {
|
||||
$key = $isBase ? 'base' : 'current';
|
||||
$key = $isBase ? 'base' : 'current';
|
||||
}
|
||||
|
||||
return 1 if $features->{$feature}->{$key};
|
||||
|
||||
@ -20,39 +20,40 @@ sub type {
|
||||
|
||||
sub plugindata {
|
||||
return {
|
||||
content => [ {images => 1, rootdir => 1}, {images => 1 , rootdir => 1}],
|
||||
format => [ { raw => 1, subvol => 1 } , 'raw' ],
|
||||
'sensitive-properties' => {},
|
||||
content => [{ images => 1, rootdir => 1 }, { images => 1, rootdir => 1 }],
|
||||
format => [{ raw => 1, subvol => 1 }, 'raw'],
|
||||
'sensitive-properties' => {},
|
||||
};
|
||||
}
|
||||
|
||||
sub properties {
|
||||
return {
|
||||
blocksize => {
|
||||
description => "block size",
|
||||
type => 'string',
|
||||
},
|
||||
sparse => {
|
||||
description => "use sparse volumes",
|
||||
type => 'boolean',
|
||||
},
|
||||
mountpoint => {
|
||||
description => "mount point",
|
||||
type => 'string', format => 'pve-storage-path',
|
||||
},
|
||||
blocksize => {
|
||||
description => "block size",
|
||||
type => 'string',
|
||||
},
|
||||
sparse => {
|
||||
description => "use sparse volumes",
|
||||
type => 'boolean',
|
||||
},
|
||||
mountpoint => {
|
||||
description => "mount point",
|
||||
type => 'string',
|
||||
format => 'pve-storage-path',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
sub options {
|
||||
return {
|
||||
pool => { fixed => 1 },
|
||||
blocksize => { optional => 1 },
|
||||
sparse => { optional => 1 },
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
mountpoint => { optional => 1 },
|
||||
pool => { fixed => 1 },
|
||||
blocksize => { optional => 1 },
|
||||
sparse => { optional => 1 },
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
mountpoint => { optional => 1 },
|
||||
};
|
||||
}
|
||||
|
||||
@ -67,35 +68,35 @@ sub zfs_parse_zvol_list {
|
||||
|
||||
my @lines = split /\n/, $text;
|
||||
foreach my $line (@lines) {
|
||||
my ($dataset, $size, $origin, $type, $refquota) = split(/\s+/, $line);
|
||||
next if !($type eq 'volume' || $type eq 'filesystem');
|
||||
my ($dataset, $size, $origin, $type, $refquota) = split(/\s+/, $line);
|
||||
next if !($type eq 'volume' || $type eq 'filesystem');
|
||||
|
||||
my $zvol = {};
|
||||
my @parts = split /\//, $dataset;
|
||||
next if scalar(@parts) < 2; # we need pool/name
|
||||
my $name = pop @parts;
|
||||
my $parsed_pool = join('/', @parts);
|
||||
next if $parsed_pool ne $pool;
|
||||
my $zvol = {};
|
||||
my @parts = split /\//, $dataset;
|
||||
next if scalar(@parts) < 2; # we need pool/name
|
||||
my $name = pop @parts;
|
||||
my $parsed_pool = join('/', @parts);
|
||||
next if $parsed_pool ne $pool;
|
||||
|
||||
next unless $name =~ m!^(vm|base|subvol|basevol)-(\d+)-(\S+)$!;
|
||||
$zvol->{owner} = $2;
|
||||
next unless $name =~ m!^(vm|base|subvol|basevol)-(\d+)-(\S+)$!;
|
||||
$zvol->{owner} = $2;
|
||||
|
||||
$zvol->{name} = $name;
|
||||
if ($type eq 'filesystem') {
|
||||
if ($refquota eq 'none') {
|
||||
$zvol->{size} = 0;
|
||||
} else {
|
||||
$zvol->{size} = $refquota + 0;
|
||||
}
|
||||
$zvol->{format} = 'subvol';
|
||||
} else {
|
||||
$zvol->{size} = $size + 0;
|
||||
$zvol->{format} = 'raw';
|
||||
}
|
||||
if ($origin !~ /^-$/) {
|
||||
$zvol->{origin} = $origin;
|
||||
}
|
||||
push @$list, $zvol;
|
||||
$zvol->{name} = $name;
|
||||
if ($type eq 'filesystem') {
|
||||
if ($refquota eq 'none') {
|
||||
$zvol->{size} = 0;
|
||||
} else {
|
||||
$zvol->{size} = $refquota + 0;
|
||||
}
|
||||
$zvol->{format} = 'subvol';
|
||||
} else {
|
||||
$zvol->{size} = $size + 0;
|
||||
$zvol->{format} = 'raw';
|
||||
}
|
||||
if ($origin !~ /^-$/) {
|
||||
$zvol->{origin} = $origin;
|
||||
}
|
||||
push @$list, $zvol;
|
||||
}
|
||||
|
||||
return $list;
|
||||
@ -105,9 +106,9 @@ sub parse_volname {
|
||||
my ($class, $volname) = @_;
|
||||
|
||||
if ($volname =~ m/^(((base|basevol)-(\d+)-\S+)\/)?((base|basevol|vm|subvol)-(\d+)-\S+)$/) {
|
||||
my $format = ($6 eq 'subvol' || $6 eq 'basevol') ? 'subvol' : 'raw';
|
||||
my $isBase = ($6 eq 'base' || $6 eq 'basevol');
|
||||
return ('images', $5, $7, $2, $4, $isBase, $format);
|
||||
my $format = ($6 eq 'subvol' || $6 eq 'basevol') ? 'subvol' : 'raw';
|
||||
my $isBase = ($6 eq 'base' || $6 eq 'basevol');
|
||||
return ('images', $5, $7, $2, $4, $isBase, $format);
|
||||
}
|
||||
|
||||
die "unable to parse zfs volume name '$volname'\n";
|
||||
@ -123,17 +124,17 @@ sub on_add_hook {
|
||||
# ignore failure, pool might currently not be imported
|
||||
my $mountpoint;
|
||||
eval {
|
||||
my $res = $class->zfs_get_properties($scfg, 'mountpoint', $scfg->{pool}, 1);
|
||||
$mountpoint = PVE::Storage::Plugin::verify_path($res, 1) if defined($res);
|
||||
my $res = $class->zfs_get_properties($scfg, 'mountpoint', $scfg->{pool}, 1);
|
||||
$mountpoint = PVE::Storage::Plugin::verify_path($res, 1) if defined($res);
|
||||
};
|
||||
|
||||
if (defined($cfg_mountpoint)) {
|
||||
if (defined($mountpoint) && !($cfg_mountpoint =~ m|^\Q$mountpoint\E/?$|)) {
|
||||
warn "warning for $storeid - mountpoint: $cfg_mountpoint " .
|
||||
"does not match current mount point: $mountpoint\n";
|
||||
}
|
||||
if (defined($mountpoint) && !($cfg_mountpoint =~ m|^\Q$mountpoint\E/?$|)) {
|
||||
warn "warning for $storeid - mountpoint: $cfg_mountpoint "
|
||||
. "does not match current mount point: $mountpoint\n";
|
||||
}
|
||||
} else {
|
||||
$scfg->{mountpoint} = $mountpoint;
|
||||
$scfg->{mountpoint} = $mountpoint;
|
||||
}
|
||||
|
||||
return;
|
||||
@ -148,14 +149,14 @@ sub path {
|
||||
my $mountpoint = $scfg->{mountpoint} // "/$scfg->{pool}";
|
||||
|
||||
if ($vtype eq "images") {
|
||||
if ($name =~ m/^subvol-/ || $name =~ m/^basevol-/) {
|
||||
$path = "$mountpoint/$name";
|
||||
} else {
|
||||
$path = "/dev/zvol/$scfg->{pool}/$name";
|
||||
}
|
||||
$path .= "\@$snapname" if defined($snapname);
|
||||
if ($name =~ m/^subvol-/ || $name =~ m/^basevol-/) {
|
||||
$path = "$mountpoint/$name";
|
||||
} else {
|
||||
$path = "/dev/zvol/$scfg->{pool}/$name";
|
||||
}
|
||||
$path .= "\@$snapname" if defined($snapname);
|
||||
} else {
|
||||
die "$vtype is not allowed in ZFSPool!";
|
||||
die "$vtype is not allowed in ZFSPool!";
|
||||
}
|
||||
|
||||
return ($path, $vmid, $vtype);
|
||||
@ -167,12 +168,12 @@ sub zfs_request {
|
||||
my $cmd = [];
|
||||
|
||||
if ($method eq 'zpool_list') {
|
||||
push @$cmd, 'zpool', 'list';
|
||||
push @$cmd, 'zpool', 'list';
|
||||
} elsif ($method eq 'zpool_import') {
|
||||
push @$cmd, 'zpool', 'import';
|
||||
$timeout = 15 if !$timeout || $timeout < 15;
|
||||
push @$cmd, 'zpool', 'import';
|
||||
$timeout = 15 if !$timeout || $timeout < 15;
|
||||
} else {
|
||||
push @$cmd, 'zfs', $method;
|
||||
push @$cmd, 'zfs', $method;
|
||||
}
|
||||
push @$cmd, @params;
|
||||
|
||||
@ -180,10 +181,10 @@ sub zfs_request {
|
||||
my $output = sub { $msg .= "$_[0]\n" };
|
||||
|
||||
if (PVE::RPCEnvironment->is_worker()) {
|
||||
$timeout = 60*60 if !$timeout;
|
||||
$timeout = 60*5 if $timeout < 60*5;
|
||||
$timeout = 60 * 60 if !$timeout;
|
||||
$timeout = 60 * 5 if $timeout < 60 * 5;
|
||||
} else {
|
||||
$timeout = 10 if !$timeout;
|
||||
$timeout = 10 if !$timeout;
|
||||
}
|
||||
|
||||
run_command($cmd, errmsg => "zfs error", outfunc => $output, timeout => $timeout);
|
||||
@ -194,17 +195,17 @@ sub zfs_request {
|
||||
sub zfs_wait_for_zvol_link {
|
||||
my ($class, $scfg, $volname, $timeout) = @_;
|
||||
|
||||
my $default_timeout = PVE::RPCEnvironment->is_worker() ? 60*5 : 10;
|
||||
my $default_timeout = PVE::RPCEnvironment->is_worker() ? 60 * 5 : 10;
|
||||
$timeout = $default_timeout if !defined($timeout);
|
||||
|
||||
my ($devname, undef, undef) = $class->path($scfg, $volname);
|
||||
|
||||
for (my $i = 1; $i <= $timeout; $i++) {
|
||||
last if -b $devname;
|
||||
die "timeout: no zvol device link for '$volname' found after $timeout sec.\n"
|
||||
if $i == $timeout;
|
||||
last if -b $devname;
|
||||
die "timeout: no zvol device link for '$volname' found after $timeout sec.\n"
|
||||
if $i == $timeout;
|
||||
|
||||
sleep(1);
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,28 +216,28 @@ sub alloc_image {
|
||||
|
||||
if ($fmt eq 'raw') {
|
||||
|
||||
die "illegal name '$volname' - should be 'vm-$vmid-*'\n"
|
||||
if $volname && $volname !~ m/^vm-$vmid-/;
|
||||
$volname = $class->find_free_diskname($storeid, $scfg, $vmid, $fmt)
|
||||
if !$volname;
|
||||
die "illegal name '$volname' - should be 'vm-$vmid-*'\n"
|
||||
if $volname && $volname !~ m/^vm-$vmid-/;
|
||||
$volname = $class->find_free_diskname($storeid, $scfg, $vmid, $fmt)
|
||||
if !$volname;
|
||||
|
||||
$class->zfs_create_zvol($scfg, $volname, $size);
|
||||
$class->zfs_wait_for_zvol_link($scfg, $volname);
|
||||
$class->zfs_create_zvol($scfg, $volname, $size);
|
||||
$class->zfs_wait_for_zvol_link($scfg, $volname);
|
||||
|
||||
} elsif ( $fmt eq 'subvol') {
|
||||
} elsif ($fmt eq 'subvol') {
|
||||
|
||||
die "illegal name '$volname' - should be 'subvol-$vmid-*'\n"
|
||||
if $volname && $volname !~ m/^subvol-$vmid-/;
|
||||
$volname = $class->find_free_diskname($storeid, $scfg, $vmid, $fmt)
|
||||
if !$volname;
|
||||
die "illegal name '$volname' - should be 'subvol-$vmid-*'\n"
|
||||
if $volname && $volname !~ m/^subvol-$vmid-/;
|
||||
$volname = $class->find_free_diskname($storeid, $scfg, $vmid, $fmt)
|
||||
if !$volname;
|
||||
|
||||
die "illegal name '$volname' - should be 'subvol-$vmid-*'\n"
|
||||
if $volname !~ m/^subvol-$vmid-/;
|
||||
die "illegal name '$volname' - should be 'subvol-$vmid-*'\n"
|
||||
if $volname !~ m/^subvol-$vmid-/;
|
||||
|
||||
$class->zfs_create_subvol($scfg, $volname, $size);
|
||||
$class->zfs_create_subvol($scfg, $volname, $size);
|
||||
|
||||
} else {
|
||||
die "unsupported format '$fmt'";
|
||||
die "unsupported format '$fmt'";
|
||||
}
|
||||
|
||||
return $volname;
|
||||
@ -260,25 +261,25 @@ sub list_images {
|
||||
my $res = [];
|
||||
|
||||
for my $info (values $zfs_list->%*) {
|
||||
my $volname = $info->{name};
|
||||
my $parent = $info->{parent};
|
||||
my $owner = $info->{vmid};
|
||||
my $volname = $info->{name};
|
||||
my $parent = $info->{parent};
|
||||
my $owner = $info->{vmid};
|
||||
|
||||
if ($parent && $parent =~ m/^(\S+)\@__base__$/) {
|
||||
my ($basename) = ($1);
|
||||
$info->{volid} = "$storeid:$basename/$volname";
|
||||
} else {
|
||||
$info->{volid} = "$storeid:$volname";
|
||||
}
|
||||
if ($parent && $parent =~ m/^(\S+)\@__base__$/) {
|
||||
my ($basename) = ($1);
|
||||
$info->{volid} = "$storeid:$basename/$volname";
|
||||
} else {
|
||||
$info->{volid} = "$storeid:$volname";
|
||||
}
|
||||
|
||||
if ($vollist) {
|
||||
my $found = grep { $_ eq $info->{volid} } @$vollist;
|
||||
next if !$found;
|
||||
} else {
|
||||
next if defined ($vmid) && ($owner ne $vmid);
|
||||
}
|
||||
if ($vollist) {
|
||||
my $found = grep { $_ eq $info->{volid} } @$vollist;
|
||||
next if !$found;
|
||||
} else {
|
||||
next if defined($vmid) && ($owner ne $vmid);
|
||||
}
|
||||
|
||||
push @$res, $info;
|
||||
push @$res, $info;
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
@ -286,8 +287,8 @@ sub list_images {
|
||||
sub zfs_get_properties {
|
||||
my ($class, $scfg, $properties, $dataset, $timeout) = @_;
|
||||
|
||||
my $result = $class->zfs_request($scfg, $timeout, 'get', '-o', 'value',
|
||||
'-Hp', $properties, $dataset);
|
||||
my $result =
|
||||
$class->zfs_request($scfg, $timeout, 'get', '-o', 'value', '-Hp', $properties, $dataset);
|
||||
my @values = split /\n/, $result;
|
||||
return wantarray ? @values : $values[0];
|
||||
}
|
||||
@ -300,12 +301,12 @@ sub zfs_get_pool_stats {
|
||||
|
||||
my @lines = $class->zfs_get_properties($scfg, 'available,used', $scfg->{pool});
|
||||
|
||||
if($lines[0] =~ /^(\d+)$/) {
|
||||
$available = $1;
|
||||
if ($lines[0] =~ /^(\d+)$/) {
|
||||
$available = $1;
|
||||
}
|
||||
|
||||
if($lines[1] =~ /^(\d+)$/) {
|
||||
$used = $1;
|
||||
if ($lines[1] =~ /^(\d+)$/) {
|
||||
$used = $1;
|
||||
}
|
||||
|
||||
return ($available, $used);
|
||||
@ -336,8 +337,8 @@ sub zfs_create_subvol {
|
||||
my $dataset = "$scfg->{pool}/$volname";
|
||||
my $quota = $size ? "${size}k" : "none";
|
||||
|
||||
my $cmd = ['create', '-o', 'acltype=posixacl', '-o', 'xattr=sa',
|
||||
'-o', "refquota=${quota}", $dataset];
|
||||
my $cmd =
|
||||
['create', '-o', 'acltype=posixacl', '-o', 'xattr=sa', '-o', "refquota=${quota}", $dataset];
|
||||
|
||||
$class->zfs_request($scfg, undef, @$cmd);
|
||||
}
|
||||
@ -349,19 +350,19 @@ sub zfs_delete_zvol {
|
||||
|
||||
for (my $i = 0; $i < 6; $i++) {
|
||||
|
||||
eval { $class->zfs_request($scfg, undef, 'destroy', '-r', "$scfg->{pool}/$zvol"); };
|
||||
if ($err = $@) {
|
||||
if ($err =~ m/^zfs error:(.*): dataset is busy.*/) {
|
||||
sleep(1);
|
||||
} elsif ($err =~ m/^zfs error:.*: dataset does not exist.*$/) {
|
||||
$err = undef;
|
||||
last;
|
||||
} else {
|
||||
die $err;
|
||||
}
|
||||
} else {
|
||||
last;
|
||||
}
|
||||
eval { $class->zfs_request($scfg, undef, 'destroy', '-r', "$scfg->{pool}/$zvol"); };
|
||||
if ($err = $@) {
|
||||
if ($err =~ m/^zfs error:(.*): dataset is busy.*/) {
|
||||
sleep(1);
|
||||
} elsif ($err =~ m/^zfs error:.*: dataset does not exist.*$/) {
|
||||
$err = undef;
|
||||
last;
|
||||
} else {
|
||||
die $err;
|
||||
}
|
||||
} else {
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
die $err if $err;
|
||||
@ -371,16 +372,16 @@ sub zfs_list_zvol {
|
||||
my ($class, $scfg) = @_;
|
||||
|
||||
my $text = $class->zfs_request(
|
||||
$scfg,
|
||||
10,
|
||||
'list',
|
||||
'-o',
|
||||
'name,volsize,origin,type,refquota',
|
||||
'-t',
|
||||
'volume,filesystem',
|
||||
'-d1',
|
||||
'-Hp',
|
||||
$scfg->{pool},
|
||||
$scfg,
|
||||
10,
|
||||
'list',
|
||||
'-o',
|
||||
'name,volsize,origin,type,refquota',
|
||||
'-t',
|
||||
'volume,filesystem',
|
||||
'-d1',
|
||||
'-Hp',
|
||||
$scfg->{pool},
|
||||
);
|
||||
# It's still required to have zfs_parse_zvol_list filter by pool, because -d1 lists
|
||||
# $scfg->{pool} too and while unlikely, it could be named to be mistaken for a volume.
|
||||
@ -389,17 +390,17 @@ sub zfs_list_zvol {
|
||||
|
||||
my $list = {};
|
||||
foreach my $zvol (@$zvols) {
|
||||
my $name = $zvol->{name};
|
||||
my $parent = $zvol->{origin};
|
||||
if($zvol->{origin} && $zvol->{origin} =~ m/^$scfg->{pool}\/(\S+)$/){
|
||||
$parent = $1;
|
||||
}
|
||||
my $name = $zvol->{name};
|
||||
my $parent = $zvol->{origin};
|
||||
if ($zvol->{origin} && $zvol->{origin} =~ m/^$scfg->{pool}\/(\S+)$/) {
|
||||
$parent = $1;
|
||||
}
|
||||
|
||||
$list->{$name} = {
|
||||
name => $name,
|
||||
size => $zvol->{size},
|
||||
parent => $parent,
|
||||
format => $zvol->{format},
|
||||
$list->{$name} = {
|
||||
name => $name,
|
||||
size => $zvol->{size},
|
||||
parent => $parent,
|
||||
format => $zvol->{format},
|
||||
vmid => $zvol->{owner},
|
||||
};
|
||||
}
|
||||
@ -420,8 +421,8 @@ sub zfs_get_sorted_snapshot_list {
|
||||
|
||||
my $snap_names = [];
|
||||
for my $snapshot (@snapshots) {
|
||||
(my $snap_name = $snapshot) =~ s/^.*@//;
|
||||
push $snap_names->@*, $snap_name;
|
||||
(my $snap_name = $snapshot) =~ s/^.*@//;
|
||||
push $snap_names->@*, $snap_name;
|
||||
}
|
||||
return $snap_names;
|
||||
}
|
||||
@ -435,9 +436,9 @@ sub status {
|
||||
my $active = 0;
|
||||
|
||||
eval {
|
||||
($free, $used) = $class->zfs_get_pool_stats($scfg);
|
||||
$active = 1;
|
||||
$total = $free + $used;
|
||||
($free, $used) = $class->zfs_get_pool_stats($scfg);
|
||||
$active = 1;
|
||||
$total = $free + $used;
|
||||
};
|
||||
warn $@ if $@;
|
||||
|
||||
@ -447,16 +448,16 @@ sub status {
|
||||
sub volume_size_info {
|
||||
my ($class, $scfg, $storeid, $volname, $timeout) = @_;
|
||||
|
||||
my (undef, $vname, undef, $parent, undef, undef, $format) =
|
||||
$class->parse_volname($volname);
|
||||
my (undef, $vname, undef, $parent, undef, undef, $format) = $class->parse_volname($volname);
|
||||
|
||||
my $attr = $format eq 'subvol' ? 'refquota' : 'volsize';
|
||||
my ($size, $used) = $class->zfs_get_properties($scfg, "$attr,usedbydataset", "$scfg->{pool}/$vname");
|
||||
my ($size, $used) =
|
||||
$class->zfs_get_properties($scfg, "$attr,usedbydataset", "$scfg->{pool}/$vname");
|
||||
|
||||
$used = ($used =~ /^(\d+)$/) ? $1 : 0;
|
||||
|
||||
if ($size =~ /^(\d+)$/) {
|
||||
return wantarray ? ($1, $format, $used, $parent) : $1;
|
||||
return wantarray ? ($1, $format, $used, $parent) : $1;
|
||||
}
|
||||
|
||||
die "Could not get zfs volume size\n";
|
||||
@ -490,10 +491,10 @@ sub volume_snapshot_rollback {
|
||||
# caches, they get mounted in activate volume again
|
||||
# see zfs bug #10931 https://github.com/openzfs/zfs/issues/10931
|
||||
if ($format eq 'subvol') {
|
||||
eval { $class->zfs_request($scfg, undef, 'unmount', "$scfg->{pool}/$vname"); };
|
||||
if (my $err = $@) {
|
||||
die $err if $err !~ m/not currently mounted$/;
|
||||
}
|
||||
eval { $class->zfs_request($scfg, undef, 'unmount', "$scfg->{pool}/$vname"); };
|
||||
if (my $err = $@) {
|
||||
die $err if $err !~ m/not currently mounted$/;
|
||||
}
|
||||
}
|
||||
|
||||
return $msg;
|
||||
@ -509,20 +510,20 @@ sub volume_rollback_is_possible {
|
||||
my $found;
|
||||
$blockers //= []; # not guaranteed to be set by caller
|
||||
for my $snapshot ($snapshots->@*) {
|
||||
if ($snapshot eq $snap) {
|
||||
$found = 1;
|
||||
} elsif ($found) {
|
||||
push $blockers->@*, $snapshot;
|
||||
}
|
||||
if ($snapshot eq $snap) {
|
||||
$found = 1;
|
||||
} elsif ($found) {
|
||||
push $blockers->@*, $snapshot;
|
||||
}
|
||||
}
|
||||
|
||||
my $volid = "${storeid}:${volname}";
|
||||
|
||||
die "can't rollback, snapshot '$snap' does not exist on '$volid'\n"
|
||||
if !$found;
|
||||
if !$found;
|
||||
|
||||
die "can't rollback, '$snap' is not most recent snapshot on '$volid'\n"
|
||||
if scalar($blockers->@*) > 0;
|
||||
if scalar($blockers->@*) > 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
@ -540,13 +541,13 @@ sub volume_snapshot_info {
|
||||
|
||||
my $info = {};
|
||||
for my $line (@lines) {
|
||||
my ($snapshot, $guid, $creation) = split(/\s+/, $line);
|
||||
(my $snap_name = $snapshot) =~ s/^.*@//;
|
||||
my ($snapshot, $guid, $creation) = split(/\s+/, $line);
|
||||
(my $snap_name = $snapshot) =~ s/^.*@//;
|
||||
|
||||
$info->{$snap_name} = {
|
||||
id => $guid,
|
||||
timestamp => $creation,
|
||||
};
|
||||
$info->{$snap_name} = {
|
||||
id => $guid,
|
||||
timestamp => $creation,
|
||||
};
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
@ -556,12 +557,12 @@ my sub dataset_mounted_heuristic {
|
||||
|
||||
my $mounts = PVE::ProcFSTools::parse_proc_mounts();
|
||||
for my $mp (@$mounts) {
|
||||
my ($what, $dir, $fs) = $mp->@*;
|
||||
next if $fs ne 'zfs';
|
||||
# check for root-dataset or any child-dataset (root-dataset could have 'canmount=off')
|
||||
# If any child is mounted heuristically assume that `zfs mount -a` was successful
|
||||
next if $what !~ m!^$dataset(?:/|$)!;
|
||||
return 1;
|
||||
my ($what, $dir, $fs) = $mp->@*;
|
||||
next if $fs ne 'zfs';
|
||||
# check for root-dataset or any child-dataset (root-dataset could have 'canmount=off')
|
||||
# If any child is mounted heuristically assume that `zfs mount -a` was successful
|
||||
next if $what !~ m!^$dataset(?:/|$)!;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -576,21 +577,21 @@ sub activate_storage {
|
||||
return 1 if dataset_mounted_heuristic($dataset); # early return
|
||||
|
||||
my $pool_imported = sub {
|
||||
my @param = ('-o', 'name', '-H', $pool);
|
||||
my $res = eval { $class->zfs_request($scfg, undef, 'zpool_list', @param) };
|
||||
warn "$@\n" if $@;
|
||||
my @param = ('-o', 'name', '-H', $pool);
|
||||
my $res = eval { $class->zfs_request($scfg, undef, 'zpool_list', @param) };
|
||||
warn "$@\n" if $@;
|
||||
|
||||
return defined($res) && $res =~ m/$pool/;
|
||||
return defined($res) && $res =~ m/$pool/;
|
||||
};
|
||||
|
||||
if (!$pool_imported->()) {
|
||||
# import can only be done if not yet imported!
|
||||
my @param = ('-d', '/dev/disk/by-id/', '-o', 'cachefile=none', $pool);
|
||||
eval { $class->zfs_request($scfg, undef, 'zpool_import', @param) };
|
||||
if (my $err = $@) {
|
||||
# just could've raced with another import, so recheck if it is imported
|
||||
die "could not activate storage '$storeid', $err\n" if !$pool_imported->();
|
||||
}
|
||||
# import can only be done if not yet imported!
|
||||
my @param = ('-d', '/dev/disk/by-id/', '-o', 'cachefile=none', $pool);
|
||||
eval { $class->zfs_request($scfg, undef, 'zpool_import', @param) };
|
||||
if (my $err = $@) {
|
||||
# just could've raced with another import, so recheck if it is imported
|
||||
die "could not activate storage '$storeid', $err\n" if !$pool_imported->();
|
||||
}
|
||||
}
|
||||
eval { $class->zfs_request($scfg, undef, 'mount', '-a') };
|
||||
die "could not activate storage '$storeid', $@\n" if $@;
|
||||
@ -610,12 +611,12 @@ sub activate_volume {
|
||||
my (undef, $dataset, undef, undef, undef, undef, $format) = $class->parse_volname($volname);
|
||||
|
||||
if ($format eq 'raw') {
|
||||
$class->zfs_wait_for_zvol_link($scfg, $volname);
|
||||
$class->zfs_wait_for_zvol_link($scfg, $volname);
|
||||
} elsif ($format eq 'subvol') {
|
||||
my $mounted = $class->zfs_get_properties($scfg, 'mounted', "$scfg->{pool}/$dataset");
|
||||
if ($mounted !~ m/^yes$/) {
|
||||
$class->zfs_request($scfg, undef, 'mount', "$scfg->{pool}/$dataset");
|
||||
}
|
||||
my $mounted = $class->zfs_get_properties($scfg, 'mounted', "$scfg->{pool}/$dataset");
|
||||
if ($mounted !~ m/^yes$/) {
|
||||
$class->zfs_request($scfg, undef, 'mount', "$scfg->{pool}/$dataset");
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
@ -639,11 +640,27 @@ sub clone_image {
|
||||
my $name = $class->find_free_diskname($storeid, $scfg, $vmid, $format);
|
||||
|
||||
if ($format eq 'subvol') {
|
||||
my $size = $class->zfs_request($scfg, undef, 'list', '-Hp', '-o', 'refquota', "$scfg->{pool}/$basename");
|
||||
chomp($size);
|
||||
$class->zfs_request($scfg, undef, 'clone', "$scfg->{pool}/$basename\@$snap", "$scfg->{pool}/$name", '-o', "refquota=$size");
|
||||
my $size = $class->zfs_request(
|
||||
$scfg, undef, 'list', '-Hp', '-o', 'refquota', "$scfg->{pool}/$basename",
|
||||
);
|
||||
chomp($size);
|
||||
$class->zfs_request(
|
||||
$scfg,
|
||||
undef,
|
||||
'clone',
|
||||
"$scfg->{pool}/$basename\@$snap",
|
||||
"$scfg->{pool}/$name",
|
||||
'-o',
|
||||
"refquota=$size",
|
||||
);
|
||||
} else {
|
||||
$class->zfs_request($scfg, undef, 'clone', "$scfg->{pool}/$basename\@$snap", "$scfg->{pool}/$name");
|
||||
$class->zfs_request(
|
||||
$scfg,
|
||||
undef,
|
||||
'clone',
|
||||
"$scfg->{pool}/$basename\@$snap",
|
||||
"$scfg->{pool}/$name",
|
||||
);
|
||||
}
|
||||
|
||||
return "$basename/$name";
|
||||
@ -660,16 +677,16 @@ sub create_base {
|
||||
die "create_base not possible with base image\n" if $isBase;
|
||||
|
||||
my $newname = $name;
|
||||
if ( $format eq 'subvol' ) {
|
||||
$newname =~ s/^subvol-/basevol-/;
|
||||
if ($format eq 'subvol') {
|
||||
$newname =~ s/^subvol-/basevol-/;
|
||||
} else {
|
||||
$newname =~ s/^vm-/base-/;
|
||||
$newname =~ s/^vm-/base-/;
|
||||
}
|
||||
my $newvolname = $basename ? "$basename/$newname" : "$newname";
|
||||
|
||||
$class->zfs_request($scfg, undef, 'rename', "$scfg->{pool}/$name", "$scfg->{pool}/$newname");
|
||||
|
||||
my $running = undef; #fixme : is create_base always offline ?
|
||||
my $running = undef; #fixme : is create_base always offline ?
|
||||
|
||||
$class->volume_snapshot($scfg, $storeid, $newname, $snap, $running);
|
||||
|
||||
@ -679,17 +696,16 @@ sub create_base {
|
||||
sub volume_resize {
|
||||
my ($class, $scfg, $storeid, $volname, $size, $running) = @_;
|
||||
|
||||
my $new_size = int($size/1024);
|
||||
my $new_size = int($size / 1024);
|
||||
|
||||
my (undef, $vname, undef, undef, undef, undef, $format) =
|
||||
$class->parse_volname($volname);
|
||||
my (undef, $vname, undef, undef, undef, undef, $format) = $class->parse_volname($volname);
|
||||
|
||||
my $attr = $format eq 'subvol' ? 'refquota' : 'volsize';
|
||||
|
||||
# align size to 1M so we always have a valid multiple of the volume block size
|
||||
if ($format eq 'raw') {
|
||||
my $padding = (1024 - $new_size % 1024) % 1024;
|
||||
$new_size = $new_size + $padding;
|
||||
my $padding = (1024 - $new_size % 1024) % 1024;
|
||||
$new_size = $new_size + $padding;
|
||||
}
|
||||
|
||||
$class->zfs_request($scfg, undef, 'set', "$attr=${new_size}k", "$scfg->{pool}/$vname");
|
||||
@ -709,24 +725,23 @@ sub volume_has_feature {
|
||||
my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
|
||||
|
||||
my $features = {
|
||||
snapshot => { current => 1, snap => 1},
|
||||
clone => { base => 1},
|
||||
template => { current => 1},
|
||||
copy => { base => 1, current => 1},
|
||||
sparseinit => { base => 1, current => 1},
|
||||
replicate => { base => 1, current => 1},
|
||||
rename => {current => 1},
|
||||
snapshot => { current => 1, snap => 1 },
|
||||
clone => { base => 1 },
|
||||
template => { current => 1 },
|
||||
copy => { base => 1, current => 1 },
|
||||
sparseinit => { base => 1, current => 1 },
|
||||
replicate => { base => 1, current => 1 },
|
||||
rename => { current => 1 },
|
||||
};
|
||||
|
||||
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
|
||||
$class->parse_volname($volname);
|
||||
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = $class->parse_volname($volname);
|
||||
|
||||
my $key = undef;
|
||||
|
||||
if ($snapname) {
|
||||
$key = 'snap';
|
||||
$key = 'snap';
|
||||
} else {
|
||||
$key = $isBase ? 'base' : 'current';
|
||||
$key = $isBase ? 'base' : 'current';
|
||||
}
|
||||
|
||||
return 1 if $features->{$feature}->{$key};
|
||||
@ -735,19 +750,20 @@ sub volume_has_feature {
|
||||
}
|
||||
|
||||
sub volume_export {
|
||||
my ($class, $scfg, $storeid, $fh, $volname, $format, $snapshot, $base_snapshot, $with_snapshots) = @_;
|
||||
my ($class, $scfg, $storeid, $fh, $volname, $format, $snapshot, $base_snapshot, $with_snapshots)
|
||||
= @_;
|
||||
|
||||
die "unsupported export stream format for $class: $format\n"
|
||||
if $format ne 'zfs';
|
||||
if $format ne 'zfs';
|
||||
|
||||
die "$class storage can only export snapshots\n"
|
||||
if !defined($snapshot);
|
||||
if !defined($snapshot);
|
||||
|
||||
my $dataset = ($class->parse_volname($volname))[1];
|
||||
|
||||
my $fd = fileno($fh);
|
||||
die "internal error: invalid file handle for volume_export\n"
|
||||
if !defined($fd);
|
||||
if !defined($fd);
|
||||
$fd = ">&$fd";
|
||||
|
||||
# For zfs we always create a replication stream (-R) which means the remote
|
||||
@ -755,8 +771,8 @@ sub volume_export {
|
||||
# for all our use cases.
|
||||
my $cmd = ['zfs', 'send', '-Rpv'];
|
||||
if (defined($base_snapshot)) {
|
||||
my $arg = $with_snapshots ? '-I' : '-i';
|
||||
push @$cmd, $arg, $base_snapshot;
|
||||
my $arg = $with_snapshots ? '-I' : '-i';
|
||||
push @$cmd, $arg, $base_snapshot;
|
||||
}
|
||||
push @$cmd, '--', "$scfg->{pool}/$dataset\@$snapshot";
|
||||
|
||||
@ -776,39 +792,53 @@ sub volume_export_formats {
|
||||
}
|
||||
|
||||
sub volume_import {
|
||||
my ($class, $scfg, $storeid, $fh, $volname, $format, $snapshot, $base_snapshot, $with_snapshots, $allow_rename) = @_;
|
||||
my (
|
||||
$class,
|
||||
$scfg,
|
||||
$storeid,
|
||||
$fh,
|
||||
$volname,
|
||||
$format,
|
||||
$snapshot,
|
||||
$base_snapshot,
|
||||
$with_snapshots,
|
||||
$allow_rename,
|
||||
) = @_;
|
||||
|
||||
die "unsupported import stream format for $class: $format\n"
|
||||
if $format ne 'zfs';
|
||||
if $format ne 'zfs';
|
||||
|
||||
my $fd = fileno($fh);
|
||||
die "internal error: invalid file handle for volume_import\n"
|
||||
if !defined($fd);
|
||||
if !defined($fd);
|
||||
|
||||
my (undef, $dataset, $vmid, undef, undef, undef, $volume_format) =
|
||||
$class->parse_volname($volname);
|
||||
$class->parse_volname($volname);
|
||||
|
||||
my $zfspath = "$scfg->{pool}/$dataset";
|
||||
my $suffix = defined($base_snapshot) ? "\@$base_snapshot" : '';
|
||||
my $exists = 0 == run_command(['zfs', 'get', '-H', 'name', $zfspath.$suffix],
|
||||
noerr => 1, quiet => 1);
|
||||
my $exists = 0 == run_command(
|
||||
['zfs', 'get', '-H', 'name', $zfspath . $suffix],
|
||||
noerr => 1,
|
||||
quiet => 1,
|
||||
);
|
||||
if (defined($base_snapshot)) {
|
||||
die "base snapshot '$zfspath\@$base_snapshot' doesn't exist\n" if !$exists;
|
||||
die "base snapshot '$zfspath\@$base_snapshot' doesn't exist\n" if !$exists;
|
||||
} elsif ($exists) {
|
||||
die "volume '$zfspath' already exists\n" if !$allow_rename;
|
||||
warn "volume '$zfspath' already exists - importing with a different name\n";
|
||||
$dataset = $class->find_free_diskname($storeid, $scfg, $vmid, $volume_format);
|
||||
$zfspath = "$scfg->{pool}/$dataset";
|
||||
die "volume '$zfspath' already exists\n" if !$allow_rename;
|
||||
warn "volume '$zfspath' already exists - importing with a different name\n";
|
||||
$dataset = $class->find_free_diskname($storeid, $scfg, $vmid, $volume_format);
|
||||
$zfspath = "$scfg->{pool}/$dataset";
|
||||
}
|
||||
|
||||
eval { run_command(['zfs', 'recv', '-F', '--', $zfspath], input => "<&$fd") };
|
||||
if (my $err = $@) {
|
||||
if (defined($base_snapshot)) {
|
||||
eval { run_command(['zfs', 'rollback', '-r', '--', "$zfspath\@$base_snapshot"]) };
|
||||
} else {
|
||||
eval { run_command(['zfs', 'destroy', '-r', '--', $zfspath]) };
|
||||
}
|
||||
die $err;
|
||||
if (defined($base_snapshot)) {
|
||||
eval { run_command(['zfs', 'rollback', '-r', '--', "$zfspath\@$base_snapshot"]) };
|
||||
} else {
|
||||
eval { run_command(['zfs', 'destroy', '-r', '--', $zfspath]) };
|
||||
}
|
||||
die $err;
|
||||
}
|
||||
|
||||
return "$storeid:$dataset";
|
||||
@ -817,30 +847,29 @@ sub volume_import {
|
||||
sub volume_import_formats {
|
||||
my ($class, $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots) = @_;
|
||||
|
||||
return $class->volume_export_formats($scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots);
|
||||
return $class->volume_export_formats(
|
||||
$scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots,
|
||||
);
|
||||
}
|
||||
|
||||
sub rename_volume {
|
||||
my ($class, $scfg, $storeid, $source_volname, $target_vmid, $target_volname) = @_;
|
||||
|
||||
my (
|
||||
undef,
|
||||
$source_image,
|
||||
$source_vmid,
|
||||
$base_name,
|
||||
$base_vmid,
|
||||
undef,
|
||||
$format
|
||||
undef, $source_image, $source_vmid, $base_name, $base_vmid, undef, $format,
|
||||
) = $class->parse_volname($source_volname);
|
||||
$target_volname = $class->find_free_diskname($storeid, $scfg, $target_vmid, $format)
|
||||
if !$target_volname;
|
||||
if !$target_volname;
|
||||
|
||||
my $pool = $scfg->{pool};
|
||||
my $source_zfspath = "${pool}/${source_image}";
|
||||
my $target_zfspath = "${pool}/${target_volname}";
|
||||
|
||||
my $exists = 0 == run_command(['zfs', 'get', '-H', 'name', $target_zfspath],
|
||||
noerr => 1, quiet => 1);
|
||||
my $exists = 0 == run_command(
|
||||
['zfs', 'get', '-H', 'name', $target_zfspath],
|
||||
noerr => 1,
|
||||
quiet => 1,
|
||||
);
|
||||
die "target volume '${target_volname}' already exists\n" if $exists;
|
||||
|
||||
$class->zfs_request($scfg, 5, 'rename', ${source_zfspath}, ${target_zfspath});
|
||||
|
||||
Reference in New Issue
Block a user