Files
pve-storage/PVE/API2/Disks.pm
Fabian Ebner 26082b7daf diskmanage: add helper for udev workaround
to avoid duplication. Current callers pass along at least one device,
but anticipate future callers that might call with the empty list. Do
nothing in that case, rather than triggering everything.

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
2021-11-10 12:35:25 +01:00

318 lines
7.4 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 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);
}});
__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);
PVE::Diskmanage::udevadm_trigger($disk);
};
my $basename = basename($disk); # avoid '/' in the ID
return $rpcenv->fork_worker('wipedisk', $basename, $authuser, $worker);
}});
1;