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;