If a storage of that type and name already exists (LVM, zpool, ...) but we do not have a Proxmox VE Storage config for it, it is possible that the creation will fail midway due to checks done by the underlying storage layer itself. This in turn can lead to disks that are already partitioned. Users would need to clean this up themselves. By adding checks early on, not only checking against the PVE storage config, but against the actual storage type itself, we can die early enough, before we touch any disk. For ZFS, the logic to gather pool data is moved into its own function to be called from the index API endpoint and the check in the create endpoint. Signed-off-by: Aaron Lauterer <a.lauterer@proxmox.com> Reviewed-by: Dominik Csapak <d.csapak@proxmox.com> Tested-by: Dominik Csapak <d.csapak@proxmox.com>
267 lines
6.3 KiB
Perl
267 lines
6.3 KiB
Perl
package PVE::API2::Disks::LVM;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use PVE::Storage::LVMPlugin;
|
|
use PVE::Diskmanage;
|
|
use PVE::JSONSchema qw(get_standard_option);
|
|
use PVE::API2::Storage::Config;
|
|
use PVE::Tools qw(lock_file run_command);
|
|
|
|
use PVE::RPCEnvironment;
|
|
use PVE::RESTHandler;
|
|
|
|
use base qw(PVE::RESTHandler);
|
|
|
|
__PACKAGE__->register_method ({
|
|
name => 'index',
|
|
path => '',
|
|
method => 'GET',
|
|
proxyto => 'node',
|
|
protected => 1,
|
|
permissions => {
|
|
check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1],
|
|
},
|
|
description => "List LVM Volume Groups",
|
|
parameters => {
|
|
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',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
code => sub {
|
|
my ($param) = @_;
|
|
|
|
my $result = [];
|
|
|
|
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;
|
|
}
|
|
|
|
return {
|
|
leaf => 0,
|
|
children => $result,
|
|
};
|
|
}});
|
|
|
|
__PACKAGE__->register_method ({
|
|
name => 'create',
|
|
path => '',
|
|
method => 'POST',
|
|
proxyto => 'node',
|
|
protected => 1,
|
|
permissions => {
|
|
check => ['perm', '/', ['Sys.Modify', 'Datastore.Allocate']],
|
|
},
|
|
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,
|
|
},
|
|
},
|
|
},
|
|
returns => { type => 'string' },
|
|
code => sub {
|
|
my ($param) = @_;
|
|
|
|
my $rpcenv = PVE::RPCEnvironment::get();
|
|
my $user = $rpcenv->get_user();
|
|
|
|
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);
|
|
PVE::Storage::assert_sid_unused($name) if $param->{add_storage};
|
|
|
|
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 $@;
|
|
}
|
|
|
|
PVE::Storage::LVMPlugin::lvm_create_volume_group($dev, $name);
|
|
|
|
PVE::Diskmanage::udevadm_trigger($dev);
|
|
|
|
if ($param->{add_storage}) {
|
|
my $storage_params = {
|
|
type => 'lvm',
|
|
vgname => $name,
|
|
storage => $name,
|
|
content => 'rootdir,images',
|
|
shared => 0,
|
|
nodes => $node,
|
|
};
|
|
|
|
PVE::API2::Storage::Config->create($storage_params);
|
|
}
|
|
});
|
|
};
|
|
|
|
return $rpcenv->fork_worker('lvmcreate', $name, $user, $worker);
|
|
}});
|
|
|
|
__PACKAGE__->register_method ({
|
|
name => 'delete',
|
|
path => '{name}',
|
|
method => 'DELETE',
|
|
proxyto => 'node',
|
|
protected => 1,
|
|
permissions => {
|
|
check => ['perm', '/', ['Sys.Modify', 'Datastore.Allocate']],
|
|
},
|
|
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,
|
|
},
|
|
},
|
|
},
|
|
returns => { type => 'string' },
|
|
code => sub {
|
|
my ($param) = @_;
|
|
|
|
my $rpcenv = PVE::RPCEnvironment::get();
|
|
my $user = $rpcenv->get_user();
|
|
|
|
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};
|
|
|
|
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 $@;
|
|
}
|
|
|
|
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;
|
|
});
|
|
};
|
|
|
|
return $rpcenv->fork_worker('lvmremove', $name, $user, $worker);
|
|
}});
|
|
|
|
1;
|