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

View File

@ -139,24 +139,30 @@ __PACKAGE__->register_method ({
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) = @_;
$storid = PVE::Systemd::unescape_unit($storid);
my $unitfile = "/etc/systemd/system/$filename";
my $unit = $read_ini->($unitfile);
push @$result, {
push @$result,
{
unitfile => $unitfile,
path => "/mnt/pve/$storid",
device => $unit->{'Mount'}->{'What'},
type => $unit->{'Mount'}->{'Type'},
options => $unit->{'Mount'}->{'Options'},
};
});
},
);
return $result;
}});
},
});
__PACKAGE__->register_method({
name => 'create',
@ -165,10 +171,12 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
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']],
},
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 => {
additionalProperties => 0,
properties => {
@ -226,7 +234,8 @@ __PACKAGE__->register_method ({
# reserve the name and add as disabled, will be enabled below if creation works out
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();
@ -251,10 +260,14 @@ __PACKAGE__->register_method ({
my ($devname) = $dev =~ m|^/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) = @_;
$part .= $partition;
});
},
);
}
# create filesystem
@ -277,14 +290,17 @@ __PACKAGE__->register_method ({
$cmd = [$BLKID, $part, '-o', 'export'];
print "# ", join(' ', @$cmd), "\n";
run_command($cmd, outfunc => sub {
run_command(
$cmd,
outfunc => sub {
my ($line) = @_;
if ($line =~ m/^UUID=(.*)$/) {
$uuid = $1;
$uuid_path = "/dev/disk/by-uuid/$uuid";
}
});
},
);
die "could not get UUID of device '$part'\n" if !$uuid;
@ -305,13 +321,15 @@ __PACKAGE__->register_method ({
if ($param->{add_storage}) {
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);
}});
},
});
__PACKAGE__->register_method({
name => 'delete',
@ -320,7 +338,8 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
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']],
},
description => "Unmounts the storage and removes the mount unit.",
@ -330,8 +349,9 @@ __PACKAGE__->register_method ({
node => get_standard_option('pve-node'),
name => get_standard_option('pve-storage-id'),
'cleanup-config' => {
description => "Marks associated storage(s) as not available on this node anymore ".
"or removes them from the configuration (if configured for this node only).",
description =>
"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',
optional => 1,
default => 0,
@ -380,7 +400,9 @@ __PACKAGE__->register_method ({
run_command(['systemctl', 'stop', $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;
if ($param->{'cleanup-config'}) {
@ -388,7 +410,9 @@ __PACKAGE__->register_method ({
my ($scfg) = @_;
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 $@;
}
@ -402,6 +426,7 @@ __PACKAGE__->register_method ({
};
return $rpcenv->fork_worker('dirremove', $name, $user, $worker);
}});
},
});
1;

View File

@ -72,7 +72,8 @@ __PACKAGE__->register_method ({
},
size => {
type => 'integer',
description => 'The size of the physical volume in bytes',
description =>
'The size of the physical volume in bytes',
},
free => {
type => 'integer',
@ -108,7 +109,8 @@ __PACKAGE__->register_method ({
leaf => 0,
children => $result,
};
}});
},
});
__PACKAGE__->register_method({
name => 'create',
@ -117,7 +119,8 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
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']],
},
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
PVE::API2::Storage::Config->create_or_update(
$name, $node, $storage_params, $verify_params, 1);
$name, $node, $storage_params, $verify_params, 1,
);
}
my $worker = sub {
@ -187,13 +191,15 @@ __PACKAGE__->register_method ({
if ($param->{add_storage}) {
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);
}});
},
});
__PACKAGE__->register_method({
name => 'delete',
@ -202,7 +208,8 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
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']],
},
description => "Remove an LVM Volume Group.",
@ -212,8 +219,9 @@ __PACKAGE__->register_method ({
node => get_standard_option('pve-node'),
name => get_standard_option('pve-storage-id'),
'cleanup-config' => {
description => "Marks associated storage(s) as not available on this node anymore ".
"or removes them from the configuration (if configured for this node only).",
description =>
"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',
optional => 1,
default => 0,
@ -251,7 +259,9 @@ __PACKAGE__->register_method ({
my ($scfg) = @_;
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 $@;
}
@ -274,6 +284,7 @@ __PACKAGE__->register_method ({
};
return $rpcenv->fork_worker('lvmremove', $name, $user, $worker);
}});
},
});
1;

View File

@ -66,7 +66,8 @@ __PACKAGE__->register_method ({
code => sub {
my ($param) = @_;
return PVE::Storage::LvmThinPlugin::list_thinpools(undef);
}});
},
});
__PACKAGE__->register_method({
name => 'create',
@ -75,7 +76,8 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
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']],
},
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
PVE::API2::Storage::Config->create_or_update(
$name, $node, $storage_params, $verify_params, 1);
$name, $node, $storage_params, $verify_params, 1,
);
}
my $worker = sub {
@ -155,24 +158,29 @@ __PACKAGE__->register_method ({
run_command([
'/sbin/lvcreate',
'--type', 'thin-pool',
'--type',
'thin-pool',
"-L${datasize}K",
'--poolmetadatasize', "${metadatasize}K",
'-n', $name,
$name
'--poolmetadatasize',
"${metadatasize}K",
'-n',
$name,
$name,
]);
PVE::Diskmanage::udevadm_trigger($dev);
if ($param->{add_storage}) {
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);
}});
},
});
__PACKAGE__->register_method({
name => 'delete',
@ -181,7 +189,8 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
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']],
},
description => "Remove an LVM thin pool.",
@ -192,8 +201,9 @@ __PACKAGE__->register_method ({
name => get_standard_option('pve-storage-id'),
'volume-group' => get_standard_option('pve-storage-id'),
'cleanup-config' => {
description => "Marks associated storage(s) as not available on this node anymore ".
"or removes them from the configuration (if configured for this node only).",
description =>
"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',
optional => 1,
default => 0,
@ -232,11 +242,14 @@ __PACKAGE__->register_method ({
if ($param->{'cleanup-config'}) {
my $match = sub {
my ($scfg) = @_;
return $scfg->{type} eq 'lvmthin'
return
$scfg->{type} eq 'lvmthin'
&& $scfg->{vgname} eq $vg
&& $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 $@;
}
@ -264,6 +277,7 @@ __PACKAGE__->register_method ({
};
return $rpcenv->fork_worker('lvmthinremove', "${vg}-${lv}", $user, $worker);
}});
},
});
1;

View File

@ -31,7 +31,9 @@ sub get_pool_data {
};
my $pools = [];
run_command([$ZPOOL, 'list', '-HpPLo', join(',', @$propnames)], outfunc => sub {
run_command(
[$ZPOOL, 'list', '-HpPLo', join(',', @$propnames)],
outfunc => sub {
my ($line) = @_;
my @props = split('\s+', trim($line));
@ -45,7 +47,8 @@ sub get_pool_data {
}
push @$pools, $pool;
});
},
);
return $pools;
}
@ -107,7 +110,8 @@ __PACKAGE__->register_method ({
my ($param) = @_;
return get_pool_data();
}});
},
});
sub preparetree {
my ($el) = @_;
@ -122,7 +126,6 @@ sub preparetree {
}
}
__PACKAGE__->register_method({
name => 'detail',
path => '{name}',
@ -172,7 +175,8 @@ __PACKAGE__->register_method ({
},
children => {
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 => {
type => 'object',
properties => {
@ -199,8 +203,8 @@ __PACKAGE__->register_method ({
},
msg => {
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 $curlvl = 0;
run_command($cmd, outfunc => sub {
run_command(
$cmd,
outfunc => sub {
my ($line) = @_;
if ($line =~ m/^\s*(\S+): (\S+.*)$/) {
@ -237,8 +243,12 @@ __PACKAGE__->register_method ({
$pool->{$curfield} .= " " . $1;
} elsif (!$config && $line =~ m/^\s*config:/) {
$config = 1;
} elsif ($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);
} elsif (
$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") {
my $lvl = int(length($space) / 2) + 1; # two spaces per level
my $vdev = {
@ -271,14 +281,16 @@ __PACKAGE__->register_method ({
$curlvl = $lvl;
}
}
});
},
);
# change treenodes for extjs tree
$pool->{name} = delete $pool->{pool};
preparetree($pool);
return $pool;
}});
},
});
my $draid_config_format = {
spares => {
@ -300,7 +312,8 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
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']],
},
description => "Create a ZFS pool.",
@ -313,13 +326,20 @@ __PACKAGE__->register_method ({
type => 'string',
description => 'The RAID level to use.',
enum => [
'single', 'mirror',
'raid10', 'raidz', 'raidz2', 'raidz3',
'draid', 'draid2', 'draid3',
'single',
'mirror',
'raid10',
'raidz',
'raidz2',
'raidz3',
'draid',
'draid2',
'draid3',
],
},
devices => {
type => 'string', format => 'string-list',
type => 'string',
format => 'string-list',
description => 'The block devices you want to create the zpool on.',
},
'draid-config' => {
@ -366,7 +386,8 @@ __PACKAGE__->register_method ({
my $draid_config;
if (exists $param->{'draid-config'}) {
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) {
@ -388,7 +409,8 @@ __PACKAGE__->register_method ({
# reserve the name and add as disabled, will be enabled below if creation works out
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();
@ -439,7 +461,10 @@ __PACKAGE__->register_method ({
if ($is_partition) {
eval {
PVE::Diskmanage::change_parttype($dev, '6a898cc3-1dd2-11b2-99a6-080020736631');
PVE::Diskmanage::change_parttype(
$dev,
'6a898cc3-1dd2-11b2-99a6-080020736631',
);
};
warn $@ if $@;
}
@ -484,7 +509,8 @@ __PACKAGE__->register_method ({
run_command($cmd);
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];
print "# ", join(' ', @$cmd), "\n";
run_command($cmd);
@ -494,14 +520,21 @@ __PACKAGE__->register_method ({
if ($param->{add_storage}) {
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);
},
);
},
});
}});
__PACKAGE__->register_method({
name => 'delete',
@ -510,7 +543,8 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
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']],
},
description => "Destroy a ZFS pool.",
@ -520,8 +554,9 @@ __PACKAGE__->register_method ({
node => get_standard_option('pve-node'),
name => get_standard_option('pve-storage-id'),
'cleanup-config' => {
description => "Marks associated storage(s) as not available on this node anymore ".
"or removes them from the configuration (if configured for this node only).",
description =>
"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',
optional => 1,
default => 0,
@ -551,7 +586,9 @@ __PACKAGE__->register_method ({
my $to_wipe = [];
if ($param->{'cleanup-disks'}) {
# 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 ($name) = PVE::Tools::split_list($line);
@ -562,7 +599,8 @@ __PACKAGE__->register_method ({
$dev =~ s|^/dev/||;
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.
my $parent = $info->{$dev}->{parent};
@ -571,15 +609,19 @@ __PACKAGE__->register_method ({
my $info9 = $info->{"${parent}9"};
$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;
});
},
);
}
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]);
}
@ -591,7 +633,9 @@ __PACKAGE__->register_method ({
my ($scfg) = @_;
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 $@;
}
@ -605,6 +649,7 @@ __PACKAGE__->register_method ({
};
return $rpcenv->fork_worker('zfsremove', $name, $user, $worker);
}});
},
});
1;

View File

@ -29,10 +29,12 @@ my $api_storage_config = sub {
my $scfg = dclone(PVE::Storage::storage_config($cfg, $storeid));
$scfg->{storage} = $storeid;
$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}) {
$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;
@ -122,7 +124,8 @@ __PACKAGE__->register_method ({
method => 'GET',
description => "Storage index.",
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',
},
parameters => {
@ -165,7 +168,8 @@ __PACKAGE__->register_method ({
}
return $res;
}});
},
});
__PACKAGE__->register_method({
name => 'read',
@ -188,7 +192,8 @@ __PACKAGE__->register_method ({
my $cfg = PVE::Storage::config();
return &$api_storage_config($cfg, $param->{storage});
}});
},
});
__PACKAGE__->register_method({
name => 'create',
@ -244,7 +249,8 @@ __PACKAGE__->register_method ({
my $opts = $plugin->check_config($storeid, $param, 1, 1);
my $returned_config;
PVE::Storage::lock_storage_config(sub {
PVE::Storage::lock_storage_config(
sub {
my $cfg = PVE::Storage::config();
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);
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"
." in Proxmox VE 9. Use 'create-base-path' or 'create-subdirs' instead.\n"
warn
"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 {
@ -275,7 +282,9 @@ __PACKAGE__->register_method ({
PVE::Storage::write_config($cfg);
}, "create storage failed");
},
"create storage failed",
);
my $res = {
storage => $storeid,
@ -283,7 +292,8 @@ __PACKAGE__->register_method ({
};
$res->{config} = $returned_config if $returned_config;
return $res;
}});
},
});
__PACKAGE__->register_method({
name => 'update',
@ -335,7 +345,8 @@ __PACKAGE__->register_method ({
}
my $returned_config;
PVE::Storage::lock_storage_config(sub {
PVE::Storage::lock_storage_config(
sub {
my $cfg = PVE::Storage::config();
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
warn "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"
warn
"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);
}, "update storage failed");
},
"update storage failed",
);
my $res = {
storage => $storeid,
@ -383,7 +397,8 @@ __PACKAGE__->register_method ({
};
$res->{config} = $returned_config if $returned_config;
return $res;
}});
},
});
__PACKAGE__->register_method({
name => 'delete',
@ -397,9 +412,12 @@ __PACKAGE__->register_method ({
parameters => {
additionalProperties => 0,
properties => {
storage => get_standard_option('pve-storage-id', {
storage => get_standard_option(
'pve-storage-id',
{
completion => \&PVE::Storage::complete_storage,
}),
},
),
},
},
returns => { type => 'null' },
@ -408,7 +426,8 @@ __PACKAGE__->register_method ({
my $storeid = extract_param($param, 'storage');
PVE::Storage::lock_storage_config(sub {
PVE::Storage::lock_storage_config(
sub {
my $cfg = PVE::Storage::config();
my $scfg = PVE::Storage::storage_config($cfg, $storeid);
@ -424,11 +443,14 @@ __PACKAGE__->register_method ({
PVE::Storage::write_config($cfg);
}, "delete storage failed");
},
"delete storage failed",
);
PVE::AccessControl::remove_storage_access($storeid);
return undef;
}});
},
});
1;

View File

@ -22,7 +22,12 @@ __PACKAGE__->register_method ({
method => 'GET',
description => "List storage content.",
permissions => {
check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
check => [
'perm',
'/storage/{storage}',
['Datastore.Audit', 'Datastore.AllocateSpace'],
any => 1,
],
},
protected => 1,
proxyto => 'node',
@ -30,20 +35,27 @@ __PACKAGE__->register_method ({
additionalProperties => 0,
properties => {
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,
}),
},
),
content => {
description => "Only list content of this type.",
type => 'string', format => 'pve-storage-content',
type => 'string',
format => 'pve-storage-content',
optional => 1,
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",
optional => 1,
completion => \&PVE::Cluster::complete_vmid,
}),
},
),
},
},
returns => {
@ -66,7 +78,8 @@ __PACKAGE__->register_method ({
optional => 1,
},
'format' => {
description => "Format identifier ('raw', 'qcow2', 'subvol', 'iso', 'tgz' ...)",
description =>
"Format identifier ('raw', 'qcow2', 'subvol', 'iso', 'tgz' ...)",
type => 'string',
},
size => {
@ -75,8 +88,8 @@ __PACKAGE__->register_method ({
renderer => 'bytes',
},
used => {
description => "Used space. Please note that most storage plugins " .
"do not report anything useful here.",
description => "Used space. Please note that most storage plugins "
. "do not report anything useful here.",
type => 'integer',
renderer => 'bytes',
optional => 1,
@ -88,18 +101,21 @@ __PACKAGE__->register_method ({
optional => 1,
},
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',
optional => 1,
},
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.",
type => 'string',
optional => 1,
},
verification => {
description => "Last backup verification result, only useful for PBS storages.",
description =>
"Last backup verification result, only useful for PBS storages.",
type => 'object',
properties => {
state => {
@ -133,11 +149,16 @@ __PACKAGE__->register_method ({
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 = [];
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 $@;
$item->{vmid} = int($item->{vmid}) if defined($item->{vmid});
$item->{size} = int($item->{size}) if defined($item->{size});
@ -146,7 +167,8 @@ __PACKAGE__->register_method ({
}
return $res;
}});
},
});
__PACKAGE__->register_method({
name => 'create',
@ -162,26 +184,36 @@ __PACKAGE__->register_method ({
additionalProperties => 0,
properties => {
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,
}),
},
),
filename => {
description => "The name of the file to create.",
type => 'string',
},
vmid => get_standard_option('pve-vmid', {
vmid => get_standard_option(
'pve-vmid',
{
description => "Specify owner VM",
completion => \&PVE::Cluster::complete_vmid,
}),
},
),
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',
pattern => '\d+[MG]?',
},
format => get_standard_option('pve-storage-image-format', {
format => get_standard_option(
'pve-storage-image-format',
{
requires => 'size',
optional => 1,
}),
},
),
},
},
returns => {
@ -210,7 +242,8 @@ __PACKAGE__->register_method ({
if ($name =~ m/\.(raw|qcow2|vmdk)$/) {
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;
$param->{format} = $fmt;
@ -218,12 +251,13 @@ __PACKAGE__->register_method ({
my $cfg = PVE::Storage::config();
my $volid = PVE::Storage::vdisk_alloc ($cfg, $storeid, $param->{vmid},
$param->{format},
$name, $size);
my $volid = PVE::Storage::vdisk_alloc(
$cfg, $storeid, $param->{vmid}, $param->{format}, $name, $size,
);
return $volid;
}});
},
});
# we allow to pass volume names (without storage prefix) if the storage
# is specified as separate parameter.
@ -287,8 +321,8 @@ __PACKAGE__->register_method ({
renderer => 'bytes',
},
used => {
description => "Used space. Please note that most storage plugins " .
"do not report anything useful here.",
description => "Used space. Please note that most storage plugins "
. "do not report anything useful here.",
type => 'integer',
renderer => 'bytes',
},
@ -343,7 +377,8 @@ __PACKAGE__->register_method ({
}
return $entry;
}});
},
});
__PACKAGE__->register_method({
name => 'updateattributes',
@ -397,7 +432,8 @@ __PACKAGE__->register_method ({
}
return undef;
}});
},
});
__PACKAGE__->register_method({
name => 'delete',
@ -405,7 +441,8 @@ __PACKAGE__->register_method ({
method => 'DELETE',
description => "Delete volume",
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',
},
protected => 1,
@ -414,10 +451,13 @@ __PACKAGE__->register_method ({
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
storage => get_standard_option('pve-storage-id', {
storage => get_standard_option(
'pve-storage-id',
{
optional => 1,
completion => \&PVE::Storage::complete_storage,
}),
},
),
volume => {
description => "Volume identifier",
type => 'string',
@ -425,14 +465,15 @@ __PACKAGE__->register_method ({
},
delay => {
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,
maximum => 30,
optional => 1,
},
},
},
returns => { type => 'string', optional => 1, },
returns => { type => 'string', optional => 1 },
code => sub {
my ($param) = @_;
@ -454,8 +495,10 @@ __PACKAGE__->register_method ({
my $worker = sub {
PVE::Storage::vdisk_free($cfg, $volid);
print "Removed volume '$volid'\n";
if ($vtype eq 'backup'
&& $path =~ /(.*\/vzdump-\w+-\d+-\d{4}_\d{2}_\d{2}-\d{2}_\d{2}_\d{2})[^\/]+$/) {
if (
$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
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
do {
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;
} while (time() < $end_time && $currently_deleting);
@ -481,7 +525,8 @@ __PACKAGE__->register_method ({
}
}
return $upid;
}});
},
});
__PACKAGE__->register_method({
name => 'copy',
@ -503,10 +548,13 @@ __PACKAGE__->register_method ({
description => "Target volume identifier",
type => 'string',
},
target_node => get_standard_option('pve-node', {
target_node => get_standard_option(
'pve-node',
{
description => "Target node. Default is local node.",
optional => 1,
}),
},
),
},
},
returns => {
@ -548,13 +596,20 @@ __PACKAGE__->register_method ({
# you need to get this working (fails currently, because storage_migrate() uses
# ssh to connect to local host (which is not needed
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";
};
return $rpcenv->fork_worker('imgcopy', undef, $user, $worker);
}});
},
});
1;

View File

@ -47,11 +47,15 @@ __PACKAGE__->register_method ({
additionalProperties => 0,
properties => {
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,
}),
},
),
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',
completion => \&PVE::Storage::complete_volume,
},
@ -139,7 +143,8 @@ __PACKAGE__->register_method ({
}
die "invalid proxmox-file-restore output";
}});
},
});
__PACKAGE__->register_method({
name => 'download',
@ -156,11 +161,15 @@ __PACKAGE__->register_method ({
additionalProperties => 0,
properties => {
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,
}),
},
),
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',
completion => \&PVE::Storage::complete_volume,
},
@ -204,11 +213,16 @@ __PACKAGE__->register_method ({
my $client = PVE::PBSClient->new($scfg, $storeid);
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);
print "Starting download of file: $name\n";
$client->file_restore_extract($fifo, $snap, $path, 1, $tar);
});
},
);
my $ret = {
download => {
@ -218,6 +232,7 @@ __PACKAGE__->register_method ({
},
};
return $ret;
}});
},
});
1;

View File

@ -16,10 +16,16 @@ __PACKAGE__->register_method ({
name => 'dryrun',
path => '',
method => 'GET',
description => "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.",
description =>
"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 => {
check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
check => [
'perm',
'/storage/{storage}',
['Datastore.Audit', 'Datastore.AllocateSpace'],
any => 1,
],
},
protected => 1,
proxyto => 'node',
@ -27,24 +33,35 @@ __PACKAGE__->register_method ({
additionalProperties => 0,
properties => {
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,
}),
'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,
}),
},
),
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',
optional => 1,
enum => ['qemu', 'lxc'],
},
vmid => get_standard_option('pve-vmid', {
vmid => get_standard_option(
'pve-vmid',
{
description => "Only consider backups for this guest.",
optional => 1,
completion => \&PVE::Cluster::complete_vmid,
}),
},
),
},
},
returns => {
@ -57,12 +74,14 @@ __PACKAGE__->register_method ({
type => 'string',
},
'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',
},
'mark' => {
description => "Whether the backup would be kept or removed. Backups that are" .
" protected or don't use the standard naming scheme are not removed.",
description =>
"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',
enum => ['keep', 'remove', 'protected', 'renamed'],
},
@ -92,7 +111,8 @@ __PACKAGE__->register_method ({
if defined($prune_backups);
return PVE::Storage::prune_backups($cfg, $storeid, $prune_backups, $vmid, $type, 1);
}});
},
});
__PACKAGE__->register_method({
name => 'delete',
@ -100,8 +120,8 @@ __PACKAGE__->register_method ({
method => 'DELETE',
description => "Prune backups. Only those using the standard naming scheme are considered.",
permissions => {
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).",
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).",
user => 'all',
},
protected => 1,
@ -110,23 +130,34 @@ __PACKAGE__->register_method ({
additionalProperties => 0,
properties => {
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,
}),
'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 => {
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',
optional => 1,
enum => ['qemu', 'lxc'],
},
vmid => get_standard_option('pve-vmid', {
vmid => get_standard_option(
'pve-vmid',
{
description => "Only prune backups for this VM.",
completion => \&PVE::Cluster::complete_vmid,
optional => 1,
}),
},
),
},
},
returns => { type => 'string' },
@ -159,6 +190,7 @@ __PACKAGE__->register_method ({
};
return $rpcenv->fork_worker('prunebackups', $id, $authuser, $worker);
}});
},
});
1;

View File

@ -52,7 +52,8 @@ __PACKAGE__->register_method({
];
return $res;
}});
},
});
__PACKAGE__->register_method({
name => 'nfsscan',
@ -70,7 +71,8 @@ __PACKAGE__->register_method({
node => get_standard_option('pve-node'),
server => {
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} };
}
return $data;
}});
},
});
__PACKAGE__->register_method({
name => 'cifsscan',
@ -119,7 +122,8 @@ __PACKAGE__->register_method({
node => get_standard_option('pve-node'),
server => {
description => "The server address (name or IP).",
type => 'string', format => 'pve-storage-server',
type => 'string',
format => 'pve-storage-server',
},
username => {
description => "User name.",
@ -172,7 +176,8 @@ __PACKAGE__->register_method({
}
return $data;
}});
},
});
__PACKAGE__->register_method({
name => 'pbsscan',
@ -190,7 +195,8 @@ __PACKAGE__->register_method({
node => get_standard_option('pve-node'),
server => {
description => "The server address (name or IP).",
type => 'string', format => 'pve-storage-server',
type => 'string',
format => 'pve-storage-server',
},
username => {
description => "User-name or API token-ID.",
@ -236,7 +242,7 @@ __PACKAGE__->register_method({
my $password = delete $param->{password};
return PVE::Storage::PBSPlugin::scan_datastores($param, $password);
}
},
});
# Note: GlusterFS currently does not have an equivalent of showmount.
@ -258,7 +264,8 @@ __PACKAGE__->register_method({
node => get_standard_option('pve-node'),
server => {
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;
}});
},
});
__PACKAGE__->register_method({
name => 'iscsiscan',
@ -305,7 +313,8 @@ __PACKAGE__->register_method({
node => get_standard_option('pve-node'),
portal => {
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;
}});
},
});
__PACKAGE__->register_method({
name => 'lvmscan',
@ -371,7 +381,8 @@ __PACKAGE__->register_method({
my $res = PVE::Storage::LVMPlugin::lvm_vgs();
return PVE::RESTHandler::hash_to_array($res, 'vg');
}});
},
});
__PACKAGE__->register_method({
name => 'lvmthinscan',
@ -410,7 +421,8 @@ __PACKAGE__->register_method({
my ($param) = @_;
return PVE::Storage::LvmThinPlugin::list_thinpools($param->{vg});
}});
},
});
__PACKAGE__->register_method({
name => 'zfsscan',
@ -444,6 +456,7 @@ __PACKAGE__->register_method({
my ($param) = @_;
return PVE::Storage::scan_zfs();
}});
},
});
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
my $hasOvf = 0;
run_command(['tar', '-t', '-f', $file], outfunc => sub {
run_command(
['tar', '-t', '-f', $file],
outfunc => sub {
my ($line) = @_;
if ($line =~ m/\.ovf$/) {
$hasOvf = 1;
}
});
},
);
die "ova archive has no .ovf file inside\n" if !$hasOvf;
@ -65,7 +68,8 @@ __PACKAGE__->register_method ({
method => 'GET',
description => "Get status for all datastores.",
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',
},
protected => 1,
@ -74,14 +78,18 @@ __PACKAGE__->register_method ({
additionalProperties => 0,
properties => {
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",
optional => 1,
completion => \&PVE::Storage::complete_storage_enabled,
}),
},
),
content => {
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,
completion => \&PVE::Storage::complete_content_type,
},
@ -91,12 +99,16 @@ __PACKAGE__->register_method ({
optional => 1,
default => 0,
},
target => get_standard_option('pve-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.",
target => get_standard_option(
'pve-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,
completion => \&PVE::Cluster::get_nodelist,
}),
},
),
'format' => {
description => "Include information about formats",
type => 'boolean',
@ -117,7 +129,8 @@ __PACKAGE__->register_method ({
},
content => {
description => "Allowed storage content types.",
type => 'string', format => 'pve-storage-content-list',
type => 'string',
format => 'pve-storage-content-list',
},
enabled => {
description => "Set when storage is enabled (not disabled).",
@ -211,7 +224,8 @@ __PACKAGE__->register_method ({
}
return PVE::RESTHandler::hash_to_array($res, 'storage');
}});
},
});
__PACKAGE__->register_method({
name => 'diridx',
@ -219,7 +233,12 @@ __PACKAGE__->register_method ({
method => 'GET',
description => "",
permissions => {
check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
check => [
'perm',
'/storage/{storage}',
['Datastore.Audit', 'Datastore.AllocateSpace'],
any => 1,
],
},
parameters => {
additionalProperties => 0,
@ -254,7 +273,8 @@ __PACKAGE__->register_method ({
];
return $res;
}});
},
});
__PACKAGE__->register_method({
name => 'read_status',
@ -262,7 +282,12 @@ __PACKAGE__->register_method ({
method => 'GET',
description => "Read storage status.",
permissions => {
check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
check => [
'perm',
'/storage/{storage}',
['Datastore.Audit', 'Datastore.AllocateSpace'],
any => 1,
],
},
protected => 1,
proxyto => 'node',
@ -290,7 +315,8 @@ __PACKAGE__->register_method ({
if !defined($data);
return $data;
}});
},
});
__PACKAGE__->register_method({
name => 'rrd',
@ -298,7 +324,12 @@ __PACKAGE__->register_method ({
method => 'GET',
description => "Read storage RRD statistics (returns PNG).",
permissions => {
check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
check => [
'perm',
'/storage/{storage}',
['Datastore.Audit', 'Datastore.AllocateSpace'],
any => 1,
],
},
protected => 1,
proxyto => 'node',
@ -314,7 +345,8 @@ __PACKAGE__->register_method ({
},
ds => {
description => "The list of datasources you want to display.",
type => 'string', format => 'pve-configid-list',
type => 'string',
format => 'pve-configid-list',
},
cf => {
description => "The RRD consolidation function",
@ -333,10 +365,10 @@ __PACKAGE__->register_method ({
code => sub {
my ($param) = @_;
return PVE::RRD::create_rrd_graph(
"pve2-storage/$param->{node}/$param->{storage}",
return PVE::RRD::create_rrd_graph("pve2-storage/$param->{node}/$param->{storage}",
$param->{timeframe}, $param->{ds}, $param->{cf});
}});
},
});
__PACKAGE__->register_method({
name => 'rrddata',
@ -344,7 +376,12 @@ __PACKAGE__->register_method ({
method => 'GET',
description => "Read storage RRD statistics.",
permissions => {
check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
check => [
'perm',
'/storage/{storage}',
['Datastore.Audit', 'Datastore.AllocateSpace'],
any => 1,
],
},
protected => 1,
proxyto => 'node',
@ -378,8 +415,11 @@ __PACKAGE__->register_method ({
return PVE::RRD::create_rrd_data(
"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
# create a copy of the file).
@ -399,11 +439,13 @@ __PACKAGE__->register_method ({
storage => get_standard_option('pve-storage-id'),
content => {
description => "Content type.",
type => 'string', format => 'pve-storage-content',
type => 'string',
format => 'pve-storage-content',
enum => ['iso', 'vztmpl', 'import'],
},
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,
type => 'string',
},
@ -421,7 +463,8 @@ __PACKAGE__->register_method ({
optional => 1,
},
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',
optional => 1,
pattern => '/var/tmp/pveupload-[0-9a-f]+',
@ -469,7 +512,9 @@ __PACKAGE__->register_method ({
}
$path = PVE::Storage::get_vztmpl_dir($cfg, $storage);
} 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" });
}
my $format = $1;
@ -500,7 +545,8 @@ __PACKAGE__->register_method ({
if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
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, '--');
@ -514,7 +560,14 @@ __PACKAGE__->register_method ({
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]) };
} else {
@ -530,11 +583,13 @@ __PACKAGE__->register_method ({
print "starting file import from: $tmpfilename\n";
eval {
my ($checksum, $checksum_algorithm) = $param->@{'checksum', 'checksum-algorithm'};
my ($checksum, $checksum_algorithm) =
$param->@{ 'checksum', 'checksum-algorithm' };
if ($checksum_algorithm) {
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)) {
print "OK, checksum verified\n";
@ -557,7 +612,8 @@ __PACKAGE__->register_method ({
};
if (my $err = $@) {
# 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";
die $err;
}
@ -570,7 +626,8 @@ __PACKAGE__->register_method ({
eval { run_command($cmd, errmsg => 'import failed'); };
# 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";
if (my $err = $@) {
@ -582,7 +639,8 @@ __PACKAGE__->register_method ({
};
return $rpcenv->fork_worker('imgcopy', undef, $user, $worker);
}});
},
});
__PACKAGE__->register_method({
name => 'download_url',
@ -591,12 +649,15 @@ __PACKAGE__->register_method({
description => "Download templates, ISO images, OVAs and VM images by using an URL.",
proxyto => 'node',
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'
. ' backwards compatibility) or the newer Sys.AccessNetwork privilege on the node.',
check => [ 'and',
check => [
'and',
['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
[ 'or',
[
'or',
['perm', '/', ['Sys.Audit', 'Sys.Modify']],
['perm', '/nodes/{node}', ['Sys.AccessNetwork']],
],
@ -615,11 +676,13 @@ __PACKAGE__->register_method({
},
content => {
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'],
},
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,
type => 'string',
},
@ -652,7 +715,7 @@ __PACKAGE__->register_method({
},
},
returns => {
type => "string"
type => "string",
},
code => sub {
my ($param) = @_;
@ -690,7 +753,9 @@ __PACKAGE__->register_method({
}
$path = PVE::Storage::get_vztmpl_dir($cfg, $storage);
} 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" });
}
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
return $rpcenv->fork_worker('download', $worker_id, $user, $worker);
}});
},
});
__PACKAGE__->register_method({
name => 'get_import_metadata',
@ -796,7 +862,8 @@ __PACKAGE__->register_method({
'create-args' => {
type => 'object',
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' => {
type => 'object',
@ -808,7 +875,8 @@ __PACKAGE__->register_method({
type => 'object',
additionalProperties => 1,
optional => 1,
description => 'Recognised network interfaces as `net$id` => { ...params } object.',
description =>
'Recognised network interfaces as `net$id` => { ...params } object.',
},
'warnings' => {
type => 'array',
@ -860,9 +928,13 @@ __PACKAGE__->register_method({
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);
},
);
},
});
}});
1;

View File

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

View File

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

View File

@ -6,9 +6,7 @@ use Net::IP;
use PVE::Tools qw(run_command);
use PVE::Cluster qw(cfs_register_file);
cfs_register_file('ceph.conf',
\&parse_ceph_config,
\&write_ceph_config);
cfs_register_file('ceph.conf', \&parse_ceph_config, \&write_ceph_config);
# For more information on how the Ceph parser works and how its grammar is
# defined, see:
@ -417,7 +415,8 @@ sub ceph_connect_option {
if (-e "/etc/pve/priv/ceph/${storeid}.conf") {
# allow custom ceph configuration for external clusters
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 {
$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');
mkdir '/etc/pve/priv/ceph';
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 = $@) {
@ -504,9 +504,12 @@ sub local_ceph_version {
my $version_string = $cache;
if (!defined($version_string)) {
run_command('ceph --version', outfunc => sub {
run_command(
'ceph --version',
outfunc => sub {
$version_string = shift;
});
},
);
}
return undef if !defined($version_string);
# 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 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 $ZPOOL = "/sbin/zpool";
@ -98,7 +99,10 @@ sub get_smart_data {
push @$cmd, $disk;
my $returncode = eval {
run_command($cmd, noerr => 1, outfunc => sub {
run_command(
$cmd,
noerr => 1,
outfunc => sub {
my ($line) = @_;
# ATA SMART attributes, e.g.:
@ -109,7 +113,12 @@ sub get_smart_data {
# Data Units Written: 5,584,952 [2.85 TB]
# 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 = {};
$entry->{name} = $2 if defined $2;
@ -140,13 +149,16 @@ sub get_smart_data {
$smartdata->{text} = '' if !defined $smartdata->{text};
$smartdata->{text} .= "$line\n";
# 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;
}
} elsif ($line =~ m/SMART Disabled/) {
$smartdata->{health} = "SMART Disabled";
}
})
},
);
};
my $err = $@;
@ -163,7 +175,9 @@ sub get_smart_data {
sub get_lsblk_info {
my $cmd = [$LSBLK, '--json', '-o', 'path,parttype,fstype'];
my $output = "";
eval { run_command($cmd, outfunc => sub { $output .= "$_[0]\n"; }) };
eval {
run_command($cmd, outfunc => sub { $output .= "$_[0]\n"; });
};
warn "$@\n" if $@;
return {} if $output eq '';
@ -175,7 +189,7 @@ sub get_lsblk_info {
map {
$_->{path} => {
parttype => $_->{parttype},
fstype => $_->{fstype}
fstype => $_->{fstype},
}
} @{$list}
};
@ -203,12 +217,15 @@ sub get_zfs_devices {
# use zpool and parttype uuid, because log and cache do not have zfs type uuid
eval {
run_command([$ZPOOL, 'list', '-HPLv'], outfunc => sub {
run_command(
[$ZPOOL, 'list', '-HPLv'],
outfunc => sub {
my ($line) = @_;
if ($line =~ m|^\t([^\t]+)\t|) {
$res->{$1} = 1;
}
});
},
);
};
# 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
};
$res = get_devices_by_partuuid($lsblk_info, $uuids, $res);
return $res;
@ -229,13 +245,16 @@ sub get_lvm_devices {
my ($lsblk_info) = @_;
my $res = {};
eval {
run_command([$PVS, '--noheadings', '--readonly', '-o', 'pv_name'], outfunc => sub{
run_command(
[$PVS, '--noheadings', '--readonly', '-o', 'pv_name'],
outfunc => sub {
my ($line) = @_;
$line = trim($line);
if ($line =~ m|^/dev/|) {
$res->{$line} = 1;
}
});
},
);
};
# 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 {
my $result = {};
my $cmd = [ $LVS, '-S', 'lv_name=~^osd-', '-o', 'devices,lv_name,lv_tags',
'--noheadings', '--readonly', '--separator', ';' ];
my $cmd = [
$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;
$line =~ s/(?:^\s+)|(?:\s+$)//g; # trim whitespaces
@ -284,7 +314,10 @@ sub get_ceph_volume_infos {
if ($fields->[1] =~ m|^osd-([^-]+)-|) {
my $type = $1;
# $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;
if (!defined($result->{$dev}->{'osdid-list'})) {
$result->{$dev}->{'osdid-list'} = [];
@ -299,7 +332,8 @@ sub get_ceph_volume_infos {
$result->{$dev}->{$type}++;
}
}
});
},
);
return $result;
}
@ -310,10 +344,13 @@ sub get_udev_info {
my $info = "";
my $data = {};
eval {
run_command(['udevadm', 'info', '-p', $dev, '--query', 'all'], outfunc => sub {
run_command(
['udevadm', 'info', '-p', $dev, '--query', 'all'],
outfunc => sub {
my ($line) = @_;
$info .= "$line\n";
});
},
);
};
warn $@ if $@;
return if !$info;
@ -403,7 +440,7 @@ sub get_wear_leveling_info {
"Lifetime_Remaining",
"Percent_Life_Remaining",
"Percent_Lifetime_Used",
"Perc_Rated_Life_Used"
"Perc_Rated_Life_Used",
);
# Search for S.M.A.R.T. attributes for known register
@ -457,7 +494,7 @@ sub mounted_blockdevs {
foreach my $mount (@$mounts) {
next if $mount->[0] !~ m|^/dev/|;
$mounted->{ abs_path($mount->[0]) } = $mount->[1];
};
}
return $mounted;
}
@ -470,7 +507,7 @@ sub mounted_paths {
foreach my $mount (@$mounts) {
$mounted->{ abs_path($mount->[1]) } = $mount->[0];
};
}
return $mounted;
}
@ -511,7 +548,10 @@ sub get_disks {
$disk_regex = "(?:" . join('|', @$disks) . ")";
}
dir_glob_foreach('/sys/block', $disk_regex, sub {
dir_glob_foreach(
'/sys/block',
$disk_regex,
sub {
my ($dev) = @_;
# whitelisting following devices
# - hdX ide block device
@ -520,9 +560,10 @@ sub get_disks {
# - xvdX: xen virtual block device
# - nvmeXnY: nvme devices
# - cciss!cXnY cciss devices
return if $dev !~ m/^(h|s|x?v)d[a-z]+$/ &&
$dev !~ m/^nvme\d+n\d+$/ &&
$dev !~ m/^cciss\!c\d+d\d+$/;
return
if $dev !~ m/^(h|s|x?v)d[a-z]+$/
&& $dev !~ m/^nvme\d+n\d+$/
&& $dev !~ m/^cciss\!c\d+d\d+$/;
my $data = get_udev_info("/sys/block/$dev") // return;
my $devpath = $data->{devpath};
@ -606,7 +647,8 @@ sub get_disks {
if (defined(my $parttype = $info->{parttype})) {
return 'BIOS boot' if $parttype eq '21686148-6449-6e6f-744e-656564454649';
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});
@ -640,7 +682,10 @@ sub get_disks {
};
my $partitions = {};
dir_glob_foreach("$sysdir", "$dev.+", sub {
dir_glob_foreach(
"$sysdir",
"$dev.+",
sub {
my ($part) = @_;
$partitions->{$part} = $collect_ceph_info->("$partpath/$part");
@ -652,7 +697,8 @@ sub get_disks {
$partitions->{$part}->{gpt} = $data->{gpt};
$partitions->{$part}->{type} = 'partition';
$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-list'} //= undef;
@ -680,7 +726,8 @@ sub get_disks {
$partitions->{$part}->{wal} = 1 if $journal_part == 3;
$partitions->{$part}->{bluestore} = 1 if $journal_part == 4;
}
});
},
);
my $used = $determine_usage->($devpath, $sysdir, 0);
if (!$include_partitions) {
@ -712,7 +759,8 @@ sub get_disks {
if ($include_partitions) {
$disklist->{$_} = $partitions->{$_} for keys %{$partitions};
}
});
},
);
return $disklist;
}
@ -783,28 +831,38 @@ sub append_partition {
$devname =~ s|^/dev/||;
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) = @_;
if ($partid >= $newpartid) {
$newpartid = $partid + 1;
}
});
},
);
$size = PVE::Tools::convert_size($size, 'b' => 'mb');
run_command([ $SGDISK, '-n', "$newpartid:0:+${size}M", $dev ],
errmsg => "error creating partition '$newpartid' on '$dev'");
run_command(
[$SGDISK, '-n', "$newpartid:0:+${size}M", $dev],
errmsg => "error creating partition '$newpartid' on '$dev'",
);
my $partition;
# loop again to detect the real partition device which does not always follow
# 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) = @_;
$partition = "/dev/$part";
});
},
);
return $partition;
}
@ -820,10 +878,14 @@ sub has_holder {
return $devpath if !dir_is_empty("/sys/class/block/${dev}/holders");
my $found;
dir_glob_foreach("/sys/block/${dev}", "${dev}.+", sub {
dir_glob_foreach(
"/sys/block/${dev}",
"${dev}.+",
sub {
my ($part) = @_;
$found = "/dev/${part}" if !dir_is_empty("/sys/class/block/${part}/holders");
});
},
);
return $found;
}
@ -841,12 +903,16 @@ sub is_mounted {
my $dev = strip_dev($devpath);
my $found;
dir_glob_foreach("/sys/block/${dev}", "${dev}.+", sub {
dir_glob_foreach(
"/sys/block/${dev}",
"${dev}.+",
sub {
my ($part) = @_;
my $partpath = "/dev/${part}";
$found = $partpath if $mounted->{$partpath};
});
},
);
return $found;
}
@ -884,10 +950,14 @@ sub wipe_blockdev {
my $count = ($size < 200) ? $size : 200;
my $to_wipe = [];
dir_glob_foreach("/sys/class/block/${devname}", "${devname}.+", sub {
dir_glob_foreach(
"/sys/class/block/${devname}",
"${devname}.+",
sub {
my ($part) = @_;
push $to_wipe->@*, "/dev/${part}" if -b "/dev/${part}";
});
},
);
if (scalar($to_wipe->@*) > 0) {
print "found child partitions to wipe: " . join(', ', $to_wipe->@*) . "\n";

View File

@ -54,14 +54,17 @@ sub extract_disk_from_import_file {
'-x',
'--force-local',
'--no-same-owner',
'-C', $tmpdir,
'-f', $ova_path,
'-C',
$tmpdir,
'-f',
$ova_path,
$inner_file,
]);
# check for symlinks and other non regular files
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!
@ -69,7 +72,8 @@ sub extract_disk_from_import_file {
# create temporary 1M image that will get overwritten by the rename
# 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);
print "renaming $source_path to $target_path\n";

View File

@ -51,7 +51,7 @@ my @resources = (
{ id => 32, dtmf_name => 'Storage Volume' },
{ id => 33, dtmf_name => 'Ethernet Connection' },
{ 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
@ -120,9 +120,7 @@ sub get_ostype {
}
my $allowed_nic_models = [
'e1000',
'e1000e',
'vmxnet3',
'e1000', 'e1000e', 'vmxnet3',
];
sub find_by {
@ -177,24 +175,31 @@ sub parse_ovf {
my $dom;
if ($isOva) {
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;
$raw .= $line;
});
},
);
$dom = XML::LibXML->load_xml(string => $raw, no_blanks => 1);
} else {
$dom = XML::LibXML->load_xml(location => $ovf, no_blanks => 1);
}
# register the xml namespaces in a xpath context object
# 'ovf' is the default namespace so it will prepended to each xml element
my $xpc = XML::LibXML::XPathContext->new($dom);
$xpc->registerNs('ovf', 'http://schemas.dmtf.org/ovf/envelope/1');
$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('vssd', 'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData');
$xpc->registerNs(
'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
my $qm;
@ -222,32 +227,39 @@ sub parse_ovf {
$ovf_name =~ s/\s+/-/g;
($qm->{name} = $ovf_name) =~ s/[^a-zA-Z0-9\-\.]//g;
} 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
# element[child] search the elements which have this [child]
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);
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);
# middle level xpath
# here we expect multiple results, so we do not read the element value with
# findvalue() but store multiple elements with findnodes()
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 $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);
$qm->{ostype} = get_ostype($ostype_id);
# 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';
$qm->{bios} = 'ovmf' if $firmware eq 'efi';
@ -290,12 +302,18 @@ sub parse_ovf {
# tricky xpath
# @ 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
my $xpath_find_fileref = sprintf("/ovf:Envelope/ovf:DiskSection/\
ovf:Disk[\@ovf:diskId='%s']/\@ovf:fileRef", $disk_id);
my $xpath_find_capacity = sprintf("/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 $xpath_find_fileref = sprintf(
"/ovf:Envelope/ovf:DiskSection/\
ovf:Disk[\@ovf:diskId='%s']/\@ovf:fileRef", $disk_id,
);
my $xpath_find_capacity = sprintf(
"/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 $capacity = $xpc->findvalue($xpath_find_capacity);
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
my $controller_id = $xpc->findvalue('rasd:Parent', $item_node);
my $xpath_find_parent_type = sprintf("/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/\
ovf:Item[rasd:InstanceID='%s']/rasd:ResourceType", $controller_id);
my $xpath_find_parent_type = sprintf(
"/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/\
ovf:Item[rasd:InstanceID='%s']/rasd:ResourceType", $controller_id,
);
my $controller_type = $xpc->findvalue($xpath_find_parent_type);
if (!$controller_type) {
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;
# 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);
if (!$filepath) {
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;
my $original_filepath = $filepath;
($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
# 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;
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 $net = {};

View File

@ -69,7 +69,10 @@ PVE::Storage::ESXiPlugin->register();
# load third-party plugins
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 $modname = 'PVE::Storage::Custom::' . $file;
$modname =~ s!\.pm$!!;
@ -79,11 +82,13 @@ if ( -d '/usr/share/perl5/PVE/Storage/Custom' ) {
require $file;
# 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');
# Check storage API version and that file is really storage plugin.
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;
my $min_version = (APIVER - APIAGE);
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();
# 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 ($@) {
warn "Error loading storage plugin \"$modname\": $@";
}
});
},
);
}
# initialize all plugins
@ -164,8 +171,7 @@ my $convert_maxfiles_to_prune_backups = sub {
$prune_backups = { 'keep-all' => 1 };
}
$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 $protected_count = grep {
$_->{protected} && (!$backup_type || ($_->{subtype} && $_->{subtype} eq $backup_type))
$_->{protected}
&& (!$backup_type || ($_->{subtype} && $_->{subtype} eq $backup_type))
} $backups->@*;
if ($max_protected_backups <= $protected_count) {
die "The number of protected backups per guest is limited to $max_protected_backups ".
"on storage '$storeid'\n";
die "The number of protected backups per guest is limited to $max_protected_backups "
. "on storage '$storeid'\n";
}
}
@ -429,7 +436,8 @@ sub volume_has_feature {
if ($storeid) {
my $scfg = storage_config($cfg, $storeid);
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) {
return undef;
} else {
@ -553,7 +561,11 @@ sub check_volume_access {
if ($vtype eq 'iso' || $vtype eq 'vztmpl' || $vtype eq 'import') {
# 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)) {
# we are owner - allow access
} elsif ($vtype eq 'backup' && $ownervm) {
@ -583,8 +595,7 @@ sub volume_is_base_and_used {
my $scfg = storage_config($cfg, $storeid);
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
my ($vtype, $name, $vmid, undef, undef, $isBase, undef) =
$plugin->parse_volname($volname);
my ($vtype, $name, $vmid, undef, undef, $isBase, undef) = $plugin->parse_volname($volname);
if ($isBase) {
my $vollist = $plugin->list_images($storeid, $scfg);
@ -783,7 +794,9 @@ my $volume_export_prepare = sub {
my $cstream;
if (defined($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;
@ -830,11 +843,19 @@ sub storage_migrate {
local $ENV{RSYNC_RSH} = PVE::Tools::cmd2string($ssh_base);
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};
}
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;
my $format = $formats[0];
@ -844,7 +865,8 @@ sub storage_migrate {
$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 $pattern = volume_imported_message(undef, 1);
@ -961,10 +983,15 @@ sub vdisk_clone {
activate_storage($cfg, $storeid);
# 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);
return "$storeid:$volname";
});
},
);
}
sub vdisk_create_base {
@ -979,10 +1006,15 @@ sub vdisk_create_base {
activate_storage($cfg, $storeid);
# 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);
return "$storeid:$volname";
});
},
);
}
sub map_volume {
@ -1031,14 +1063,20 @@ sub vdisk_alloc {
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
# 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 $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 = $@;
umask $old_umask;
die $err if $err;
return "$storeid:$volname";
});
},
);
}
sub vdisk_free {
@ -1053,7 +1091,11 @@ sub vdisk_free {
my $cleanup_worker;
# 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!
die "base volume '$volname' is still in use by linked clones\n"
if volume_is_base_and_used($cfg, $volid);
@ -1061,7 +1103,8 @@ sub vdisk_free {
my (undef, undef, undef, undef, undef, $isBase, $format) =
$plugin->parse_volname($volname);
$cleanup_worker = $plugin->free_image($storeid, $scfg, $volname, $isBase, $format);
});
},
);
return if !$cleanup_worker;
@ -1111,7 +1154,8 @@ sub vdisk_list {
my $scfg = $ids->{$sid};
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
$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;
@ -1276,9 +1320,7 @@ sub deactivate_volumes {
my $scfg = storage_config($cfg, $storeid);
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
eval {
$plugin->deactivate_volume($storeid, $scfg, $volname, $snapname, $cache);
};
eval { $plugin->deactivate_volume($storeid, $scfg, $volname, $snapname, $cache); };
if (my $err = $@) {
warn $err;
push @errlist, $volid;
@ -1321,7 +1363,8 @@ sub storage_info {
avail => 0,
used => 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,
enabled => $storage_enabled ? 1 : 0,
};
@ -1390,14 +1433,17 @@ sub scan_nfs {
my $cmd = ['/sbin/showmount', '--no-headers', '--exports', $server];
my $res = {};
run_command($cmd, outfunc => sub {
run_command(
$cmd,
outfunc => sub {
my $line = shift;
# note: howto handle white spaces in export path??
if ($line =~ m!^(/\S+)\s+(.+)$!) {
$res->{$1} = $2;
}
});
},
);
return $res;
}
@ -1418,10 +1464,11 @@ sub scan_cifs {
my $res = {};
my $err = '';
run_command($cmd,
run_command(
$cmd,
noerr => 1,
errfunc => sub {
$err .= "$_[0]\n"
$err .= "$_[0]\n";
},
outfunc => sub {
my $line = shift;
@ -1445,7 +1492,9 @@ sub scan_zfs {
my $cmd = ['zfs', 'list', '-t', 'filesystem', '-Hp', '-o', 'name,avail,used'];
my $res = [];
run_command($cmd, outfunc => sub {
run_command(
$cmd,
outfunc => sub {
my $line = shift;
if ($line =~ m/^(\S+)\s+(\S+)\s+(\S+)$/) {
@ -1457,7 +1506,8 @@ sub scan_zfs {
return if $pool =~ m!/basevol-\d+-[^/]+$!;
push @$res, { pool => $pool, size => $size, free => $size - $used };
}
});
},
);
return $res;
}
@ -1478,7 +1528,6 @@ sub resolv_portal {
raise_param_exc({ portal => "unable to resolve portal address '$portal'" });
}
sub scan_iscsi {
my ($portal_in) = @_;
@ -1630,7 +1679,9 @@ sub archive_info {
$info->{filename} = $filename;
$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->{notesfilename} = "$filename" . PVE::Storage::Plugin::NOTES_EXT;
$info->{vmid} = int($2);
@ -1678,8 +1729,8 @@ sub extract_vzdump_config_tar {
die "ERROR: file '$archive' does not exist\n" if !-f $archive;
my $pid = open(my $fh, '-|', 'tar', 'tf', $archive) ||
die "unable to open file '$archive'\n";
my $pid = open(my $fh, '-|', 'tar', 'tf', $archive)
|| die "unable to open file '$archive'\n";
my $file;
while (defined($file = <$fh>)) {
@ -1725,7 +1776,11 @@ sub extract_vzdump_config_vma {
my $errstring;
my $err = sub {
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;
} elsif (!defined($errstring) && $output !~ m/^\s*$/) {
$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}];
$prune_mark->($prune_list, $keep->{'keep-last'}, sub {
$prune_mark->(
$prune_list,
$keep->{'keep-last'},
sub {
my ($ctime) = @_;
return $ctime;
});
$prune_mark->($prune_list, $keep->{'keep-hourly'}, sub {
},
);
$prune_mark->(
$prune_list,
$keep->{'keep-hourly'},
sub {
my ($ctime) = @_;
my (undef, undef, $hour, $day, $month, $year) = localtime($ctime);
return "$hour/$day/$month/$year";
});
$prune_mark->($prune_list, $keep->{'keep-daily'}, sub {
},
);
$prune_mark->(
$prune_list,
$keep->{'keep-daily'},
sub {
my ($ctime) = @_;
my (undef, undef, undef, $day, $month, $year) = localtime($ctime);
return "$day/$month/$year";
});
$prune_mark->($prune_list, $keep->{'keep-weekly'}, sub {
},
);
$prune_mark->(
$prune_list,
$keep->{'keep-weekly'},
sub {
my ($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_year = int(strftime("%G", $sec, $min, $hour, $day, $month, $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 (undef, undef, undef, undef, $month, $year) = localtime($ctime);
return "$month/$year";
});
$prune_mark->($prune_list, $keep->{'keep-yearly'}, sub {
},
);
$prune_mark->(
$prune_list,
$keep->{'keep-yearly'},
sub {
my ($ctime) = @_;
my $year = (localtime($ctime))[5];
return "$year";
});
},
);
foreach my $prune_entry (@{$prune_list}) {
$prune_entry->{mark} //= 'remove';
@ -1899,8 +1978,9 @@ sub volume_export : prototype($$$$$$$) {
die "cannot export volume '$volid'\n" if !$storeid;
my $scfg = storage_config($cfg, $storeid);
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
return $plugin->volume_export($scfg, $storeid, $fh, $volname, $format,
$snapshot, $base_snapshot, $with_snapshots);
return $plugin->volume_export(
$scfg, $storeid, $fh, $volname, $format, $snapshot, $base_snapshot, $with_snapshots,
);
}
sub volume_import : prototype($$$$$$$$) {
@ -1930,9 +2010,9 @@ sub volume_export_formats : prototype($$$$$) {
return if !$storeid;
my $scfg = storage_config($cfg, $storeid);
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
return $plugin->volume_export_formats($scfg, $storeid, $volname,
$snapshot, $base_snapshot,
$with_snapshots);
return $plugin->volume_export_formats(
$scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots,
);
}
sub volume_import_formats : prototype($$$$$) {
@ -1943,19 +2023,16 @@ sub volume_import_formats : prototype($$$$$) {
my $scfg = storage_config($cfg, $storeid);
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
return $plugin->volume_import_formats(
$scfg,
$storeid,
$volname,
$snapshot,
$base_snapshot,
$with_snapshots,
$scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots,
);
}
sub volume_transfer_formats {
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 @import_formats = volume_import_formats($cfg, $dst_volid, $snapshot, $base_snapshot, $with_snapshots);
my @export_formats =
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 @common = grep { $import_hash{$_} } @export_formats;
return @common;
@ -1987,7 +2064,8 @@ sub volume_import_start {
my $volid = "$storeid:$volname";
# 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 %import_hash = map { $_ => 1 } @import_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;
return $plugin->cluster_lock_storage($storeid, $scfg->{shared}, undef, sub {
return $plugin->rename_volume($scfg, $storeid, $source_volname, $target_vmid, $target_volname);
});
return $plugin->cluster_lock_storage(
$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
@ -2171,7 +2256,8 @@ sub get_bandwidth_limit {
# limits, therefore it also allows us to override them.
# 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:
return $override if $rpcenv && $rpcenv->check($authuser, '/storage', ['Datastore.Allocate'], 1);
return $override
if $rpcenv && $rpcenv->check($authuser, '/storage', ['Datastore.Allocate'], 1);
my %done;
foreach my $storage (@$storage_list) {
@ -2181,7 +2267,10 @@ sub get_bandwidth_limit {
$done{$storage} = 1;
# 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.
my $storecfg = storage_config($config, $storage);
$apply_limit->($storecfg->{bwlimit});

View File

@ -44,7 +44,7 @@ sub plugindata {
},
{ images => 1, rootdir => 1 },
],
format => [ { raw => 1, subvol => 1 }, 'raw', ],
format => [{ raw => 1, subvol => 1 }, 'raw'],
'sensitive-properties' => {},
};
}
@ -95,7 +95,8 @@ sub options {
# Reuse `DirPlugin`'s `check_config`. This simply checks for invalid paths.
sub check_config {
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($) {
@ -142,18 +143,14 @@ sub status {
sub get_volume_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 {
my ($class, $scfg, $storeid, $volname, $attribute, $value) = @_;
return PVE::Storage::DirPlugin::update_volume_attribute(
$class,
$scfg,
$storeid,
$volname,
$attribute,
$value,
$class, $scfg, $storeid, $volname, $attribute, $value,
);
}
@ -190,8 +187,7 @@ sub raw_file_to_subvol($) {
sub filesystem_path {
my ($class, $scfg, $volname, $snapname) = @_;
my ($vtype, $name, $vmid, undef, undef, $isBase, $format) =
$class->parse_volname($volname);
my ($vtype, $name, $vmid, undef, undef, $isBase, $format) = $class->parse_volname($volname);
my $path = $class->get_subdir($scfg, $vtype);
@ -415,19 +411,22 @@ my sub foreach_snapshot_of_subvol : prototype($$) {
my $basename = basename($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);
return if !path_is_subvolume("$dir/$volume");
return if $name ne $basename;
$code->($snap_name);
});
},
);
}
sub free_image {
my ($class, $storeid, $scfg, $volname, $isBase, $_format) = @_;
my ($vtype, undef, $vmid, undef, undef, undef, $format) =
$class->parse_volname($volname);
my ($vtype, undef, $vmid, undef, undef, undef, $format) = $class->parse_volname($volname);
if (!defined($format) || $vtype ne 'images' || ($format ne 'subvol' && $format ne 'raw')) {
return $class->SUPER::free_image($storeid, $scfg, $volname, $isBase, $_format);
@ -441,10 +440,13 @@ sub free_image {
}
my @snapshot_vols;
foreach_snapshot_of_subvol($subvol, sub {
foreach_snapshot_of_subvol(
$subvol,
sub {
my ($snap_name) = @_;
push @snapshot_vols, "$subvol\@$snap_name";
});
},
);
$class->btrfs_cmd(['subvolume', 'delete', '--', @snapshot_vols, $subvol]);
# try to cleanup directory to not clutter storage with empty $vmid dirs if
@ -604,7 +606,7 @@ sub volume_has_feature {
my $features = {
snapshot => {
current => { qcow2 => 1, raw => 1, subvol => 1 },
snap => { qcow2 => 1, raw => 1, subvol => 1 }
snap => { qcow2 => 1, raw => 1, subvol => 1 },
},
clone => {
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;
if ($snapname) {
@ -674,9 +677,8 @@ sub list_images {
$format = 'subvol';
} else {
$format = $ext;
($size, undef, $used, $parent, $ctime) = eval {
PVE::Storage::Plugin::file_size_info($fn, undef, $format);
};
($size, undef, $used, $parent, $ctime) =
eval { PVE::Storage::Plugin::file_size_info($fn, undef, $format); };
if (my $err = $@) {
die $err if $err !~ m/Image is not in \S+ format$/;
warn "image '$fn' is not in expected format '$format', querying as raw\n";
@ -692,8 +694,12 @@ sub list_images {
}
my $info = {
volid => $volid, format => $format,
size => $size, vmid => $owner, used => $used, parent => $parent,
volid => $volid,
format => $format,
size => $size,
vmid => $owner,
used => $used,
parent => $parent,
};
$info->{ctime} = $ctime if $ctime;
@ -730,13 +736,7 @@ sub volume_import_formats {
# Same as export-formats, beware the parameter order:
return volume_export_formats(
$class,
$scfg,
$storeid,
$volname,
$snapshot,
$base_snapshot,
$with_snapshots,
$class, $scfg, $storeid, $volname, $snapshot, $base_snapshot, $with_snapshots,
);
}
@ -787,11 +787,15 @@ sub volume_export {
push @$cmd, (map { "$path\@$_" } ($with_snapshots // [])->@*);
push @$cmd, $path if !defined($base_snapshot);
} else {
foreach_snapshot_of_subvol($path, sub {
foreach_snapshot_of_subvol(
$path,
sub {
my ($snap_name) = @_;
# 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);
push @$cmd, $path;
@ -858,7 +862,10 @@ sub volume_import {
my $dh = IO::Dir->new($tmppath)
or die "failed to open temporary receive directory '$tmppath' - $!\n";
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;
my ($diskname, $found_snapshot, @snapshots);
@ -891,38 +898,39 @@ sub volume_import {
# Rotate the disk into place, first the current state:
# 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:
$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(
-1,
"$tmppath/$diskname\@$snapshot",
-1,
$destination,
&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";
# Now recreate the actual snapshot:
$class->btrfs_cmd([
'subvolume',
'snapshot',
'-r',
'--',
$destination,
"$destination\@$snapshot",
'subvolume', 'snapshot', '-r', '--', $destination, "$destination\@$snapshot",
]);
# Now go through the remaining snapshots (if any)
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(
-1,
"$tmppath/$diskname\@$snap",
-1,
"$destination\@$snap",
&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";
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 $@;
}
};
@ -938,10 +946,11 @@ sub volume_import {
eval { $class->btrfs_cmd(['subvolume', 'delete', '--', "$tmppath/$entry"]) };
warn $@ if $@;
}
$dh->close; undef $dh;
$dh->close;
undef $dh;
}
if (!rmdir($tmppath)) {
warn "failed to remove temporary directory '$tmppath' - $!\n"
warn "failed to remove temporary directory '$tmppath' - $!\n";
}
};
warn $@ if $@;
@ -961,7 +970,9 @@ sub rename_volume {
my $format = ($class->parse_volname($source_volname))[6];
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)
@ -978,8 +989,8 @@ sub rename_volume {
my $new_path = "${basedir}/${target_dir}";
die "target volume '${target_volname}' already exists\n" if -e $new_path;
rename $old_path, $new_path ||
die "rename '$old_path' to '$new_path' failed - $!\n";
rename $old_path, $new_path
|| die "rename '$old_path' to '$new_path' failed - $!\n";
return "${storeid}:$target_volname";
}

View File

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

View File

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

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
# 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',
enum => ['raw', 'qcow2', 'subvol', 'vmdk'],
description => "Format of the image.",
});
},
);
=pod

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -83,7 +83,15 @@ sub run_lun_command {
$target = 'root@' . $scfg->{portal};
my $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $luncmd, $lunmethod, @params];
my $cmd = [
@ssh_cmd,
'-i',
"$id_rsa_path/$scfg->{portal}_id_rsa",
$target,
$luncmd,
$lunmethod,
@params,
];
run_command($cmd, outfunc => $output, timeout => $timeout);

View File

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

View File

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

View File

@ -58,21 +58,27 @@ my $execute_remote_command = sub {
my $errfunc = sub { $err .= "$_[0]\n" };
$target = 'root@' . $scfg->{portal};
$cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, '--', $remote_command, @params];
$cmd = [
@ssh_cmd,
'-i',
"$id_rsa_path/$scfg->{portal}_id_rsa",
$target,
'--',
$remote_command,
@params,
];
eval {
run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout);
};
eval { run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout); };
if ($@) {
$res = {
result => 0,
msg => $err,
}
};
} else {
$res = {
result => 1,
msg => $msg,
}
};
}
return $res;
@ -96,7 +102,8 @@ my $read_config = sub {
$target = 'root@' . $scfg->{portal};
foreach my $oneFile (@CONFIG_FILES) {
my $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $luncmd, $oneFile];
my $cmd =
[@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target, $luncmd, $oneFile];
eval {
run_command($cmd, outfunc => $output, errfunc => $errfunc, timeout => $timeout);
};
@ -139,7 +146,8 @@ my $parser = sub {
if ($tpg =~ /^tpg(\d+)$/) {
$tpg_tag = $1;
} else {
die "Target Portal Group has invalid value, must contain string 'tpg' and a suffix number, eg 'tpg17'\n";
die
"Target Portal Group has invalid value, must contain string 'tpg' and a suffix number, eg 'tpg17'\n";
}
my $config = $get_config->($scfg);

View File

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

View File

@ -24,9 +24,9 @@ sub nfs_is_mounted {
$mountdata = PVE::ProcFSTools::parse_proc_mounts() if !$mountdata;
return $mountpoint if grep {
$_->[2] =~ /^nfs/ &&
$_->[0] =~ m|^\Q$source\E/?$| &&
$_->[1] eq $mountpoint
$_->[2] =~ /^nfs/
&& $_->[0] =~ m|^\Q$source\E/?$|
&& $_->[1] eq $mountpoint
} @$mountdata;
return undef;
}
@ -53,8 +53,18 @@ sub type {
sub plugindata {
return {
content => [ { images => 1, rootdir => 1, vztmpl => 1, iso => 1, backup => 1, snippets => 1, import => 1 },
{ images => 1 }],
content => [
{
images => 1,
rootdir => 1,
vztmpl => 1,
iso => 1,
backup => 1,
snippets => 1,
import => 1,
},
{ images => 1 },
],
format => [{ raw => 1, qcow2 => 1, vmdk => 1 }, 'raw'],
'sensitive-properties' => {},
};
@ -64,11 +74,13 @@ sub properties {
return {
export => {
description => "NFS export path.",
type => 'string', format => 'pve-storage-path',
type => 'string',
format => 'pve-storage-path',
},
server => {
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 {
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
$class->config_aware_base_mkdir($scfg, $path);
die "unable to activate storage '$storeid' - " .
"directory '$path' does not exist\n" if ! -d $path;
die "unable to activate storage '$storeid' - " . "directory '$path' does not exist\n"
if !-d $path;
nfs_mount($server, $export, $path, $scfg->{options});
}
@ -184,7 +195,9 @@ sub check_connection {
$cmd = ['/sbin/showmount', '--no-headers', '--exports', $server];
}
eval { run_command($cmd, timeout => 10, outfunc => sub {}, errfunc => sub {}) };
eval {
run_command($cmd, timeout => 10, outfunc => sub { }, errfunc => sub { });
};
if (my $err = $@) {
if ($is_v4) {
my $port = 2049;

View File

@ -47,11 +47,13 @@ sub properties {
# openssl s_client -connect <host>:8007 2>&1 |openssl x509 -fingerprint -sha256
fingerprint => get_standard_option('fingerprint-sha256'),
'encryption-key' => {
description => "Encryption key. Use 'autogen' to generate one automatically without passphrase.",
description =>
"Encryption key. Use 'autogen' to generate one automatically without passphrase.",
type => 'string',
},
'master-pubkey' => {
description => "Base64-encoded, PEM-formatted public RSA key. Used to encrypt a copy of the encryption-key which will be added to each encrypted backup.",
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',
},
};
@ -361,8 +363,11 @@ sub run_client_cmd {
$param = [@$param, '--output-format=json'] if !$no_output;
do_raw_client_cmd($scfg, $storeid, $client_cmd, $param,
outfunc => $outfunc, errmsg => 'proxmox-backup-client failed');
do_raw_client_cmd(
$scfg, $storeid, $client_cmd, $param,
outfunc => $outfunc,
errmsg => 'proxmox-backup-client failed',
);
return undef if $no_output;
@ -390,8 +395,11 @@ sub extract_vzdump_config {
die "unable to extract configuration for backup format '$format'\n";
}
do_raw_client_cmd($scfg, $storeid, 'restore', [ $name, $config_name, '-' ],
outfunc => $outfunc, errmsg => 'proxmox-backup-client failed');
do_raw_client_cmd(
$scfg, $storeid, 'restore', [$name, $config_name, '-'],
outfunc => $outfunc,
errmsg => 'proxmox-backup-client failed',
);
return $config;
}
@ -462,7 +470,8 @@ sub prune_backups {
my $mark = $backup->{keep} ? 'keep' : 'remove';
$mark = 'protected' if $backup->{protected};
push @{$prune_list}, {
push @{$prune_list},
{
ctime => $ctime,
mark => $mark,
type => $type eq 'vm' ? 'qemu' : 'lxc',
@ -596,7 +605,9 @@ sub on_delete_hook {
sub parse_volname {
my ($class, $volname) = @_;
if ($volname =~ m!^backup/([^\s_]+)/([^\s_]+)/([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z)$!) {
if ($volname =~
m!^backup/([^\s_]+)/([^\s_]+)/([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z)$!
) {
my $btype = $1;
my $bid = $2;
my $btime = $3;
@ -662,7 +673,6 @@ sub free_image {
return;
}
sub list_images {
my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_;

View File

@ -35,16 +35,7 @@ our @COMMON_TAR_FLAGS = qw(
);
our @SHARED_STORAGE = (
'iscsi',
'nfs',
'cifs',
'rbd',
'cephfs',
'iscsidirect',
'glusterfs',
'zfs',
'drbd',
'pbs',
'iscsi', 'nfs', 'cifs', 'rbd', 'cephfs', 'iscsidirect', 'glusterfs', 'zfs', 'drbd', 'pbs',
);
our $QCOW2_PREALLOCATION = {
@ -62,13 +53,16 @@ our $RAW_PREALLOCATION = {
our $MAX_VOLUMES_PER_GUEST = 1024;
cfs_register_file ('storage.cfg',
cfs_register_file(
'storage.cfg',
sub { __PACKAGE__->parse_config(@_); },
sub { __PACKAGE__->write_config(@_); });
sub { __PACKAGE__->write_config(@_); },
);
my %prune_option = (
optional => 1,
type => 'integer', minimum => '0',
type => 'integer',
minimum => '0',
format_description => 'N',
);
@ -79,36 +73,36 @@ our $prune_backups_format = {
optional => 1,
},
'keep-last' => {
%prune_option,
description => 'Keep the last <N> backups.',
%prune_option, description => 'Keep the last <N> backups.',
},
'keep-hourly' => {
%prune_option,
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.'
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.',
},
'keep-daily' => {
%prune_option,
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.'
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.',
},
'keep-weekly' => {
%prune_option,
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.'
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.',
},
'keep-monthly' => {
%prune_option,
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.'
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.',
},
'keep-yearly' => {
%prune_option,
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.'
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.',
},
};
PVE::JSONSchema::register_format('prune-backups', $prune_backups_format, \&validate_prune_backups);
sub validate_prune_backups {
my ($prune_backups) = @_;
@ -124,30 +118,39 @@ sub validate_prune_backups {
return $res;
}
register_standard_option('prune-backups', {
description => "The retention options with shorter intervals are processed first " .
"with --keep-last being the very first one. Each option covers a " .
"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.",
register_standard_option(
'prune-backups',
{
description => "The retention options with shorter intervals are processed first "
. "with --keep-last being the very first one. Each option covers a "
. "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,
type => 'string',
format => 'prune-backups',
});
},
);
my $defaultData = {
propertyList => {
type => { description => "Storage type." },
storage => get_standard_option('pve-storage-id',
{ completion => \&PVE::Storage::complete_storage }),
nodes => get_standard_option('pve-node-list', {
storage => get_standard_option(
'pve-storage-id',
{ completion => \&PVE::Storage::complete_storage },
),
nodes => get_standard_option(
'pve-node-list',
{
description => "List of nodes for which the storage configuration applies.",
optional => 1,
}),
},
),
content => {
description => "Allowed content types.\n\nNOTE: the value " .
"'rootdir' is used for Containers, and value 'images' for VMs.\n",
type => 'string', format => 'pve-storage-content-list',
description => "Allowed content types.\n\nNOTE: the value "
. "'rootdir' is used for Containers, and value 'images' for VMs.\n",
type => 'string',
format => 'pve-storage-content-list',
optional => 1,
completion => \&PVE::Storage::complete_content_type,
},
@ -157,22 +160,25 @@ my $defaultData = {
optional => 1,
},
maxfiles => {
description => "Deprecated: use 'prune-backups' instead. " .
"Maximal number of backup files per VM. Use '0' for unlimited.",
description => "Deprecated: use 'prune-backups' instead. "
. "Maximal number of backup files per VM. Use '0' for unlimited.",
type => 'integer',
minimum => 0,
optional => 1,
},
'prune-backups' => get_standard_option('prune-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',
minimum => -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 => {
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 "
. "local storage automatically accessible to other nodes, it just marks an already "
. "shared storage as such!",
@ -181,23 +187,29 @@ my $defaultData = {
},
subdir => {
description => "Subdir to mount.",
type => 'string', format => 'pve-storage-path',
type => 'string',
format => 'pve-storage-path',
optional => 1,
},
format => get_standard_option('pve-storage-image-format', {
format => get_standard_option(
'pve-storage-image-format',
{
description => "Default image format.",
optional => 1,
}),
},
),
preallocation => {
description => "Preallocation mode for raw and qcow2 images. " .
"Using 'metadata' on raw images results in preallocation=off.",
type => 'string', enum => ['off', 'metadata', 'falloc', 'full'],
description => "Preallocation mode for raw and qcow2 images. "
. "Using 'metadata' on raw images results in preallocation=off.",
type => 'string',
enum => ['off', 'metadata', 'falloc', 'full'],
default => 'metadata',
optional => 1,
},
'content-dirs' => {
description => "Overrides for default content type directories.",
type => "string", format => "pve-dir-override-list",
type => "string",
format => "pve-dir-override-list",
optional => 1,
},
options => {
@ -207,7 +219,8 @@ my $defaultData = {
optional => 1,
},
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"
. " configure the port via the mount options.",
type => 'integer',
@ -285,6 +298,7 @@ sub default_format {
}
PVE::JSONSchema::register_format('pve-storage-path', \&verify_path);
sub verify_path {
my ($path, $noerr) = @_;
@ -298,12 +312,14 @@ sub verify_path {
}
PVE::JSONSchema::register_format('pve-storage-server', \&verify_server);
sub verify_server {
my ($server, $noerr) = @_;
if (!(PVE::JSONSchema::pve_verify_ip($server, 1) ||
PVE::JSONSchema::pve_verify_dns_name($server, 1)))
{
if (!(
PVE::JSONSchema::pve_verify_ip($server, 1)
|| PVE::JSONSchema::pve_verify_dns_name($server, 1)
)) {
return undef if $noerr;
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);
sub parse_lvm_name {
my ($name, $noerr) = @_;
@ -336,6 +353,7 @@ sub parse_lvm_name {
#}
PVE::JSONSchema::register_format('pve-storage-portal-dns', \&verify_portal_dns);
sub verify_portal_dns {
my ($portal, $noerr) = @_;
@ -348,6 +366,7 @@ sub verify_portal_dns {
}
PVE::JSONSchema::register_format('pve-storage-content', \&verify_content);
sub verify_content {
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
# format.
PVE::JSONSchema::register_format('pve-storage-format', \&verify_format);
sub verify_format {
my ($fmt, $noerr) = @_;
@ -380,6 +400,7 @@ sub verify_format {
}
PVE::JSONSchema::register_format('pve-storage-options', \&verify_options);
sub verify_options {
my ($value, $noerr) = @_;
@ -393,6 +414,7 @@ sub verify_options {
}
PVE::JSONSchema::register_format('pve-volume-id', \&parse_volume_id);
sub parse_volume_id {
my ($volid, $noerr) = @_;
@ -404,6 +426,7 @@ sub parse_volume_id {
}
PVE::JSONSchema::register_format('pve-dir-override', \&verify_dir_override);
sub verify_dir_override {
my ($value, $noerr) = @_;
@ -411,7 +434,10 @@ sub verify_dir_override {
my ($content_type, $relative_path) = ($1, $2);
if (verify_content($content_type, $noerr)) {
# 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;
}
}
@ -536,8 +562,11 @@ sub parse_config {
# make sure we have a reasonable 'local:' storage
# we want 'local' to be always the same 'type' (on all cluster nodes)
if (!$ids->{local} || $ids->{local}->{type} ne 'dir' ||
($ids->{local}->{path} && $ids->{local}->{path} ne '/var/lib/vz')) {
if (
!$ids->{local}
|| $ids->{local}->{type} ne 'dir'
|| ($ids->{local}->{path} && $ids->{local}->{path} ne '/var/lib/vz')
) {
$ids->{local} = {
type => 'dir',
priority => 0, # force first entry
@ -690,11 +719,15 @@ sub parse_volname {
return ('backup', $fn, undef, undef, undef, undef, 'raw');
} elsif ($volname =~ m!^snippets/([^/]+)$!) {
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 $format = $2;
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);
}
@ -731,8 +764,7 @@ sub get_subdir {
sub filesystem_path {
my ($class, $scfg, $volname, $snapname) = @_;
my ($vtype, $name, $vmid, undef, undef, $isBase, $format) =
$class->parse_volname($volname);
my ($vtype, $name, $vmid, undef, undef, $isBase, $format) = $class->parse_volname($volname);
# Note: qcow2/qed has internal snapshot, so path is always
# the same (with or without snapshot => same file).
@ -778,15 +810,17 @@ sub create_base {
my $newname = $name;
$newname =~ s/^vm-/base-/;
my $newvolname = $basename ? "$basevmid/$basename/$vmid/$newname" :
"$vmid/$newname";
my $newvolname =
$basename
? "$basevmid/$basename/$vmid/$newname"
: "$vmid/$newname";
my $newpath = $class->filesystem_path($scfg, $newvolname);
die "file '$newpath' already exists\n" if -f $newpath;
rename($path, $newpath) ||
die "rename '$path' to '$newpath' failed - $!\n";
rename($path, $newpath)
|| die "rename '$path' to '$newpath' failed - $!\n";
# We try to protect base volume
@ -807,7 +841,7 @@ my $get_vm_disk_number = sub {
my $type = $scfg->{type};
my $def = { %{ $defaultData->{plugindata}->{$type} } };
my $valid = $def->{format}[0];
my $valid = $def->{format}->[0];
if ($valid->{subvol}) {
$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 {
@ -885,8 +919,17 @@ sub clone_image {
eval {
local $CWD = $imagedir;
my $cmd = ['/usr/bin/qemu-img', 'create', '-b', "../$basevmid/$basename",
'-F', $format, '-f', 'qcow2', $path];
my $cmd = [
'/usr/bin/qemu-img',
'create',
'-b',
"../$basevmid/$basename",
'-F',
$format,
'-f',
'qcow2',
$path,
];
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
# evaluating breakage potential.
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';
}
my $cmd = ['/usr/bin/qemu-img', 'info', '--output=json', $filename];
@ -1049,7 +1093,8 @@ sub file_size_info {
my $json = '';
my $err_output = '';
eval {
run_command($cmd,
run_command(
$cmd,
timeout => $timeout,
outfunc => sub { $json .= shift },
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;
@ -1241,6 +1287,7 @@ sub volume_snapshot_needs_fsfreeze {
return 0;
}
sub storage_can_replicate {
my ($class, $scfg, $storeid, $format) = @_;
@ -1286,8 +1333,8 @@ sub volume_has_feature {
return 0 if $class->can('api') && $class->api() < 10;
}
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;
if ($snapname) {
@ -1322,9 +1369,7 @@ sub list_images {
next if !$vollist && defined($vmid) && ($owner ne $vmid);
my ($size, undef, $used, $parent, $ctime) = eval {
file_size_info($fn, undef, $format);
};
my ($size, undef, $used, $parent, $ctime) = eval { file_size_info($fn, undef, $format); };
if (my $err = $@) {
die $err if $err !~ m/Image is not in \S+ format$/;
warn "image '$fn' is not in expected format '$format', querying as raw\n";
@ -1347,8 +1392,12 @@ sub list_images {
}
my $info = {
volid => $volid, format => $format,
size => $size, vmid => $owner, used => $used, parent => $parent
volid => $volid,
format => $format,
size => $size,
vmid => $owner,
used => $used,
parent => $parent,
};
$info->{ctime} = $ctime if $ctime;
@ -1405,7 +1454,8 @@ my $get_subdir_files = sub {
my $notes_fn = $original . NOTES_EXT;
if (-f $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);
@ -1416,7 +1466,9 @@ my $get_subdir_files = sub {
format => 'snippet',
};
} 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" };
}
@ -1519,8 +1571,8 @@ sub activate_storage {
# this path test may hang indefinitely on unresponsive mounts
my $timeout = 2;
if (!PVE::Tools::run_fork_with_timeout($timeout, sub { -d $path })) {
die "unable to activate storage '$storeid' - " .
"directory '$path' does not exist or is unreachable\n";
die "unable to activate storage '$storeid' - "
. "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
@ -1720,7 +1772,8 @@ sub read_common_header($) {
# Export a volume into a file handle as a stream of desired format.
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";
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';
write_common_header($fh, $size);
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 {
run_command(['qemu-img', 'convert', '-f', $file_format, '-O', 'raw', $file, '/dev/stdout'],
output => '>&'.fileno($fh));
run_command(
[
'qemu-img',
'convert',
'-f',
$file_format,
'-O',
'raw',
$file,
'/dev/stdout',
],
output => '>&' . fileno($fh),
);
}
return;
} elsif ($format =~ /^(qcow2|vmdk)\+size$/) {
my $data_format = $1;
die $err_msg if !$with_snapshots || $file_format ne $data_format;
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;
} elsif ($format eq 'tar+size') {
die $err_msg if $file_format ne 'subvol';
write_common_header($fh, $size);
run_command(['tar', @COMMON_TAR_FLAGS, '-cf', '-', '-C', $file, '.'],
output => '>&'.fileno($fh));
run_command(
['tar', @COMMON_TAR_FLAGS, '-cf', '-', '-C', $file, '.'],
output => '>&' . fileno($fh),
);
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.
sub volume_import {
my ($class, $scfg, $storeid, $fh, $volname, $format, $snapshot, $base_snapshot, $with_snapshots, $allow_rename) = @_;
my (
$class,
$scfg,
$storeid,
$fh,
$volname,
$format,
$snapshot,
$base_snapshot,
$with_snapshots,
$allow_rename,
) = @_;
die "volume import format '$format' not available for $class\n"
if $format !~ /^(raw|tar|qcow2|vmdk)\+size$/;
@ -1813,11 +1896,15 @@ sub volume_import {
my ($file) = $class->path($scfg, $volname, $storeid)
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') {
run_command(['dd', "of=$file", 'conv=sparse', 'bs=64k'],
input => '<&'.fileno($fh));
run_command(
['dd', "of=$file", 'conv=sparse', 'bs=64k'],
input => '<&' . fileno($fh),
);
} elsif ($data_format eq 'tar') {
run_command(['tar', @COMMON_TAR_FLAGS, '-C', $file, '-xf', '-'],
input => '<&'.fileno($fh));
run_command(
['tar', @COMMON_TAR_FLAGS, '-C', $file, '-xf', '-'],
input => '<&' . fileno($fh),
);
} else {
die "volume import format '$format' not available for $class";
}
@ -1851,13 +1938,7 @@ sub rename_volume {
die "no path found\n" if !$scfg->{path};
my (
undef,
$source_image,
$source_vmid,
$base_name,
$base_vmid,
undef,
$format
undef, $source_image, $source_vmid, $base_name, $base_vmid, undef, $format,
) = $class->parse_volname($source_volname);
$target_volname = $class->find_free_diskname($storeid, $scfg, $target_vmid, $format, 1)
@ -1874,8 +1955,8 @@ sub rename_volume {
my $base = $base_name ? "${base_vmid}/${base_name}/" : '';
rename($old_path, $new_path) ||
die "rename '$old_path' to '$new_path' failed - $!\n";
rename($old_path, $new_path)
|| die "rename '$old_path' to '$new_path' failed - $!\n";
return "${storeid}:${base}${target_vmid}/${target_volname}";
}

View File

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

View File

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

View File

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

View File

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

View File

@ -132,9 +132,9 @@ my $decompressor = {
};
my $bkp_suffix = {
qemu => [ 'vma', $decompressor->{vma}, ],
lxc => [ 'tar', $decompressor->{tar}, ],
openvz => [ 'tar', $decompressor->{tar}, ],
qemu => ['vma', $decompressor->{vma}],
lxc => ['tar', $decompressor->{tar}],
openvz => ['tar', $decompressor->{tar}],
};
# 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";
for my $suffix (sort keys %$decomp) {
push @$tests, {
push @$tests,
{
description => "Backup archive, $virt, $format.$suffix",
archive => "backup/$archive_name.$format.$suffix",
expected => {
@ -162,13 +163,12 @@ for my $virt (sort keys %$bkp_suffix) {
}
}
# add compression formats to test failed matches
my $non_bkp_suffix = {
'openvz' => [ 'zip', 'tgz.lzo', 'zip.gz', '', ],
'lxc' => [ 'zip', 'tgz.lzo', 'zip.gz', '', ],
'qemu' => [ 'vma.xz', 'vms.gz', 'vmx.zst', '', ],
'none' => [ 'tar.gz', ],
'openvz' => ['zip', 'tgz.lzo', 'zip.gz', ''],
'lxc' => ['zip', 'tgz.lzo', 'zip.gz', ''],
'qemu' => ['vma.xz', 'vms.gz', 'vmx.zst', ''],
'none' => ['tar.gz'],
};
# create tests for failed matches
@ -176,7 +176,8 @@ for my $virt (sort keys %$non_bkp_suffix) {
my $suffix = $non_bkp_suffix->{$virt};
for my $s (@$suffix) {
my $archive = "backup/vzdump-$virt-$vmid-2020_03_30-21_12_40.$s";
push @$tests, {
push @$tests,
{
description => "Failed match: Backup archive, $virt, $s",
archive => $archive,
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;
for my $tt (@$tests) {

View File

@ -152,7 +152,6 @@ sub read_test_file {
return $output;
}
sub test_disk_list {
my ($testdir) = @_;
subtest "Test '$testdir'" => sub {
@ -161,9 +160,7 @@ sub test_disk_list {
my $disks;
my $expected_disk_list;
eval {
$disks = PVE::Diskmanage::get_disks();
};
eval { $disks = PVE::Diskmanage::get_disks(); };
warn $@ if $@;
$expected_disk_list = decode_json(read_test_file('disklist_expected.json'));
@ -194,16 +191,21 @@ sub test_disk_list {
warn $@ if $@;
$testcount++;
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
eval {
PVE::Diskmanage::get_disks( { test => 1 } );
};
eval { PVE::Diskmanage::get_disks({ test => 1 }); };
my $err = $@;
$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
@ -235,14 +237,16 @@ $diskmanage_module->mock('is_iscsi' => \&mocked_is_iscsi);
print("\tMocked is_iscsi\n");
$diskmanage_module->mock('assert_blockdev' => sub { return 1; });
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
my $val = shift;
if ($val =~ m|^/sys/block/.+/.+/|) {
return 0;
}
return 1;
});
},
);
print("\tMocked dir_is_empty\n");
$diskmanage_module->mock('check_bin' => sub { return 1; });
print("\tMocked check_bin\n");

View File

@ -19,50 +19,40 @@ my $tests = [
volname => '1234/vm-1234-disk-0.raw',
snapname => undef,
expected => [
"$path/images/1234/vm-1234-disk-0.raw",
'1234',
'images'
"$path/images/1234/vm-1234-disk-0.raw", '1234', 'images',
],
},
{
volname => '1234/vm-1234-disk-0.raw',
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',
snapname => undef,
expected => [
"$path/images/1234/vm-1234-disk-0.qcow2",
'1234',
'images'
"$path/images/1234/vm-1234-disk-0.qcow2", '1234', 'images',
],
},
{
volname => '1234/vm-1234-disk-0.qcow2',
snapname => 'my_snap',
expected => [
"$path/images/1234/vm-1234-disk-0.qcow2",
'1234',
'images'
"$path/images/1234/vm-1234-disk-0.qcow2", '1234', 'images',
],
},
{
volname => 'iso/my-awesome-proxmox.iso',
snapname => undef,
expected => [
"$path/template/iso/my-awesome-proxmox.iso",
undef,
'iso'
"$path/template/iso/my-awesome-proxmox.iso", undef, 'iso',
],
},
{
volname => "backup/vzdump-qemu-1234-2020_03_30-21_12_40.vma",
snapname => undef,
expected => [
"$path/dump/vzdump-qemu-1234-2020_03_30-21_12_40.vma",
1234,
'backup'
"$path/dump/vzdump-qemu-1234-2020_03_30-21_12_40.vma", 1234, 'backup',
],
},
];
@ -76,9 +66,7 @@ foreach my $tt (@$tests) {
my $scfg = { path => $path };
my $got;
eval {
$got = [ PVE::Storage::Plugin->filesystem_path($scfg, $volname, $snapname) ];
};
eval { $got = [PVE::Storage::Plugin->filesystem_path($scfg, $volname, $snapname)]; };
$got = $@ if $@;
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) {
my $override = "${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;

View File

@ -56,8 +56,8 @@ my $mocked_vmlist = {
'node' => 'x42',
'type' => 'qemu',
'version' => 6,
}
}
},
},
};
my $storage_dir = File::Temp->newdir();
@ -257,8 +257,7 @@ my @tests = (
"$storage_dir/images/16114/vm-16114-disk-1.qcow2",
],
parent => [
"../9004/base-9004-disk-0.qcow2",
"../9004/base-9004-disk-1.qcow2",
"../9004/base-9004-disk-0.qcow2", "../9004/base-9004-disk-1.qcow2",
],
expected => [
{
@ -444,7 +443,7 @@ my @tests = (
'used' => DEFAULT_USED,
'vmid' => '1234',
'volid' => 'local:1234/vm-1234-disk-0.qcow2',
}
},
],
},
{
@ -466,7 +465,6 @@ my @tests = (
},
);
# provide static vmlist for tests
my $mock_cluster = Test::MockModule->new('PVE::Cluster', no_auto => 1);
$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
# an blessed array.
my $mock_stat = Test::MockModule->new('File::stat', no_auto => 1);
$mock_stat->redefine(populate => sub {
$mock_stat->redefine(
populate => sub {
my (@st) = @_;
$st[7] = DEFAULT_SIZE;
$st[10] = DEFAULT_CTIME;
@ -482,18 +481,22 @@ $mock_stat->redefine(populate => sub {
my $result = $mock_stat->original('populate')->(@st);
return $result;
});
},
);
# override info provided by qemu-img in file_size_info
my $mock_fsi = Test::MockModule->new('PVE::Storage::Plugin', no_auto => 1);
$mock_fsi->redefine(file_size_info => sub {
my ($size, $format, $used, $parent, $ctime) = $mock_fsi->original('file_size_info')->(@_);
$mock_fsi->redefine(
file_size_info => sub {
my ($size, $format, $used, $parent, $ctime) =
$mock_fsi->original('file_size_info')->(@_);
$size = DEFAULT_SIZE;
$used = DEFAULT_USED;
return wantarray ? ($size, $format, $used, $parent, $ctime) : $size;
});
},
);
my $plan = scalar @tests;
plan tests => $plan + 1;
@ -507,13 +510,15 @@ plan tests => $plan + 1;
PVE::Storage::Plugin->list_volumes('sid', $scfg_with_type, undef, ['images']);
is_deeply ($tested_vmlist, $original_vmlist,
'PVE::Cluster::vmlist remains unmodified')
|| diag ("Expected vmlist to remain\n", explain($original_vmlist),
"but it turned to\n", explain($tested_vmlist));
is_deeply($tested_vmlist, $original_vmlist, 'PVE::Cluster::vmlist remains unmodified')
|| diag(
"Expected vmlist to remain\n",
explain($original_vmlist),
"but it turned to\n",
explain($tested_vmlist),
);
}
{
my $sid = 'local';
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-',
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
@ -34,7 +42,8 @@ my $tests = [
{
description => 'ISO image, 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
@ -42,17 +51,41 @@ my $tests = [
{
description => 'Container template 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',
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',
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
@ -65,12 +98,21 @@ my $tests = [
{
description => 'Container rootdir, 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',
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
@ -101,7 +143,8 @@ my $tests = [
{
description => "Import, innner file of ova",
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",
@ -111,7 +154,8 @@ my $tests = [
{
description => "Import, innner file of ova with whitespace in name",
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",
@ -129,12 +173,14 @@ my $tests = [
{
description => 'Failed match: ISO image, 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',
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',
@ -149,12 +195,14 @@ my $tests = [
{
description => 'Failed match: VM disk image, linked, qcow2, first vmid',
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',
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",
@ -171,13 +219,7 @@ foreach my $s (@$disk_suffix) {
description => "VM disk image, $s",
volname => "$vmid/vm-$vmid-disk-1.$s",
expected => [
'images',
"vm-$vmid-disk-1.$s",
"$vmid",
undef,
undef,
undef,
"$s",
'images', "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",
volname => "$vmid/base-$vmid-disk-0.$s",
expected => [
'images',
"base-$vmid-disk-0.$s",
"$vmid",
undef,
undef,
'base-',
"$s"
'images', "base-$vmid-disk-0.$s", "$vmid", undef, undef, 'base-', "$s",
],
},
);
@ -211,7 +247,6 @@ foreach my $s (@$disk_suffix) {
push @$tests, @arr;
}
# create more test cases for backup files matches
my $bkp_suffix = {
qemu => ['vma', 'vma.gz', 'vma.lzo', 'vma.zst'],
@ -233,7 +268,7 @@ foreach my $virt (keys %$bkp_suffix) {
undef,
undef,
undef,
'raw'
'raw',
],
},
);
@ -242,7 +277,6 @@ foreach my $virt (keys %$bkp_suffix) {
}
}
# create more test cases for failed backup files matches
my $non_bkp_suffix = {
qemu => ['vms.gz', 'vma.xz'],
@ -255,7 +289,8 @@ foreach my $virt (keys %$non_bkp_suffix) {
{
description => "Failed match: Backup archive, $virt, $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
#

View File

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

View File

@ -18,7 +18,8 @@ my $mocked_backups_lists = {};
my $basetime = 1577881101; # 2020_01_01-12_18_21 UTC
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",
'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",
'ctime' => 83,
@ -74,13 +76,15 @@ push @{$mocked_backups_lists->{year1970}}, (
'vmid' => 321,
},
);
push @{$mocked_backups_lists->{novmid}}, (
push @{ $mocked_backups_lists->{novmid} },
(
{
'volid' => "$storeid:backup/vzdump-lxc-novmid.tar.gz",
'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",
'ctime' => $basetime - 7 * 24 * 60 * 60,
@ -97,7 +101,8 @@ push @{$mocked_backups_lists->{threeway}}, (
'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",
'ctime' => $basetime + (366 - 31 + 2) * 24 * 60 * 60,
@ -116,7 +121,8 @@ push @{$mocked_backups_lists->{weekboundary}}, (
);
my $current_list;
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 $list = $mocked_backups_lists->{$current_list};
@ -124,14 +130,16 @@ $mock_plugin->redefine(list_volumes => sub {
return $list if !defined($vmid);
return [grep { $_->{vmid} eq $vmid } @{$list}];
});
},
);
sub generate_expected {
my ($vmids, $type, $marks) = @_;
my @expected;
foreach my $vmid (@{$vmids}) {
push @expected, (
push @expected,
(
{
'volid' => "$storeid:backup/vzdump-qemu-$vmid-2018_05_26-11_18_21.tar.zst",
'type' => 'qemu',
@ -175,7 +183,8 @@ sub generate_expected {
'vmid' => $vmid,
},
) if !defined($type) || $type eq 'qemu';
push @expected, (
push @expected,
(
{
'volid' => "$storeid:backup/vzdump-lxc-$vmid-2020_01_01-12_18_21.tar.zst",
'type' => 'lxc',
@ -184,7 +193,8 @@ sub generate_expected {
'vmid' => $vmid,
},
) if !defined($type) || $type eq 'lxc';
push @expected, (
push @expected,
(
{
'volid' => "$storeid:backup/vzdump-$vmid-renamed.tar.zst",
'type' => 'unknown',
@ -212,7 +222,8 @@ my $tests = [
keep => {
'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',
@ -220,7 +231,11 @@ my $tests = [
keep => {
'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',
@ -230,7 +245,8 @@ my $tests = [
'keep-weekly' => 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',
@ -239,7 +255,11 @@ my $tests = [
'keep-hourly' => 4,
'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',
@ -250,7 +270,11 @@ my $tests = [
'keep-monthly' => 0,
'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',
@ -259,7 +283,11 @@ my $tests = [
'keep-last' => 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',
@ -267,7 +295,8 @@ my $tests = [
'keep-last' => 1,
'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',
@ -275,7 +304,11 @@ my $tests = [
keep => {
'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',
@ -284,7 +317,8 @@ my $tests = [
'keep-daily' => 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',
@ -296,7 +330,8 @@ my $tests = [
'keep-monthly' => 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',
@ -304,7 +339,11 @@ my $tests = [
keep => {
'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',
@ -312,7 +351,11 @@ my $tests = [
'keep-weekly' => 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',
@ -321,7 +364,11 @@ my $tests = [
'keep-weekly' => 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',
@ -331,7 +378,11 @@ my $tests = [
'keep-weekly' => 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',
@ -383,7 +434,8 @@ my $tests = [
{
description => 'all missing, multiple IDs',
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',
@ -395,7 +447,8 @@ my $tests = [
'keep-monthyl' => 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',
@ -406,7 +459,8 @@ my $tests = [
'keep-monthyl' => 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',
@ -479,7 +533,9 @@ for my $tt (@$tests) {
my $got = eval {
$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}];
};
$got = $@ if $@;

View File

@ -69,6 +69,7 @@ my $vmid_linked_clone = int($vmid) - 2;
sub jp {
print to_json($_[0], { utf8 => 8, pretty => 1, canonical => 1 }) . "\n";
}
sub dbgvar {
jp(@_) if $DEBUG;
}
@ -79,9 +80,7 @@ sub run_cmd {
my $raw = '';
my $parser = sub { $raw .= shift; };
eval {
run_command($cmd, outfunc => $parser);
};
eval { run_command($cmd, outfunc => $parser); };
if (my $err = $@) {
die $err if !$ignore_errors;
}
@ -109,9 +108,7 @@ sub run_test_cmd {
$raw .= "${line}\n";
};
eval {
run_command($cmd, outfunc => $out);
};
eval { run_command($cmd, outfunc => $out); };
if (my $err = $@) {
print $raw;
print $err;
@ -167,13 +164,28 @@ sub prepare {
run_cmd(['pvesm', 'add', 'rbd', $pool, '--pool', $pool, '--content', 'images,rootdir']);
}
# create PVE storages (librbd / krbd)
run_cmd(['pvesm', 'add', 'rbd', ${storage_name}, '--krbd', '0', '--pool', ${pool}, '--namespace', ${namespace}, '--content', 'images,rootdir'])
if !$rbd_found;
run_cmd(
[
'pvesm',
'add',
'rbd',
${storage_name},
'--krbd',
'0',
'--pool',
${pool},
'--namespace',
${namespace},
'--content',
'images,rootdir',
],
) if !$rbd_found;
# create test VM
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) {
# TODO: introduce a force flag to make this behaviour configurable
@ -183,10 +195,21 @@ sub prepare {
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 {
print "Cleaning up test environment!\n";
print "Removing VMs\n";
@ -195,7 +218,21 @@ sub cleanup {
run_cmd(['qm', 'stop', ${vmid_clone}], 0, 1);
run_cmd(['qm', 'destroy', ${vmid_linked_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', 'destroy', ${vmid}], 0, 1);
@ -237,8 +274,7 @@ my $tests = [
{
name => 'snapshot/rollback',
steps => [
['qm', 'snapshot', $vmid, 'test'],
['qm', 'rollback', $vmid, 'test'],
['qm', 'snapshot', $vmid, 'test'], ['qm', 'rollback', $vmid, 'test'],
],
cleanup => [
['qm', 'unlock', $vmid],
@ -260,8 +296,7 @@ my $tests = [
{
name => 'switch to krbd',
preparations => [
['qm', 'stop', $vmid],
['pvesm', 'set', $storage_name, '--krbd', 1]
['qm', 'stop', $vmid], ['pvesm', 'set', $storage_name, '--krbd', 1],
],
},
{
@ -273,8 +308,7 @@ my $tests = [
{
name => 'snapshot/rollback with krbd',
steps => [
['qm', 'snapshot', $vmid, 'test'],
['qm', 'rollback', $vmid, 'test'],
['qm', 'snapshot', $vmid, 'test'], ['qm', 'rollback', $vmid, 'test'],
],
cleanup => [
['qm', 'unlock', $vmid],
@ -304,7 +338,7 @@ my $tests = [
preparations => [
['qm', 'stop', $vmid],
['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',
preparations => [
['pvesm', 'set', $storage_name, '--krbd', 1]
],
preparations => [['pvesm', 'set', $storage_name, '--krbd', 1]],
steps => [
['qm', 'start', $vmid_linked_clone],
['qm', 'stop', $vmid_linked_clone],
['qm', 'start', $vmid_linked_clone], ['qm', 'stop', $vmid_linked_clone],
],
},
];

View File

@ -51,9 +51,9 @@ EOF
my $permissions = {
'user1@test' => {},
'user2@test' => { '/' => ['Sys.Modify'], },
'user3@test' => { '/storage' => ['Datastore.Allocate'], },
'user4@test' => { '/storage/d20m40r30' => ['Datastore.Allocate'], },
'user2@test' => { '/' => ['Sys.Modify'] },
'user3@test' => { '/storage' => ['Datastore.Allocate'] },
'user4@test' => { '/storage/d20m40r30' => ['Datastore.Allocate'] },
};
my $pve_cluster_module;
@ -96,8 +96,16 @@ my $rpcenv = PVE::RPCEnvironment->init('pub');
my @tests = (
[user => 'root@pam'],
[['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'],
[['move', ['d50m40r30'], undef], 40, 'root / specific storage limit (move)'],
[['restore', ['d50m40r30'], undef], 30, 'root / specific storage limit (restore)'],
@ -110,7 +118,11 @@ my @tests = (
[['migrate', undef, 100], 100, 'root / undef storage (migrate)'],
[['migrate', [], 100], 100, 'root / no storage (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'],
[['unknown', ['nolimit'], undef], 100, 'generic default limit'],
@ -119,78 +131,290 @@ my @tests = (
[['unknown', ['d50m40r30'], undef], 50, 'storage default limit'],
[['move', ['d50m40r30'], undef], 40, 'specific storage limit (move)'],
[['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)' ],
[ ['restore', ['d200m400r300'], undef], 300, 'specific storage limit above datacenter limits (restore)' ],
[
['unknown', ['d200m400r300'], undef],
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'],
[['move', ['d50'], undef], 50, 'storage default limit (move)'],
[['restore', ['d50'], undef], 50, 'storage default limit (restore)'],
[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'],
[['move', ['nolimit'], undef], 80, 'specific default limit with Sys.Modify (move)'],
[['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'],
[['restore', ['d50m40r30'], undef], 30, 'specific storage limit with Sys.Modify (restore)'],
[['move', ['d50m40r30'], undef], 40, 'specific storage limit with Sys.Modify (move)'],
[user => 'user3@test'],
[['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'], 0], 0, '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' ],
[
['move', ['nolimit'], 0],
0,
'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'], 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'], 0], 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)' ],
[
['move', ['d50m40r30'], 0],
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'],
[ ['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'], 0], 100, 'generic default limit with privileges on a different storage, passing unlimited' ],
[ ['move', ['nolimit'], undef], 80, 'specific default limit with privileges on a different storage (move)' ],
[ ['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)' ],
[ ['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)' ],
[
['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'], 0],
100,
'generic default limit with privileges on a different storage, passing unlimited',
],
[
['move', ['nolimit'], undef],
80,
'specific default limit with privileges on a different storage (move)',
],
[
['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)',
],
[
['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], undef], 80, '[undef] storage, no override (move)'],

View File

@ -14,7 +14,8 @@ my $test_manifests = join ('/', $Bin, 'ovf_manifests');
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 = $@) {
fail('parse win2008');
warn("error: $err\n");
@ -28,7 +29,8 @@ if (my $err = $@) {
} else {
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 = $@) {
fail("parse win10 no default rasd NS");
warn("error: $err\n");
@ -38,26 +40,59 @@ if (my $err = $@) {
print "testing disks\n";
is($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]->{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}->[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]->{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($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($win10noNs->{disks}->[0]->{disk_address}, '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');
is(
$win10noNs->{disks}->[0]->{disk_address},
'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";
is($win2008->{net}->{net0}->{model}, 'e1000', 'win2008 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";

File diff suppressed because it is too large Load Diff