auto-format code using perltidy with Proxmox style guide

using the new top-level `make tidy` target, which calls perltidy via
our wrapper to enforce the desired style as closely as possible.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
Thomas Lamprecht
2025-06-11 10:03:21 +02:00
parent 5d23073cb6
commit 5a66c27cc6
54 changed files with 14137 additions and 12461 deletions

View File

@ -19,27 +19,27 @@ use PVE::API2::Disks::ZFS;
use PVE::RESTHandler;
use base qw(PVE::RESTHandler);
__PACKAGE__->register_method ({
subclass => "PVE::API2::Disks::LVM",
path => 'lvm',
__PACKAGE__->register_method({
subclass => "PVE::API2::Disks::LVM",
path => 'lvm',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::Disks::LVMThin",
path => 'lvmthin',
__PACKAGE__->register_method({
subclass => "PVE::API2::Disks::LVMThin",
path => 'lvmthin',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::Disks::Directory",
path => 'directory',
__PACKAGE__->register_method({
subclass => "PVE::API2::Disks::Directory",
path => 'directory',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::Disks::ZFS",
path => 'zfs',
__PACKAGE__->register_method({
subclass => "PVE::API2::Disks::ZFS",
path => 'zfs',
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'index',
path => '',
method => 'GET',
@ -47,37 +47,38 @@ __PACKAGE__->register_method ({
permissions => { user => 'all' },
description => "Node index.",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
},
returns => {
type => 'array',
items => {
type => "object",
properties => {},
},
links => [ { rel => 'child', href => "{name}" } ],
type => 'array',
items => {
type => "object",
properties => {},
},
links => [{ rel => 'child', href => "{name}" }],
},
code => sub {
my ($param) = @_;
my ($param) = @_;
my $result = [
{ name => 'list' },
{ name => 'initgpt' },
{ name => 'smart' },
{ name => 'lvm' },
{ name => 'lvmthin' },
{ name => 'directory' },
{ name => 'wipedisk' },
{ name => 'zfs' },
];
my $result = [
{ name => 'list' },
{ name => 'initgpt' },
{ name => 'smart' },
{ name => 'lvm' },
{ name => 'lvmthin' },
{ name => 'directory' },
{ name => 'wipedisk' },
{ name => 'zfs' },
];
return $result;
}});
return $result;
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'list',
path => 'list',
method => 'GET',
@ -85,98 +86,97 @@ __PACKAGE__->register_method ({
protected => 1,
proxyto => 'node',
permissions => {
check => ['or', ['perm', '/', ['Sys.Audit']], ['perm', '/nodes/{node}', ['Sys.Audit']]],
check => ['or', ['perm', '/', ['Sys.Audit']], ['perm', '/nodes/{node}', ['Sys.Audit']]],
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
'include-partitions' => {
description => "Also include partitions.",
type => 'boolean',
optional => 1,
default => 0,
},
skipsmart => {
description => "Skip smart checks.",
type => 'boolean',
optional => 1,
default => 0,
},
type => {
description => "Only list specific types of disks.",
type => 'string',
enum => ['unused', 'journal_disks'],
optional => 1,
},
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
'include-partitions' => {
description => "Also include partitions.",
type => 'boolean',
optional => 1,
default => 0,
},
skipsmart => {
description => "Skip smart checks.",
type => 'boolean',
optional => 1,
default => 0,
},
type => {
description => "Only list specific types of disks.",
type => 'string',
enum => ['unused', 'journal_disks'],
optional => 1,
},
},
},
returns => {
type => 'array',
items => {
type => 'object',
properties => {
devpath => {
type => 'string',
description => 'The device path',
},
used => { type => 'string', optional => 1 },
gpt => { type => 'boolean' },
mounted => { type => 'boolean' },
size => { type => 'integer'},
osdid => { type => 'integer'}, # TODO: deprecate / remove in PVE 9?
'osdid-list' => {
type => 'array',
items => { type => 'integer' },
},
vendor => { type => 'string', optional => 1 },
model => { type => 'string', optional => 1 },
serial => { type => 'string', optional => 1 },
wwn => { type => 'string', optional => 1},
health => { type => 'string', optional => 1},
parent => {
type => 'string',
description => 'For partitions only. The device path of ' .
'the disk the partition resides on.',
optional => 1
},
},
},
type => 'array',
items => {
type => 'object',
properties => {
devpath => {
type => 'string',
description => 'The device path',
},
used => { type => 'string', optional => 1 },
gpt => { type => 'boolean' },
mounted => { type => 'boolean' },
size => { type => 'integer' },
osdid => { type => 'integer' }, # TODO: deprecate / remove in PVE 9?
'osdid-list' => {
type => 'array',
items => { type => 'integer' },
},
vendor => { type => 'string', optional => 1 },
model => { type => 'string', optional => 1 },
serial => { type => 'string', optional => 1 },
wwn => { type => 'string', optional => 1 },
health => { type => 'string', optional => 1 },
parent => {
type => 'string',
description => 'For partitions only. The device path of '
. 'the disk the partition resides on.',
optional => 1,
},
},
},
},
code => sub {
my ($param) = @_;
my ($param) = @_;
my $skipsmart = $param->{skipsmart} // 0;
my $include_partitions = $param->{'include-partitions'} // 0;
my $skipsmart = $param->{skipsmart} // 0;
my $include_partitions = $param->{'include-partitions'} // 0;
my $disks = PVE::Diskmanage::get_disks(
undef,
$skipsmart,
$include_partitions
);
my $disks = PVE::Diskmanage::get_disks(
undef, $skipsmart, $include_partitions,
);
my $type = $param->{type} // '';
my $result = [];
my $type = $param->{type} // '';
my $result = [];
foreach my $disk (sort keys %$disks) {
my $entry = $disks->{$disk};
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');
}
} elsif ($type eq 'unused') {
next if $entry->{used};
} elsif ($type ne '') {
die "internal error"; # should not happen
}
push @$result, $entry;
}
return $result;
}});
foreach my $disk (sort keys %$disks) {
my $entry = $disks->{$disk};
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');
}
} elsif ($type eq 'unused') {
next if $entry->{used};
} elsif ($type ne '') {
die "internal error"; # should not happen
}
push @$result, $entry;
}
return $result;
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'smart',
path => 'smart',
method => 'GET',
@ -184,47 +184,48 @@ __PACKAGE__->register_method ({
protected => 1,
proxyto => "node",
permissions => {
check => ['perm', '/', ['Sys.Audit']],
check => ['perm', '/', ['Sys.Audit']],
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
disk => {
type => 'string',
pattern => '^/dev/[a-zA-Z0-9\/]+$',
description => "Block device name",
},
healthonly => {
type => 'boolean',
description => "If true returns only the health status",
optional => 1,
},
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
disk => {
type => 'string',
pattern => '^/dev/[a-zA-Z0-9\/]+$',
description => "Block device name",
},
healthonly => {
type => 'boolean',
description => "If true returns only the health status",
optional => 1,
},
},
},
returns => {
type => 'object',
properties => {
health => { type => 'string' },
type => { type => 'string', optional => 1 },
attributes => { type => 'array', optional => 1},
text => { type => 'string', optional => 1 },
},
type => 'object',
properties => {
health => { type => 'string' },
type => { type => 'string', optional => 1 },
attributes => { type => 'array', optional => 1 },
text => { type => 'string', optional => 1 },
},
},
code => sub {
my ($param) = @_;
my ($param) = @_;
my $disk = PVE::Diskmanage::verify_blockdev_path($param->{disk});
my $disk = PVE::Diskmanage::verify_blockdev_path($param->{disk});
my $result = PVE::Diskmanage::get_smart_data($disk, $param->{healthonly});
my $result = PVE::Diskmanage::get_smart_data($disk, $param->{healthonly});
$result->{health} = 'UNKNOWN' if !defined $result->{health};
$result = { health => $result->{health} } if $param->{healthonly};
$result->{health} = 'UNKNOWN' if !defined $result->{health};
$result = { health => $result->{health} } if $param->{healthonly};
return $result;
}});
return $result;
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'initgpt',
path => 'initgpt',
method => 'POST',
@ -232,48 +233,49 @@ __PACKAGE__->register_method ({
protected => 1,
proxyto => "node",
permissions => {
check => ['perm', '/', ['Sys.Modify']],
check => ['perm', '/', ['Sys.Modify']],
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
disk => {
type => 'string',
description => "Block device name",
pattern => '^/dev/[a-zA-Z0-9\/]+$',
},
uuid => {
type => 'string',
description => 'UUID for the GPT table',
pattern => '[a-fA-F0-9\-]+',
maxLength => 36,
optional => 1,
},
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
disk => {
type => 'string',
description => "Block device name",
pattern => '^/dev/[a-zA-Z0-9\/]+$',
},
uuid => {
type => 'string',
description => 'UUID for the GPT table',
pattern => '[a-fA-F0-9\-]+',
maxLength => 36,
optional => 1,
},
},
},
returns => { type => 'string' },
code => sub {
my ($param) = @_;
my ($param) = @_;
my $disk = PVE::Diskmanage::verify_blockdev_path($param->{disk});
my $disk = PVE::Diskmanage::verify_blockdev_path($param->{disk});
my $rpcenv = PVE::RPCEnvironment::get();
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $authuser = $rpcenv->get_user();
die "$disk is a partition\n" if PVE::Diskmanage::is_partition($disk);
die "disk $disk already in use\n" if PVE::Diskmanage::disk_is_used($disk);
my $worker = sub {
PVE::Diskmanage::init_disk($disk, $param->{uuid});
};
die "$disk is a partition\n" if PVE::Diskmanage::is_partition($disk);
die "disk $disk already in use\n" if PVE::Diskmanage::disk_is_used($disk);
my $worker = sub {
PVE::Diskmanage::init_disk($disk, $param->{uuid});
};
my $diskid = $disk;
$diskid =~ s|^.*/||; # remove all up to the last slash
return $rpcenv->fork_worker('diskinit', $diskid, $authuser, $worker);
}});
my $diskid = $disk;
$diskid =~ s|^.*/||; # remove all up to the last slash
return $rpcenv->fork_worker('diskinit', $diskid, $authuser, $worker);
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'wipe_disk',
path => 'wipedisk',
method => 'PUT',
@ -281,39 +283,40 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
disk => {
type => 'string',
description => "Block device name",
pattern => '^/dev/[a-zA-Z0-9\/]+$',
},
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
disk => {
type => 'string',
description => "Block device name",
pattern => '^/dev/[a-zA-Z0-9\/]+$',
},
},
},
returns => { type => 'string' },
code => sub {
my ($param) = @_;
my ($param) = @_;
my $disk = PVE::Diskmanage::verify_blockdev_path($param->{disk});
my $disk = PVE::Diskmanage::verify_blockdev_path($param->{disk});
my $mounted = PVE::Diskmanage::is_mounted($disk);
die "disk/partition '${mounted}' is mounted\n" if $mounted;
my $mounted = PVE::Diskmanage::is_mounted($disk);
die "disk/partition '${mounted}' is mounted\n" if $mounted;
my $held = PVE::Diskmanage::has_holder($disk);
die "disk/partition '${held}' has a holder\n" if $held;
my $held = PVE::Diskmanage::has_holder($disk);
die "disk/partition '${held}' has a holder\n" if $held;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $worker = sub {
PVE::Diskmanage::wipe_blockdev($disk);
PVE::Diskmanage::udevadm_trigger($disk);
};
my $worker = sub {
PVE::Diskmanage::wipe_blockdev($disk);
PVE::Diskmanage::udevadm_trigger($disk);
};
my $basename = basename($disk); # avoid '/' in the ID
my $basename = basename($disk); # avoid '/' in the ID
return $rpcenv->fork_worker('wipedisk', $basename, $authuser, $worker);
}});
return $rpcenv->fork_worker('wipedisk', $basename, $authuser, $worker);
},
});
1;

View File

@ -30,32 +30,32 @@ my $read_ini = sub {
my $section;
foreach my $line (@lines) {
$line = trim($line);
if ($line =~ m/^\[([^\]]+)\]/) {
$section = $1;
if (!defined($result->{$section})) {
$result->{$section} = {};
}
} elsif ($line =~ m/^(.*?)=(.*)$/) {
my ($key, $val) = ($1, $2);
if (!$section) {
warn "key value pair found without section, skipping\n";
next;
}
$line = trim($line);
if ($line =~ m/^\[([^\]]+)\]/) {
$section = $1;
if (!defined($result->{$section})) {
$result->{$section} = {};
}
} elsif ($line =~ m/^(.*?)=(.*)$/) {
my ($key, $val) = ($1, $2);
if (!$section) {
warn "key value pair found without section, skipping\n";
next;
}
if ($result->{$section}->{$key}) {
# make duplicate properties to arrays to keep the order
my $prop = $result->{$section}->{$key};
if (ref($prop) eq 'ARRAY') {
push @$prop, $val;
} else {
$result->{$section}->{$key} = [$prop, $val];
}
} else {
$result->{$section}->{$key} = $val;
}
}
# ignore everything else
if ($result->{$section}->{$key}) {
# make duplicate properties to arrays to keep the order
my $prop = $result->{$section}->{$key};
if (ref($prop) eq 'ARRAY') {
push @$prop, $val;
} else {
$result->{$section}->{$key} = [$prop, $val];
}
} else {
$result->{$section}->{$key} = $val;
}
}
# ignore everything else
}
return $result;
@ -67,341 +67,366 @@ my $write_ini = sub {
my $content = "";
foreach my $sname (sort keys %$ini) {
my $section = $ini->{$sname};
my $section = $ini->{$sname};
$content .= "[$sname]\n";
$content .= "[$sname]\n";
foreach my $pname (sort keys %$section) {
my $prop = $section->{$pname};
foreach my $pname (sort keys %$section) {
my $prop = $section->{$pname};
if (!ref($prop)) {
$content .= "$pname=$prop\n";
} elsif (ref($prop) eq 'ARRAY') {
foreach my $val (@$prop) {
$content .= "$pname=$val\n";
}
} else {
die "invalid property '$pname'\n";
}
}
$content .= "\n";
if (!ref($prop)) {
$content .= "$pname=$prop\n";
} elsif (ref($prop) eq 'ARRAY') {
foreach my $val (@$prop) {
$content .= "$pname=$val\n";
}
} else {
die "invalid property '$pname'\n";
}
}
$content .= "\n";
}
file_set_contents($filename, $content);
};
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'index',
path => '',
method => 'GET',
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', ['Sys.Audit']],
check => ['perm', '/', ['Sys.Audit']],
},
description => "PVE Managed Directory storages.",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
},
returns => {
type => 'array',
items => {
type => 'object',
properties => {
unitfile => {
type => 'string',
description => 'The path of the mount unit.',
},
path => {
type => 'string',
description => 'The mount path.',
},
device => {
type => 'string',
description => 'The mounted device.',
},
type => {
type => 'string',
description => 'The filesystem type.',
},
options => {
type => 'string',
description => 'The mount options.',
},
},
},
type => 'array',
items => {
type => 'object',
properties => {
unitfile => {
type => 'string',
description => 'The path of the mount unit.',
},
path => {
type => 'string',
description => 'The mount path.',
},
device => {
type => 'string',
description => 'The mounted device.',
},
type => {
type => 'string',
description => 'The filesystem type.',
},
options => {
type => 'string',
description => 'The mount options.',
},
},
},
},
code => sub {
my ($param) = @_;
my ($param) = @_;
my $result = [];
my $result = [];
dir_glob_foreach('/etc/systemd/system', '^mnt-pve-(.+)\.mount$', sub {
my ($filename, $storid) = @_;
$storid = PVE::Systemd::unescape_unit($storid);
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);
my $unitfile = "/etc/systemd/system/$filename";
my $unit = $read_ini->($unitfile);
push @$result, {
unitfile => $unitfile,
path => "/mnt/pve/$storid",
device => $unit->{'Mount'}->{'What'},
type => $unit->{'Mount'}->{'Type'},
options => $unit->{'Mount'}->{'Options'},
};
});
push @$result,
{
unitfile => $unitfile,
path => "/mnt/pve/$storid",
device => $unit->{'Mount'}->{'What'},
type => $unit->{'Mount'}->{'Type'},
options => $unit->{'Mount'}->{'Options'},
};
},
);
return $result;
}});
return $result;
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'create',
path => '',
method => 'POST',
proxyto => 'node',
protected => 1,
permissions => {
description => "Requires additionally 'Datastore.Allocate' on /storage when setting 'add_storage'",
check => ['perm', '/', ['Sys.Modify']],
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 => {
node => get_standard_option('pve-node'),
name => get_standard_option('pve-storage-id'),
device => {
type => 'string',
description => 'The block device you want to create the filesystem on.',
},
add_storage => {
description => "Configure storage using the directory.",
type => 'boolean',
optional => 1,
default => 0,
},
filesystem => {
description => "The desired filesystem.",
type => 'string',
enum => ['ext4', 'xfs'],
optional => 1,
default => 'ext4',
},
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
name => get_standard_option('pve-storage-id'),
device => {
type => 'string',
description => 'The block device you want to create the filesystem on.',
},
add_storage => {
description => "Configure storage using the directory.",
type => 'boolean',
optional => 1,
default => 0,
},
filesystem => {
description => "The desired filesystem.",
type => 'string',
enum => ['ext4', 'xfs'],
optional => 1,
default => 'ext4',
},
},
},
returns => { type => 'string' },
code => sub {
my ($param) = @_;
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $user = $rpcenv->get_user();
my $rpcenv = PVE::RPCEnvironment::get();
my $user = $rpcenv->get_user();
my $name = $param->{name};
my $dev = $param->{device};
my $node = $param->{node};
my $type = $param->{filesystem} // 'ext4';
my $path = "/mnt/pve/$name";
my $mountunitname = PVE::Systemd::escape_unit($path, 1) . ".mount";
my $mountunitpath = "/etc/systemd/system/$mountunitname";
my $name = $param->{name};
my $dev = $param->{device};
my $node = $param->{node};
my $type = $param->{filesystem} // 'ext4';
my $path = "/mnt/pve/$name";
my $mountunitname = PVE::Systemd::escape_unit($path, 1) . ".mount";
my $mountunitpath = "/etc/systemd/system/$mountunitname";
$dev = PVE::Diskmanage::verify_blockdev_path($dev);
PVE::Diskmanage::assert_disk_unused($dev);
$dev = PVE::Diskmanage::verify_blockdev_path($dev);
PVE::Diskmanage::assert_disk_unused($dev);
my $storage_params = {
type => 'dir',
storage => $name,
content => 'rootdir,images,iso,backup,vztmpl,snippets',
is_mountpoint => 1,
path => $path,
nodes => $node,
};
my $verify_params = [qw(path)];
my $storage_params = {
type => 'dir',
storage => $name,
content => 'rootdir,images,iso,backup,vztmpl,snippets',
is_mountpoint => 1,
path => $path,
nodes => $node,
};
my $verify_params = [qw(path)];
if ($param->{add_storage}) {
$rpcenv->check($user, "/storage", ['Datastore.Allocate']);
if ($param->{add_storage}) {
$rpcenv->check($user, "/storage", ['Datastore.Allocate']);
# 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);
}
# 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,
);
}
my $mounted = PVE::Diskmanage::mounted_paths();
die "the path for '${name}' is already mounted: ${path} ($mounted->{$path})\n"
if $mounted->{$path};
die "a systemd mount unit already exists: ${mountunitpath}\n" if -e $mountunitpath;
my $mounted = PVE::Diskmanage::mounted_paths();
die "the path for '${name}' is already mounted: ${path} ($mounted->{$path})\n"
if $mounted->{$path};
die "a systemd mount unit already exists: ${mountunitpath}\n" if -e $mountunitpath;
my $worker = sub {
PVE::Diskmanage::locked_disk_action(sub {
PVE::Diskmanage::assert_disk_unused($dev);
my $worker = sub {
PVE::Diskmanage::locked_disk_action(sub {
PVE::Diskmanage::assert_disk_unused($dev);
my $part = $dev;
my $part = $dev;
if (PVE::Diskmanage::is_partition($dev)) {
eval { PVE::Diskmanage::change_parttype($dev, '8300'); };
warn $@ if $@;
} else {
# create partition
my $cmd = [$SGDISK, '-n1', '-t1:8300', $dev];
print "# ", join(' ', @$cmd), "\n";
run_command($cmd);
if (PVE::Diskmanage::is_partition($dev)) {
eval { PVE::Diskmanage::change_parttype($dev, '8300'); };
warn $@ if $@;
} else {
# create partition
my $cmd = [$SGDISK, '-n1', '-t1:8300', $dev];
print "# ", join(' ', @$cmd), "\n";
run_command($cmd);
my ($devname) = $dev =~ m|^/dev/(.*)$|;
$part = "/dev/";
dir_glob_foreach("/sys/block/$devname", qr/\Q$devname\E.+/, sub {
my ($partition) = @_;
$part .= $partition;
});
}
my ($devname) = $dev =~ m|^/dev/(.*)$|;
$part = "/dev/";
dir_glob_foreach(
"/sys/block/$devname",
qr/\Q$devname\E.+/,
sub {
my ($partition) = @_;
$part .= $partition;
},
);
}
# create filesystem
my $cmd = [$MKFS, '-t', $type, $part];
print "# ", join(' ', @$cmd), "\n";
run_command($cmd);
# create filesystem
my $cmd = [$MKFS, '-t', $type, $part];
print "# ", join(' ', @$cmd), "\n";
run_command($cmd);
# create systemd mount unit and enable & start it
my $ini = {
'Unit' => {
'Description' => "Mount storage '$name' under /mnt/pve",
},
'Install' => {
'WantedBy' => 'multi-user.target',
},
};
# create systemd mount unit and enable & start it
my $ini = {
'Unit' => {
'Description' => "Mount storage '$name' under /mnt/pve",
},
'Install' => {
'WantedBy' => 'multi-user.target',
},
};
my $uuid_path;
my $uuid;
my $uuid_path;
my $uuid;
$cmd = [$BLKID, $part, '-o', 'export'];
print "# ", join(' ', @$cmd), "\n";
run_command($cmd, outfunc => sub {
my ($line) = @_;
$cmd = [$BLKID, $part, '-o', 'export'];
print "# ", join(' ', @$cmd), "\n";
run_command(
$cmd,
outfunc => sub {
my ($line) = @_;
if ($line =~ m/^UUID=(.*)$/) {
$uuid = $1;
$uuid_path = "/dev/disk/by-uuid/$uuid";
}
});
if ($line =~ m/^UUID=(.*)$/) {
$uuid = $1;
$uuid_path = "/dev/disk/by-uuid/$uuid";
}
},
);
die "could not get UUID of device '$part'\n" if !$uuid;
die "could not get UUID of device '$part'\n" if !$uuid;
$ini->{'Mount'} = {
'What' => $uuid_path,
'Where' => $path,
'Type' => $type,
'Options' => 'defaults',
};
$ini->{'Mount'} = {
'What' => $uuid_path,
'Where' => $path,
'Type' => $type,
'Options' => 'defaults',
};
$write_ini->($ini, $mountunitpath);
$write_ini->($ini, $mountunitpath);
PVE::Diskmanage::udevadm_trigger($part);
PVE::Diskmanage::udevadm_trigger($part);
run_command(['systemctl', 'daemon-reload']);
run_command(['systemctl', 'enable', $mountunitname]);
run_command(['systemctl', 'start', $mountunitname]);
run_command(['systemctl', 'daemon-reload']);
run_command(['systemctl', 'enable', $mountunitname]);
run_command(['systemctl', 'start', $mountunitname]);
if ($param->{add_storage}) {
PVE::API2::Storage::Config->create_or_update(
$name, $node, $storage_params, $verify_params);
}
});
};
if ($param->{add_storage}) {
PVE::API2::Storage::Config->create_or_update(
$name, $node, $storage_params, $verify_params,
);
}
});
};
return $rpcenv->fork_worker('dircreate', $name, $user, $worker);
}});
return $rpcenv->fork_worker('dircreate', $name, $user, $worker);
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'delete',
path => '{name}',
method => 'DELETE',
proxyto => 'node',
protected => 1,
permissions => {
description => "Requires additionally 'Datastore.Allocate' on /storage when setting 'cleanup-config'",
check => ['perm', '/', ['Sys.Modify']],
description =>
"Requires additionally 'Datastore.Allocate' on /storage when setting 'cleanup-config'",
check => ['perm', '/', ['Sys.Modify']],
},
description => "Unmounts the storage and removes the mount unit.",
parameters => {
additionalProperties => 0,
properties => {
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).",
type => 'boolean',
optional => 1,
default => 0,
},
'cleanup-disks' => {
description => "Also wipe disk so it can be repurposed afterwards.",
type => 'boolean',
optional => 1,
default => 0,
},
},
additionalProperties => 0,
properties => {
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).",
type => 'boolean',
optional => 1,
default => 0,
},
'cleanup-disks' => {
description => "Also wipe disk so it can be repurposed afterwards.",
type => 'boolean',
optional => 1,
default => 0,
},
},
},
returns => { type => 'string' },
code => sub {
my ($param) = @_;
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $user = $rpcenv->get_user();
my $rpcenv = PVE::RPCEnvironment::get();
my $user = $rpcenv->get_user();
$rpcenv->check($user, "/storage", ['Datastore.Allocate']) if $param->{'cleanup-config'};
$rpcenv->check($user, "/storage", ['Datastore.Allocate']) if $param->{'cleanup-config'};
my $name = $param->{name};
my $node = $param->{node};
my $name = $param->{name};
my $node = $param->{node};
my $worker = sub {
my $path = "/mnt/pve/$name";
my $mountunitname = PVE::Systemd::escape_unit($path, 1) . ".mount";
my $mountunitpath = "/etc/systemd/system/$mountunitname";
my $worker = sub {
my $path = "/mnt/pve/$name";
my $mountunitname = PVE::Systemd::escape_unit($path, 1) . ".mount";
my $mountunitpath = "/etc/systemd/system/$mountunitname";
PVE::Diskmanage::locked_disk_action(sub {
my $to_wipe;
if ($param->{'cleanup-disks'}) {
my $unit = $read_ini->($mountunitpath);
PVE::Diskmanage::locked_disk_action(sub {
my $to_wipe;
if ($param->{'cleanup-disks'}) {
my $unit = $read_ini->($mountunitpath);
my $dev = PVE::Diskmanage::verify_blockdev_path($unit->{'Mount'}->{'What'});
$to_wipe = $dev;
my $dev = PVE::Diskmanage::verify_blockdev_path($unit->{'Mount'}->{'What'});
$to_wipe = $dev;
# clean up whole device if this is the only partition
$dev =~ s|^/dev/||;
my $info = PVE::Diskmanage::get_disks($dev, 1, 1);
die "unable to obtain information for disk '$dev'\n" if !$info->{$dev};
$to_wipe = $info->{$dev}->{parent}
if $info->{$dev}->{parent} && scalar(keys $info->%*) == 2;
}
# clean up whole device if this is the only partition
$dev =~ s|^/dev/||;
my $info = PVE::Diskmanage::get_disks($dev, 1, 1);
die "unable to obtain information for disk '$dev'\n" if !$info->{$dev};
$to_wipe = $info->{$dev}->{parent}
if $info->{$dev}->{parent} && scalar(keys $info->%*) == 2;
}
run_command(['systemctl', 'stop', $mountunitname]);
run_command(['systemctl', 'disable', $mountunitname]);
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'}) {
my $match = sub {
my ($scfg) = @_;
return $scfg->{type} eq 'dir' && $scfg->{path} eq $path;
};
eval { PVE::API2::Storage::Config->cleanup_storages_for_node($match, $node); };
warn $config_err = $@ if $@;
}
my $config_err;
if ($param->{'cleanup-config'}) {
my $match = sub {
my ($scfg) = @_;
return $scfg->{type} eq 'dir' && $scfg->{path} eq $path;
};
eval {
PVE::API2::Storage::Config->cleanup_storages_for_node($match, $node);
};
warn $config_err = $@ if $@;
}
if ($to_wipe) {
PVE::Diskmanage::wipe_blockdev($to_wipe);
PVE::Diskmanage::udevadm_trigger($to_wipe);
}
if ($to_wipe) {
PVE::Diskmanage::wipe_blockdev($to_wipe);
PVE::Diskmanage::udevadm_trigger($to_wipe);
}
die "config cleanup failed - $config_err" if $config_err;
});
};
die "config cleanup failed - $config_err" if $config_err;
});
};
return $rpcenv->fork_worker('dirremove', $name, $user, $worker);
}});
return $rpcenv->fork_worker('dirremove', $name, $user, $worker);
},
});
1;

View File

@ -14,266 +14,277 @@ use PVE::RESTHandler;
use base qw(PVE::RESTHandler);
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'index',
path => '',
method => 'GET',
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', ['Sys.Audit']],
check => ['perm', '/', ['Sys.Audit']],
},
description => "List LVM Volume Groups",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
},
returns => {
type => 'object',
properties => {
leaf => {
type => 'boolean',
},
children => {
type => 'array',
items => {
type => "object",
properties => {
leaf => {
type => 'boolean',
},
name => {
type => 'string',
description => 'The name of the volume group',
},
size => {
type => 'integer',
description => 'The size of the volume group in bytes',
},
free => {
type => 'integer',
description => 'The free bytes in the volume group',
},
children => {
optional => 1,
type => 'array',
description => 'The underlying physical volumes',
items => {
type => 'object',
properties => {
leaf => {
type => 'boolean',
},
name => {
type => 'string',
description => 'The name of the physical volume',
},
size => {
type => 'integer',
description => 'The size of the physical volume in bytes',
},
free => {
type => 'integer',
description => 'The free bytes in the physical volume',
},
},
},
},
},
},
},
},
type => 'object',
properties => {
leaf => {
type => 'boolean',
},
children => {
type => 'array',
items => {
type => "object",
properties => {
leaf => {
type => 'boolean',
},
name => {
type => 'string',
description => 'The name of the volume group',
},
size => {
type => 'integer',
description => 'The size of the volume group in bytes',
},
free => {
type => 'integer',
description => 'The free bytes in the volume group',
},
children => {
optional => 1,
type => 'array',
description => 'The underlying physical volumes',
items => {
type => 'object',
properties => {
leaf => {
type => 'boolean',
},
name => {
type => 'string',
description => 'The name of the physical volume',
},
size => {
type => 'integer',
description =>
'The size of the physical volume in bytes',
},
free => {
type => 'integer',
description => 'The free bytes in the physical volume',
},
},
},
},
},
},
},
},
},
code => sub {
my ($param) = @_;
my ($param) = @_;
my $result = [];
my $result = [];
my $vgs = PVE::Storage::LVMPlugin::lvm_vgs(1);
my $vgs = PVE::Storage::LVMPlugin::lvm_vgs(1);
foreach my $vg_name (sort keys %$vgs) {
my $vg = $vgs->{$vg_name};
$vg->{name} = $vg_name;
$vg->{leaf} = 0;
foreach my $pv (@{$vg->{pvs}}) {
$pv->{leaf} = 1;
}
$vg->{children} = delete $vg->{pvs};
push @$result, $vg;
}
foreach my $vg_name (sort keys %$vgs) {
my $vg = $vgs->{$vg_name};
$vg->{name} = $vg_name;
$vg->{leaf} = 0;
foreach my $pv (@{ $vg->{pvs} }) {
$pv->{leaf} = 1;
}
$vg->{children} = delete $vg->{pvs};
push @$result, $vg;
}
return {
leaf => 0,
children => $result,
};
}});
return {
leaf => 0,
children => $result,
};
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'create',
path => '',
method => 'POST',
proxyto => 'node',
protected => 1,
permissions => {
description => "Requires additionally 'Datastore.Allocate' on /storage when setting 'add_storage'",
check => ['perm', '/', ['Sys.Modify']],
description =>
"Requires additionally 'Datastore.Allocate' on /storage when setting 'add_storage'",
check => ['perm', '/', ['Sys.Modify']],
},
description => "Create an LVM Volume Group",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
name => get_standard_option('pve-storage-id'),
device => {
type => 'string',
description => 'The block device you want to create the volume group on',
},
add_storage => {
description => "Configure storage using the Volume Group",
type => 'boolean',
optional => 1,
default => 0,
},
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
name => get_standard_option('pve-storage-id'),
device => {
type => 'string',
description => 'The block device you want to create the volume group on',
},
add_storage => {
description => "Configure storage using the Volume Group",
type => 'boolean',
optional => 1,
default => 0,
},
},
},
returns => { type => 'string' },
code => sub {
my ($param) = @_;
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $user = $rpcenv->get_user();
my $rpcenv = PVE::RPCEnvironment::get();
my $user = $rpcenv->get_user();
my $name = $param->{name};
my $dev = $param->{device};
my $node = $param->{node};
my $name = $param->{name};
my $dev = $param->{device};
my $node = $param->{node};
$dev = PVE::Diskmanage::verify_blockdev_path($dev);
PVE::Diskmanage::assert_disk_unused($dev);
$dev = PVE::Diskmanage::verify_blockdev_path($dev);
PVE::Diskmanage::assert_disk_unused($dev);
my $storage_params = {
type => 'lvm',
vgname => $name,
storage => $name,
content => 'rootdir,images',
shared => 0,
nodes => $node,
};
my $verify_params = [qw(vgname)];
my $storage_params = {
type => 'lvm',
vgname => $name,
storage => $name,
content => 'rootdir,images',
shared => 0,
nodes => $node,
};
my $verify_params = [qw(vgname)];
if ($param->{add_storage}) {
$rpcenv->check($user, "/storage", ['Datastore.Allocate']);
if ($param->{add_storage}) {
$rpcenv->check($user, "/storage", ['Datastore.Allocate']);
# 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);
}
# 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,
);
}
my $worker = sub {
PVE::Diskmanage::locked_disk_action(sub {
PVE::Diskmanage::assert_disk_unused($dev);
die "volume group with name '${name}' already exists on node '${node}'\n"
if PVE::Storage::LVMPlugin::lvm_vgs()->{$name};
my $worker = sub {
PVE::Diskmanage::locked_disk_action(sub {
PVE::Diskmanage::assert_disk_unused($dev);
die "volume group with name '${name}' already exists on node '${node}'\n"
if PVE::Storage::LVMPlugin::lvm_vgs()->{$name};
if (PVE::Diskmanage::is_partition($dev)) {
eval { PVE::Diskmanage::change_parttype($dev, '8E00'); };
warn $@ if $@;
}
if (PVE::Diskmanage::is_partition($dev)) {
eval { PVE::Diskmanage::change_parttype($dev, '8E00'); };
warn $@ if $@;
}
PVE::Storage::LVMPlugin::lvm_create_volume_group($dev, $name);
PVE::Storage::LVMPlugin::lvm_create_volume_group($dev, $name);
PVE::Diskmanage::udevadm_trigger($dev);
PVE::Diskmanage::udevadm_trigger($dev);
if ($param->{add_storage}) {
PVE::API2::Storage::Config->create_or_update(
$name, $node, $storage_params, $verify_params);
}
});
};
if ($param->{add_storage}) {
PVE::API2::Storage::Config->create_or_update(
$name, $node, $storage_params, $verify_params,
);
}
});
};
return $rpcenv->fork_worker('lvmcreate', $name, $user, $worker);
}});
return $rpcenv->fork_worker('lvmcreate', $name, $user, $worker);
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'delete',
path => '{name}',
method => 'DELETE',
proxyto => 'node',
protected => 1,
permissions => {
description => "Requires additionally 'Datastore.Allocate' on /storage when setting 'cleanup-config'",
check => ['perm', '/', ['Sys.Modify']],
description =>
"Requires additionally 'Datastore.Allocate' on /storage when setting 'cleanup-config'",
check => ['perm', '/', ['Sys.Modify']],
},
description => "Remove an LVM Volume Group.",
parameters => {
additionalProperties => 0,
properties => {
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).",
type => 'boolean',
optional => 1,
default => 0,
},
'cleanup-disks' => {
description => "Also wipe disks so they can be repurposed afterwards.",
type => 'boolean',
optional => 1,
default => 0,
},
},
additionalProperties => 0,
properties => {
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).",
type => 'boolean',
optional => 1,
default => 0,
},
'cleanup-disks' => {
description => "Also wipe disks so they can be repurposed afterwards.",
type => 'boolean',
optional => 1,
default => 0,
},
},
},
returns => { type => 'string' },
code => sub {
my ($param) = @_;
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $user = $rpcenv->get_user();
my $rpcenv = PVE::RPCEnvironment::get();
my $user = $rpcenv->get_user();
$rpcenv->check($user, "/storage", ['Datastore.Allocate']) if $param->{'cleanup-config'};
$rpcenv->check($user, "/storage", ['Datastore.Allocate']) if $param->{'cleanup-config'};
my $name = $param->{name};
my $node = $param->{node};
my $name = $param->{name};
my $node = $param->{node};
my $worker = sub {
PVE::Diskmanage::locked_disk_action(sub {
my $vgs = PVE::Storage::LVMPlugin::lvm_vgs(1);
die "no such volume group '$name'\n" if !$vgs->{$name};
my $worker = sub {
PVE::Diskmanage::locked_disk_action(sub {
my $vgs = PVE::Storage::LVMPlugin::lvm_vgs(1);
die "no such volume group '$name'\n" if !$vgs->{$name};
PVE::Storage::LVMPlugin::lvm_destroy_volume_group($name);
PVE::Storage::LVMPlugin::lvm_destroy_volume_group($name);
my $config_err;
if ($param->{'cleanup-config'}) {
my $match = sub {
my ($scfg) = @_;
return $scfg->{type} eq 'lvm' && $scfg->{vgname} eq $name;
};
eval { PVE::API2::Storage::Config->cleanup_storages_for_node($match, $node); };
warn $config_err = $@ if $@;
}
my $config_err;
if ($param->{'cleanup-config'}) {
my $match = sub {
my ($scfg) = @_;
return $scfg->{type} eq 'lvm' && $scfg->{vgname} eq $name;
};
eval {
PVE::API2::Storage::Config->cleanup_storages_for_node($match, $node);
};
warn $config_err = $@ if $@;
}
if ($param->{'cleanup-disks'}) {
my $wiped = [];
eval {
for my $pv ($vgs->{$name}->{pvs}->@*) {
my $dev = PVE::Diskmanage::verify_blockdev_path($pv->{name});
PVE::Diskmanage::wipe_blockdev($dev);
push $wiped->@*, $dev;
}
};
my $err = $@;
PVE::Diskmanage::udevadm_trigger($wiped->@*);
die "cleanup failed - $err" if $err;
}
if ($param->{'cleanup-disks'}) {
my $wiped = [];
eval {
for my $pv ($vgs->{$name}->{pvs}->@*) {
my $dev = PVE::Diskmanage::verify_blockdev_path($pv->{name});
PVE::Diskmanage::wipe_blockdev($dev);
push $wiped->@*, $dev;
}
};
my $err = $@;
PVE::Diskmanage::udevadm_trigger($wiped->@*);
die "cleanup failed - $err" if $err;
}
die "config cleanup failed - $config_err" if $config_err;
});
};
die "config cleanup failed - $config_err" if $config_err;
});
};
return $rpcenv->fork_worker('lvmremove', $name, $user, $worker);
}});
return $rpcenv->fork_worker('lvmremove', $name, $user, $worker);
},
});
1;

View File

@ -15,255 +15,269 @@ use PVE::RESTHandler;
use base qw(PVE::RESTHandler);
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'index',
path => '',
method => 'GET',
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', ['Sys.Audit']],
check => ['perm', '/', ['Sys.Audit']],
},
description => "List LVM thinpools",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
},
returns => {
type => 'array',
items => {
type => 'object',
properties => {
lv => {
type => 'string',
description => 'The name of the thinpool.',
},
vg => {
type => 'string',
description => 'The associated volume group.',
},
lv_size => {
type => 'integer',
description => 'The size of the thinpool in bytes.',
},
used => {
type => 'integer',
description => 'The used bytes of the thinpool.',
},
metadata_size => {
type => 'integer',
description => 'The size of the metadata lv in bytes.',
},
metadata_used => {
type => 'integer',
description => 'The used bytes of the metadata lv.',
},
},
},
type => 'array',
items => {
type => 'object',
properties => {
lv => {
type => 'string',
description => 'The name of the thinpool.',
},
vg => {
type => 'string',
description => 'The associated volume group.',
},
lv_size => {
type => 'integer',
description => 'The size of the thinpool in bytes.',
},
used => {
type => 'integer',
description => 'The used bytes of the thinpool.',
},
metadata_size => {
type => 'integer',
description => 'The size of the metadata lv in bytes.',
},
metadata_used => {
type => 'integer',
description => 'The used bytes of the metadata lv.',
},
},
},
},
code => sub {
my ($param) = @_;
return PVE::Storage::LvmThinPlugin::list_thinpools(undef);
}});
my ($param) = @_;
return PVE::Storage::LvmThinPlugin::list_thinpools(undef);
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'create',
path => '',
method => 'POST',
proxyto => 'node',
protected => 1,
permissions => {
description => "Requires additionally 'Datastore.Allocate' on /storage when setting 'add_storage'",
check => ['perm', '/', ['Sys.Modify']],
description =>
"Requires additionally 'Datastore.Allocate' on /storage when setting 'add_storage'",
check => ['perm', '/', ['Sys.Modify']],
},
description => "Create an LVM thinpool",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
name => get_standard_option('pve-storage-id'),
device => {
type => 'string',
description => 'The block device you want to create the thinpool on.',
},
add_storage => {
description => "Configure storage using the thinpool.",
type => 'boolean',
optional => 1,
default => 0,
},
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
name => get_standard_option('pve-storage-id'),
device => {
type => 'string',
description => 'The block device you want to create the thinpool on.',
},
add_storage => {
description => "Configure storage using the thinpool.",
type => 'boolean',
optional => 1,
default => 0,
},
},
},
returns => { type => 'string' },
code => sub {
my ($param) = @_;
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $user = $rpcenv->get_user();
my $rpcenv = PVE::RPCEnvironment::get();
my $user = $rpcenv->get_user();
my $name = $param->{name};
my $dev = $param->{device};
my $node = $param->{node};
my $name = $param->{name};
my $dev = $param->{device};
my $node = $param->{node};
$dev = PVE::Diskmanage::verify_blockdev_path($dev);
PVE::Diskmanage::assert_disk_unused($dev);
$dev = PVE::Diskmanage::verify_blockdev_path($dev);
PVE::Diskmanage::assert_disk_unused($dev);
my $storage_params = {
type => 'lvmthin',
vgname => $name,
thinpool => $name,
storage => $name,
content => 'rootdir,images',
nodes => $node,
};
my $verify_params = [qw(vgname thinpool)];
my $storage_params = {
type => 'lvmthin',
vgname => $name,
thinpool => $name,
storage => $name,
content => 'rootdir,images',
nodes => $node,
};
my $verify_params = [qw(vgname thinpool)];
if ($param->{add_storage}) {
$rpcenv->check($user, "/storage", ['Datastore.Allocate']);
if ($param->{add_storage}) {
$rpcenv->check($user, "/storage", ['Datastore.Allocate']);
# 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);
}
# 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,
);
}
my $worker = sub {
PVE::Diskmanage::locked_disk_action(sub {
PVE::Diskmanage::assert_disk_unused($dev);
my $worker = sub {
PVE::Diskmanage::locked_disk_action(sub {
PVE::Diskmanage::assert_disk_unused($dev);
die "volume group with name '${name}' already exists on node '${node}'\n"
if PVE::Storage::LVMPlugin::lvm_vgs()->{$name};
die "volume group with name '${name}' already exists on node '${node}'\n"
if PVE::Storage::LVMPlugin::lvm_vgs()->{$name};
if (PVE::Diskmanage::is_partition($dev)) {
eval { PVE::Diskmanage::change_parttype($dev, '8E00'); };
warn $@ if $@;
}
if (PVE::Diskmanage::is_partition($dev)) {
eval { PVE::Diskmanage::change_parttype($dev, '8E00'); };
warn $@ if $@;
}
PVE::Storage::LVMPlugin::lvm_create_volume_group($dev, $name);
my $pv = PVE::Storage::LVMPlugin::lvm_pv_info($dev);
# keep some free space just in case
my $datasize = $pv->{size} - 128*1024;
# default to 1% for metadata
my $metadatasize = $datasize/100;
# but at least 1G, as recommended in lvmthin man
$metadatasize = 1024*1024 if $metadatasize < 1024*1024;
# but at most 16G, which is the current lvm max
$metadatasize = 16*1024*1024 if $metadatasize > 16*1024*1024;
# shrink data by needed amount for metadata
$datasize -= 2*$metadatasize;
PVE::Storage::LVMPlugin::lvm_create_volume_group($dev, $name);
my $pv = PVE::Storage::LVMPlugin::lvm_pv_info($dev);
# keep some free space just in case
my $datasize = $pv->{size} - 128 * 1024;
# default to 1% for metadata
my $metadatasize = $datasize / 100;
# but at least 1G, as recommended in lvmthin man
$metadatasize = 1024 * 1024 if $metadatasize < 1024 * 1024;
# but at most 16G, which is the current lvm max
$metadatasize = 16 * 1024 * 1024 if $metadatasize > 16 * 1024 * 1024;
# shrink data by needed amount for metadata
$datasize -= 2 * $metadatasize;
run_command([
'/sbin/lvcreate',
'--type', 'thin-pool',
"-L${datasize}K",
'--poolmetadatasize', "${metadatasize}K",
'-n', $name,
$name
]);
run_command([
'/sbin/lvcreate',
'--type',
'thin-pool',
"-L${datasize}K",
'--poolmetadatasize',
"${metadatasize}K",
'-n',
$name,
$name,
]);
PVE::Diskmanage::udevadm_trigger($dev);
PVE::Diskmanage::udevadm_trigger($dev);
if ($param->{add_storage}) {
PVE::API2::Storage::Config->create_or_update(
$name, $node, $storage_params, $verify_params);
}
});
};
if ($param->{add_storage}) {
PVE::API2::Storage::Config->create_or_update(
$name, $node, $storage_params, $verify_params,
);
}
});
};
return $rpcenv->fork_worker('lvmthincreate', $name, $user, $worker);
}});
return $rpcenv->fork_worker('lvmthincreate', $name, $user, $worker);
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'delete',
path => '{name}',
method => 'DELETE',
proxyto => 'node',
protected => 1,
permissions => {
description => "Requires additionally 'Datastore.Allocate' on /storage when setting 'cleanup-config'",
check => ['perm', '/', ['Sys.Modify']],
description =>
"Requires additionally 'Datastore.Allocate' on /storage when setting 'cleanup-config'",
check => ['perm', '/', ['Sys.Modify']],
},
description => "Remove an LVM thin pool.",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
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).",
type => 'boolean',
optional => 1,
default => 0,
},
'cleanup-disks' => {
description => "Also wipe disks so they can be repurposed afterwards.",
type => 'boolean',
optional => 1,
default => 0,
},
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
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).",
type => 'boolean',
optional => 1,
default => 0,
},
'cleanup-disks' => {
description => "Also wipe disks so they can be repurposed afterwards.",
type => 'boolean',
optional => 1,
default => 0,
},
},
},
returns => { type => 'string' },
code => sub {
my ($param) = @_;
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $user = $rpcenv->get_user();
my $rpcenv = PVE::RPCEnvironment::get();
my $user = $rpcenv->get_user();
$rpcenv->check($user, "/storage", ['Datastore.Allocate']) if $param->{'cleanup-config'};
$rpcenv->check($user, "/storage", ['Datastore.Allocate']) if $param->{'cleanup-config'};
my $vg = $param->{'volume-group'};
my $lv = $param->{name};
my $node = $param->{node};
my $vg = $param->{'volume-group'};
my $lv = $param->{name};
my $node = $param->{node};
my $worker = sub {
PVE::Diskmanage::locked_disk_action(sub {
my $thinpools = PVE::Storage::LvmThinPlugin::list_thinpools();
my $worker = sub {
PVE::Diskmanage::locked_disk_action(sub {
my $thinpools = PVE::Storage::LvmThinPlugin::list_thinpools();
die "no such thin pool ${vg}/${lv}\n"
if !grep { $_->{lv} eq $lv && $_->{vg} eq $vg } $thinpools->@*;
die "no such thin pool ${vg}/${lv}\n"
if !grep { $_->{lv} eq $lv && $_->{vg} eq $vg } $thinpools->@*;
run_command(['lvremove', '-y', "${vg}/${lv}"]);
run_command(['lvremove', '-y', "${vg}/${lv}"]);
my $config_err;
if ($param->{'cleanup-config'}) {
my $match = sub {
my ($scfg) = @_;
return $scfg->{type} eq 'lvmthin'
&& $scfg->{vgname} eq $vg
&& $scfg->{thinpool} eq $lv;
};
eval { PVE::API2::Storage::Config->cleanup_storages_for_node($match, $node); };
warn $config_err = $@ if $@;
}
my $config_err;
if ($param->{'cleanup-config'}) {
my $match = sub {
my ($scfg) = @_;
return
$scfg->{type} eq 'lvmthin'
&& $scfg->{vgname} eq $vg
&& $scfg->{thinpool} eq $lv;
};
eval {
PVE::API2::Storage::Config->cleanup_storages_for_node($match, $node);
};
warn $config_err = $@ if $@;
}
if ($param->{'cleanup-disks'}) {
my $vgs = PVE::Storage::LVMPlugin::lvm_vgs(1);
if ($param->{'cleanup-disks'}) {
my $vgs = PVE::Storage::LVMPlugin::lvm_vgs(1);
die "no such volume group '$vg'\n" if !$vgs->{$vg};
die "volume group '$vg' still in use\n" if $vgs->{$vg}->{lvcount} > 0;
die "no such volume group '$vg'\n" if !$vgs->{$vg};
die "volume group '$vg' still in use\n" if $vgs->{$vg}->{lvcount} > 0;
my $wiped = [];
eval {
for my $pv ($vgs->{$vg}->{pvs}->@*) {
my $dev = PVE::Diskmanage::verify_blockdev_path($pv->{name});
PVE::Diskmanage::wipe_blockdev($dev);
push $wiped->@*, $dev;
}
};
my $err = $@;
PVE::Diskmanage::udevadm_trigger($wiped->@*);
die "cleanup failed - $err" if $err;
}
my $wiped = [];
eval {
for my $pv ($vgs->{$vg}->{pvs}->@*) {
my $dev = PVE::Diskmanage::verify_blockdev_path($pv->{name});
PVE::Diskmanage::wipe_blockdev($dev);
push $wiped->@*, $dev;
}
};
my $err = $@;
PVE::Diskmanage::udevadm_trigger($wiped->@*);
die "cleanup failed - $err" if $err;
}
die "config cleanup failed - $config_err" if $config_err;
});
};
die "config cleanup failed - $config_err" if $config_err;
});
};
return $rpcenv->fork_worker('lvmthinremove', "${vg}-${lv}", $user, $worker);
}});
return $rpcenv->fork_worker('lvmthinremove', "${vg}-${lv}", $user, $worker);
},
});
1;

File diff suppressed because it is too large Load Diff

View File

@ -29,10 +29,12 @@ my $api_storage_config = sub {
my $scfg = dclone(PVE::Storage::storage_config($cfg, $storeid));
$scfg->{storage} = $storeid;
$scfg->{digest} = $cfg->{digest};
$scfg->{content} = PVE::Storage::Plugin->encode_value($scfg->{type}, 'content', $scfg->{content});
$scfg->{content} =
PVE::Storage::Plugin->encode_value($scfg->{type}, 'content', $scfg->{content});
if ($scfg->{nodes}) {
$scfg->{nodes} = PVE::Storage::Plugin->encode_value($scfg->{type}, 'nodes', $scfg->{nodes});
$scfg->{nodes} =
PVE::Storage::Plugin->encode_value($scfg->{type}, 'nodes', $scfg->{nodes});
}
return $scfg;
@ -47,21 +49,21 @@ sub cleanup_storages_for_node {
my $cluster_nodes = PVE::Cluster::get_nodelist();
for my $storeid (keys $config->{ids}->%*) {
my $scfg = PVE::Storage::storage_config($config, $storeid);
next if !$match->($scfg);
my $scfg = PVE::Storage::storage_config($config, $storeid);
next if !$match->($scfg);
my $nodes = $scfg->{nodes} || { map { $_ => 1 } $cluster_nodes->@* };
next if !$nodes->{$node}; # not configured on $node, so nothing to do
delete $nodes->{$node};
my $nodes = $scfg->{nodes} || { map { $_ => 1 } $cluster_nodes->@* };
next if !$nodes->{$node}; # not configured on $node, so nothing to do
delete $nodes->{$node};
if (scalar(keys $nodes->%*) > 0) {
$self->update({
nodes => join(',', sort keys $nodes->%*),
storage => $storeid,
});
} else {
$self->delete({storage => $storeid});
}
if (scalar(keys $nodes->%*) > 0) {
$self->update({
nodes => join(',', sort keys $nodes->%*),
storage => $storeid,
});
} else {
$self->delete({ storage => $storeid });
}
}
}
@ -80,355 +82,375 @@ sub create_or_update {
my $scfg = PVE::Storage::storage_config($cfg, $sid, 1);
if ($scfg) {
die "storage config for '${sid}' exists but no parameters to verify were provided\n"
if !$verify_params;
die "storage config for '${sid}' exists but no parameters to verify were provided\n"
if !$verify_params;
$node = PVE::INotify::nodename() if !$node || ($node eq 'localhost');
die "Storage ID '${sid}' already exists on node ${node}\n"
if !defined($scfg->{nodes}) || $scfg->{nodes}->{$node};
$node = PVE::INotify::nodename() if !$node || ($node eq 'localhost');
die "Storage ID '${sid}' already exists on node ${node}\n"
if !defined($scfg->{nodes}) || $scfg->{nodes}->{$node};
# check for type mismatch first to get a clear error
for my $key ('type', $verify_params->@*) {
if (!defined($scfg->{$key})) {
die "Option '${key}' is not configured for storage '$sid', "
."expected it to be '$storage_params->{$key}'";
}
if ($storage_params->{$key} ne $scfg->{$key}) {
die "Option '${key}' ($storage_params->{$key}) does not match "
."existing storage configuration '$scfg->{$key}'\n";
}
}
# check for type mismatch first to get a clear error
for my $key ('type', $verify_params->@*) {
if (!defined($scfg->{$key})) {
die "Option '${key}' is not configured for storage '$sid', "
. "expected it to be '$storage_params->{$key}'";
}
if ($storage_params->{$key} ne $scfg->{$key}) {
die "Option '${key}' ($storage_params->{$key}) does not match "
. "existing storage configuration '$scfg->{$key}'\n";
}
}
}
if (!$dryrun) {
if ($scfg) {
if ($scfg->{nodes}) {
$scfg->{nodes}->{$node} = 1;
$self->update({
nodes => join(',', sort keys $scfg->{nodes}->%*),
storage => $sid,
});
print "Added '${node}' to nodes for storage '${sid}'\n";
}
} else {
$self->create($storage_params);
}
if ($scfg) {
if ($scfg->{nodes}) {
$scfg->{nodes}->{$node} = 1;
$self->update({
nodes => join(',', sort keys $scfg->{nodes}->%*),
storage => $sid,
});
print "Added '${node}' to nodes for storage '${sid}'\n";
}
} else {
$self->create($storage_params);
}
}
}
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'index',
path => '',
method => 'GET',
description => "Storage index.",
permissions => {
description => "Only list entries where you have 'Datastore.Audit' or 'Datastore.AllocateSpace' permissions on '/storage/<storage>'",
user => 'all',
description =>
"Only list entries where you have 'Datastore.Audit' or 'Datastore.AllocateSpace' permissions on '/storage/<storage>'",
user => 'all',
},
parameters => {
additionalProperties => 0,
properties => {
type => {
description => "Only list storage of specific type",
type => 'string',
enum => $storage_type_enum,
optional => 1,
},
},
additionalProperties => 0,
properties => {
type => {
description => "Only list storage of specific type",
type => 'string',
enum => $storage_type_enum,
optional => 1,
},
},
},
returns => {
type => 'array',
items => {
type => "object",
properties => { storage => { type => 'string'} },
},
links => [ { rel => 'child', href => "{storage}" } ],
type => 'array',
items => {
type => "object",
properties => { storage => { type => 'string' } },
},
links => [{ rel => 'child', href => "{storage}" }],
},
code => sub {
my ($param) = @_;
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $cfg = PVE::Storage::config();
my $cfg = PVE::Storage::config();
my @sids = PVE::Storage::storage_ids($cfg);
my @sids = PVE::Storage::storage_ids($cfg);
my $res = [];
foreach my $storeid (@sids) {
my $privs = [ 'Datastore.Audit', 'Datastore.AllocateSpace' ];
next if !$rpcenv->check_any($authuser, "/storage/$storeid", $privs, 1);
my $res = [];
foreach my $storeid (@sids) {
my $privs = ['Datastore.Audit', 'Datastore.AllocateSpace'];
next if !$rpcenv->check_any($authuser, "/storage/$storeid", $privs, 1);
my $scfg = &$api_storage_config($cfg, $storeid);
next if $param->{type} && $param->{type} ne $scfg->{type};
push @$res, $scfg;
}
my $scfg = &$api_storage_config($cfg, $storeid);
next if $param->{type} && $param->{type} ne $scfg->{type};
push @$res, $scfg;
}
return $res;
}});
return $res;
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'read',
path => '{storage}',
method => 'GET',
description => "Read storage configuration.",
permissions => {
check => ['perm', '/storage/{storage}', ['Datastore.Allocate']],
check => ['perm', '/storage/{storage}', ['Datastore.Allocate']],
},
parameters => {
additionalProperties => 0,
properties => {
storage => get_standard_option('pve-storage-id'),
},
additionalProperties => 0,
properties => {
storage => get_standard_option('pve-storage-id'),
},
},
returns => { type => 'object' },
code => sub {
my ($param) = @_;
my ($param) = @_;
my $cfg = PVE::Storage::config();
my $cfg = PVE::Storage::config();
return &$api_storage_config($cfg, $param->{storage});
}});
return &$api_storage_config($cfg, $param->{storage});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'create',
protected => 1,
path => '',
method => 'POST',
description => "Create a new storage.",
permissions => {
check => ['perm', '/storage', ['Datastore.Allocate']],
check => ['perm', '/storage', ['Datastore.Allocate']],
},
parameters => PVE::Storage::Plugin->createSchema(),
returns => {
type => 'object',
properties => {
storage => {
description => "The ID of the created storage.",
type => 'string',
},
type => {
description => "The type of the created storage.",
type => 'string',
enum => $storage_type_enum,
},
config => {
description => "Partial, possible server generated, configuration properties.",
type => 'object',
optional => 1,
additionalProperties => 1,
properties => {
'encryption-key' => {
description => "The, possible auto-generated, encryption-key.",
optional => 1,
type => 'string',
},
},
},
},
type => 'object',
properties => {
storage => {
description => "The ID of the created storage.",
type => 'string',
},
type => {
description => "The type of the created storage.",
type => 'string',
enum => $storage_type_enum,
},
config => {
description => "Partial, possible server generated, configuration properties.",
type => 'object',
optional => 1,
additionalProperties => 1,
properties => {
'encryption-key' => {
description => "The, possible auto-generated, encryption-key.",
optional => 1,
type => 'string',
},
},
},
},
},
code => sub {
my ($param) = @_;
my ($param) = @_;
my $type = extract_param($param, 'type');
my $storeid = extract_param($param, 'storage');
my $type = extract_param($param, 'type');
my $storeid = extract_param($param, 'storage');
# revent an empty nodelist.
# fix me in section config create never need an empty entity.
delete $param->{nodes} if !$param->{nodes};
# revent an empty nodelist.
# fix me in section config create never need an empty entity.
delete $param->{nodes} if !$param->{nodes};
my $sensitive_params = PVE::Storage::Plugin::sensitive_properties($type);
my $sensitive = extract_sensitive_params($param, $sensitive_params, []);
my $sensitive_params = PVE::Storage::Plugin::sensitive_properties($type);
my $sensitive = extract_sensitive_params($param, $sensitive_params, []);
my $plugin = PVE::Storage::Plugin->lookup($type);
my $opts = $plugin->check_config($storeid, $param, 1, 1);
my $plugin = PVE::Storage::Plugin->lookup($type);
my $opts = $plugin->check_config($storeid, $param, 1, 1);
my $returned_config;
PVE::Storage::lock_storage_config(sub {
my $cfg = PVE::Storage::config();
my $returned_config;
PVE::Storage::lock_storage_config(
sub {
my $cfg = PVE::Storage::config();
if (my $scfg = PVE::Storage::storage_config($cfg, $storeid, 1)) {
die "storage ID '$storeid' already defined\n";
}
if (my $scfg = PVE::Storage::storage_config($cfg, $storeid, 1)) {
die "storage ID '$storeid' already defined\n";
}
$cfg->{ids}->{$storeid} = $opts;
$cfg->{ids}->{$storeid} = $opts;
$returned_config = $plugin->on_add_hook($storeid, $opts, %$sensitive);
$returned_config = $plugin->on_add_hook($storeid, $opts, %$sensitive);
if (defined($opts->{mkdir})) { # TODO: remove complete option in Proxmox VE 9
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"
}
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";
}
eval {
# try to activate if enabled on local node,
# we only do this to detect errors/problems sooner
if (PVE::Storage::storage_check_enabled($cfg, $storeid, undef, 1)) {
PVE::Storage::activate_storage($cfg, $storeid);
}
};
if (my $err = $@) {
eval { $plugin->on_delete_hook($storeid, $opts) };
warn "$@\n" if $@;
die $err;
}
eval {
# try to activate if enabled on local node,
# we only do this to detect errors/problems sooner
if (PVE::Storage::storage_check_enabled($cfg, $storeid, undef, 1)) {
PVE::Storage::activate_storage($cfg, $storeid);
}
};
if (my $err = $@) {
eval { $plugin->on_delete_hook($storeid, $opts) };
warn "$@\n" if $@;
die $err;
}
PVE::Storage::write_config($cfg);
PVE::Storage::write_config($cfg);
}, "create storage failed");
},
"create storage failed",
);
my $res = {
storage => $storeid,
type => $type,
};
$res->{config} = $returned_config if $returned_config;
return $res;
}});
my $res = {
storage => $storeid,
type => $type,
};
$res->{config} = $returned_config if $returned_config;
return $res;
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'update',
protected => 1,
path => '{storage}',
method => 'PUT',
description => "Update storage configuration.",
permissions => {
check => ['perm', '/storage', ['Datastore.Allocate']],
check => ['perm', '/storage', ['Datastore.Allocate']],
},
parameters => PVE::Storage::Plugin->updateSchema(),
returns => {
type => 'object',
properties => {
storage => {
description => "The ID of the created storage.",
type => 'string',
},
type => {
description => "The type of the created storage.",
type => 'string',
enum => $storage_type_enum,
},
config => {
description => "Partial, possible server generated, configuration properties.",
type => 'object',
optional => 1,
additionalProperties => 1,
properties => {
'encryption-key' => {
description => "The, possible auto-generated, encryption-key.",
optional => 1,
type => 'string',
},
},
},
},
type => 'object',
properties => {
storage => {
description => "The ID of the created storage.",
type => 'string',
},
type => {
description => "The type of the created storage.",
type => 'string',
enum => $storage_type_enum,
},
config => {
description => "Partial, possible server generated, configuration properties.",
type => 'object',
optional => 1,
additionalProperties => 1,
properties => {
'encryption-key' => {
description => "The, possible auto-generated, encryption-key.",
optional => 1,
type => 'string',
},
},
},
},
},
code => sub {
my ($param) = @_;
my ($param) = @_;
my $storeid = extract_param($param, 'storage');
my $digest = extract_param($param, 'digest');
my $delete = extract_param($param, 'delete');
my $type;
my $storeid = extract_param($param, 'storage');
my $digest = extract_param($param, 'digest');
my $delete = extract_param($param, 'delete');
my $type;
if ($delete) {
$delete = [ PVE::Tools::split_list($delete) ];
}
if ($delete) {
$delete = [PVE::Tools::split_list($delete)];
}
my $returned_config;
PVE::Storage::lock_storage_config(sub {
my $cfg = PVE::Storage::config();
my $returned_config;
PVE::Storage::lock_storage_config(
sub {
my $cfg = PVE::Storage::config();
PVE::SectionConfig::assert_if_modified($cfg, $digest);
PVE::SectionConfig::assert_if_modified($cfg, $digest);
my $scfg = PVE::Storage::storage_config($cfg, $storeid);
$type = $scfg->{type};
my $scfg = PVE::Storage::storage_config($cfg, $storeid);
$type = $scfg->{type};
my $sensitive_params = PVE::Storage::Plugin::sensitive_properties($type);
my $sensitive = extract_sensitive_params($param, $sensitive_params, $delete);
my $sensitive_params = PVE::Storage::Plugin::sensitive_properties($type);
my $sensitive = extract_sensitive_params($param, $sensitive_params, $delete);
my $plugin = PVE::Storage::Plugin->lookup($type);
my $opts = $plugin->check_config($storeid, $param, 0, 1);
my $plugin = PVE::Storage::Plugin->lookup($type);
my $opts = $plugin->check_config($storeid, $param, 0, 1);
if ($delete) {
my $options = $plugin->private()->{options}->{$type};
foreach my $k (@$delete) {
my $d = $options->{$k} || die "no such option '$k'\n";
die "unable to delete required option '$k'\n" if !$d->{optional};
die "unable to delete fixed option '$k'\n" if $d->{fixed};
die "cannot set and delete property '$k' at the same time!\n"
if defined($opts->{$k});
if ($delete) {
my $options = $plugin->private()->{options}->{$type};
foreach my $k (@$delete) {
my $d = $options->{$k} || die "no such option '$k'\n";
die "unable to delete required option '$k'\n" if !$d->{optional};
die "unable to delete fixed option '$k'\n" if $d->{fixed};
die "cannot set and delete property '$k' at the same time!\n"
if defined($opts->{$k});
delete $scfg->{$k};
}
}
delete $scfg->{$k};
}
}
$returned_config = $plugin->on_update_hook($storeid, $opts, %$sensitive);
$returned_config = $plugin->on_update_hook($storeid, $opts, %$sensitive);
for my $k (keys %$opts) {
$scfg->{$k} = $opts->{$k};
}
for my $k (keys %$opts) {
$scfg->{$k} = $opts->{$k};
}
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"
}
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";
}
PVE::Storage::write_config($cfg);
PVE::Storage::write_config($cfg);
}, "update storage failed");
},
"update storage failed",
);
my $res = {
storage => $storeid,
type => $type,
};
$res->{config} = $returned_config if $returned_config;
return $res;
}});
my $res = {
storage => $storeid,
type => $type,
};
$res->{config} = $returned_config if $returned_config;
return $res;
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'delete',
protected => 1,
path => '{storage}', # /storage/config/{storage}
method => 'DELETE',
description => "Delete storage configuration.",
permissions => {
check => ['perm', '/storage', ['Datastore.Allocate']],
check => ['perm', '/storage', ['Datastore.Allocate']],
},
parameters => {
additionalProperties => 0,
properties => {
storage => get_standard_option('pve-storage-id', {
completion => \&PVE::Storage::complete_storage,
}),
},
additionalProperties => 0,
properties => {
storage => get_standard_option(
'pve-storage-id',
{
completion => \&PVE::Storage::complete_storage,
},
),
},
},
returns => { type => 'null' },
code => sub {
my ($param) = @_;
my ($param) = @_;
my $storeid = extract_param($param, 'storage');
my $storeid = extract_param($param, 'storage');
PVE::Storage::lock_storage_config(sub {
my $cfg = PVE::Storage::config();
PVE::Storage::lock_storage_config(
sub {
my $cfg = PVE::Storage::config();
my $scfg = PVE::Storage::storage_config($cfg, $storeid);
my $scfg = PVE::Storage::storage_config($cfg, $storeid);
die "can't remove storage - storage is used as base of another storage\n"
if PVE::Storage::storage_is_used($cfg, $storeid);
die "can't remove storage - storage is used as base of another storage\n"
if PVE::Storage::storage_is_used($cfg, $storeid);
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
$plugin->on_delete_hook($storeid, $scfg);
$plugin->on_delete_hook($storeid, $scfg);
delete $cfg->{ids}->{$storeid};
delete $cfg->{ids}->{$storeid};
PVE::Storage::write_config($cfg);
PVE::Storage::write_config($cfg);
}, "delete storage failed");
},
"delete storage failed",
);
PVE::AccessControl::remove_storage_access($storeid);
PVE::AccessControl::remove_storage_access($storeid);
return undef;
}});
return undef;
},
});
1;

View File

@ -16,214 +16,248 @@ use PVE::SSHInfo;
use base qw(PVE::RESTHandler);
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'index',
path => '',
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',
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
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',
optional => 1,
completion => \&PVE::Storage::complete_content_type,
},
vmid => get_standard_option('pve-vmid', {
description => "Only list images for this VM",
optional => 1,
completion => \&PVE::Cluster::complete_vmid,
}),
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
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',
optional => 1,
completion => \&PVE::Storage::complete_content_type,
},
vmid => get_standard_option(
'pve-vmid',
{
description => "Only list images for this VM",
optional => 1,
completion => \&PVE::Cluster::complete_vmid,
},
),
},
},
returns => {
type => 'array',
items => {
type => "object",
properties => {
volid => {
description => "Volume identifier.",
type => 'string',
},
vmid => {
description => "Associated Owner VMID.",
type => 'integer',
optional => 1,
},
parent => {
description => "Volume identifier of parent (for linked cloned).",
type => 'string',
optional => 1,
},
'format' => {
description => "Format identifier ('raw', 'qcow2', 'subvol', 'iso', 'tgz' ...)",
type => 'string',
},
size => {
description => "Volume size in bytes.",
type => 'integer',
renderer => 'bytes',
},
used => {
description => "Used space. Please note that most storage plugins " .
"do not report anything useful here.",
type => 'integer',
renderer => 'bytes',
optional => 1,
},
ctime => {
description => "Creation time (seconds since the UNIX Epoch).",
type => 'integer',
minimum => 0,
optional => 1,
},
notes => {
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' "
." 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.",
type => 'object',
properties => {
state => {
description => "Last backup verification state.",
type => 'string',
},
upid => {
description => "Last backup verification UPID.",
type => 'string',
},
},
optional => 1,
},
protected => {
description => "Protection status. Currently only supported for backups.",
type => 'boolean',
optional => 1,
},
},
},
links => [ { rel => 'child', href => "{volid}" } ],
type => 'array',
items => {
type => "object",
properties => {
volid => {
description => "Volume identifier.",
type => 'string',
},
vmid => {
description => "Associated Owner VMID.",
type => 'integer',
optional => 1,
},
parent => {
description => "Volume identifier of parent (for linked cloned).",
type => 'string',
optional => 1,
},
'format' => {
description =>
"Format identifier ('raw', 'qcow2', 'subvol', 'iso', 'tgz' ...)",
type => 'string',
},
size => {
description => "Volume size in bytes.",
type => 'integer',
renderer => 'bytes',
},
used => {
description => "Used space. Please note that most storage plugins "
. "do not report anything useful here.",
type => 'integer',
renderer => 'bytes',
optional => 1,
},
ctime => {
description => "Creation time (seconds since the UNIX Epoch).",
type => 'integer',
minimum => 0,
optional => 1,
},
notes => {
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' "
. " 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.",
type => 'object',
properties => {
state => {
description => "Last backup verification state.",
type => 'string',
},
upid => {
description => "Last backup verification UPID.",
type => 'string',
},
},
optional => 1,
},
protected => {
description => "Protection status. Currently only supported for backups.",
type => 'boolean',
optional => 1,
},
},
},
links => [{ rel => 'child', href => "{volid}" }],
},
code => sub {
my ($param) = @_;
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $authuser = $rpcenv->get_user();
my $storeid = $param->{storage};
my $storeid = $param->{storage};
my $cfg = PVE::Storage::config();
my $cfg = PVE::Storage::config();
my $vollist = PVE::Storage::volume_list($cfg, $storeid, $param->{vmid}, $param->{content});
my $vollist =
PVE::Storage::volume_list($cfg, $storeid, $param->{vmid}, $param->{content});
my $res = [];
foreach my $item (@$vollist) {
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});
$item->{used} = int($item->{used}) if defined($item->{used});
push @$res, $item;
}
my $res = [];
foreach my $item (@$vollist) {
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});
$item->{used} = int($item->{used}) if defined($item->{used});
push @$res, $item;
}
return $res;
}});
return $res;
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'create',
path => '',
method => 'POST',
description => "Allocate disk images.",
permissions => {
check => ['perm', '/storage/{storage}', ['Datastore.AllocateSpace']],
check => ['perm', '/storage/{storage}', ['Datastore.AllocateSpace']],
},
protected => 1,
proxyto => 'node',
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
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', {
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)",
type => 'string',
pattern => '\d+[MG]?',
},
format => get_standard_option('pve-storage-image-format', {
requires => 'size',
optional => 1,
}),
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
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',
{
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)",
type => 'string',
pattern => '\d+[MG]?',
},
format => get_standard_option(
'pve-storage-image-format',
{
requires => 'size',
optional => 1,
},
),
},
},
returns => {
description => "Volume identifier",
type => 'string',
description => "Volume identifier",
type => 'string',
},
code => sub {
my ($param) = @_;
my ($param) = @_;
my $storeid = $param->{storage};
my $name = $param->{filename};
my $sizestr = $param->{size};
my $storeid = $param->{storage};
my $name = $param->{filename};
my $sizestr = $param->{size};
my $size;
if ($sizestr =~ m/^\d+$/) {
$size = $sizestr;
} elsif ($sizestr =~ m/^(\d+)M$/) {
$size = $1 * 1024;
} elsif ($sizestr =~ m/^(\d+)G$/) {
$size = $1 * 1024 * 1024;
} else {
raise_param_exc({ size => "unable to parse size '$sizestr'" });
}
my $size;
if ($sizestr =~ m/^\d+$/) {
$size = $sizestr;
} elsif ($sizestr =~ m/^(\d+)M$/) {
$size = $1 * 1024;
} elsif ($sizestr =~ m/^(\d+)G$/) {
$size = $1 * 1024 * 1024;
} else {
raise_param_exc({ size => "unable to parse size '$sizestr'" });
}
# extract FORMAT from name
if ($name =~ m/\.(raw|qcow2|vmdk)$/) {
my $fmt = $1;
# extract FORMAT from name
if ($name =~ m/\.(raw|qcow2|vmdk)$/) {
my $fmt = $1;
raise_param_exc({ format => "different storage formats ($param->{format} != $fmt)" })
if $param->{format} && $param->{format} ne $fmt;
raise_param_exc({
format => "different storage formats ($param->{format} != $fmt)" })
if $param->{format} && $param->{format} ne $fmt;
$param->{format} = $fmt;
}
$param->{format} = $fmt;
}
my $cfg = PVE::Storage::config();
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;
}});
return $volid;
},
});
# we allow to pass volume names (without storage prefix) if the storage
# is specified as separate parameter.
@ -233,257 +267,268 @@ my $real_volume_id = sub {
my $volid;
if ($volume =~ m/:/) {
eval {
my ($sid, $volname) = PVE::Storage::parse_volume_id ($volume);
die "storage ID mismatch ($sid != $storeid)\n"
if $storeid && $sid ne $storeid;
$volid = $volume;
$storeid = $sid;
};
raise_param_exc({ volume => $@ }) if $@;
eval {
my ($sid, $volname) = PVE::Storage::parse_volume_id($volume);
die "storage ID mismatch ($sid != $storeid)\n"
if $storeid && $sid ne $storeid;
$volid = $volume;
$storeid = $sid;
};
raise_param_exc({ volume => $@ }) if $@;
} else {
raise_param_exc({ volume => "no storage specified - incomplete volume ID" })
if !$storeid;
raise_param_exc({ volume => "no storage specified - incomplete volume ID" })
if !$storeid;
$volid = "$storeid:$volume";
$volid = "$storeid:$volume";
}
return wantarray ? ($volid, $storeid) : $volid;
};
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'info',
path => '{volume}',
method => 'GET',
description => "Get volume attributes",
permissions => {
description => "You need read access for the volume.",
user => 'all',
description => "You need read access for the volume.",
user => 'all',
},
protected => 1,
proxyto => 'node',
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
storage => get_standard_option('pve-storage-id', { optional => 1 }),
volume => {
description => "Volume identifier",
type => 'string',
},
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
storage => get_standard_option('pve-storage-id', { optional => 1 }),
volume => {
description => "Volume identifier",
type => 'string',
},
},
},
returns => {
type => 'object',
properties => {
path => {
description => "The Path",
type => 'string',
},
size => {
description => "Volume size in bytes.",
type => 'integer',
renderer => 'bytes',
},
used => {
description => "Used space. Please note that most storage plugins " .
"do not report anything useful here.",
type => 'integer',
renderer => 'bytes',
},
format => {
description => "Format identifier ('raw', 'qcow2', 'subvol', 'iso', 'tgz' ...)",
type => 'string',
},
notes => {
description => "Optional notes.",
optional => 1,
type => 'string',
},
protected => {
description => "Protection status. Currently only supported for backups.",
type => 'boolean',
optional => 1,
},
},
type => 'object',
properties => {
path => {
description => "The Path",
type => 'string',
},
size => {
description => "Volume size in bytes.",
type => 'integer',
renderer => 'bytes',
},
used => {
description => "Used space. Please note that most storage plugins "
. "do not report anything useful here.",
type => 'integer',
renderer => 'bytes',
},
format => {
description => "Format identifier ('raw', 'qcow2', 'subvol', 'iso', 'tgz' ...)",
type => 'string',
},
notes => {
description => "Optional notes.",
optional => 1,
type => 'string',
},
protected => {
description => "Protection status. Currently only supported for backups.",
type => 'boolean',
optional => 1,
},
},
},
code => sub {
my ($param) = @_;
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my ($volid, $storeid) = &$real_volume_id($param->{storage}, $param->{volume});
my ($volid, $storeid) = &$real_volume_id($param->{storage}, $param->{volume});
my $cfg = PVE::Storage::config();
my $cfg = PVE::Storage::config();
PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $volid);
PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $volid);
my $path = PVE::Storage::path($cfg, $volid);
my ($size, $format, $used, $parent) = PVE::Storage::volume_size_info($cfg, $volid);
die "volume_size_info on '$volid' failed - no format\n" if !$format;
die "volume_size_info on '$volid' failed - no size\n" if !defined($size);
die "volume '$volid' has size zero\n" if !$size && $format ne 'subvol';
my $path = PVE::Storage::path($cfg, $volid);
my ($size, $format, $used, $parent) = PVE::Storage::volume_size_info($cfg, $volid);
die "volume_size_info on '$volid' failed - no format\n" if !$format;
die "volume_size_info on '$volid' failed - no size\n" if !defined($size);
die "volume '$volid' has size zero\n" if !$size && $format ne 'subvol';
my $entry = {
path => $path,
size => int($size), # cast to integer in case it was changed to a string previously
used => int($used),
format => $format,
};
my $entry = {
path => $path,
size => int($size), # cast to integer in case it was changed to a string previously
used => int($used),
format => $format,
};
for my $attribute (qw(notes protected)) {
# keep going if fetching an optional attribute fails
eval {
my $value = PVE::Storage::get_volume_attribute($cfg, $volid, $attribute);
$entry->{$attribute} = $value if defined($value);
};
warn $@ if $@;
}
for my $attribute (qw(notes protected)) {
# keep going if fetching an optional attribute fails
eval {
my $value = PVE::Storage::get_volume_attribute($cfg, $volid, $attribute);
$entry->{$attribute} = $value if defined($value);
};
warn $@ if $@;
}
return $entry;
}});
return $entry;
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'updateattributes',
path => '{volume}',
method => 'PUT',
description => "Update volume attributes",
permissions => {
description => "You need read access for the volume.",
user => 'all',
description => "You need read access for the volume.",
user => 'all',
},
protected => 1,
proxyto => 'node',
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
storage => get_standard_option('pve-storage-id', { optional => 1 }),
volume => {
description => "Volume identifier",
type => 'string',
},
notes => {
description => "The new notes.",
type => 'string',
optional => 1,
},
protected => {
description => "Protection status. Currently only supported for backups.",
type => 'boolean',
optional => 1,
},
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
storage => get_standard_option('pve-storage-id', { optional => 1 }),
volume => {
description => "Volume identifier",
type => 'string',
},
notes => {
description => "The new notes.",
type => 'string',
optional => 1,
},
protected => {
description => "Protection status. Currently only supported for backups.",
type => 'boolean',
optional => 1,
},
},
},
returns => { type => 'null' },
code => sub {
my ($param) = @_;
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my ($volid, $storeid) = &$real_volume_id($param->{storage}, $param->{volume});
my ($volid, $storeid) = &$real_volume_id($param->{storage}, $param->{volume});
my $cfg = PVE::Storage::config();
my $cfg = PVE::Storage::config();
PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $volid);
PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $volid);
for my $attr (qw(notes protected)) {
if (exists $param->{$attr}) {
PVE::Storage::update_volume_attribute($cfg, $volid, $attr, $param->{$attr});
}
}
for my $attr (qw(notes protected)) {
if (exists $param->{$attr}) {
PVE::Storage::update_volume_attribute($cfg, $volid, $attr, $param->{$attr});
}
}
return undef;
}});
return undef;
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'delete',
path => '{volume}',
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).",
user => 'all',
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,
proxyto => 'node',
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
storage => get_standard_option('pve-storage-id', {
optional => 1,
completion => \&PVE::Storage::complete_storage,
}),
volume => {
description => "Volume identifier",
type => 'string',
completion => \&PVE::Storage::complete_volume,
},
delay => {
type => 'integer',
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,
},
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
storage => get_standard_option(
'pve-storage-id',
{
optional => 1,
completion => \&PVE::Storage::complete_storage,
},
),
volume => {
description => "Volume identifier",
type => 'string',
completion => \&PVE::Storage::complete_volume,
},
delay => {
type => 'integer',
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) = @_;
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $cfg = PVE::Storage::config();
my $cfg = PVE::Storage::config();
my ($volid, $storeid) = &$real_volume_id($param->{storage}, $param->{volume});
my ($volid, $storeid) = &$real_volume_id($param->{storage}, $param->{volume});
my ($path, $ownervm, $vtype) = PVE::Storage::path($cfg, $volid);
if ($vtype eq 'backup' && $ownervm) {
$rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
$rpcenv->check($authuser, "/vms/$ownervm", ['VM.Backup']);
} else {
$rpcenv->check($authuser, "/storage/$storeid", ['Datastore.Allocate']);
}
my ($path, $ownervm, $vtype) = PVE::Storage::path($cfg, $volid);
if ($vtype eq 'backup' && $ownervm) {
$rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
$rpcenv->check($authuser, "/vms/$ownervm", ['VM.Backup']);
} else {
$rpcenv->check($authuser, "/storage/$storeid", ['Datastore.Allocate']);
}
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})[^\/]+$/) {
# Remove log file #318 and notes file #3972 if they still exist
PVE::Storage::archive_auxiliaries_remove($path);
}
};
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})[^\/]+$/
) {
# Remove log file #318 and notes file #3972 if they still exist
PVE::Storage::archive_auxiliaries_remove($path);
}
};
my $id = (defined $ownervm ? "$ownervm@" : '') . $storeid;
my $upid = $rpcenv->fork_worker('imgdel', $id, $authuser, $worker);
my $background_delay = $param->{delay};
if ($background_delay) {
my $end_time = time() + $background_delay;
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});
sleep 1 if $currently_deleting;
} while (time() < $end_time && $currently_deleting);
my $id = (defined $ownervm ? "$ownervm@" : '') . $storeid;
my $upid = $rpcenv->fork_worker('imgdel', $id, $authuser, $worker);
my $background_delay = $param->{delay};
if ($background_delay) {
my $end_time = time() + $background_delay;
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});
sleep 1 if $currently_deleting;
} while (time() < $end_time && $currently_deleting);
if (!$currently_deleting) {
my $status = PVE::Tools::upid_read_status($upid);
chomp $status;
return undef if !PVE::Tools::upid_status_is_error($status);
die "$status\n";
}
}
return $upid;
}});
if (!$currently_deleting) {
my $status = PVE::Tools::upid_read_status($upid);
chomp $status;
return undef if !PVE::Tools::upid_status_is_error($status);
die "$status\n";
}
}
return $upid;
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'copy',
path => '{volume}',
method => 'POST',
@ -491,70 +536,80 @@ __PACKAGE__->register_method ({
protected => 1,
proxyto => 'node',
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
storage => get_standard_option('pve-storage-id', { optional => 1}),
volume => {
description => "Source volume identifier",
type => 'string',
},
target => {
description => "Target volume identifier",
type => 'string',
},
target_node => get_standard_option('pve-node', {
description => "Target node. Default is local node.",
optional => 1,
}),
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
storage => get_standard_option('pve-storage-id', { optional => 1 }),
volume => {
description => "Source volume identifier",
type => 'string',
},
target => {
description => "Target volume identifier",
type => 'string',
},
target_node => get_standard_option(
'pve-node',
{
description => "Target node. Default is local node.",
optional => 1,
},
),
},
},
returns => {
type => 'string',
type => 'string',
},
code => sub {
my ($param) = @_;
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $rpcenv = PVE::RPCEnvironment::get();
my $user = $rpcenv->get_user();
my $user = $rpcenv->get_user();
my $target_node = $param->{target_node} || PVE::INotify::nodename();
# pvesh examples
# cd /nodes/localhost/storage/local/content
# pve:/> create local:103/vm-103-disk-1.raw -target local:103/vm-103-disk-2.raw
# pve:/> create 103/vm-103-disk-1.raw -target 103/vm-103-disk-3.raw
my $target_node = $param->{target_node} || PVE::INotify::nodename();
# pvesh examples
# cd /nodes/localhost/storage/local/content
# pve:/> create local:103/vm-103-disk-1.raw -target local:103/vm-103-disk-2.raw
# pve:/> create 103/vm-103-disk-1.raw -target 103/vm-103-disk-3.raw
my $src_volid = &$real_volume_id($param->{storage}, $param->{volume});
my $dst_volid = &$real_volume_id($param->{storage}, $param->{target});
my $src_volid = &$real_volume_id($param->{storage}, $param->{volume});
my $dst_volid = &$real_volume_id($param->{storage}, $param->{target});
print "DEBUG: COPY $src_volid TO $dst_volid\n";
print "DEBUG: COPY $src_volid TO $dst_volid\n";
my $cfg = PVE::Storage::config();
my $cfg = PVE::Storage::config();
# do all parameter checks first
# do all parameter checks first
# then do all short running task (to raise errors before we go to background)
# then do all short running task (to raise errors before we go to background)
# then start the worker task
my $worker = sub {
my $upid = shift;
# then start the worker task
my $worker = sub {
my $upid = shift;
print "DEBUG: starting worker $upid\n";
print "DEBUG: starting worker $upid\n";
my ($target_sid, $target_volname) = PVE::Storage::parse_volume_id($dst_volid);
#my $target_ip = PVE::Cluster::remote_node_ip($target_node);
my ($target_sid, $target_volname) = PVE::Storage::parse_volume_id($dst_volid);
#my $target_ip = PVE::Cluster::remote_node_ip($target_node);
# 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});
# 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 },
);
print "DEBUG: end worker $upid\n";
print "DEBUG: end worker $upid\n";
};
};
return $rpcenv->fork_worker('imgcopy', undef, $user, $worker);
}});
return $rpcenv->fork_worker('imgcopy', undef, $user, $worker);
},
});
1;

View File

@ -20,204 +20,219 @@ my $parse_volname_or_id = sub {
my ($sid, $volname) = PVE::Storage::parse_volume_id($volume, 1);
if (defined($sid)) {
raise_param_exc({ volume => "storage ID mismatch ($sid != $storeid)." })
if $sid ne $storeid;
raise_param_exc({ volume => "storage ID mismatch ($sid != $storeid)." })
if $sid ne $storeid;
$volid = $volume;
$volid = $volume;
} elsif ($volume =~ m/^backup\//) {
$volid = "$storeid:$volume";
$volid = "$storeid:$volume";
} else {
$volid = "$storeid:backup/$volume";
$volid = "$storeid:backup/$volume";
}
return $volid;
};
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'list',
path => 'list',
method => 'GET',
proxyto => 'node',
permissions => {
description => "You need read access for the volume.",
user => 'all',
description => "You need read access for the volume.",
user => 'all',
},
description => "List files and directories for single file restore under the given path.",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
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.",
type => 'string',
completion => \&PVE::Storage::complete_volume,
},
filepath => {
description => 'base64-path to the directory or file being listed, or "/".',
type => 'string',
},
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
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.",
type => 'string',
completion => \&PVE::Storage::complete_volume,
},
filepath => {
description => 'base64-path to the directory or file being listed, or "/".',
type => 'string',
},
},
},
returns => {
type => 'array',
items => {
type => "object",
properties => {
filepath => {
description => "base64 path of the current entry",
type => 'string',
},
type => {
description => "Entry type.",
type => 'string',
},
text => {
description => "Entry display text.",
type => 'string',
},
leaf => {
description => "If this entry is a leaf in the directory graph.",
type => 'boolean',
},
size => {
description => "Entry file size.",
type => 'integer',
optional => 1,
},
mtime => {
description => "Entry last-modified time (unix timestamp).",
type => 'integer',
optional => 1,
},
},
},
type => 'array',
items => {
type => "object",
properties => {
filepath => {
description => "base64 path of the current entry",
type => 'string',
},
type => {
description => "Entry type.",
type => 'string',
},
text => {
description => "Entry display text.",
type => 'string',
},
leaf => {
description => "If this entry is a leaf in the directory graph.",
type => 'boolean',
},
size => {
description => "Entry file size.",
type => 'integer',
optional => 1,
},
mtime => {
description => "Entry last-modified time (unix timestamp).",
type => 'integer',
optional => 1,
},
},
},
},
protected => 1,
code => sub {
my ($param) = @_;
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $user = $rpcenv->get_user();
my $rpcenv = PVE::RPCEnvironment::get();
my $user = $rpcenv->get_user();
my $path = extract_param($param, 'filepath') || "/";
my $base64 = $path ne "/";
my $path = extract_param($param, 'filepath') || "/";
my $base64 = $path ne "/";
my $storeid = extract_param($param, 'storage');
my $storeid = extract_param($param, 'storage');
my $volid = $parse_volname_or_id->($storeid, $param->{volume});
my $cfg = PVE::Storage::config();
my $scfg = PVE::Storage::storage_config($cfg, $storeid);
my $volid = $parse_volname_or_id->($storeid, $param->{volume});
my $cfg = PVE::Storage::config();
my $scfg = PVE::Storage::storage_config($cfg, $storeid);
PVE::Storage::check_volume_access($rpcenv, $user, $cfg, undef, $volid, 'backup');
PVE::Storage::check_volume_access($rpcenv, $user, $cfg, undef, $volid, 'backup');
raise_param_exc({'storage' => "Only PBS storages supported for file-restore."})
if $scfg->{type} ne 'pbs';
raise_param_exc({ 'storage' => "Only PBS storages supported for file-restore." })
if $scfg->{type} ne 'pbs';
my (undef, $snap) = PVE::Storage::parse_volname($cfg, $volid);
my (undef, $snap) = PVE::Storage::parse_volname($cfg, $volid);
my $client = PVE::PBSClient->new($scfg, $storeid);
my $ret = $client->file_restore_list($snap, $path, $base64, { timeout => 25 });
my $client = PVE::PBSClient->new($scfg, $storeid);
my $ret = $client->file_restore_list($snap, $path, $base64, { timeout => 25 });
if (ref($ret) eq "HASH") {
my $msg = $ret->{message};
if (my $code = $ret->{code}) {
die PVE::Exception->new("$msg\n", code => $code);
} else {
die "$msg\n";
}
} elsif (ref($ret) eq "ARRAY") {
# 'leaf' is a proper JSON boolean, map to perl-y bool
# TODO: make PBSClient decode all bools always as 1/0?
foreach my $item (@$ret) {
$item->{leaf} = $item->{leaf} ? 1 : 0;
}
if (ref($ret) eq "HASH") {
my $msg = $ret->{message};
if (my $code = $ret->{code}) {
die PVE::Exception->new("$msg\n", code => $code);
} else {
die "$msg\n";
}
} elsif (ref($ret) eq "ARRAY") {
# 'leaf' is a proper JSON boolean, map to perl-y bool
# TODO: make PBSClient decode all bools always as 1/0?
foreach my $item (@$ret) {
$item->{leaf} = $item->{leaf} ? 1 : 0;
}
return $ret;
}
return $ret;
}
die "invalid proxmox-file-restore output";
}});
die "invalid proxmox-file-restore output";
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'download',
path => 'download',
method => 'GET',
proxyto => 'node',
download_allowed => 1,
permissions => {
description => "You need read access for the volume.",
user => 'all',
description => "You need read access for the volume.",
user => 'all',
},
description => "Extract a file or directory (as zip archive) from a PBS backup.",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
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.",
type => 'string',
completion => \&PVE::Storage::complete_volume,
},
filepath => {
description => 'base64-path to the directory or file to download.',
type => 'string',
},
tar => {
description => "Download dirs as 'tar.zst' instead of 'zip'.",
type => 'boolean',
optional => 1,
default => 0,
},
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
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.",
type => 'string',
completion => \&PVE::Storage::complete_volume,
},
filepath => {
description => 'base64-path to the directory or file to download.',
type => 'string',
},
tar => {
description => "Download dirs as 'tar.zst' instead of 'zip'.",
type => 'boolean',
optional => 1,
default => 0,
},
},
},
returns => {
type => 'any', # download
type => 'any', # download
},
protected => 1,
code => sub {
my ($param) = @_;
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $user = $rpcenv->get_user();
my $rpcenv = PVE::RPCEnvironment::get();
my $user = $rpcenv->get_user();
my $path = extract_param($param, 'filepath');
my $storeid = extract_param($param, 'storage');
my $volid = $parse_volname_or_id->($storeid, $param->{volume});
my $tar = extract_param($param, 'tar') // 0;
my $path = extract_param($param, 'filepath');
my $storeid = extract_param($param, 'storage');
my $volid = $parse_volname_or_id->($storeid, $param->{volume});
my $tar = extract_param($param, 'tar') // 0;
my $cfg = PVE::Storage::config();
my $scfg = PVE::Storage::storage_config($cfg, $storeid);
my $cfg = PVE::Storage::config();
my $scfg = PVE::Storage::storage_config($cfg, $storeid);
PVE::Storage::check_volume_access($rpcenv, $user, $cfg, undef, $volid, 'backup');
PVE::Storage::check_volume_access($rpcenv, $user, $cfg, undef, $volid, 'backup');
raise_param_exc({'storage' => "Only PBS storages supported for file-restore."})
if $scfg->{type} ne 'pbs';
raise_param_exc({ 'storage' => "Only PBS storages supported for file-restore." })
if $scfg->{type} ne 'pbs';
my (undef, $snap) = PVE::Storage::parse_volname($cfg, $volid);
my (undef, $snap) = PVE::Storage::parse_volname($cfg, $volid);
my $client = PVE::PBSClient->new($scfg, $storeid);
my $fifo = $client->file_restore_extract_prepare();
my $client = PVE::PBSClient->new($scfg, $storeid);
my $fifo = $client->file_restore_extract_prepare();
$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);
});
$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 => {
path => $fifo,
stream => 1,
'content-type' => 'application/octet-stream',
},
};
return $ret;
}});
my $ret = {
download => {
path => $fifo,
stream => 1,
'content-type' => 'application/octet-stream',
},
};
return $ret;
},
});
1;

View File

@ -12,153 +12,185 @@ use PVE::Tools qw(extract_param);
use base qw(PVE::RESTHandler);
__PACKAGE__->register_method ({
__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',
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
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.",
optional => 1,
}),
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', {
description => "Only consider backups for this guest.",
optional => 1,
completion => \&PVE::Cluster::complete_vmid,
}),
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
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.",
optional => 1,
},
),
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',
{
description => "Only consider backups for this guest.",
optional => 1,
completion => \&PVE::Cluster::complete_vmid,
},
),
},
},
returns => {
type => 'array',
items => {
type => 'object',
properties => {
volid => {
description => "Backup volume ID.",
type => 'string',
},
'ctime' => {
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.",
type => 'string',
enum => ['keep', 'remove', 'protected', 'renamed'],
},
type => {
description => "One of 'qemu', 'lxc', 'openvz' or 'unknown'.",
type => 'string',
},
'vmid' => {
description => "The VM the backup belongs to.",
type => 'integer',
optional => 1,
},
},
},
type => 'array',
items => {
type => 'object',
properties => {
volid => {
description => "Backup volume ID.",
type => 'string',
},
'ctime' => {
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.",
type => 'string',
enum => ['keep', 'remove', 'protected', 'renamed'],
},
type => {
description => "One of 'qemu', 'lxc', 'openvz' or 'unknown'.",
type => 'string',
},
'vmid' => {
description => "The VM the backup belongs to.",
type => 'integer',
optional => 1,
},
},
},
},
code => sub {
my ($param) = @_;
my ($param) = @_;
my $cfg = PVE::Storage::config();
my $cfg = PVE::Storage::config();
my $vmid = extract_param($param, 'vmid');
my $type = extract_param($param, 'type');
my $storeid = extract_param($param, 'storage');
my $vmid = extract_param($param, 'vmid');
my $type = extract_param($param, 'type');
my $storeid = extract_param($param, 'storage');
my $prune_backups = extract_param($param, 'prune-backups');
$prune_backups = PVE::JSONSchema::parse_property_string('prune-backups', $prune_backups)
if defined($prune_backups);
my $prune_backups = extract_param($param, 'prune-backups');
$prune_backups = PVE::JSONSchema::parse_property_string('prune-backups', $prune_backups)
if defined($prune_backups);
return PVE::Storage::prune_backups($cfg, $storeid, $prune_backups, $vmid, $type, 1);
}});
return PVE::Storage::prune_backups($cfg, $storeid, $prune_backups, $vmid, $type, 1);
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'delete',
path => '',
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).",
user => 'all',
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,
proxyto => 'node',
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
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.",
}),
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', {
description => "Only prune backups for this VM.",
completion => \&PVE::Cluster::complete_vmid,
optional => 1,
}),
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
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.",
},
),
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',
{
description => "Only prune backups for this VM.",
completion => \&PVE::Cluster::complete_vmid,
optional => 1,
},
),
},
},
returns => { type => 'string' },
code => sub {
my ($param) = @_;
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $cfg = PVE::Storage::config();
my $cfg = PVE::Storage::config();
my $vmid = extract_param($param, 'vmid');
my $type = extract_param($param, 'type');
my $storeid = extract_param($param, 'storage');
my $vmid = extract_param($param, 'vmid');
my $type = extract_param($param, 'type');
my $storeid = extract_param($param, 'storage');
my $prune_backups = extract_param($param, 'prune-backups');
$prune_backups = PVE::JSONSchema::parse_property_string('prune-backups', $prune_backups)
if defined($prune_backups);
my $prune_backups = extract_param($param, 'prune-backups');
$prune_backups = PVE::JSONSchema::parse_property_string('prune-backups', $prune_backups)
if defined($prune_backups);
if (defined($vmid)) {
$rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
$rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup']);
} else {
$rpcenv->check($authuser, "/storage/$storeid", ['Datastore.Allocate']);
}
if (defined($vmid)) {
$rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
$rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup']);
} else {
$rpcenv->check($authuser, "/storage/$storeid", ['Datastore.Allocate']);
}
my $id = (defined($vmid) ? "$vmid@" : '') . $storeid;
my $worker = sub {
PVE::Storage::prune_backups($cfg, $storeid, $prune_backups, $vmid, $type, 0);
};
my $id = (defined($vmid) ? "$vmid@" : '') . $storeid;
my $worker = sub {
PVE::Storage::prune_backups($cfg, $storeid, $prune_backups, $vmid, $type, 0);
};
return $rpcenv->fork_worker('prunebackups', $id, $authuser, $worker);
}});
return $rpcenv->fork_worker('prunebackups', $id, $authuser, $worker);
},
});
1;

View File

@ -20,39 +20,40 @@ __PACKAGE__->register_method({
method => 'GET',
description => "Index of available scan methods",
permissions => {
user => 'all',
user => 'all',
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
},
returns => {
type => 'array',
items => {
type => "object",
properties => {
method => { type => 'string'},
},
},
links => [ { rel => 'child', href => "{method}" } ],
type => 'array',
items => {
type => "object",
properties => {
method => { type => 'string' },
},
},
links => [{ rel => 'child', href => "{method}" }],
},
code => sub {
my ($param) = @_;
my ($param) = @_;
my $res = [
{ method => 'cifs' },
{ method => 'glusterfs' },
{ method => 'iscsi' },
{ method => 'lvm' },
{ method => 'nfs' },
{ method => 'pbs' },
{ method => 'zfs' },
];
my $res = [
{ method => 'cifs' },
{ method => 'glusterfs' },
{ method => 'iscsi' },
{ method => 'lvm' },
{ method => 'nfs' },
{ method => 'pbs' },
{ method => 'zfs' },
];
return $res;
}});
return $res;
},
});
__PACKAGE__->register_method({
name => 'nfsscan',
@ -62,46 +63,48 @@ __PACKAGE__->register_method({
protected => 1,
proxyto => "node",
permissions => {
check => ['perm', '/storage', ['Datastore.Allocate']],
check => ['perm', '/storage', ['Datastore.Allocate']],
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
server => {
description => "The server address (name or IP).",
type => 'string', format => 'pve-storage-server',
},
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
server => {
description => "The server address (name or IP).",
type => 'string',
format => 'pve-storage-server',
},
},
},
returns => {
type => 'array',
items => {
type => "object",
properties => {
path => {
description => "The exported path.",
type => 'string',
},
options => {
description => "NFS export options.",
type => 'string',
},
},
},
type => 'array',
items => {
type => "object",
properties => {
path => {
description => "The exported path.",
type => 'string',
},
options => {
description => "NFS export options.",
type => 'string',
},
},
},
},
code => sub {
my ($param) = @_;
my ($param) = @_;
my $server = $param->{server};
my $res = PVE::Storage::scan_nfs($server);
my $server = $param->{server};
my $res = PVE::Storage::scan_nfs($server);
my $data = [];
foreach my $k (sort keys %$res) {
push @$data, { path => $k, options => $res->{$k} };
}
return $data;
}});
my $data = [];
foreach my $k (sort keys %$res) {
push @$data, { path => $k, options => $res->{$k} };
}
return $data;
},
});
__PACKAGE__->register_method({
name => 'cifsscan',
@ -111,68 +114,70 @@ __PACKAGE__->register_method({
protected => 1,
proxyto => "node",
permissions => {
check => ['perm', '/storage', ['Datastore.Allocate']],
check => ['perm', '/storage', ['Datastore.Allocate']],
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
server => {
description => "The server address (name or IP).",
type => 'string', format => 'pve-storage-server',
},
username => {
description => "User name.",
type => 'string',
optional => 1,
},
password => {
description => "User password.",
type => 'string',
optional => 1,
},
domain => {
description => "SMB domain (Workgroup).",
type => 'string',
optional => 1,
},
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
server => {
description => "The server address (name or IP).",
type => 'string',
format => 'pve-storage-server',
},
username => {
description => "User name.",
type => 'string',
optional => 1,
},
password => {
description => "User password.",
type => 'string',
optional => 1,
},
domain => {
description => "SMB domain (Workgroup).",
type => 'string',
optional => 1,
},
},
},
returns => {
type => 'array',
items => {
type => "object",
properties => {
share => {
description => "The cifs share name.",
type => 'string',
},
description => {
description => "Descriptive text from server.",
type => 'string',
},
},
},
type => 'array',
items => {
type => "object",
properties => {
share => {
description => "The cifs share name.",
type => 'string',
},
description => {
description => "Descriptive text from server.",
type => 'string',
},
},
},
},
code => sub {
my ($param) = @_;
my ($param) = @_;
my $server = $param->{server};
my $server = $param->{server};
my $username = $param->{username};
my $password = $param->{password};
my $domain = $param->{domain};
my $username = $param->{username};
my $password = $param->{password};
my $domain = $param->{domain};
my $res = PVE::Storage::scan_cifs($server, $username, $password, $domain);
my $res = PVE::Storage::scan_cifs($server, $username, $password, $domain);
my $data = [];
foreach my $k (sort keys %$res) {
next if $k =~ m/NT_STATUS_/;
push @$data, { share => $k, description => $res->{$k} };
}
my $data = [];
foreach my $k (sort keys %$res) {
next if $k =~ m/NT_STATUS_/;
push @$data, { share => $k, description => $res->{$k} };
}
return $data;
}});
return $data;
},
});
__PACKAGE__->register_method({
name => 'pbsscan',
@ -182,61 +187,62 @@ __PACKAGE__->register_method({
protected => 1,
proxyto => "node",
permissions => {
check => ['perm', '/storage', ['Datastore.Allocate']],
check => ['perm', '/storage', ['Datastore.Allocate']],
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
server => {
description => "The server address (name or IP).",
type => 'string', format => 'pve-storage-server',
},
username => {
description => "User-name or API token-ID.",
type => 'string',
},
password => {
description => "User password or API token secret.",
type => 'string',
},
fingerprint => get_standard_option('fingerprint-sha256', {
optional => 1,
}),
port => {
description => "Optional port.",
type => 'integer',
minimum => 1,
maximum => 65535,
default => 8007,
optional => 1,
},
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
server => {
description => "The server address (name or IP).",
type => 'string',
format => 'pve-storage-server',
},
username => {
description => "User-name or API token-ID.",
type => 'string',
},
password => {
description => "User password or API token secret.",
type => 'string',
},
fingerprint => get_standard_option('fingerprint-sha256', {
optional => 1,
}),
port => {
description => "Optional port.",
type => 'integer',
minimum => 1,
maximum => 65535,
default => 8007,
optional => 1,
},
},
},
returns => {
type => 'array',
items => {
type => "object",
properties => {
store => {
description => "The datastore name.",
type => 'string',
},
comment => {
description => "Comment from server.",
type => 'string',
optional => 1,
},
},
},
type => 'array',
items => {
type => "object",
properties => {
store => {
description => "The datastore name.",
type => 'string',
},
comment => {
description => "Comment from server.",
type => 'string',
optional => 1,
},
},
},
},
code => sub {
my ($param) = @_;
my ($param) = @_;
my $password = delete $param->{password};
my $password = delete $param->{password};
return PVE::Storage::PBSPlugin::scan_datastores($param, $password);
}
return PVE::Storage::PBSPlugin::scan_datastores($param, $password);
},
});
# Note: GlusterFS currently does not have an equivalent of showmount.
@ -250,44 +256,46 @@ __PACKAGE__->register_method({
protected => 1,
proxyto => "node",
permissions => {
check => ['perm', '/storage', ['Datastore.Allocate']],
check => ['perm', '/storage', ['Datastore.Allocate']],
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
server => {
description => "The server address (name or IP).",
type => 'string', format => 'pve-storage-server',
},
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
server => {
description => "The server address (name or IP).",
type => 'string',
format => 'pve-storage-server',
},
},
},
returns => {
type => 'array',
items => {
type => "object",
properties => {
volname => {
description => "The volume name.",
type => 'string',
},
},
},
type => 'array',
items => {
type => "object",
properties => {
volname => {
description => "The volume name.",
type => 'string',
},
},
},
},
code => sub {
my ($param) = @_;
my ($param) = @_;
my $server = $param->{server};
my $res = PVE::Storage::scan_nfs($server);
my $server = $param->{server};
my $res = PVE::Storage::scan_nfs($server);
my $data = [];
foreach my $path (sort keys %$res) {
if ($path =~ m!^/([^\s/]+)$!) {
push @$data, { volname => $1 };
}
}
return $data;
}});
my $data = [];
foreach my $path (sort keys %$res) {
if ($path =~ m!^/([^\s/]+)$!) {
push @$data, { volname => $1 };
}
}
return $data;
},
});
__PACKAGE__->register_method({
name => 'iscsiscan',
@ -297,46 +305,48 @@ __PACKAGE__->register_method({
protected => 1,
proxyto => "node",
permissions => {
check => ['perm', '/storage', ['Datastore.Allocate']],
check => ['perm', '/storage', ['Datastore.Allocate']],
},
parameters => {
additionalProperties => 0,
properties => {
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',
},
},
additionalProperties => 0,
properties => {
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',
},
},
},
returns => {
type => 'array',
items => {
type => "object",
properties => {
target => {
description => "The iSCSI target name.",
type => 'string',
},
portal => {
description => "The iSCSI portal name.",
type => 'string',
},
},
},
type => 'array',
items => {
type => "object",
properties => {
target => {
description => "The iSCSI target name.",
type => 'string',
},
portal => {
description => "The iSCSI portal name.",
type => 'string',
},
},
},
},
code => sub {
my ($param) = @_;
my ($param) = @_;
my $res = PVE::Storage::scan_iscsi($param->{portal});
my $res = PVE::Storage::scan_iscsi($param->{portal});
my $data = [];
foreach my $k (sort keys %$res) {
push @$data, { target => $k, portal => join(',', @{$res->{$k}}) };
}
my $data = [];
foreach my $k (sort keys %$res) {
push @$data, { target => $k, portal => join(',', @{ $res->{$k} }) };
}
return $data;
}});
return $data;
},
});
__PACKAGE__->register_method({
name => 'lvmscan',
@ -346,32 +356,33 @@ __PACKAGE__->register_method({
protected => 1,
proxyto => "node",
permissions => {
check => ['perm', '/storage', ['Datastore.Allocate']],
check => ['perm', '/storage', ['Datastore.Allocate']],
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
},
returns => {
type => 'array',
items => {
type => "object",
properties => {
vg => {
description => "The LVM logical volume group name.",
type => 'string',
},
},
},
type => 'array',
items => {
type => "object",
properties => {
vg => {
description => "The LVM logical volume group name.",
type => 'string',
},
},
},
},
code => sub {
my ($param) = @_;
my ($param) = @_;
my $res = PVE::Storage::LVMPlugin::lvm_vgs();
return PVE::RESTHandler::hash_to_array($res, 'vg');
}});
my $res = PVE::Storage::LVMPlugin::lvm_vgs();
return PVE::RESTHandler::hash_to_array($res, 'vg');
},
});
__PACKAGE__->register_method({
name => 'lvmthinscan',
@ -381,36 +392,37 @@ __PACKAGE__->register_method({
protected => 1,
proxyto => "node",
permissions => {
check => ['perm', '/storage', ['Datastore.Allocate']],
check => ['perm', '/storage', ['Datastore.Allocate']],
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vg => {
type => 'string',
pattern => '[a-zA-Z0-9\.\+\_][a-zA-Z0-9\.\+\_\-]+', # see lvm(8) manpage
maxLength => 100,
},
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vg => {
type => 'string',
pattern => '[a-zA-Z0-9\.\+\_][a-zA-Z0-9\.\+\_\-]+', # see lvm(8) manpage
maxLength => 100,
},
},
},
returns => {
type => 'array',
items => {
type => "object",
properties => {
lv => {
description => "The LVM Thin Pool name (LVM logical volume).",
type => 'string',
},
},
},
type => 'array',
items => {
type => "object",
properties => {
lv => {
description => "The LVM Thin Pool name (LVM logical volume).",
type => 'string',
},
},
},
},
code => sub {
my ($param) = @_;
my ($param) = @_;
return PVE::Storage::LvmThinPlugin::list_thinpools($param->{vg});
}});
return PVE::Storage::LvmThinPlugin::list_thinpools($param->{vg});
},
});
__PACKAGE__->register_method({
name => 'zfsscan',
@ -420,30 +432,31 @@ __PACKAGE__->register_method({
protected => 1,
proxyto => "node",
permissions => {
check => ['perm', '/storage', ['Datastore.Allocate']],
check => ['perm', '/storage', ['Datastore.Allocate']],
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
},
returns => {
type => 'array',
items => {
type => "object",
properties => {
pool => {
description => "ZFS pool name.",
type => 'string',
},
},
},
type => 'array',
items => {
type => "object",
properties => {
pool => {
description => "ZFS pool name.",
type => 'string',
},
},
},
},
code => sub {
my ($param) = @_;
my ($param) = @_;
return PVE::Storage::scan_zfs();
}});
return PVE::Storage::scan_zfs();
},
});
1;

File diff suppressed because it is too large Load Diff