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:
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) = @_;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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 = {};
|
||||
|
||||
@ -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});
|
||||
|
||||
@ -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";
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) = @_;
|
||||
|
||||
|
||||
@ -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}";
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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});
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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'];
|
||||
|
||||
@ -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
|
||||
#
|
||||
|
||||
@ -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',
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
@ -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 $@;
|
||||
|
||||
@ -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],
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@ -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)'],
|
||||
|
||||
@ -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
Reference in New Issue
Block a user