There is a udev bug [0] which can ultimately lead to the udev database for certain devices not being actively updated. Determining whether a disk is used or not in get_disks() (in part) relies upon lsblk, which queries the udev database. Ensure the information is updated by manually calling 'udevadm trigger' for the changed devices. It's most important for the 'directory' API path, as mounting depends on the '/dev/disk/by-uuid'-symlink to be generated. [0]: https://github.com/systemd/systemd/issues/18525 Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
322 lines
7.6 KiB
Perl
322 lines
7.6 KiB
Perl
package PVE::API2::Disks;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use File::Basename;
|
|
use HTTP::Status qw(:constants);
|
|
|
|
use PVE::Diskmanage;
|
|
use PVE::JSONSchema qw(get_standard_option);
|
|
use PVE::SafeSyslog;
|
|
use PVE::Tools qw(run_command);
|
|
|
|
use PVE::API2::Disks::Directory;
|
|
use PVE::API2::Disks::LVM;
|
|
use PVE::API2::Disks::LVMThin;
|
|
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::LVMThin",
|
|
path => 'lvmthin',
|
|
});
|
|
|
|
__PACKAGE__->register_method ({
|
|
subclass => "PVE::API2::Disks::Directory",
|
|
path => 'directory',
|
|
});
|
|
|
|
__PACKAGE__->register_method ({
|
|
subclass => "PVE::API2::Disks::ZFS",
|
|
path => 'zfs',
|
|
});
|
|
|
|
__PACKAGE__->register_method ({
|
|
name => 'index',
|
|
path => '',
|
|
method => 'GET',
|
|
proxyto => 'node',
|
|
permissions => { user => 'all' },
|
|
description => "Node index.",
|
|
parameters => {
|
|
additionalProperties => 0,
|
|
properties => {
|
|
node => get_standard_option('pve-node'),
|
|
},
|
|
},
|
|
returns => {
|
|
type => 'array',
|
|
items => {
|
|
type => "object",
|
|
properties => {},
|
|
},
|
|
links => [ { rel => 'child', href => "{name}" } ],
|
|
},
|
|
code => sub {
|
|
my ($param) = @_;
|
|
|
|
my $result = [
|
|
{ name => 'list' },
|
|
{ name => 'initgpt' },
|
|
{ name => 'smart' },
|
|
{ name => 'lvm' },
|
|
{ name => 'lvmthin' },
|
|
{ name => 'directory' },
|
|
{ name => 'wipedisk' },
|
|
{ name => 'zfs' },
|
|
];
|
|
|
|
return $result;
|
|
}});
|
|
|
|
__PACKAGE__->register_method ({
|
|
name => 'list',
|
|
path => 'list',
|
|
method => 'GET',
|
|
description => "List local disks.",
|
|
protected => 1,
|
|
proxyto => 'node',
|
|
permissions => {
|
|
check => ['or',
|
|
['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1],
|
|
['perm', '/nodes/{node}', ['Sys.Audit', 'Datastore.Audit'], any => 1],
|
|
],
|
|
},
|
|
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,
|
|
},
|
|
},
|
|
},
|
|
returns => {
|
|
type => 'array',
|
|
items => {
|
|
type => 'object',
|
|
properties => {
|
|
devpath => {
|
|
type => 'string',
|
|
description => 'The device path',
|
|
},
|
|
used => { type => 'string', optional => 1 },
|
|
gpt => { type => 'boolean' },
|
|
size => { type => 'integer'},
|
|
osdid => { 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 $skipsmart = $param->{skipsmart} // 0;
|
|
my $include_partitions = $param->{'include-partitions'} // 0;
|
|
|
|
my $disks = PVE::Diskmanage::get_disks(
|
|
undef,
|
|
$skipsmart,
|
|
$include_partitions
|
|
);
|
|
|
|
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;
|
|
}});
|
|
|
|
__PACKAGE__->register_method ({
|
|
name => 'smart',
|
|
path => 'smart',
|
|
method => 'GET',
|
|
description => "Get SMART Health of a disk.",
|
|
protected => 1,
|
|
proxyto => "node",
|
|
permissions => {
|
|
check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1],
|
|
},
|
|
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,
|
|
},
|
|
},
|
|
},
|
|
returns => {
|
|
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 $disk = PVE::Diskmanage::verify_blockdev_path($param->{disk});
|
|
|
|
my $result = PVE::Diskmanage::get_smart_data($disk, $param->{healthonly});
|
|
|
|
$result->{health} = 'UNKNOWN' if !defined $result->{health};
|
|
$result = { health => $result->{health} } if $param->{healthonly};
|
|
|
|
return $result;
|
|
}});
|
|
|
|
__PACKAGE__->register_method ({
|
|
name => 'initgpt',
|
|
path => 'initgpt',
|
|
method => 'POST',
|
|
description => "Initialize Disk with GPT",
|
|
protected => 1,
|
|
proxyto => "node",
|
|
permissions => {
|
|
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,
|
|
},
|
|
},
|
|
},
|
|
returns => { type => 'string' },
|
|
code => sub {
|
|
my ($param) = @_;
|
|
|
|
my $disk = PVE::Diskmanage::verify_blockdev_path($param->{disk});
|
|
|
|
my $rpcenv = PVE::RPCEnvironment::get();
|
|
|
|
my $authuser = $rpcenv->get_user();
|
|
|
|
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);
|
|
}});
|
|
|
|
__PACKAGE__->register_method ({
|
|
name => 'wipe_disk',
|
|
path => 'wipedisk',
|
|
method => 'PUT',
|
|
description => "Wipe a disk or partition.",
|
|
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\/]+$',
|
|
},
|
|
},
|
|
},
|
|
returns => { type => 'string' },
|
|
code => sub {
|
|
my ($param) = @_;
|
|
|
|
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 $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 $worker = sub {
|
|
PVE::Diskmanage::wipe_blockdev($disk);
|
|
|
|
# FIXME: Remove once we depend on systemd >= v249.
|
|
# Work around udev bug https://github.com/systemd/systemd/issues/18525 to ensure the
|
|
# udev database is updated.
|
|
eval { run_command(['udevadm', 'trigger', $disk]); };
|
|
warn $@ if $@;
|
|
};
|
|
|
|
my $basename = basename($disk); # avoid '/' in the ID
|
|
|
|
return $rpcenv->fork_worker('wipedisk', $basename, $authuser, $worker);
|
|
}});
|
|
|
|
1;
|