From 39c85db819dc564e89270f6f6d15dbce79d0540b Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Thu, 26 Jan 2012 12:42:01 +0100 Subject: [PATCH 1/1] add pool API --- PVE/API2/AccessControl.pm | 6 + PVE/API2/Group.pm | 4 +- PVE/API2/Makefile | 13 +- PVE/API2/Pool.pm | 275 ++++++++++++++++++++++++++++++++++++++ PVE/AccessControl.pm | 89 +++++++----- PVE/RPCEnvironment.pm | 6 +- pveum | 5 + 7 files changed, 355 insertions(+), 43 deletions(-) create mode 100644 PVE/API2/Pool.pm diff --git a/PVE/API2/AccessControl.pm b/PVE/API2/AccessControl.pm index a407648..913bdd8 100644 --- a/PVE/API2/AccessControl.pm +++ b/PVE/API2/AccessControl.pm @@ -15,6 +15,7 @@ use PVE::API2::User; use PVE::API2::Group; use PVE::API2::Role; use PVE::API2::ACL; +use PVE::API2::Pool; use base qw(PVE::RESTHandler); @@ -43,6 +44,11 @@ __PACKAGE__->register_method ({ path => 'domains', }); +__PACKAGE__->register_method ({ + subclass => "PVE::API2::Pool", + path => 'pools', +}); + __PACKAGE__->register_method ({ name => 'index', path => '', diff --git a/PVE/API2/Group.pm b/PVE/API2/Group.pm index 883c45d..373e8e2 100644 --- a/PVE/API2/Group.pm +++ b/PVE/API2/Group.pm @@ -148,10 +148,10 @@ __PACKAGE__->register_method ({ die "group '$group' does not exist\n" if !$data; - $data->{comment} = $param->{comment} if $param->{comment}; + $data->{comment} = $param->{comment} if defined($param->{comment}); cfs_write_file("user.cfg", $usercfg); - }, "create group failed"); + }, "update group failed"); return undef; }}); diff --git a/PVE/API2/Makefile b/PVE/API2/Makefile index 64aa8d3..c9d44df 100644 --- a/PVE/API2/Makefile +++ b/PVE/API2/Makefile @@ -1,10 +1,11 @@ -API2_SOURCES= \ - AccessControl.pm \ - Domains.pm \ - ACL.pm \ - Role.pm \ - Group.pm \ +API2_SOURCES= \ + AccessControl.pm \ + Pool.pm \ + Domains.pm \ + ACL.pm \ + Role.pm \ + Group.pm \ User.pm .PHONY: install diff --git a/PVE/API2/Pool.pm b/PVE/API2/Pool.pm new file mode 100644 index 0000000..f224777 --- /dev/null +++ b/PVE/API2/Pool.pm @@ -0,0 +1,275 @@ +package PVE::API2::Pool; + +use strict; +use warnings; +use PVE::Exception qw(raise_param_exc); +use PVE::Cluster qw (cfs_read_file cfs_write_file); +use PVE::AccessControl; + +use PVE::SafeSyslog; + +use Data::Dumper; # fixme: remove + +use PVE::RESTHandler; + +use base qw(PVE::RESTHandler); + +my $extract_pool_data = sub { + my ($data, $full) = @_; + + my $res = {}; + + $res->{comment} = $data->{comment} if defined($data->{comment}); + + return $res if !$full; + + $res->{vms} = $data->{vms} ? [ keys %{$data->{vms}} ] : []; + + $res->{storages} = $data->{storage} ? [ keys %{$data->{storage}} ] : []; + + return $res; +}; + +__PACKAGE__->register_method ({ + name => 'index', + path => '', + method => 'GET', + description => "Pool index.", + permissions => { + user => 'all', + }, + parameters => { + additionalProperties => 0, + properties => {}, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => { + poolid => { type => 'string' }, + }, + }, + links => [ { rel => 'child', href => "{poolid}" } ], + }, + code => sub { + my ($param) = @_; + + my $rpcenv = PVE::RPCEnvironment::get(); + + my $res = []; + + my $usercfg = $rpcenv->{user_cfg}; + + foreach my $poolpath (keys %{$usercfg->{pools}}) { + my $entry = &$extract_pool_data($usercfg->{pools}->{$poolpath}); + $entry->{poolid} = $poolpath; + push @$res, $entry; + } + + return $res; + }}); + +__PACKAGE__->register_method ({ + name => 'create_pool', + protected => 1, + path => '', + method => 'POST', + permissions => { + check => ['perm', '/access', ['Sys.Modify']], + }, + description => "Create new pool.", + parameters => { + additionalProperties => 0, + properties => { + poolid => { type => 'string', format => 'pve-poolid' }, + comment => { type => 'string', optional => 1 }, + }, + }, + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + PVE::AccessControl::lock_user_config( + sub { + + my $usercfg = cfs_read_file("user.cfg"); + + my $pool = $param->{poolid}; + + die "pool '$pool' already exists\n" + if $usercfg->{pools}->{$pool}; + + $usercfg->{pools}->{$pool} = { vms => {}, storage => {} }; + + $usercfg->{pools}->{$pool}->{comment} = $param->{comment} if $param->{comment}; + + cfs_write_file("user.cfg", $usercfg); + }, "create pool failed"); + + return undef; + }}); + +__PACKAGE__->register_method ({ + name => 'update_pool', + protected => 1, + path => '{poolid}', + method => 'PUT', + permissions => { + check => ['perm', '/access', ['Sys.Modify']], + }, + description => "Update pool data.", + parameters => { + additionalProperties => 0, + properties => { + poolid => { type => 'string', format => 'pve-poolid' }, + comment => { type => 'string', optional => 1 }, + vms => { + description => "List of virtual machines.", + type => 'string', format => 'pve-vmid-list', + optional => 1, + }, + storage => { + description => "List of storage IDs.", + type => 'string', format => 'pve-storage-id-list', + optional => 1, + }, + delete => { + description => "Remove vms/storage (instead of adding it).", + type => 'boolean', + optional => 1, + }, + }, + }, + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + PVE::AccessControl::lock_user_config( + sub { + + my $usercfg = cfs_read_file("user.cfg"); + + my $pool = $param->{poolid}; + + my $data = $usercfg->{pools}->{$pool}; + + die "pool '$pool' does not exist\n" + if !$data; + + $data->{comment} = $param->{comment} if defined($param->{comment}); + + if (defined($param->{vms})) { + foreach my $vmid (PVE::Tools::split_list($param->{vms})) { + if ($param->{delete}) { + die "VM $vmid is not a pool member\n" + if !$data->{vms}->{$vmid}; + delete $data->{vms}->{$vmid}; + delete $usercfg->{vms}->{$vmid}; + } else { + die "VM $vmid is already a pool member\n" + if $data->{vms}->{$vmid}; + die "VM $vmid belongs to pool '$usercfg->{vms}->{$vmid}'\n" + if $usercfg->{vms}->{$vmid}; + + $data->{vms}->{$vmid} = 1; + $usercfg->{vms}->{$vmid} = 1; + } + } + } + + if (defined($param->{storage})) { + foreach my $storeid (PVE::Tools::split_list($param->{storage})) { + if ($param->{delete}) { + die "Storage '$storeid' is not a pool member\n" + if !$data->{storage}->{$storeid}; + delete $data->{storage}->{$storeid}; + } else { + die "Storage '$storeid' is already a pool member\n" + if $data->{storage}->{$storeid}; + + $data->{storage}->{$storeid} = 1; + } + } + } + + cfs_write_file("user.cfg", $usercfg); + }, "update pools failed"); + + return undef; + }}); + +# fixme: return format! +__PACKAGE__->register_method ({ + name => 'read_pool', + path => '{poolid}', + method => 'GET', + permissions => { + check => ['perm', '/access', ['Sys.Audit']], + }, + description => "Get group configuration.", + parameters => { + additionalProperties => 0, + properties => { + poolid => {type => 'string', format => 'pve-poolid' }, + }, + }, + returns => {}, + code => sub { + my ($param) = @_; + + my $usercfg = cfs_read_file("user.cfg"); + + my $pool = $param->{poolid}; + + my $data = $usercfg->{pools}->{$pool}; + + die "pool '$pool' does not exist\n" + if !$data; + + return &$extract_pool_data($data, 1); + }}); + + +__PACKAGE__->register_method ({ + name => 'delete_pool', + protected => 1, + path => '{poolid}', + method => 'DELETE', + permissions => { + check => ['perm', '/access', ['Sys.Modify']], + }, + description => "Delete group.", + parameters => { + additionalProperties => 0, + properties => { + poolid => { type => 'string', format => 'pve-poolid' }, + } + }, + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + PVE::AccessControl::lock_user_config( + sub { + + my $usercfg = cfs_read_file("user.cfg"); + + my $pool = $param->{poolid}; + + my $data = $usercfg->{pools}->{$pool}; + + die "pool '$pool' does not exist\n" + if !$data; + + delete ($usercfg->{pools}->{$pool}); + + PVE::AccessControl::delete_pool_acl($pool, $usercfg); + + cfs_write_file("user.cfg", $usercfg); + }, "delete pool failed"); + + return undef; + }}); + +1; diff --git a/PVE/AccessControl.pm b/PVE/AccessControl.pm index 331823e..0dce3e6 100644 --- a/PVE/AccessControl.pm +++ b/PVE/AccessControl.pm @@ -514,8 +514,8 @@ sub delete_user_acl { delete ($usercfg->{acl}->{$acl}->{users}->{$username}) if $usercfg->{acl}->{$acl}->{users}->{$username}; } - } + sub delete_group_acl { my ($group, $usercfg) = @_; @@ -525,7 +525,18 @@ sub delete_group_acl { delete ($usercfg->{acl}->{$acl}->{groups}->{$group}) if $usercfg->{acl}->{$acl}->{groups}->{$group}; } +} + +sub delete_pool_acl { + + my ($pool, $usercfg) = @_; + my $path = "/pool/$pool"; + + foreach my $aclpath (keys %{$usercfg->{acl}}) { + delete ($usercfg->{acl}->{$aclpath}) + if $usercfg->{acl}->{$aclpath} eq 'path'; + } } # we automatically create some predefined roles by splitting privs @@ -749,6 +760,20 @@ sub verify_rolename { return $rolename; } +PVE::JSONSchema::register_format('pve-poolid', \&verify_groupname); +sub verify_poolname { + my ($poolname, $noerr) = @_; + + if ($poolname !~ m/^[A-Za-z0-9\.\-_]+$/) { + + die "pool name '$poolname' contains invalid characters\n" if !$noerr; + + return undef; + } + + return $poolname; +} + PVE::JSONSchema::register_format('pve-priv', \&verify_privname); sub verify_privname { my ($priv, $noerr) = @_; @@ -915,42 +940,42 @@ sub parse_user_config { warn "user config - ignore invalid path in acl '$pathtxt'\n"; } } elsif ($et eq 'pool') { - my ($pathtxt, $comment, $vmlist, $storelist) = @data; - - if (my $path = normalize_path($pathtxt)) { - # make sure to add the pool (even if there are no members) - $cfg->{pools}->{$path} = { vms => {}, storage => {} } if !$cfg->{pools}->{$path}; + my ($pool, $comment, $vmlist, $storelist) = @data; - $cfg->{pools}->{$path}->{comment} = PVE::Tools::decode_text($comment) if $comment; + if (!verify_poolname($pool, 1)) { + warn "user config - ignore pool '$pool' - invalid characters in pool name\n"; + next; + } - foreach my $vmid (split_list($vmlist)) { - if ($vmid !~ m/^\d+$/) { - warn "user config - ignore invalid vmid '$vmid' in pool '$path'\n"; - next; - } - $vmid = int($vmid); + # make sure to add the pool (even if there are no members) + $cfg->{pools}->{$pool} = { vms => {}, storage => {} } if !$cfg->{pools}->{$pool}; - if ($cfg->{vms}->{$vmid}) { - warn "user config - ignore duplicate vmid '$vmid' in pool '$path'\n"; - next; - } + $cfg->{pools}->{$pool}->{comment} = PVE::Tools::decode_text($comment) if $comment; - $cfg->{pools}->{$path}->{vms}->{$vmid} = 1; - - # record vmid ==> pool relation - $cfg->{vms}->{$vmid} = $path; + foreach my $vmid (split_list($vmlist)) { + if ($vmid !~ m/^\d+$/) { + warn "user config - ignore invalid vmid '$vmid' in pool '$pool'\n"; + next; } + $vmid = int($vmid); - foreach my $storeid (split_list($storelist)) { - if ($storeid !~ m/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i) { - warn "user config - ignore invalid storage '$storeid' in pool '$path'\n"; - next; - } - $cfg->{pools}->{$path}->{storage}->{$storeid} = 1; + if ($cfg->{vms}->{$vmid}) { + warn "user config - ignore duplicate vmid '$vmid' in pool '$pool'\n"; + next; } - } else { - warn "user config - ignore invalid path in pool'$pathtxt'\n"; + $cfg->{pools}->{$pool}->{vms}->{$vmid} = 1; + + # record vmid ==> pool relation + $cfg->{vms}->{$vmid} = $pool; + } + + foreach my $storeid (split_list($storelist)) { + if ($storeid !~ m/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i) { + warn "user config - ignore invalid storage '$storeid' in pool '$pool'\n"; + next; + } + $cfg->{pools}->{$pool}->{storage}->{$storeid} = 1; } } else { warn "user config - ignore config line: $line\n"; @@ -1174,12 +1199,12 @@ sub write_user_config { $data .= "\n"; - foreach my $path (keys %{$cfg->{pools}}) { - my $d = $cfg->{pools}->{$path}; + foreach my $pool (keys %{$cfg->{pools}}) { + my $d = $cfg->{pools}->{$pool}; my $vmlist = join (',', keys %{$d->{vms}}); my $storelist = join (',', keys %{$d->{storage}}); my $comment = $d->{comment} ? PVE::Tools::encode_text($d->{comment}) : ''; - $data .= "pool:$path:$comment:$vmlist:$storelist:\n"; + $data .= "pool:$pool:$comment:$vmlist:$storelist:\n"; } $data .= "\n"; diff --git a/PVE/RPCEnvironment.pm b/PVE/RPCEnvironment.pm index 0b8147e..e6651d0 100644 --- a/PVE/RPCEnvironment.pm +++ b/PVE/RPCEnvironment.pm @@ -106,9 +106,9 @@ my $compile_acl_path = sub { if (!$data->{poolroles}) { $data->{poolroles} = {}; - foreach my $poolpath (keys %{$cfg->{pools}}) { - my $d = $cfg->{pools}->{$poolpath}; - my @ra = PVE::AccessControl::roles($cfg, $user, "/pool$poolpath"); # pool roles + foreach my $pool (keys %{$cfg->{pools}}) { + my $d = $cfg->{pools}->{$pool}; + my @ra = PVE::AccessControl::roles($cfg, $user, "/pool/$pool"); # pool roles next if !scalar(@ra); foreach my $vmid (keys %{$d->{vms}}) { for my $role (@ra) { diff --git a/pveum b/pveum index 2c468d0..9214d1f 100755 --- a/pveum +++ b/pveum @@ -14,6 +14,7 @@ use PVE::API2::User; use PVE::API2::Group; use PVE::API2::Role; use PVE::API2::ACL; +use PVE::API2::Pool; use PVE::API2::AccessControl; use PVE::JSONSchema qw(get_standard_option); use PVE::CLIHandler; @@ -72,6 +73,10 @@ my $cmddef = { rolemod => [ 'PVE::API2::Role', 'update_role', ['roleid'] ], roledel => [ 'PVE::API2::Role', 'delete_role', ['roleid'] ], + pooladd => [ 'PVE::API2::Pool', 'create_pool', ['poolid'] ], + poolmod => [ 'PVE::API2::Pool', 'update_pool', ['poolid'] ], + pooldel => [ 'PVE::API2::Pool', 'delete_pool', ['poolid'] ], + aclmod => [ 'PVE::API2::ACL', 'update_acl', ['path', 'roles'], { delete => 0 }], acldel => [ 'PVE::API2::ACL', 'update_acl', ['path', 'roles'], { delete => 1 }], }; -- 2.39.2