]> git.proxmox.com Git - pve-ha-manager.git/blobdiff - src/PVE/API2/HA/Resources.pm
treewide trailing whitespace cleanup
[pve-ha-manager.git] / src / PVE / API2 / HA / Resources.pm
index a8810c5959a49e6d7bca355efff8feb7d1f4e62e..1ffdf2ef488f75833b140a71285275152105489a 100644 (file)
@@ -5,15 +5,13 @@ use warnings;
 
 use PVE::SafeSyslog;
 use PVE::Tools qw(extract_param);
-use PVE::Cluster qw(cfs_read_file cfs_write_file);
+use PVE::Cluster;
 use PVE::HA::Config;
 use PVE::HA::Resources;
-use PVE::HA::Env::PVE2;
 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,12 +19,8 @@ use base qw(PVE::RESTHandler);
 
 # fixme: use cfs_read_file
 
-my $ha_resources_config = "/etc/pve/ha/resources.cfg";
-
 my $resource_type_enum = PVE::HA::Resources->lookup_types();
 
-# fixme: fix permissions
-
 my $api_copy_config = sub {
     my ($cfg, $sid) = @_;
 
@@ -39,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 => {
@@ -66,12 +75,16 @@ __PACKAGE__->register_method ({
     code => sub {
        my ($param) = @_;
 
-       my $cfg = PVE::HA::Env::PVE2::read_resources_config();
+       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;
        }
 
@@ -82,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::Env::PVE2::read_resources_config();
+       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 ({
@@ -103,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
@@ -120,12 +182,14 @@ __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::Env::PVE2::read_resources_config();
+               my $cfg = PVE::HA::Config::read_resources_config();
 
                if ($cfg->{ids}->{$sid}) {
                    die "resource ID '$sid' already defined\n";
@@ -133,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");
 
@@ -146,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 {
@@ -154,18 +221,26 @@ __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::Env::PVE2::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::Env::PVE2::read_resources_config();
+               my $cfg = PVE::HA::Config::read_resources_config();
 
                PVE::SectionConfig::assert_if_modified($cfg, $digest);
 
@@ -192,7 +267,7 @@ __PACKAGE__->register_method ({
                    }
                }
 
-               PVE::HA::Env::PVE2::write_resources_config($cfg)
+               PVE::HA::Config::write_resources_config($cfg)
 
            }, "update resource failed");
 
@@ -205,28 +280,107 @@ __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::Env::PVE2::lock_ha_config(
-           sub {
+       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";
+       }
+
+       PVE::HA::Config::lock_ha_domain(sub {
+
+           $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 resource failed");
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'migrate',
+    protected => 1,
+    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-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) = @_;
+
+       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;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'relocate',
+    protected => 1,
+    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-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) = @_;
 
-               my $cfg = PVE::HA::Env::PVE2::read_resources_config();
+       my ($sid, $type, $name) = PVE::HA::Config::parse_sid(extract_param($param, 'sid'));
 
-               delete $cfg->{ids}->{$sid};
+       PVE::HA::Config::service_is_ha_managed($sid);
 
-               PVE::HA::Env::PVE2::write_resources_config($cfg)
+       check_service_state($sid);
 
-           }, "delete storage failed");
+       PVE::HA::Config::queue_crm_commands("relocate $sid $param->{node}");
 
        return undef;
     }});