From 793d720cd233453e26162bf89c20a08567543ead Mon Sep 17 00:00:00 2001 From: Dominik Csapak Date: Mon, 30 Jul 2018 10:26:06 +0200 Subject: [PATCH] add API for add Directory storage creates/lists systemd mount units for /mnt/pve/.* filetypes allowed are ext4 and xfs for now mount with /dev/disk/by-uuid Signed-off-by: Dominik Csapak --- PVE/API2/Disks.pm | 7 + PVE/API2/Disks/Directory.pm | 290 ++++++++++++++++++++++++++++++++++++ PVE/API2/Disks/Makefile | 3 +- 3 files changed, 299 insertions(+), 1 deletion(-) create mode 100644 PVE/API2/Disks/Directory.pm diff --git a/PVE/API2/Disks.pm b/PVE/API2/Disks.pm index c49d439..b501c60 100644 --- a/PVE/API2/Disks.pm +++ b/PVE/API2/Disks.pm @@ -10,6 +10,7 @@ use PVE::JSONSchema qw(get_standard_option); use PVE::API2::Disks::LVM; use PVE::API2::Disks::LVMThin; +use PVE::API2::Disks::Directory; use PVE::RESTHandler; @@ -25,6 +26,11 @@ __PACKAGE__->register_method ({ path => 'lvmthin', }); +__PACKAGE__->register_method ({ + subclass => "PVE::API2::Disks::Directory", + path => 'directory', +}); + __PACKAGE__->register_method ({ name => 'index', path => '', @@ -55,6 +61,7 @@ __PACKAGE__->register_method ({ { name => 'smart' }, { name => 'lvm' }, { name => 'lvmthin' }, + { name => 'directory' }, ]; return $result; diff --git a/PVE/API2/Disks/Directory.pm b/PVE/API2/Disks/Directory.pm new file mode 100644 index 0000000..f554d6c --- /dev/null +++ b/PVE/API2/Disks/Directory.pm @@ -0,0 +1,290 @@ +package PVE::API2::Disks::Directory; + +use strict; +use warnings; + +use PVE::Diskmanage; +use PVE::JSONSchema qw(get_standard_option); +use PVE::API2::Storage::Config; +use PVE::Tools qw(run_command trim file_set_contents file_get_contents dir_glob_foreach lock_file); + +use PVE::RPCEnvironment; +use PVE::RESTHandler; + +use base qw(PVE::RESTHandler); + +my $SGDISK = '/sbin/sgdisk'; +my $MKFS = '/sbin/mkfs'; +my $BLKID = '/sbin/blkid'; + +my $read_ini = sub { + my ($filename) = @_; + + my $content = file_get_contents($filename); + my @lines = split /\n/, $content; + + my $result = {}; + my $section; + + foreach my $line (@lines) { + $line = trim($line); + if ($line =~ m/^\[([^\]]+)\]/) { + $section = $1; + if (!defined($result->{$section})) { + $result->{$section} = {}; + } + } elsif ($line =~ m/^(.*?)=(.*)$/) { + my ($key, $val) = ($1, $2); + if (!$section) { + warn "key value pair found without section, skipping\n"; + next; + } + + if ($result->{$section}->{$key}) { + # make duplicate properties to arrays to keep the order + my $prop = $result->{$section}->{$key}; + if (ref($prop) eq 'ARRAY') { + push @$prop, $val; + } else { + $result->{$section}->{$key} = [$prop, $val]; + } + } else { + $result->{$section}->{$key} = $val; + } + } + # ignore everything else + } + + return $result; +}; + +my $write_ini = sub { + my ($ini, $filename) = @_; + + my $content = ""; + + foreach my $sname (sort keys %$ini) { + my $section = $ini->{$sname}; + + $content .= "[$sname]\n"; + + foreach my $pname (sort keys %$section) { + my $prop = $section->{$pname}; + + if (!ref($prop)) { + $content .= "$pname=$prop\n"; + } elsif (ref($prop) eq 'ARRAY') { + foreach my $val (@$prop) { + $content .= "$pname=$val\n"; + } + } else { + die "invalid property '$pname'\n"; + } + } + $content .= "\n"; + } + + file_set_contents($filename, $content); +}; + +__PACKAGE__->register_method ({ + name => 'index', + path => '', + method => 'GET', + proxyto => 'node', + protected => 1, + permissions => { + check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1], + }, + description => "PVE Managed Directory storages.", + parameters => { + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, + }, + returns => { + type => 'array', + items => { + type => 'object', + properties => { + unitfile => { + type => 'string', + description => 'The path of the mount unit'., + }, + path => { + type => 'string', + description => 'The mount path.', + }, + device => { + type => 'string', + description => 'The mounted device.', + }, + type => { + type => 'string', + description => 'The filesystem type.', + }, + options => { + type => 'string', + description => 'The mount options.', + }, + }, + }, + }, + code => sub { + my ($param) = @_; + + my $result = []; + + dir_glob_foreach('/etc/systemd/system', '^mnt-pve-(.+)\.mount$', sub { + my ($filename, $storid) = @_; + + my $unitfile = "/etc/systemd/system/$filename"; + my $unit = $read_ini->($unitfile); + + push @$result, { + unitfile => $unitfile, + path => "/mnt/pve/$storid", + device => $unit->{'Mount'}->{'What'}, + type => $unit->{'Mount'}->{'Type'}, + options => $unit->{'Mount'}->{'Options'}, + }; + }); + + return $result; + }}); + +__PACKAGE__->register_method ({ + name => 'create', + path => '', + method => 'POST', + proxyto => 'node', + protected => 1, + permissions => { + check => ['perm', '/', ['Sys.Modify', 'Datastore.Allocate']], + }, + description => "Create a Filesystem on an unused disk. Will be mounted under '/mnt/pve/NAME'.", + 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 thinpool on.', + }, + add_storage => { + description => "Configure storage using the directory.", + type => 'boolean', + optional => 1, + default => 0, + }, + filesystem => { + description => "The desired filesystem.", + type => 'string', + enum => ['ext4', 'xfs'], + optional => 1, + default => 'ext4', + }, + }, + }, + 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}; + my $type = $param->{filesystem} // 'ext4'; + + $dev = PVE::Diskmanage::verify_blockdev_path($dev); + die "device $dev is already in use\n" if PVE::Diskmanage::disk_is_used($dev); + + my $cfg = PVE::Storage::config(); + + if (my $scfg = PVE::Storage::storage_config($cfg, $name, 1)) { + die "storage ID '$name' already defined\n"; + } + + my $worker = sub { + my $path = "/mnt/pve/$name"; + my $mountunitname = "mnt-pve-$name.mount"; + my $mountunitpath = "/etc/systemd/system/$mountunitname"; + + lock_file('/run/lock/pve-diskmanage.lck', undef, sub { + # create partition + my $cmd = [$SGDISK, '-n1', '-t1:8300', $dev]; + print "# ", join(' ', @$cmd), "\n"; + run_command($cmd); + + my $part = "${dev}1"; + + # create filesystem + $cmd = [$MKFS, '-t', $type, $part]; + print "# ", join(' ', @$cmd), "\n"; + run_command($cmd); + + # create systemd mount unit and enable & start it + my $ini = { + 'Unit' => { + 'Description' => "Mount storage '$name' under /mnt/pve", + }, + 'Install' => { + 'WantedBy' => 'multi-user.target', + }, + }; + + my $uuid_path; + my $uuid; + + $cmd = [$BLKID, $part, '-o', 'export']; + print "# ", join(' ', @$cmd), "\n"; + run_command($cmd, outfunc => sub { + my ($line) = @_; + + if ($line =~ m/^UUID=(.*)$/) { + $uuid = $1; + $uuid_path = "/dev/disk/by-uuid/$uuid"; + } + }); + + die "could not get UUID of device '$part'\n" if !$uuid; + + $ini->{'Mount'} = { + 'What' => $uuid_path, + 'Where' => $path, + 'Type' => $type, + 'Options' => 'defaults', + }; + + $write_ini->($ini, $mountunitpath); + + run_command(['systemctl', 'daemon-reload']); + run_command(['systemctl', 'enable', $mountunitname]); + run_command(['systemctl', 'start', $mountunitname]); + + if ($param->{add_storage}) { + my $storage_params = { + type => 'dir', + storage => $name, + content => 'rootdir,images,iso,backup,vztmpl', + is_mountpoint => 1, + path => $path, + nodes => $node, + }; + + PVE::API2::Storage::Config->create($storage_params); + } + }); + + die $@ if $@; + }; + + + return $rpcenv->fork_worker('dircreate', $name, $user, $worker); + }}); + +1; diff --git a/PVE/API2/Disks/Makefile b/PVE/API2/Disks/Makefile index bc2af8f..ccbe004 100644 --- a/PVE/API2/Disks/Makefile +++ b/PVE/API2/Disks/Makefile @@ -1,6 +1,7 @@ SOURCES= LVM.pm\ - LVMThin.pm + LVMThin.pm\ + Directory.pm .PHONY: install install: -- 2.39.2