X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=src%2FPVE%2FAPI2%2FHA%2FResources.pm;h=1ffdf2ef488f75833b140a71285275152105489a;hb=289e47849216182923a79a58023a0cbf829c5625;hp=98f429acc6a4bf205179f477381d6bf30e0d8002;hpb=139a9b9061007332e8e7c80886397096795d6812;p=pve-ha-manager.git diff --git a/src/PVE/API2/HA/Resources.pm b/src/PVE/API2/HA/Resources.pm index 98f429a..1ffdf2e 100644 --- a/src/PVE/API2/HA/Resources.pm +++ b/src/PVE/API2/HA/Resources.pm @@ -5,13 +5,13 @@ use warnings; use PVE::SafeSyslog; use PVE::Tools qw(extract_param); +use PVE::Cluster; use PVE::HA::Config; use PVE::HA::Resources; use HTTP::Status qw(:constants); use Storable qw(dclone); use PVE::JSONSchema qw(get_standard_option); use PVE::RPCEnvironment; -use Data::Dumper; use PVE::RESTHandler; @@ -21,8 +21,6 @@ use base qw(PVE::RESTHandler); my $resource_type_enum = PVE::HA::Resources->lookup_types(); -# fixme: fix permissions - my $api_copy_config = sub { my ($cfg, $sid) = @_; @@ -35,11 +33,26 @@ my $api_copy_config = sub { return $scfg; }; +sub check_service_state { + my ($sid, $req_state) = @_; + + my $service_status = PVE::HA::Config::get_service_status($sid); + if ($service_status->{managed} && $service_status->{state} eq 'error') { + # service in error state, must get disabled before new state request + # can be executed + return if defined($req_state) && $req_state eq 'disabled'; + die "service '$sid' in error state, must be disabled and fixed first\n"; + } +} + __PACKAGE__->register_method ({ name => 'index', path => '', method => 'GET', description => "List HA resources.", + permissions => { + check => ['perm', '/', [ 'Sys.Audit' ]], + }, parameters => { additionalProperties => 0, properties => { @@ -63,11 +76,15 @@ __PACKAGE__->register_method ({ my ($param) = @_; my $cfg = PVE::HA::Config::read_resources_config(); + my $groups = PVE::HA::Config::read_group_config(); my $res = []; foreach my $sid (keys %{$cfg->{ids}}) { my $scfg = &$api_copy_config($cfg, $sid); next if $param->{type} && $param->{type} ne $scfg->{type}; + if ($scfg->{group} && !$groups->{ids}->{$scfg->{group}}) { + $scfg->{errors}->{group} = "group '$scfg->{group}' does not exist"; + } push @$res, $scfg; } @@ -78,20 +95,63 @@ __PACKAGE__->register_method ({ name => 'read', path => '{sid}', method => 'GET', + permissions => { + check => ['perm', '/', [ 'Sys.Audit' ]], + }, description => "Read resource configuration.", parameters => { additionalProperties => 0, properties => { - sid => get_standard_option('pve-ha-resource-id'), + sid => get_standard_option('pve-ha-resource-or-vm-id', + { completion => \&PVE::HA::Tools::complete_sid }), + }, + }, + returns => { + type => 'object', + properties => { + sid => get_standard_option('pve-ha-resource-or-vm-id'), + digest => { + type => 'string', + description => 'Can be used to prevent concurrent modifications.', + }, + type => { + type => 'string', + description => 'The type of the resources.', + }, + state => { + type => 'string', + enum => ['started', 'stopped', 'enabled', 'disabled', 'ignored'], + optional => 1, + description => "Requested resource state.", + }, + group => get_standard_option('pve-ha-group-id', { optional => 1, }), + max_restart => { + description => "Maximal number of tries to restart the service on". + " a node after its start failed.", + type => 'integer', + optional => 1, + }, + max_relocate => { + description => "Maximal number of service relocate tries when a". + " service failes to start.", + type => 'integer', + optional => 1, + }, + comment => { + description => "Description.", + type => 'string', + optional => 1, + }, }, }, - returns => {}, code => sub { my ($param) = @_; my $cfg = PVE::HA::Config::read_resources_config(); - return &$api_copy_config($cfg, $param->{sid}); + my $sid = PVE::HA::Config::parse_sid($param->{sid}); + + return &$api_copy_config($cfg, $sid); }}); __PACKAGE__->register_method ({ @@ -99,14 +159,20 @@ __PACKAGE__->register_method ({ protected => 1, path => '', method => 'POST', + permissions => { + check => ['perm', '/', [ 'Sys.Console' ]], + }, description => "Create a new HA resource.", parameters => PVE::HA::Resources->createSchema(), returns => { type => 'null' }, code => sub { my ($param) = @_; - my $sid = extract_param($param, 'sid'); - my ($type, $name) = PVE::HA::Tools::parse_sid($sid); + # create /etc/pve/ha directory + PVE::Cluster::check_cfs_quorum(); + mkdir("/etc/pve/ha"); + + my ($sid, $type, $name) = PVE::HA::Config::parse_sid(extract_param($param, 'sid')); if (my $param_type = extract_param($param, 'type')) { # useless, but do it anyway @@ -116,9 +182,11 @@ __PACKAGE__->register_method ({ my $plugin = PVE::HA::Resources->lookup($type); $plugin->verify_name($name); + $plugin->exists($name); + my $opts = $plugin->check_config($sid, $param, 1, 1); - PVE::HA::Env::PVE2::lock_ha_config( + PVE::HA::Config::lock_ha_domain( sub { my $cfg = PVE::HA::Config::read_resources_config(); @@ -129,7 +197,7 @@ __PACKAGE__->register_method ({ $cfg->{ids}->{$sid} = $opts; - PVE::HA::Env::PVE2::write_resources_config($cfg) + PVE::HA::Config::write_resources_config($cfg) }, "create resource failed"); @@ -142,6 +210,9 @@ __PACKAGE__->register_method ({ path => '{sid}', method => 'PUT', description => "Update resource configuration.", + permissions => { + check => ['perm', '/', [ 'Sys.Console' ]], + }, parameters => PVE::HA::Resources->updateSchema(), returns => { type => 'null' }, code => sub { @@ -150,15 +221,23 @@ __PACKAGE__->register_method ({ my $digest = extract_param($param, 'digest'); my $delete = extract_param($param, 'delete'); - my $sid = extract_param($param, 'sid'); - my ($type, $name) = PVE::HA::Tools::parse_sid($sid); + my ($sid, $type, $name) = PVE::HA::Config::parse_sid(extract_param($param, 'sid')); if (my $param_type = extract_param($param, 'type')) { # useless, but do it anyway die "types does not match\n" if $param_type ne $type; } - PVE::HA::Config::lock_ha_config( + if (my $group = $param->{group}) { + my $group_cfg = PVE::HA::Config::read_group_config(); + + die "HA group '$group' does not exist\n" + if !$group_cfg->{ids}->{$group}; + } + + check_service_state($sid, $param->{state}); + + PVE::HA::Config::lock_ha_domain( sub { my $cfg = PVE::HA::Config::read_resources_config(); @@ -201,28 +280,37 @@ __PACKAGE__->register_method ({ path => '{sid}', method => 'DELETE', description => "Delete resource configuration.", + permissions => { + check => ['perm', '/', [ 'Sys.Console' ]], + }, parameters => { additionalProperties => 0, properties => { - sid => get_standard_option('pve-ha-resource-id'), + sid => get_standard_option('pve-ha-resource-or-vm-id', + { completion => \&PVE::HA::Tools::complete_sid }), }, }, returns => { type => 'null' }, code => sub { my ($param) = @_; - my $sid = extract_param($param, 'sid'); + my ($sid, $type, $name) = PVE::HA::Config::parse_sid(extract_param($param, 'sid')); - PVE::HA::Config::lock_ha_config( - sub { + my $cfg = PVE::HA::Config::read_resources_config(); - my $cfg = PVE::HA::Config::read_resources_config(); + # cannot use service_is_ha_managed as it ignores 'ignored' services, + # see bug report #1602 + if (!defined($cfg->{ids}) || !defined($cfg->{ids}->{$sid})) { + die "cannot delete service '$sid', not HA managed!\n"; + } - delete $cfg->{ids}->{$sid}; + PVE::HA::Config::lock_ha_domain(sub { - PVE::HA::Config::write_resources_config($cfg) + $cfg = PVE::HA::Config::read_resources_config(); + delete $cfg->{ids}->{$sid} or die "'$sid' not configured!\n"; + PVE::HA::Config::write_resources_config($cfg); - }, "delete storage failed"); + }, "delete resource failed"); return undef; }}); @@ -233,19 +321,32 @@ __PACKAGE__->register_method ({ path => '{sid}/migrate', method => 'POST', description => "Request resource migration (online) to another node.", + permissions => { + check => ['perm', '/', [ 'Sys.Console' ]], + }, parameters => { additionalProperties => 0, properties => { - sid => get_standard_option('pve-ha-resource-id'), - node => get_standard_option('pve-node'), + sid => get_standard_option('pve-ha-resource-or-vm-id', + { completion => \&PVE::HA::Tools::complete_sid }), + node => get_standard_option('pve-node', { + completion => \&PVE::Cluster::complete_migration_target, + description => "Target node.", + }), }, }, returns => { type => 'null' }, code => sub { my ($param) = @_; - PVE::HA::Config::queue_crm_commands("migrate $param->{sid} $param->{node}"); - + my ($sid, $type, $name) = PVE::HA::Config::parse_sid(extract_param($param, 'sid')); + + PVE::HA::Config::service_is_ha_managed($sid); + + check_service_state($sid); + + PVE::HA::Config::queue_crm_commands("migrate $sid $param->{node}"); + return undef; }}); @@ -255,19 +356,32 @@ __PACKAGE__->register_method ({ path => '{sid}/relocate', method => 'POST', description => "Request resource relocatzion to another node. This stops the service on the old node, and restarts it on the target node.", + permissions => { + check => ['perm', '/', [ 'Sys.Console' ]], + }, parameters => { additionalProperties => 0, properties => { - sid => get_standard_option('pve-ha-resource-id'), - node => get_standard_option('pve-node'), + sid => get_standard_option('pve-ha-resource-or-vm-id', + { completion => \&PVE::HA::Tools::complete_sid }), + node => get_standard_option('pve-node', { + completion => \&PVE::Cluster::complete_migration_target, + description => "Target node.", + }), }, }, returns => { type => 'null' }, code => sub { my ($param) = @_; - PVE::HA::Config::queue_crm_commands("relocate $param->{sid} $param->{node}"); - + my ($sid, $type, $name) = PVE::HA::Config::parse_sid(extract_param($param, 'sid')); + + PVE::HA::Config::service_is_ha_managed($sid); + + check_service_state($sid); + + PVE::HA::Config::queue_crm_commands("relocate $sid $param->{node}"); + return undef; }});