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:
Thomas Lamprecht
2025-06-11 10:03:21 +02:00
parent 5d23073cb6
commit 5a66c27cc6
54 changed files with 14137 additions and 12461 deletions

View File

@ -75,7 +75,8 @@ __PACKAGE__->register_method ({
]; ];
return $result; return $result;
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'list', name => 'list',
@ -136,9 +137,9 @@ __PACKAGE__->register_method ({
health => { type => 'string', optional => 1 }, health => { type => 'string', optional => 1 },
parent => { parent => {
type => 'string', type => 'string',
description => 'For partitions only. The device path of ' . description => 'For partitions only. The device path of '
'the disk the partition resides on.', . 'the disk the partition resides on.',
optional => 1 optional => 1,
}, },
}, },
}, },
@ -150,9 +151,7 @@ __PACKAGE__->register_method ({
my $include_partitions = $param->{'include-partitions'} // 0; my $include_partitions = $param->{'include-partitions'} // 0;
my $disks = PVE::Diskmanage::get_disks( my $disks = PVE::Diskmanage::get_disks(
undef, undef, $skipsmart, $include_partitions,
$skipsmart,
$include_partitions
); );
my $type = $param->{type} // ''; my $type = $param->{type} // '';
@ -163,8 +162,8 @@ __PACKAGE__->register_method ({
if ($type eq 'journal_disks') { if ($type eq 'journal_disks') {
next if $entry->{osdid} >= 0; next if $entry->{osdid} >= 0;
if (my $usage = $entry->{used}) { if (my $usage = $entry->{used}) {
next if !($usage eq 'partitions' && $entry->{gpt} next
|| $usage eq 'LVM'); if !($usage eq 'partitions' && $entry->{gpt} || $usage eq 'LVM');
} }
} elsif ($type eq 'unused') { } elsif ($type eq 'unused') {
next if $entry->{used}; next if $entry->{used};
@ -174,7 +173,8 @@ __PACKAGE__->register_method ({
push @$result, $entry; push @$result, $entry;
} }
return $result; return $result;
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'smart', name => 'smart',
@ -222,7 +222,8 @@ __PACKAGE__->register_method ({
$result = { health => $result->{health} } if $param->{healthonly}; $result = { health => $result->{health} } if $param->{healthonly};
return $result; return $result;
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'initgpt', name => 'initgpt',
@ -271,7 +272,8 @@ __PACKAGE__->register_method ({
my $diskid = $disk; my $diskid = $disk;
$diskid =~ s|^.*/||; # remove all up to the last slash $diskid =~ s|^.*/||; # remove all up to the last slash
return $rpcenv->fork_worker('diskinit', $diskid, $authuser, $worker); return $rpcenv->fork_worker('diskinit', $diskid, $authuser, $worker);
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'wipe_disk', name => 'wipe_disk',
@ -314,6 +316,7 @@ __PACKAGE__->register_method ({
my $basename = basename($disk); # avoid '/' in the ID my $basename = basename($disk); # avoid '/' in the ID
return $rpcenv->fork_worker('wipedisk', $basename, $authuser, $worker); return $rpcenv->fork_worker('wipedisk', $basename, $authuser, $worker);
}}); },
});
1; 1;

View File

@ -139,24 +139,30 @@ __PACKAGE__->register_method ({
my $result = []; my $result = [];
dir_glob_foreach('/etc/systemd/system', '^mnt-pve-(.+)\.mount$', sub { dir_glob_foreach(
'/etc/systemd/system',
'^mnt-pve-(.+)\.mount$',
sub {
my ($filename, $storid) = @_; my ($filename, $storid) = @_;
$storid = PVE::Systemd::unescape_unit($storid); $storid = PVE::Systemd::unescape_unit($storid);
my $unitfile = "/etc/systemd/system/$filename"; my $unitfile = "/etc/systemd/system/$filename";
my $unit = $read_ini->($unitfile); my $unit = $read_ini->($unitfile);
push @$result, { push @$result,
{
unitfile => $unitfile, unitfile => $unitfile,
path => "/mnt/pve/$storid", path => "/mnt/pve/$storid",
device => $unit->{'Mount'}->{'What'}, device => $unit->{'Mount'}->{'What'},
type => $unit->{'Mount'}->{'Type'}, type => $unit->{'Mount'}->{'Type'},
options => $unit->{'Mount'}->{'Options'}, options => $unit->{'Mount'}->{'Options'},
}; };
}); },
);
return $result; return $result;
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'create', name => 'create',
@ -165,10 +171,12 @@ __PACKAGE__->register_method ({
proxyto => 'node', proxyto => 'node',
protected => 1, protected => 1,
permissions => { permissions => {
description => "Requires additionally 'Datastore.Allocate' on /storage when setting 'add_storage'", description =>
"Requires additionally 'Datastore.Allocate' on /storage when setting 'add_storage'",
check => ['perm', '/', ['Sys.Modify']], check => ['perm', '/', ['Sys.Modify']],
}, },
description => "Create a Filesystem on an unused disk. Will be mounted under '/mnt/pve/NAME'.", description =>
"Create a Filesystem on an unused disk. Will be mounted under '/mnt/pve/NAME'.",
parameters => { parameters => {
additionalProperties => 0, additionalProperties => 0,
properties => { properties => {
@ -226,7 +234,8 @@ __PACKAGE__->register_method ({
# reserve the name and add as disabled, will be enabled below if creation works out # reserve the name and add as disabled, will be enabled below if creation works out
PVE::API2::Storage::Config->create_or_update( PVE::API2::Storage::Config->create_or_update(
$name, $node, $storage_params, $verify_params, 1); $name, $node, $storage_params, $verify_params, 1,
);
} }
my $mounted = PVE::Diskmanage::mounted_paths(); my $mounted = PVE::Diskmanage::mounted_paths();
@ -251,10 +260,14 @@ __PACKAGE__->register_method ({
my ($devname) = $dev =~ m|^/dev/(.*)$|; my ($devname) = $dev =~ m|^/dev/(.*)$|;
$part = "/dev/"; $part = "/dev/";
dir_glob_foreach("/sys/block/$devname", qr/\Q$devname\E.+/, sub { dir_glob_foreach(
"/sys/block/$devname",
qr/\Q$devname\E.+/,
sub {
my ($partition) = @_; my ($partition) = @_;
$part .= $partition; $part .= $partition;
}); },
);
} }
# create filesystem # create filesystem
@ -277,14 +290,17 @@ __PACKAGE__->register_method ({
$cmd = [$BLKID, $part, '-o', 'export']; $cmd = [$BLKID, $part, '-o', 'export'];
print "# ", join(' ', @$cmd), "\n"; print "# ", join(' ', @$cmd), "\n";
run_command($cmd, outfunc => sub { run_command(
$cmd,
outfunc => sub {
my ($line) = @_; my ($line) = @_;
if ($line =~ m/^UUID=(.*)$/) { if ($line =~ m/^UUID=(.*)$/) {
$uuid = $1; $uuid = $1;
$uuid_path = "/dev/disk/by-uuid/$uuid"; $uuid_path = "/dev/disk/by-uuid/$uuid";
} }
}); },
);
die "could not get UUID of device '$part'\n" if !$uuid; die "could not get UUID of device '$part'\n" if !$uuid;
@ -305,13 +321,15 @@ __PACKAGE__->register_method ({
if ($param->{add_storage}) { if ($param->{add_storage}) {
PVE::API2::Storage::Config->create_or_update( PVE::API2::Storage::Config->create_or_update(
$name, $node, $storage_params, $verify_params); $name, $node, $storage_params, $verify_params,
);
} }
}); });
}; };
return $rpcenv->fork_worker('dircreate', $name, $user, $worker); return $rpcenv->fork_worker('dircreate', $name, $user, $worker);
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'delete', name => 'delete',
@ -320,7 +338,8 @@ __PACKAGE__->register_method ({
proxyto => 'node', proxyto => 'node',
protected => 1, protected => 1,
permissions => { permissions => {
description => "Requires additionally 'Datastore.Allocate' on /storage when setting 'cleanup-config'", description =>
"Requires additionally 'Datastore.Allocate' on /storage when setting 'cleanup-config'",
check => ['perm', '/', ['Sys.Modify']], check => ['perm', '/', ['Sys.Modify']],
}, },
description => "Unmounts the storage and removes the mount unit.", description => "Unmounts the storage and removes the mount unit.",
@ -330,8 +349,9 @@ __PACKAGE__->register_method ({
node => get_standard_option('pve-node'), node => get_standard_option('pve-node'),
name => get_standard_option('pve-storage-id'), name => get_standard_option('pve-storage-id'),
'cleanup-config' => { 'cleanup-config' => {
description => "Marks associated storage(s) as not available on this node anymore ". description =>
"or removes them from the configuration (if configured for this node only).", "Marks associated storage(s) as not available on this node anymore "
. "or removes them from the configuration (if configured for this node only).",
type => 'boolean', type => 'boolean',
optional => 1, optional => 1,
default => 0, default => 0,
@ -380,7 +400,9 @@ __PACKAGE__->register_method ({
run_command(['systemctl', 'stop', $mountunitname]); run_command(['systemctl', 'stop', $mountunitname]);
run_command(['systemctl', 'disable', $mountunitname]); run_command(['systemctl', 'disable', $mountunitname]);
unlink $mountunitpath or $! == ENOENT or die "cannot remove $mountunitpath - $!\n"; unlink $mountunitpath
or $! == ENOENT
or die "cannot remove $mountunitpath - $!\n";
my $config_err; my $config_err;
if ($param->{'cleanup-config'}) { if ($param->{'cleanup-config'}) {
@ -388,7 +410,9 @@ __PACKAGE__->register_method ({
my ($scfg) = @_; my ($scfg) = @_;
return $scfg->{type} eq 'dir' && $scfg->{path} eq $path; return $scfg->{type} eq 'dir' && $scfg->{path} eq $path;
}; };
eval { PVE::API2::Storage::Config->cleanup_storages_for_node($match, $node); }; eval {
PVE::API2::Storage::Config->cleanup_storages_for_node($match, $node);
};
warn $config_err = $@ if $@; warn $config_err = $@ if $@;
} }
@ -402,6 +426,7 @@ __PACKAGE__->register_method ({
}; };
return $rpcenv->fork_worker('dirremove', $name, $user, $worker); return $rpcenv->fork_worker('dirremove', $name, $user, $worker);
}}); },
});
1; 1;

View File

@ -72,7 +72,8 @@ __PACKAGE__->register_method ({
}, },
size => { size => {
type => 'integer', type => 'integer',
description => 'The size of the physical volume in bytes', description =>
'The size of the physical volume in bytes',
}, },
free => { free => {
type => 'integer', type => 'integer',
@ -108,7 +109,8 @@ __PACKAGE__->register_method ({
leaf => 0, leaf => 0,
children => $result, children => $result,
}; };
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'create', name => 'create',
@ -117,7 +119,8 @@ __PACKAGE__->register_method ({
proxyto => 'node', proxyto => 'node',
protected => 1, protected => 1,
permissions => { permissions => {
description => "Requires additionally 'Datastore.Allocate' on /storage when setting 'add_storage'", description =>
"Requires additionally 'Datastore.Allocate' on /storage when setting 'add_storage'",
check => ['perm', '/', ['Sys.Modify']], check => ['perm', '/', ['Sys.Modify']],
}, },
description => "Create an LVM Volume Group", description => "Create an LVM Volume Group",
@ -167,7 +170,8 @@ __PACKAGE__->register_method ({
# reserve the name and add as disabled, will be enabled below if creation works out # reserve the name and add as disabled, will be enabled below if creation works out
PVE::API2::Storage::Config->create_or_update( PVE::API2::Storage::Config->create_or_update(
$name, $node, $storage_params, $verify_params, 1); $name, $node, $storage_params, $verify_params, 1,
);
} }
my $worker = sub { my $worker = sub {
@ -187,13 +191,15 @@ __PACKAGE__->register_method ({
if ($param->{add_storage}) { if ($param->{add_storage}) {
PVE::API2::Storage::Config->create_or_update( PVE::API2::Storage::Config->create_or_update(
$name, $node, $storage_params, $verify_params); $name, $node, $storage_params, $verify_params,
);
} }
}); });
}; };
return $rpcenv->fork_worker('lvmcreate', $name, $user, $worker); return $rpcenv->fork_worker('lvmcreate', $name, $user, $worker);
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'delete', name => 'delete',
@ -202,7 +208,8 @@ __PACKAGE__->register_method ({
proxyto => 'node', proxyto => 'node',
protected => 1, protected => 1,
permissions => { permissions => {
description => "Requires additionally 'Datastore.Allocate' on /storage when setting 'cleanup-config'", description =>
"Requires additionally 'Datastore.Allocate' on /storage when setting 'cleanup-config'",
check => ['perm', '/', ['Sys.Modify']], check => ['perm', '/', ['Sys.Modify']],
}, },
description => "Remove an LVM Volume Group.", description => "Remove an LVM Volume Group.",
@ -212,8 +219,9 @@ __PACKAGE__->register_method ({
node => get_standard_option('pve-node'), node => get_standard_option('pve-node'),
name => get_standard_option('pve-storage-id'), name => get_standard_option('pve-storage-id'),
'cleanup-config' => { 'cleanup-config' => {
description => "Marks associated storage(s) as not available on this node anymore ". description =>
"or removes them from the configuration (if configured for this node only).", "Marks associated storage(s) as not available on this node anymore "
. "or removes them from the configuration (if configured for this node only).",
type => 'boolean', type => 'boolean',
optional => 1, optional => 1,
default => 0, default => 0,
@ -251,7 +259,9 @@ __PACKAGE__->register_method ({
my ($scfg) = @_; my ($scfg) = @_;
return $scfg->{type} eq 'lvm' && $scfg->{vgname} eq $name; return $scfg->{type} eq 'lvm' && $scfg->{vgname} eq $name;
}; };
eval { PVE::API2::Storage::Config->cleanup_storages_for_node($match, $node); }; eval {
PVE::API2::Storage::Config->cleanup_storages_for_node($match, $node);
};
warn $config_err = $@ if $@; warn $config_err = $@ if $@;
} }
@ -274,6 +284,7 @@ __PACKAGE__->register_method ({
}; };
return $rpcenv->fork_worker('lvmremove', $name, $user, $worker); return $rpcenv->fork_worker('lvmremove', $name, $user, $worker);
}}); },
});
1; 1;

View File

@ -66,7 +66,8 @@ __PACKAGE__->register_method ({
code => sub { code => sub {
my ($param) = @_; my ($param) = @_;
return PVE::Storage::LvmThinPlugin::list_thinpools(undef); return PVE::Storage::LvmThinPlugin::list_thinpools(undef);
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'create', name => 'create',
@ -75,7 +76,8 @@ __PACKAGE__->register_method ({
proxyto => 'node', proxyto => 'node',
protected => 1, protected => 1,
permissions => { permissions => {
description => "Requires additionally 'Datastore.Allocate' on /storage when setting 'add_storage'", description =>
"Requires additionally 'Datastore.Allocate' on /storage when setting 'add_storage'",
check => ['perm', '/', ['Sys.Modify']], check => ['perm', '/', ['Sys.Modify']],
}, },
description => "Create an LVM thinpool", description => "Create an LVM thinpool",
@ -125,7 +127,8 @@ __PACKAGE__->register_method ({
# reserve the name and add as disabled, will be enabled below if creation works out # reserve the name and add as disabled, will be enabled below if creation works out
PVE::API2::Storage::Config->create_or_update( PVE::API2::Storage::Config->create_or_update(
$name, $node, $storage_params, $verify_params, 1); $name, $node, $storage_params, $verify_params, 1,
);
} }
my $worker = sub { my $worker = sub {
@ -155,24 +158,29 @@ __PACKAGE__->register_method ({
run_command([ run_command([
'/sbin/lvcreate', '/sbin/lvcreate',
'--type', 'thin-pool', '--type',
'thin-pool',
"-L${datasize}K", "-L${datasize}K",
'--poolmetadatasize', "${metadatasize}K", '--poolmetadatasize',
'-n', $name, "${metadatasize}K",
$name '-n',
$name,
$name,
]); ]);
PVE::Diskmanage::udevadm_trigger($dev); PVE::Diskmanage::udevadm_trigger($dev);
if ($param->{add_storage}) { if ($param->{add_storage}) {
PVE::API2::Storage::Config->create_or_update( PVE::API2::Storage::Config->create_or_update(
$name, $node, $storage_params, $verify_params); $name, $node, $storage_params, $verify_params,
);
} }
}); });
}; };
return $rpcenv->fork_worker('lvmthincreate', $name, $user, $worker); return $rpcenv->fork_worker('lvmthincreate', $name, $user, $worker);
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'delete', name => 'delete',
@ -181,7 +189,8 @@ __PACKAGE__->register_method ({
proxyto => 'node', proxyto => 'node',
protected => 1, protected => 1,
permissions => { permissions => {
description => "Requires additionally 'Datastore.Allocate' on /storage when setting 'cleanup-config'", description =>
"Requires additionally 'Datastore.Allocate' on /storage when setting 'cleanup-config'",
check => ['perm', '/', ['Sys.Modify']], check => ['perm', '/', ['Sys.Modify']],
}, },
description => "Remove an LVM thin pool.", description => "Remove an LVM thin pool.",
@ -192,8 +201,9 @@ __PACKAGE__->register_method ({
name => get_standard_option('pve-storage-id'), name => get_standard_option('pve-storage-id'),
'volume-group' => get_standard_option('pve-storage-id'), 'volume-group' => get_standard_option('pve-storage-id'),
'cleanup-config' => { 'cleanup-config' => {
description => "Marks associated storage(s) as not available on this node anymore ". description =>
"or removes them from the configuration (if configured for this node only).", "Marks associated storage(s) as not available on this node anymore "
. "or removes them from the configuration (if configured for this node only).",
type => 'boolean', type => 'boolean',
optional => 1, optional => 1,
default => 0, default => 0,
@ -232,11 +242,14 @@ __PACKAGE__->register_method ({
if ($param->{'cleanup-config'}) { if ($param->{'cleanup-config'}) {
my $match = sub { my $match = sub {
my ($scfg) = @_; my ($scfg) = @_;
return $scfg->{type} eq 'lvmthin' return
$scfg->{type} eq 'lvmthin'
&& $scfg->{vgname} eq $vg && $scfg->{vgname} eq $vg
&& $scfg->{thinpool} eq $lv; && $scfg->{thinpool} eq $lv;
}; };
eval { PVE::API2::Storage::Config->cleanup_storages_for_node($match, $node); }; eval {
PVE::API2::Storage::Config->cleanup_storages_for_node($match, $node);
};
warn $config_err = $@ if $@; warn $config_err = $@ if $@;
} }
@ -264,6 +277,7 @@ __PACKAGE__->register_method ({
}; };
return $rpcenv->fork_worker('lvmthinremove', "${vg}-${lv}", $user, $worker); return $rpcenv->fork_worker('lvmthinremove', "${vg}-${lv}", $user, $worker);
}}); },
});
1; 1;

View File

@ -31,7 +31,9 @@ sub get_pool_data {
}; };
my $pools = []; my $pools = [];
run_command([$ZPOOL, 'list', '-HpPLo', join(',', @$propnames)], outfunc => sub { run_command(
[$ZPOOL, 'list', '-HpPLo', join(',', @$propnames)],
outfunc => sub {
my ($line) = @_; my ($line) = @_;
my @props = split('\s+', trim($line)); my @props = split('\s+', trim($line));
@ -45,7 +47,8 @@ sub get_pool_data {
} }
push @$pools, $pool; push @$pools, $pool;
}); },
);
return $pools; return $pools;
} }
@ -107,7 +110,8 @@ __PACKAGE__->register_method ({
my ($param) = @_; my ($param) = @_;
return get_pool_data(); return get_pool_data();
}}); },
});
sub preparetree { sub preparetree {
my ($el) = @_; my ($el) = @_;
@ -122,7 +126,6 @@ sub preparetree {
} }
} }
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'detail', name => 'detail',
path => '{name}', path => '{name}',
@ -172,7 +175,8 @@ __PACKAGE__->register_method ({
}, },
children => { children => {
type => 'array', type => 'array',
description => "The pool configuration information, including the vdevs for each section (e.g. spares, cache), may be nested.", description =>
"The pool configuration information, including the vdevs for each section (e.g. spares, cache), may be nested.",
items => { items => {
type => 'object', type => 'object',
properties => { properties => {
@ -199,8 +203,8 @@ __PACKAGE__->register_method ({
}, },
msg => { msg => {
type => 'string', type => 'string',
description => 'An optional message about the vdev.' description => 'An optional message about the vdev.',
} },
}, },
}, },
}, },
@ -225,7 +229,9 @@ __PACKAGE__->register_method ({
my $stack = [$pool]; my $stack = [$pool];
my $curlvl = 0; my $curlvl = 0;
run_command($cmd, outfunc => sub { run_command(
$cmd,
outfunc => sub {
my ($line) = @_; my ($line) = @_;
if ($line =~ m/^\s*(\S+): (\S+.*)$/) { if ($line =~ m/^\s*(\S+): (\S+.*)$/) {
@ -237,8 +243,12 @@ __PACKAGE__->register_method ({
$pool->{$curfield} .= " " . $1; $pool->{$curfield} .= " " . $1;
} elsif (!$config && $line =~ m/^\s*config:/) { } elsif (!$config && $line =~ m/^\s*config:/) {
$config = 1; $config = 1;
} elsif ($config && $line =~ m/^(\s+)(\S+)\s*(\S+)?(?:\s+(\S+)\s+(\S+)\s+(\S+))?\s*(.*)$/) { } elsif (
my ($space, $name, $state, $read, $write, $cksum, $msg) = ($1, $2, $3, $4, $5, $6, $7); $config
&& $line =~ m/^(\s+)(\S+)\s*(\S+)?(?:\s+(\S+)\s+(\S+)\s+(\S+))?\s*(.*)$/
) {
my ($space, $name, $state, $read, $write, $cksum, $msg) =
($1, $2, $3, $4, $5, $6, $7);
if ($name ne "NAME") { if ($name ne "NAME") {
my $lvl = int(length($space) / 2) + 1; # two spaces per level my $lvl = int(length($space) / 2) + 1; # two spaces per level
my $vdev = { my $vdev = {
@ -271,14 +281,16 @@ __PACKAGE__->register_method ({
$curlvl = $lvl; $curlvl = $lvl;
} }
} }
}); },
);
# change treenodes for extjs tree # change treenodes for extjs tree
$pool->{name} = delete $pool->{pool}; $pool->{name} = delete $pool->{pool};
preparetree($pool); preparetree($pool);
return $pool; return $pool;
}}); },
});
my $draid_config_format = { my $draid_config_format = {
spares => { spares => {
@ -300,7 +312,8 @@ __PACKAGE__->register_method ({
proxyto => 'node', proxyto => 'node',
protected => 1, protected => 1,
permissions => { permissions => {
description => "Requires additionally 'Datastore.Allocate' on /storage when setting 'add_storage'", description =>
"Requires additionally 'Datastore.Allocate' on /storage when setting 'add_storage'",
check => ['perm', '/', ['Sys.Modify']], check => ['perm', '/', ['Sys.Modify']],
}, },
description => "Create a ZFS pool.", description => "Create a ZFS pool.",
@ -313,13 +326,20 @@ __PACKAGE__->register_method ({
type => 'string', type => 'string',
description => 'The RAID level to use.', description => 'The RAID level to use.',
enum => [ enum => [
'single', 'mirror', 'single',
'raid10', 'raidz', 'raidz2', 'raidz3', 'mirror',
'draid', 'draid2', 'draid3', 'raid10',
'raidz',
'raidz2',
'raidz3',
'draid',
'draid2',
'draid3',
], ],
}, },
devices => { devices => {
type => 'string', format => 'string-list', type => 'string',
format => 'string-list',
description => 'The block devices you want to create the zpool on.', description => 'The block devices you want to create the zpool on.',
}, },
'draid-config' => { 'draid-config' => {
@ -366,7 +386,8 @@ __PACKAGE__->register_method ({
my $draid_config; my $draid_config;
if (exists $param->{'draid-config'}) { if (exists $param->{'draid-config'}) {
die "draid-config set without using dRAID level\n" if $raidlevel !~ m/^draid/; die "draid-config set without using dRAID level\n" if $raidlevel !~ m/^draid/;
$draid_config = parse_property_string($draid_config_format, $param->{'draid-config'}); $draid_config =
parse_property_string($draid_config_format, $param->{'draid-config'});
} }
for my $dev (@$devs) { for my $dev (@$devs) {
@ -388,7 +409,8 @@ __PACKAGE__->register_method ({
# reserve the name and add as disabled, will be enabled below if creation works out # reserve the name and add as disabled, will be enabled below if creation works out
PVE::API2::Storage::Config->create_or_update( PVE::API2::Storage::Config->create_or_update(
$name, $node, $storage_params, $verify_params, 1); $name, $node, $storage_params, $verify_params, 1,
);
} }
my $pools = get_pool_data(); my $pools = get_pool_data();
@ -439,7 +461,10 @@ __PACKAGE__->register_method ({
if ($is_partition) { if ($is_partition) {
eval { eval {
PVE::Diskmanage::change_parttype($dev, '6a898cc3-1dd2-11b2-99a6-080020736631'); PVE::Diskmanage::change_parttype(
$dev,
'6a898cc3-1dd2-11b2-99a6-080020736631',
);
}; };
warn $@ if $@; warn $@ if $@;
} }
@ -484,7 +509,8 @@ __PACKAGE__->register_method ({
run_command($cmd); run_command($cmd);
if (-e '/lib/systemd/system/zfs-import@.service') { if (-e '/lib/systemd/system/zfs-import@.service') {
my $importunit = 'zfs-import@'. PVE::Systemd::escape_unit($name, undef) . '.service'; my $importunit =
'zfs-import@' . PVE::Systemd::escape_unit($name, undef) . '.service';
$cmd = ['systemctl', 'enable', $importunit]; $cmd = ['systemctl', 'enable', $importunit];
print "# ", join(' ', @$cmd), "\n"; print "# ", join(' ', @$cmd), "\n";
run_command($cmd); run_command($cmd);
@ -494,14 +520,21 @@ __PACKAGE__->register_method ({
if ($param->{add_storage}) { if ($param->{add_storage}) {
PVE::API2::Storage::Config->create_or_update( PVE::API2::Storage::Config->create_or_update(
$name, $node, $storage_params, $verify_params); $name, $node, $storage_params, $verify_params,
);
} }
}; };
return $rpcenv->fork_worker('zfscreate', $name, $user, sub { return $rpcenv->fork_worker(
'zfscreate',
$name,
$user,
sub {
PVE::Diskmanage::locked_disk_action($code); PVE::Diskmanage::locked_disk_action($code);
},
);
},
}); });
}});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'delete', name => 'delete',
@ -510,7 +543,8 @@ __PACKAGE__->register_method ({
proxyto => 'node', proxyto => 'node',
protected => 1, protected => 1,
permissions => { permissions => {
description => "Requires additionally 'Datastore.Allocate' on /storage when setting 'cleanup-config'", description =>
"Requires additionally 'Datastore.Allocate' on /storage when setting 'cleanup-config'",
check => ['perm', '/', ['Sys.Modify']], check => ['perm', '/', ['Sys.Modify']],
}, },
description => "Destroy a ZFS pool.", description => "Destroy a ZFS pool.",
@ -520,8 +554,9 @@ __PACKAGE__->register_method ({
node => get_standard_option('pve-node'), node => get_standard_option('pve-node'),
name => get_standard_option('pve-storage-id'), name => get_standard_option('pve-storage-id'),
'cleanup-config' => { 'cleanup-config' => {
description => "Marks associated storage(s) as not available on this node anymore ". description =>
"or removes them from the configuration (if configured for this node only).", "Marks associated storage(s) as not available on this node anymore "
. "or removes them from the configuration (if configured for this node only).",
type => 'boolean', type => 'boolean',
optional => 1, optional => 1,
default => 0, default => 0,
@ -551,7 +586,9 @@ __PACKAGE__->register_method ({
my $to_wipe = []; my $to_wipe = [];
if ($param->{'cleanup-disks'}) { if ($param->{'cleanup-disks'}) {
# Using -o name does not only output the name in combination with -v. # Using -o name does not only output the name in combination with -v.
run_command(['zpool', 'list', '-vHPL', $name], outfunc => sub { run_command(
['zpool', 'list', '-vHPL', $name],
outfunc => sub {
my ($line) = @_; my ($line) = @_;
my ($name) = PVE::Tools::split_list($line); my ($name) = PVE::Tools::split_list($line);
@ -562,7 +599,8 @@ __PACKAGE__->register_method ({
$dev =~ s|^/dev/||; $dev =~ s|^/dev/||;
my $info = PVE::Diskmanage::get_disks($dev, 1, 1); my $info = PVE::Diskmanage::get_disks($dev, 1, 1);
die "unable to obtain information for disk '$dev'\n" if !$info->{$dev}; die "unable to obtain information for disk '$dev'\n"
if !$info->{$dev};
# Wipe whole disk if usual ZFS layout with partition 9 as ZFS reserved. # Wipe whole disk if usual ZFS layout with partition 9 as ZFS reserved.
my $parent = $info->{$dev}->{parent}; my $parent = $info->{$dev}->{parent};
@ -571,15 +609,19 @@ __PACKAGE__->register_method ({
my $info9 = $info->{"${parent}9"}; my $info9 = $info->{"${parent}9"};
$wipe = $info->{$dev}->{parent} # need leading /dev/ $wipe = $info->{$dev}->{parent} # need leading /dev/
if $info9 && $info9->{used} && $info9->{used} =~ m/^ZFS reserved/; if $info9
&& $info9->{used}
&& $info9->{used} =~ m/^ZFS reserved/;
} }
push $to_wipe->@*, $wipe; push $to_wipe->@*, $wipe;
}); },
);
} }
if (-e '/lib/systemd/system/zfs-import@.service') { if (-e '/lib/systemd/system/zfs-import@.service') {
my $importunit = 'zfs-import@' . PVE::Systemd::escape_unit($name) . '.service'; my $importunit =
'zfs-import@' . PVE::Systemd::escape_unit($name) . '.service';
run_command(['systemctl', 'disable', $importunit]); run_command(['systemctl', 'disable', $importunit]);
} }
@ -591,7 +633,9 @@ __PACKAGE__->register_method ({
my ($scfg) = @_; my ($scfg) = @_;
return $scfg->{type} eq 'zfspool' && $scfg->{pool} eq $name; return $scfg->{type} eq 'zfspool' && $scfg->{pool} eq $name;
}; };
eval { PVE::API2::Storage::Config->cleanup_storages_for_node($match, $node); }; eval {
PVE::API2::Storage::Config->cleanup_storages_for_node($match, $node);
};
warn $config_err = $@ if $@; warn $config_err = $@ if $@;
} }
@ -605,6 +649,7 @@ __PACKAGE__->register_method ({
}; };
return $rpcenv->fork_worker('zfsremove', $name, $user, $worker); return $rpcenv->fork_worker('zfsremove', $name, $user, $worker);
}}); },
});
1; 1;

View File

@ -29,10 +29,12 @@ my $api_storage_config = sub {
my $scfg = dclone(PVE::Storage::storage_config($cfg, $storeid)); my $scfg = dclone(PVE::Storage::storage_config($cfg, $storeid));
$scfg->{storage} = $storeid; $scfg->{storage} = $storeid;
$scfg->{digest} = $cfg->{digest}; $scfg->{digest} = $cfg->{digest};
$scfg->{content} = PVE::Storage::Plugin->encode_value($scfg->{type}, 'content', $scfg->{content}); $scfg->{content} =
PVE::Storage::Plugin->encode_value($scfg->{type}, 'content', $scfg->{content});
if ($scfg->{nodes}) { if ($scfg->{nodes}) {
$scfg->{nodes} = PVE::Storage::Plugin->encode_value($scfg->{type}, 'nodes', $scfg->{nodes}); $scfg->{nodes} =
PVE::Storage::Plugin->encode_value($scfg->{type}, 'nodes', $scfg->{nodes});
} }
return $scfg; return $scfg;
@ -122,7 +124,8 @@ __PACKAGE__->register_method ({
method => 'GET', method => 'GET',
description => "Storage index.", description => "Storage index.",
permissions => { permissions => {
description => "Only list entries where you have 'Datastore.Audit' or 'Datastore.AllocateSpace' permissions on '/storage/<storage>'", description =>
"Only list entries where you have 'Datastore.Audit' or 'Datastore.AllocateSpace' permissions on '/storage/<storage>'",
user => 'all', user => 'all',
}, },
parameters => { parameters => {
@ -165,7 +168,8 @@ __PACKAGE__->register_method ({
} }
return $res; return $res;
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'read', name => 'read',
@ -188,7 +192,8 @@ __PACKAGE__->register_method ({
my $cfg = PVE::Storage::config(); my $cfg = PVE::Storage::config();
return &$api_storage_config($cfg, $param->{storage}); return &$api_storage_config($cfg, $param->{storage});
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'create', name => 'create',
@ -244,7 +249,8 @@ __PACKAGE__->register_method ({
my $opts = $plugin->check_config($storeid, $param, 1, 1); my $opts = $plugin->check_config($storeid, $param, 1, 1);
my $returned_config; my $returned_config;
PVE::Storage::lock_storage_config(sub { PVE::Storage::lock_storage_config(
sub {
my $cfg = PVE::Storage::config(); my $cfg = PVE::Storage::config();
if (my $scfg = PVE::Storage::storage_config($cfg, $storeid, 1)) { if (my $scfg = PVE::Storage::storage_config($cfg, $storeid, 1)) {
@ -256,8 +262,9 @@ __PACKAGE__->register_method ({
$returned_config = $plugin->on_add_hook($storeid, $opts, %$sensitive); $returned_config = $plugin->on_add_hook($storeid, $opts, %$sensitive);
if (defined($opts->{mkdir})) { # TODO: remove complete option in Proxmox VE 9 if (defined($opts->{mkdir})) { # TODO: remove complete option in Proxmox VE 9
warn "NOTE: The 'mkdir' option set for '${storeid}' is deprecated and will be removed" warn
." in Proxmox VE 9. Use 'create-base-path' or 'create-subdirs' instead.\n" "NOTE: The 'mkdir' option set for '${storeid}' is deprecated and will be removed"
. " in Proxmox VE 9. Use 'create-base-path' or 'create-subdirs' instead.\n";
} }
eval { eval {
@ -275,7 +282,9 @@ __PACKAGE__->register_method ({
PVE::Storage::write_config($cfg); PVE::Storage::write_config($cfg);
}, "create storage failed"); },
"create storage failed",
);
my $res = { my $res = {
storage => $storeid, storage => $storeid,
@ -283,7 +292,8 @@ __PACKAGE__->register_method ({
}; };
$res->{config} = $returned_config if $returned_config; $res->{config} = $returned_config if $returned_config;
return $res; return $res;
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'update', name => 'update',
@ -335,7 +345,8 @@ __PACKAGE__->register_method ({
} }
my $returned_config; my $returned_config;
PVE::Storage::lock_storage_config(sub { PVE::Storage::lock_storage_config(
sub {
my $cfg = PVE::Storage::config(); my $cfg = PVE::Storage::config();
PVE::SectionConfig::assert_if_modified($cfg, $digest); PVE::SectionConfig::assert_if_modified($cfg, $digest);
@ -369,13 +380,16 @@ __PACKAGE__->register_method ({
} }
if (defined($scfg->{mkdir})) { # TODO: remove complete option in Proxmox VE 9 if (defined($scfg->{mkdir})) { # TODO: remove complete option in Proxmox VE 9
warn "NOTE: The 'mkdir' option set for '${storeid}' is deprecated and will be removed" warn
." in Proxmox VE 9. Use 'create-base-path' or 'create-subdirs' instead.\n" "NOTE: The 'mkdir' option set for '${storeid}' is deprecated and will be removed"
. " in Proxmox VE 9. Use 'create-base-path' or 'create-subdirs' instead.\n";
} }
PVE::Storage::write_config($cfg); PVE::Storage::write_config($cfg);
}, "update storage failed"); },
"update storage failed",
);
my $res = { my $res = {
storage => $storeid, storage => $storeid,
@ -383,7 +397,8 @@ __PACKAGE__->register_method ({
}; };
$res->{config} = $returned_config if $returned_config; $res->{config} = $returned_config if $returned_config;
return $res; return $res;
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'delete', name => 'delete',
@ -397,9 +412,12 @@ __PACKAGE__->register_method ({
parameters => { parameters => {
additionalProperties => 0, additionalProperties => 0,
properties => { properties => {
storage => get_standard_option('pve-storage-id', { storage => get_standard_option(
'pve-storage-id',
{
completion => \&PVE::Storage::complete_storage, completion => \&PVE::Storage::complete_storage,
}), },
),
}, },
}, },
returns => { type => 'null' }, returns => { type => 'null' },
@ -408,7 +426,8 @@ __PACKAGE__->register_method ({
my $storeid = extract_param($param, 'storage'); my $storeid = extract_param($param, 'storage');
PVE::Storage::lock_storage_config(sub { PVE::Storage::lock_storage_config(
sub {
my $cfg = PVE::Storage::config(); my $cfg = PVE::Storage::config();
my $scfg = PVE::Storage::storage_config($cfg, $storeid); my $scfg = PVE::Storage::storage_config($cfg, $storeid);
@ -424,11 +443,14 @@ __PACKAGE__->register_method ({
PVE::Storage::write_config($cfg); PVE::Storage::write_config($cfg);
}, "delete storage failed"); },
"delete storage failed",
);
PVE::AccessControl::remove_storage_access($storeid); PVE::AccessControl::remove_storage_access($storeid);
return undef; return undef;
}}); },
});
1; 1;

View File

@ -22,7 +22,12 @@ __PACKAGE__->register_method ({
method => 'GET', method => 'GET',
description => "List storage content.", description => "List storage content.",
permissions => { permissions => {
check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1], check => [
'perm',
'/storage/{storage}',
['Datastore.Audit', 'Datastore.AllocateSpace'],
any => 1,
],
}, },
protected => 1, protected => 1,
proxyto => 'node', proxyto => 'node',
@ -30,20 +35,27 @@ __PACKAGE__->register_method ({
additionalProperties => 0, additionalProperties => 0,
properties => { properties => {
node => get_standard_option('pve-node'), node => get_standard_option('pve-node'),
storage => get_standard_option('pve-storage-id', { storage => get_standard_option(
'pve-storage-id',
{
completion => \&PVE::Storage::complete_storage_enabled, completion => \&PVE::Storage::complete_storage_enabled,
}), },
),
content => { content => {
description => "Only list content of this type.", description => "Only list content of this type.",
type => 'string', format => 'pve-storage-content', type => 'string',
format => 'pve-storage-content',
optional => 1, optional => 1,
completion => \&PVE::Storage::complete_content_type, completion => \&PVE::Storage::complete_content_type,
}, },
vmid => get_standard_option('pve-vmid', { vmid => get_standard_option(
'pve-vmid',
{
description => "Only list images for this VM", description => "Only list images for this VM",
optional => 1, optional => 1,
completion => \&PVE::Cluster::complete_vmid, completion => \&PVE::Cluster::complete_vmid,
}), },
),
}, },
}, },
returns => { returns => {
@ -66,7 +78,8 @@ __PACKAGE__->register_method ({
optional => 1, optional => 1,
}, },
'format' => { 'format' => {
description => "Format identifier ('raw', 'qcow2', 'subvol', 'iso', 'tgz' ...)", description =>
"Format identifier ('raw', 'qcow2', 'subvol', 'iso', 'tgz' ...)",
type => 'string', type => 'string',
}, },
size => { size => {
@ -75,8 +88,8 @@ __PACKAGE__->register_method ({
renderer => 'bytes', renderer => 'bytes',
}, },
used => { used => {
description => "Used space. Please note that most storage plugins " . description => "Used space. Please note that most storage plugins "
"do not report anything useful here.", . "do not report anything useful here.",
type => 'integer', type => 'integer',
renderer => 'bytes', renderer => 'bytes',
optional => 1, optional => 1,
@ -88,18 +101,21 @@ __PACKAGE__->register_method ({
optional => 1, optional => 1,
}, },
notes => { notes => {
description => "Optional notes. If they contain multiple lines, only the first one is returned here.", description =>
"Optional notes. If they contain multiple lines, only the first one is returned here.",
type => 'string', type => 'string',
optional => 1, optional => 1,
}, },
encrypted => { encrypted => {
description => "If whole backup is encrypted, value is the fingerprint or '1' " description =>
"If whole backup is encrypted, value is the fingerprint or '1' "
. " if encrypted. Only useful for the Proxmox Backup Server storage type.", . " if encrypted. Only useful for the Proxmox Backup Server storage type.",
type => 'string', type => 'string',
optional => 1, optional => 1,
}, },
verification => { verification => {
description => "Last backup verification result, only useful for PBS storages.", description =>
"Last backup verification result, only useful for PBS storages.",
type => 'object', type => 'object',
properties => { properties => {
state => { state => {
@ -133,11 +149,16 @@ __PACKAGE__->register_method ({
my $cfg = PVE::Storage::config(); my $cfg = PVE::Storage::config();
my $vollist = PVE::Storage::volume_list($cfg, $storeid, $param->{vmid}, $param->{content}); my $vollist =
PVE::Storage::volume_list($cfg, $storeid, $param->{vmid}, $param->{content});
my $res = []; my $res = [];
foreach my $item (@$vollist) { foreach my $item (@$vollist) {
eval { PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $item->{volid}); }; eval {
PVE::Storage::check_volume_access(
$rpcenv, $authuser, $cfg, undef, $item->{volid},
);
};
next if $@; next if $@;
$item->{vmid} = int($item->{vmid}) if defined($item->{vmid}); $item->{vmid} = int($item->{vmid}) if defined($item->{vmid});
$item->{size} = int($item->{size}) if defined($item->{size}); $item->{size} = int($item->{size}) if defined($item->{size});
@ -146,7 +167,8 @@ __PACKAGE__->register_method ({
} }
return $res; return $res;
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'create', name => 'create',
@ -162,26 +184,36 @@ __PACKAGE__->register_method ({
additionalProperties => 0, additionalProperties => 0,
properties => { properties => {
node => get_standard_option('pve-node'), node => get_standard_option('pve-node'),
storage => get_standard_option('pve-storage-id', { storage => get_standard_option(
'pve-storage-id',
{
completion => \&PVE::Storage::complete_storage_enabled, completion => \&PVE::Storage::complete_storage_enabled,
}), },
),
filename => { filename => {
description => "The name of the file to create.", description => "The name of the file to create.",
type => 'string', type => 'string',
}, },
vmid => get_standard_option('pve-vmid', { vmid => get_standard_option(
'pve-vmid',
{
description => "Specify owner VM", description => "Specify owner VM",
completion => \&PVE::Cluster::complete_vmid, completion => \&PVE::Cluster::complete_vmid,
}), },
),
size => { size => {
description => "Size in kilobyte (1024 bytes). Optional suffixes 'M' (megabyte, 1024K) and 'G' (gigabyte, 1024M)", description =>
"Size in kilobyte (1024 bytes). Optional suffixes 'M' (megabyte, 1024K) and 'G' (gigabyte, 1024M)",
type => 'string', type => 'string',
pattern => '\d+[MG]?', pattern => '\d+[MG]?',
}, },
format => get_standard_option('pve-storage-image-format', { format => get_standard_option(
'pve-storage-image-format',
{
requires => 'size', requires => 'size',
optional => 1, optional => 1,
}), },
),
}, },
}, },
returns => { returns => {
@ -210,7 +242,8 @@ __PACKAGE__->register_method ({
if ($name =~ m/\.(raw|qcow2|vmdk)$/) { if ($name =~ m/\.(raw|qcow2|vmdk)$/) {
my $fmt = $1; my $fmt = $1;
raise_param_exc({ format => "different storage formats ($param->{format} != $fmt)" }) raise_param_exc({
format => "different storage formats ($param->{format} != $fmt)" })
if $param->{format} && $param->{format} ne $fmt; if $param->{format} && $param->{format} ne $fmt;
$param->{format} = $fmt; $param->{format} = $fmt;
@ -218,12 +251,13 @@ __PACKAGE__->register_method ({
my $cfg = PVE::Storage::config(); my $cfg = PVE::Storage::config();
my $volid = PVE::Storage::vdisk_alloc ($cfg, $storeid, $param->{vmid}, my $volid = PVE::Storage::vdisk_alloc(
$param->{format}, $cfg, $storeid, $param->{vmid}, $param->{format}, $name, $size,
$name, $size); );
return $volid; return $volid;
}}); },
});
# we allow to pass volume names (without storage prefix) if the storage # we allow to pass volume names (without storage prefix) if the storage
# is specified as separate parameter. # is specified as separate parameter.
@ -287,8 +321,8 @@ __PACKAGE__->register_method ({
renderer => 'bytes', renderer => 'bytes',
}, },
used => { used => {
description => "Used space. Please note that most storage plugins " . description => "Used space. Please note that most storage plugins "
"do not report anything useful here.", . "do not report anything useful here.",
type => 'integer', type => 'integer',
renderer => 'bytes', renderer => 'bytes',
}, },
@ -343,7 +377,8 @@ __PACKAGE__->register_method ({
} }
return $entry; return $entry;
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'updateattributes', name => 'updateattributes',
@ -397,7 +432,8 @@ __PACKAGE__->register_method ({
} }
return undef; return undef;
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'delete', name => 'delete',
@ -405,7 +441,8 @@ __PACKAGE__->register_method ({
method => 'DELETE', method => 'DELETE',
description => "Delete volume", description => "Delete volume",
permissions => { permissions => {
description => "You need 'Datastore.Allocate' privilege on the storage (or 'Datastore.AllocateSpace' for backup volumes if you have VM.Backup privilege on the VM).", description =>
"You need 'Datastore.Allocate' privilege on the storage (or 'Datastore.AllocateSpace' for backup volumes if you have VM.Backup privilege on the VM).",
user => 'all', user => 'all',
}, },
protected => 1, protected => 1,
@ -414,10 +451,13 @@ __PACKAGE__->register_method ({
additionalProperties => 0, additionalProperties => 0,
properties => { properties => {
node => get_standard_option('pve-node'), node => get_standard_option('pve-node'),
storage => get_standard_option('pve-storage-id', { storage => get_standard_option(
'pve-storage-id',
{
optional => 1, optional => 1,
completion => \&PVE::Storage::complete_storage, completion => \&PVE::Storage::complete_storage,
}), },
),
volume => { volume => {
description => "Volume identifier", description => "Volume identifier",
type => 'string', type => 'string',
@ -425,14 +465,15 @@ __PACKAGE__->register_method ({
}, },
delay => { delay => {
type => 'integer', type => 'integer',
description => "Time to wait for the task to finish. We return 'null' if the task finish within that time.", description =>
"Time to wait for the task to finish. We return 'null' if the task finish within that time.",
minimum => 1, minimum => 1,
maximum => 30, maximum => 30,
optional => 1, optional => 1,
}, },
}, },
}, },
returns => { type => 'string', optional => 1, }, returns => { type => 'string', optional => 1 },
code => sub { code => sub {
my ($param) = @_; my ($param) = @_;
@ -454,8 +495,10 @@ __PACKAGE__->register_method ({
my $worker = sub { my $worker = sub {
PVE::Storage::vdisk_free($cfg, $volid); PVE::Storage::vdisk_free($cfg, $volid);
print "Removed volume '$volid'\n"; print "Removed volume '$volid'\n";
if ($vtype eq 'backup' if (
&& $path =~ /(.*\/vzdump-\w+-\d+-\d{4}_\d{2}_\d{2}-\d{2}_\d{2}_\d{2})[^\/]+$/) { $vtype eq 'backup'
&& $path =~ /(.*\/vzdump-\w+-\d+-\d{4}_\d{2}_\d{2}-\d{2}_\d{2}_\d{2})[^\/]+$/
) {
# Remove log file #318 and notes file #3972 if they still exist # Remove log file #318 and notes file #3972 if they still exist
PVE::Storage::archive_auxiliaries_remove($path); PVE::Storage::archive_auxiliaries_remove($path);
} }
@ -469,7 +512,8 @@ __PACKAGE__->register_method ({
my $currently_deleting; # not necessarily true, e.g. sequential api call from cli my $currently_deleting; # not necessarily true, e.g. sequential api call from cli
do { do {
my $task = PVE::Tools::upid_decode($upid); my $task = PVE::Tools::upid_decode($upid);
$currently_deleting = PVE::ProcFSTools::check_process_running($task->{pid}, $task->{pstart}); $currently_deleting =
PVE::ProcFSTools::check_process_running($task->{pid}, $task->{pstart});
sleep 1 if $currently_deleting; sleep 1 if $currently_deleting;
} while (time() < $end_time && $currently_deleting); } while (time() < $end_time && $currently_deleting);
@ -481,7 +525,8 @@ __PACKAGE__->register_method ({
} }
} }
return $upid; return $upid;
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'copy', name => 'copy',
@ -503,10 +548,13 @@ __PACKAGE__->register_method ({
description => "Target volume identifier", description => "Target volume identifier",
type => 'string', type => 'string',
}, },
target_node => get_standard_option('pve-node', { target_node => get_standard_option(
'pve-node',
{
description => "Target node. Default is local node.", description => "Target node. Default is local node.",
optional => 1, optional => 1,
}), },
),
}, },
}, },
returns => { returns => {
@ -548,13 +596,20 @@ __PACKAGE__->register_method ({
# you need to get this working (fails currently, because storage_migrate() uses # you need to get this working (fails currently, because storage_migrate() uses
# ssh to connect to local host (which is not needed # ssh to connect to local host (which is not needed
my $sshinfo = PVE::SSHInfo::get_ssh_info($target_node); my $sshinfo = PVE::SSHInfo::get_ssh_info($target_node);
PVE::Storage::storage_migrate($cfg, $src_volid, $sshinfo, $target_sid, {'target_volname' => $target_volname}); PVE::Storage::storage_migrate(
$cfg,
$src_volid,
$sshinfo,
$target_sid,
{ 'target_volname' => $target_volname },
);
print "DEBUG: end worker $upid\n"; print "DEBUG: end worker $upid\n";
}; };
return $rpcenv->fork_worker('imgcopy', undef, $user, $worker); return $rpcenv->fork_worker('imgcopy', undef, $user, $worker);
}}); },
});
1; 1;

View File

@ -47,11 +47,15 @@ __PACKAGE__->register_method ({
additionalProperties => 0, additionalProperties => 0,
properties => { properties => {
node => get_standard_option('pve-node'), node => get_standard_option('pve-node'),
storage => get_standard_option('pve-storage-id', { storage => get_standard_option(
'pve-storage-id',
{
completion => \&PVE::Storage::complete_storage_enabled, completion => \&PVE::Storage::complete_storage_enabled,
}), },
),
volume => { volume => {
description => "Backup volume ID or name. Currently only PBS snapshots are supported.", description =>
"Backup volume ID or name. Currently only PBS snapshots are supported.",
type => 'string', type => 'string',
completion => \&PVE::Storage::complete_volume, completion => \&PVE::Storage::complete_volume,
}, },
@ -139,7 +143,8 @@ __PACKAGE__->register_method ({
} }
die "invalid proxmox-file-restore output"; die "invalid proxmox-file-restore output";
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'download', name => 'download',
@ -156,11 +161,15 @@ __PACKAGE__->register_method ({
additionalProperties => 0, additionalProperties => 0,
properties => { properties => {
node => get_standard_option('pve-node'), node => get_standard_option('pve-node'),
storage => get_standard_option('pve-storage-id', { storage => get_standard_option(
'pve-storage-id',
{
completion => \&PVE::Storage::complete_storage_enabled, completion => \&PVE::Storage::complete_storage_enabled,
}), },
),
volume => { volume => {
description => "Backup volume ID or name. Currently only PBS snapshots are supported.", description =>
"Backup volume ID or name. Currently only PBS snapshots are supported.",
type => 'string', type => 'string',
completion => \&PVE::Storage::complete_volume, completion => \&PVE::Storage::complete_volume,
}, },
@ -204,11 +213,16 @@ __PACKAGE__->register_method ({
my $client = PVE::PBSClient->new($scfg, $storeid); my $client = PVE::PBSClient->new($scfg, $storeid);
my $fifo = $client->file_restore_extract_prepare(); my $fifo = $client->file_restore_extract_prepare();
$rpcenv->fork_worker('pbs-download', undef, $user, sub { $rpcenv->fork_worker(
'pbs-download',
undef,
$user,
sub {
my $name = decode_base64($path); my $name = decode_base64($path);
print "Starting download of file: $name\n"; print "Starting download of file: $name\n";
$client->file_restore_extract($fifo, $snap, $path, 1, $tar); $client->file_restore_extract($fifo, $snap, $path, 1, $tar);
}); },
);
my $ret = { my $ret = {
download => { download => {
@ -218,6 +232,7 @@ __PACKAGE__->register_method ({
}, },
}; };
return $ret; return $ret;
}}); },
});
1; 1;

View File

@ -16,10 +16,16 @@ __PACKAGE__->register_method ({
name => 'dryrun', name => 'dryrun',
path => '', path => '',
method => 'GET', method => 'GET',
description => "Get prune information for backups. NOTE: this is only a preview and might not be " . description =>
"what a subsequent prune call does if backups are removed/added in the meantime.", "Get prune information for backups. NOTE: this is only a preview and might not be "
. "what a subsequent prune call does if backups are removed/added in the meantime.",
permissions => { permissions => {
check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1], check => [
'perm',
'/storage/{storage}',
['Datastore.Audit', 'Datastore.AllocateSpace'],
any => 1,
],
}, },
protected => 1, protected => 1,
proxyto => 'node', proxyto => 'node',
@ -27,24 +33,35 @@ __PACKAGE__->register_method ({
additionalProperties => 0, additionalProperties => 0,
properties => { properties => {
node => get_standard_option('pve-node'), node => get_standard_option('pve-node'),
storage => get_standard_option('pve-storage-id', { storage => get_standard_option(
'pve-storage-id',
{
completion => \&PVE::Storage::complete_storage_enabled, completion => \&PVE::Storage::complete_storage_enabled,
}), },
'prune-backups' => get_standard_option('prune-backups', { ),
description => "Use these retention options instead of those from the storage configuration.", 'prune-backups' => get_standard_option(
'prune-backups',
{
description =>
"Use these retention options instead of those from the storage configuration.",
optional => 1, optional => 1,
}), },
),
type => { type => {
description => "Either 'qemu' or 'lxc'. Only consider backups for guests of this type.", description =>
"Either 'qemu' or 'lxc'. Only consider backups for guests of this type.",
type => 'string', type => 'string',
optional => 1, optional => 1,
enum => ['qemu', 'lxc'], enum => ['qemu', 'lxc'],
}, },
vmid => get_standard_option('pve-vmid', { vmid => get_standard_option(
'pve-vmid',
{
description => "Only consider backups for this guest.", description => "Only consider backups for this guest.",
optional => 1, optional => 1,
completion => \&PVE::Cluster::complete_vmid, completion => \&PVE::Cluster::complete_vmid,
}), },
),
}, },
}, },
returns => { returns => {
@ -57,12 +74,14 @@ __PACKAGE__->register_method ({
type => 'string', type => 'string',
}, },
'ctime' => { 'ctime' => {
description => "Creation time of the backup (seconds since the UNIX epoch).", description =>
"Creation time of the backup (seconds since the UNIX epoch).",
type => 'integer', type => 'integer',
}, },
'mark' => { 'mark' => {
description => "Whether the backup would be kept or removed. Backups that are" . description =>
" protected or don't use the standard naming scheme are not removed.", "Whether the backup would be kept or removed. Backups that are"
. " protected or don't use the standard naming scheme are not removed.",
type => 'string', type => 'string',
enum => ['keep', 'remove', 'protected', 'renamed'], enum => ['keep', 'remove', 'protected', 'renamed'],
}, },
@ -92,7 +111,8 @@ __PACKAGE__->register_method ({
if defined($prune_backups); if defined($prune_backups);
return PVE::Storage::prune_backups($cfg, $storeid, $prune_backups, $vmid, $type, 1); return PVE::Storage::prune_backups($cfg, $storeid, $prune_backups, $vmid, $type, 1);
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'delete', name => 'delete',
@ -100,8 +120,8 @@ __PACKAGE__->register_method ({
method => 'DELETE', method => 'DELETE',
description => "Prune backups. Only those using the standard naming scheme are considered.", description => "Prune backups. Only those using the standard naming scheme are considered.",
permissions => { permissions => {
description => "You need the 'Datastore.Allocate' privilege on the storage " . description => "You need the 'Datastore.Allocate' privilege on the storage "
"(or if a VM ID is specified, 'Datastore.AllocateSpace' and 'VM.Backup' for the VM).", . "(or if a VM ID is specified, 'Datastore.AllocateSpace' and 'VM.Backup' for the VM).",
user => 'all', user => 'all',
}, },
protected => 1, protected => 1,
@ -110,23 +130,34 @@ __PACKAGE__->register_method ({
additionalProperties => 0, additionalProperties => 0,
properties => { properties => {
node => get_standard_option('pve-node'), node => get_standard_option('pve-node'),
storage => get_standard_option('pve-storage-id', { storage => get_standard_option(
'pve-storage-id',
{
completion => \&PVE::Storage::complete_storage, completion => \&PVE::Storage::complete_storage,
}), },
'prune-backups' => get_standard_option('prune-backups', { ),
description => "Use these retention options instead of those from the storage configuration.", 'prune-backups' => get_standard_option(
}), 'prune-backups',
{
description =>
"Use these retention options instead of those from the storage configuration.",
},
),
type => { type => {
description => "Either 'qemu' or 'lxc'. Only consider backups for guests of this type.", description =>
"Either 'qemu' or 'lxc'. Only consider backups for guests of this type.",
type => 'string', type => 'string',
optional => 1, optional => 1,
enum => ['qemu', 'lxc'], enum => ['qemu', 'lxc'],
}, },
vmid => get_standard_option('pve-vmid', { vmid => get_standard_option(
'pve-vmid',
{
description => "Only prune backups for this VM.", description => "Only prune backups for this VM.",
completion => \&PVE::Cluster::complete_vmid, completion => \&PVE::Cluster::complete_vmid,
optional => 1, optional => 1,
}), },
),
}, },
}, },
returns => { type => 'string' }, returns => { type => 'string' },
@ -159,6 +190,7 @@ __PACKAGE__->register_method ({
}; };
return $rpcenv->fork_worker('prunebackups', $id, $authuser, $worker); return $rpcenv->fork_worker('prunebackups', $id, $authuser, $worker);
}}); },
});
1; 1;

View File

@ -52,7 +52,8 @@ __PACKAGE__->register_method({
]; ];
return $res; return $res;
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'nfsscan', name => 'nfsscan',
@ -70,7 +71,8 @@ __PACKAGE__->register_method({
node => get_standard_option('pve-node'), node => get_standard_option('pve-node'),
server => { server => {
description => "The server address (name or IP).", description => "The server address (name or IP).",
type => 'string', format => 'pve-storage-server', type => 'string',
format => 'pve-storage-server',
}, },
}, },
}, },
@ -101,7 +103,8 @@ __PACKAGE__->register_method({
push @$data, { path => $k, options => $res->{$k} }; push @$data, { path => $k, options => $res->{$k} };
} }
return $data; return $data;
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'cifsscan', name => 'cifsscan',
@ -119,7 +122,8 @@ __PACKAGE__->register_method({
node => get_standard_option('pve-node'), node => get_standard_option('pve-node'),
server => { server => {
description => "The server address (name or IP).", description => "The server address (name or IP).",
type => 'string', format => 'pve-storage-server', type => 'string',
format => 'pve-storage-server',
}, },
username => { username => {
description => "User name.", description => "User name.",
@ -172,7 +176,8 @@ __PACKAGE__->register_method({
} }
return $data; return $data;
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'pbsscan', name => 'pbsscan',
@ -190,7 +195,8 @@ __PACKAGE__->register_method({
node => get_standard_option('pve-node'), node => get_standard_option('pve-node'),
server => { server => {
description => "The server address (name or IP).", description => "The server address (name or IP).",
type => 'string', format => 'pve-storage-server', type => 'string',
format => 'pve-storage-server',
}, },
username => { username => {
description => "User-name or API token-ID.", description => "User-name or API token-ID.",
@ -236,7 +242,7 @@ __PACKAGE__->register_method({
my $password = delete $param->{password}; my $password = delete $param->{password};
return PVE::Storage::PBSPlugin::scan_datastores($param, $password); return PVE::Storage::PBSPlugin::scan_datastores($param, $password);
} },
}); });
# Note: GlusterFS currently does not have an equivalent of showmount. # Note: GlusterFS currently does not have an equivalent of showmount.
@ -258,7 +264,8 @@ __PACKAGE__->register_method({
node => get_standard_option('pve-node'), node => get_standard_option('pve-node'),
server => { server => {
description => "The server address (name or IP).", description => "The server address (name or IP).",
type => 'string', format => 'pve-storage-server', type => 'string',
format => 'pve-storage-server',
}, },
}, },
}, },
@ -287,7 +294,8 @@ __PACKAGE__->register_method({
} }
} }
return $data; return $data;
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'iscsiscan', name => 'iscsiscan',
@ -305,7 +313,8 @@ __PACKAGE__->register_method({
node => get_standard_option('pve-node'), node => get_standard_option('pve-node'),
portal => { portal => {
description => "The iSCSI portal (IP or DNS name with optional port).", description => "The iSCSI portal (IP or DNS name with optional port).",
type => 'string', format => 'pve-storage-portal-dns', type => 'string',
format => 'pve-storage-portal-dns',
}, },
}, },
}, },
@ -336,7 +345,8 @@ __PACKAGE__->register_method({
} }
return $data; return $data;
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'lvmscan', name => 'lvmscan',
@ -371,7 +381,8 @@ __PACKAGE__->register_method({
my $res = PVE::Storage::LVMPlugin::lvm_vgs(); my $res = PVE::Storage::LVMPlugin::lvm_vgs();
return PVE::RESTHandler::hash_to_array($res, 'vg'); return PVE::RESTHandler::hash_to_array($res, 'vg');
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'lvmthinscan', name => 'lvmthinscan',
@ -410,7 +421,8 @@ __PACKAGE__->register_method({
my ($param) = @_; my ($param) = @_;
return PVE::Storage::LvmThinPlugin::list_thinpools($param->{vg}); return PVE::Storage::LvmThinPlugin::list_thinpools($param->{vg});
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'zfsscan', name => 'zfsscan',
@ -444,6 +456,7 @@ __PACKAGE__->register_method({
my ($param) = @_; my ($param) = @_;
return PVE::Storage::scan_zfs(); return PVE::Storage::scan_zfs();
}}); },
});
1; 1;

View File

@ -46,13 +46,16 @@ my sub assert_ova_contents {
# test if it's really a tar file with an ovf file inside # test if it's really a tar file with an ovf file inside
my $hasOvf = 0; my $hasOvf = 0;
run_command(['tar', '-t', '-f', $file], outfunc => sub { run_command(
['tar', '-t', '-f', $file],
outfunc => sub {
my ($line) = @_; my ($line) = @_;
if ($line =~ m/\.ovf$/) { if ($line =~ m/\.ovf$/) {
$hasOvf = 1; $hasOvf = 1;
} }
}); },
);
die "ova archive has no .ovf file inside\n" if !$hasOvf; die "ova archive has no .ovf file inside\n" if !$hasOvf;
@ -65,7 +68,8 @@ __PACKAGE__->register_method ({
method => 'GET', method => 'GET',
description => "Get status for all datastores.", description => "Get status for all datastores.",
permissions => { permissions => {
description => "Only list entries where you have 'Datastore.Audit' or 'Datastore.AllocateSpace' permissions on '/storage/<storage>'", description =>
"Only list entries where you have 'Datastore.Audit' or 'Datastore.AllocateSpace' permissions on '/storage/<storage>'",
user => 'all', user => 'all',
}, },
protected => 1, protected => 1,
@ -74,14 +78,18 @@ __PACKAGE__->register_method ({
additionalProperties => 0, additionalProperties => 0,
properties => { properties => {
node => get_standard_option('pve-node'), node => get_standard_option('pve-node'),
storage => get_standard_option('pve-storage-id', { storage => get_standard_option(
'pve-storage-id',
{
description => "Only list status for specified storage", description => "Only list status for specified storage",
optional => 1, optional => 1,
completion => \&PVE::Storage::complete_storage_enabled, completion => \&PVE::Storage::complete_storage_enabled,
}), },
),
content => { content => {
description => "Only list stores which support this content type.", description => "Only list stores which support this content type.",
type => 'string', format => 'pve-storage-content-list', type => 'string',
format => 'pve-storage-content-list',
optional => 1, optional => 1,
completion => \&PVE::Storage::complete_content_type, completion => \&PVE::Storage::complete_content_type,
}, },
@ -91,12 +99,16 @@ __PACKAGE__->register_method ({
optional => 1, optional => 1,
default => 0, default => 0,
}, },
target => get_standard_option('pve-node', { target => get_standard_option(
description => "If target is different to 'node', we only lists shared storages which " . 'pve-node',
"content is accessible on this 'node' and the specified 'target' node.", {
description =>
"If target is different to 'node', we only lists shared storages which "
. "content is accessible on this 'node' and the specified 'target' node.",
optional => 1, optional => 1,
completion => \&PVE::Cluster::get_nodelist, completion => \&PVE::Cluster::get_nodelist,
}), },
),
'format' => { 'format' => {
description => "Include information about formats", description => "Include information about formats",
type => 'boolean', type => 'boolean',
@ -117,7 +129,8 @@ __PACKAGE__->register_method ({
}, },
content => { content => {
description => "Allowed storage content types.", description => "Allowed storage content types.",
type => 'string', format => 'pve-storage-content-list', type => 'string',
format => 'pve-storage-content-list',
}, },
enabled => { enabled => {
description => "Set when storage is enabled (not disabled).", description => "Set when storage is enabled (not disabled).",
@ -211,7 +224,8 @@ __PACKAGE__->register_method ({
} }
return PVE::RESTHandler::hash_to_array($res, 'storage'); return PVE::RESTHandler::hash_to_array($res, 'storage');
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'diridx', name => 'diridx',
@ -219,7 +233,12 @@ __PACKAGE__->register_method ({
method => 'GET', method => 'GET',
description => "", description => "",
permissions => { permissions => {
check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1], check => [
'perm',
'/storage/{storage}',
['Datastore.Audit', 'Datastore.AllocateSpace'],
any => 1,
],
}, },
parameters => { parameters => {
additionalProperties => 0, additionalProperties => 0,
@ -254,7 +273,8 @@ __PACKAGE__->register_method ({
]; ];
return $res; return $res;
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'read_status', name => 'read_status',
@ -262,7 +282,12 @@ __PACKAGE__->register_method ({
method => 'GET', method => 'GET',
description => "Read storage status.", description => "Read storage status.",
permissions => { permissions => {
check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1], check => [
'perm',
'/storage/{storage}',
['Datastore.Audit', 'Datastore.AllocateSpace'],
any => 1,
],
}, },
protected => 1, protected => 1,
proxyto => 'node', proxyto => 'node',
@ -290,7 +315,8 @@ __PACKAGE__->register_method ({
if !defined($data); if !defined($data);
return $data; return $data;
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'rrd', name => 'rrd',
@ -298,7 +324,12 @@ __PACKAGE__->register_method ({
method => 'GET', method => 'GET',
description => "Read storage RRD statistics (returns PNG).", description => "Read storage RRD statistics (returns PNG).",
permissions => { permissions => {
check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1], check => [
'perm',
'/storage/{storage}',
['Datastore.Audit', 'Datastore.AllocateSpace'],
any => 1,
],
}, },
protected => 1, protected => 1,
proxyto => 'node', proxyto => 'node',
@ -314,7 +345,8 @@ __PACKAGE__->register_method ({
}, },
ds => { ds => {
description => "The list of datasources you want to display.", description => "The list of datasources you want to display.",
type => 'string', format => 'pve-configid-list', type => 'string',
format => 'pve-configid-list',
}, },
cf => { cf => {
description => "The RRD consolidation function", description => "The RRD consolidation function",
@ -333,10 +365,10 @@ __PACKAGE__->register_method ({
code => sub { code => sub {
my ($param) = @_; my ($param) = @_;
return PVE::RRD::create_rrd_graph( return PVE::RRD::create_rrd_graph("pve2-storage/$param->{node}/$param->{storage}",
"pve2-storage/$param->{node}/$param->{storage}",
$param->{timeframe}, $param->{ds}, $param->{cf}); $param->{timeframe}, $param->{ds}, $param->{cf});
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'rrddata', name => 'rrddata',
@ -344,7 +376,12 @@ __PACKAGE__->register_method ({
method => 'GET', method => 'GET',
description => "Read storage RRD statistics.", description => "Read storage RRD statistics.",
permissions => { permissions => {
check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1], check => [
'perm',
'/storage/{storage}',
['Datastore.Audit', 'Datastore.AllocateSpace'],
any => 1,
],
}, },
protected => 1, protected => 1,
proxyto => 'node', proxyto => 'node',
@ -378,8 +415,11 @@ __PACKAGE__->register_method ({
return PVE::RRD::create_rrd_data( return PVE::RRD::create_rrd_data(
"pve2-storage/$param->{node}/$param->{storage}", "pve2-storage/$param->{node}/$param->{storage}",
$param->{timeframe}, $param->{cf}); $param->{timeframe},
}}); $param->{cf},
);
},
});
# makes no sense for big images and backup files (because it # makes no sense for big images and backup files (because it
# create a copy of the file). # create a copy of the file).
@ -399,11 +439,13 @@ __PACKAGE__->register_method ({
storage => get_standard_option('pve-storage-id'), storage => get_standard_option('pve-storage-id'),
content => { content => {
description => "Content type.", description => "Content type.",
type => 'string', format => 'pve-storage-content', type => 'string',
format => 'pve-storage-content',
enum => ['iso', 'vztmpl', 'import'], enum => ['iso', 'vztmpl', 'import'],
}, },
filename => { filename => {
description => "The name of the file to create. Caution: This will be normalized!", description =>
"The name of the file to create. Caution: This will be normalized!",
maxLength => 255, maxLength => 255,
type => 'string', type => 'string',
}, },
@ -421,7 +463,8 @@ __PACKAGE__->register_method ({
optional => 1, optional => 1,
}, },
tmpfilename => { tmpfilename => {
description => "The source file name. This parameter is usually set by the REST handler. You can only overwrite it when connecting to the trusted port on localhost.", description =>
"The source file name. This parameter is usually set by the REST handler. You can only overwrite it when connecting to the trusted port on localhost.",
type => 'string', type => 'string',
optional => 1, optional => 1,
pattern => '/var/tmp/pveupload-[0-9a-f]+', pattern => '/var/tmp/pveupload-[0-9a-f]+',
@ -469,7 +512,9 @@ __PACKAGE__->register_method ({
} }
$path = PVE::Storage::get_vztmpl_dir($cfg, $storage); $path = PVE::Storage::get_vztmpl_dir($cfg, $storage);
} elsif ($content eq 'import') { } elsif ($content eq 'import') {
if ($filename !~ m!${PVE::Storage::SAFE_CHAR_CLASS_RE}+$PVE::Storage::UPLOAD_IMPORT_EXT_RE_1$!) { if ($filename !~
m!${PVE::Storage::SAFE_CHAR_CLASS_RE}+$PVE::Storage::UPLOAD_IMPORT_EXT_RE_1$!
) {
raise_param_exc({ filename => "invalid filename or wrong extension" }); raise_param_exc({ filename => "invalid filename or wrong extension" });
} }
my $format = $1; my $format = $1;
@ -500,7 +545,8 @@ __PACKAGE__->register_method ({
if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) { if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
my $remip = PVE::Cluster::remote_node_ip($node); my $remip = PVE::Cluster::remote_node_ip($node);
my $ssh_options = PVE::SSHInfo::ssh_info_to_ssh_opts({ ip => $remip, name => $node }); my $ssh_options =
PVE::SSHInfo::ssh_info_to_ssh_opts({ ip => $remip, name => $node });
my @remcmd = ('/usr/bin/ssh', $ssh_options->@*, $remip, '--'); my @remcmd = ('/usr/bin/ssh', $ssh_options->@*, $remip, '--');
@ -514,7 +560,14 @@ __PACKAGE__->register_method ({
errmsg => "mkdir failed", errmsg => "mkdir failed",
); );
$cmd = ['/usr/bin/scp', $ssh_options->@*, '-p', '--', $tmpfilename, "[$remip]:" . PVE::Tools::shell_quote($dest)]; $cmd = [
'/usr/bin/scp',
$ssh_options->@*,
'-p',
'--',
$tmpfilename,
"[$remip]:" . PVE::Tools::shell_quote($dest),
];
$err_cleanup = sub { run_command([@remcmd, 'rm', '-f', '--', $dest]) }; $err_cleanup = sub { run_command([@remcmd, 'rm', '-f', '--', $dest]) };
} else { } else {
@ -530,11 +583,13 @@ __PACKAGE__->register_method ({
print "starting file import from: $tmpfilename\n"; print "starting file import from: $tmpfilename\n";
eval { eval {
my ($checksum, $checksum_algorithm) = $param->@{'checksum', 'checksum-algorithm'}; my ($checksum, $checksum_algorithm) =
$param->@{ 'checksum', 'checksum-algorithm' };
if ($checksum_algorithm) { if ($checksum_algorithm) {
print "calculating checksum..."; print "calculating checksum...";
my $checksum_got = PVE::Tools::get_file_hash($checksum_algorithm, $tmpfilename); my $checksum_got =
PVE::Tools::get_file_hash($checksum_algorithm, $tmpfilename);
if (lc($checksum_got) eq lc($checksum)) { if (lc($checksum_got) eq lc($checksum)) {
print "OK, checksum verified\n"; print "OK, checksum verified\n";
@ -557,7 +612,8 @@ __PACKAGE__->register_method ({
}; };
if (my $err = $@) { if (my $err = $@) {
# unlinks only the temporary file from the http server # unlinks only the temporary file from the http server
unlink $tmpfilename or $! == ENOENT unlink $tmpfilename
or $! == ENOENT
or warn "unable to clean up temporory file '$tmpfilename' - $!\n"; or warn "unable to clean up temporory file '$tmpfilename' - $!\n";
die $err; die $err;
} }
@ -570,7 +626,8 @@ __PACKAGE__->register_method ({
eval { run_command($cmd, errmsg => 'import failed'); }; eval { run_command($cmd, errmsg => 'import failed'); };
# the temporary file got only uploaded locally, no need to rm remote # the temporary file got only uploaded locally, no need to rm remote
unlink $tmpfilename or $! == ENOENT unlink $tmpfilename
or $! == ENOENT
or warn "unable to clean up temporary file '$tmpfilename' - $!\n"; or warn "unable to clean up temporary file '$tmpfilename' - $!\n";
if (my $err = $@) { if (my $err = $@) {
@ -582,7 +639,8 @@ __PACKAGE__->register_method ({
}; };
return $rpcenv->fork_worker('imgcopy', undef, $user, $worker); return $rpcenv->fork_worker('imgcopy', undef, $user, $worker);
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'download_url', name => 'download_url',
@ -591,12 +649,15 @@ __PACKAGE__->register_method({
description => "Download templates, ISO images, OVAs and VM images by using an URL.", description => "Download templates, ISO images, OVAs and VM images by using an URL.",
proxyto => 'node', proxyto => 'node',
permissions => { permissions => {
description => 'Requires allocation access on the storage and as this allows one to probe' description =>
'Requires allocation access on the storage and as this allows one to probe'
. ' the (local!) host network indirectly it also requires one of Sys.Modify on / (for' . ' the (local!) host network indirectly it also requires one of Sys.Modify on / (for'
. ' backwards compatibility) or the newer Sys.AccessNetwork privilege on the node.', . ' backwards compatibility) or the newer Sys.AccessNetwork privilege on the node.',
check => [ 'and', check => [
'and',
['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']], ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
[ 'or', [
'or',
['perm', '/', ['Sys.Audit', 'Sys.Modify']], ['perm', '/', ['Sys.Audit', 'Sys.Modify']],
['perm', '/nodes/{node}', ['Sys.AccessNetwork']], ['perm', '/nodes/{node}', ['Sys.AccessNetwork']],
], ],
@ -615,11 +676,13 @@ __PACKAGE__->register_method({
}, },
content => { content => {
description => "Content type.", # TODO: could be optional & detected in most cases description => "Content type.", # TODO: could be optional & detected in most cases
type => 'string', format => 'pve-storage-content', type => 'string',
format => 'pve-storage-content',
enum => ['iso', 'vztmpl', 'import'], enum => ['iso', 'vztmpl', 'import'],
}, },
filename => { filename => {
description => "The name of the file to create. Caution: This will be normalized!", description =>
"The name of the file to create. Caution: This will be normalized!",
maxLength => 255, maxLength => 255,
type => 'string', type => 'string',
}, },
@ -652,7 +715,7 @@ __PACKAGE__->register_method({
}, },
}, },
returns => { returns => {
type => "string" type => "string",
}, },
code => sub { code => sub {
my ($param) = @_; my ($param) = @_;
@ -690,7 +753,9 @@ __PACKAGE__->register_method({
} }
$path = PVE::Storage::get_vztmpl_dir($cfg, $storage); $path = PVE::Storage::get_vztmpl_dir($cfg, $storage);
} elsif ($content eq 'import') { } elsif ($content eq 'import') {
if ($filename !~ m!${PVE::Storage::SAFE_CHAR_CLASS_RE}+$PVE::Storage::UPLOAD_IMPORT_EXT_RE_1$!) { if ($filename !~
m!${PVE::Storage::SAFE_CHAR_CLASS_RE}+$PVE::Storage::UPLOAD_IMPORT_EXT_RE_1$!
) {
raise_param_exc({ filename => "invalid filename or wrong extension" }); raise_param_exc({ filename => "invalid filename or wrong extension" });
} }
my $format = $1; my $format = $1;
@ -752,7 +817,8 @@ __PACKAGE__->register_method({
my $worker_id = PVE::Tools::encode_text($filename); # must not pass : or the like as w-ID my $worker_id = PVE::Tools::encode_text($filename); # must not pass : or the like as w-ID
return $rpcenv->fork_worker('download', $worker_id, $user, $worker); return $rpcenv->fork_worker('download', $worker_id, $user, $worker);
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'get_import_metadata', name => 'get_import_metadata',
@ -796,7 +862,8 @@ __PACKAGE__->register_method({
'create-args' => { 'create-args' => {
type => 'object', type => 'object',
additionalProperties => 1, additionalProperties => 1,
description => 'Parameters which can be used in a call to create a VM or container.', description =>
'Parameters which can be used in a call to create a VM or container.',
}, },
'disks' => { 'disks' => {
type => 'object', type => 'object',
@ -808,7 +875,8 @@ __PACKAGE__->register_method({
type => 'object', type => 'object',
additionalProperties => 1, additionalProperties => 1,
optional => 1, optional => 1,
description => 'Recognised network interfaces as `net$id` => { ...params } object.', description =>
'Recognised network interfaces as `net$id` => { ...params } object.',
}, },
'warnings' => { 'warnings' => {
type => 'array', type => 'array',
@ -860,9 +928,13 @@ __PACKAGE__->register_method({
PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $volid); PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $volid);
return PVE::Tools::run_with_timeout(30, sub { return PVE::Tools::run_with_timeout(
30,
sub {
return PVE::Storage::get_import_metadata($cfg, $volid); return PVE::Storage::get_import_metadata($cfg, $volid);
},
);
},
}); });
}});
1; 1;

View File

@ -168,6 +168,7 @@ The message to be printed.
=back =back
=cut =cut
sub new { sub new {
my ($class, $storage_plugin, $scfg, $storeid, $log_function) = @_; my ($class, $storage_plugin, $scfg, $storeid, $log_function) = @_;
@ -183,6 +184,7 @@ Returns the name of the backup provider. It will be printed in some log lines.
=back =back
=cut =cut
sub provider_name { sub provider_name {
my ($self) = @_; my ($self) = @_;
@ -211,6 +213,7 @@ Unix time-stamp of when the job started.
=back =back
=cut =cut
sub job_init { sub job_init {
my ($self, $start_time) = @_; my ($self, $start_time) = @_;
@ -227,6 +230,7 @@ the backup server. Called in both, success and failure scenarios.
=back =back
=cut =cut
sub job_cleanup { sub job_cleanup {
my ($self) = @_; my ($self) = @_;
@ -271,6 +275,7 @@ Unix time-stamp of when the guest backup started.
=back =back
=cut =cut
sub backup_init { sub backup_init {
my ($self, $vmid, $vmtype, $start_time) = @_; my ($self, $vmid, $vmtype, $start_time) = @_;
@ -326,6 +331,7 @@ Present if there was a failure. The error message indicating the failure.
=back =back
=cut =cut
sub backup_cleanup { sub backup_cleanup {
my ($self, $vmid, $vmtype, $success, $info) = @_; my ($self, $vmid, $vmtype, $success, $info) = @_;
@ -366,6 +372,7 @@ The type of the guest being backed up. Currently, either C<qemu> or C<lxc>.
=back =back
=cut =cut
sub backup_get_mechanism { sub backup_get_mechanism {
my ($self, $vmid, $vmtype) = @_; my ($self, $vmid, $vmtype) = @_;
@ -396,6 +403,7 @@ Path to the file with the backup log.
=back =back
=cut =cut
sub backup_handle_log_file { sub backup_handle_log_file {
my ($self, $vmid, $filename) = @_; my ($self, $vmid, $filename) = @_;
@ -462,6 +470,7 @@ bitmap and existing ones will be discarded.
=back =back
=cut =cut
sub backup_vm_query_incremental { sub backup_vm_query_incremental {
my ($self, $vmid, $volumes) = @_; my ($self, $vmid, $volumes) = @_;
@ -619,6 +628,7 @@ configuration as raw data.
=back =back
=cut =cut
sub backup_vm { sub backup_vm {
my ($self, $vmid, $guest_config, $volumes, $info) = @_; my ($self, $vmid, $guest_config, $volumes, $info) = @_;
@ -652,6 +662,7 @@ description there.
=back =back
=cut =cut
sub backup_container_prepare { sub backup_container_prepare {
my ($self, $vmid, $info) = @_; my ($self, $vmid, $info) = @_;
@ -752,6 +763,7 @@ for unprivileged containers by default.
=back =back
=cut =cut
sub backup_container { sub backup_container {
my ($self, $vmid, $guest_config, $exclude_patterns, $info) = @_; my ($self, $vmid, $guest_config, $exclude_patterns, $info) = @_;
@ -797,6 +809,7 @@ The volume ID of the archive being restored.
=back =back
=cut =cut
sub restore_get_mechanism { sub restore_get_mechanism {
my ($self, $volname) = @_; my ($self, $volname) = @_;
@ -824,6 +837,7 @@ The volume ID of the archive being restored.
=back =back
=cut =cut
sub archive_get_guest_config { sub archive_get_guest_config {
my ($self, $volname) = @_; my ($self, $volname) = @_;
@ -853,6 +867,7 @@ The volume ID of the archive being restored.
=back =back
=cut =cut
sub archive_get_firewall_config { sub archive_get_firewall_config {
my ($self, $volname) = @_; my ($self, $volname) = @_;
@ -901,6 +916,7 @@ The volume ID of the archive being restored.
=back =back
=cut =cut
sub restore_vm_init { sub restore_vm_init {
my ($self, $volname) = @_; my ($self, $volname) = @_;
@ -927,6 +943,7 @@ The volume ID of the archive being restored.
=back =back
=cut =cut
sub restore_vm_cleanup { sub restore_vm_cleanup {
my ($self, $volname) = @_; my ($self, $volname) = @_;
@ -984,6 +1001,7 @@ empty.
=back =back
=cut =cut
sub restore_vm_volume_init { sub restore_vm_volume_init {
my ($self, $volname, $device_name, $info) = @_; my ($self, $volname, $device_name, $info) = @_;
@ -1020,6 +1038,7 @@ empty.
=back =back
=cut =cut
sub restore_vm_volume_cleanup { sub restore_vm_volume_cleanup {
my ($self, $volname, $device_name, $info) = @_; my ($self, $volname, $device_name, $info) = @_;
@ -1086,6 +1105,7 @@ empty.
=back =back
=cut =cut
sub restore_container_init { sub restore_container_init {
my ($self, $volname, $info) = @_; my ($self, $volname, $info) = @_;
@ -1117,6 +1137,7 @@ empty.
=back =back
=cut =cut
sub restore_container_cleanup { sub restore_container_cleanup {
my ($self, $volname, $info) = @_; my ($self, $volname, $info) = @_;

View File

@ -35,13 +35,16 @@ my $nodename = PVE::INotify::nodename();
sub param_mapping { sub param_mapping {
my ($name) = @_; my ($name) = @_;
my $password_map = PVE::CLIHandler::get_standard_mapping('pve-password', { my $password_map = PVE::CLIHandler::get_standard_mapping(
'pve-password',
{
func => sub { func => sub {
my ($value) = @_; my ($value) = @_;
return $value if $value; return $value if $value;
return PVE::PTY::read_password("Enter Password: "); return PVE::PTY::read_password("Enter Password: ");
}, },
}); },
);
my $enc_key_map = { my $enc_key_map = {
name => 'encryption-key', name => 'encryption-key',
@ -50,7 +53,7 @@ sub param_mapping {
my ($value) = @_; my ($value) = @_;
return $value if $value eq 'autogen'; return $value if $value eq 'autogen';
return PVE::Tools::file_get_contents($value); return PVE::Tools::file_get_contents($value);
} },
}; };
my $master_key_map = { my $master_key_map = {
@ -59,7 +62,7 @@ sub param_mapping {
func => sub { func => sub {
my ($value) = @_; my ($value) = @_;
return encode_base64(PVE::Tools::file_get_contents($value), ''); return encode_base64(PVE::Tools::file_get_contents($value), '');
} },
}; };
my $keyring_map = { my $keyring_map = {
@ -106,7 +109,7 @@ __PACKAGE__->register_method ({
apiver => PVE::Storage::APIVER, apiver => PVE::Storage::APIVER,
apiage => PVE::Storage::APIAGE, apiage => PVE::Storage::APIAGE,
}; };
} },
}); });
__PACKAGE__->register_method({ __PACKAGE__->register_method({
@ -119,7 +122,8 @@ __PACKAGE__->register_method ({
properties => { properties => {
volume => { volume => {
description => "Volume identifier", description => "Volume identifier",
type => 'string', format => 'pve-volume-id', type => 'string',
format => 'pve-volume-id',
completion => \&PVE::Storage::complete_volume, completion => \&PVE::Storage::complete_volume,
}, },
}, },
@ -137,7 +141,8 @@ __PACKAGE__->register_method ({
return undef; return undef;
}}); },
});
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'extractconfig', name => 'extractconfig',
@ -145,7 +150,8 @@ __PACKAGE__->register_method ({
method => 'GET', method => 'GET',
description => "Extract configuration from vzdump backup archive.", description => "Extract configuration from vzdump backup archive.",
permissions => { permissions => {
description => "The user needs 'VM.Backup' permissions on the backed up guest ID, and 'Datastore.AllocateSpace' on the backup storage.", description =>
"The user needs 'VM.Backup' permissions on the backed up guest ID, and 'Datastore.AllocateSpace' on the backup storage.",
user => 'all', user => 'all',
}, },
protected => 1, protected => 1,
@ -169,12 +175,7 @@ __PACKAGE__->register_method ({
my $storage_cfg = PVE::Storage::config(); my $storage_cfg = PVE::Storage::config();
PVE::Storage::check_volume_access( PVE::Storage::check_volume_access(
$rpcenv, $rpcenv, $authuser, $storage_cfg, undef, $volume, 'backup',
$authuser,
$storage_cfg,
undef,
$volume,
'backup',
); );
if (PVE::Storage::parse_volume_id($volume, 1)) { if (PVE::Storage::parse_volume_id($volume, 1)) {
@ -186,7 +187,8 @@ __PACKAGE__->register_method ({
print "$config_raw\n"; print "$config_raw\n";
return; return;
}}); },
});
my $print_content = sub { my $print_content = sub {
my ($list) = @_; my ($list) = @_;
@ -207,7 +209,8 @@ my $print_content = sub {
next if !$info->{vmid}; next if !$info->{vmid};
my $volid = $info->{volid}; my $volid = $info->{volid};
printf "$basefmt %d\n", $volid, $info->{format}, $info->{content}, $info->{size}, $info->{vmid}; printf "$basefmt %d\n", $volid, $info->{format}, $info->{content}, $info->{size},
$info->{vmid};
} }
foreach my $info (sort { $a->{format} cmp $b->{format} } @$list) { foreach my $info (sort { $a->{format} cmp $b->{format} } @$list) {
@ -288,8 +291,7 @@ __PACKAGE__->register_method ({
optional => 1, optional => 1,
}, },
'with-snapshots' => { 'with-snapshots' => {
description => description => "Whether to include intermediate snapshots in the stream",
"Whether to include intermediate snapshots in the stream",
type => 'boolean', type => 'boolean',
optional => 1, optional => 1,
default => 0, default => 0,
@ -326,8 +328,15 @@ __PACKAGE__->register_method ({
eval { eval {
my $cfg = PVE::Storage::config(); my $cfg = PVE::Storage::config();
PVE::Storage::volume_export($cfg, $outfh, $param->{volume}, $param->{format}, PVE::Storage::volume_export(
$param->{snapshot}, $param->{base}, $with_snapshots); $cfg,
$outfh,
$param->{volume},
$param->{format},
$param->{snapshot},
$param->{base},
$with_snapshots,
);
}; };
my $err = $@; my $err = $@;
if ($filename ne '-') { if ($filename ne '-') {
@ -336,7 +345,7 @@ __PACKAGE__->register_method ({
} }
die $err if $err; die $err if $err;
return; return;
} },
}); });
__PACKAGE__->register_method({ __PACKAGE__->register_method({
@ -359,10 +368,10 @@ __PACKAGE__->register_method ({
enum => $PVE::Storage::KNOWN_EXPORT_FORMATS, enum => $PVE::Storage::KNOWN_EXPORT_FORMATS,
}, },
filename => { filename => {
description => "Source file name. For '-' stdin is used, the " . description => "Source file name. For '-' stdin is used, the "
"tcp://<IP-or-CIDR> format allows to use a TCP connection, " . . "tcp://<IP-or-CIDR> format allows to use a TCP connection, "
"the unix://PATH-TO-SOCKET format a UNIX socket as input." . . "the unix://PATH-TO-SOCKET format a UNIX socket as input."
"Else, the file is treated as common file.", . "Else, the file is treated as common file.",
type => 'string', type => 'string',
}, },
base => { base => {
@ -373,8 +382,7 @@ __PACKAGE__->register_method ({
optional => 1, optional => 1,
}, },
'with-snapshots' => { 'with-snapshots' => {
description => description => "Whether the stream includes intermediate snapshots",
"Whether the stream includes intermediate snapshots",
type => 'boolean', type => 'boolean',
optional => 1, optional => 1,
default => 0, default => 0,
@ -387,8 +395,8 @@ __PACKAGE__->register_method ({
optional => 1, optional => 1,
}, },
'allow-rename' => { 'allow-rename' => {
description => "Choose a new volume ID if the requested " . description => "Choose a new volume ID if the requested "
"volume ID already exists, instead of throwing an error.", . "volume ID already exists, instead of throwing an error.",
type => 'boolean', type => 'boolean',
optional => 1, optional => 1,
default => 0, default => 0,
@ -474,21 +482,28 @@ __PACKAGE__->register_method ({
my $cfg = PVE::Storage::config(); my $cfg = PVE::Storage::config();
my $volume = $param->{volume}; my $volume = $param->{volume};
my $delete = $param->{'delete-snapshot'}; my $delete = $param->{'delete-snapshot'};
my $imported_volid = PVE::Storage::volume_import($cfg, $infh, $volume, $param->{format}, my $imported_volid = PVE::Storage::volume_import(
$param->{snapshot}, $param->{base}, $param->{'with-snapshots'}, $cfg,
$param->{'allow-rename'}); $infh,
$volume,
$param->{format},
$param->{snapshot},
$param->{base},
$param->{'with-snapshots'},
$param->{'allow-rename'},
);
PVE::Storage::volume_snapshot_delete($cfg, $imported_volid, $delete) PVE::Storage::volume_snapshot_delete($cfg, $imported_volid, $delete)
if defined($delete); if defined($delete);
return $imported_volid; return $imported_volid;
} },
}); });
__PACKAGE__->register_method({ __PACKAGE__->register_method({
name => 'prunebackups', name => 'prunebackups',
path => 'prunebackups', path => 'prunebackups',
method => 'GET', method => 'GET',
description => "Prune backups. Only those using the standard naming scheme are considered. " . description => "Prune backups. Only those using the standard naming scheme are considered. "
"If no keep options are specified, those from the storage configuration are used.", . "If no keep options are specified, those from the storage configuration are used.",
protected => 1, protected => 1,
proxyto => 'node', proxyto => 'node',
parameters => { parameters => {
@ -500,28 +515,36 @@ __PACKAGE__->register_method ({
optional => 1, optional => 1,
}, },
node => get_standard_option('pve-node'), node => get_standard_option('pve-node'),
storage => get_standard_option('pve-storage-id', { storage => get_standard_option(
'pve-storage-id',
{
completion => \&PVE::Storage::complete_storage_enabled, completion => \&PVE::Storage::complete_storage_enabled,
}), },
),
%{$PVE::Storage::Plugin::prune_backups_format}, %{$PVE::Storage::Plugin::prune_backups_format},
type => { type => {
description => "Either 'qemu' or 'lxc'. Only consider backups for guests of this type.", description =>
"Either 'qemu' or 'lxc'. Only consider backups for guests of this type.",
type => 'string', type => 'string',
optional => 1, optional => 1,
enum => ['qemu', 'lxc'], enum => ['qemu', 'lxc'],
}, },
vmid => get_standard_option('pve-vmid', { vmid => get_standard_option(
'pve-vmid',
{
description => "Only consider backups for this guest.", description => "Only consider backups for this guest.",
optional => 1, optional => 1,
completion => \&PVE::Cluster::complete_vmid, completion => \&PVE::Cluster::complete_vmid,
}), },
),
}, },
}, },
returns => { returns => {
type => 'object', type => 'object',
properties => { properties => {
dryrun => { dryrun => {
description => 'If it was a dry run or not. The list will only be defined in that case.', description =>
'If it was a dry run or not. The list will only be defined in that case.',
type => 'boolean', type => 'boolean',
}, },
list => { list => {
@ -534,12 +557,14 @@ __PACKAGE__->register_method ({
type => 'string', type => 'string',
}, },
'ctime' => { 'ctime' => {
description => "Creation time of the backup (seconds since the UNIX epoch).", description =>
"Creation time of the backup (seconds since the UNIX epoch).",
type => 'integer', type => 'integer',
}, },
'mark' => { 'mark' => {
description => "Whether the backup would be kept or removed. For backups that don't " . description =>
"use the standard naming scheme, it's 'protected'.", "Whether the backup would be kept or removed. For backups that don't "
. "use the standard naming scheme, it's 'protected'.",
type => 'string', type => 'string',
}, },
type => { type => {
@ -566,7 +591,9 @@ __PACKAGE__->register_method ({
$keep_opts->{$keep} = extract_param($param, $keep) if defined($param->{$keep}); $keep_opts->{$keep} = extract_param($param, $keep) if defined($param->{$keep});
} }
$param->{'prune-backups'} = PVE::JSONSchema::print_property_string( $param->{'prune-backups'} = PVE::JSONSchema::print_property_string(
$keep_opts, $PVE::Storage::Plugin::prune_backups_format) if $keep_opts; $keep_opts,
$PVE::Storage::Plugin::prune_backups_format,
) if $keep_opts;
my $list = []; my $list = [];
if ($dryrun) { if ($dryrun) {
@ -579,7 +606,8 @@ __PACKAGE__->register_method ({
dryrun => $dryrun, dryrun => $dryrun,
list => $list, list => $list,
}; };
}}); },
});
my $print_api_result = sub { my $print_api_result = sub {
my ($data, $schema, $options) = @_; my ($data, $schema, $options) = @_;
@ -590,19 +618,32 @@ our $cmddef = {
add => ["PVE::API2::Storage::Config", 'create', ['type', 'storage']], add => ["PVE::API2::Storage::Config", 'create', ['type', 'storage']],
set => ["PVE::API2::Storage::Config", 'update', ['storage']], set => ["PVE::API2::Storage::Config", 'update', ['storage']],
remove => ["PVE::API2::Storage::Config", 'delete', ['storage']], remove => ["PVE::API2::Storage::Config", 'delete', ['storage']],
status => [ "PVE::API2::Storage::Status", 'index', [], status => ["PVE::API2::Storage::Status", 'index', [], { node => $nodename }, $print_status],
{ node => $nodename }, $print_status ], list => [
list => [ "PVE::API2::Storage::Content", 'index', ['storage'], "PVE::API2::Storage::Content",
{ node => $nodename }, $print_content ], 'index',
alloc => [ "PVE::API2::Storage::Content", 'create', ['storage', 'vmid', 'filename', 'size'], ['storage'],
{ node => $nodename }, sub { { node => $nodename },
$print_content,
],
alloc => [
"PVE::API2::Storage::Content",
'create',
['storage', 'vmid', 'filename', 'size'],
{ node => $nodename },
sub {
my $volid = shift; my $volid = shift;
print "successfully created '$volid'\n"; print "successfully created '$volid'\n";
}], },
free => [ "PVE::API2::Storage::Content", 'delete', ['volume'], ],
{ node => $nodename } ], free => ["PVE::API2::Storage::Content", 'delete', ['volume'], { node => $nodename }],
scan => { scan => {
nfs => [ "PVE::API2::Storage::Scan", 'nfsscan', ['server'], { node => $nodename }, sub { nfs => [
"PVE::API2::Storage::Scan",
'nfsscan',
['server'],
{ node => $nodename },
sub {
my $res = shift; my $res = shift;
my $maxlen = 0; my $maxlen = 0;
@ -613,8 +654,14 @@ our $cmddef = {
foreach my $rec (@$res) { foreach my $rec (@$res) {
printf "%-${maxlen}s %s\n", $rec->{path}, $rec->{options}; printf "%-${maxlen}s %s\n", $rec->{path}, $rec->{options};
} }
}], },
cifs => [ "PVE::API2::Storage::Scan", 'cifsscan', ['server'], { node => $nodename }, sub { ],
cifs => [
"PVE::API2::Storage::Scan",
'cifsscan',
['server'],
{ node => $nodename },
sub {
my $res = shift; my $res = shift;
my $maxlen = 0; my $maxlen = 0;
@ -625,15 +672,27 @@ our $cmddef = {
foreach my $rec (@$res) { foreach my $rec (@$res) {
printf "%-${maxlen}s %s\n", $rec->{share}, $rec->{description}; printf "%-${maxlen}s %s\n", $rec->{share}, $rec->{description};
} }
}], },
glusterfs => [ "PVE::API2::Storage::Scan", 'glusterfsscan', ['server'], { node => $nodename }, sub { ],
glusterfs => [
"PVE::API2::Storage::Scan",
'glusterfsscan',
['server'],
{ node => $nodename },
sub {
my $res = shift; my $res = shift;
foreach my $rec (@$res) { foreach my $rec (@$res) {
printf "%s\n", $rec->{volname}; printf "%s\n", $rec->{volname};
} }
}], },
iscsi => [ "PVE::API2::Storage::Scan", 'iscsiscan', ['portal'], { node => $nodename }, sub { ],
iscsi => [
"PVE::API2::Storage::Scan",
'iscsiscan',
['portal'],
{ node => $nodename },
sub {
my $res = shift; my $res = shift;
my $maxlen = 0; my $maxlen = 0;
@ -644,19 +703,32 @@ our $cmddef = {
foreach my $rec (@$res) { foreach my $rec (@$res) {
printf "%-${maxlen}s %s\n", $rec->{target}, $rec->{portal}; printf "%-${maxlen}s %s\n", $rec->{target}, $rec->{portal};
} }
}], },
lvm => [ "PVE::API2::Storage::Scan", 'lvmscan', [], { node => $nodename }, sub { ],
lvm => [
"PVE::API2::Storage::Scan",
'lvmscan',
[],
{ node => $nodename },
sub {
my $res = shift; my $res = shift;
foreach my $rec (@$res) { foreach my $rec (@$res) {
printf "$rec->{vg}\n"; printf "$rec->{vg}\n";
} }
}], },
lvmthin => [ "PVE::API2::Storage::Scan", 'lvmthinscan', ['vg'], { node => $nodename }, sub { ],
lvmthin => [
"PVE::API2::Storage::Scan",
'lvmthinscan',
['vg'],
{ node => $nodename },
sub {
my $res = shift; my $res = shift;
foreach my $rec (@$res) { foreach my $rec (@$res) {
printf "$rec->{lv}\n"; printf "$rec->{lv}\n";
} }
}], },
],
pbs => [ pbs => [
"PVE::API2::Storage::Scan", "PVE::API2::Storage::Scan",
'pbsscan', 'pbsscan',
@ -665,13 +737,19 @@ our $cmddef = {
$print_api_result, $print_api_result,
$PVE::RESTHandler::standard_output_options, $PVE::RESTHandler::standard_output_options,
], ],
zfs => [ "PVE::API2::Storage::Scan", 'zfsscan', [], { node => $nodename }, sub { zfs => [
"PVE::API2::Storage::Scan",
'zfsscan',
[],
{ node => $nodename },
sub {
my $res = shift; my $res = shift;
foreach my $rec (@$res) { foreach my $rec (@$res) {
printf "$rec->{pool}\n"; printf "$rec->{pool}\n";
} }
}], },
],
}, },
nfsscan => { alias => 'scan nfs' }, nfsscan => { alias => 'scan nfs' },
cifsscan => { alias => 'scan cifs' }, cifsscan => { alias => 'scan cifs' },
@ -683,17 +761,34 @@ our $cmddef = {
path => [__PACKAGE__, 'path', ['volume']], path => [__PACKAGE__, 'path', ['volume']],
extractconfig => [__PACKAGE__, 'extractconfig', ['volume']], extractconfig => [__PACKAGE__, 'extractconfig', ['volume']],
export => [__PACKAGE__, 'export', ['volume', 'format', 'filename']], export => [__PACKAGE__, 'export', ['volume', 'format', 'filename']],
import => [ __PACKAGE__, 'import', ['volume', 'format', 'filename'], {}, sub { import => [
__PACKAGE__,
'import',
['volume', 'format', 'filename'],
{},
sub {
my $volid = shift; my $volid = shift;
print PVE::Storage::volume_imported_message($volid); print PVE::Storage::volume_imported_message($volid);
}], },
apiinfo => [ __PACKAGE__, 'apiinfo', [], {}, sub { ],
apiinfo => [
__PACKAGE__,
'apiinfo',
[],
{},
sub {
my $res = shift; my $res = shift;
print "APIVER $res->{apiver}\n"; print "APIVER $res->{apiver}\n";
print "APIAGE $res->{apiage}\n"; print "APIAGE $res->{apiage}\n";
}], },
'prune-backups' => [ __PACKAGE__, 'prunebackups', ['storage'], { node => $nodename }, sub { ],
'prune-backups' => [
__PACKAGE__,
'prunebackups',
['storage'],
{ node => $nodename },
sub {
my $res = shift; my $res = shift;
my ($dryrun, $list) = ($res->{dryrun}, $res->{list}); my ($dryrun, $list) = ($res->{dryrun}, $res->{list});
@ -705,11 +800,12 @@ our $cmddef = {
return; return;
} }
print "NOTE: this is only a preview and might not be what a subsequent\n" . print "NOTE: this is only a preview and might not be what a subsequent\n"
"prune call does if backups are removed/added in the meantime.\n\n"; . "prune call does if backups are removed/added in the meantime.\n\n";
my @sorted = sort { my @sorted = sort {
my $vmcmp = PVE::Tools::safe_compare($a->{vmid}, $b->{vmid}, sub { $_[0] <=> $_[1] }); my $vmcmp =
PVE::Tools::safe_compare($a->{vmid}, $b->{vmid}, sub { $_[0] <=> $_[1] });
return $vmcmp if $vmcmp ne 0; return $vmcmp if $vmcmp ne 0;
return $a->{ctime} <=> $b->{ctime}; return $a->{ctime} <=> $b->{ctime};
} @{$list}; } @{$list};
@ -726,9 +822,15 @@ our $cmddef = {
my $type = $backup->{type}; my $type = $backup->{type};
my $vmid = $backup->{vmid}; my $vmid = $backup->{vmid};
my $backup_id = defined($vmid) ? "$type/$vmid" : "$type"; my $backup_id = defined($vmid) ? "$type/$vmid" : "$type";
printf("%-${maxlen}s %15s %10s\n", $backup->{volid}, $backup_id, $backup->{mark}); printf(
"%-${maxlen}s %15s %10s\n",
$backup->{volid},
$backup_id,
$backup->{mark},
);
} }
}], },
],
}; };
1; 1;

View File

@ -6,9 +6,7 @@ use Net::IP;
use PVE::Tools qw(run_command); use PVE::Tools qw(run_command);
use PVE::Cluster qw(cfs_register_file); use PVE::Cluster qw(cfs_register_file);
cfs_register_file('ceph.conf', cfs_register_file('ceph.conf', \&parse_ceph_config, \&write_ceph_config);
\&parse_ceph_config,
\&write_ceph_config);
# For more information on how the Ceph parser works and how its grammar is # For more information on how the Ceph parser works and how its grammar is
# defined, see: # defined, see:
@ -417,7 +415,8 @@ sub ceph_connect_option {
if (-e "/etc/pve/priv/ceph/${storeid}.conf") { if (-e "/etc/pve/priv/ceph/${storeid}.conf") {
# allow custom ceph configuration for external clusters # allow custom ceph configuration for external clusters
if ($pveceph_managed) { if ($pveceph_managed) {
warn "ignoring custom ceph config for storage '$storeid', 'monhost' is not set (assuming pveceph managed cluster)!\n"; warn
"ignoring custom ceph config for storage '$storeid', 'monhost' is not set (assuming pveceph managed cluster)!\n";
} else { } else {
$cmd_option->{ceph_conf} = "/etc/pve/priv/ceph/${storeid}.conf"; $cmd_option->{ceph_conf} = "/etc/pve/priv/ceph/${storeid}.conf";
} }
@ -463,7 +462,8 @@ sub ceph_create_keyfile {
my $cephfs_secret = $ceph_get_key->($ceph_admin_keyring, 'admin'); my $cephfs_secret = $ceph_get_key->($ceph_admin_keyring, 'admin');
mkdir '/etc/pve/priv/ceph'; mkdir '/etc/pve/priv/ceph';
chomp $cephfs_secret; chomp $cephfs_secret;
PVE::Tools::file_set_contents($ceph_storage_keyring, "${cephfs_secret}\n", 0400); PVE::Tools::file_set_contents($ceph_storage_keyring, "${cephfs_secret}\n",
0400);
} }
}; };
if (my $err = $@) { if (my $err = $@) {
@ -504,9 +504,12 @@ sub local_ceph_version {
my $version_string = $cache; my $version_string = $cache;
if (!defined($version_string)) { if (!defined($version_string)) {
run_command('ceph --version', outfunc => sub { run_command(
'ceph --version',
outfunc => sub {
$version_string = shift; $version_string = shift;
}); },
);
} }
return undef if !defined($version_string); return undef if !defined($version_string);
# subversion is an array ref. with the version parts from major to minor # subversion is an array ref. with the version parts from major to minor

View File

@ -11,7 +11,8 @@ use File::Basename;
use File::stat; use File::stat;
use JSON; use JSON;
use PVE::Tools qw(extract_param run_command file_get_contents file_read_firstline dir_glob_regex dir_glob_foreach trim); use PVE::Tools
qw(extract_param run_command file_get_contents file_read_firstline dir_glob_regex dir_glob_foreach trim);
my $SMARTCTL = "/usr/sbin/smartctl"; my $SMARTCTL = "/usr/sbin/smartctl";
my $ZPOOL = "/sbin/zpool"; my $ZPOOL = "/sbin/zpool";
@ -98,7 +99,10 @@ sub get_smart_data {
push @$cmd, $disk; push @$cmd, $disk;
my $returncode = eval { my $returncode = eval {
run_command($cmd, noerr => 1, outfunc => sub { run_command(
$cmd,
noerr => 1,
outfunc => sub {
my ($line) = @_; my ($line) = @_;
# ATA SMART attributes, e.g.: # ATA SMART attributes, e.g.:
@ -109,7 +113,12 @@ sub get_smart_data {
# Data Units Written: 5,584,952 [2.85 TB] # Data Units Written: 5,584,952 [2.85 TB]
# Accumulated start-stop cycles: 34 # Accumulated start-stop cycles: 34
if (defined($type) && $type eq 'ata' && $line =~ m/^([ \d]{2}\d)\s+(\S+)\s+(\S{6})\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(.*)$/) { if (
defined($type)
&& $type eq 'ata'
&& $line =~
m/^([ \d]{2}\d)\s+(\S+)\s+(\S{6})\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(.*)$/
) {
my $entry = {}; my $entry = {};
$entry->{name} = $2 if defined $2; $entry->{name} = $2 if defined $2;
@ -140,13 +149,16 @@ sub get_smart_data {
$smartdata->{text} = '' if !defined $smartdata->{text}; $smartdata->{text} = '' if !defined $smartdata->{text};
$smartdata->{text} .= "$line\n"; $smartdata->{text} .= "$line\n";
# extract wearout from nvme/sas text, allow for decimal values # extract wearout from nvme/sas text, allow for decimal values
if ($line =~ m/Percentage Used(?: endurance indicator)?:\s*(\d+(?:\.\d+)?)\%/i) { if ($line =~
m/Percentage Used(?: endurance indicator)?:\s*(\d+(?:\.\d+)?)\%/i
) {
$smartdata->{wearout} = 100 - $1; $smartdata->{wearout} = 100 - $1;
} }
} elsif ($line =~ m/SMART Disabled/) { } elsif ($line =~ m/SMART Disabled/) {
$smartdata->{health} = "SMART Disabled"; $smartdata->{health} = "SMART Disabled";
} }
}) },
);
}; };
my $err = $@; my $err = $@;
@ -163,7 +175,9 @@ sub get_smart_data {
sub get_lsblk_info { sub get_lsblk_info {
my $cmd = [$LSBLK, '--json', '-o', 'path,parttype,fstype']; my $cmd = [$LSBLK, '--json', '-o', 'path,parttype,fstype'];
my $output = ""; my $output = "";
eval { run_command($cmd, outfunc => sub { $output .= "$_[0]\n"; }) }; eval {
run_command($cmd, outfunc => sub { $output .= "$_[0]\n"; });
};
warn "$@\n" if $@; warn "$@\n" if $@;
return {} if $output eq ''; return {} if $output eq '';
@ -175,7 +189,7 @@ sub get_lsblk_info {
map { map {
$_->{path} => { $_->{path} => {
parttype => $_->{parttype}, parttype => $_->{parttype},
fstype => $_->{fstype} fstype => $_->{fstype},
} }
} @{$list} } @{$list}
}; };
@ -203,12 +217,15 @@ sub get_zfs_devices {
# use zpool and parttype uuid, because log and cache do not have zfs type uuid # use zpool and parttype uuid, because log and cache do not have zfs type uuid
eval { eval {
run_command([$ZPOOL, 'list', '-HPLv'], outfunc => sub { run_command(
[$ZPOOL, 'list', '-HPLv'],
outfunc => sub {
my ($line) = @_; my ($line) = @_;
if ($line =~ m|^\t([^\t]+)\t|) { if ($line =~ m|^\t([^\t]+)\t|) {
$res->{$1} = 1; $res->{$1} = 1;
} }
}); },
);
}; };
# only warn here, because maybe zfs tools are not installed # only warn here, because maybe zfs tools are not installed
@ -219,7 +236,6 @@ sub get_zfs_devices {
"516e7cba-6ecf-11d6-8ff8-00022d09712b" => 1, # bsd "516e7cba-6ecf-11d6-8ff8-00022d09712b" => 1, # bsd
}; };
$res = get_devices_by_partuuid($lsblk_info, $uuids, $res); $res = get_devices_by_partuuid($lsblk_info, $uuids, $res);
return $res; return $res;
@ -229,13 +245,16 @@ sub get_lvm_devices {
my ($lsblk_info) = @_; my ($lsblk_info) = @_;
my $res = {}; my $res = {};
eval { eval {
run_command([$PVS, '--noheadings', '--readonly', '-o', 'pv_name'], outfunc => sub{ run_command(
[$PVS, '--noheadings', '--readonly', '-o', 'pv_name'],
outfunc => sub {
my ($line) = @_; my ($line) = @_;
$line = trim($line); $line = trim($line);
if ($line =~ m|^/dev/|) { if ($line =~ m|^/dev/|) {
$res->{$line} = 1; $res->{$line} = 1;
} }
}); },
);
}; };
# if something goes wrong, we do not want to give up, but indicate an error has occurred # if something goes wrong, we do not want to give up, but indicate an error has occurred
@ -270,10 +289,21 @@ sub get_ceph_journals {
sub get_ceph_volume_infos { sub get_ceph_volume_infos {
my $result = {}; my $result = {};
my $cmd = [ $LVS, '-S', 'lv_name=~^osd-', '-o', 'devices,lv_name,lv_tags', my $cmd = [
'--noheadings', '--readonly', '--separator', ';' ]; $LVS,
'-S',
'lv_name=~^osd-',
'-o',
'devices,lv_name,lv_tags',
'--noheadings',
'--readonly',
'--separator',
';',
];
run_command($cmd, outfunc => sub { run_command(
$cmd,
outfunc => sub {
my $line = shift; my $line = shift;
$line =~ s/(?:^\s+)|(?:\s+$)//g; # trim whitespaces $line =~ s/(?:^\s+)|(?:\s+$)//g; # trim whitespaces
@ -284,7 +314,10 @@ sub get_ceph_volume_infos {
if ($fields->[1] =~ m|^osd-([^-]+)-|) { if ($fields->[1] =~ m|^osd-([^-]+)-|) {
my $type = $1; my $type = $1;
# $result autovivification is wanted, to not creating empty hashes # $result autovivification is wanted, to not creating empty hashes
if (($type eq 'block' || $type eq 'data') && $fields->[2] =~ m/ceph.osd_id=([^,]+)/) { if (
($type eq 'block' || $type eq 'data')
&& $fields->[2] =~ m/ceph.osd_id=([^,]+)/
) {
$result->{$dev}->{osdid} = $1; $result->{$dev}->{osdid} = $1;
if (!defined($result->{$dev}->{'osdid-list'})) { if (!defined($result->{$dev}->{'osdid-list'})) {
$result->{$dev}->{'osdid-list'} = []; $result->{$dev}->{'osdid-list'} = [];
@ -299,7 +332,8 @@ sub get_ceph_volume_infos {
$result->{$dev}->{$type}++; $result->{$dev}->{$type}++;
} }
} }
}); },
);
return $result; return $result;
} }
@ -310,10 +344,13 @@ sub get_udev_info {
my $info = ""; my $info = "";
my $data = {}; my $data = {};
eval { eval {
run_command(['udevadm', 'info', '-p', $dev, '--query', 'all'], outfunc => sub { run_command(
['udevadm', 'info', '-p', $dev, '--query', 'all'],
outfunc => sub {
my ($line) = @_; my ($line) = @_;
$info .= "$line\n"; $info .= "$line\n";
}); },
);
}; };
warn $@ if $@; warn $@ if $@;
return if !$info; return if !$info;
@ -403,7 +440,7 @@ sub get_wear_leveling_info {
"Lifetime_Remaining", "Lifetime_Remaining",
"Percent_Life_Remaining", "Percent_Life_Remaining",
"Percent_Lifetime_Used", "Percent_Lifetime_Used",
"Perc_Rated_Life_Used" "Perc_Rated_Life_Used",
); );
# Search for S.M.A.R.T. attributes for known register # Search for S.M.A.R.T. attributes for known register
@ -457,7 +494,7 @@ sub mounted_blockdevs {
foreach my $mount (@$mounts) { foreach my $mount (@$mounts) {
next if $mount->[0] !~ m|^/dev/|; next if $mount->[0] !~ m|^/dev/|;
$mounted->{ abs_path($mount->[0]) } = $mount->[1]; $mounted->{ abs_path($mount->[0]) } = $mount->[1];
}; }
return $mounted; return $mounted;
} }
@ -470,7 +507,7 @@ sub mounted_paths {
foreach my $mount (@$mounts) { foreach my $mount (@$mounts) {
$mounted->{ abs_path($mount->[1]) } = $mount->[0]; $mounted->{ abs_path($mount->[1]) } = $mount->[0];
}; }
return $mounted; return $mounted;
} }
@ -511,7 +548,10 @@ sub get_disks {
$disk_regex = "(?:" . join('|', @$disks) . ")"; $disk_regex = "(?:" . join('|', @$disks) . ")";
} }
dir_glob_foreach('/sys/block', $disk_regex, sub { dir_glob_foreach(
'/sys/block',
$disk_regex,
sub {
my ($dev) = @_; my ($dev) = @_;
# whitelisting following devices # whitelisting following devices
# - hdX ide block device # - hdX ide block device
@ -520,9 +560,10 @@ sub get_disks {
# - xvdX: xen virtual block device # - xvdX: xen virtual block device
# - nvmeXnY: nvme devices # - nvmeXnY: nvme devices
# - cciss!cXnY cciss devices # - cciss!cXnY cciss devices
return if $dev !~ m/^(h|s|x?v)d[a-z]+$/ && return
$dev !~ m/^nvme\d+n\d+$/ && if $dev !~ m/^(h|s|x?v)d[a-z]+$/
$dev !~ m/^cciss\!c\d+d\d+$/; && $dev !~ m/^nvme\d+n\d+$/
&& $dev !~ m/^cciss\!c\d+d\d+$/;
my $data = get_udev_info("/sys/block/$dev") // return; my $data = get_udev_info("/sys/block/$dev") // return;
my $devpath = $data->{devpath}; my $devpath = $data->{devpath};
@ -606,7 +647,8 @@ sub get_disks {
if (defined(my $parttype = $info->{parttype})) { if (defined(my $parttype = $info->{parttype})) {
return 'BIOS boot' if $parttype eq '21686148-6449-6e6f-744e-656564454649'; return 'BIOS boot' if $parttype eq '21686148-6449-6e6f-744e-656564454649';
return 'EFI' if $parttype eq 'c12a7328-f81f-11d2-ba4b-00a0c93ec93b'; return 'EFI' if $parttype eq 'c12a7328-f81f-11d2-ba4b-00a0c93ec93b';
return 'ZFS reserved' if $parttype eq '6a945a3b-1dd2-11b2-99a6-080020736631'; return 'ZFS reserved'
if $parttype eq '6a945a3b-1dd2-11b2-99a6-080020736631';
} }
return "$info->{fstype}" if defined($info->{fstype}); return "$info->{fstype}" if defined($info->{fstype});
@ -640,7 +682,10 @@ sub get_disks {
}; };
my $partitions = {}; my $partitions = {};
dir_glob_foreach("$sysdir", "$dev.+", sub { dir_glob_foreach(
"$sysdir",
"$dev.+",
sub {
my ($part) = @_; my ($part) = @_;
$partitions->{$part} = $collect_ceph_info->("$partpath/$part"); $partitions->{$part} = $collect_ceph_info->("$partpath/$part");
@ -652,7 +697,8 @@ sub get_disks {
$partitions->{$part}->{gpt} = $data->{gpt}; $partitions->{$part}->{gpt} = $data->{gpt};
$partitions->{$part}->{type} = 'partition'; $partitions->{$part}->{type} = 'partition';
$partitions->{$part}->{size} = get_sysdir_size("$sysdir/$part") // 0; $partitions->{$part}->{size} = get_sysdir_size("$sysdir/$part") // 0;
$partitions->{$part}->{used} = $determine_usage->("$partpath/$part", "$sysdir/$part", 1); $partitions->{$part}->{used} =
$determine_usage->("$partpath/$part", "$sysdir/$part", 1);
$partitions->{$part}->{osdid} //= -1; $partitions->{$part}->{osdid} //= -1;
$partitions->{$part}->{'osdid-list'} //= undef; $partitions->{$part}->{'osdid-list'} //= undef;
@ -680,7 +726,8 @@ sub get_disks {
$partitions->{$part}->{wal} = 1 if $journal_part == 3; $partitions->{$part}->{wal} = 1 if $journal_part == 3;
$partitions->{$part}->{bluestore} = 1 if $journal_part == 4; $partitions->{$part}->{bluestore} = 1 if $journal_part == 4;
} }
}); },
);
my $used = $determine_usage->($devpath, $sysdir, 0); my $used = $determine_usage->($devpath, $sysdir, 0);
if (!$include_partitions) { if (!$include_partitions) {
@ -712,7 +759,8 @@ sub get_disks {
if ($include_partitions) { if ($include_partitions) {
$disklist->{$_} = $partitions->{$_} for keys %{$partitions}; $disklist->{$_} = $partitions->{$_} for keys %{$partitions};
} }
}); },
);
return $disklist; return $disklist;
} }
@ -783,28 +831,38 @@ sub append_partition {
$devname =~ s|^/dev/||; $devname =~ s|^/dev/||;
my $newpartid = 1; my $newpartid = 1;
dir_glob_foreach("/sys/block/$devname", qr/\Q$devname\E.*?(\d+)/, sub { dir_glob_foreach(
"/sys/block/$devname",
qr/\Q$devname\E.*?(\d+)/,
sub {
my ($part, $partid) = @_; my ($part, $partid) = @_;
if ($partid >= $newpartid) { if ($partid >= $newpartid) {
$newpartid = $partid + 1; $newpartid = $partid + 1;
} }
}); },
);
$size = PVE::Tools::convert_size($size, 'b' => 'mb'); $size = PVE::Tools::convert_size($size, 'b' => 'mb');
run_command([ $SGDISK, '-n', "$newpartid:0:+${size}M", $dev ], run_command(
errmsg => "error creating partition '$newpartid' on '$dev'"); [$SGDISK, '-n', "$newpartid:0:+${size}M", $dev],
errmsg => "error creating partition '$newpartid' on '$dev'",
);
my $partition; my $partition;
# loop again to detect the real partition device which does not always follow # loop again to detect the real partition device which does not always follow
# a strict $devname$partition scheme like /dev/nvme0n1 -> /dev/nvme0n1p1 # a strict $devname$partition scheme like /dev/nvme0n1 -> /dev/nvme0n1p1
dir_glob_foreach("/sys/block/$devname", qr/\Q$devname\E.*$newpartid/, sub { dir_glob_foreach(
"/sys/block/$devname",
qr/\Q$devname\E.*$newpartid/,
sub {
my ($part) = @_; my ($part) = @_;
$partition = "/dev/$part"; $partition = "/dev/$part";
}); },
);
return $partition; return $partition;
} }
@ -820,10 +878,14 @@ sub has_holder {
return $devpath if !dir_is_empty("/sys/class/block/${dev}/holders"); return $devpath if !dir_is_empty("/sys/class/block/${dev}/holders");
my $found; my $found;
dir_glob_foreach("/sys/block/${dev}", "${dev}.+", sub { dir_glob_foreach(
"/sys/block/${dev}",
"${dev}.+",
sub {
my ($part) = @_; my ($part) = @_;
$found = "/dev/${part}" if !dir_is_empty("/sys/class/block/${part}/holders"); $found = "/dev/${part}" if !dir_is_empty("/sys/class/block/${part}/holders");
}); },
);
return $found; return $found;
} }
@ -841,12 +903,16 @@ sub is_mounted {
my $dev = strip_dev($devpath); my $dev = strip_dev($devpath);
my $found; my $found;
dir_glob_foreach("/sys/block/${dev}", "${dev}.+", sub { dir_glob_foreach(
"/sys/block/${dev}",
"${dev}.+",
sub {
my ($part) = @_; my ($part) = @_;
my $partpath = "/dev/${part}"; my $partpath = "/dev/${part}";
$found = $partpath if $mounted->{$partpath}; $found = $partpath if $mounted->{$partpath};
}); },
);
return $found; return $found;
} }
@ -884,10 +950,14 @@ sub wipe_blockdev {
my $count = ($size < 200) ? $size : 200; my $count = ($size < 200) ? $size : 200;
my $to_wipe = []; my $to_wipe = [];
dir_glob_foreach("/sys/class/block/${devname}", "${devname}.+", sub { dir_glob_foreach(
"/sys/class/block/${devname}",
"${devname}.+",
sub {
my ($part) = @_; my ($part) = @_;
push $to_wipe->@*, "/dev/${part}" if -b "/dev/${part}"; push $to_wipe->@*, "/dev/${part}" if -b "/dev/${part}";
}); },
);
if (scalar($to_wipe->@*) > 0) { if (scalar($to_wipe->@*) > 0) {
print "found child partitions to wipe: " . join(', ', $to_wipe->@*) . "\n"; print "found child partitions to wipe: " . join(', ', $to_wipe->@*) . "\n";

View File

@ -54,14 +54,17 @@ sub extract_disk_from_import_file {
'-x', '-x',
'--force-local', '--force-local',
'--no-same-owner', '--no-same-owner',
'-C', $tmpdir, '-C',
'-f', $ova_path, $tmpdir,
'-f',
$ova_path,
$inner_file, $inner_file,
]); ]);
# check for symlinks and other non regular files # check for symlinks and other non regular files
if (-l $source_path || !-f $source_path) { if (-l $source_path || !-f $source_path) {
die "extracted file '$inner_file' from archive '$archive_volid' is not a regular file\n"; die
"extracted file '$inner_file' from archive '$archive_volid' is not a regular file\n";
} }
# check potentially untrusted image file! # check potentially untrusted image file!
@ -69,7 +72,8 @@ sub extract_disk_from_import_file {
# create temporary 1M image that will get overwritten by the rename # create temporary 1M image that will get overwritten by the rename
# to reserve the filename and take care of locking # to reserve the filename and take care of locking
$target_volid = PVE::Storage::vdisk_alloc($cfg, $target_storeid, $vmid, $inner_fmt, undef, 1024); $target_volid =
PVE::Storage::vdisk_alloc($cfg, $target_storeid, $vmid, $inner_fmt, undef, 1024);
$target_path = PVE::Storage::path($cfg, $target_volid); $target_path = PVE::Storage::path($cfg, $target_volid);
print "renaming $source_path to $target_path\n"; print "renaming $source_path to $target_path\n";

View File

@ -51,7 +51,7 @@ my @resources = (
{ id => 32, dtmf_name => 'Storage Volume' }, { id => 32, dtmf_name => 'Storage Volume' },
{ id => 33, dtmf_name => 'Ethernet Connection' }, { id => 33, dtmf_name => 'Ethernet Connection' },
{ id => 34, dtmf_name => 'DMTF reserved' }, { id => 34, dtmf_name => 'DMTF reserved' },
{ id => 35, dtmf_name => 'Vendor Reserved'} { id => 35, dtmf_name => 'Vendor Reserved' },
); );
# see https://schemas.dmtf.org/wbem/cim-html/2.55.0+/CIM_OperatingSystem.html # see https://schemas.dmtf.org/wbem/cim-html/2.55.0+/CIM_OperatingSystem.html
@ -120,9 +120,7 @@ sub get_ostype {
} }
my $allowed_nic_models = [ my $allowed_nic_models = [
'e1000', 'e1000', 'e1000e', 'vmxnet3',
'e1000e',
'vmxnet3',
]; ];
sub find_by { sub find_by {
@ -177,24 +175,31 @@ sub parse_ovf {
my $dom; my $dom;
if ($isOva) { if ($isOva) {
my $raw = ""; my $raw = "";
PVE::Tools::run_command(['tar', '-xO', '--wildcards', '--occurrence=1', '-f', $ovf, '*.ovf'], outfunc => sub { PVE::Tools::run_command(
['tar', '-xO', '--wildcards', '--occurrence=1', '-f', $ovf, '*.ovf'],
outfunc => sub {
my $line = shift; my $line = shift;
$raw .= $line; $raw .= $line;
}); },
);
$dom = XML::LibXML->load_xml(string => $raw, no_blanks => 1); $dom = XML::LibXML->load_xml(string => $raw, no_blanks => 1);
} else { } else {
$dom = XML::LibXML->load_xml(location => $ovf, no_blanks => 1); $dom = XML::LibXML->load_xml(location => $ovf, no_blanks => 1);
} }
# register the xml namespaces in a xpath context object # register the xml namespaces in a xpath context object
# 'ovf' is the default namespace so it will prepended to each xml element # 'ovf' is the default namespace so it will prepended to each xml element
my $xpc = XML::LibXML::XPathContext->new($dom); my $xpc = XML::LibXML::XPathContext->new($dom);
$xpc->registerNs('ovf', 'http://schemas.dmtf.org/ovf/envelope/1'); $xpc->registerNs('ovf', 'http://schemas.dmtf.org/ovf/envelope/1');
$xpc->registerNs('vmw', 'http://www.vmware.com/schema/ovf'); $xpc->registerNs('vmw', 'http://www.vmware.com/schema/ovf');
$xpc->registerNs('rasd', 'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData'); $xpc->registerNs(
$xpc->registerNs('vssd', 'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData'); 'rasd',
'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData',
);
$xpc->registerNs(
'vssd',
'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData',
);
# hash to save qm.conf parameters # hash to save qm.conf parameters
my $qm; my $qm;
@ -222,32 +227,39 @@ sub parse_ovf {
$ovf_name =~ s/\s+/-/g; $ovf_name =~ s/\s+/-/g;
($qm->{name} = $ovf_name) =~ s/[^a-zA-Z0-9\-\.]//g; ($qm->{name} = $ovf_name) =~ s/[^a-zA-Z0-9\-\.]//g;
} else { } else {
warn "warning: unable to parse the VM name in this OVF manifest, generating a default value\n"; warn
"warning: unable to parse the VM name in this OVF manifest, generating a default value\n";
} }
# middle level xpath # middle level xpath
# element[child] search the elements which have this [child] # element[child] search the elements which have this [child]
my $processor_id = dtmf_name_to_id('Processor'); my $processor_id = dtmf_name_to_id('Processor');
my $xpath_find_vcpu_count = "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType=${processor_id}]/rasd:VirtualQuantity"; my $xpath_find_vcpu_count =
"/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType=${processor_id}]/rasd:VirtualQuantity";
$qm->{'cores'} = $xpc->findvalue($xpath_find_vcpu_count); $qm->{'cores'} = $xpc->findvalue($xpath_find_vcpu_count);
my $memory_id = dtmf_name_to_id('Memory'); my $memory_id = dtmf_name_to_id('Memory');
my $xpath_find_memory = ("/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType=${memory_id}]/rasd:VirtualQuantity"); my $xpath_find_memory = (
"/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType=${memory_id}]/rasd:VirtualQuantity"
);
$qm->{'memory'} = $xpc->findvalue($xpath_find_memory); $qm->{'memory'} = $xpc->findvalue($xpath_find_memory);
# middle level xpath # middle level xpath
# here we expect multiple results, so we do not read the element value with # here we expect multiple results, so we do not read the element value with
# findvalue() but store multiple elements with findnodes() # findvalue() but store multiple elements with findnodes()
my $disk_id = dtmf_name_to_id('Disk Drive'); my $disk_id = dtmf_name_to_id('Disk Drive');
my $xpath_find_disks = "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType=${disk_id}]"; my $xpath_find_disks =
"/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType=${disk_id}]";
my @disk_items = $xpc->findnodes($xpath_find_disks); my @disk_items = $xpc->findnodes($xpath_find_disks);
my $xpath_find_ostype_id = "/ovf:Envelope/ovf:VirtualSystem/ovf:OperatingSystemSection/\@ovf:id"; my $xpath_find_ostype_id =
"/ovf:Envelope/ovf:VirtualSystem/ovf:OperatingSystemSection/\@ovf:id";
my $ostype_id = $xpc->findvalue($xpath_find_ostype_id); my $ostype_id = $xpc->findvalue($xpath_find_ostype_id);
$qm->{ostype} = get_ostype($ostype_id); $qm->{ostype} = get_ostype($ostype_id);
# vmware specific firmware config, seems to not be standardized in ovf ? # vmware specific firmware config, seems to not be standardized in ovf ?
my $xpath_find_firmware = "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/vmw:Config[\@vmw:key=\"firmware\"]/\@vmw:value"; my $xpath_find_firmware =
"/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/vmw:Config[\@vmw:key=\"firmware\"]/\@vmw:value";
my $firmware = $xpc->findvalue($xpath_find_firmware) || 'seabios'; my $firmware = $xpc->findvalue($xpath_find_firmware) || 'seabios';
$qm->{bios} = 'ovmf' if $firmware eq 'efi'; $qm->{bios} = 'ovmf' if $firmware eq 'efi';
@ -290,12 +302,18 @@ sub parse_ovf {
# tricky xpath # tricky xpath
# @ means we filter the result query based on a the value of an item attribute ( @ = attribute) # @ means we filter the result query based on a the value of an item attribute ( @ = attribute)
# @ needs to be escaped to prevent Perl double quote interpolation # @ needs to be escaped to prevent Perl double quote interpolation
my $xpath_find_fileref = sprintf("/ovf:Envelope/ovf:DiskSection/\ my $xpath_find_fileref = sprintf(
ovf:Disk[\@ovf:diskId='%s']/\@ovf:fileRef", $disk_id); "/ovf:Envelope/ovf:DiskSection/\
my $xpath_find_capacity = sprintf("/ovf:Envelope/ovf:DiskSection/\ ovf:Disk[\@ovf:diskId='%s']/\@ovf:fileRef", $disk_id,
ovf:Disk[\@ovf:diskId='%s']/\@ovf:capacity", $disk_id); );
my $xpath_find_capacity_unit = sprintf("/ovf:Envelope/ovf:DiskSection/\ my $xpath_find_capacity = sprintf(
ovf:Disk[\@ovf:diskId='%s']/\@ovf:capacityAllocationUnits", $disk_id); "/ovf:Envelope/ovf:DiskSection/\
ovf:Disk[\@ovf:diskId='%s']/\@ovf:capacity", $disk_id,
);
my $xpath_find_capacity_unit = sprintf(
"/ovf:Envelope/ovf:DiskSection/\
ovf:Disk[\@ovf:diskId='%s']/\@ovf:capacityAllocationUnits", $disk_id,
);
my $fileref = $xpc->findvalue($xpath_find_fileref); my $fileref = $xpc->findvalue($xpath_find_fileref);
my $capacity = $xpc->findvalue($xpath_find_capacity); my $capacity = $xpc->findvalue($xpath_find_capacity);
my $capacity_unit = $xpc->findvalue($xpath_find_capacity_unit); my $capacity_unit = $xpc->findvalue($xpath_find_capacity_unit);
@ -312,8 +330,10 @@ ovf:Disk[\@ovf:diskId='%s']/\@ovf:capacityAllocationUnits", $disk_id);
# from Item, find owning Controller type # from Item, find owning Controller type
my $controller_id = $xpc->findvalue('rasd:Parent', $item_node); my $controller_id = $xpc->findvalue('rasd:Parent', $item_node);
my $xpath_find_parent_type = sprintf("/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/\ my $xpath_find_parent_type = sprintf(
ovf:Item[rasd:InstanceID='%s']/rasd:ResourceType", $controller_id); "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/\
ovf:Item[rasd:InstanceID='%s']/rasd:ResourceType", $controller_id,
);
my $controller_type = $xpc->findvalue($xpath_find_parent_type); my $controller_type = $xpc->findvalue($xpath_find_parent_type);
if (!$controller_type) { if (!$controller_type) {
warn "invalid or missing controller: $controller_type, skipping\n"; warn "invalid or missing controller: $controller_type, skipping\n";
@ -326,7 +346,8 @@ ovf:Item[rasd:InstanceID='%s']/rasd:ResourceType", $controller_id);
my $pve_disk_address = id_to_pve($controller_type) . $adress_on_controller; my $pve_disk_address = id_to_pve($controller_type) . $adress_on_controller;
# from Disk Node, find corresponding filepath # from Disk Node, find corresponding filepath
my $xpath_find_filepath = sprintf("/ovf:Envelope/ovf:References/ovf:File[\@ovf:id='%s']/\@ovf:href", $fileref); my $xpath_find_filepath =
sprintf("/ovf:Envelope/ovf:References/ovf:File[\@ovf:id='%s']/\@ovf:href", $fileref);
my $filepath = $xpc->findvalue($xpath_find_filepath); my $filepath = $xpc->findvalue($xpath_find_filepath);
if (!$filepath) { if (!$filepath) {
warn "invalid file reference $fileref, skipping\n"; warn "invalid file reference $fileref, skipping\n";
@ -335,7 +356,8 @@ ovf:Item[rasd:InstanceID='%s']/rasd:ResourceType", $controller_id);
print "file path: $filepath\n" if $debug; print "file path: $filepath\n" if $debug;
my $original_filepath = $filepath; my $original_filepath = $filepath;
($filepath) = $filepath =~ m|^(${PVE::Storage::SAFE_CHAR_WITH_WHITESPACE_CLASS_RE}+)$|; # untaint & check no sub/parent dirs ($filepath) = $filepath =~ m|^(${PVE::Storage::SAFE_CHAR_WITH_WHITESPACE_CLASS_RE}+)$|; # untaint & check no sub/parent dirs
die "referenced path '$original_filepath' is invalid\n" if !$filepath || $filepath eq "." || $filepath eq ".."; die "referenced path '$original_filepath' is invalid\n"
if !$filepath || $filepath eq "." || $filepath eq "..";
# resolve symlinks and relative path components # resolve symlinks and relative path components
# and die if the diskimage is not somewhere under the $ovf path # and die if the diskimage is not somewhere under the $ovf path
@ -374,7 +396,8 @@ ovf:Item[rasd:InstanceID='%s']/rasd:ResourceType", $controller_id);
$qm->{boot} = "order=" . join(';', @$boot_order) if scalar(@$boot_order) > 0; $qm->{boot} = "order=" . join(';', @$boot_order) if scalar(@$boot_order) > 0;
my $nic_id = dtmf_name_to_id('Ethernet Adapter'); my $nic_id = dtmf_name_to_id('Ethernet Adapter');
my $xpath_find_nics = "/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType=${nic_id}]"; my $xpath_find_nics =
"/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType=${nic_id}]";
my @nic_items = $xpc->findnodes($xpath_find_nics); my @nic_items = $xpc->findnodes($xpath_find_nics);
my $net = {}; my $net = {};

View File

@ -69,7 +69,10 @@ PVE::Storage::ESXiPlugin->register();
# load third-party plugins # load third-party plugins
if (-d '/usr/share/perl5/PVE/Storage/Custom') { if (-d '/usr/share/perl5/PVE/Storage/Custom') {
dir_glob_foreach('/usr/share/perl5/PVE/Storage/Custom', '.*\.pm$', sub { dir_glob_foreach(
'/usr/share/perl5/PVE/Storage/Custom',
'.*\.pm$',
sub {
my ($file) = @_; my ($file) = @_;
my $modname = 'PVE::Storage::Custom::' . $file; my $modname = 'PVE::Storage::Custom::' . $file;
$modname =~ s!\.pm$!!; $modname =~ s!\.pm$!!;
@ -79,11 +82,13 @@ if ( -d '/usr/share/perl5/PVE/Storage/Custom' ) {
require $file; require $file;
# Check perl interface: # Check perl interface:
die "not derived from PVE::Storage::Plugin\n" if !$modname->isa('PVE::Storage::Plugin'); die "not derived from PVE::Storage::Plugin\n"
if !$modname->isa('PVE::Storage::Plugin');
die "does not provide an api() method\n" if !$modname->can('api'); die "does not provide an api() method\n" if !$modname->can('api');
# Check storage API version and that file is really storage plugin. # Check storage API version and that file is really storage plugin.
my $version = $modname->api(); my $version = $modname->api();
die "implements an API version newer than current ($version > " . APIVER . ")\n" die "implements an API version newer than current ($version > "
. APIVER . ")\n"
if $version > APIVER; if $version > APIVER;
my $min_version = (APIVER - APIAGE); my $min_version = (APIVER - APIAGE);
die "API version too old, please update the plugin ($version < $min_version)\n" die "API version too old, please update the plugin ($version < $min_version)\n"
@ -93,13 +98,15 @@ if ( -d '/usr/share/perl5/PVE/Storage/Custom' ) {
$modname->register(); $modname->register();
# If we got this far and the API version is not the same, make some noise: # If we got this far and the API version is not the same, make some noise:
warn "Plugin \"$modname\" is implementing an older storage API, an upgrade is recommended\n" warn
"Plugin \"$modname\" is implementing an older storage API, an upgrade is recommended\n"
if $version != APIVER; if $version != APIVER;
}; };
if ($@) { if ($@) {
warn "Error loading storage plugin \"$modname\": $@"; warn "Error loading storage plugin \"$modname\": $@";
} }
}); },
);
} }
# initialize all plugins # initialize all plugins
@ -164,8 +171,7 @@ my $convert_maxfiles_to_prune_backups = sub {
$prune_backups = { 'keep-all' => 1 }; $prune_backups = { 'keep-all' => 1 };
} }
$scfg->{'prune-backups'} = PVE::JSONSchema::print_property_string( $scfg->{'prune-backups'} = PVE::JSONSchema::print_property_string(
$prune_backups, $prune_backups, 'prune-backups',
'prune-backups'
); );
} }
}; };
@ -287,12 +293,13 @@ sub update_volume_attribute {
my ($backup_type) = map { $_->{subtype} } grep { $_->{volid} eq $volid } $backups->@*; my ($backup_type) = map { $_->{subtype} } grep { $_->{volid} eq $volid } $backups->@*;
my $protected_count = grep { my $protected_count = grep {
$_->{protected} && (!$backup_type || ($_->{subtype} && $_->{subtype} eq $backup_type)) $_->{protected}
&& (!$backup_type || ($_->{subtype} && $_->{subtype} eq $backup_type))
} $backups->@*; } $backups->@*;
if ($max_protected_backups <= $protected_count) { if ($max_protected_backups <= $protected_count) {
die "The number of protected backups per guest is limited to $max_protected_backups ". die "The number of protected backups per guest is limited to $max_protected_backups "
"on storage '$storeid'\n"; . "on storage '$storeid'\n";
} }
} }
@ -429,7 +436,8 @@ sub volume_has_feature {
if ($storeid) { if ($storeid) {
my $scfg = storage_config($cfg, $storeid); my $scfg = storage_config($cfg, $storeid);
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
return $plugin->volume_has_feature($scfg, $feature, $storeid, $volname, $snap, $running, $opts); return $plugin->volume_has_feature($scfg, $feature, $storeid, $volname, $snap, $running,
$opts);
} elsif ($volid =~ m|^(/.+)$| && -e $volid) { } elsif ($volid =~ m|^(/.+)$| && -e $volid) {
return undef; return undef;
} else { } else {
@ -553,7 +561,11 @@ sub check_volume_access {
if ($vtype eq 'iso' || $vtype eq 'vztmpl' || $vtype eq 'import') { if ($vtype eq 'iso' || $vtype eq 'vztmpl' || $vtype eq 'import') {
# require at least read access to storage, (custom) templates/ISOs could be sensitive # require at least read access to storage, (custom) templates/ISOs could be sensitive
$rpcenv->check_any($user, "/storage/$sid", ['Datastore.AllocateSpace', 'Datastore.Audit']); $rpcenv->check_any(
$user,
"/storage/$sid",
['Datastore.AllocateSpace', 'Datastore.Audit'],
);
} elsif (defined($ownervm) && defined($vmid) && ($ownervm == $vmid)) { } elsif (defined($ownervm) && defined($vmid) && ($ownervm == $vmid)) {
# we are owner - allow access # we are owner - allow access
} elsif ($vtype eq 'backup' && $ownervm) { } elsif ($vtype eq 'backup' && $ownervm) {
@ -583,8 +595,7 @@ sub volume_is_base_and_used {
my $scfg = storage_config($cfg, $storeid); my $scfg = storage_config($cfg, $storeid);
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
my ($vtype, $name, $vmid, undef, undef, $isBase, undef) = my ($vtype, $name, $vmid, undef, undef, $isBase, undef) = $plugin->parse_volname($volname);
$plugin->parse_volname($volname);
if ($isBase) { if ($isBase) {
my $vollist = $plugin->list_images($storeid, $scfg); my $vollist = $plugin->list_images($storeid, $scfg);
@ -783,7 +794,9 @@ my $volume_export_prepare = sub {
my $cstream; my $cstream;
if (defined($ratelimit_bps)) { if (defined($ratelimit_bps)) {
$cstream = ['/usr/bin/cstream', '-t', $ratelimit_bps]; $cstream = ['/usr/bin/cstream', '-t', $ratelimit_bps];
$logfunc->("using a bandwidth limit of $ratelimit_bps bytes per second for transferring '$volid'") if $logfunc; $logfunc->(
"using a bandwidth limit of $ratelimit_bps bytes per second for transferring '$volid'")
if $logfunc;
} }
volume_snapshot($cfg, $volid, $snapshot) if $migration_snapshot; volume_snapshot($cfg, $volid, $snapshot) if $migration_snapshot;
@ -830,11 +843,19 @@ sub storage_migrate {
local $ENV{RSYNC_RSH} = PVE::Tools::cmd2string($ssh_base); local $ENV{RSYNC_RSH} = PVE::Tools::cmd2string($ssh_base);
if (!defined($opts->{snapshot})) { if (!defined($opts->{snapshot})) {
$opts->{migration_snapshot} = storage_migrate_snapshot($cfg, $storeid, $opts->{with_snapshots}); $opts->{migration_snapshot} =
storage_migrate_snapshot($cfg, $storeid, $opts->{with_snapshots});
$opts->{snapshot} = '__migration__' if $opts->{migration_snapshot}; $opts->{snapshot} = '__migration__' if $opts->{migration_snapshot};
} }
my @formats = volume_transfer_formats($cfg, $volid, $target_volid, $opts->{snapshot}, $opts->{base_snapshot}, $opts->{with_snapshots}); my @formats = volume_transfer_formats(
$cfg,
$volid,
$target_volid,
$opts->{snapshot},
$opts->{base_snapshot},
$opts->{with_snapshots},
);
die "cannot migrate from storage type '$scfg->{type}' to '$tcfg->{type}'\n" if !@formats; die "cannot migrate from storage type '$scfg->{type}' to '$tcfg->{type}'\n" if !@formats;
my $format = $formats[0]; my $format = $formats[0];
@ -844,7 +865,8 @@ sub storage_migrate {
$import_fn = "tcp://$net"; $import_fn = "tcp://$net";
} }
my $recv = [ @$ssh, '--', $volume_import_prepare->($target_volid, $format, $import_fn, $opts)->@* ]; my $recv =
[@$ssh, '--', $volume_import_prepare->($target_volid, $format, $import_fn, $opts)->@*];
my $new_volid; my $new_volid;
my $pattern = volume_imported_message(undef, 1); my $pattern = volume_imported_message(undef, 1);
@ -961,10 +983,15 @@ sub vdisk_clone {
activate_storage($cfg, $storeid); activate_storage($cfg, $storeid);
# lock shared storage # lock shared storage
return $plugin->cluster_lock_storage($storeid, $scfg->{shared}, undef, sub { return $plugin->cluster_lock_storage(
$storeid,
$scfg->{shared},
undef,
sub {
my $volname = $plugin->clone_image($scfg, $storeid, $volname, $vmid, $snap); my $volname = $plugin->clone_image($scfg, $storeid, $volname, $vmid, $snap);
return "$storeid:$volname"; return "$storeid:$volname";
}); },
);
} }
sub vdisk_create_base { sub vdisk_create_base {
@ -979,10 +1006,15 @@ sub vdisk_create_base {
activate_storage($cfg, $storeid); activate_storage($cfg, $storeid);
# lock shared storage # lock shared storage
return $plugin->cluster_lock_storage($storeid, $scfg->{shared}, undef, sub { return $plugin->cluster_lock_storage(
$storeid,
$scfg->{shared},
undef,
sub {
my $volname = $plugin->create_base($storeid, $scfg, $volname); my $volname = $plugin->create_base($storeid, $scfg, $volname);
return "$storeid:$volname"; return "$storeid:$volname";
}); },
);
} }
sub map_volume { sub map_volume {
@ -1031,14 +1063,20 @@ sub vdisk_alloc {
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
# lock shared storage # lock shared storage
return $plugin->cluster_lock_storage($storeid, $scfg->{shared}, undef, sub { return $plugin->cluster_lock_storage(
$storeid,
$scfg->{shared},
undef,
sub {
my $old_umask = umask(umask | 0037); my $old_umask = umask(umask | 0037);
my $volname = eval { $plugin->alloc_image($storeid, $scfg, $vmid, $fmt, $name, $size) }; my $volname =
eval { $plugin->alloc_image($storeid, $scfg, $vmid, $fmt, $name, $size) };
my $err = $@; my $err = $@;
umask $old_umask; umask $old_umask;
die $err if $err; die $err if $err;
return "$storeid:$volname"; return "$storeid:$volname";
}); },
);
} }
sub vdisk_free { sub vdisk_free {
@ -1053,7 +1091,11 @@ sub vdisk_free {
my $cleanup_worker; my $cleanup_worker;
# lock shared storage # lock shared storage
$plugin->cluster_lock_storage($storeid, $scfg->{shared}, undef, sub { $plugin->cluster_lock_storage(
$storeid,
$scfg->{shared},
undef,
sub {
# LVM-thin allows deletion of still referenced base volumes! # LVM-thin allows deletion of still referenced base volumes!
die "base volume '$volname' is still in use by linked clones\n" die "base volume '$volname' is still in use by linked clones\n"
if volume_is_base_and_used($cfg, $volid); if volume_is_base_and_used($cfg, $volid);
@ -1061,7 +1103,8 @@ sub vdisk_free {
my (undef, undef, undef, undef, undef, $isBase, $format) = my (undef, undef, undef, undef, undef, $isBase, $format) =
$plugin->parse_volname($volname); $plugin->parse_volname($volname);
$cleanup_worker = $plugin->free_image($storeid, $scfg, $volname, $isBase, $format); $cleanup_worker = $plugin->free_image($storeid, $scfg, $volname, $isBase, $format);
}); },
);
return if !$cleanup_worker; return if !$cleanup_worker;
@ -1111,7 +1154,8 @@ sub vdisk_list {
my $scfg = $ids->{$sid}; my $scfg = $ids->{$sid};
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
$res->{$sid} = $plugin->list_images($sid, $scfg, $vmid, $vollist, $cache); $res->{$sid} = $plugin->list_images($sid, $scfg, $vmid, $vollist, $cache);
@{$res->{$sid}} = sort {lc($a->{volid}) cmp lc ($b->{volid}) } @{$res->{$sid}} if $res->{$sid}; @{ $res->{$sid} } = sort { lc($a->{volid}) cmp lc($b->{volid}) } @{ $res->{$sid} }
if $res->{$sid};
} }
return $res; return $res;
@ -1276,9 +1320,7 @@ sub deactivate_volumes {
my $scfg = storage_config($cfg, $storeid); my $scfg = storage_config($cfg, $storeid);
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
eval { eval { $plugin->deactivate_volume($storeid, $scfg, $volname, $snapname, $cache); };
$plugin->deactivate_volume($storeid, $scfg, $volname, $snapname, $cache);
};
if (my $err = $@) { if (my $err = $@) {
warn $err; warn $err;
push @errlist, $volid; push @errlist, $volid;
@ -1321,7 +1363,8 @@ sub storage_info {
avail => 0, avail => 0,
used => 0, used => 0,
shared => $ids->{$storeid}->{shared} ? 1 : 0, shared => $ids->{$storeid}->{shared} ? 1 : 0,
content => PVE::Storage::Plugin::content_hash_to_string($ids->{$storeid}->{content}), content =>
PVE::Storage::Plugin::content_hash_to_string($ids->{$storeid}->{content}),
active => 0, active => 0,
enabled => $storage_enabled ? 1 : 0, enabled => $storage_enabled ? 1 : 0,
}; };
@ -1390,14 +1433,17 @@ sub scan_nfs {
my $cmd = ['/sbin/showmount', '--no-headers', '--exports', $server]; my $cmd = ['/sbin/showmount', '--no-headers', '--exports', $server];
my $res = {}; my $res = {};
run_command($cmd, outfunc => sub { run_command(
$cmd,
outfunc => sub {
my $line = shift; my $line = shift;
# note: howto handle white spaces in export path?? # note: howto handle white spaces in export path??
if ($line =~ m!^(/\S+)\s+(.+)$!) { if ($line =~ m!^(/\S+)\s+(.+)$!) {
$res->{$1} = $2; $res->{$1} = $2;
} }
}); },
);
return $res; return $res;
} }
@ -1418,10 +1464,11 @@ sub scan_cifs {
my $res = {}; my $res = {};
my $err = ''; my $err = '';
run_command($cmd, run_command(
$cmd,
noerr => 1, noerr => 1,
errfunc => sub { errfunc => sub {
$err .= "$_[0]\n" $err .= "$_[0]\n";
}, },
outfunc => sub { outfunc => sub {
my $line = shift; my $line = shift;
@ -1445,7 +1492,9 @@ sub scan_zfs {
my $cmd = ['zfs', 'list', '-t', 'filesystem', '-Hp', '-o', 'name,avail,used']; my $cmd = ['zfs', 'list', '-t', 'filesystem', '-Hp', '-o', 'name,avail,used'];
my $res = []; my $res = [];
run_command($cmd, outfunc => sub { run_command(
$cmd,
outfunc => sub {
my $line = shift; my $line = shift;
if ($line =~ m/^(\S+)\s+(\S+)\s+(\S+)$/) { if ($line =~ m/^(\S+)\s+(\S+)\s+(\S+)$/) {
@ -1457,7 +1506,8 @@ sub scan_zfs {
return if $pool =~ m!/basevol-\d+-[^/]+$!; return if $pool =~ m!/basevol-\d+-[^/]+$!;
push @$res, { pool => $pool, size => $size, free => $size - $used }; push @$res, { pool => $pool, size => $size, free => $size - $used };
} }
}); },
);
return $res; return $res;
} }
@ -1478,7 +1528,6 @@ sub resolv_portal {
raise_param_exc({ portal => "unable to resolve portal address '$portal'" }); raise_param_exc({ portal => "unable to resolve portal address '$portal'" });
} }
sub scan_iscsi { sub scan_iscsi {
my ($portal_in) = @_; my ($portal_in) = @_;
@ -1630,7 +1679,9 @@ sub archive_info {
$info->{filename} = $filename; $info->{filename} = $filename;
$info->{type} = $type; $info->{type} = $type;
if ($volid =~ /^(vzdump-${type}-([1-9][0-9]{2,8})-(\d{4})_(\d{2})_(\d{2})-(\d{2})_(\d{2})_(\d{2}))\.${extension}$/) { if ($volid =~
/^(vzdump-${type}-([1-9][0-9]{2,8})-(\d{4})_(\d{2})_(\d{2})-(\d{2})_(\d{2})_(\d{2}))\.${extension}$/
) {
$info->{logfilename} = "$1" . PVE::Storage::Plugin::LOG_EXT; $info->{logfilename} = "$1" . PVE::Storage::Plugin::LOG_EXT;
$info->{notesfilename} = "$filename" . PVE::Storage::Plugin::NOTES_EXT; $info->{notesfilename} = "$filename" . PVE::Storage::Plugin::NOTES_EXT;
$info->{vmid} = int($2); $info->{vmid} = int($2);
@ -1678,8 +1729,8 @@ sub extract_vzdump_config_tar {
die "ERROR: file '$archive' does not exist\n" if !-f $archive; die "ERROR: file '$archive' does not exist\n" if !-f $archive;
my $pid = open(my $fh, '-|', 'tar', 'tf', $archive) || my $pid = open(my $fh, '-|', 'tar', 'tf', $archive)
die "unable to open file '$archive'\n"; || die "unable to open file '$archive'\n";
my $file; my $file;
while (defined($file = <$fh>)) { while (defined($file = <$fh>)) {
@ -1725,7 +1776,11 @@ sub extract_vzdump_config_vma {
my $errstring; my $errstring;
my $err = sub { my $err = sub {
my $output = shift; my $output = shift;
if ($output =~ m/lzop: Broken pipe: <stdout>/ || $output =~ m/gzip: stdout: Broken pipe/ || $output =~ m/zstd: error 70 : Write error.*Broken pipe/) { if (
$output =~ m/lzop: Broken pipe: <stdout>/
|| $output =~ m/gzip: stdout: Broken pipe/
|| $output =~ m/zstd: error 70 : Write error.*Broken pipe/
) {
$broken_pipe = 1; $broken_pipe = 1;
} elsif (!defined($errstring) && $output !~ m/^\s*$/) { } elsif (!defined($errstring) && $output !~ m/^\s*$/) {
$errstring = "Failed to extract config from VMA archive: $output\n"; $errstring = "Failed to extract config from VMA archive: $output\n";
@ -1855,37 +1910,61 @@ sub prune_mark_backup_group {
my $prune_list = [sort { $b->{ctime} <=> $a->{ctime} } @{$backup_group}]; my $prune_list = [sort { $b->{ctime} <=> $a->{ctime} } @{$backup_group}];
$prune_mark->($prune_list, $keep->{'keep-last'}, sub { $prune_mark->(
$prune_list,
$keep->{'keep-last'},
sub {
my ($ctime) = @_; my ($ctime) = @_;
return $ctime; return $ctime;
}); },
$prune_mark->($prune_list, $keep->{'keep-hourly'}, sub { );
$prune_mark->(
$prune_list,
$keep->{'keep-hourly'},
sub {
my ($ctime) = @_; my ($ctime) = @_;
my (undef, undef, $hour, $day, $month, $year) = localtime($ctime); my (undef, undef, $hour, $day, $month, $year) = localtime($ctime);
return "$hour/$day/$month/$year"; return "$hour/$day/$month/$year";
}); },
$prune_mark->($prune_list, $keep->{'keep-daily'}, sub { );
$prune_mark->(
$prune_list,
$keep->{'keep-daily'},
sub {
my ($ctime) = @_; my ($ctime) = @_;
my (undef, undef, undef, $day, $month, $year) = localtime($ctime); my (undef, undef, undef, $day, $month, $year) = localtime($ctime);
return "$day/$month/$year"; return "$day/$month/$year";
}); },
$prune_mark->($prune_list, $keep->{'keep-weekly'}, sub { );
$prune_mark->(
$prune_list,
$keep->{'keep-weekly'},
sub {
my ($ctime) = @_; my ($ctime) = @_;
my ($sec, $min, $hour, $day, $month, $year) = localtime($ctime); my ($sec, $min, $hour, $day, $month, $year) = localtime($ctime);
my $iso_week = int(strftime("%V", $sec, $min, $hour, $day, $month, $year)); my $iso_week = int(strftime("%V", $sec, $min, $hour, $day, $month, $year));
my $iso_week_year = int(strftime("%G", $sec, $min, $hour, $day, $month, $year)); my $iso_week_year = int(strftime("%G", $sec, $min, $hour, $day, $month, $year));
return "$iso_week/$iso_week_year"; return "$iso_week/$iso_week_year";
}); },
$prune_mark->($prune_list, $keep->{'keep-monthly'}, sub { );
$prune_mark->(
$prune_list,
$keep->{'keep-monthly'},
sub {
my ($ctime) = @_; my ($ctime) = @_;
my (undef, undef, undef, undef, $month, $year) = localtime($ctime); my (undef, undef, undef, undef, $month, $year) = localtime($ctime);
return "$month/$year"; return "$month/$year";
}); },
$prune_mark->($prune_list, $keep->{'keep-yearly'}, sub { );
$prune_mark->(
$prune_list,
$keep->{'keep-yearly'},
sub {
my ($ctime) = @_; my ($ctime) = @_;
my $year = (localtime($ctime))[5]; my $year = (localtime($ctime))[5];
return "$year"; return "$year";
}); },
);
foreach my $prune_entry (@{$prune_list}) { foreach my $prune_entry (@{$prune_list}) {
$prune_entry->{mark} //= 'remove'; $prune_entry->{mark} //= 'remove';
@ -1899,8 +1978,9 @@ sub volume_export : prototype($$$$$$$) {
die "cannot export volume '$volid'\n" if !$storeid; die "cannot export volume '$volid'\n" if !$storeid;
my $scfg = storage_config($cfg, $storeid); my $scfg = storage_config($cfg, $storeid);
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
return $plugin->volume_export($scfg, $storeid, $fh, $volname, $format, return $plugin->volume_export(
$snapshot, $base_snapshot, $with_snapshots); $scfg, $storeid, $fh, $volname, $format, $snapshot, $base_snapshot, $with_snapshots,
);
} }
sub volume_import : prototype($$$$$$$$) { sub volume_import : prototype($$$$$$$$) {
@ -1930,9 +2010,9 @@ sub volume_export_formats : prototype($$$$$) {
return if !$storeid; return if !$storeid;
my $scfg = storage_config($cfg, $storeid); my $scfg = storage_config($cfg, $storeid);
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
return $plugin->volume_export_formats($scfg, $storeid, $volname, return $plugin->volume_export_formats(
$snapshot, $base_snapshot, $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots,
$with_snapshots); );
} }
sub volume_import_formats : prototype($$$$$) { sub volume_import_formats : prototype($$$$$) {
@ -1943,19 +2023,16 @@ sub volume_import_formats : prototype($$$$$) {
my $scfg = storage_config($cfg, $storeid); my $scfg = storage_config($cfg, $storeid);
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
return $plugin->volume_import_formats( return $plugin->volume_import_formats(
$scfg, $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots,
$storeid,
$volname,
$snapshot,
$base_snapshot,
$with_snapshots,
); );
} }
sub volume_transfer_formats { sub volume_transfer_formats {
my ($cfg, $src_volid, $dst_volid, $snapshot, $base_snapshot, $with_snapshots) = @_; my ($cfg, $src_volid, $dst_volid, $snapshot, $base_snapshot, $with_snapshots) = @_;
my @export_formats = volume_export_formats($cfg, $src_volid, $snapshot, $base_snapshot, $with_snapshots); my @export_formats =
my @import_formats = volume_import_formats($cfg, $dst_volid, $snapshot, $base_snapshot, $with_snapshots); volume_export_formats($cfg, $src_volid, $snapshot, $base_snapshot, $with_snapshots);
my @import_formats =
volume_import_formats($cfg, $dst_volid, $snapshot, $base_snapshot, $with_snapshots);
my %import_hash = map { $_ => 1 } @import_formats; my %import_hash = map { $_ => 1 } @import_formats;
my @common = grep { $import_hash{$_} } @export_formats; my @common = grep { $import_hash{$_} } @export_formats;
return @common; return @common;
@ -1987,7 +2064,8 @@ sub volume_import_start {
my $volid = "$storeid:$volname"; my $volid = "$storeid:$volname";
# find common import/export format, like volume_transfer_formats # find common import/export format, like volume_transfer_formats
my @import_formats = PVE::Storage::volume_import_formats($cfg, $volid, $opts->{snapshot}, undef, $with_snapshots); my @import_formats = PVE::Storage::volume_import_formats($cfg, $volid, $opts->{snapshot}, undef,
$with_snapshots);
my @export_formats = PVE::Tools::split_list($opts->{export_formats}); my @export_formats = PVE::Tools::split_list($opts->{export_formats});
my %import_hash = map { $_ => 1 } @import_formats; my %import_hash = map { $_ => 1 } @import_formats;
my @common = grep { $import_hash{$_} } @export_formats; my @common = grep { $import_hash{$_} } @export_formats;
@ -2124,9 +2202,16 @@ sub rename_volume {
$target_vmid = ($plugin->parse_volname($source_volname))[3] if !$target_vmid; $target_vmid = ($plugin->parse_volname($source_volname))[3] if !$target_vmid;
return $plugin->cluster_lock_storage($storeid, $scfg->{shared}, undef, sub { return $plugin->cluster_lock_storage(
return $plugin->rename_volume($scfg, $storeid, $source_volname, $target_vmid, $target_volname); $storeid,
}); $scfg->{shared},
undef,
sub {
return $plugin->rename_volume(
$scfg, $storeid, $source_volname, $target_vmid, $target_volname,
);
},
);
} }
# Various io-heavy operations require io/bandwidth limits which can be # Various io-heavy operations require io/bandwidth limits which can be
@ -2171,7 +2256,8 @@ sub get_bandwidth_limit {
# limits, therefore it also allows us to override them. # limits, therefore it also allows us to override them.
# Since we have most likely multiple storages to check, do a quick check on # Since we have most likely multiple storages to check, do a quick check on
# the general '/storage' path to see if we can skip the checks entirely: # the general '/storage' path to see if we can skip the checks entirely:
return $override if $rpcenv && $rpcenv->check($authuser, '/storage', ['Datastore.Allocate'], 1); return $override
if $rpcenv && $rpcenv->check($authuser, '/storage', ['Datastore.Allocate'], 1);
my %done; my %done;
foreach my $storage (@$storage_list) { foreach my $storage (@$storage_list) {
@ -2181,7 +2267,10 @@ sub get_bandwidth_limit {
$done{$storage} = 1; $done{$storage} = 1;
# Otherwise we may still have individual /storage/$ID permissions: # Otherwise we may still have individual /storage/$ID permissions:
if (!$rpcenv || !$rpcenv->check($authuser, "/storage/$storage", ['Datastore.Allocate'], 1)) { if (
!$rpcenv
|| !$rpcenv->check($authuser, "/storage/$storage", ['Datastore.Allocate'], 1)
) {
# And if not: apply the limits. # And if not: apply the limits.
my $storecfg = storage_config($config, $storage); my $storecfg = storage_config($config, $storage);
$apply_limit->($storecfg->{bwlimit}); $apply_limit->($storecfg->{bwlimit});

View File

@ -44,7 +44,7 @@ sub plugindata {
}, },
{ images => 1, rootdir => 1 }, { images => 1, rootdir => 1 },
], ],
format => [ { raw => 1, subvol => 1 }, 'raw', ], format => [{ raw => 1, subvol => 1 }, 'raw'],
'sensitive-properties' => {}, 'sensitive-properties' => {},
}; };
} }
@ -95,7 +95,8 @@ sub options {
# Reuse `DirPlugin`'s `check_config`. This simply checks for invalid paths. # Reuse `DirPlugin`'s `check_config`. This simply checks for invalid paths.
sub check_config { sub check_config {
my ($self, $sectionId, $config, $create, $skipSchemaCheck) = @_; my ($self, $sectionId, $config, $create, $skipSchemaCheck) = @_;
return PVE::Storage::DirPlugin::check_config($self, $sectionId, $config, $create, $skipSchemaCheck); return PVE::Storage::DirPlugin::check_config($self, $sectionId, $config, $create,
$skipSchemaCheck);
} }
my sub getfsmagic($) { my sub getfsmagic($) {
@ -142,18 +143,14 @@ sub status {
sub get_volume_attribute { sub get_volume_attribute {
my ($class, $scfg, $storeid, $volname, $attribute) = @_; my ($class, $scfg, $storeid, $volname, $attribute) = @_;
return PVE::Storage::DirPlugin::get_volume_attribute($class, $scfg, $storeid, $volname, $attribute); return PVE::Storage::DirPlugin::get_volume_attribute($class, $scfg, $storeid, $volname,
$attribute);
} }
sub update_volume_attribute { sub update_volume_attribute {
my ($class, $scfg, $storeid, $volname, $attribute, $value) = @_; my ($class, $scfg, $storeid, $volname, $attribute, $value) = @_;
return PVE::Storage::DirPlugin::update_volume_attribute( return PVE::Storage::DirPlugin::update_volume_attribute(
$class, $class, $scfg, $storeid, $volname, $attribute, $value,
$scfg,
$storeid,
$volname,
$attribute,
$value,
); );
} }
@ -190,8 +187,7 @@ sub raw_file_to_subvol($) {
sub filesystem_path { sub filesystem_path {
my ($class, $scfg, $volname, $snapname) = @_; my ($class, $scfg, $volname, $snapname) = @_;
my ($vtype, $name, $vmid, undef, undef, $isBase, $format) = my ($vtype, $name, $vmid, undef, undef, $isBase, $format) = $class->parse_volname($volname);
$class->parse_volname($volname);
my $path = $class->get_subdir($scfg, $vtype); my $path = $class->get_subdir($scfg, $vtype);
@ -415,19 +411,22 @@ my sub foreach_snapshot_of_subvol : prototype($$) {
my $basename = basename($subvol); my $basename = basename($subvol);
my $dir = dirname($subvol); my $dir = dirname($subvol);
dir_glob_foreach($dir, $BTRFS_SNAPSHOT_REGEX, sub { dir_glob_foreach(
$dir,
$BTRFS_SNAPSHOT_REGEX,
sub {
my ($volume, $name, $snap_name) = ($1, $2, $3); my ($volume, $name, $snap_name) = ($1, $2, $3);
return if !path_is_subvolume("$dir/$volume"); return if !path_is_subvolume("$dir/$volume");
return if $name ne $basename; return if $name ne $basename;
$code->($snap_name); $code->($snap_name);
}); },
);
} }
sub free_image { sub free_image {
my ($class, $storeid, $scfg, $volname, $isBase, $_format) = @_; my ($class, $storeid, $scfg, $volname, $isBase, $_format) = @_;
my ($vtype, undef, $vmid, undef, undef, undef, $format) = my ($vtype, undef, $vmid, undef, undef, undef, $format) = $class->parse_volname($volname);
$class->parse_volname($volname);
if (!defined($format) || $vtype ne 'images' || ($format ne 'subvol' && $format ne 'raw')) { if (!defined($format) || $vtype ne 'images' || ($format ne 'subvol' && $format ne 'raw')) {
return $class->SUPER::free_image($storeid, $scfg, $volname, $isBase, $_format); return $class->SUPER::free_image($storeid, $scfg, $volname, $isBase, $_format);
@ -441,10 +440,13 @@ sub free_image {
} }
my @snapshot_vols; my @snapshot_vols;
foreach_snapshot_of_subvol($subvol, sub { foreach_snapshot_of_subvol(
$subvol,
sub {
my ($snap_name) = @_; my ($snap_name) = @_;
push @snapshot_vols, "$subvol\@$snap_name"; push @snapshot_vols, "$subvol\@$snap_name";
}); },
);
$class->btrfs_cmd(['subvolume', 'delete', '--', @snapshot_vols, $subvol]); $class->btrfs_cmd(['subvolume', 'delete', '--', @snapshot_vols, $subvol]);
# try to cleanup directory to not clutter storage with empty $vmid dirs if # try to cleanup directory to not clutter storage with empty $vmid dirs if
@ -604,7 +606,7 @@ sub volume_has_feature {
my $features = { my $features = {
snapshot => { snapshot => {
current => { qcow2 => 1, raw => 1, subvol => 1 }, current => { qcow2 => 1, raw => 1, subvol => 1 },
snap => { qcow2 => 1, raw => 1, subvol => 1 } snap => { qcow2 => 1, raw => 1, subvol => 1 },
}, },
clone => { clone => {
base => { qcow2 => 1, raw => 1, subvol => 1, vmdk => 1 }, base => { qcow2 => 1, raw => 1, subvol => 1, vmdk => 1 },
@ -628,7 +630,8 @@ sub volume_has_feature {
}, },
}; };
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase, $format) = $class->parse_volname($volname); my ($vtype, $name, $vmid, $basename, $basevmid, $isBase, $format) =
$class->parse_volname($volname);
my $key = undef; my $key = undef;
if ($snapname) { if ($snapname) {
@ -674,9 +677,8 @@ sub list_images {
$format = 'subvol'; $format = 'subvol';
} else { } else {
$format = $ext; $format = $ext;
($size, undef, $used, $parent, $ctime) = eval { ($size, undef, $used, $parent, $ctime) =
PVE::Storage::Plugin::file_size_info($fn, undef, $format); eval { PVE::Storage::Plugin::file_size_info($fn, undef, $format); };
};
if (my $err = $@) { if (my $err = $@) {
die $err if $err !~ m/Image is not in \S+ format$/; die $err if $err !~ m/Image is not in \S+ format$/;
warn "image '$fn' is not in expected format '$format', querying as raw\n"; warn "image '$fn' is not in expected format '$format', querying as raw\n";
@ -692,8 +694,12 @@ sub list_images {
} }
my $info = { my $info = {
volid => $volid, format => $format, volid => $volid,
size => $size, vmid => $owner, used => $used, parent => $parent, format => $format,
size => $size,
vmid => $owner,
used => $used,
parent => $parent,
}; };
$info->{ctime} = $ctime if $ctime; $info->{ctime} = $ctime if $ctime;
@ -730,13 +736,7 @@ sub volume_import_formats {
# Same as export-formats, beware the parameter order: # Same as export-formats, beware the parameter order:
return volume_export_formats( return volume_export_formats(
$class, $class, $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots,
$scfg,
$storeid,
$volname,
$snapshot,
$base_snapshot,
$with_snapshots,
); );
} }
@ -787,11 +787,15 @@ sub volume_export {
push @$cmd, (map { "$path\@$_" } ($with_snapshots // [])->@*); push @$cmd, (map { "$path\@$_" } ($with_snapshots // [])->@*);
push @$cmd, $path if !defined($base_snapshot); push @$cmd, $path if !defined($base_snapshot);
} else { } else {
foreach_snapshot_of_subvol($path, sub { foreach_snapshot_of_subvol(
$path,
sub {
my ($snap_name) = @_; my ($snap_name) = @_;
# NOTE: if there is a $snapshot specified via the arguments, it is added last below. # NOTE: if there is a $snapshot specified via the arguments, it is added last below.
push @$cmd, "$path\@$snap_name" if !(defined($snapshot) && $snap_name eq $snapshot); push @$cmd, "$path\@$snap_name"
}); if !(defined($snapshot) && $snap_name eq $snapshot);
},
);
} }
$path .= "\@$snapshot" if defined($snapshot); $path .= "\@$snapshot" if defined($snapshot);
push @$cmd, $path; push @$cmd, $path;
@ -858,7 +862,10 @@ sub volume_import {
my $dh = IO::Dir->new($tmppath) my $dh = IO::Dir->new($tmppath)
or die "failed to open temporary receive directory '$tmppath' - $!\n"; or die "failed to open temporary receive directory '$tmppath' - $!\n";
eval { eval {
run_command(['btrfs', '-q', 'receive', '-e', '--', $tmppath], input => '<&'.fileno($fh)); run_command(
['btrfs', '-q', 'receive', '-e', '--', $tmppath],
input => '<&' . fileno($fh),
);
# Analyze the received subvolumes; # Analyze the received subvolumes;
my ($diskname, $found_snapshot, @snapshots); my ($diskname, $found_snapshot, @snapshots);
@ -891,38 +898,39 @@ sub volume_import {
# Rotate the disk into place, first the current state: # Rotate the disk into place, first the current state:
# Note that read-only subvolumes cannot be moved into different directories, but for the # Note that read-only subvolumes cannot be moved into different directories, but for the
# "current" state we also want a writable copy, so start with that: # "current" state we also want a writable copy, so start with that:
$class->btrfs_cmd(['property', 'set', '-f', "$tmppath/$diskname\@$snapshot", 'ro', 'false']); $class->btrfs_cmd(
['property', 'set', '-f', "$tmppath/$diskname\@$snapshot", 'ro', 'false']);
PVE::Tools::renameat2( PVE::Tools::renameat2(
-1, -1,
"$tmppath/$diskname\@$snapshot", "$tmppath/$diskname\@$snapshot",
-1, -1,
$destination, $destination,
&PVE::Tools::RENAME_NOREPLACE, &PVE::Tools::RENAME_NOREPLACE,
) or die "failed to move received snapshot '$tmppath/$diskname\@$snapshot'" )
or die "failed to move received snapshot '$tmppath/$diskname\@$snapshot'"
. " into place at '$destination' - $!\n"; . " into place at '$destination' - $!\n";
# Now recreate the actual snapshot: # Now recreate the actual snapshot:
$class->btrfs_cmd([ $class->btrfs_cmd([
'subvolume', 'subvolume', 'snapshot', '-r', '--', $destination, "$destination\@$snapshot",
'snapshot',
'-r',
'--',
$destination,
"$destination\@$snapshot",
]); ]);
# Now go through the remaining snapshots (if any) # Now go through the remaining snapshots (if any)
foreach my $snap (@snapshots) { foreach my $snap (@snapshots) {
$class->btrfs_cmd(['property', 'set', '-f', "$tmppath/$diskname\@$snap", 'ro', 'false']); $class->btrfs_cmd(
['property', 'set', '-f', "$tmppath/$diskname\@$snap", 'ro', 'false']);
PVE::Tools::renameat2( PVE::Tools::renameat2(
-1, -1,
"$tmppath/$diskname\@$snap", "$tmppath/$diskname\@$snap",
-1, -1,
"$destination\@$snap", "$destination\@$snap",
&PVE::Tools::RENAME_NOREPLACE, &PVE::Tools::RENAME_NOREPLACE,
) or die "failed to move received snapshot '$tmppath/$diskname\@$snap'" )
or die "failed to move received snapshot '$tmppath/$diskname\@$snap'"
. " into place at '$destination\@$snap' - $!\n"; . " into place at '$destination\@$snap' - $!\n";
eval { $class->btrfs_cmd(['property', 'set', "$destination\@$snap", 'ro', 'true']) }; eval {
$class->btrfs_cmd(['property', 'set', "$destination\@$snap", 'ro', 'true']);
};
warn "failed to make $destination\@$snap read-only - $!\n" if $@; warn "failed to make $destination\@$snap read-only - $!\n" if $@;
} }
}; };
@ -938,10 +946,11 @@ sub volume_import {
eval { $class->btrfs_cmd(['subvolume', 'delete', '--', "$tmppath/$entry"]) }; eval { $class->btrfs_cmd(['subvolume', 'delete', '--', "$tmppath/$entry"]) };
warn $@ if $@; warn $@ if $@;
} }
$dh->close; undef $dh; $dh->close;
undef $dh;
} }
if (!rmdir($tmppath)) { if (!rmdir($tmppath)) {
warn "failed to remove temporary directory '$tmppath' - $!\n" warn "failed to remove temporary directory '$tmppath' - $!\n";
} }
}; };
warn $@ if $@; warn $@ if $@;
@ -961,7 +970,9 @@ sub rename_volume {
my $format = ($class->parse_volname($source_volname))[6]; my $format = ($class->parse_volname($source_volname))[6];
if ($format ne 'raw' && $format ne 'subvol') { if ($format ne 'raw' && $format ne 'subvol') {
return $class->SUPER::rename_volume($scfg, $storeid, $source_volname, $target_vmid, $target_volname); return $class->SUPER::rename_volume(
$scfg, $storeid, $source_volname, $target_vmid, $target_volname,
);
} }
$target_volname = $class->find_free_diskname($storeid, $scfg, $target_vmid, $format, 1) $target_volname = $class->find_free_diskname($storeid, $scfg, $target_vmid, $format, 1)
@ -978,8 +989,8 @@ sub rename_volume {
my $new_path = "${basedir}/${target_dir}"; my $new_path = "${basedir}/${target_dir}";
die "target volume '${target_volname}' already exists\n" if -e $new_path; die "target volume '${target_volname}' already exists\n" if -e $new_path;
rename $old_path, $new_path || rename $old_path, $new_path
die "rename '$old_path' to '$new_path' failed - $!\n"; || die "rename '$old_path' to '$new_path' failed - $!\n";
return "${storeid}:$target_volname"; return "${storeid}:$target_volname";
} }

View File

@ -24,9 +24,9 @@ sub cifs_is_mounted : prototype($$) {
$mountdata = PVE::ProcFSTools::parse_proc_mounts() if !$mountdata; $mountdata = PVE::ProcFSTools::parse_proc_mounts() if !$mountdata;
return $mountpoint if grep { return $mountpoint if grep {
$_->[2] =~ /^cifs/ && $_->[2] =~ /^cifs/
$_->[0] =~ m|^\Q$source\E/?$| && && $_->[0] =~ m|^\Q$source\E/?$|
$_->[1] eq $mountpoint && $_->[1] eq $mountpoint
} @$mountdata; } @$mountdata;
return undef; return undef;
} }
@ -98,8 +98,18 @@ sub type {
sub plugindata { sub plugindata {
return { return {
content => [ { images => 1, rootdir => 1, vztmpl => 1, iso => 1, content => [
backup => 1, snippets => 1, import => 1}, { images => 1 }], {
images => 1,
rootdir => 1,
vztmpl => 1,
iso => 1,
backup => 1,
snippets => 1,
import => 1,
},
{ images => 1 },
],
format => [{ raw => 1, qcow2 => 1, vmdk => 1 }, 'raw'], format => [{ raw => 1, qcow2 => 1, vmdk => 1 }, 'raw'],
'sensitive-properties' => { password => 1 }, 'sensitive-properties' => { password => 1 },
}; };
@ -123,7 +133,8 @@ sub properties {
maxLength => 256, maxLength => 256,
}, },
smbversion => { smbversion => {
description => "SMB protocol version. 'default' if not set, negotiates the highest SMB2+" description =>
"SMB protocol version. 'default' if not set, negotiates the highest SMB2+"
. " version supported by both the client and server.", . " version supported by both the client and server.",
type => 'string', type => 'string',
default => 'default', default => 'default',
@ -160,7 +171,6 @@ sub options {
}; };
} }
sub check_config { sub check_config {
my ($class, $sectionId, $config, $create, $skipSchemaCheck) = @_; my ($class, $sectionId, $config, $create, $skipSchemaCheck) = @_;
@ -235,11 +245,10 @@ sub activate_storage {
$class->config_aware_base_mkdir($scfg, $path); $class->config_aware_base_mkdir($scfg, $path);
die "unable to activate storage '$storeid' - " . die "unable to activate storage '$storeid' - " . "directory '$path' does not exist\n"
"directory '$path' does not exist\n" if ! -d $path; if !-d $path;
cifs_mount($scfg, $storeid, $scfg->{smbversion}, cifs_mount($scfg, $storeid, $scfg->{smbversion}, $scfg->{username}, $scfg->{domain});
$scfg->{username}, $scfg->{domain});
} }
$class->SUPER::activate_storage($storeid, $scfg, $cache); $class->SUPER::activate_storage($storeid, $scfg, $cache);
@ -282,11 +291,14 @@ sub check_connection {
my $out_str; my $out_str;
my $out = sub { $out_str .= shift }; 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 = $@) { if (my $err = $@) {
die "$out_str\n" if defined($out_str) && die "$out_str\n"
($out_str =~ m/NT_STATUS_(ACCESS_DENIED|INVALID_PARAMETER|LOGON_FAILURE)/); if defined($out_str)
&& ($out_str =~ m/NT_STATUS_(ACCESS_DENIED|INVALID_PARAMETER|LOGON_FAILURE)/);
return 0; return 0;
} }

View File

@ -27,9 +27,9 @@ sub cephfs_is_mounted {
$mountdata = PVE::ProcFSTools::parse_proc_mounts() if !$mountdata; $mountdata = PVE::ProcFSTools::parse_proc_mounts() if !$mountdata;
return $mountpoint if grep { return $mountpoint if grep {
$_->[2] =~ m#^ceph|fuse\.ceph-fuse# && $_->[2] =~ m#^ceph|fuse\.ceph-fuse#
$_->[0] =~ m#\Q:$subdir\E$|^ceph-fuse$# && && $_->[0] =~ m#\Q:$subdir\E$|^ceph-fuse$#
$_->[1] eq $mountpoint && $_->[1] eq $mountpoint
} @$mountdata; } @$mountdata;
warn "A filesystem is already mounted on $mountpoint\n" warn "A filesystem is already mounted on $mountpoint\n"
@ -116,8 +116,8 @@ sub type {
sub plugindata { sub plugindata {
return { return {
content => [ { vztmpl => 1, iso => 1, backup => 1, snippets => 1, import => 1 }, content =>
{ backup => 1 }], [{ vztmpl => 1, iso => 1, backup => 1, snippets => 1, import => 1 }, { backup => 1 }],
'sensitive-properties' => { keyring => 1 }, 'sensitive-properties' => { keyring => 1 },
}; };
} }
@ -130,7 +130,8 @@ sub properties {
}, },
'fs-name' => { 'fs-name' => {
description => "The Ceph filesystem name.", description => "The Ceph filesystem name.",
type => 'string', format => 'pve-configid', type => 'string',
format => 'pve-configid',
}, },
}; };
} }
@ -219,8 +220,8 @@ sub activate_storage {
$class->config_aware_base_mkdir($scfg, $path); $class->config_aware_base_mkdir($scfg, $path);
die "unable to activate storage '$storeid' - " . die "unable to activate storage '$storeid' - " . "directory '$path' does not exist\n"
"directory '$path' does not exist\n" if ! -d $path; if !-d $path;
cephfs_mount($scfg, $storeid); cephfs_mount($scfg, $storeid);
} }

View File

@ -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 # 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. # 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', { PVE::JSONSchema::register_standard_option(
'pve-storage-image-format',
{
type => 'string', type => 'string',
enum => ['raw', 'qcow2', 'subvol', 'vmdk'], enum => ['raw', 'qcow2', 'subvol', 'vmdk'],
description => "Format of the image.", description => "Format of the image.",
}); },
);
=pod =pod

View File

@ -24,8 +24,19 @@ sub type {
sub plugindata { sub plugindata {
return { return {
content => [ { images => 1, rootdir => 1, vztmpl => 1, iso => 1, backup => 1, snippets => 1, none => 1, import => 1 }, content => [
{ images => 1, rootdir => 1 }], {
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'], format => [{ raw => 1, qcow2 => 1, vmdk => 1, subvol => 1 }, 'raw'],
'sensitive-properties' => {}, 'sensitive-properties' => {},
}; };
@ -35,10 +46,12 @@ sub properties {
return { return {
path => { path => {
description => "File system path.", description => "File system path.",
type => 'string', format => 'pve-storage-path', type => 'string',
format => 'pve-storage-path',
}, },
mkdir => { mkdir => {
description => "Create the directory if it doesn't exist and populate it with default sub-dirs." 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.", . " NOTE: Deprecated, use the 'create-base-path' and 'create-subdirs' options instead.",
type => 'boolean', type => 'boolean',
default => 'yes', default => 'yes',
@ -54,10 +67,9 @@ sub properties {
default => 'yes', default => 'yes',
}, },
is_mountpoint => { is_mountpoint => {
description => description => "Assume the given path is an externally managed mountpoint "
"Assume the given path is an externally managed mountpoint " . . "and consider the storage offline if it is not mounted. "
"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.",
"Using a boolean (yes/no) value serves as a shortcut to using the target path in this field.",
type => 'string', type => 'string',
default => 'no', default => 'no',
}, },
@ -201,7 +213,8 @@ sub update_volume_attribute {
or die "unable to create protection file '$protection_path' - $!\n"; or die "unable to create protection file '$protection_path' - $!\n";
close($fh); close($fh);
} else { } else {
unlink $protection_path or $! == ENOENT unlink $protection_path
or $! == ENOENT
or die "could not delete protection file '$protection_path' - $!\n"; or die "could not delete protection file '$protection_path' - $!\n";
} }
@ -224,7 +237,6 @@ sub status {
return $class->SUPER::status($storeid, $scfg, $cache); return $class->SUPER::status($storeid, $scfg, $cache);
} }
sub activate_storage { sub activate_storage {
my ($class, $storeid, $scfg, $cache) = @_; my ($class, $storeid, $scfg, $cache) = @_;
@ -232,8 +244,8 @@ sub activate_storage {
my $mp = parse_is_mountpoint($scfg); my $mp = parse_is_mountpoint($scfg);
if (defined($mp) && !path_is_mounted($mp, $cache->{mountdata})) { if (defined($mp) && !path_is_mounted($mp, $cache->{mountdata})) {
die "unable to activate storage '$storeid' - " . die "unable to activate storage '$storeid' - "
"directory is expected to be a mount point but is not mounted: '$mp'\n"; . "directory is expected to be a mount point but is not mounted: '$mp'\n";
} }
$class->config_aware_base_mkdir($scfg, $path); $class->config_aware_base_mkdir($scfg, $path);
@ -242,7 +254,8 @@ sub activate_storage {
sub check_config { sub check_config {
my ($self, $sectionId, $config, $create, $skipSchemaCheck) = @_; 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; return $opts if !$create;
if ($opts->{path} !~ m|^/[-/a-zA-Z0-9_.@]+$|) { 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";
@ -278,7 +291,7 @@ sub get_import_metadata {
if ($isOva) { if ($isOva) {
$volid = "$storeid:$volname/$path"; $volid = "$storeid:$volname/$path";
} else { } else {
$volid = "$storeid:import/$path", $volid = "$storeid:import/$path",;
} }
$disks->{$id} = { $disks->{$id} = {
volid => $volid, volid => $volid,

View File

@ -38,7 +38,8 @@ sub plugindata {
sub properties { sub properties {
return { return {
'skip-cert-verification' => { 'skip-cert-verification' => {
description => 'Disable TLS certificate verification, only enable on fully trusted networks!', description =>
'Disable TLS certificate verification, only enable on fully trusted networks!',
type => 'boolean', type => 'boolean',
default => 'false', default => 'false',
}, },
@ -241,7 +242,7 @@ sub esxi_mount : prototype($$$;$) {
print {$wr} "ERROR: $err"; print {$wr} "ERROR: $err";
} }
POSIX::_exit(1); POSIX::_exit(1);
}; }
undef $wr; undef $wr;
my $result = do { local $/ = undef; <$rd> }; my $result = do { local $/ = undef; <$rd> };
@ -291,11 +292,7 @@ sub get_import_metadata : prototype($$$$$) {
my $manifest = $class->get_manifest($storeid, $scfg, 0); my $manifest = $class->get_manifest($storeid, $scfg, 0);
my $contents = file_get_contents($vmx_path); my $contents = file_get_contents($vmx_path);
my $vmx = PVE::Storage::ESXiPlugin::VMX->parse( my $vmx = PVE::Storage::ESXiPlugin::VMX->parse(
$storeid, $storeid, $scfg, $volname, $contents, $manifest,
$scfg,
$volname,
$contents,
$manifest,
); );
return $vmx->get_create_args(); return $vmx->get_create_args();
} }
@ -306,12 +303,13 @@ sub query_vmdk_size : prototype($;$) {
my $json = eval { my $json = eval {
my $json = ''; my $json = '';
run_command(['/usr/bin/qemu-img', 'info', '--output=json', $filename], run_command(
['/usr/bin/qemu-img', 'info', '--output=json', $filename],
timeout => $timeout, timeout => $timeout,
outfunc => sub { $json .= $_[0]; }, outfunc => sub { $json .= $_[0]; },
errfunc => sub { warn "$_[0]\n"; } errfunc => sub { warn "$_[0]\n"; },
); );
from_json($json) from_json($json);
}; };
warn $@ if $@; warn $@ if $@;
@ -447,7 +445,8 @@ sub list_volumes {
my $vm = $vms->{$vm_name}; my $vm = $vms->{$vm_name};
my $ds_name = $vm->{config}->{datastore}; my $ds_name = $vm->{config}->{datastore};
my $path = $vm->{config}->{path}; my $path = $vm->{config}->{path};
push @$res, { push @$res,
{
content => 'import', content => 'import',
format => 'vmx', format => 'vmx',
name => $vm_name, name => $vm_name,
@ -507,7 +506,8 @@ sub volume_export_formats {
} }
sub volume_export { 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)
= @_;
# FIXME: maybe we can support raw+size via `qemu-img dd`? # FIXME: maybe we can support raw+size via `qemu-img dd`?
@ -521,7 +521,18 @@ sub volume_import_formats {
} }
sub volume_import { 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 "importing not supported for $class\n"; die "importing not supported for $class\n";
} }
@ -554,6 +565,7 @@ sub volume_snapshot_delete {
die "deleting snapshots is not supported for $class\n"; die "deleting snapshots is not supported for $class\n";
} }
sub volume_snapshot_info { sub volume_snapshot_info {
my ($class, $scfg, $storeid, $volname) = @_; my ($class, $scfg, $storeid, $volname) = @_;
@ -978,14 +990,15 @@ sub smbios1_uuid {
# vmware stores space separated bytes and has 1 dash in the middle... # vmware stores space separated bytes and has 1 dash in the middle...
$uuid =~ s/[^0-9a-fA-f]//g; $uuid =~ s/[^0-9a-fA-f]//g;
if ($uuid =~ /^ if (
$uuid =~ /^
([0-9a-fA-F]{8}) ([0-9a-fA-F]{8})
([0-9a-fA-F]{4}) ([0-9a-fA-F]{4})
([0-9a-fA-F]{4}) ([0-9a-fA-F]{4})
([0-9a-fA-F]{4}) ([0-9a-fA-F]{4})
([0-9a-fA-F]{12}) ([0-9a-fA-F]{12})
$/x) $/x
{ ) {
return "$1-$2-$3-$4-$5"; return "$1-$2-$3-$4-$5";
} }
return; return;

View File

@ -54,7 +54,12 @@ my $get_active_server = sub {
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 }; $server_test_results->{$server} = { time => time(), active => $status };
@ -72,9 +77,9 @@ sub glusterfs_is_mounted {
$mountdata = PVE::ProcFSTools::parse_proc_mounts() if !$mountdata; $mountdata = PVE::ProcFSTools::parse_proc_mounts() if !$mountdata;
return $mountpoint if grep { return $mountpoint if grep {
$_->[2] eq 'fuse.glusterfs' && $_->[2] eq 'fuse.glusterfs'
$_->[0] =~ /^\S+:\Q$volume\E$/ && && $_->[0] =~ /^\S+:\Q$volume\E$/
$_->[1] eq $mountpoint && $_->[1] eq $mountpoint
} @$mountdata; } @$mountdata;
return undef; return undef;
} }
@ -97,8 +102,10 @@ sub type {
sub plugindata { sub plugindata {
return { return {
content => [ { images => 1, vztmpl => 1, iso => 1, backup => 1, snippets => 1, import => 1}, content => [
{ images => 1 }], { images => 1, vztmpl => 1, iso => 1, backup => 1, snippets => 1, import => 1 },
{ images => 1 },
],
format => [{ raw => 1, qcow2 => 1, vmdk => 1 }, 'raw'], format => [{ raw => 1, qcow2 => 1, vmdk => 1 }, 'raw'],
'sensitive-properties' => {}, 'sensitive-properties' => {},
}; };
@ -112,7 +119,8 @@ sub properties {
}, },
server2 => { server2 => {
description => "Backup volfile server IP or DNS name.", description => "Backup volfile server IP or DNS name.",
type => 'string', format => 'pve-storage-server', type => 'string',
format => 'pve-storage-server',
requires => 'server', requires => 'server',
}, },
transport => { transport => {
@ -145,7 +153,6 @@ sub options {
}; };
} }
sub check_config { sub check_config {
my ($class, $sectionId, $config, $create, $skipSchemaCheck) = @_; my ($class, $sectionId, $config, $create, $skipSchemaCheck) = @_;
@ -169,8 +176,7 @@ sub parse_name_dir {
sub path { sub path {
my ($class, $scfg, $volname, $storeid, $snapname) = @_; my ($class, $scfg, $volname, $storeid, $snapname) = @_;
my ($vtype, $name, $vmid, undef, undef, $isBase, $format) = my ($vtype, $name, $vmid, undef, undef, $isBase, $format) = $class->parse_volname($volname);
$class->parse_volname($volname);
# Note: qcow2/qed has internal snapshot, so path is always # Note: qcow2/qed has internal snapshot, so path is always
# the same (with or without snapshot => same file). # the same (with or without snapshot => same file).
@ -232,8 +238,17 @@ sub clone_image {
my $glustervolume = $scfg->{volume}; my $glustervolume = $scfg->{volume};
my $volumepath = "gluster://$server/$glustervolume/images/$vmid/$name"; my $volumepath = "gluster://$server/$glustervolume/images/$vmid/$name";
my $cmd = ['/usr/bin/qemu-img', 'create', '-b', "../$basevmid/$basename", my $cmd = [
'-F', $format, '-f', 'qcow2', $volumepath]; '/usr/bin/qemu-img',
'create',
'-b',
"../$basevmid/$basename",
'-F',
$format,
'-f',
'qcow2',
$volumepath,
];
run_command($cmd, errmsg => "unable to create image"); run_command($cmd, errmsg => "unable to create image");
@ -307,8 +322,8 @@ sub activate_storage {
if (!glusterfs_is_mounted($volume, $path, $cache->{mountdata})) { 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' - " . die "unable to activate storage '$storeid' - " . "directory '$path' does not exist\n"
"directory '$path' does not exist\n" if ! -d $path; if !-d $path;
my $server = &$get_active_server($scfg, 1); my $server = &$get_active_server($scfg, 1);

View File

@ -24,13 +24,18 @@ sub iscsi_ls {
"k" => 1024, "k" => 1024,
"M" => 1024 * 1024, "M" => 1024 * 1024,
"G" => 1024 * 1024 * 1024, "G" => 1024 * 1024 * 1024,
"T" => 1024*1024*1024*1024 "T" => 1024 * 1024 * 1024 * 1024,
); );
eval { eval {
run_command($cmd, errmsg => "iscsi error", errfunc => sub {}, outfunc => sub { run_command(
$cmd,
errmsg => "iscsi error",
errfunc => sub { },
outfunc => sub {
my $line = shift; my $line = shift;
$line = trim($line); $line = trim($line);
if( $line =~ /Lun:(\d+)\s+([A-Za-z0-9\-\_\.\:]*)\s+\(Size:([0-9\.]*)(k|M|G|T)\)/ ) { if ($line =~ /Lun:(\d+)\s+([A-Za-z0-9\-\_\.\:]*)\s+\(Size:([0-9\.]*)(k|M|G|T)\)/
) {
my $image = "lun" . $1; my $image = "lun" . $1;
my $size = $3; my $size = $3;
my $unit = $4; my $unit = $4;
@ -41,7 +46,8 @@ sub iscsi_ls {
format => 'raw', format => 'raw',
}; };
} }
}); },
);
}; };
my $err = $@; my $err = $@;
@ -80,7 +86,6 @@ sub options {
sub parse_volname { sub parse_volname {
my ($class, $volname) = @_; my ($class, $volname) = @_;
if ($volname =~ m/^lun(\d+)$/) { if ($volname =~ m/^lun(\d+)$/) {
return ('images', $1, undef, undef, undef, undef, 'raw'); return ('images', $1, undef, undef, undef, undef, 'raw');
} }
@ -231,8 +236,7 @@ sub volume_has_feature {
copy => { current => 1 }, copy => { current => 1 },
}; };
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = $class->parse_volname($volname);
$class->parse_volname($volname);
my $key = undef; my $key = undef;
if ($snapname) { if ($snapname) {

View File

@ -9,7 +9,8 @@ use IO::File;
use PVE::JSONSchema qw(get_standard_option); use PVE::JSONSchema qw(get_standard_option);
use PVE::Storage::Plugin; 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); use base qw(PVE::Storage::Plugin);
@ -41,15 +42,21 @@ sub iscsi_session_list {
my $res = {}; my $res = {};
eval { eval {
run_command($cmd, errmsg => 'iscsi session scan failed', outfunc => sub { run_command(
$cmd,
errmsg => 'iscsi session scan failed',
outfunc => sub {
my $line = shift; 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) # 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*$/) { if ($line =~
m/^tcp:\s+\[(\S+)\]\s+((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s+\S+?\s*$/
) {
my ($session_id, $portal, $target) = ($1, $2, $3); my ($session_id, $portal, $target) = ($1, $2, $3);
# there can be several sessions per target (multipath) # there can be several sessions per target (multipath)
push @{ $res->{$target} }, { session_id => $session_id, portal => $portal }; push @{ $res->{$target} }, { session_id => $session_id, portal => $portal };
} }
}); },
);
}; };
if (my $err = $@) { if (my $err = $@) {
die $err if $err !~ m/: No active sessions.$/i; die $err if $err !~ m/: No active sessions.$/i;
@ -95,7 +102,9 @@ sub iscsi_portals {
my $res = []; my $res = [];
my $cmd = [$ISCSIADM, '--mode', 'node']; my $cmd = [$ISCSIADM, '--mode', 'node'];
eval { eval {
run_command($cmd, outfunc => sub { run_command(
$cmd,
outfunc => sub {
my $line = shift; my $line = shift;
if ($line =~ $ISCSI_TARGET_RE) { if ($line =~ $ISCSI_TARGET_RE) {
@ -104,7 +113,8 @@ sub iscsi_portals {
push @{$res}, $portal; push @{$res}, $portal;
} }
} }
}); },
);
}; };
my $err = $@; my $err = $@;
@ -128,7 +138,9 @@ sub iscsi_discovery {
my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets', '--portal', $portal]; my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets', '--portal', $portal];
eval { eval {
run_command($cmd, outfunc => sub { run_command(
$cmd,
outfunc => sub {
my $line = shift; my $line = shift;
if ($line =~ $ISCSI_TARGET_RE) { if ($line =~ $ISCSI_TARGET_RE) {
@ -137,7 +149,8 @@ sub iscsi_discovery {
# and sendtargets should return all of them in single call # and sendtargets should return all of them in single call
push @{ $res->{$target} }, $portal; push @{ $res->{$target} }, $portal;
} }
}); },
);
}; };
# In case of multipath we can stop after receiving targets from any available portal # In case of multipath we can stop after receiving targets from any available portal
@ -159,11 +172,16 @@ sub iscsi_login {
eval { eval {
my $cmd = [ my $cmd = [
$ISCSIADM, $ISCSIADM,
'--mode', 'node', '--mode',
'--targetname', $target, 'node',
'--op', 'update', '--targetname',
'--name', 'node.session.initial_login_retry_max', $target,
'--value', '0', '--op',
'update',
'--name',
'node.session.initial_login_retry_max',
'--value',
'0',
]; ];
run_command($cmd); run_command($cmd);
}; };
@ -204,7 +222,9 @@ sub iscsi_session_rescan {
foreach my $session (@$session_list) { foreach my $session (@$session_list) {
my $cmd = [$ISCSIADM, '--mode', 'session', '--sid', $session->{session_id}, '--rescan']; my $cmd = [$ISCSIADM, '--mode', 'session', '--sid', $session->{session_id}, '--rescan'];
eval { run_command($cmd, outfunc => sub {}); }; eval {
run_command($cmd, outfunc => sub { });
};
warn $@ if $@; warn $@ if $@;
} }
} }
@ -241,7 +261,10 @@ sub iscsi_device_list {
my $stable_paths = load_stable_scsi_paths(); my $stable_paths = load_stable_scsi_paths();
dir_glob_foreach($dirname, 'session(\d+)', sub { dir_glob_foreach(
$dirname,
'session(\d+)',
sub {
my ($ent, $session) = @_; my ($ent, $session) = @_;
my $target = file_read_firstline("$dirname/$ent/targetname"); my $target = file_read_firstline("$dirname/$ent/targetname");
@ -250,7 +273,10 @@ sub iscsi_device_list {
my (undef, $host) = dir_glob_regex("$dirname/$ent/device", 'target(\d+):.*'); my (undef, $host) = dir_glob_regex("$dirname/$ent/device", 'target(\d+):.*');
return if !defined($host); return if !defined($host);
dir_glob_foreach("/sys/bus/scsi/devices", "$host:" . '(\d+):(\d+):(\d+)', sub { dir_glob_foreach(
"/sys/bus/scsi/devices",
"$host:" . '(\d+):(\d+):(\d+)',
sub {
my ($tmp, $channel, $id, $lun) = @_; my ($tmp, $channel, $id, $lun) = @_;
my $type = file_read_firstline("/sys/bus/scsi/devices/$tmp/type"); my $type = file_read_firstline("/sys/bus/scsi/devices/$tmp/type");
@ -258,15 +284,18 @@ sub iscsi_device_list {
my $bdev; my $bdev;
if (-d "/sys/bus/scsi/devices/$tmp/block") { # newer kernels 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*)'); (undef, $bdev) =
dir_glob_regex("/sys/bus/scsi/devices/$tmp/block/", '([A-Za-z]\S*)');
} else { } else {
(undef, $bdev) = dir_glob_regex("/sys/bus/scsi/devices/$tmp", 'block:(\S+)'); (undef, $bdev) =
dir_glob_regex("/sys/bus/scsi/devices/$tmp", 'block:(\S+)');
} }
return if !$bdev; return if !$bdev;
#check multipath #check multipath
if (-d "/sys/block/$bdev/holders") { if (-d "/sys/block/$bdev/holders") {
my $multipathdev = dir_glob_regex("/sys/block/$bdev/holders", '[A-Za-z]\S*'); my $multipathdev =
dir_glob_regex("/sys/block/$bdev/holders", '[A-Za-z]\S*');
$bdev = $multipathdev if $multipathdev; $bdev = $multipathdev if $multipathdev;
} }
@ -288,9 +317,11 @@ sub iscsi_device_list {
}; };
#print "TEST: $target $session $host,$bus,$tg,$lun $blockdev\n"; #print "TEST: $target $session $host,$bus,$tg,$lun $blockdev\n";
}); },
);
}); },
);
return $res; return $res;
} }
@ -317,7 +348,8 @@ sub properties {
}, },
portal => { portal => {
description => "iSCSI portal (IP or DNS name with optional port).", description => "iSCSI portal (IP or DNS name with optional port).",
type => 'string', format => 'pve-storage-portal-dns', type => 'string',
format => 'pve-storage-portal-dns',
}, },
}; };
} }
@ -514,15 +546,15 @@ my $udev_query_path = sub {
my $device_path; my $device_path;
my $cmd = [ my $cmd = [
'udevadm', 'udevadm', 'info', '--query=path', $dev,
'info',
'--query=path',
$dev,
]; ];
eval { eval {
run_command($cmd, outfunc => sub { run_command(
$cmd,
outfunc => sub {
$device_path = shift; $device_path = shift;
}); },
);
}; };
die "failed to query device path for '$dev': $@\n" if $@; die "failed to query device path for '$dev': $@\n" if $@;
@ -540,7 +572,10 @@ $resolve_virtual_devices = sub {
my $resolved = []; my $resolved = [];
if ($dev =~ m!^/devices/virtual/block/!) { if ($dev =~ m!^/devices/virtual/block/!) {
dir_glob_foreach("/sys/$dev/slaves", '([^.].+)', sub { dir_glob_foreach(
"/sys/$dev/slaves",
'([^.].+)',
sub {
my ($slave) = @_; my ($slave) = @_;
# don't check devices multiple times # don't check devices multiple times
@ -554,7 +589,8 @@ $resolve_virtual_devices = sub {
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 { } else {
push @$resolved, $dev; push @$resolved, $dev;
} }
@ -604,8 +640,7 @@ sub volume_has_feature {
copy => { current => 1 }, copy => { current => 1 },
}; };
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = $class->parse_volname($volname);
$class->parse_volname($volname);
my $key = undef; my $key = undef;
if ($snapname) { if ($snapname) {
@ -647,11 +682,14 @@ sub volume_export {
my $file = $class->filesystem_path($scfg, $volname, $snapshot); my $file = $class->filesystem_path($scfg, $volname, $snapshot);
my $size; my $size;
run_command(['/sbin/blockdev', '--getsize64', $file], outfunc => sub { run_command(
['/sbin/blockdev', '--getsize64', $file],
outfunc => sub {
my ($line) = @_; my ($line) = @_;
die "unexpected output from /sbin/blockdev: $line\n" if $line !~ /^(\d+)$/; die "unexpected output from /sbin/blockdev: $line\n" if $line !~ /^(\d+)$/;
$size = int($1); $size = int($1);
}); },
);
PVE::Storage::Plugin::write_common_header($fh, $size); 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; return;

View File

@ -32,19 +32,34 @@ sub lvm_pv_info {
my $has_label = 0; my $has_label = 0;
my $cmd = ['/usr/bin/file', '-L', '-s', $device]; my $cmd = ['/usr/bin/file', '-L', '-s', $device];
run_command($cmd, outfunc => sub { run_command(
$cmd,
outfunc => sub {
my $line = shift; my $line = shift;
$has_label = 1 if $line =~ m/LVM2/; $has_label = 1 if $line =~ m/LVM2/;
}); },
);
return undef if !$has_label; return undef if !$has_label;
$cmd = ['/sbin/pvs', '--separator', ':', '--noheadings', '--units', 'k', $cmd = [
'--unbuffered', '--nosuffix', '--options', '/sbin/pvs',
'pv_name,pv_size,vg_name,pv_uuid', $device]; '--separator',
':',
'--noheadings',
'--units',
'k',
'--unbuffered',
'--nosuffix',
'--options',
'pv_name,pv_size,vg_name,pv_uuid',
$device,
];
my $pvinfo; my $pvinfo;
run_command($cmd, outfunc => sub { run_command(
$cmd,
outfunc => sub {
my $line = shift; my $line = shift;
$line = trim($line); $line = trim($line);
@ -60,7 +75,8 @@ sub lvm_pv_info {
vgname => $vgname, vgname => $vgname,
uuid => $uuid, uuid => $uuid,
}; };
}); },
);
return $pvinfo; return $pvinfo;
} }
@ -96,7 +112,12 @@ sub lvm_create_volume_group {
$cmd = ['/sbin/vgcreate', $vgname, $device]; $cmd = ['/sbin/vgcreate', $vgname, $device];
# push @$cmd, '-c', 'y' if $shared; # we do not use this yet # 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 { sub lvm_destroy_volume_group {
@ -113,8 +134,17 @@ sub lvm_destroy_volume_group {
sub lvm_vgs { sub lvm_vgs {
my ($includepvs) = @_; my ($includepvs) = @_;
my $cmd = ['/sbin/vgs', '--separator', ':', '--noheadings', '--units', 'b', my $cmd = [
'--unbuffered', '--nosuffix', '--options']; '/sbin/vgs',
'--separator',
':',
'--noheadings',
'--units',
'b',
'--unbuffered',
'--nosuffix',
'--options',
];
my $cols = [qw(vg_name vg_size vg_free lv_count)]; my $cols = [qw(vg_name vg_size vg_free lv_count)];
@ -126,20 +156,24 @@ sub lvm_vgs {
my $vgs = {}; my $vgs = {};
eval { eval {
run_command($cmd, outfunc => sub { run_command(
$cmd,
outfunc => sub {
my $line = shift; my $line = shift;
$line = trim($line); $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} //= { $vgs->{$name} //= {
size => int($size), size => int($size),
free => int($free), free => int($free),
lvcount => int($lvcount) lvcount => int($lvcount),
}; };
if (defined($pvname) && defined($pvsize) && defined($pvfree)) { if (defined($pvname) && defined($pvsize) && defined($pvfree)) {
push @{$vgs->{$name}->{pvs}}, { push @{ $vgs->{$name}->{pvs} },
{
name => $pvname, name => $pvname,
size => int($pvsize), size => int($pvsize),
free => int($pvfree), free => int($pvfree),
@ -161,24 +195,48 @@ sub lvm_vgs {
sub lvm_list_volumes { sub lvm_list_volumes {
my ($vgname) = @_; 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 = [ my $cmd = [
'/sbin/lvs', '--separator', ':', '--noheadings', '--units', 'b', '/sbin/lvs',
'--unbuffered', '--nosuffix', '--separator',
'--config', 'report/time_format="%s"', ':',
'--options', $option_list, '--noheadings',
'--units',
'b',
'--unbuffered',
'--nosuffix',
'--config',
'report/time_format="%s"',
'--options',
$option_list,
]; ];
push @$cmd, $vgname if $vgname; push @$cmd, $vgname if $vgname;
my $lvs = {}; my $lvs = {};
run_command($cmd, outfunc => sub { run_command(
$cmd,
outfunc => sub {
my $line = shift; 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); 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 !$vg_name;
return if !$lv_name; return if !$lv_name;
@ -226,11 +284,13 @@ sub properties {
return { return {
vgname => { vgname => {
description => "Volume group name.", description => "Volume group name.",
type => 'string', format => 'pve-storage-vgname', type => 'string',
format => 'pve-storage-vgname',
}, },
base => { base => {
description => "Base volume. This volume is automatically activated.", description => "Base volume. This volume is automatically activated.",
type => 'string', format => 'pve-volume-id', type => 'string',
format => 'pve-volume-id',
}, },
saferemove => { saferemove => {
description => "Zero-out data when removing LVs.", description => "Zero-out data when removing LVs.",
@ -243,7 +303,7 @@ sub properties {
tagged_only => { tagged_only => {
description => "Only use logical volumes tagged with 'pve-vm-ID'.", description => "Only use logical volumes tagged with 'pve-vm-ID'.",
type => 'boolean', type => 'boolean',
} },
}; };
} }
@ -408,20 +468,36 @@ sub free_image {
my $cmd = [ my $cmd = [
'/usr/bin/cstream', '/usr/bin/cstream',
'-i', '/dev/zero', '-i',
'-o', "/dev/$vg/del-$volname", '/dev/zero',
'-T', '10', '-o',
'-v', '1', "/dev/$vg/del-$volname",
'-b', '1048576', '-T',
'-t', "$throughput" '10',
'-v',
'1',
'-b',
'1048576',
'-t',
"$throughput",
]; ];
eval { run_command($cmd, errmsg => "zero out finished (note: 'No space left on device' is ok here)"); }; eval {
run_command(
$cmd,
errmsg => "zero out finished (note: 'No space left on device' is ok here)",
);
};
warn $@ if $@; warn $@ if $@;
$class->cluster_lock_storage($storeid, $scfg->{shared}, undef, sub { $class->cluster_lock_storage(
$storeid,
$scfg->{shared},
undef,
sub {
my $cmd = ['/sbin/lvremove', '-f', "$vg/del-$volname"]; my $cmd = ['/sbin/lvremove', '-f', "$vg/del-$volname"];
run_command($cmd, errmsg => "lvremove '$vg/del-$volname' error"); run_command($cmd, errmsg => "lvremove '$vg/del-$volname' error");
}); },
);
print "successfully removed volume $volname ($vg/del-$volname)\n"; print "successfully removed volume $volname ($vg/del-$volname)\n";
}; };
@ -482,8 +558,12 @@ sub list_images {
next if defined($vmid) && ($owner ne $vmid); next if defined($vmid) && ($owner ne $vmid);
} }
push @$res, { push @$res,
volid => $volid, format => 'raw', size => $info->{lv_size}, vmid => $owner, {
volid => $volid,
format => 'raw',
size => $info->{lv_size},
vmid => $owner,
ctime => $info->{ctime}, ctime => $info->{ctime},
}; };
} }
@ -513,11 +593,16 @@ sub activate_storage {
# In LVM2, vgscans take place automatically; # In LVM2, vgscans take place automatically;
# this is just to be sure # this is just to be sure
if ($cache->{vgs} && !$cache->{vgscaned} && if (
!$cache->{vgs}->{$scfg->{vgname}}) { $cache->{vgs}
&& !$cache->{vgscaned}
&& !$cache->{vgs}->{ $scfg->{vgname} }
) {
$cache->{vgscaned} = 1; $cache->{vgscaned} = 1;
my $cmd = ['/sbin/vgscan', '--ignorelockingfailure', '--mknodes']; my $cmd = ['/sbin/vgscan', '--ignorelockingfailure', '--mknodes'];
eval { run_command($cmd, outfunc => sub {}); }; eval {
run_command($cmd, outfunc => sub { });
};
warn $@ if $@; warn $@ if $@;
} }
@ -563,9 +648,14 @@ sub volume_resize {
my $path = $class->path($scfg, $volname); my $path = $class->path($scfg, $volname);
my $cmd = ['/sbin/lvextend', '-L', $size, $path]; my $cmd = ['/sbin/lvextend', '-L', $size, $path];
$class->cluster_lock_storage($storeid, $scfg->{shared}, undef, sub { $class->cluster_lock_storage(
$storeid,
$scfg->{shared},
undef,
sub {
run_command($cmd, errmsg => "error resizing volume '$path'"); run_command($cmd, errmsg => "error resizing volume '$path'");
}); },
);
return 1; return 1;
} }
@ -574,14 +664,29 @@ sub volume_size_info {
my ($class, $scfg, $storeid, $volname, $timeout) = @_; my ($class, $scfg, $storeid, $volname, $timeout) = @_;
my $path = $class->filesystem_path($scfg, $volname); my $path = $class->filesystem_path($scfg, $volname);
my $cmd = ['/sbin/lvs', '--separator', ':', '--noheadings', '--units', 'b', my $cmd = [
'--unbuffered', '--nosuffix', '--options', 'lv_size', $path]; '/sbin/lvs',
'--separator',
':',
'--noheadings',
'--units',
'b',
'--unbuffered',
'--nosuffix',
'--options',
'lv_size',
$path,
];
my $size; my $size;
run_command($cmd, timeout => $timeout, errmsg => "can't get size of '$path'", run_command(
$cmd,
timeout => $timeout,
errmsg => "can't get size of '$path'",
outfunc => sub { outfunc => sub {
$size = int(shift); $size = int(shift);
}); },
);
return wantarray ? ($size, 'raw', 0, undef) : $size; return wantarray ? ($size, 'raw', 0, undef) : $size;
} }
@ -611,8 +716,7 @@ sub volume_has_feature {
rename => { current => 1 }, rename => { current => 1 },
}; };
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = $class->parse_volname($volname);
$class->parse_volname($volname);
my $key = undef; my $key = undef;
if ($snapname) { if ($snapname) {
@ -628,11 +732,14 @@ sub volume_has_feature {
sub volume_export_formats { sub volume_export_formats {
my ($class, $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots) = @_; my ($class, $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots) = @_;
return () if defined($snapshot); # lvm-thin only 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 { 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" 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" die "cannot export volumes together with their snapshots in $class\n"
@ -642,11 +749,14 @@ sub volume_export {
my $file = $class->path($scfg, $volname, $storeid); my $file = $class->path($scfg, $volname, $storeid);
my $size; my $size;
# should be faster than querying LVM, also checks for the device file's availability # should be faster than querying LVM, also checks for the device file's availability
run_command(['/sbin/blockdev', '--getsize64', $file], outfunc => sub { run_command(
['/sbin/blockdev', '--getsize64', $file],
outfunc => sub {
my ($line) = @_; my ($line) = @_;
die "unexpected output from /sbin/blockdev: $line\n" if $line !~ /^(\d+)$/; die "unexpected output from /sbin/blockdev: $line\n" if $line !~ /^(\d+)$/;
$size = int($1); $size = int($1);
}); },
);
PVE::Storage::Plugin::write_common_header($fh, $size); 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));
} }
@ -659,7 +769,18 @@ sub volume_import_formats {
} }
sub volume_import { 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" 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" die "cannot import volumes together with their snapshots in $class\n"
@ -713,21 +834,14 @@ sub volume_import {
sub volume_import_write { sub volume_import_write {
my ($class, $input_fh, $output_file) = @_; my ($class, $input_fh, $output_file) = @_;
run_command(['dd', "of=$output_file", 'bs=64k'], run_command(['dd', "of=$output_file", 'bs=64k'], input => '<&' . fileno($input_fh));
input => '<&'.fileno($input_fh));
} }
sub rename_volume { sub rename_volume {
my ($class, $scfg, $storeid, $source_volname, $target_vmid, $target_volname) = @_; my ($class, $scfg, $storeid, $source_volname, $target_vmid, $target_volname) = @_;
my ( my (
undef, undef, $source_image, $source_vmid, $base_name, $base_vmid, undef, $format,
$source_image,
$source_vmid,
$base_name,
$base_vmid,
undef,
$format
) = $class->parse_volname($source_volname); ) = $class->parse_volname($source_volname);
$target_volname = $class->find_free_diskname($storeid, $scfg, $target_vmid, $format) $target_volname = $class->find_free_diskname($storeid, $scfg, $target_vmid, $format)
if !$target_volname; if !$target_volname;

View File

@ -83,7 +83,15 @@ sub run_lun_command {
$target = 'root@' . $scfg->{portal}; $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); run_command($cmd, outfunc => $output, timeout => $timeout);

View File

@ -59,25 +59,31 @@ my $execute_command = sub {
if ($exec eq 'scp') { if ($exec eq 'scp') {
$target = 'root@[' . $scfg->{portal} . ']'; $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 { } else {
$target = 'root@' . $scfg->{portal}; $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 { eval { run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout); };
run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout);
};
if ($@) { if ($@) {
$res = { $res = {
result => 0, result => 0,
msg => $err, msg => $err,
} };
} else { } else {
$res = { $res = {
result => 1, result => 1,
msg => $msg, msg => $msg,
} };
} }
return $res; return $res;
@ -104,10 +110,9 @@ my $read_config = sub {
$target = 'root@' . $scfg->{portal}; $target = 'root@' . $scfg->{portal};
my $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $luncmd, $CONFIG_FILE]; my $cmd =
eval { [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $luncmd, $CONFIG_FILE];
run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout); eval { run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout); };
};
if ($@) { if ($@) {
die $err if ($err !~ /No such file or directory/); die $err if ($err !~ /No such file or directory/);
die "No configuration found. Install iet on $scfg->{portal}" if $msg eq ''; die "No configuration found. Install iet on $scfg->{portal}" if $msg eq '';
@ -202,7 +207,12 @@ my $update_config = sub {
my $config = ''; my $config = '';
while ((my $option, my $value) = each(%$SETTINGS)) { 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') { if ($option eq 'target') {
$config = "\n\nTarget " . $SETTINGS->{target} . "\n" . $config; $config = "\n\nTarget " . $SETTINGS->{target} . "\n" . $config;
} else { } else {
@ -310,7 +320,8 @@ my $free_lu_name = sub {
my $make_lun = sub { my $make_lun = sub {
my ($scfg, $path) = @_; 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 $lun = $get_lu_name->();
my $conf = { my $conf = {

View File

@ -83,7 +83,8 @@ my $read_config = sub {
my $daemon = 0; my $daemon = 0;
foreach my $config (@CONFIG_FILES) { foreach my $config (@CONFIG_FILES) {
$err = undef; $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 { eval {
run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout); run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout);
}; };
@ -236,7 +237,8 @@ my $make_lun = sub {
my ($scfg, $path) = @_; my ($scfg, $path) = @_;
my $target = $SETTINGS->{current}; 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 @options = ();
my $lun = $get_lu_name->($target); my $lun = $get_lu_name->($target);
@ -326,7 +328,7 @@ my $parser = sub {
Storage => $storage, Storage => $storage,
Size => $size, Size => $size,
options => @options, options => @options,
} };
} }
push @$lu, $conf if $conf; push @$lu, $conf if $conf;
delete $SETTINGS->{"LogicalUnit$i"}->{$key}; delete $SETTINGS->{"LogicalUnit$i"}->{$key};
@ -540,9 +542,22 @@ sub run_lun_command {
$method = $res->{method}; $method = $res->{method};
@params = @{ $res->{params} }; @params = @{ $res->{params} };
if ($res->{cmd} eq 'scp') { 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 { } 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 { } else {
return $res; return $res;
@ -550,12 +565,18 @@ sub run_lun_command {
} else { } else {
$luncmd = $cmdmap->{cmd}; $luncmd = $cmdmap->{cmd};
$method = $cmdmap->{method}; $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 { eval { run_command($cmd, outfunc => $output, timeout => $timeout); };
run_command($cmd, outfunc => $output, timeout => $timeout);
};
if ($@ && $is_add_view) { if ($@ && $is_add_view) {
my $err = $@; my $err = $@;
if ($OLD_CONFIG) { if ($OLD_CONFIG) {
@ -565,15 +586,11 @@ sub run_lun_command {
print $fh $OLD_CONFIG; print $fh $OLD_CONFIG;
close $fh; close $fh;
$cmd = [@scp_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $file, $CONFIG_FILE]; $cmd = [@scp_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $file, $CONFIG_FILE];
eval { eval { run_command($cmd, outfunc => $output, timeout => $timeout); };
run_command($cmd, outfunc => $output, timeout => $timeout);
};
$err1 = $@ if $@; $err1 = $@ if $@;
unlink $file; unlink $file;
die "$err\n$err1" if $err1; die "$err\n$err1" if $err1;
eval { eval { run_lun_command($scfg, undef, 'add_view', 'restart'); };
run_lun_command($scfg, undef, 'add_view', 'restart');
};
die "$err\n$@" if ($@); die "$err\n$@" if ($@);
} }
die $err; die $err;

View File

@ -58,21 +58,27 @@ my $execute_remote_command = sub {
my $errfunc = sub { $err .= "$_[0]\n" }; my $errfunc = sub { $err .= "$_[0]\n" };
$target = 'root@' . $scfg->{portal}; $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 { eval { run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout); };
run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout);
};
if ($@) { if ($@) {
$res = { $res = {
result => 0, result => 0,
msg => $err, msg => $err,
} };
} else { } else {
$res = { $res = {
result => 1, result => 1,
msg => $msg, msg => $msg,
} };
} }
return $res; return $res;
@ -96,7 +102,8 @@ my $read_config = sub {
$target = 'root@' . $scfg->{portal}; $target = 'root@' . $scfg->{portal};
foreach my $oneFile (@CONFIG_FILES) { foreach my $oneFile (@CONFIG_FILES) {
my $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $luncmd, $oneFile]; my $cmd =
[@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $luncmd, $oneFile];
eval { eval {
run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout); run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout);
}; };
@ -139,7 +146,8 @@ my $parser = sub {
if ($tpg =~ /^tpg(\d+)$/) { if ($tpg =~ /^tpg(\d+)$/) {
$tpg_tag = $1; $tpg_tag = $1;
} else { } 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 $config = $get_config->($scfg);

View File

@ -39,7 +39,8 @@ sub properties {
return { return {
thinpool => { thinpool => {
description => "LVM thin pool LV name.", description => "LVM thin pool LV name.",
type => 'string', format => 'pve-storage-vgname', type => 'string',
format => 'pve-storage-vgname',
}, },
}; };
} }
@ -99,8 +100,16 @@ sub alloc_image {
$name = $class->find_free_diskname($storeid, $scfg, $vmid) $name = $class->find_free_diskname($storeid, $scfg, $vmid)
if !$name; if !$name;
my $cmd = ['/sbin/lvcreate', '-aly', '-V', "${size}k", '--name', $name, my $cmd = [
'--thinpool', "$vg/$scfg->{thinpool}" ]; '/sbin/lvcreate',
'-aly',
'-V',
"${size}k",
'--name',
$name,
'--thinpool',
"$vg/$scfg->{thinpool}",
];
run_command($cmd, errmsg => "lvcreate '$vg/$name' error"); run_command($cmd, errmsg => "lvcreate '$vg/$name' error");
@ -164,8 +173,12 @@ sub list_images {
next if defined($vmid) && ($owner ne $vmid); next if defined($vmid) && ($owner ne $vmid);
} }
push @$res, { push @$res,
volid => $volid, format => 'raw', size => $info->{lv_size}, vmid => $owner, {
volid => $volid,
format => 'raw',
size => $info->{lv_size},
vmid => $owner,
ctime => $info->{ctime}, ctime => $info->{ctime},
}; };
} }
@ -221,7 +234,10 @@ my $activate_lv = sub {
return if $lvs->{$vg}->{$lv}->{lv_state} eq 'a'; 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 $lvs->{$vg}->{$lv}->{lv_state} = 'a'; # update cache
@ -271,8 +287,7 @@ sub clone_image {
if ($snap) { if ($snap) {
$lv = "$vg/snap_${volname}_$snap"; $lv = "$vg/snap_${volname}_$snap";
} else { } else {
my ($vtype, undef, undef, undef, undef, $isBase, $format) = my ($vtype, undef, undef, undef, undef, $isBase, $format) = $class->parse_volname($volname);
$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;
@ -290,8 +305,7 @@ sub clone_image {
sub create_base { sub create_base {
my ($class, $storeid, $scfg, $volname) = @_; my ($class, $storeid, $scfg, $volname) = @_;
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = $class->parse_volname($volname);
$class->parse_volname($volname);
die "create_base not possible with base image\n" if $isBase; die "create_base not possible with base image\n" if $isBase;
@ -370,8 +384,7 @@ sub volume_has_feature {
rename => { current => 1 }, rename => { current => 1 },
}; };
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = $class->parse_volname($volname);
$class->parse_volname($volname);
my $key = undef; my $key = undef;
if ($snapname) { if ($snapname) {
@ -385,7 +398,18 @@ sub volume_has_feature {
} }
sub volume_import { 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) = my ($vtype, $name, $vmid, $basename, $basevmid, $isBase, $file_format) =
$class->parse_volname($volname); $class->parse_volname($volname);
@ -400,7 +424,7 @@ sub volume_import {
$snapshot, $snapshot,
$base_snapshot, $base_snapshot,
$with_snapshots, $with_snapshots,
$allow_rename $allow_rename,
); );
} else { } else {
my $tempname; my $tempname;
@ -425,7 +449,7 @@ sub volume_import {
$snapshot, $snapshot,
$base_snapshot, $base_snapshot,
$with_snapshots, $with_snapshots,
$allow_rename $allow_rename,
); );
($storeid, my $newname) = PVE::Storage::parse_volume_id($newvolid); ($storeid, my $newname) = PVE::Storage::parse_volume_id($newvolid);
@ -438,8 +462,10 @@ sub volume_import {
# used in LVMPlugin->volume_import # used in LVMPlugin->volume_import
sub volume_import_write { sub volume_import_write {
my ($class, $input_fh, $output_file) = @_; my ($class, $input_fh, $output_file) = @_;
run_command(['dd', "of=$output_file", 'conv=sparse', 'bs=64k'], run_command(
input => '<&'.fileno($input_fh)); ['dd', "of=$output_file", 'conv=sparse', 'bs=64k'],
input => '<&' . fileno($input_fh),
);
} }
1; 1;

View File

@ -24,9 +24,9 @@ sub nfs_is_mounted {
$mountdata = PVE::ProcFSTools::parse_proc_mounts() if !$mountdata; $mountdata = PVE::ProcFSTools::parse_proc_mounts() if !$mountdata;
return $mountpoint if grep { return $mountpoint if grep {
$_->[2] =~ /^nfs/ && $_->[2] =~ /^nfs/
$_->[0] =~ m|^\Q$source\E/?$| && && $_->[0] =~ m|^\Q$source\E/?$|
$_->[1] eq $mountpoint && $_->[1] eq $mountpoint
} @$mountdata; } @$mountdata;
return undef; return undef;
} }
@ -53,8 +53,18 @@ sub type {
sub plugindata { sub plugindata {
return { return {
content => [ { images => 1, rootdir => 1, vztmpl => 1, iso => 1, backup => 1, snippets => 1, import => 1 }, content => [
{ images => 1 }], {
images => 1,
rootdir => 1,
vztmpl => 1,
iso => 1,
backup => 1,
snippets => 1,
import => 1,
},
{ images => 1 },
],
format => [{ raw => 1, qcow2 => 1, vmdk => 1 }, 'raw'], format => [{ raw => 1, qcow2 => 1, vmdk => 1 }, 'raw'],
'sensitive-properties' => {}, 'sensitive-properties' => {},
}; };
@ -64,11 +74,13 @@ sub properties {
return { return {
export => { export => {
description => "NFS export path.", description => "NFS export path.",
type => 'string', format => 'pve-storage-path', type => 'string',
format => 'pve-storage-path',
}, },
server => { server => {
description => "Server IP or DNS name.", description => "Server IP or DNS name.",
type => 'string', format => 'pve-storage-server', type => 'string',
format => 'pve-storage-server',
}, },
}; };
} }
@ -95,7 +107,6 @@ sub options {
}; };
} }
sub check_config { sub check_config {
my ($class, $sectionId, $config, $create, $skipSchemaCheck) = @_; my ($class, $sectionId, $config, $create, $skipSchemaCheck) = @_;
@ -135,8 +146,8 @@ sub activate_storage {
# NOTE: only call mkpath when not mounted (avoid hang when NFS server is offline # NOTE: only call mkpath when not mounted (avoid hang when NFS server is offline
$class->config_aware_base_mkdir($scfg, $path); $class->config_aware_base_mkdir($scfg, $path);
die "unable to activate storage '$storeid' - " . die "unable to activate storage '$storeid' - " . "directory '$path' does not exist\n"
"directory '$path' does not exist\n" if ! -d $path; if !-d $path;
nfs_mount($server, $export, $path, $scfg->{options}); nfs_mount($server, $export, $path, $scfg->{options});
} }
@ -184,7 +195,9 @@ sub check_connection {
$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 (my $err = $@) {
if ($is_v4) { if ($is_v4) {
my $port = 2049; my $port = 2049;

View File

@ -47,11 +47,13 @@ sub properties {
# openssl s_client -connect <host>:8007 2>&1 |openssl x509 -fingerprint -sha256 # openssl s_client -connect <host>:8007 2>&1 |openssl x509 -fingerprint -sha256
fingerprint => get_standard_option('fingerprint-sha256'), fingerprint => get_standard_option('fingerprint-sha256'),
'encryption-key' => { 'encryption-key' => {
description => "Encryption key. Use 'autogen' to generate one automatically without passphrase.", description =>
"Encryption key. Use 'autogen' to generate one automatically without passphrase.",
type => 'string', type => 'string',
}, },
'master-pubkey' => { '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.", 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', type => 'string',
}, },
}; };
@ -361,8 +363,11 @@ sub run_client_cmd {
$param = [@$param, '--output-format=json'] if !$no_output; $param = [@$param, '--output-format=json'] if !$no_output;
do_raw_client_cmd($scfg, $storeid, $client_cmd, $param, do_raw_client_cmd(
outfunc => $outfunc, errmsg => 'proxmox-backup-client failed'); $scfg, $storeid, $client_cmd, $param,
outfunc => $outfunc,
errmsg => 'proxmox-backup-client failed',
);
return undef if $no_output; return undef if $no_output;
@ -390,8 +395,11 @@ sub extract_vzdump_config {
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, '-' ], do_raw_client_cmd(
outfunc => $outfunc, errmsg => 'proxmox-backup-client failed'); $scfg, $storeid, 'restore', [$name, $config_name, '-'],
outfunc => $outfunc,
errmsg => 'proxmox-backup-client failed',
);
return $config; return $config;
} }
@ -462,7 +470,8 @@ sub prune_backups {
my $mark = $backup->{keep} ? 'keep' : 'remove'; my $mark = $backup->{keep} ? 'keep' : 'remove';
$mark = 'protected' if $backup->{protected}; $mark = 'protected' if $backup->{protected};
push @{$prune_list}, { push @{$prune_list},
{
ctime => $ctime, ctime => $ctime,
mark => $mark, mark => $mark,
type => $type eq 'vm' ? 'qemu' : 'lxc', type => $type eq 'vm' ? 'qemu' : 'lxc',
@ -596,7 +605,9 @@ sub on_delete_hook {
sub parse_volname { sub parse_volname {
my ($class, $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)$!) { 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 $btype = $1;
my $bid = $2; my $bid = $2;
my $btime = $3; my $btime = $3;
@ -662,7 +673,6 @@ sub free_image {
return; return;
} }
sub list_images { sub list_images {
my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_; my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_;

View File

@ -35,16 +35,7 @@ our @COMMON_TAR_FLAGS = qw(
); );
our @SHARED_STORAGE = ( our @SHARED_STORAGE = (
'iscsi', 'iscsi', 'nfs', 'cifs', 'rbd', 'cephfs', 'iscsidirect', 'glusterfs', 'zfs', 'drbd', 'pbs',
'nfs',
'cifs',
'rbd',
'cephfs',
'iscsidirect',
'glusterfs',
'zfs',
'drbd',
'pbs',
); );
our $QCOW2_PREALLOCATION = { our $QCOW2_PREALLOCATION = {
@ -62,13 +53,16 @@ our $RAW_PREALLOCATION = {
our $MAX_VOLUMES_PER_GUEST = 1024; our $MAX_VOLUMES_PER_GUEST = 1024;
cfs_register_file ('storage.cfg', cfs_register_file(
'storage.cfg',
sub { __PACKAGE__->parse_config(@_); }, sub { __PACKAGE__->parse_config(@_); },
sub { __PACKAGE__->write_config(@_); }); sub { __PACKAGE__->write_config(@_); },
);
my %prune_option = ( my %prune_option = (
optional => 1, optional => 1,
type => 'integer', minimum => '0', type => 'integer',
minimum => '0',
format_description => 'N', format_description => 'N',
); );
@ -79,36 +73,36 @@ our $prune_backups_format = {
optional => 1, optional => 1,
}, },
'keep-last' => { 'keep-last' => {
%prune_option, %prune_option, description => 'Keep the last <N> backups.',
description => 'Keep the last <N> backups.',
}, },
'keep-hourly' => { 'keep-hourly' => {
%prune_option, %prune_option,
description => 'Keep backups for the last <N> different hours. If there is more' . description => 'Keep backups for the last <N> different hours. If there is more'
'than one backup for a single hour, only the latest one is kept.' . 'than one backup for a single hour, only the latest one is kept.',
}, },
'keep-daily' => { 'keep-daily' => {
%prune_option, %prune_option,
description => 'Keep backups for the last <N> different days. If there is more' . description => 'Keep backups for the last <N> different days. If there is more'
'than one backup for a single day, only the latest one is kept.' . 'than one backup for a single day, only the latest one is kept.',
}, },
'keep-weekly' => { 'keep-weekly' => {
%prune_option, %prune_option,
description => 'Keep backups for the last <N> different weeks. If there is more' . description => 'Keep backups for the last <N> different weeks. If there is more'
'than one backup for a single week, only the latest one is kept.' . 'than one backup for a single week, only the latest one is kept.',
}, },
'keep-monthly' => { 'keep-monthly' => {
%prune_option, %prune_option,
description => 'Keep backups for the last <N> different months. If there is more' . description => 'Keep backups for the last <N> different months. If there is more'
'than one backup for a single month, only the latest one is kept.' . 'than one backup for a single month, only the latest one is kept.',
}, },
'keep-yearly' => { 'keep-yearly' => {
%prune_option, %prune_option,
description => 'Keep backups for the last <N> different years. If there is more' . description => 'Keep backups for the last <N> different years. If there is more'
'than one backup for a single year, only the latest one is kept.' . 'than one backup for a single year, only the latest one is kept.',
}, },
}; };
PVE::JSONSchema::register_format('prune-backups', $prune_backups_format, \&validate_prune_backups); PVE::JSONSchema::register_format('prune-backups', $prune_backups_format, \&validate_prune_backups);
sub validate_prune_backups { sub validate_prune_backups {
my ($prune_backups) = @_; my ($prune_backups) = @_;
@ -124,30 +118,39 @@ sub validate_prune_backups {
return $res; return $res;
} }
register_standard_option('prune-backups', { register_standard_option(
description => "The retention options with shorter intervals are processed first " . 'prune-backups',
"with --keep-last being the very first one. Each option covers a " . {
"specific period of time. We say that backups within this period " . description => "The retention options with shorter intervals are processed first "
"are covered by this option. The next option does not take care " . . "with --keep-last being the very first one. Each option covers a "
"of already covered backups and only considers older backups.", . "specific period of time. We say that backups within this period "
. "are covered by this option. The next option does not take care "
. "of already covered backups and only considers older backups.",
optional => 1, optional => 1,
type => 'string', type => 'string',
format => 'prune-backups', format => 'prune-backups',
}); },
);
my $defaultData = { my $defaultData = {
propertyList => { propertyList => {
type => { description => "Storage type." }, type => { description => "Storage type." },
storage => get_standard_option('pve-storage-id', storage => get_standard_option(
{ completion => \&PVE::Storage::complete_storage }), 'pve-storage-id',
nodes => get_standard_option('pve-node-list', { { completion => \&PVE::Storage::complete_storage },
),
nodes => get_standard_option(
'pve-node-list',
{
description => "List of nodes for which the storage configuration applies.", description => "List of nodes for which the storage configuration applies.",
optional => 1, optional => 1,
}), },
),
content => { content => {
description => "Allowed content types.\n\nNOTE: the value " . description => "Allowed content types.\n\nNOTE: the value "
"'rootdir' is used for Containers, and value 'images' for VMs.\n", . "'rootdir' is used for Containers, and value 'images' for VMs.\n",
type => 'string', format => 'pve-storage-content-list', type => 'string',
format => 'pve-storage-content-list',
optional => 1, optional => 1,
completion => \&PVE::Storage::complete_content_type, completion => \&PVE::Storage::complete_content_type,
}, },
@ -157,22 +160,25 @@ my $defaultData = {
optional => 1, optional => 1,
}, },
maxfiles => { maxfiles => {
description => "Deprecated: use 'prune-backups' instead. " . description => "Deprecated: use 'prune-backups' instead. "
"Maximal number of backup files per VM. Use '0' for unlimited.", . "Maximal number of backup files per VM. Use '0' for unlimited.",
type => 'integer', type => 'integer',
minimum => 0, minimum => 0,
optional => 1, optional => 1,
}, },
'prune-backups' => get_standard_option('prune-backups'), 'prune-backups' => get_standard_option('prune-backups'),
'max-protected-backups' => { 'max-protected-backups' => {
description => "Maximal number of protected backups per guest. Use '-1' for unlimited.", description =>
"Maximal number of protected backups per guest. Use '-1' for unlimited.",
type => 'integer', type => 'integer',
minimum => -1, minimum => -1,
optional => 1, optional => 1,
default => "Unlimited for users with Datastore.Allocate privilege, 5 for other users", default =>
"Unlimited for users with Datastore.Allocate privilege, 5 for other users",
}, },
shared => { shared => {
description => "Indicate that this is a single storage with the same contents on all " description =>
"Indicate that this is a single storage with the same contents on all "
. "nodes (or all listed in the 'nodes' option). It will not make the contents of a " . "nodes (or all listed in the 'nodes' option). It will not make the contents of a "
. "local storage automatically accessible to other nodes, it just marks an already " . "local storage automatically accessible to other nodes, it just marks an already "
. "shared storage as such!", . "shared storage as such!",
@ -181,23 +187,29 @@ my $defaultData = {
}, },
subdir => { subdir => {
description => "Subdir to mount.", description => "Subdir to mount.",
type => 'string', format => 'pve-storage-path', type => 'string',
format => 'pve-storage-path',
optional => 1, optional => 1,
}, },
format => get_standard_option('pve-storage-image-format', { format => get_standard_option(
'pve-storage-image-format',
{
description => "Default image format.", description => "Default image format.",
optional => 1, optional => 1,
}), },
),
preallocation => { preallocation => {
description => "Preallocation mode for raw and qcow2 images. " . description => "Preallocation mode for raw and qcow2 images. "
"Using 'metadata' on raw images results in preallocation=off.", . "Using 'metadata' on raw images results in preallocation=off.",
type => 'string', enum => ['off', 'metadata', 'falloc', 'full'], type => 'string',
enum => ['off', 'metadata', 'falloc', 'full'],
default => 'metadata', default => 'metadata',
optional => 1, optional => 1,
}, },
'content-dirs' => { 'content-dirs' => {
description => "Overrides for default content type directories.", description => "Overrides for default content type directories.",
type => "string", format => "pve-dir-override-list", type => "string",
format => "pve-dir-override-list",
optional => 1, optional => 1,
}, },
options => { options => {
@ -207,7 +219,8 @@ my $defaultData = {
optional => 1, optional => 1,
}, },
port => { port => {
description => "Use this port to connect to the storage instead of the default one (for" description =>
"Use this port to connect to the storage instead of the default one (for"
. " example, with PBS or ESXi). For NFS and CIFS, use the 'options' option to" . " example, with PBS or ESXi). For NFS and CIFS, use the 'options' option to"
. " configure the port via the mount options.", . " configure the port via the mount options.",
type => 'integer', type => 'integer',
@ -285,6 +298,7 @@ sub default_format {
} }
PVE::JSONSchema::register_format('pve-storage-path', \&verify_path); PVE::JSONSchema::register_format('pve-storage-path', \&verify_path);
sub verify_path { sub verify_path {
my ($path, $noerr) = @_; my ($path, $noerr) = @_;
@ -298,12 +312,14 @@ sub verify_path {
} }
PVE::JSONSchema::register_format('pve-storage-server', \&verify_server); PVE::JSONSchema::register_format('pve-storage-server', \&verify_server);
sub verify_server { sub verify_server {
my ($server, $noerr) = @_; my ($server, $noerr) = @_;
if (!(PVE::JSONSchema::pve_verify_ip($server, 1) || if (!(
PVE::JSONSchema::pve_verify_dns_name($server, 1))) PVE::JSONSchema::pve_verify_ip($server, 1)
{ || PVE::JSONSchema::pve_verify_dns_name($server, 1)
)) {
return undef if $noerr; return undef if $noerr;
die "value does not look like a valid server name or IP address\n"; die "value does not look like a valid server name or IP address\n";
} }
@ -311,6 +327,7 @@ sub verify_server {
} }
PVE::JSONSchema::register_format('pve-storage-vgname', \&parse_lvm_name); PVE::JSONSchema::register_format('pve-storage-vgname', \&parse_lvm_name);
sub parse_lvm_name { sub parse_lvm_name {
my ($name, $noerr) = @_; my ($name, $noerr) = @_;
@ -336,6 +353,7 @@ sub parse_lvm_name {
#} #}
PVE::JSONSchema::register_format('pve-storage-portal-dns', \&verify_portal_dns); PVE::JSONSchema::register_format('pve-storage-portal-dns', \&verify_portal_dns);
sub verify_portal_dns { sub verify_portal_dns {
my ($portal, $noerr) = @_; my ($portal, $noerr) = @_;
@ -348,6 +366,7 @@ sub verify_portal_dns {
} }
PVE::JSONSchema::register_format('pve-storage-content', \&verify_content); PVE::JSONSchema::register_format('pve-storage-content', \&verify_content);
sub verify_content { sub verify_content {
my ($ct, $noerr) = @_; my ($ct, $noerr) = @_;
@ -368,6 +387,7 @@ sub verify_content {
# TODO PVE 9 - remove after doing a versioned breaks for pve-guest-common, which was using this # TODO PVE 9 - remove after doing a versioned breaks for pve-guest-common, which was using this
# format. # format.
PVE::JSONSchema::register_format('pve-storage-format', \&verify_format); PVE::JSONSchema::register_format('pve-storage-format', \&verify_format);
sub verify_format { sub verify_format {
my ($fmt, $noerr) = @_; my ($fmt, $noerr) = @_;
@ -380,6 +400,7 @@ sub verify_format {
} }
PVE::JSONSchema::register_format('pve-storage-options', \&verify_options); PVE::JSONSchema::register_format('pve-storage-options', \&verify_options);
sub verify_options { sub verify_options {
my ($value, $noerr) = @_; my ($value, $noerr) = @_;
@ -393,6 +414,7 @@ sub verify_options {
} }
PVE::JSONSchema::register_format('pve-volume-id', \&parse_volume_id); PVE::JSONSchema::register_format('pve-volume-id', \&parse_volume_id);
sub parse_volume_id { sub parse_volume_id {
my ($volid, $noerr) = @_; my ($volid, $noerr) = @_;
@ -404,6 +426,7 @@ sub parse_volume_id {
} }
PVE::JSONSchema::register_format('pve-dir-override', \&verify_dir_override); PVE::JSONSchema::register_format('pve-dir-override', \&verify_dir_override);
sub verify_dir_override { sub verify_dir_override {
my ($value, $noerr) = @_; my ($value, $noerr) = @_;
@ -411,7 +434,10 @@ sub verify_dir_override {
my ($content_type, $relative_path) = ($1, $2); my ($content_type, $relative_path) = ($1, $2);
if (verify_content($content_type, $noerr)) { if (verify_content($content_type, $noerr)) {
# linux has 4k max-path, but limit total length to lower as its concat'd for full path # linux has 4k max-path, but limit total length to lower as its concat'd for full path
if (length($relative_path) < 1023 && !(grep { length($_) >= 255 } split('/', $relative_path))) { if (
length($relative_path) < 1023
&& !(grep { length($_) >= 255 } split('/', $relative_path))
) {
return $value; return $value;
} }
} }
@ -536,8 +562,11 @@ sub parse_config {
# make sure we have a reasonable 'local:' storage # make sure we have a reasonable 'local:' storage
# we want 'local' to be always the same 'type' (on all cluster nodes) # we want 'local' to be always the same 'type' (on all cluster nodes)
if (!$ids->{local} || $ids->{local}->{type} ne 'dir' || if (
($ids->{local}->{path} && $ids->{local}->{path} ne '/var/lib/vz')) { !$ids->{local}
|| $ids->{local}->{type} ne 'dir'
|| ($ids->{local}->{path} && $ids->{local}->{path} ne '/var/lib/vz')
) {
$ids->{local} = { $ids->{local} = {
type => 'dir', type => 'dir',
priority => 0, # force first entry priority => 0, # force first entry
@ -690,11 +719,15 @@ sub parse_volname {
return ('backup', $fn, undef, undef, undef, undef, 'raw'); return ('backup', $fn, undef, undef, undef, undef, 'raw');
} elsif ($volname =~ m!^snippets/([^/]+)$!) { } elsif ($volname =~ m!^snippets/([^/]+)$!) {
return ('snippets', $1, undef, undef, undef, undef, 'raw'); return ('snippets', $1, undef, undef, undef, undef, 'raw');
} elsif ($volname =~ m!^import/(${PVE::Storage::SAFE_CHAR_WITH_WHITESPACE_CLASS_RE}+\.ova\/${PVE::Storage::OVA_CONTENT_RE_1})$!) { } elsif ($volname =~
m!^import/(${PVE::Storage::SAFE_CHAR_WITH_WHITESPACE_CLASS_RE}+\.ova\/${PVE::Storage::OVA_CONTENT_RE_1})$!
) {
my $packed_image = $1; my $packed_image = $1;
my $format = $2; my $format = $2;
return ('import', $packed_image, undef, undef, undef, undef, "ova+$format"); return ('import', $packed_image, undef, undef, undef, undef, "ova+$format");
} elsif ($volname =~ m!^import/(${PVE::Storage::SAFE_CHAR_WITH_WHITESPACE_CLASS_RE}+$PVE::Storage::IMPORT_EXT_RE_1)$!) { } elsif ($volname =~
m!^import/(${PVE::Storage::SAFE_CHAR_WITH_WHITESPACE_CLASS_RE}+$PVE::Storage::IMPORT_EXT_RE_1)$!
) {
return ('import', $1, undef, undef, undef, undef, $2); return ('import', $1, undef, undef, undef, undef, $2);
} }
@ -731,8 +764,7 @@ sub get_subdir {
sub filesystem_path { sub filesystem_path {
my ($class, $scfg, $volname, $snapname) = @_; my ($class, $scfg, $volname, $snapname) = @_;
my ($vtype, $name, $vmid, undef, undef, $isBase, $format) = my ($vtype, $name, $vmid, undef, undef, $isBase, $format) = $class->parse_volname($volname);
$class->parse_volname($volname);
# Note: qcow2/qed has internal snapshot, so path is always # Note: qcow2/qed has internal snapshot, so path is always
# the same (with or without snapshot => same file). # the same (with or without snapshot => same file).
@ -778,15 +810,17 @@ sub create_base {
my $newname = $name; my $newname = $name;
$newname =~ s/^vm-/base-/; $newname =~ s/^vm-/base-/;
my $newvolname = $basename ? "$basevmid/$basename/$vmid/$newname" : my $newvolname =
"$vmid/$newname"; $basename
? "$basevmid/$basename/$vmid/$newname"
: "$vmid/$newname";
my $newpath = $class->filesystem_path($scfg, $newvolname); my $newpath = $class->filesystem_path($scfg, $newvolname);
die "file '$newpath' already exists\n" if -f $newpath; die "file '$newpath' already exists\n" if -f $newpath;
rename($path, $newpath) || rename($path, $newpath)
die "rename '$path' to '$newpath' failed - $!\n"; || die "rename '$path' to '$newpath' failed - $!\n";
# We try to protect base volume # We try to protect base volume
@ -807,7 +841,7 @@ my $get_vm_disk_number = sub {
my $type = $scfg->{type}; my $type = $scfg->{type};
my $def = { %{ $defaultData->{plugindata}->{$type} } }; my $def = { %{ $defaultData->{plugindata}->{$type} } };
my $valid = $def->{format}[0]; my $valid = $def->{format}->[0];
if ($valid->{subvol}) { if ($valid->{subvol}) {
$disk_regex = qr/(vm|base|subvol|basevol)-$vmid-disk-(\d+)/; $disk_regex = qr/(vm|base|subvol|basevol)-$vmid-disk-(\d+)/;
} }
@ -838,7 +872,7 @@ sub get_next_vm_diskname {
} }
} }
die "unable to allocate an image name for VM $vmid in storage '$storeid'\n" die "unable to allocate an image name for VM $vmid in storage '$storeid'\n";
} }
sub find_free_diskname { sub find_free_diskname {
@ -885,8 +919,17 @@ sub clone_image {
eval { eval {
local $CWD = $imagedir; local $CWD = $imagedir;
my $cmd = ['/usr/bin/qemu-img', 'create', '-b', "../$basevmid/$basename", my $cmd = [
'-F', $format, '-f', 'qcow2', $path]; '/usr/bin/qemu-img',
'create',
'-b',
"../$basevmid/$basename",
'-F',
$format,
'-f',
'qcow2',
$path,
];
run_command($cmd); run_command($cmd);
}; };
@ -1040,7 +1083,8 @@ sub file_size_info {
# TODO PVE 9 - consider upgrading to "die" if an unsupported format is passed in after # TODO PVE 9 - consider upgrading to "die" if an unsupported format is passed in after
# evaluating breakage potential. # evaluating breakage potential.
if ($file_format && !grep { $_ eq $file_format } @checked_qemu_img_formats) { if ($file_format && !grep { $_ eq $file_format } @checked_qemu_img_formats) {
warn "file_size_info: '$filename': falling back to 'raw' from unknown format '$file_format'\n"; warn
"file_size_info: '$filename': falling back to 'raw' from unknown format '$file_format'\n";
$file_format = 'raw'; $file_format = 'raw';
} }
my $cmd = ['/usr/bin/qemu-img', 'info', '--output=json', $filename]; my $cmd = ['/usr/bin/qemu-img', 'info', '--output=json', $filename];
@ -1049,7 +1093,8 @@ sub file_size_info {
my $json = ''; my $json = '';
my $err_output = ''; my $err_output = '';
eval { eval {
run_command($cmd, run_command(
$cmd,
timeout => $timeout, timeout => $timeout,
outfunc => sub { $json .= shift }, outfunc => sub { $json .= shift },
errfunc => sub { $err_output .= shift . "\n" }, errfunc => sub { $err_output .= shift . "\n" },
@ -1084,7 +1129,8 @@ sub file_size_info {
} }
} }
my ($size, $format, $used, $parent) = $info->@{qw(virtual-size format actual-size backing-filename)}; my ($size, $format, $used, $parent) =
$info->@{qw(virtual-size format actual-size backing-filename)};
die "backing file not allowed for untrusted image '$filename'!\n" if $untrusted && $parent; die "backing file not allowed for untrusted image '$filename'!\n" if $untrusted && $parent;
@ -1241,6 +1287,7 @@ sub volume_snapshot_needs_fsfreeze {
return 0; return 0;
} }
sub storage_can_replicate { sub storage_can_replicate {
my ($class, $scfg, $storeid, $format) = @_; my ($class, $scfg, $storeid, $format) = @_;
@ -1286,8 +1333,8 @@ sub volume_has_feature {
return 0 if $class->can('api') && $class->api() < 10; return 0 if $class->can('api') && $class->api() < 10;
} }
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase, $format) =
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase, $format) = $class->parse_volname($volname); $class->parse_volname($volname);
my $key = undef; my $key = undef;
if ($snapname) { if ($snapname) {
@ -1322,9 +1369,7 @@ sub list_images {
next if !$vollist && defined($vmid) && ($owner ne $vmid); next if !$vollist && defined($vmid) && ($owner ne $vmid);
my ($size, undef, $used, $parent, $ctime) = eval { my ($size, undef, $used, $parent, $ctime) = eval { file_size_info($fn, undef, $format); };
file_size_info($fn, undef, $format);
};
if (my $err = $@) { if (my $err = $@) {
die $err if $err !~ m/Image is not in \S+ format$/; die $err if $err !~ m/Image is not in \S+ format$/;
warn "image '$fn' is not in expected format '$format', querying as raw\n"; warn "image '$fn' is not in expected format '$format', querying as raw\n";
@ -1347,8 +1392,12 @@ sub list_images {
} }
my $info = { my $info = {
volid => $volid, format => $format, volid => $volid,
size => $size, vmid => $owner, used => $used, parent => $parent format => $format,
size => $size,
vmid => $owner,
used => $used,
parent => $parent,
}; };
$info->{ctime} = $ctime if $ctime; $info->{ctime} = $ctime if $ctime;
@ -1405,7 +1454,8 @@ my $get_subdir_files = sub {
my $notes_fn = $original . NOTES_EXT; my $notes_fn = $original . NOTES_EXT;
if (-f $notes_fn) { if (-f $notes_fn) {
my $notes = PVE::Tools::file_read_firstline($notes_fn); my $notes = PVE::Tools::file_read_firstline($notes_fn);
$info->{notes} = eval { decode('UTF-8', $notes, 1) } // $notes if defined($notes); $info->{notes} = eval { decode('UTF-8', $notes, 1) } // $notes
if defined($notes);
} }
$info->{protected} = 1 if -e PVE::Storage::protection_file_path($original); $info->{protected} = 1 if -e PVE::Storage::protection_file_path($original);
@ -1416,7 +1466,9 @@ my $get_subdir_files = sub {
format => 'snippet', format => 'snippet',
}; };
} elsif ($tt eq 'import') { } elsif ($tt eq 'import') {
next if $fn !~ m!/(${PVE::Storage::SAFE_CHAR_CLASS_RE}+$PVE::Storage::IMPORT_EXT_RE_1)$!i; next
if $fn !~
m!/(${PVE::Storage::SAFE_CHAR_CLASS_RE}+$PVE::Storage::IMPORT_EXT_RE_1)$!i;
$info = { volid => "$sid:import/$1", format => "$2" }; $info = { volid => "$sid:import/$1", format => "$2" };
} }
@ -1519,8 +1571,8 @@ sub activate_storage {
# this path test may hang indefinitely on unresponsive mounts # this path test may hang indefinitely on unresponsive mounts
my $timeout = 2; my $timeout = 2;
if (!PVE::Tools::run_fork_with_timeout($timeout, sub { -d $path })) { if (!PVE::Tools::run_fork_with_timeout($timeout, sub { -d $path })) {
die "unable to activate storage '$storeid' - " . die "unable to activate storage '$storeid' - "
"directory '$path' does not exist or is unreachable\n"; . "directory '$path' does not exist or is unreachable\n";
} }
# TODO: mkdir is basically deprecated since 8.0, but we don't warn here until 8.4 or 9.0, as we # TODO: mkdir is basically deprecated since 8.0, but we don't warn here until 8.4 or 9.0, as we
@ -1720,7 +1772,8 @@ sub read_common_header($) {
# Export a volume into a file handle as a stream of desired format. # Export a volume into a file handle as a stream of desired format.
sub volume_export { 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)
= @_;
my $err_msg = "volume export format $format not available for $class\n"; my $err_msg = "volume export format $format not available for $class\n";
if ($scfg->{path} && !defined($snapshot) && !defined($base_snapshot)) { if ($scfg->{path} && !defined($snapshot) && !defined($base_snapshot)) {
@ -1732,23 +1785,42 @@ sub volume_export {
die $err_msg if $with_snapshots || $file_format eq 'subvol'; die $err_msg if $with_snapshots || $file_format eq 'subvol';
write_common_header($fh, $size); write_common_header($fh, $size);
if ($file_format eq 'raw') { if ($file_format eq 'raw') {
run_command(['dd', "if=$file", "bs=4k", "status=progress"], output => '>&'.fileno($fh)); run_command(
['dd', "if=$file", "bs=4k", "status=progress"],
output => '>&' . fileno($fh),
);
} else { } else {
run_command(['qemu-img', 'convert', '-f', $file_format, '-O', 'raw', $file, '/dev/stdout'], run_command(
output => '>&'.fileno($fh)); [
'qemu-img',
'convert',
'-f',
$file_format,
'-O',
'raw',
$file,
'/dev/stdout',
],
output => '>&' . fileno($fh),
);
} }
return; return;
} elsif ($format =~ /^(qcow2|vmdk)\+size$/) { } elsif ($format =~ /^(qcow2|vmdk)\+size$/) {
my $data_format = $1; my $data_format = $1;
die $err_msg if !$with_snapshots || $file_format ne $data_format; die $err_msg if !$with_snapshots || $file_format ne $data_format;
write_common_header($fh, $size); write_common_header($fh, $size);
run_command(['dd', "if=$file", "bs=4k", "status=progress"], output => '>&'.fileno($fh)); run_command(
['dd', "if=$file", "bs=4k", "status=progress"],
output => '>&' . fileno($fh),
);
return; return;
} elsif ($format eq 'tar+size') { } elsif ($format eq 'tar+size') {
die $err_msg if $file_format ne 'subvol'; die $err_msg if $file_format ne 'subvol';
write_common_header($fh, $size); write_common_header($fh, $size);
run_command(['tar', @COMMON_TAR_FLAGS, '-cf', '-', '-C', $file, '.'], run_command(
output => '>&'.fileno($fh)); ['tar', @COMMON_TAR_FLAGS, '-cf', '-', '-C', $file, '.'],
output => '>&' . fileno($fh),
);
return; return;
} }
} }
@ -1772,7 +1844,18 @@ sub volume_export_formats {
# Import data from a stream, creating a new or replacing or adding to an existing volume. # Import data from a stream, creating a new or replacing or adding to an existing volume.
sub volume_import { 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" die "volume import format '$format' not available for $class\n"
if $format !~ /^(raw|tar|qcow2|vmdk)\+size$/; if $format !~ /^(raw|tar|qcow2|vmdk)\+size$/;
@ -1813,11 +1896,15 @@ sub volume_import {
my ($file) = $class->path($scfg, $volname, $storeid) my ($file) = $class->path($scfg, $volname, $storeid)
or die "internal error: failed to get path to newly allocated volume $volname\n"; or die "internal error: failed to get path to newly allocated volume $volname\n";
if ($data_format eq 'raw' || $data_format eq 'qcow2' || $data_format eq 'vmdk') { if ($data_format eq 'raw' || $data_format eq 'qcow2' || $data_format eq 'vmdk') {
run_command(['dd', "of=$file", 'conv=sparse', 'bs=64k'], run_command(
input => '<&'.fileno($fh)); ['dd', "of=$file", 'conv=sparse', 'bs=64k'],
input => '<&' . fileno($fh),
);
} elsif ($data_format eq 'tar') { } elsif ($data_format eq 'tar') {
run_command(['tar', @COMMON_TAR_FLAGS, '-C', $file, '-xf', '-'], run_command(
input => '<&'.fileno($fh)); ['tar', @COMMON_TAR_FLAGS, '-C', $file, '-xf', '-'],
input => '<&' . fileno($fh),
);
} else { } else {
die "volume import format '$format' not available for $class"; die "volume import format '$format' not available for $class";
} }
@ -1851,13 +1938,7 @@ sub rename_volume {
die "no path found\n" if !$scfg->{path}; die "no path found\n" if !$scfg->{path};
my ( my (
undef, undef, $source_image, $source_vmid, $base_name, $base_vmid, undef, $format,
$source_image,
$source_vmid,
$base_name,
$base_vmid,
undef,
$format
) = $class->parse_volname($source_volname); ) = $class->parse_volname($source_volname);
$target_volname = $class->find_free_diskname($storeid, $scfg, $target_vmid, $format, 1) $target_volname = $class->find_free_diskname($storeid, $scfg, $target_vmid, $format, 1)
@ -1874,8 +1955,8 @@ sub rename_volume {
my $base = $base_name ? "${base_vmid}/${base_name}/" : ''; my $base = $base_name ? "${base_vmid}/${base_name}/" : '';
rename($old_path, $new_path) || rename($old_path, $new_path)
die "rename '$old_path' to '$new_path' failed - $!\n"; || die "rename '$old_path' to '$new_path' failed - $!\n";
return "${storeid}:${base}${target_vmid}/${target_volname}"; return "${storeid}:${base}${target_vmid}/${target_volname}";
} }

View File

@ -10,7 +10,7 @@ use Net::IP;
use POSIX qw(ceil); use POSIX qw(ceil);
use PVE::CephConfig; 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::JSONSchema qw(get_standard_option);
use PVE::ProcFSTools; use PVE::ProcFSTools;
use PVE::RADOS; use PVE::RADOS;
@ -47,7 +47,7 @@ my sub get_rbd_path {
$path .= "/$scfg->{namespace}" if defined($scfg->{namespace}); $path .= "/$scfg->{namespace}" if defined($scfg->{namespace});
$path .= "/$volume" if defined($volume); $path .= "/$volume" if defined($volume);
return $path; return $path;
}; }
my sub get_rbd_dev_path { my sub get_rbd_dev_path {
my ($scfg, $storeid, $volume) = @_; my ($scfg, $storeid, $volume) = @_;
@ -106,7 +106,8 @@ my $rbd_cmd = sub {
} }
push @$cmd, '-c', $cmd_option->{ceph_conf} if ($cmd_option->{ceph_conf}); 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, '-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, '-n', "client.$cmd_option->{userid}" if ($cmd_option->{userid});
push @$cmd, '--keyring', $cmd_option->{keyring} if ($cmd_option->{keyring}); push @$cmd, '--keyring', $cmd_option->{keyring} if ($cmd_option->{keyring});
@ -144,11 +145,13 @@ my $krbd_feature_update = sub {
my $to_enable = join(',', grep { !$active_features->{$_} } @enable); my $to_enable = join(',', grep { !$active_features->{$_} } @enable);
if ($to_disable) { if ($to_disable) {
print "disable RBD image features this kernel RBD drivers is not compatible with: $to_disable\n"; 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); my $cmd = $rbd_cmd->($scfg, $storeid, 'feature', 'disable', $name, $to_disable);
run_rbd_command( run_rbd_command(
$cmd, $cmd,
errmsg => "could not disable krbd-incompatible image features '$to_disable' for rbd image: $name", errmsg =>
"could not disable krbd-incompatible image features '$to_disable' for rbd image: $name",
); );
} }
if ($to_enable) { if ($to_enable) {
@ -157,7 +160,8 @@ my $krbd_feature_update = sub {
my $cmd = $rbd_cmd->($scfg, $storeid, 'feature', 'enable', $name, $to_enable); my $cmd = $rbd_cmd->($scfg, $storeid, 'feature', 'enable', $name, $to_enable);
run_rbd_command( run_rbd_command(
$cmd, $cmd,
errmsg => "could not enable krbd-compatible image features '$to_enable' for rbd image: $name", errmsg =>
"could not enable krbd-compatible image features '$to_enable' for rbd image: $name",
); );
}; };
warn "$@" if $@; warn "$@" if $@;
@ -174,7 +178,9 @@ sub run_rbd_command {
# at least 1 child(ren) in pool cephstor1 # at least 1 child(ren) in pool cephstor1
$args{errfunc} = sub { $args{errfunc} = sub {
my $line = shift; my $line = shift;
if ($line =~ m/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+ [0-9a-f]+ [\-\d]+ librbd: (.*)$/) { if ($line =~
m/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+ [0-9a-f]+ [\-\d]+ librbd: (.*)$/
) {
$lasterr = "$1\n"; $lasterr = "$1\n";
} else { } else {
$lasterr = $line; $lasterr = $line;
@ -225,7 +231,7 @@ sub rbd_ls {
name => $image, name => $image,
size => $el->{size}, size => $el->{size},
parent => $get_parent_image_name->($el->{parent}), parent => $get_parent_image_name->($el->{parent}),
vmid => $owner vmid => $owner,
}; };
} }
@ -238,7 +244,12 @@ sub rbd_ls_snap {
my $cmd = $rbd_cmd->($scfg, $storeid, 'snap', 'ls', $name, '--format', 'json'); my $cmd = $rbd_cmd->($scfg, $storeid, 'snap', 'ls', $name, '--format', 'json');
my $raw = ''; 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; my $list;
if ($raw =~ m/^(\[.*\])$/s) { # untaint if ($raw =~ m/^(\[.*\])$/s) { # untaint
@ -291,7 +302,8 @@ sub rbd_volume_info {
} }
$volume->{parent} = $get_parent_image_name->($volume->{parent}); $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)}; return $volume->@{qw(size parent format protected features)};
} }
@ -341,7 +353,11 @@ my sub rbd_volume_exists {
my $cmd = $rbd_cmd->($scfg, $storeid, 'ls', '--format', 'json'); my $cmd = $rbd_cmd->($scfg, $storeid, 'ls', '--format', 'json');
my $raw = ''; my $raw = '';
run_rbd_command( 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; my $list;
if ($raw =~ m/^(\[.*\])$/s) { # untaint if ($raw =~ m/^(\[.*\])$/s) { # untaint
@ -375,7 +391,8 @@ sub properties {
return { return {
monhost => { monhost => {
description => "IP addresses of monitors (for external clusters).", description => "IP addresses of monitors (for external clusters).",
type => 'string', format => 'pve-storage-portal-dns-list', type => 'string',
format => 'pve-storage-portal-dns-list',
}, },
pool => { pool => {
description => "Pool.", description => "Pool.",
@ -522,8 +539,7 @@ sub create_base {
my $snap = '__base__'; my $snap = '__base__';
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = $class->parse_volname($volname);
$class->parse_volname($volname);
die "create_base not possible with base image\n" if $isBase; die "create_base not possible with base image\n" if $isBase;
@ -541,9 +557,7 @@ sub create_base {
my $newvolname = $basename ? "$basename/$newname" : "$newname"; my $newvolname = $basename ? "$basename/$newname" : "$newname";
my $cmd = $rbd_cmd->( my $cmd = $rbd_cmd->(
$scfg, $scfg, $storeid, 'rename',
$storeid,
'rename',
get_rbd_path($scfg, $name), get_rbd_path($scfg, $name),
get_rbd_path($scfg, $newname), get_rbd_path($scfg, $newname),
); );
@ -573,8 +587,7 @@ sub clone_image {
my $snap = '__base__'; my $snap = '__base__';
$snap = $snapname if length $snapname; $snap = $snapname if length $snapname;
my ($vtype, $basename, $basevmid, undef, undef, $isBase) = my ($vtype, $basename, $basevmid, undef, undef, $isBase) = $class->parse_volname($volname);
$class->parse_volname($volname);
die "$volname is not a base image and snapname is not provided\n" die "$volname is not a base image and snapname is not provided\n"
if !$isBase && !length($snapname); if !$isBase && !length($snapname);
@ -584,7 +597,8 @@ sub clone_image {
warn "clone $volname: $basename snapname $snap to $name\n"; warn "clone $volname: $basename snapname $snap to $name\n";
if (length($snapname)) { 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) { if (!$protected) {
my $cmd = $rbd_cmd->($scfg, $storeid, 'snap', 'protect', $volname, '--snap', $snapname); my $cmd = $rbd_cmd->($scfg, $storeid, 'snap', 'protect', $volname, '--snap', $snapname);
@ -596,8 +610,7 @@ sub clone_image {
$newvol = $name if length($snapname); $newvol = $name if length($snapname);
my @options = ( my @options = (
get_rbd_path($scfg, $basename), get_rbd_path($scfg, $basename), '--snap', $snap,
'--snap', $snap,
); );
push @options, ('--data-pool', $scfg->{'data-pool'}) if $scfg->{'data-pool'}; push @options, ('--data-pool', $scfg->{'data-pool'}) if $scfg->{'data-pool'};
@ -610,15 +623,13 @@ sub clone_image {
sub alloc_image { sub alloc_image {
my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_; my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
die "illegal name '$name' - should be 'vm-$vmid-*'\n" 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; $name = $class->find_free_diskname($storeid, $scfg, $vmid) if !$name;
my @options = ( my @options = (
'--image-format' , 2, '--image-format', 2, '--size', int(($size + 1023) / 1024),
'--size', int(($size + 1023) / 1024),
); );
push @options, ('--data-pool', $scfg->{'data-pool'}) if $scfg->{'data-pool'}; push @options, ('--data-pool', $scfg->{'data-pool'}) if $scfg->{'data-pool'};
@ -631,9 +642,7 @@ sub alloc_image {
sub free_image { sub free_image {
my ($class, $storeid, $scfg, $volname, $isBase) = @_; my ($class, $storeid, $scfg, $volname, $isBase) = @_;
my ($vtype, $name, $vmid, undef, undef, undef) = my ($vtype, $name, $vmid, undef, undef, undef) = $class->parse_volname($volname);
$class->parse_volname($volname);
my $snaps = rbd_ls_snap($scfg, $storeid, $name); my $snaps = rbd_ls_snap($scfg, $storeid, $name);
foreach my $snap (keys %$snaps) { foreach my $snap (keys %$snaps) {
@ -790,7 +799,8 @@ sub volume_resize {
my ($vtype, $name, $vmid) = $class->parse_volname($volname); 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"); run_rbd_command($cmd, errmsg => "rbd resize '$volname' error");
return undef; return undef;
} }
@ -867,7 +877,8 @@ sub volume_export_formats {
my ($class, $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots) = @_; my ($class, $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots) = @_;
return $class->volume_import_formats( return $class->volume_import_formats(
$scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots); $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots,
);
} }
sub volume_export { sub volume_export {
@ -961,13 +972,7 @@ sub rename_volume {
my ($class, $scfg, $storeid, $source_volname, $target_vmid, $target_volname) = @_; my ($class, $scfg, $storeid, $source_volname, $target_vmid, $target_volname) = @_;
my ( my (
undef, undef, $source_image, $source_vmid, $base_name, $base_vmid, undef, $format,
$source_image,
$source_vmid,
$base_name,
$base_vmid,
undef,
$format
) = $class->parse_volname($source_volname); ) = $class->parse_volname($source_volname);
$target_volname = $class->find_free_diskname($storeid, $scfg, $target_vmid, $format) $target_volname = $class->find_free_diskname($storeid, $scfg, $target_vmid, $format)
if !$target_volname; if !$target_volname;

View File

@ -14,7 +14,6 @@ use PVE::Storage::LunCmd::Istgt;
use PVE::Storage::LunCmd::Iet; use PVE::Storage::LunCmd::Iet;
use PVE::Storage::LunCmd::LIO; use PVE::Storage::LunCmd::LIO;
my @ssh_opts = ('-o', 'BatchMode=yes'); my @ssh_opts = ('-o', 'BatchMode=yes');
my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts); my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts);
my $id_rsa_path = '/etc/pve/priv/zfs'; my $id_rsa_path = '/etc/pve/priv/zfs';
@ -61,7 +60,8 @@ sub zfs_request {
if ($lun_cmds->{$method}) { if ($lun_cmds->{$method}) {
if ($scfg->{iscsiprovider} eq 'comstar') { 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') { } elsif ($scfg->{iscsiprovider} eq 'istgt') {
$msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params); $msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params);
} elsif ($scfg->{iscsiprovider} eq 'iet') { } elsif ($scfg->{iscsiprovider} eq 'iet') {
@ -252,8 +252,7 @@ sub create_base {
my $snap = '__base__'; my $snap = '__base__';
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = $class->parse_volname($volname);
$class->parse_volname($volname);
die "create_base not possible with base image\n" if $isBase; die "create_base not possible with base image\n" if $isBase;
@ -376,8 +375,7 @@ sub volume_has_feature {
copy => { base => 1, current => 1 }, copy => { base => 1, current => 1 },
}; };
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = $class->parse_volname($volname);
$class->parse_volname($volname);
my $key = undef; my $key = undef;

View File

@ -38,7 +38,8 @@ sub properties {
}, },
mountpoint => { mountpoint => {
description => "mount point", description => "mount point",
type => 'string', format => 'pve-storage-path', type => 'string',
format => 'pve-storage-path',
}, },
}; };
} }
@ -129,8 +130,8 @@ sub on_add_hook {
if (defined($cfg_mountpoint)) { if (defined($cfg_mountpoint)) {
if (defined($mountpoint) && !($cfg_mountpoint =~ m|^\Q$mountpoint\E/?$|)) { if (defined($mountpoint) && !($cfg_mountpoint =~ m|^\Q$mountpoint\E/?$|)) {
warn "warning for $storeid - mountpoint: $cfg_mountpoint " . warn "warning for $storeid - mountpoint: $cfg_mountpoint "
"does not match current mount point: $mountpoint\n"; . "does not match current mount point: $mountpoint\n";
} }
} else { } else {
$scfg->{mountpoint} = $mountpoint; $scfg->{mountpoint} = $mountpoint;
@ -286,8 +287,8 @@ sub list_images {
sub zfs_get_properties { sub zfs_get_properties {
my ($class, $scfg, $properties, $dataset, $timeout) = @_; my ($class, $scfg, $properties, $dataset, $timeout) = @_;
my $result = $class->zfs_request($scfg, $timeout, 'get', '-o', 'value', my $result =
'-Hp', $properties, $dataset); $class->zfs_request($scfg, $timeout, 'get', '-o', 'value', '-Hp', $properties, $dataset);
my @values = split /\n/, $result; my @values = split /\n/, $result;
return wantarray ? @values : $values[0]; return wantarray ? @values : $values[0];
} }
@ -336,8 +337,8 @@ sub zfs_create_subvol {
my $dataset = "$scfg->{pool}/$volname"; my $dataset = "$scfg->{pool}/$volname";
my $quota = $size ? "${size}k" : "none"; my $quota = $size ? "${size}k" : "none";
my $cmd = ['create', '-o', 'acltype=posixacl', '-o', 'xattr=sa', my $cmd =
'-o', "refquota=${quota}", $dataset]; ['create', '-o', 'acltype=posixacl', '-o', 'xattr=sa', '-o', "refquota=${quota}", $dataset];
$class->zfs_request($scfg, undef, @$cmd); $class->zfs_request($scfg, undef, @$cmd);
} }
@ -447,11 +448,11 @@ sub status {
sub volume_size_info { sub volume_size_info {
my ($class, $scfg, $storeid, $volname, $timeout) = @_; my ($class, $scfg, $storeid, $volname, $timeout) = @_;
my (undef, $vname, undef, $parent, undef, undef, $format) = my (undef, $vname, undef, $parent, undef, undef, $format) = $class->parse_volname($volname);
$class->parse_volname($volname);
my $attr = $format eq 'subvol' ? 'refquota' : 'volsize'; 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; $used = ($used =~ /^(\d+)$/) ? $1 : 0;
@ -639,11 +640,27 @@ sub clone_image {
my $name = $class->find_free_diskname($storeid, $scfg, $vmid, $format); my $name = $class->find_free_diskname($storeid, $scfg, $vmid, $format);
if ($format eq 'subvol') { if ($format eq 'subvol') {
my $size = $class->zfs_request($scfg, undef, 'list', '-Hp', '-o', 'refquota', "$scfg->{pool}/$basename"); my $size = $class->zfs_request(
$scfg, undef, 'list', '-Hp', '-o', 'refquota', "$scfg->{pool}/$basename",
);
chomp($size); chomp($size);
$class->zfs_request($scfg, undef, 'clone', "$scfg->{pool}/$basename\@$snap", "$scfg->{pool}/$name", '-o', "refquota=$size"); $class->zfs_request(
$scfg,
undef,
'clone',
"$scfg->{pool}/$basename\@$snap",
"$scfg->{pool}/$name",
'-o',
"refquota=$size",
);
} else { } 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"; return "$basename/$name";
@ -681,8 +698,7 @@ sub volume_resize {
my $new_size = int($size / 1024); my $new_size = int($size / 1024);
my (undef, $vname, undef, undef, undef, undef, $format) = my (undef, $vname, undef, undef, undef, undef, $format) = $class->parse_volname($volname);
$class->parse_volname($volname);
my $attr = $format eq 'subvol' ? 'refquota' : 'volsize'; my $attr = $format eq 'subvol' ? 'refquota' : 'volsize';
@ -718,8 +734,7 @@ sub volume_has_feature {
rename => { current => 1 }, rename => { current => 1 },
}; };
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = $class->parse_volname($volname);
$class->parse_volname($volname);
my $key = undef; my $key = undef;
@ -735,7 +750,8 @@ sub volume_has_feature {
} }
sub volume_export { 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" die "unsupported export stream format for $class: $format\n"
if $format ne 'zfs'; if $format ne 'zfs';
@ -776,7 +792,18 @@ sub volume_export_formats {
} }
sub volume_import { 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" die "unsupported import stream format for $class: $format\n"
if $format ne 'zfs'; if $format ne 'zfs';
@ -790,8 +817,11 @@ sub volume_import {
my $zfspath = "$scfg->{pool}/$dataset"; my $zfspath = "$scfg->{pool}/$dataset";
my $suffix = defined($base_snapshot) ? "\@$base_snapshot" : ''; my $suffix = defined($base_snapshot) ? "\@$base_snapshot" : '';
my $exists = 0 == run_command(['zfs', 'get', '-H', 'name', $zfspath.$suffix], my $exists = 0 == run_command(
noerr => 1, quiet => 1); ['zfs', 'get', '-H', 'name', $zfspath . $suffix],
noerr => 1,
quiet => 1,
);
if (defined($base_snapshot)) { 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) { } elsif ($exists) {
@ -817,20 +847,16 @@ sub volume_import {
sub volume_import_formats { sub volume_import_formats {
my ($class, $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots) = @_; 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 { sub rename_volume {
my ($class, $scfg, $storeid, $source_volname, $target_vmid, $target_volname) = @_; my ($class, $scfg, $storeid, $source_volname, $target_vmid, $target_volname) = @_;
my ( my (
undef, undef, $source_image, $source_vmid, $base_name, $base_vmid, undef, $format,
$source_image,
$source_vmid,
$base_name,
$base_vmid,
undef,
$format
) = $class->parse_volname($source_volname); ) = $class->parse_volname($source_volname);
$target_volname = $class->find_free_diskname($storeid, $scfg, $target_vmid, $format) $target_volname = $class->find_free_diskname($storeid, $scfg, $target_vmid, $format)
if !$target_volname; if !$target_volname;
@ -839,8 +865,11 @@ sub rename_volume {
my $source_zfspath = "${pool}/${source_image}"; my $source_zfspath = "${pool}/${source_image}";
my $target_zfspath = "${pool}/${target_volname}"; my $target_zfspath = "${pool}/${target_volname}";
my $exists = 0 == run_command(['zfs', 'get', '-H', 'name', $target_zfspath], my $exists = 0 == run_command(
noerr => 1, quiet => 1); ['zfs', 'get', '-H', 'name', $target_zfspath],
noerr => 1,
quiet => 1,
);
die "target volume '${target_volname}' already exists\n" if $exists; die "target volume '${target_volname}' already exists\n" if $exists;
$class->zfs_request($scfg, 5, 'rename', ${source_zfspath}, ${target_zfspath}); $class->zfs_request($scfg, 5, 'rename', ${source_zfspath}, ${target_zfspath});

View File

@ -9,7 +9,6 @@ use Test::More;
use PVE::CephConfig; use PVE::CephConfig;
# An array of test cases. # An array of test cases.
# Each test case is comprised of the following keys: # Each test case is comprised of the following keys:
# description => to identify a single test # description => to identify a single test
@ -91,8 +90,8 @@ my $tests = [
EOF EOF
}, },
{ {
description => 'single section, section header ' . description => 'single section, section header '
'with preceding whitespace and comment', . 'with preceding whitespace and comment',
expected_cfg => { expected_cfg => {
foo => { foo => {
bar => 'baz', bar => 'baz',
@ -263,8 +262,7 @@ my $tests = [
EOF EOF
}, },
{ {
description => 'single section, keys with quoted values, ' description => 'single section, keys with quoted values, ' . 'comments after values',
. 'comments after values',
expected_cfg => { expected_cfg => {
foo => { foo => {
bar => 'baz', bar => 'baz',
@ -525,8 +523,7 @@ my $tests = [
EOF EOF
}, },
{ {
description => 'single section, key-value pairs with ' . description => 'single section, key-value pairs with ' . 'continued lines and comments',
'continued lines and comments',
expected_cfg => { expected_cfg => {
foo => { foo => {
bar => 'baz continued baz', bar => 'baz continued baz',
@ -548,8 +545,8 @@ my $tests = [
EOF EOF
}, },
{ {
description => 'single section, key-value pairs with ' . description => 'single section, key-value pairs with '
'escaped commment literals in values', . 'escaped commment literals in values',
expected_cfg => { expected_cfg => {
foo => { foo => {
bar => 'baz#escaped', bar => 'baz#escaped',
@ -563,8 +560,8 @@ my $tests = [
EOF EOF
}, },
{ {
description => 'single section, key-value pairs with ' . description => 'single section, key-value pairs with '
'continued lines and escaped commment literals in values', . 'continued lines and escaped commment literals in values',
expected_cfg => { expected_cfg => {
foo => { foo => {
bar => 'baz#escaped', bar => 'baz#escaped',
@ -771,8 +768,7 @@ sub test_write_ceph_config {
sub main { sub main {
my $test_subs = [ my $test_subs = [
\&test_parse_ceph_config, \&test_parse_ceph_config, \&test_write_ceph_config,
\&test_write_ceph_config,
]; ];
plan(tests => scalar($tests->@*) * scalar($test_subs->@*)); plan(tests => scalar($tests->@*) * scalar($test_subs->@*));
@ -785,7 +781,7 @@ sub main {
$test_sub->($case); $test_sub->($case);
}; };
warn "$@\n" if $@; warn "$@\n" if $@;
}; }
} }
done_testing(); done_testing();

View File

@ -132,9 +132,9 @@ my $decompressor = {
}; };
my $bkp_suffix = { my $bkp_suffix = {
qemu => [ 'vma', $decompressor->{vma}, ], qemu => ['vma', $decompressor->{vma}],
lxc => [ 'tar', $decompressor->{tar}, ], lxc => ['tar', $decompressor->{tar}],
openvz => [ 'tar', $decompressor->{tar}, ], openvz => ['tar', $decompressor->{tar}],
}; };
# create more test cases for backup files matches # create more test cases for backup files matches
@ -143,7 +143,8 @@ for my $virt (sort keys %$bkp_suffix) {
my $archive_name = "vzdump-$virt-$vmid-2020_03_30-21_12_40"; my $archive_name = "vzdump-$virt-$vmid-2020_03_30-21_12_40";
for my $suffix (sort keys %$decomp) { for my $suffix (sort keys %$decomp) {
push @$tests, { push @$tests,
{
description => "Backup archive, $virt, $format.$suffix", description => "Backup archive, $virt, $format.$suffix",
archive => "backup/$archive_name.$format.$suffix", archive => "backup/$archive_name.$format.$suffix",
expected => { expected => {
@ -162,13 +163,12 @@ for my $virt (sort keys %$bkp_suffix) {
} }
} }
# add compression formats to test failed matches # add compression formats to test failed matches
my $non_bkp_suffix = { my $non_bkp_suffix = {
'openvz' => [ 'zip', 'tgz.lzo', 'zip.gz', '', ], 'openvz' => ['zip', 'tgz.lzo', 'zip.gz', ''],
'lxc' => [ 'zip', 'tgz.lzo', 'zip.gz', '', ], 'lxc' => ['zip', 'tgz.lzo', 'zip.gz', ''],
'qemu' => [ 'vma.xz', 'vms.gz', 'vmx.zst', '', ], 'qemu' => ['vma.xz', 'vms.gz', 'vmx.zst', ''],
'none' => [ 'tar.gz', ], 'none' => ['tar.gz'],
}; };
# create tests for failed matches # create tests for failed matches
@ -176,7 +176,8 @@ for my $virt (sort keys %$non_bkp_suffix) {
my $suffix = $non_bkp_suffix->{$virt}; my $suffix = $non_bkp_suffix->{$virt};
for my $s (@$suffix) { for my $s (@$suffix) {
my $archive = "backup/vzdump-$virt-$vmid-2020_03_30-21_12_40.$s"; my $archive = "backup/vzdump-$virt-$vmid-2020_03_30-21_12_40.$s";
push @$tests, { push @$tests,
{
description => "Failed match: Backup archive, $virt, $s", description => "Failed match: Backup archive, $virt, $s",
archive => $archive, archive => $archive,
expected => "ERROR: couldn't determine archive info from '$archive'\n", expected => "ERROR: couldn't determine archive info from '$archive'\n",
@ -184,7 +185,6 @@ for my $virt (sort keys %$non_bkp_suffix) {
} }
} }
plan tests => scalar @$tests; plan tests => scalar @$tests;
for my $tt (@$tests) { for my $tt (@$tests) {

View File

@ -152,7 +152,6 @@ sub read_test_file {
return $output; return $output;
} }
sub test_disk_list { sub test_disk_list {
my ($testdir) = @_; my ($testdir) = @_;
subtest "Test '$testdir'" => sub { subtest "Test '$testdir'" => sub {
@ -161,9 +160,7 @@ sub test_disk_list {
my $disks; my $disks;
my $expected_disk_list; my $expected_disk_list;
eval { eval { $disks = PVE::Diskmanage::get_disks(); };
$disks = PVE::Diskmanage::get_disks();
};
warn $@ if $@; warn $@ if $@;
$expected_disk_list = decode_json(read_test_file('disklist_expected.json')); $expected_disk_list = decode_json(read_test_file('disklist_expected.json'));
@ -194,16 +191,21 @@ sub test_disk_list {
warn $@ if $@; warn $@ if $@;
$testcount++; $testcount++;
print Dumper $disk_tmp if $print; print Dumper $disk_tmp if $print;
is_deeply($disk_tmp->{$disk}, $expected_disk_list->{$disk}, "disk $disk should be the same"); is_deeply(
$disk_tmp->{$disk},
$expected_disk_list->{$disk},
"disk $disk should be the same",
);
# test wrong parameter # test wrong parameter
eval { eval { PVE::Diskmanage::get_disks({ test => 1 }); };
PVE::Diskmanage::get_disks( { test => 1 } );
};
my $err = $@; my $err = $@;
$testcount++; $testcount++;
is_deeply($err, "disks is not a string or array reference\n", "error message should be the same"); is_deeply(
$err,
"disks is not a string or array reference\n",
"error message should be the same",
);
} }
# test multi disk parameter # test multi disk parameter
@ -235,14 +237,16 @@ $diskmanage_module->mock('is_iscsi' => \&mocked_is_iscsi);
print("\tMocked is_iscsi\n"); print("\tMocked is_iscsi\n");
$diskmanage_module->mock('assert_blockdev' => sub { return 1; }); $diskmanage_module->mock('assert_blockdev' => sub { return 1; });
print("\tMocked assert_blockdev\n"); print("\tMocked assert_blockdev\n");
$diskmanage_module->mock('dir_is_empty' => sub { $diskmanage_module->mock(
'dir_is_empty' => sub {
# all partitions have a holder dir # all partitions have a holder dir
my $val = shift; my $val = shift;
if ($val =~ m|^/sys/block/.+/.+/|) { if ($val =~ m|^/sys/block/.+/.+/|) {
return 0; return 0;
} }
return 1; return 1;
}); },
);
print("\tMocked dir_is_empty\n"); print("\tMocked dir_is_empty\n");
$diskmanage_module->mock('check_bin' => sub { return 1; }); $diskmanage_module->mock('check_bin' => sub { return 1; });
print("\tMocked check_bin\n"); print("\tMocked check_bin\n");

View File

@ -19,50 +19,40 @@ my $tests = [
volname => '1234/vm-1234-disk-0.raw', volname => '1234/vm-1234-disk-0.raw',
snapname => undef, snapname => undef,
expected => [ expected => [
"$path/images/1234/vm-1234-disk-0.raw", "$path/images/1234/vm-1234-disk-0.raw", '1234', 'images',
'1234',
'images'
], ],
}, },
{ {
volname => '1234/vm-1234-disk-0.raw', volname => '1234/vm-1234-disk-0.raw',
snapname => 'my_snap', snapname => 'my_snap',
expected => "can't snapshot this image format\n" expected => "can't snapshot this image format\n",
}, },
{ {
volname => '1234/vm-1234-disk-0.qcow2', volname => '1234/vm-1234-disk-0.qcow2',
snapname => undef, snapname => undef,
expected => [ expected => [
"$path/images/1234/vm-1234-disk-0.qcow2", "$path/images/1234/vm-1234-disk-0.qcow2", '1234', 'images',
'1234',
'images'
], ],
}, },
{ {
volname => '1234/vm-1234-disk-0.qcow2', volname => '1234/vm-1234-disk-0.qcow2',
snapname => 'my_snap', snapname => 'my_snap',
expected => [ expected => [
"$path/images/1234/vm-1234-disk-0.qcow2", "$path/images/1234/vm-1234-disk-0.qcow2", '1234', 'images',
'1234',
'images'
], ],
}, },
{ {
volname => 'iso/my-awesome-proxmox.iso', volname => 'iso/my-awesome-proxmox.iso',
snapname => undef, snapname => undef,
expected => [ expected => [
"$path/template/iso/my-awesome-proxmox.iso", "$path/template/iso/my-awesome-proxmox.iso", undef, 'iso',
undef,
'iso'
], ],
}, },
{ {
volname => "backup/vzdump-qemu-1234-2020_03_30-21_12_40.vma", volname => "backup/vzdump-qemu-1234-2020_03_30-21_12_40.vma",
snapname => undef, snapname => undef,
expected => [ expected => [
"$path/dump/vzdump-qemu-1234-2020_03_30-21_12_40.vma", "$path/dump/vzdump-qemu-1234-2020_03_30-21_12_40.vma", 1234, 'backup',
1234,
'backup'
], ],
}, },
]; ];
@ -76,9 +66,7 @@ foreach my $tt (@$tests) {
my $scfg = { path => $path }; my $scfg = { path => $path };
my $got; my $got;
eval { eval { $got = [PVE::Storage::Plugin->filesystem_path($scfg, $volname, $snapname)]; };
$got = [ PVE::Storage::Plugin->filesystem_path($scfg, $volname, $snapname) ];
};
$got = $@ if $@; $got = $@ if $@;
is_deeply($got, $expected, "wantarray: filesystem_path for $volname") is_deeply($got, $expected, "wantarray: filesystem_path for $volname")

View File

@ -31,7 +31,12 @@ foreach my $type (keys %$vtype_subdirs) {
foreach my $type (keys %$vtype_subdirs) { foreach my $type (keys %$vtype_subdirs) {
my $override = "${type}_override"; my $override = "${type}_override";
my $scfg_with_override = { path => '/some/path', 'content-dirs' => { $type => $override } }; my $scfg_with_override = { path => '/some/path', 'content-dirs' => { $type => $override } };
push @$tests, [ $scfg_with_override, $type, "$scfg_with_override->{path}/$scfg_with_override->{'content-dirs'}->{$type}" ]; push @$tests,
[
$scfg_with_override,
$type,
"$scfg_with_override->{path}/$scfg_with_override->{'content-dirs'}->{$type}",
];
} }
plan tests => scalar @$tests; plan tests => scalar @$tests;

View File

@ -56,8 +56,8 @@ my $mocked_vmlist = {
'node' => 'x42', 'node' => 'x42',
'type' => 'qemu', 'type' => 'qemu',
'version' => 6, 'version' => 6,
} },
} },
}; };
my $storage_dir = File::Temp->newdir(); my $storage_dir = File::Temp->newdir();
@ -257,8 +257,7 @@ my @tests = (
"$storage_dir/images/16114/vm-16114-disk-1.qcow2", "$storage_dir/images/16114/vm-16114-disk-1.qcow2",
], ],
parent => [ parent => [
"../9004/base-9004-disk-0.qcow2", "../9004/base-9004-disk-0.qcow2", "../9004/base-9004-disk-1.qcow2",
"../9004/base-9004-disk-1.qcow2",
], ],
expected => [ expected => [
{ {
@ -444,7 +443,7 @@ my @tests = (
'used' => DEFAULT_USED, 'used' => DEFAULT_USED,
'vmid' => '1234', 'vmid' => '1234',
'volid' => 'local:1234/vm-1234-disk-0.qcow2', 'volid' => 'local:1234/vm-1234-disk-0.qcow2',
} },
], ],
}, },
{ {
@ -466,7 +465,6 @@ my @tests = (
}, },
); );
# provide static vmlist for tests # provide static vmlist for tests
my $mock_cluster = Test::MockModule->new('PVE::Cluster', no_auto => 1); my $mock_cluster = Test::MockModule->new('PVE::Cluster', no_auto => 1);
$mock_cluster->redefine(get_vmlist => sub { return $mocked_vmlist; }); $mock_cluster->redefine(get_vmlist => sub { return $mocked_vmlist; });
@ -474,7 +472,8 @@ $mock_cluster->redefine(get_vmlist => sub { return $mocked_vmlist; });
# populate is File::stat's method to fill all information from CORE::stat into # populate is File::stat's method to fill all information from CORE::stat into
# an blessed array. # an blessed array.
my $mock_stat = Test::MockModule->new('File::stat', no_auto => 1); my $mock_stat = Test::MockModule->new('File::stat', no_auto => 1);
$mock_stat->redefine(populate => sub { $mock_stat->redefine(
populate => sub {
my (@st) = @_; my (@st) = @_;
$st[7] = DEFAULT_SIZE; $st[7] = DEFAULT_SIZE;
$st[10] = DEFAULT_CTIME; $st[10] = DEFAULT_CTIME;
@ -482,18 +481,22 @@ $mock_stat->redefine(populate => sub {
my $result = $mock_stat->original('populate')->(@st); my $result = $mock_stat->original('populate')->(@st);
return $result; return $result;
}); },
);
# override info provided by qemu-img in file_size_info # override info provided by qemu-img in file_size_info
my $mock_fsi = Test::MockModule->new('PVE::Storage::Plugin', no_auto => 1); my $mock_fsi = Test::MockModule->new('PVE::Storage::Plugin', no_auto => 1);
$mock_fsi->redefine(file_size_info => sub { $mock_fsi->redefine(
my ($size, $format, $used, $parent, $ctime) = $mock_fsi->original('file_size_info')->(@_); file_size_info => sub {
my ($size, $format, $used, $parent, $ctime) =
$mock_fsi->original('file_size_info')->(@_);
$size = DEFAULT_SIZE; $size = DEFAULT_SIZE;
$used = DEFAULT_USED; $used = DEFAULT_USED;
return wantarray ? ($size, $format, $used, $parent, $ctime) : $size; return wantarray ? ($size, $format, $used, $parent, $ctime) : $size;
}); },
);
my $plan = scalar @tests; my $plan = scalar @tests;
plan tests => $plan + 1; plan tests => $plan + 1;
@ -507,13 +510,15 @@ plan tests => $plan + 1;
PVE::Storage::Plugin->list_volumes('sid', $scfg_with_type, undef, ['images']); PVE::Storage::Plugin->list_volumes('sid', $scfg_with_type, undef, ['images']);
is_deeply ($tested_vmlist, $original_vmlist, is_deeply($tested_vmlist, $original_vmlist, 'PVE::Cluster::vmlist remains unmodified')
'PVE::Cluster::vmlist remains unmodified') || diag(
|| diag ("Expected vmlist to remain\n", explain($original_vmlist), "Expected vmlist to remain\n",
"but it turned to\n", explain($tested_vmlist)); explain($original_vmlist),
"but it turned to\n",
explain($tested_vmlist),
);
} }
{ {
my $sid = 'local'; my $sid = 'local';
my $types = ['rootdir', 'images', 'vztmpl', 'iso', 'backup', 'snippets']; my $types = ['rootdir', 'images', 'vztmpl', 'iso', 'backup', 'snippets'];

View File

@ -21,7 +21,15 @@ my $tests = [
{ {
description => 'VM disk image, linked, qcow2, vm- as base-', description => 'VM disk image, linked, qcow2, vm- as base-',
volname => "$vmid/vm-$vmid-disk-0.qcow2/$vmid/vm-$vmid-disk-0.qcow2", volname => "$vmid/vm-$vmid-disk-0.qcow2/$vmid/vm-$vmid-disk-0.qcow2",
expected => [ 'images', "vm-$vmid-disk-0.qcow2", "$vmid", "vm-$vmid-disk-0.qcow2", "$vmid", undef, 'qcow2', ], expected => [
'images',
"vm-$vmid-disk-0.qcow2",
"$vmid",
"vm-$vmid-disk-0.qcow2",
"$vmid",
undef,
'qcow2',
],
}, },
# #
# iso # iso
@ -34,7 +42,8 @@ my $tests = [
{ {
description => 'ISO image, img', description => 'ISO image, img',
volname => 'iso/some-other-installation-disk.img', volname => 'iso/some-other-installation-disk.img',
expected => ['iso', 'some-other-installation-disk.img', undef, undef, undef, undef, 'raw'], expected =>
['iso', 'some-other-installation-disk.img', undef, undef, undef, undef, 'raw'],
}, },
# #
# container templates # container templates
@ -42,17 +51,41 @@ my $tests = [
{ {
description => 'Container template tar.gz', description => 'Container template tar.gz',
volname => 'vztmpl/debian-10.0-standard_10.0-1_amd64.tar.gz', volname => 'vztmpl/debian-10.0-standard_10.0-1_amd64.tar.gz',
expected => ['vztmpl', 'debian-10.0-standard_10.0-1_amd64.tar.gz', undef, undef, undef, undef, 'raw'], expected => [
'vztmpl',
'debian-10.0-standard_10.0-1_amd64.tar.gz',
undef,
undef,
undef,
undef,
'raw',
],
}, },
{ {
description => 'Container template tar.xz', description => 'Container template tar.xz',
volname => 'vztmpl/debian-10.0-standard_10.0-1_amd64.tar.xz', volname => 'vztmpl/debian-10.0-standard_10.0-1_amd64.tar.xz',
expected => ['vztmpl', 'debian-10.0-standard_10.0-1_amd64.tar.xz', undef, undef, undef, undef, 'raw'], expected => [
'vztmpl',
'debian-10.0-standard_10.0-1_amd64.tar.xz',
undef,
undef,
undef,
undef,
'raw',
],
}, },
{ {
description => 'Container template tar.bz2', description => 'Container template tar.bz2',
volname => 'vztmpl/debian-10.0-standard_10.0-1_amd64.tar.bz2', volname => 'vztmpl/debian-10.0-standard_10.0-1_amd64.tar.bz2',
expected => ['vztmpl', 'debian-10.0-standard_10.0-1_amd64.tar.bz2', undef, undef, undef, undef, 'raw'], expected => [
'vztmpl',
'debian-10.0-standard_10.0-1_amd64.tar.bz2',
undef,
undef,
undef,
undef,
'raw',
],
}, },
# #
# container rootdir # container rootdir
@ -65,12 +98,21 @@ my $tests = [
{ {
description => 'Container rootdir, subvol', description => 'Container rootdir, subvol',
volname => "$vmid/subvol-$vmid-disk-0.subvol", volname => "$vmid/subvol-$vmid-disk-0.subvol",
expected => [ 'images', "subvol-$vmid-disk-0.subvol", "$vmid", undef, undef, undef, 'subvol' ], expected =>
['images', "subvol-$vmid-disk-0.subvol", "$vmid", undef, undef, undef, 'subvol'],
}, },
{ {
description => 'Backup archive, no virtualization type', description => 'Backup archive, no virtualization type',
volname => "backup/vzdump-none-$vmid-2020_03_30-21_39_30.tar", volname => "backup/vzdump-none-$vmid-2020_03_30-21_39_30.tar",
expected => ['backup', "vzdump-none-$vmid-2020_03_30-21_39_30.tar", undef, undef, undef, undef, 'raw'], expected => [
'backup',
"vzdump-none-$vmid-2020_03_30-21_39_30.tar",
undef,
undef,
undef,
undef,
'raw',
],
}, },
# #
# Snippets # Snippets
@ -101,7 +143,8 @@ my $tests = [
{ {
description => "Import, innner file of ova", description => "Import, innner file of ova",
volname => 'import/import.ova/disk.qcow2', volname => 'import/import.ova/disk.qcow2',
expected => ['import', 'import.ova/disk.qcow2', undef, undef, undef, undef, 'ova+qcow2'], expected =>
['import', 'import.ova/disk.qcow2', undef, undef, undef, undef, 'ova+qcow2'],
}, },
{ {
description => "Import, innner file of ova", description => "Import, innner file of ova",
@ -111,7 +154,8 @@ my $tests = [
{ {
description => "Import, innner file of ova with whitespace in name", description => "Import, innner file of ova with whitespace in name",
volname => 'import/import.ova/OS disk.vmdk', volname => 'import/import.ova/OS disk.vmdk',
expected => ['import', 'import.ova/OS disk.vmdk', undef, undef, undef, undef, 'ova+vmdk'], expected =>
['import', 'import.ova/OS disk.vmdk', undef, undef, undef, undef, 'ova+vmdk'],
}, },
{ {
description => "Import, innner file of ova", description => "Import, innner file of ova",
@ -129,12 +173,14 @@ my $tests = [
{ {
description => 'Failed match: ISO image, dvd', description => 'Failed match: ISO image, dvd',
volname => 'iso/yet-again-a-installation-disk.dvd', volname => 'iso/yet-again-a-installation-disk.dvd',
expected => "unable to parse directory volume name 'iso/yet-again-a-installation-disk.dvd'\n", expected =>
"unable to parse directory volume name 'iso/yet-again-a-installation-disk.dvd'\n",
}, },
{ {
description => 'Failed match: Container template, zip.gz', description => 'Failed match: Container template, zip.gz',
volname => 'vztmpl/debian-10.0-standard_10.0-1_amd64.zip.gz', volname => 'vztmpl/debian-10.0-standard_10.0-1_amd64.zip.gz',
expected => "unable to parse directory volume name 'vztmpl/debian-10.0-standard_10.0-1_amd64.zip.gz'\n", expected =>
"unable to parse directory volume name 'vztmpl/debian-10.0-standard_10.0-1_amd64.zip.gz'\n",
}, },
{ {
description => 'Failed match: Container rootdir, subvol', description => 'Failed match: Container rootdir, subvol',
@ -149,12 +195,14 @@ my $tests = [
{ {
description => 'Failed match: VM disk image, linked, qcow2, first vmid', description => 'Failed match: VM disk image, linked, qcow2, first vmid',
volname => "ssss/base-$vmid-disk-0.qcow2/$vmid/vm-$vmid-disk-0.qcow2", volname => "ssss/base-$vmid-disk-0.qcow2/$vmid/vm-$vmid-disk-0.qcow2",
expected => "unable to parse directory volume name 'ssss/base-$vmid-disk-0.qcow2/$vmid/vm-$vmid-disk-0.qcow2'\n", expected =>
"unable to parse directory volume name 'ssss/base-$vmid-disk-0.qcow2/$vmid/vm-$vmid-disk-0.qcow2'\n",
}, },
{ {
description => 'Failed match: VM disk image, linked, qcow2, second vmid', description => 'Failed match: VM disk image, linked, qcow2, second vmid',
volname => "$vmid/base-$vmid-disk-0.qcow2/ssss/vm-$vmid-disk-0.qcow2", volname => "$vmid/base-$vmid-disk-0.qcow2/ssss/vm-$vmid-disk-0.qcow2",
expected => "unable to parse volume filename 'base-$vmid-disk-0.qcow2/ssss/vm-$vmid-disk-0.qcow2'\n", expected =>
"unable to parse volume filename 'base-$vmid-disk-0.qcow2/ssss/vm-$vmid-disk-0.qcow2'\n",
}, },
{ {
description => "Failed match: import dir but no ova/ovf/disk image", description => "Failed match: import dir but no ova/ovf/disk image",
@ -171,13 +219,7 @@ foreach my $s (@$disk_suffix) {
description => "VM disk image, $s", description => "VM disk image, $s",
volname => "$vmid/vm-$vmid-disk-1.$s", volname => "$vmid/vm-$vmid-disk-1.$s",
expected => [ expected => [
'images', 'images', "vm-$vmid-disk-1.$s", "$vmid", undef, undef, undef, "$s",
"vm-$vmid-disk-1.$s",
"$vmid",
undef,
undef,
undef,
"$s",
], ],
}, },
{ {
@ -197,13 +239,7 @@ foreach my $s (@$disk_suffix) {
description => "VM disk image, base, $s", description => "VM disk image, base, $s",
volname => "$vmid/base-$vmid-disk-0.$s", volname => "$vmid/base-$vmid-disk-0.$s",
expected => [ expected => [
'images', 'images', "base-$vmid-disk-0.$s", "$vmid", undef, undef, 'base-', "$s",
"base-$vmid-disk-0.$s",
"$vmid",
undef,
undef,
'base-',
"$s"
], ],
}, },
); );
@ -211,7 +247,6 @@ foreach my $s (@$disk_suffix) {
push @$tests, @arr; push @$tests, @arr;
} }
# create more test cases for backup files matches # create more test cases for backup files matches
my $bkp_suffix = { my $bkp_suffix = {
qemu => ['vma', 'vma.gz', 'vma.lzo', 'vma.zst'], qemu => ['vma', 'vma.gz', 'vma.lzo', 'vma.zst'],
@ -233,7 +268,7 @@ foreach my $virt (keys %$bkp_suffix) {
undef, undef,
undef, undef,
undef, undef,
'raw' 'raw',
], ],
}, },
); );
@ -242,7 +277,6 @@ foreach my $virt (keys %$bkp_suffix) {
} }
} }
# create more test cases for failed backup files matches # create more test cases for failed backup files matches
my $non_bkp_suffix = { my $non_bkp_suffix = {
qemu => ['vms.gz', 'vma.xz'], qemu => ['vms.gz', 'vma.xz'],
@ -255,7 +289,8 @@ foreach my $virt (keys %$non_bkp_suffix) {
{ {
description => "Failed match: Backup archive, $virt, $s", description => "Failed match: Backup archive, $virt, $s",
volname => "backup/vzdump-$virt-$vmid-2020_03_30-21_12_40.$s", volname => "backup/vzdump-$virt-$vmid-2020_03_30-21_12_40.$s",
expected => "unable to parse directory volume name 'backup/vzdump-$virt-$vmid-2020_03_30-21_12_40.$s'\n", expected =>
"unable to parse directory volume name 'backup/vzdump-$virt-$vmid-2020_03_30-21_12_40.$s'\n",
}, },
); );
@ -263,7 +298,6 @@ foreach my $virt (keys %$non_bkp_suffix) {
} }
} }
# #
# run through test case array # run through test case array
# #

View File

@ -47,24 +47,21 @@ my @tests = (
description => 'Image, qcow2', description => 'Image, qcow2',
volname => "$storage_dir/images/16110/vm-16110-disk-0.qcow2", volname => "$storage_dir/images/16110/vm-16110-disk-0.qcow2",
expected => [ expected => [
'images', 'images', 'local:16110/vm-16110-disk-0.qcow2',
'local:16110/vm-16110-disk-0.qcow2',
], ],
}, },
{ {
description => 'Image, raw', description => 'Image, raw',
volname => "$storage_dir/images/16112/vm-16112-disk-0.raw", volname => "$storage_dir/images/16112/vm-16112-disk-0.raw",
expected => [ expected => [
'images', 'images', 'local:16112/vm-16112-disk-0.raw',
'local:16112/vm-16112-disk-0.raw',
], ],
}, },
{ {
description => 'Image template, qcow2', description => 'Image template, qcow2',
volname => "$storage_dir/images/9004/base-9004-disk-0.qcow2", volname => "$storage_dir/images/9004/base-9004-disk-0.qcow2",
expected => [ expected => [
'images', 'images', 'local:9004/base-9004-disk-0.qcow2',
'local:9004/base-9004-disk-0.qcow2',
], ],
}, },
@ -72,56 +69,49 @@ my @tests = (
description => 'Backup, vma.gz', description => 'Backup, vma.gz',
volname => "$storage_dir/dump/vzdump-qemu-16110-2020_03_30-21_11_40.vma.gz", volname => "$storage_dir/dump/vzdump-qemu-16110-2020_03_30-21_11_40.vma.gz",
expected => [ expected => [
'backup', 'backup', 'local:backup/vzdump-qemu-16110-2020_03_30-21_11_40.vma.gz',
'local:backup/vzdump-qemu-16110-2020_03_30-21_11_40.vma.gz',
], ],
}, },
{ {
description => 'Backup, vma.lzo', description => 'Backup, vma.lzo',
volname => "$storage_dir/dump/vzdump-qemu-16110-2020_03_30-21_12_45.vma.lzo", volname => "$storage_dir/dump/vzdump-qemu-16110-2020_03_30-21_12_45.vma.lzo",
expected => [ expected => [
'backup', 'backup', 'local:backup/vzdump-qemu-16110-2020_03_30-21_12_45.vma.lzo',
'local:backup/vzdump-qemu-16110-2020_03_30-21_12_45.vma.lzo',
], ],
}, },
{ {
description => 'Backup, vma', description => 'Backup, vma',
volname => "$storage_dir/dump/vzdump-qemu-16110-2020_03_30-21_13_55.vma", volname => "$storage_dir/dump/vzdump-qemu-16110-2020_03_30-21_13_55.vma",
expected => [ expected => [
'backup', 'backup', 'local:backup/vzdump-qemu-16110-2020_03_30-21_13_55.vma',
'local:backup/vzdump-qemu-16110-2020_03_30-21_13_55.vma',
], ],
}, },
{ {
description => 'Backup, tar.lzo', description => 'Backup, tar.lzo',
volname => "$storage_dir/dump/vzdump-lxc-16112-2020_03_30-21_39_30.tar.lzo", volname => "$storage_dir/dump/vzdump-lxc-16112-2020_03_30-21_39_30.tar.lzo",
expected => [ expected => [
'backup', 'backup', 'local:backup/vzdump-lxc-16112-2020_03_30-21_39_30.tar.lzo',
'local:backup/vzdump-lxc-16112-2020_03_30-21_39_30.tar.lzo',
], ],
}, },
{ {
description => 'Backup, vma.zst', description => 'Backup, vma.zst',
volname => "$storage_dir/dump/vzdump-qemu-16110-2020_03_30-21_13_55.vma.zst", volname => "$storage_dir/dump/vzdump-qemu-16110-2020_03_30-21_13_55.vma.zst",
expected => [ expected => [
'backup', 'backup', 'local:backup/vzdump-qemu-16110-2020_03_30-21_13_55.vma.zst',
'local:backup/vzdump-qemu-16110-2020_03_30-21_13_55.vma.zst'
], ],
}, },
{ {
description => 'Backup, tar.zst', description => 'Backup, tar.zst',
volname => "$storage_dir/dump/vzdump-lxc-16112-2020_03_30-21_39_30.tar.zst", volname => "$storage_dir/dump/vzdump-lxc-16112-2020_03_30-21_39_30.tar.zst",
expected => [ expected => [
'backup', 'backup', 'local:backup/vzdump-lxc-16112-2020_03_30-21_39_30.tar.zst',
'local:backup/vzdump-lxc-16112-2020_03_30-21_39_30.tar.zst'
], ],
}, },
{ {
description => 'Backup, tar.bz2', description => 'Backup, tar.bz2',
volname => "$storage_dir/dump/vzdump-openvz-16112-2020_03_30-21_39_30.tar.bz2", volname => "$storage_dir/dump/vzdump-openvz-16112-2020_03_30-21_39_30.tar.bz2",
expected => [ expected => [
'backup', 'backup', 'local:backup/vzdump-openvz-16112-2020_03_30-21_39_30.tar.bz2',
'local:backup/vzdump-openvz-16112-2020_03_30-21_39_30.tar.bz2',
], ],
}, },
@ -129,24 +119,21 @@ my @tests = (
description => 'ISO file', description => 'ISO file',
volname => "$storage_dir/template/iso/yet-again-a-installation-disk.iso", volname => "$storage_dir/template/iso/yet-again-a-installation-disk.iso",
expected => [ expected => [
'iso', 'iso', 'local:iso/yet-again-a-installation-disk.iso',
'local:iso/yet-again-a-installation-disk.iso',
], ],
}, },
{ {
description => 'CT template, tar.gz', description => 'CT template, tar.gz',
volname => "$storage_dir/template/cache/debian-10.0-standard_10.0-1_amd64.tar.gz", volname => "$storage_dir/template/cache/debian-10.0-standard_10.0-1_amd64.tar.gz",
expected => [ expected => [
'vztmpl', 'vztmpl', 'local:vztmpl/debian-10.0-standard_10.0-1_amd64.tar.gz',
'local:vztmpl/debian-10.0-standard_10.0-1_amd64.tar.gz',
], ],
}, },
{ {
description => 'CT template, wrong ending, tar bz2', description => 'CT template, wrong ending, tar bz2',
volname => "$storage_dir/template/cache/debian-10.0-standard_10.0-1_amd64.tar.bz2", volname => "$storage_dir/template/cache/debian-10.0-standard_10.0-1_amd64.tar.bz2",
expected => [ expected => [
'vztmpl', 'vztmpl', 'local:vztmpl/debian-10.0-standard_10.0-1_amd64.tar.bz2',
'local:vztmpl/debian-10.0-standard_10.0-1_amd64.tar.bz2',
], ],
}, },
@ -154,56 +141,49 @@ my @tests = (
description => 'Rootdir', description => 'Rootdir',
volname => "$storage_dir/private/1234/", # fileparse needs / at the end volname => "$storage_dir/private/1234/", # fileparse needs / at the end
expected => [ expected => [
'rootdir', 'rootdir', 'local:rootdir/1234',
'local:rootdir/1234',
], ],
}, },
{ {
description => 'Rootdir, folder subvol', description => 'Rootdir, folder subvol',
volname => "$storage_dir/images/1234/subvol-1234-disk-0.subvol/", # fileparse needs / at the end volname => "$storage_dir/images/1234/subvol-1234-disk-0.subvol/", # fileparse needs / at the end
expected => [ expected => [
'images', 'images', 'local:1234/subvol-1234-disk-0.subvol',
'local:1234/subvol-1234-disk-0.subvol'
], ],
}, },
{ {
description => 'Snippets, yaml', description => 'Snippets, yaml',
volname => "$storage_dir/snippets/userconfig.yaml", volname => "$storage_dir/snippets/userconfig.yaml",
expected => [ expected => [
'snippets', 'snippets', 'local:snippets/userconfig.yaml',
'local:snippets/userconfig.yaml',
], ],
}, },
{ {
description => 'Snippets, hookscript', description => 'Snippets, hookscript',
volname => "$storage_dir/snippets/hookscript.pl", volname => "$storage_dir/snippets/hookscript.pl",
expected => [ expected => [
'snippets', 'snippets', 'local:snippets/hookscript.pl',
'local:snippets/hookscript.pl',
], ],
}, },
{ {
description => 'CT template, tar.xz', description => 'CT template, tar.xz',
volname => "$storage_dir/template/cache/debian-10.0-standard_10.0-1_amd64.tar.xz", volname => "$storage_dir/template/cache/debian-10.0-standard_10.0-1_amd64.tar.xz",
expected => [ expected => [
'vztmpl', 'vztmpl', 'local:vztmpl/debian-10.0-standard_10.0-1_amd64.tar.xz',
'local:vztmpl/debian-10.0-standard_10.0-1_amd64.tar.xz',
], ],
}, },
{ {
description => 'Import, ova', description => 'Import, ova',
volname => "$storage_dir/import/import.ova", volname => "$storage_dir/import/import.ova",
expected => [ expected => [
'import', 'import', 'local:import/import.ova',
'local:import/import.ova',
], ],
}, },
{ {
description => 'Import, ovf', description => 'Import, ovf',
volname => "$storage_dir/import/import.ovf", volname => "$storage_dir/import/import.ovf",
expected => [ expected => [
'import', 'import', 'local:import/import.ovf',
'local:import/import.ovf',
], ],
}, },

View File

@ -18,7 +18,8 @@ my $mocked_backups_lists = {};
my $basetime = 1577881101; # 2020_01_01-12_18_21 UTC my $basetime = 1577881101; # 2020_01_01-12_18_21 UTC
foreach my $vmid (@vmids) { foreach my $vmid (@vmids) {
push @{$mocked_backups_lists->{default}}, ( push @{ $mocked_backups_lists->{default} },
(
{ {
'volid' => "$storeid:backup/vzdump-qemu-$vmid-2018_05_26-11_18_21.tar.zst", 'volid' => "$storeid:backup/vzdump-qemu-$vmid-2018_05_26-11_18_21.tar.zst",
'ctime' => $basetime - 585 * 24 * 60 * 60 - 60 * 60, 'ctime' => $basetime - 585 * 24 * 60 * 60 - 60 * 60,
@ -62,7 +63,8 @@ foreach my $vmid (@vmids) {
}, },
); );
} }
push @{$mocked_backups_lists->{year1970}}, ( push @{ $mocked_backups_lists->{year1970} },
(
{ {
'volid' => "$storeid:backup/vzdump-lxc-321-1970_01_01-00_01_23.tar.zst", 'volid' => "$storeid:backup/vzdump-lxc-321-1970_01_01-00_01_23.tar.zst",
'ctime' => 83, 'ctime' => 83,
@ -74,13 +76,15 @@ push @{$mocked_backups_lists->{year1970}}, (
'vmid' => 321, 'vmid' => 321,
}, },
); );
push @{$mocked_backups_lists->{novmid}}, ( push @{ $mocked_backups_lists->{novmid} },
(
{ {
'volid' => "$storeid:backup/vzdump-lxc-novmid.tar.gz", 'volid' => "$storeid:backup/vzdump-lxc-novmid.tar.gz",
'ctime' => 1234, 'ctime' => 1234,
}, },
); );
push @{$mocked_backups_lists->{threeway}}, ( push @{ $mocked_backups_lists->{threeway} },
(
{ {
'volid' => "$storeid:backup/vzdump-qemu-7654-2019_12_25-12_18_21.tar.zst", 'volid' => "$storeid:backup/vzdump-qemu-7654-2019_12_25-12_18_21.tar.zst",
'ctime' => $basetime - 7 * 24 * 60 * 60, 'ctime' => $basetime - 7 * 24 * 60 * 60,
@ -97,7 +101,8 @@ push @{$mocked_backups_lists->{threeway}}, (
'vmid' => 7654, 'vmid' => 7654,
}, },
); );
push @{$mocked_backups_lists->{weekboundary}}, ( push @{ $mocked_backups_lists->{weekboundary} },
(
{ {
'volid' => "$storeid:backup/vzdump-qemu-7654-2020_12_03-12_18_21.tar.zst", 'volid' => "$storeid:backup/vzdump-qemu-7654-2020_12_03-12_18_21.tar.zst",
'ctime' => $basetime + (366 - 31 + 2) * 24 * 60 * 60, 'ctime' => $basetime + (366 - 31 + 2) * 24 * 60 * 60,
@ -116,7 +121,8 @@ push @{$mocked_backups_lists->{weekboundary}}, (
); );
my $current_list; my $current_list;
my $mock_plugin = Test::MockModule->new('PVE::Storage::Plugin'); my $mock_plugin = Test::MockModule->new('PVE::Storage::Plugin');
$mock_plugin->redefine(list_volumes => sub { $mock_plugin->redefine(
list_volumes => sub {
my ($class, $storeid, $scfg, $vmid, $content_types) = @_; my ($class, $storeid, $scfg, $vmid, $content_types) = @_;
my $list = $mocked_backups_lists->{$current_list}; my $list = $mocked_backups_lists->{$current_list};
@ -124,14 +130,16 @@ $mock_plugin->redefine(list_volumes => sub {
return $list if !defined($vmid); return $list if !defined($vmid);
return [grep { $_->{vmid} eq $vmid } @{$list}]; return [grep { $_->{vmid} eq $vmid } @{$list}];
}); },
);
sub generate_expected { sub generate_expected {
my ($vmids, $type, $marks) = @_; my ($vmids, $type, $marks) = @_;
my @expected; my @expected;
foreach my $vmid (@{$vmids}) { foreach my $vmid (@{$vmids}) {
push @expected, ( push @expected,
(
{ {
'volid' => "$storeid:backup/vzdump-qemu-$vmid-2018_05_26-11_18_21.tar.zst", 'volid' => "$storeid:backup/vzdump-qemu-$vmid-2018_05_26-11_18_21.tar.zst",
'type' => 'qemu', 'type' => 'qemu',
@ -175,7 +183,8 @@ sub generate_expected {
'vmid' => $vmid, 'vmid' => $vmid,
}, },
) if !defined($type) || $type eq 'qemu'; ) if !defined($type) || $type eq 'qemu';
push @expected, ( push @expected,
(
{ {
'volid' => "$storeid:backup/vzdump-lxc-$vmid-2020_01_01-12_18_21.tar.zst", 'volid' => "$storeid:backup/vzdump-lxc-$vmid-2020_01_01-12_18_21.tar.zst",
'type' => 'lxc', 'type' => 'lxc',
@ -184,7 +193,8 @@ sub generate_expected {
'vmid' => $vmid, 'vmid' => $vmid,
}, },
) if !defined($type) || $type eq 'lxc'; ) if !defined($type) || $type eq 'lxc';
push @expected, ( push @expected,
(
{ {
'volid' => "$storeid:backup/vzdump-$vmid-renamed.tar.zst", 'volid' => "$storeid:backup/vzdump-$vmid-renamed.tar.zst",
'type' => 'unknown', 'type' => 'unknown',
@ -212,7 +222,8 @@ my $tests = [
keep => { keep => {
'keep-last' => 3, 'keep-last' => 3,
}, },
expected => generate_expected(\@vmids, undef, ['remove', 'remove', 'keep', 'keep', 'keep', 'keep']), expected =>
generate_expected(\@vmids, undef, ['remove', 'remove', 'keep', 'keep', 'keep', 'keep']),
}, },
{ {
description => 'weekly=2, one ID', description => 'weekly=2, one ID',
@ -220,7 +231,11 @@ my $tests = [
keep => { keep => {
'keep-weekly' => 2, 'keep-weekly' => 2,
}, },
expected => generate_expected([$vmids[0]], undef, ['keep', 'remove', 'remove', 'remove', 'keep', 'keep']), expected => generate_expected(
[$vmids[0]],
undef,
['keep', 'remove', 'remove', 'remove', 'keep', 'keep'],
),
}, },
{ {
description => 'daily=weekly=monthly=1, multiple IDs', description => 'daily=weekly=monthly=1, multiple IDs',
@ -230,7 +245,8 @@ my $tests = [
'keep-weekly' => 1, 'keep-weekly' => 1,
'keep-monthly' => 1, 'keep-monthly' => 1,
}, },
expected => generate_expected(\@vmids, undef, ['keep', 'remove', 'keep', 'remove', 'keep', 'keep']), expected =>
generate_expected(\@vmids, undef, ['keep', 'remove', 'keep', 'remove', 'keep', 'keep']),
}, },
{ {
description => 'hourly=4, one ID', description => 'hourly=4, one ID',
@ -239,7 +255,11 @@ my $tests = [
'keep-hourly' => 4, 'keep-hourly' => 4,
'keep-daily' => 0, 'keep-daily' => 0,
}, },
expected => generate_expected([$vmids[0]], undef, ['keep', 'remove', 'keep', 'keep', 'keep', 'keep']), expected => generate_expected(
[$vmids[0]],
undef,
['keep', 'remove', 'keep', 'keep', 'keep', 'keep'],
),
}, },
{ {
description => 'yearly=2, multiple IDs', description => 'yearly=2, multiple IDs',
@ -250,7 +270,11 @@ my $tests = [
'keep-monthly' => 0, 'keep-monthly' => 0,
'keep-yearly' => 2, 'keep-yearly' => 2,
}, },
expected => generate_expected(\@vmids, undef, ['remove', 'remove', 'keep', 'remove', 'keep', 'keep']), expected => generate_expected(
\@vmids,
undef,
['remove', 'remove', 'keep', 'remove', 'keep', 'keep'],
),
}, },
{ {
description => 'last=2,hourly=2 one ID', description => 'last=2,hourly=2 one ID',
@ -259,7 +283,11 @@ my $tests = [
'keep-last' => 2, 'keep-last' => 2,
'keep-hourly' => 2, 'keep-hourly' => 2,
}, },
expected => generate_expected([$vmids[0]], undef, ['keep', 'remove', 'keep', 'keep', 'keep', 'keep']), expected => generate_expected(
[$vmids[0]],
undef,
['keep', 'remove', 'keep', 'keep', 'keep', 'keep'],
),
}, },
{ {
description => 'last=1,monthly=2, multiple IDs', description => 'last=1,monthly=2, multiple IDs',
@ -267,7 +295,8 @@ my $tests = [
'keep-last' => 1, 'keep-last' => 1,
'keep-monthly' => 2, 'keep-monthly' => 2,
}, },
expected => generate_expected(\@vmids, undef, ['keep', 'remove', 'keep', 'remove', 'keep', 'keep']), expected =>
generate_expected(\@vmids, undef, ['keep', 'remove', 'keep', 'remove', 'keep', 'keep']),
}, },
{ {
description => 'monthly=3, one ID', description => 'monthly=3, one ID',
@ -275,7 +304,11 @@ my $tests = [
keep => { keep => {
'keep-monthly' => 3, 'keep-monthly' => 3,
}, },
expected => generate_expected([$vmids[0]], undef, ['keep', 'remove', 'keep', 'remove', 'keep', 'keep']), expected => generate_expected(
[$vmids[0]],
undef,
['keep', 'remove', 'keep', 'remove', 'keep', 'keep'],
),
}, },
{ {
description => 'last=daily=weekly=1, multiple IDs', description => 'last=daily=weekly=1, multiple IDs',
@ -284,7 +317,8 @@ my $tests = [
'keep-daily' => 1, 'keep-daily' => 1,
'keep-weekly' => 1, 'keep-weekly' => 1,
}, },
expected => generate_expected(\@vmids, undef, ['keep', 'remove', 'keep', 'remove', 'keep', 'keep']), expected =>
generate_expected(\@vmids, undef, ['keep', 'remove', 'keep', 'remove', 'keep', 'keep']),
}, },
{ {
description => 'last=daily=weekly=1, others zero, multiple IDs', description => 'last=daily=weekly=1, others zero, multiple IDs',
@ -296,7 +330,8 @@ my $tests = [
'keep-monthly' => 0, 'keep-monthly' => 0,
'keep-yearly' => 0, 'keep-yearly' => 0,
}, },
expected => generate_expected(\@vmids, undef, ['keep', 'remove', 'keep', 'remove', 'keep', 'keep']), expected =>
generate_expected(\@vmids, undef, ['keep', 'remove', 'keep', 'remove', 'keep', 'keep']),
}, },
{ {
description => 'daily=2, one ID', description => 'daily=2, one ID',
@ -304,7 +339,11 @@ my $tests = [
keep => { keep => {
'keep-daily' => 2, 'keep-daily' => 2,
}, },
expected => generate_expected([$vmids[0]], undef, ['remove', 'remove', 'keep', 'remove', 'keep', 'keep']), expected => generate_expected(
[$vmids[0]],
undef,
['remove', 'remove', 'keep', 'remove', 'keep', 'keep'],
),
}, },
{ {
description => 'weekly=monthly=1, multiple IDs', description => 'weekly=monthly=1, multiple IDs',
@ -312,7 +351,11 @@ my $tests = [
'keep-weekly' => 1, 'keep-weekly' => 1,
'keep-monthly' => 1, 'keep-monthly' => 1,
}, },
expected => generate_expected(\@vmids, undef, ['keep', 'remove', 'remove', 'remove', 'keep', 'keep']), expected => generate_expected(
\@vmids,
undef,
['keep', 'remove', 'remove', 'remove', 'keep', 'keep'],
),
}, },
{ {
description => 'weekly=yearly=1, one ID', description => 'weekly=yearly=1, one ID',
@ -321,7 +364,11 @@ my $tests = [
'keep-weekly' => 1, 'keep-weekly' => 1,
'keep-yearly' => 1, 'keep-yearly' => 1,
}, },
expected => generate_expected([$vmids[0]], undef, ['keep', 'remove', 'remove', 'remove', 'keep', 'keep']), expected => generate_expected(
[$vmids[0]],
undef,
['keep', 'remove', 'remove', 'remove', 'keep', 'keep'],
),
}, },
{ {
description => 'weekly=yearly=1, one ID, type qemu', description => 'weekly=yearly=1, one ID, type qemu',
@ -331,7 +378,11 @@ my $tests = [
'keep-weekly' => 1, 'keep-weekly' => 1,
'keep-yearly' => 1, 'keep-yearly' => 1,
}, },
expected => generate_expected([$vmids[0]], 'qemu', ['keep', 'remove', 'remove', 'remove', 'keep', '']), expected => generate_expected(
[$vmids[0]],
'qemu',
['keep', 'remove', 'remove', 'remove', 'keep', ''],
),
}, },
{ {
description => 'week=yearly=1, one ID, type lxc', description => 'week=yearly=1, one ID, type lxc',
@ -383,7 +434,8 @@ my $tests = [
{ {
description => 'all missing, multiple IDs', description => 'all missing, multiple IDs',
keep => {}, keep => {},
expected => generate_expected(\@vmids, undef, ['keep', 'keep', 'keep', 'keep', 'keep', 'keep']), expected =>
generate_expected(\@vmids, undef, ['keep', 'keep', 'keep', 'keep', 'keep', 'keep']),
}, },
{ {
description => 'all zero, multiple IDs', description => 'all zero, multiple IDs',
@ -395,7 +447,8 @@ my $tests = [
'keep-monthyl' => 0, 'keep-monthyl' => 0,
'keep-yearly' => 0, 'keep-yearly' => 0,
}, },
expected => generate_expected(\@vmids, undef, ['keep', 'keep', 'keep', 'keep', 'keep', 'keep']), expected =>
generate_expected(\@vmids, undef, ['keep', 'keep', 'keep', 'keep', 'keep', 'keep']),
}, },
{ {
description => 'some zero, some missing, multiple IDs', description => 'some zero, some missing, multiple IDs',
@ -406,7 +459,8 @@ my $tests = [
'keep-monthyl' => 0, 'keep-monthyl' => 0,
'keep-yearly' => 0, 'keep-yearly' => 0,
}, },
expected => generate_expected(\@vmids, undef, ['keep', 'keep', 'keep', 'keep', 'keep', 'keep']), expected =>
generate_expected(\@vmids, undef, ['keep', 'keep', 'keep', 'keep', 'keep', 'keep']),
}, },
{ {
description => 'daily=weekly=monthly=1', description => 'daily=weekly=monthly=1',
@ -479,7 +533,9 @@ for my $tt (@$tests) {
my $got = eval { my $got = eval {
$current_list = $tt->{list} // 'default'; $current_list = $tt->{list} // 'default';
my $res = PVE::Storage::Plugin->prune_backups($tt->{scfg}, $storeid, $tt->{keep}, $tt->{vmid}, $tt->{type}, 1); my $res = PVE::Storage::Plugin->prune_backups(
$tt->{scfg}, $storeid, $tt->{keep}, $tt->{vmid}, $tt->{type}, 1,
);
return [sort { $a->{volid} cmp $b->{volid} } @{$res}]; return [sort { $a->{volid} cmp $b->{volid} } @{$res}];
}; };
$got = $@ if $@; $got = $@ if $@;

View File

@ -69,6 +69,7 @@ my $vmid_linked_clone = int($vmid) - 2;
sub jp { sub jp {
print to_json($_[0], { utf8 => 8, pretty => 1, canonical => 1 }) . "\n"; print to_json($_[0], { utf8 => 8, pretty => 1, canonical => 1 }) . "\n";
} }
sub dbgvar { sub dbgvar {
jp(@_) if $DEBUG; jp(@_) if $DEBUG;
} }
@ -79,9 +80,7 @@ sub run_cmd {
my $raw = ''; my $raw = '';
my $parser = sub { $raw .= shift; }; my $parser = sub { $raw .= shift; };
eval { eval { run_command($cmd, outfunc => $parser); };
run_command($cmd, outfunc => $parser);
};
if (my $err = $@) { if (my $err = $@) {
die $err if !$ignore_errors; die $err if !$ignore_errors;
} }
@ -109,9 +108,7 @@ sub run_test_cmd {
$raw .= "${line}\n"; $raw .= "${line}\n";
}; };
eval { eval { run_command($cmd, outfunc => $out); };
run_command($cmd, outfunc => $out);
};
if (my $err = $@) { if (my $err = $@) {
print $raw; print $raw;
print $err; print $err;
@ -167,13 +164,28 @@ sub prepare {
run_cmd(['pvesm', 'add', 'rbd', $pool, '--pool', $pool, '--content', 'images,rootdir']); run_cmd(['pvesm', 'add', 'rbd', $pool, '--pool', $pool, '--content', 'images,rootdir']);
} }
# create PVE storages (librbd / krbd) # create PVE storages (librbd / krbd)
run_cmd(['pvesm', 'add', 'rbd', ${storage_name}, '--krbd', '0', '--pool', ${pool}, '--namespace', ${namespace}, '--content', 'images,rootdir']) run_cmd(
if !$rbd_found; [
'pvesm',
'add',
'rbd',
${storage_name},
'--krbd',
'0',
'--pool',
${pool},
'--namespace',
${namespace},
'--content',
'images,rootdir',
],
) if !$rbd_found;
# create test VM # create test VM
print "Create test VM ${vmid}\n"; print "Create test VM ${vmid}\n";
my $vms = run_cmd(['pvesh', 'get', 'cluster/resources', '--type', 'vm', '--output-format', 'json'], 1); my $vms =
run_cmd(['pvesh', 'get', 'cluster/resources', '--type', 'vm', '--output-format', 'json'],
1);
for my $vm (@$vms) { for my $vm (@$vms) {
# TODO: introduce a force flag to make this behaviour configurable # TODO: introduce a force flag to make this behaviour configurable
@ -183,10 +195,21 @@ sub prepare {
run_cmd(['qm', 'destroy', ${vmid}]); run_cmd(['qm', 'destroy', ${vmid}]);
} }
} }
run_cmd(['qm', 'create', ${vmid}, '--bios', 'ovmf', '--efidisk0', "${storage_name}:1", '--scsi0', "${storage_name}:2"]); run_cmd(
[
'qm',
'create',
${vmid},
'--bios',
'ovmf',
'--efidisk0',
"${storage_name}:1",
'--scsi0',
"${storage_name}:2",
],
);
} }
sub cleanup { sub cleanup {
print "Cleaning up test environment!\n"; print "Cleaning up test environment!\n";
print "Removing VMs\n"; print "Removing VMs\n";
@ -195,7 +218,21 @@ sub cleanup {
run_cmd(['qm', 'stop', ${vmid_clone}], 0, 1); run_cmd(['qm', 'stop', ${vmid_clone}], 0, 1);
run_cmd(['qm', 'destroy', ${vmid_linked_clone}], 0, 1); run_cmd(['qm', 'destroy', ${vmid_linked_clone}], 0, 1);
run_cmd(['qm', 'destroy', ${vmid_clone}], 0, 1); run_cmd(['qm', 'destroy', ${vmid_clone}], 0, 1);
run_cmd(['for', 'i', 'in', "/dev/rbd/${pool}/${namespace}/*;", 'do', '/usr/bin/rbd', 'unmap', '\$i;', 'done'], 0, 1); run_cmd(
[
'for',
'i',
'in',
"/dev/rbd/${pool}/${namespace}/*;",
'do',
'/usr/bin/rbd',
'unmap',
'\$i;',
'done',
],
0,
1,
);
run_cmd(['qm', 'unlock', ${vmid}], 0, 1); run_cmd(['qm', 'unlock', ${vmid}], 0, 1);
run_cmd(['qm', 'destroy', ${vmid}], 0, 1); run_cmd(['qm', 'destroy', ${vmid}], 0, 1);
@ -237,8 +274,7 @@ my $tests = [
{ {
name => 'snapshot/rollback', name => 'snapshot/rollback',
steps => [ steps => [
['qm', 'snapshot', $vmid, 'test'], ['qm', 'snapshot', $vmid, 'test'], ['qm', 'rollback', $vmid, 'test'],
['qm', 'rollback', $vmid, 'test'],
], ],
cleanup => [ cleanup => [
['qm', 'unlock', $vmid], ['qm', 'unlock', $vmid],
@ -260,8 +296,7 @@ my $tests = [
{ {
name => 'switch to krbd', name => 'switch to krbd',
preparations => [ preparations => [
['qm', 'stop', $vmid], ['qm', 'stop', $vmid], ['pvesm', 'set', $storage_name, '--krbd', 1],
['pvesm', 'set', $storage_name, '--krbd', 1]
], ],
}, },
{ {
@ -273,8 +308,7 @@ my $tests = [
{ {
name => 'snapshot/rollback with krbd', name => 'snapshot/rollback with krbd',
steps => [ steps => [
['qm', 'snapshot', $vmid, 'test'], ['qm', 'snapshot', $vmid, 'test'], ['qm', 'rollback', $vmid, 'test'],
['qm', 'rollback', $vmid, 'test'],
], ],
cleanup => [ cleanup => [
['qm', 'unlock', $vmid], ['qm', 'unlock', $vmid],
@ -304,7 +338,7 @@ my $tests = [
preparations => [ preparations => [
['qm', 'stop', $vmid], ['qm', 'stop', $vmid],
['qm', 'stop', $vmid_clone], ['qm', 'stop', $vmid_clone],
['pvesm', 'set', $storage_name, '--krbd', 0] ['pvesm', 'set', $storage_name, '--krbd', 0],
], ],
}, },
{ {
@ -318,12 +352,9 @@ my $tests = [
}, },
{ {
name => 'start linked clone with krbd', name => 'start linked clone with krbd',
preparations => [ preparations => [['pvesm', 'set', $storage_name, '--krbd', 1]],
['pvesm', 'set', $storage_name, '--krbd', 1]
],
steps => [ steps => [
['qm', 'start', $vmid_linked_clone], ['qm', 'start', $vmid_linked_clone], ['qm', 'stop', $vmid_linked_clone],
['qm', 'stop', $vmid_linked_clone],
], ],
}, },
]; ];

View File

@ -51,9 +51,9 @@ EOF
my $permissions = { my $permissions = {
'user1@test' => {}, 'user1@test' => {},
'user2@test' => { '/' => ['Sys.Modify'], }, 'user2@test' => { '/' => ['Sys.Modify'] },
'user3@test' => { '/storage' => ['Datastore.Allocate'], }, 'user3@test' => { '/storage' => ['Datastore.Allocate'] },
'user4@test' => { '/storage/d20m40r30' => ['Datastore.Allocate'], }, 'user4@test' => { '/storage/d20m40r30' => ['Datastore.Allocate'] },
}; };
my $pve_cluster_module; my $pve_cluster_module;
@ -96,8 +96,16 @@ my $rpcenv = PVE::RPCEnvironment->init('pub');
my @tests = ( my @tests = (
[user => 'root@pam'], [user => 'root@pam'],
[['unknown', ['nolimit'], undef], 100, 'root / generic default limit, requesting default'], [['unknown', ['nolimit'], undef], 100, 'root / generic default limit, requesting default'],
[ ['move', ['nolimit'], undef], 80, 'root / specific default limit, requesting default (move)' ], [
[ ['restore', ['nolimit'], undef], 60, 'root / specific default limit, requesting default (restore)' ], ['move', ['nolimit'], undef],
80,
'root / specific default limit, requesting default (move)',
],
[
['restore', ['nolimit'], undef],
60,
'root / specific default limit, requesting default (restore)',
],
[['unknown', ['d50m40r30'], undef], 50, 'root / storage default limit'], [['unknown', ['d50m40r30'], undef], 50, 'root / storage default limit'],
[['move', ['d50m40r30'], undef], 40, 'root / specific storage limit (move)'], [['move', ['d50m40r30'], undef], 40, 'root / specific storage limit (move)'],
[['restore', ['d50m40r30'], undef], 30, 'root / specific storage limit (restore)'], [['restore', ['d50m40r30'], undef], 30, 'root / specific storage limit (restore)'],
@ -110,7 +118,11 @@ my @tests = (
[['migrate', undef, 100], 100, 'root / undef storage (migrate)'], [['migrate', undef, 100], 100, 'root / undef storage (migrate)'],
[['migrate', [], 100], 100, 'root / no storage (migrate)'], [['migrate', [], 100], 100, 'root / no storage (migrate)'],
[['migrate', [undef], undef], 100, 'root / [undef] storage no override (migrate)'], [['migrate', [undef], undef], 100, 'root / [undef] storage no override (migrate)'],
[ ['migrate', [undef, undef], 200], 200, 'root / list of undef storages with override (migrate)' ], [
['migrate', [undef, undef], 200],
200,
'root / list of undef storages with override (migrate)',
],
[user => 'user1@test'], [user => 'user1@test'],
[['unknown', ['nolimit'], undef], 100, 'generic default limit'], [['unknown', ['nolimit'], undef], 100, 'generic default limit'],
@ -119,78 +131,290 @@ my @tests = (
[['unknown', ['d50m40r30'], undef], 50, 'storage default limit'], [['unknown', ['d50m40r30'], undef], 50, 'storage default limit'],
[['move', ['d50m40r30'], undef], 40, 'specific storage limit (move)'], [['move', ['d50m40r30'], undef], 40, 'specific storage limit (move)'],
[['restore', ['d50m40r30'], undef], 30, 'specific storage limit (restore)'], [['restore', ['d50m40r30'], undef], 30, 'specific storage limit (restore)'],
[ ['unknown', ['d200m400r300'], undef], 200, 'storage default limit above datacenter limits' ], [
[ ['move', ['d200m400r300'], undef], 400, 'specific storage limit above datacenter limits (move)' ], ['unknown', ['d200m400r300'], undef],
[ ['restore', ['d200m400r300'], undef], 300, 'specific storage limit above datacenter limits (restore)' ], 200,
'storage default limit above datacenter limits',
],
[
['move', ['d200m400r300'], undef],
400,
'specific storage limit above datacenter limits (move)',
],
[
['restore', ['d200m400r300'], undef],
300,
'specific storage limit above datacenter limits (restore)',
],
[['unknown', ['d50'], undef], 50, 'storage default limit'], [['unknown', ['d50'], undef], 50, 'storage default limit'],
[['move', ['d50'], undef], 50, 'storage default limit (move)'], [['move', ['d50'], undef], 50, 'storage default limit (move)'],
[['restore', ['d50'], undef], 50, 'storage default limit (restore)'], [['restore', ['d50'], undef], 50, 'storage default limit (restore)'],
[user => 'user2@test'], [user => 'user2@test'],
[ ['unknown', ['nolimit'], 0], 0, 'generic default limit with Sys.Modify, passing unlimited' ], [
['unknown', ['nolimit'], 0],
0,
'generic default limit with Sys.Modify, passing unlimited',
],
[['unknown', ['nolimit'], undef], 100, 'generic default limit with Sys.Modify'], [['unknown', ['nolimit'], undef], 100, 'generic default limit with Sys.Modify'],
[['move', ['nolimit'], undef], 80, 'specific default limit with Sys.Modify (move)'], [['move', ['nolimit'], undef], 80, 'specific default limit with Sys.Modify (move)'],
[['restore', ['nolimit'], undef], 60, 'specific default limit with Sys.Modify (restore)'], [['restore', ['nolimit'], undef], 60, 'specific default limit with Sys.Modify (restore)'],
[ ['restore', ['nolimit'], 0], 0, 'specific default limit with Sys.Modify, passing unlimited (restore)' ], [
[ ['move', ['nolimit'], 0], 0, 'specific default limit with Sys.Modify, passing unlimited (move)' ], ['restore', ['nolimit'], 0],
0,
'specific default limit with Sys.Modify, passing unlimited (restore)',
],
[
['move', ['nolimit'], 0],
0,
'specific default limit with Sys.Modify, passing unlimited (move)',
],
[['unknown', ['d50m40r30'], undef], 50, 'storage default limit with Sys.Modify'], [['unknown', ['d50m40r30'], undef], 50, 'storage default limit with Sys.Modify'],
[['restore', ['d50m40r30'], undef], 30, 'specific storage limit with Sys.Modify (restore)'], [['restore', ['d50m40r30'], undef], 30, 'specific storage limit with Sys.Modify (restore)'],
[['move', ['d50m40r30'], undef], 40, 'specific storage limit with Sys.Modify (move)'], [['move', ['d50m40r30'], undef], 40, 'specific storage limit with Sys.Modify (move)'],
[user => 'user3@test'], [user => 'user3@test'],
[['unknown', ['nolimit'], undef], 100, 'generic default limit with privileges on /'], [['unknown', ['nolimit'], undef], 100, 'generic default limit with privileges on /'],
[ ['unknown', ['nolimit'], 80], 80, 'generic default limit with privileges on /, passing an override value' ], [
[ ['unknown', ['nolimit'], 0], 0, 'generic default limit with privileges on /, passing unlimited' ], ['unknown', ['nolimit'], 80],
80,
'generic default limit with privileges on /, passing an override value',
],
[
['unknown', ['nolimit'], 0],
0,
'generic default limit with privileges on /, passing unlimited',
],
[['move', ['nolimit'], undef], 80, 'specific default limit with privileges on / (move)'], [['move', ['nolimit'], undef], 80, 'specific default limit with privileges on / (move)'],
[ ['move', ['nolimit'], 0], 0, 'specific default limit with privileges on /, passing unlimited (move)' ], [
[ ['restore', ['nolimit'], undef], 60, 'specific default limit with privileges on / (restore)' ], ['move', ['nolimit'], 0],
[ ['restore', ['nolimit'], 0], 0, 'specific default limit with privileges on /, passing unlimited (restore)' ], 0,
[ ['unknown', ['d50m40r30'], 0], 0, 'storage default limit with privileges on /, passing unlimited' ], 'specific default limit with privileges on /, passing unlimited (move)',
],
[
['restore', ['nolimit'], undef],
60,
'specific default limit with privileges on / (restore)',
],
[
['restore', ['nolimit'], 0],
0,
'specific default limit with privileges on /, passing unlimited (restore)',
],
[
['unknown', ['d50m40r30'], 0],
0,
'storage default limit with privileges on /, passing unlimited',
],
[['unknown', ['d50m40r30'], undef], 50, 'storage default limit with privileges on /'], [['unknown', ['d50m40r30'], undef], 50, 'storage default limit with privileges on /'],
[ ['unknown', ['d50m40r30'], 0], 0, 'storage default limit with privileges on, passing unlimited /' ], [
['unknown', ['d50m40r30'], 0],
0,
'storage default limit with privileges on, passing unlimited /',
],
[['move', ['d50m40r30'], undef], 40, 'specific storage limit with privileges on / (move)'], [['move', ['d50m40r30'], undef], 40, 'specific storage limit with privileges on / (move)'],
[ ['move', ['d50m40r30'], 0], 0, 'specific storage limit with privileges on, passing unlimited / (move)' ], [
[ ['restore', ['d50m40r30'], undef], 30, 'specific storage limit with privileges on / (restore)' ], ['move', ['d50m40r30'], 0],
[ ['restore', ['d50m40r30'], 0], 0, 'specific storage limit with privileges on /, passing unlimited (restore)' ], 0,
'specific storage limit with privileges on, passing unlimited / (move)',
],
[
['restore', ['d50m40r30'], undef],
30,
'specific storage limit with privileges on / (restore)',
],
[
['restore', ['d50m40r30'], 0],
0,
'specific storage limit with privileges on /, passing unlimited (restore)',
],
[user => 'user4@test'], [user => 'user4@test'],
[ ['unknown', ['nolimit'], 10], 10, 'generic default limit with privileges on a different storage, passing lower override' ], [
[ ['unknown', ['nolimit'], undef], 100, 'generic default limit with privileges on a different storage' ], ['unknown', ['nolimit'], 10],
[ ['unknown', ['nolimit'], 0], 100, 'generic default limit with privileges on a different storage, passing unlimited' ], 10,
[ ['move', ['nolimit'], undef], 80, 'specific default limit with privileges on a different storage (move)' ], 'generic default limit with privileges on a different storage, passing lower override',
[ ['restore', ['nolimit'], undef], 60, 'specific default limit with privileges on a different storage (restore)' ], ],
[ ['unknown', ['d50m40r30'], undef], 50, 'storage default limit with privileges on a different storage' ], [
[ ['move', ['d50m40r30'], undef], 40, 'specific storage limit with privileges on a different storage (move)' ], ['unknown', ['nolimit'], undef],
[ ['restore', ['d50m40r30'], undef], 30, 'specific storage limit with privileges on a different storage (restore)' ], 100,
[ ['unknown', ['d20m40r30'], undef], 20, 'storage default limit with privileges on that storage' ], 'generic default limit with privileges on a different storage',
[ ['unknown', ['d20m40r30'], 0], 0, 'storage default limit with privileges on that storage, passing unlimited' ], ],
[ ['move', ['d20m40r30'], undef], 40, 'specific storage limit with privileges on that storage (move)' ], [
[ ['move', ['d20m40r30'], 0], 0, 'specific storage limit with privileges on that storage, passing unlimited (move)' ], ['unknown', ['nolimit'], 0],
[ ['move', ['d20m40r30'], 10], 10, 'specific storage limit with privileges on that storage, passing low override (move)' ], 100,
[ ['move', ['d20m40r30'], 300], 300, 'specific storage limit with privileges on that storage, passing high override (move)' ], 'generic default limit with privileges on a different storage, passing unlimited',
[ ['restore', ['d20m40r30'], undef], 30, 'specific storage limit with privileges on that storage (restore)' ], ],
[ ['restore', ['d20m40r30'], 0], 0, 'specific storage limit with privileges on that storage, passing unlimited (restore)' ], [
[ ['unknown', ['d50m40r30', 'd20m40r30'], 0], 50, 'multiple storages default limit with privileges on one of them, passing unlimited' ], ['move', ['nolimit'], undef],
[ ['move', ['d50m40r30', 'd20m40r30'], 0], 40, 'multiple storages specific limit with privileges on one of them, passing unlimited (move)' ], 80,
[ ['restore', ['d50m40r30', 'd20m40r30'], 0], 30, 'multiple storages specific limit with privileges on one of them, passing unlimited (restore)' ], 'specific default limit with privileges on a different storage (move)',
[ ['unknown', ['d50m40r30', 'd20m40r30'], undef], 20, 'multiple storages default limit with privileges on one of them' ], ],
[ ['unknown', ['d10', 'd20m40r30'], undef], 10, 'multiple storages default limit with privileges on one of them (storage limited)' ], [
[ ['move', ['d10', 'd20m40r30'], undef], 10, 'multiple storages specific limit with privileges on one of them (storage limited) (move)' ], ['restore', ['nolimit'], undef],
[ ['restore', ['d10', 'd20m40r30'], undef], 10, 'multiple storages specific limit with privileges on one of them (storage limited) (restore)' ], 60,
[ ['restore', ['d10', 'd20m40r30'], 5], 5, 'multiple storages specific limit (storage limited) (restore), passing lower override' ], 'specific default limit with privileges on a different storage (restore)',
[ ['restore', ['d200', 'd200m400r300'], 65], 65, 'multiple storages specific limit (storage limited) (restore), passing lower override' ], ],
[ ['restore', ['d200', 'd200m400r300'], 400], 200, 'multiple storages specific limit (storage limited) (restore), passing higher override' ], [
[ ['restore', ['d200', 'd200m400r300'], 0], 200, 'multiple storages specific limit (storage limited) (restore), passing unlimited' ], ['unknown', ['d50m40r30'], undef],
[ ['restore', ['d200', 'd200m400r300'], 1], 1, 'multiple storages specific limit (storage limited) (restore), passing 1' ], 50,
[ ['restore', ['d10', 'd20m40r30'], 500], 10, 'multiple storages specific limit with privileges on one of them (storage limited) (restore), passing higher override' ], 'storage default limit with privileges on a different storage',
[ ['unknown', ['nolimit', 'd20m40r30'], 0], 100, 'multiple storages default limit with privileges on one of them, passing unlimited (default limited)' ], ],
[ ['move', ['nolimit', 'd20m40r30'], 0], 80, 'multiple storages specific limit with privileges on one of them, passing unlimited (default limited) (move)' ], [
[ ['restore', ['nolimit', 'd20m40r30'], 0], 60, 'multiple storages specific limit with privileges on one of them, passing unlimited (default limited) (restore)' ], ['move', ['d50m40r30'], undef],
[ ['unknown', ['nolimit', 'd20m40r30'], undef], 20, 'multiple storages default limit with privileges on one of them (default limited)' ], 40,
[ ['move', ['nolimit', 'd20m40r30'], undef], 40, 'multiple storages specific limit with privileges on one of them (default limited) (move)' ], 'specific storage limit with privileges on a different storage (move)',
[ ['restore', ['nolimit', 'd20m40r30'], undef], 30, 'multiple storages specific limit with privileges on one of them (default limited) (restore)' ], ],
[ ['restore', ['d20m40r30', 'm50'], 200], 60, 'multiple storages specific limit with privileges on one of them (global default limited) (restore)' ], [
[ ['move', ['nolimit', undef ], 40] , 40, 'multiple storages one undefined, passing 40 (move)' ], ['restore', ['d50m40r30'], undef],
30,
'specific storage limit with privileges on a different storage (restore)',
],
[
['unknown', ['d20m40r30'], undef],
20,
'storage default limit with privileges on that storage',
],
[
['unknown', ['d20m40r30'], 0],
0,
'storage default limit with privileges on that storage, passing unlimited',
],
[
['move', ['d20m40r30'], undef],
40,
'specific storage limit with privileges on that storage (move)',
],
[
['move', ['d20m40r30'], 0],
0,
'specific storage limit with privileges on that storage, passing unlimited (move)',
],
[
['move', ['d20m40r30'], 10],
10,
'specific storage limit with privileges on that storage, passing low override (move)',
],
[
['move', ['d20m40r30'], 300],
300,
'specific storage limit with privileges on that storage, passing high override (move)',
],
[
['restore', ['d20m40r30'], undef],
30,
'specific storage limit with privileges on that storage (restore)',
],
[
['restore', ['d20m40r30'], 0],
0,
'specific storage limit with privileges on that storage, passing unlimited (restore)',
],
[
['unknown', ['d50m40r30', 'd20m40r30'], 0],
50,
'multiple storages default limit with privileges on one of them, passing unlimited',
],
[
['move', ['d50m40r30', 'd20m40r30'], 0],
40,
'multiple storages specific limit with privileges on one of them, passing unlimited (move)',
],
[
['restore', ['d50m40r30', 'd20m40r30'], 0],
30,
'multiple storages specific limit with privileges on one of them, passing unlimited (restore)',
],
[
['unknown', ['d50m40r30', 'd20m40r30'], undef],
20,
'multiple storages default limit with privileges on one of them',
],
[
['unknown', ['d10', 'd20m40r30'], undef],
10,
'multiple storages default limit with privileges on one of them (storage limited)',
],
[
['move', ['d10', 'd20m40r30'], undef],
10,
'multiple storages specific limit with privileges on one of them (storage limited) (move)',
],
[
['restore', ['d10', 'd20m40r30'], undef],
10,
'multiple storages specific limit with privileges on one of them (storage limited) (restore)',
],
[
['restore', ['d10', 'd20m40r30'], 5],
5,
'multiple storages specific limit (storage limited) (restore), passing lower override',
],
[
['restore', ['d200', 'd200m400r300'], 65],
65,
'multiple storages specific limit (storage limited) (restore), passing lower override',
],
[
['restore', ['d200', 'd200m400r300'], 400],
200,
'multiple storages specific limit (storage limited) (restore), passing higher override',
],
[
['restore', ['d200', 'd200m400r300'], 0],
200,
'multiple storages specific limit (storage limited) (restore), passing unlimited',
],
[
['restore', ['d200', 'd200m400r300'], 1],
1,
'multiple storages specific limit (storage limited) (restore), passing 1',
],
[
['restore', ['d10', 'd20m40r30'], 500],
10,
'multiple storages specific limit with privileges on one of them (storage limited) (restore), passing higher override',
],
[
['unknown', ['nolimit', 'd20m40r30'], 0],
100,
'multiple storages default limit with privileges on one of them, passing unlimited (default limited)',
],
[
['move', ['nolimit', 'd20m40r30'], 0],
80,
'multiple storages specific limit with privileges on one of them, passing unlimited (default limited) (move)',
],
[
['restore', ['nolimit', 'd20m40r30'], 0],
60,
'multiple storages specific limit with privileges on one of them, passing unlimited (default limited) (restore)',
],
[
['unknown', ['nolimit', 'd20m40r30'], undef],
20,
'multiple storages default limit with privileges on one of them (default limited)',
],
[
['move', ['nolimit', 'd20m40r30'], undef],
40,
'multiple storages specific limit with privileges on one of them (default limited) (move)',
],
[
['restore', ['nolimit', 'd20m40r30'], undef],
30,
'multiple storages specific limit with privileges on one of them (default limited) (restore)',
],
[
['restore', ['d20m40r30', 'm50'], 200],
60,
'multiple storages specific limit with privileges on one of them (global default limited) (restore)',
],
[
['move', ['nolimit', undef], 40],
40,
'multiple storages one undefined, passing 40 (move)',
],
[['move', undef, 100], 80, 'undef storage, passing 100 (move)'], [['move', undef, 100], 80, 'undef storage, passing 100 (move)'],
[['move', [undef], 100], 80, '[undef] storage, passing 100 (move)'], [['move', [undef], 100], 80, '[undef] storage, passing 100 (move)'],
[['move', [undef], undef], 80, '[undef] storage, no override (move)'], [['move', [undef], undef], 80, '[undef] storage, no override (move)'],

View File

@ -14,7 +14,8 @@ my $test_manifests = join ('/', $Bin, 'ovf_manifests');
print "parsing ovfs\n"; print "parsing ovfs\n";
my $win2008 = eval { PVE::GuestImport::OVF::parse_ovf("$test_manifests/Win_2008_R2_two-disks.ovf") }; my $win2008 =
eval { PVE::GuestImport::OVF::parse_ovf("$test_manifests/Win_2008_R2_two-disks.ovf") };
if (my $err = $@) { if (my $err = $@) {
fail('parse win2008'); fail('parse win2008');
warn("error: $err\n"); warn("error: $err\n");
@ -28,7 +29,8 @@ if (my $err = $@) {
} else { } else {
ok('parse win10'); ok('parse win10');
} }
my $win10noNs = eval { PVE::GuestImport::OVF::parse_ovf("$test_manifests/Win10-Liz_no_default_ns.ovf") }; my $win10noNs =
eval { PVE::GuestImport::OVF::parse_ovf("$test_manifests/Win10-Liz_no_default_ns.ovf") };
if (my $err = $@) { if (my $err = $@) {
fail("parse win10 no default rasd NS"); fail("parse win10 no default rasd NS");
warn("error: $err\n"); warn("error: $err\n");
@ -38,26 +40,59 @@ if (my $err = $@) {
print "testing disks\n"; print "testing disks\n";
is($win2008->{disks}->[0]->{disk_address}, 'scsi0', 'multidisk vm has the correct first disk controller'); is(
is($win2008->{disks}->[0]->{backing_file}, "$test_manifests/disk1.vmdk", 'multidisk vm has the correct first disk backing device'); $win2008->{disks}->[0]->{disk_address},
'scsi0',
'multidisk vm has the correct first disk controller',
);
is(
$win2008->{disks}->[0]->{backing_file},
"$test_manifests/disk1.vmdk",
'multidisk vm has the correct first disk backing device',
);
is($win2008->{disks}->[0]->{virtual_size}, 2048, 'multidisk vm has the correct first disk size'); is($win2008->{disks}->[0]->{virtual_size}, 2048, 'multidisk vm has the correct first disk size');
is($win2008->{disks}->[1]->{disk_address}, 'scsi1', 'multidisk vm has the correct second disk controller'); is(
is($win2008->{disks}->[1]->{backing_file}, "$test_manifests/disk2.vmdk", 'multidisk vm has the correct second disk backing device'); $win2008->{disks}->[1]->{disk_address},
'scsi1',
'multidisk vm has the correct second disk controller',
);
is(
$win2008->{disks}->[1]->{backing_file},
"$test_manifests/disk2.vmdk",
'multidisk vm has the correct second disk backing device',
);
is($win2008->{disks}->[1]->{virtual_size}, 2048, 'multidisk vm has the correct second disk size'); is($win2008->{disks}->[1]->{virtual_size}, 2048, 'multidisk vm has the correct second disk size');
is($win10->{disks}->[0]->{disk_address}, 'scsi0', 'single disk vm has the correct disk controller'); is($win10->{disks}->[0]->{disk_address}, 'scsi0', 'single disk vm has the correct disk controller');
is($win10->{disks}->[0]->{backing_file}, "$test_manifests/Win10-Liz-disk1.vmdk", 'single disk vm has the correct disk backing device'); is(
$win10->{disks}->[0]->{backing_file},
"$test_manifests/Win10-Liz-disk1.vmdk",
'single disk vm has the correct disk backing device',
);
is($win10->{disks}->[0]->{virtual_size}, 2048, 'single disk vm has the correct size'); is($win10->{disks}->[0]->{virtual_size}, 2048, 'single disk vm has the correct size');
is($win10noNs->{disks}->[0]->{disk_address}, 'scsi0', 'single disk vm (no default rasd NS) has the correct disk controller'); is(
is($win10noNs->{disks}->[0]->{backing_file}, "$test_manifests/Win10-Liz-disk1.vmdk", 'single disk vm (no default rasd NS) has the correct disk backing device'); $win10noNs->{disks}->[0]->{disk_address},
is($win10noNs->{disks}->[0]->{virtual_size}, 2048, 'single disk vm (no default rasd NS) has the correct size'); 'scsi0',
'single disk vm (no default rasd NS) has the correct disk controller',
);
is(
$win10noNs->{disks}->[0]->{backing_file},
"$test_manifests/Win10-Liz-disk1.vmdk",
'single disk vm (no default rasd NS) has the correct disk backing device',
);
is(
$win10noNs->{disks}->[0]->{virtual_size},
2048,
'single disk vm (no default rasd NS) has the correct size',
);
print "testing nics\n"; print "testing nics\n";
is($win2008->{net}->{net0}->{model}, 'e1000', 'win2008 has correct nic model'); is($win2008->{net}->{net0}->{model}, 'e1000', 'win2008 has correct nic model');
is($win10->{net}->{net0}->{model}, 'e1000e', 'win10 has correct nic model'); is($win10->{net}->{net0}->{model}, 'e1000e', 'win10 has correct nic model');
is($win10noNs->{net}->{net0}->{model}, 'e1000e', 'win10 (no default rasd NS) has correct nic model'); is($win10noNs->{net}->{net0}->{model}, 'e1000e',
'win10 (no default rasd NS) has correct nic model');
print "\ntesting vm.conf extraction\n"; print "\ntesting vm.conf extraction\n";

File diff suppressed because it is too large Load Diff