Files
pve-storage/PVE/API2/Disks/LVM.pm
Aaron Lauterer bd485fd4aa disks: allow add_storage for already configured local storage
One of the smaller annoyances, especially for less experienced users, is
the fact, that when creating a local storage (ZFS, LVM (thin), dir) in a
cluster, one can only leave the "Add Storage" option enabled the first
time.

On any following node, this option needed to be disabled and the new
node manually added to the list of nodes for that storage.

This patch changes the behavior. If a storage of the same name already
exists, it will verify that necessary parameters match the already
existing one.
Then, if the 'nodes' parameter is set, it adds the current node and
updates the storage config.
In case there is no nodes list, nothing else needs to be done, and the
GUI will stop showing the question mark for the configured, but until
then, not existing local storage.

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>
2022-09-13 10:05:20 +02:00

282 lines
6.5 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);
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}) {
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};
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}) {
PVE::API2::Storage::Config->create_or_update(
$name,
$node,
$storage_params,
$verify_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;