]> git.proxmox.com Git - pve-ha-manager.git/blobdiff - src/PVE/HA/LRM.pm
lrm: fix log call on wrong module
[pve-ha-manager.git] / src / PVE / HA / LRM.pm
index af4e21e3a36a6030dec13051f440f34347aba917..5605ab5bdaadf20def1cc8d32a3266dead543a1f 100644 (file)
@@ -16,9 +16,14 @@ use PVE::HA::Resources;
 my $valid_states = {
     wait_for_agent_lock => "waiting for agent lock",
     active => "got agent_lock",
+    maintenance => "going into maintenance",
     lost_agent_lock => "lost agent_lock",
 };
 
+# we sleep ~10s per 'active' round, so if no services is available for >= 10 min we'd go in wait
+# state givining up the watchdog and the LRM lock acquire voluntary, ensuring the WD can do no harm
+my $max_active_idle_rounds = 60;
+
 sub new {
     my ($this, $haenv) = @_;
 
@@ -35,6 +40,7 @@ sub new {
        # mode can be: active, reboot, shutdown, restart
        mode => 'active',
        cluster_state_update => 0,
+       active_idle_rounds => 0,
     }, $class;
 
     $self->set_local_status({ state =>         'wait_for_agent_lock' });
@@ -61,18 +67,27 @@ sub shutdown_request {
     }
 
     my $freeze_all;
+    my $maintenance;
     if ($shutdown_policy eq 'conditional') {
        $freeze_all = $reboot;
     } elsif ($shutdown_policy eq 'freeze') {
        $freeze_all = 1;
     } elsif ($shutdown_policy eq 'failover') {
        $freeze_all = 0;
+    } elsif ($shutdown_policy eq 'migrate') {
+       $maintenance = 1;
     } else {
        $haenv->log('err', "unknown shutdown policy '$shutdown_policy', fall back to conditional");
        $freeze_all = $reboot;
     }
 
-    if ($shutdown) {
+    if ($maintenance) {
+       # we get marked as unaivalable by the manager, then all services will
+       # be migrated away, we'll still have the same "can we exit" clause than
+       # a normal shutdown -> no running service on this node
+       # FIXME: after X minutes, add shutdown command for remaining services,
+       # e.g., if they have no alternative node???
+    } elsif ($shutdown) {
        # *always* queue stop jobs for all services if the node shuts down,
        # independent if it's a reboot or a poweroff, else we may corrupt
        # services or hinder node shutdown
@@ -88,12 +103,12 @@ sub shutdown_request {
     }
 
     if ($shutdown) {
-       if ($freeze_all) {
-           if ($reboot) {
-               $haenv->log('info', "reboot LRM, stop and freeze all services");
-           } else {
-               $haenv->log('info', "shutdown LRM, stop and freeze all services");
-           }
+       my $shutdown_type = $reboot ? 'reboot' : 'shutdown';
+       if ($maintenance) {
+           $haenv->log('info', "$shutdown_type LRM, doing maintenance, removing this node from active list");
+           $self->{mode} = 'maintenance';
+       } elsif ($freeze_all) {
+           $haenv->log('info', "$shutdown_type LRM, stop and freeze all services");
            $self->{mode} = 'restart';
        } else {
            $haenv->log('info', "shutdown LRM, stop all services");
@@ -104,11 +119,11 @@ sub shutdown_request {
        $self->{mode} = 'restart';
     }
 
-    $self->{shutdown_request} = 1;
+    $self->{shutdown_request} = $haenv->get_time();
 
-    eval { $self->update_lrm_status(); };
+    eval { $self->update_lrm_status() or die "not quorate?\n"; };
     if (my $err = $@) {
-       $self->log('err', "unable to update lrm status file - $err");
+       $haenv->log('err', "unable to update lrm status file - $err");
     }
 }
 
@@ -206,17 +221,32 @@ sub get_protected_ha_agent_lock {
     return 0;
 }
 
-sub active_service_count {
+# only cares if any service has the local node as their node, independent of which req.state it is
+sub has_configured_service_on_local_node {
     my ($self) = @_;
 
     my $haenv = $self->{haenv};
+    my $nodename = $haenv->nodename();
+
+    my $ss = $self->{service_status};
+    foreach my $sid (keys %$ss) {
+       my $sd = $ss->{$sid};
+       next if !$sd->{node} || $sd->{node} ne $nodename;
+
+       return 1;
+    }
+    return 0;
+}
 
+sub active_service_count {
+    my ($self) = @_;
+
+    my $haenv = $self->{haenv};
     my $nodename = $haenv->nodename();
 
     my $ss = $self->{service_status};
 
     my $count = 0;
-
     foreach my $sid (keys %$ss) {
        my $sd = $ss->{$sid};
        next if !$sd->{node};
@@ -224,6 +254,7 @@ sub active_service_count {
        my $req_state = $sd->{state};
        next if !defined($req_state);
        next if $req_state eq 'stopped';
+       # NOTE: 'ignored' ones are already dropped by the manager from service_status
        next if $req_state eq 'freeze';
        # erroneous services are not managed by HA, don't count them as active
        next if $req_state eq 'error';
@@ -252,6 +283,17 @@ sub do_one_iteration {
     return $res;
 }
 
+# NOTE: this is disabling the self-fence mechanism, so it must NOT be called with active services
+# It's normally *only* OK on graceful shutdown (with no services, or all services frozen)
+my sub give_up_watchdog_protection {
+    my ($self) = @_;
+
+    if ($self->{ha_agent_wd}) {
+       $self->{haenv}->watchdog_close($self->{ha_agent_wd});
+       delete $self->{ha_agent_wd}; # only delete after close!
+    }
+}
+
 sub work {
     my ($self) = @_;
 
@@ -303,6 +345,31 @@ sub work {
            $self->set_local_status({ state => 'lost_agent_lock'});
        } elsif (!$self->get_protected_ha_agent_lock()) {
            $self->set_local_status({ state => 'lost_agent_lock'});
+       } elsif ($self->{mode} eq 'maintenance') {
+           $self->set_local_status({ state => 'maintenance'});
+       } else {
+           if (!$self->has_configured_service_on_local_node() && !$self->run_workers()) {
+               # no active service configured for this node and all (old) workers are done
+               $self->{active_idle_rounds}++;
+               if ($self->{active_idle_rounds} > $max_active_idle_rounds) {
+                   $haenv->log('info', "node had no service configured for $max_active_idle_rounds rounds, going idle.\n");
+                   # safety: no active service & no running worker for quite some time -> OK
+                   $haenv->release_ha_agent_lock();
+                   give_up_watchdog_protection($self);
+                   $self->set_local_status({ state => 'wait_for_agent_lock'});
+                   $self->{active_idle_rounds} = 0;
+               }
+           } elsif ($self->{active_idle_rounds}) {
+               $self->{active_idle_rounds} = 0;
+           }
+       }
+    } elsif ($state eq 'maintenance') {
+
+       if ($fence_request) {
+           $haenv->log('err', "node need to be fenced during maintenance mode - releasing agent_lock\n");
+           $self->set_local_status({ state => 'lost_agent_lock'});
+       } elsif (!$self->get_protected_ha_agent_lock()) {
+           $self->set_local_status({ state => 'lost_agent_lock'});
        }
     }
 
@@ -342,13 +409,9 @@ sub work {
                    my $service_count = $self->active_service_count();
 
                    if ($service_count == 0) {
-
                        if ($self->run_workers() == 0) {
-                           if ($self->{ha_agent_wd}) {
-                               $haenv->watchdog_close($self->{ha_agent_wd});
-                               delete $self->{ha_agent_wd};
-                           }
-
+                           # safety: no active services or workers -> OK
+                           give_up_watchdog_protection($self);
                            $shutdown = 1;
 
                            # restart with no or freezed services, release the lock
@@ -359,10 +422,8 @@ sub work {
 
                    if ($self->run_workers() == 0) {
                        if ($self->{shutdown_errors} == 0) {
-                           if ($self->{ha_agent_wd}) {
-                               $haenv->watchdog_close($self->{ha_agent_wd});
-                               delete $self->{ha_agent_wd};
-                           }
+                           # safety: no active services and LRM shutdown -> OK
+                           give_up_watchdog_protection($self);
 
                            # shutdown with all services stopped thus release the lock
                            $haenv->release_ha_agent_lock();
@@ -396,10 +457,8 @@ sub work {
 
     } elsif ($state eq 'lost_agent_lock') {
 
-       # Note: watchdog is active an will triger soon!
-
+       # NOTE: watchdog is active an will trigger soon!
        # so we hope to get the lock back soon!
-
        if ($self->{shutdown_request}) {
 
            my $service_count = $self->active_service_count();
@@ -421,13 +480,8 @@ sub work {
                    }
                }
            } else {
-
-               # all services are stopped, so we can close the watchdog
-
-               if ($self->{ha_agent_wd}) {
-                   $haenv->watchdog_close($self->{ha_agent_wd});
-                   delete $self->{ha_agent_wd};
-               }
+               # safety: all services are stopped, so we can close the watchdog
+               give_up_watchdog_protection($self);
 
                return 0;
            }
@@ -435,6 +489,36 @@ sub work {
 
        $haenv->sleep(5);
 
+    } elsif ($state eq 'maintenance') {
+
+       my $startime = $haenv->get_time();
+       return if !$self->update_service_status();
+
+       # wait until all active services moved away
+       my $service_count = $self->active_service_count();
+
+       my $exit_lrm = 0;
+
+       if ($self->{shutdown_request}) {
+           if ($service_count == 0 && $self->run_workers() == 0) {
+               # safety: going into maintenance and all active services got moved -> OK
+               give_up_watchdog_protection($self);
+
+               $exit_lrm = 1;
+
+               # restart with no or freezed services, release the lock
+               $haenv->release_ha_agent_lock();
+           }
+       }
+
+       $self->manage_resources() if !$exit_lrm;
+
+       $self->update_lrm_status();
+
+       return 0 if $exit_lrm;
+
+       $haenv->sleep_until($startime + 5);
+
     } else {
 
        die "got unexpected status '$state'\n";
@@ -477,7 +561,7 @@ sub run_workers {
                        # do work
                        my $res = -1;
                        eval {
-                           $res = $self->exec_resource_agent($sid, $sc->{$sid}, $w->{state}, $w->{target});
+                           $res = $self->exec_resource_agent($sid, $sc->{$sid}, $w->{state}, $w->{params});
                        };
                        if (my $err = $@) {
                            $haenv->log('err', $err);
@@ -491,7 +575,7 @@ sub run_workers {
                } else {
                    my $res = -1;
                    eval {
-                       $res = $self->exec_resource_agent($sid, $sc->{$sid}, $w->{state}, $w->{target});
+                       $res = $self->exec_resource_agent($sid, $sc->{$sid}, $w->{state}, $w->{params});
                        $res = $res << 8 if $res > 0;
                    };
                    if (my $err = $@) {
@@ -534,15 +618,23 @@ sub manage_resources {
        next if $sd->{node} ne $nodename;
        my $req_state = $sd->{state};
        next if !defined($req_state);
+       # can only happen for restricted groups where the failed node itself needs to be the
+       # reocvery target. Always let the master first do so, it will then marked as 'stopped' and
+       # we can just continue normally. But we must NOT do anything with it while still in recovery
+       next if $req_state eq 'recovery';
        next if $req_state eq 'freeze';
-       $self->queue_resource_command($sid, $sd->{uid}, $req_state, $sd->{target});
+
+       $self->queue_resource_command($sid, $sd->{uid}, $req_state, {
+           'target' => $sd->{target},
+           'timeout' => $sd->{timeout},
+       });
     }
 
     return $self->run_workers();
 }
 
 sub queue_resource_command {
-    my ($self, $sid, $uid, $state, $target) = @_;
+    my ($self, $sid, $uid, $state, $params) = @_;
 
     # do not queue the excatly same command twice as this may lead to
     # an inconsistent HA state when the first command fails but the CRM
@@ -564,7 +656,7 @@ sub queue_resource_command {
        state => $state,
     };
 
-    $self->{workers}->{$sid}->{target} = $target if $target;
+    $self->{workers}->{$sid}->{params} = $params if $params;
 }
 
 sub check_active_workers {
@@ -711,7 +803,7 @@ sub handle_service_exitcode {
 }
 
 sub exec_resource_agent {
-    my ($self, $sid, $service_config, $cmd, @params) = @_;
+    my ($self, $sid, $service_config, $cmd, $params) = @_;
 
     # setup execution environment
 
@@ -736,7 +828,6 @@ sub exec_resource_agent {
 
     # process error state early
     if ($cmd eq 'error') {
-
        $haenv->log('err', "service $sid is in an error state and needs manual " .
                    "intervention. Look up 'ERROR RECOVERY' in the documentation.");
 
@@ -774,9 +865,13 @@ sub exec_resource_agent {
 
        return SUCCESS if !$running;
 
-       $haenv->log("info", "stopping service $sid");
+       if (defined($params->{timeout})) {
+           $haenv->log("info", "stopping service $sid (timeout=$params->{timeout})");
+       } else {
+           $haenv->log("info", "stopping service $sid");
+       }
 
-       $plugin->shutdown($haenv, $id);
+       $plugin->shutdown($haenv, $id, $params->{timeout});
 
        $running = $plugin->check_running($haenv, $id);
 
@@ -790,7 +885,7 @@ sub exec_resource_agent {
 
     } elsif ($cmd eq 'migrate' || $cmd eq 'relocate') {
 
-       my $target = $params[0];
+       my $target = $params->{target};
        if (!defined($target)) {
            die "$cmd '$sid' failed - missing target\n" if !defined($target);
            return EINVALID_PARAMETER;