]> git.proxmox.com Git - pve-ha-manager.git/blobdiff - src/PVE/HA/Env/PVE2.pm
get_pve_lock: always send a lock update request
[pve-ha-manager.git] / src / PVE / HA / Env / PVE2.pm
index d1313528067b89f4cd8b3bba5593aeea2753aeec..2e6b1561a2f4dee6dbd7b114c63eb877c5cfb50c 100644 (file)
@@ -8,58 +8,24 @@ use IO::Socket::UNIX;
 
 use PVE::SafeSyslog;
 use PVE::Tools;
-use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_lock_file);
+use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
 use PVE::INotify;
 use PVE::RPCEnvironment;
 
-use PVE::HA::Tools;
+use PVE::HA::Tools ':exit_codes';
 use PVE::HA::Env;
 use PVE::HA::Config;
+use PVE::HA::FenceConfig;
+use PVE::HA::Resources;
+use PVE::HA::Resources::PVEVM;
+use PVE::HA::Resources::PVECT;
 
-use PVE::QemuServer;
-use PVE::API2::Qemu;
+PVE::HA::Resources::PVEVM->register();
+PVE::HA::Resources::PVECT->register();
 
-my $lockdir = "/etc/pve/priv/lock";
-
-my $manager_status_filename = "/etc/pve/ha/manager_status";
-my $ha_groups_config = "/etc/pve/ha/groups.cfg";
-my $ha_resources_config = "/etc/pve/ha/resources.cfg";
-
-# fixme:
-#cfs_register_file($ha_groups_config, 
-#                sub { PVE::HA::Groups->parse_config(@_); },
-#                sub { PVE::HA::Groups->write_config(@_); });
-#cfs_register_file($ha_resources_config, 
-#                sub { PVE::HA::Resources->parse_config(@_); },
-#                sub { PVE::HA::Resources->write_config(@_); });
-
-sub read_resources_config {
-    my $raw = '';
-
-    $raw = PVE::Tools::file_get_contents($ha_resources_config)
-       if -f $ha_resources_config;
-    
-    return PVE::HA::Config::parse_resources_config($ha_resources_config, $raw);
-}
-
-sub write_resources_config {
-    my ($cfg) = @_;
-
-    my $raw = PVE::HA::Resources->write_config($ha_resources_config, $cfg);
-    PVE::Tools::file_set_contents($ha_resources_config, $raw);
-}
-
-sub lock_ha_config {
-    my ($code, $errmsg) = @_;
+PVE::HA::Resources->init();
 
-    # fixme: do not use cfs_lock_storage (replace with cfs_lock_ha)
-    my $res = PVE::Cluster::cfs_lock_storage("_ha_crm_commands", undef, $code);
-    my $err = $@;
-    if ($err) {
-       $errmsg ? die "$errmsg: $err" : die $err;
-    }
-    return $res;
-}
+my $lockdir = "/etc/pve/priv/lock";
 
 sub new {
     my ($this, $nodename) = @_;
@@ -81,20 +47,22 @@ sub nodename {
     return $self->{nodename};
 }
 
+sub hardware {
+    my ($self) = @_;
+
+    die "hardware is for testing and simulation only";
+}
+
 sub read_manager_status {
     my ($self) = @_;
-    
-    my $filename = $manager_status_filename;
 
-    return PVE::HA::Tools::read_json_from_file($filename, {});  
+    return PVE::HA::Config::read_manager_status();
 }
 
 sub write_manager_status {
     my ($self, $status_obj) = @_;
-    
-    my $filename = $manager_status_filename;
 
-    PVE::HA::Tools::write_json_to_file($filename, $status_obj); 
+    PVE::HA::Config::write_manager_status($status_obj);
 }
 
 sub read_lrm_status {
@@ -102,9 +70,7 @@ sub read_lrm_status {
 
     $node = $self->{nodename} if !defined($node);
 
-    my $filename = "/etc/pve/nodes/$node/lrm_status";
-
-    return PVE::HA::Tools::read_json_from_file($filename, {});  
+    return PVE::HA::Config::read_lrm_status($node);
 }
 
 sub write_lrm_status {
@@ -112,103 +78,92 @@ sub write_lrm_status {
 
     my $node = $self->{nodename};
 
-    my $filename = "/etc/pve/nodes/$node/lrm_status";
+    PVE::HA::Config::write_lrm_status($node, $status_obj);
+}
+
+sub is_node_shutdown {
+    my ($self) = @_;
+
+    my $shutdown = 0;
+
+    my $code = sub {
+       my $line = shift;
+
+       $shutdown = 1 if ($line =~ m/shutdown\.target/);
+    };
+
+    my $cmd = ['/bin/systemctl', 'list-jobs'];
+    eval { PVE::Tools::run_command($cmd, outfunc => $code, noerr => 1); };
+
+    return $shutdown;
+}
+
+sub queue_crm_commands {
+    my ($self, $cmd) = @_;
 
-    PVE::HA::Tools::write_json_to_file($filename, $status_obj); 
+    return PVE::HA::Config::queue_crm_commands($cmd);
 }
 
-sub service_config_exists {
+sub read_crm_commands {
     my ($self) = @_;
 
-    return -f $ha_resources_config ? 1 : 0;
+    return PVE::HA::Config::read_crm_commands();
 }
 
 sub read_service_config {
     my ($self) = @_;
 
-    my $res = read_resources_config();
-
-    my $vmlist = PVE::Cluster::get_vmlist();
-    my $conf = {};
-
-    foreach my $sid (keys %{$res->{ids}}) {
-       my $d = $res->{ids}->{$sid};
-       my $name = PVE::HA::Tools::parse_sid($sid);
-       $d->{state} = 'enabled' if !defined($d->{state});
-       if ($d->{type} eq 'pvevm') {
-           if (my $vmd = $vmlist->{ids}->{$name}) {
-               if (!$vmd) {
-                   warn "no such VM '$name'\n";
-               } else {
-                   $d->{node} = $vmd->{node};
-                   $conf->{$sid} = $d;
-               }
-           } else {
-               if (defined($d->{node})) {
-                   $conf->{$sid} = $d;
-               } else {
-                   warn "service '$sid' without node\n";
-               }
-           }
-       }
-    }
-    
-    return $conf;
+    return PVE::HA::Config::read_and_check_resources_config();
 }
 
-sub change_service_location {
-    my ($self, $sid, $node) = @_;
+sub read_fence_config {
+    my ($self) = @_;
 
-    die "implement me";
+    return PVE::HA::Config::read_fence_config();
 }
 
-sub read_group_config {
+sub fencing_mode {
     my ($self) = @_;
 
-    # fixme: use cfs_read_file
-    
-    my $raw = '';
+    my $datacenterconfig = cfs_read_file('datacenter.cfg');
+
+    return 'watchdog' if !$datacenterconfig->{fencing};
 
-    $raw = PVE::Tools::file_get_contents($ha_groups_config)
-       if -f $ha_groups_config;
-    
-    return PVE::HA::Config::parse_groups_config($ha_groups_config, $raw);
+    return $datacenterconfig->{fencing};
 }
 
-sub queue_crm_commands {
-    my ($self, $cmd) = @_;
+sub exec_fence_agent {
+    my ($self, $agent, $node, @param) = @_;
 
-    chomp $cmd;
+    # setup execution environment
+    $ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';
 
-    my $code = sub {
-       my $data = '';
-       my $filename = "/etc/pve/ha/crm_commands";
-       if (-f $filename) {
-           $data = PVE::Tools::file_get_contents($filename);
-       }
-       $data .= "$cmd\n";
-       PVE::Tools::file_set_contents($filename, $data);
-    };
+    my $cmd = "$agent " . PVE::HA::FenceConfig::gen_arg_str(@param);
 
-    return lock_ha_config($code);
+    exec($cmd);
+    exit -1;
 }
 
-sub read_crm_commands {
-    my ($self) = @_;
+# this is only allowed by the master to recover a _fenced_ service
+sub steal_service {
+    my ($self, $sid, $current_node, $new_node) = @_;
 
-    my $code = sub {
-       my $data = '';
+    my (undef, $type, $name) = PVE::HA::Tools::parse_sid($sid);
 
-       my $filename = "/etc/pve/ha/crm_commands";
-       if (-f $filename) {
-           $data = PVE::Tools::file_get_contents($filename);
-           PVE::Tools::file_set_contents($filename, '');
-       }
+    if(my $plugin = PVE::HA::Resources->lookup($type)) {
+       my $old = $plugin->config_file($name, $current_node);
+       my $new = $plugin->config_file($name, $new_node);
+       rename($old, $new) ||
+           die "rename '$old' to '$new' failed - $!\n";
+    } else {
+       die "implement me";
+    }
+}
 
-       return $data;
-    };
+sub read_group_config {
+    my ($self) = @_;
 
-    return lock_ha_config($code);
+    return PVE::HA::Config::read_group_config();
 }
 
 # this should return a hash containing info
@@ -217,7 +172,7 @@ sub get_node_info {
     my ($self) = @_;
 
     my ($node_info, $quorate) = ({}, 0);
-   
+
     my $nodename = $self->{nodename};
 
     $quorate = PVE::Cluster::check_cfs_quorum(1) || 0;
@@ -226,11 +181,11 @@ sub get_node_info {
 
     foreach my $node (keys %$members) {
        my $d = $members->{$node};
-       $node_info->{$node}->{online} = $d->{online}; 
+       $node_info->{$node}->{online} = $d->{online};
     }
-       
+
     $node_info->{$nodename}->{online} = 1; # local node is always up
-    
+
     return ($node_info, $quorate);
 }
 
@@ -242,7 +197,16 @@ sub log {
     syslog($level, $msg);
 }
 
-my $last_lock_status = {};
+sub sendmail {
+    my ($self, $subject, $text) = @_;
+
+    my $mailfrom = 'root@' . $self->nodename();
+    my $mailto = 'root@localhost';
+
+    PVE::Tools::sendmail($mailto, $subject, $text, undef, $mailfrom);
+}
+
+my $last_lock_status_hash = {};
 
 sub get_pve_lock {
     my ($self, $lockid) = @_;
@@ -251,9 +215,14 @@ sub get_pve_lock {
 
     my $filename = "$lockdir/$lockid";
 
-    my $last = $last_lock_status->{$lockid} || 0;
+    $last_lock_status_hash->{$lockid} //= { lock_time => 0, got_lock => 0};
+    my $last = $last_lock_status_hash->{$lockid};
 
     my $ctime = time();
+    my $last_lock_time = $last->{lock_time} // 0;
+    my $last_got_lock = $last->{got_lock};
+
+    my $retry_timeout = 100; # fixme: what timeout
 
     eval {
 
@@ -262,31 +231,38 @@ sub get_pve_lock {
        # pve cluster filesystem not online
        die "can't create '$lockdir' (pmxcfs not mounted?)\n" if ! -d $lockdir;
 
-       if ($last && (($ctime - $last) < 100)) { # fixme: what timeout
-           utime(0, $ctime, $filename) || # cfs lock update request
-               die "cfs lock update failed - $!\n";
-       } else {
+       # try cfs lock update request (utime)
+       if (utime(0, $ctime, $filename))  {
+           $got_lock = 1;
+           return;
+       }
 
-           # fixme: wait some time?
-           if (!(mkdir $filename)) {
-               utime 0, 0, $filename; # cfs unlock request
-               die "can't get cfs lock\n";
-           }
+       if ($last_lock_time && (($ctime - $last_lock_time) < $retry_timeout)) {
+           die "cfs lock update failed - $!\n";
        }
 
-       $got_lock = 1;
+       if (mkdir $filename) {
+           $got_lock = 1;
+           return;
+       }
+
+       utime 0, 0, $filename; # cfs unlock request
+       die "can't get cfs lock\n";
     };
 
     my $err = $@;
 
-    $last_lock_status->{$lockid} = $got_lock ? $ctime : 0;
+    #$self->log('err', $err) if $err; # for debugging
 
-    if (!!$got_lock != !!$last) {
+    $last->{got_lock} = $got_lock;
+    $last->{lock_time} = $ctime if $got_lock;
+
+    if (!!$got_lock != !!$last_got_lock) {
        if ($got_lock) {
-           $self->log('info', "successfully aquired lock '$lockid'");
+           $self->log('info', "successfully acquired lock '$lockid'");
        } else {
            my $msg = "lost lock '$lockid";
-           $msg .= " - $err" if $err; 
+           $msg .= " - $err" if $err;
            $self->log('err', $msg);
        }
     }
@@ -300,33 +276,42 @@ sub get_ha_manager_lock {
     return $self->get_pve_lock("ha_manager_lock");
 }
 
-sub get_ha_agent_lock {
+# release the cluster wide manager lock.
+# when released another CRM may step up and get the lock, thus this should only
+# get called when shutting down/deactivating the current master
+sub release_ha_manager_lock {
     my ($self) = @_;
-    
-    my $node = $self->nodename();
 
-    return $self->get_pve_lock("ha_agent_${node}_lock");
+    return rmdir("$lockdir/ha_manager_lock");
 }
 
-sub test_ha_agent_lock {
+sub get_ha_agent_lock {
     my ($self, $node) = @_;
-    
-    my $lockid = "ha_agent_${node}_lock";
-    my $filename = "$lockdir/$lockid";
-    my $res = $self->get_pve_lock($lockid);
-    rmdir $filename if $res; # cfs unlock
 
-    return $res;
+    $node = $self->nodename() if !defined($node);
+
+    return $self->get_pve_lock("ha_agent_${node}_lock");
+}
+
+# release the respective node agent lock.
+# this should only get called if the nodes LRM gracefully shuts down with
+# all services already cleanly stopped!
+sub release_ha_agent_lock {
+    my ($self) = @_;
+
+    my $node = $self->nodename();
+
+    return rmdir("$lockdir/ha_agent_${node}_lock");
 }
 
 sub quorate {
     my ($self) = @_;
 
     my $quorate = 0;
-    eval { 
-       $quorate = PVE::Cluster::check_cfs_quorum(); 
+    eval {
+       $quorate = PVE::Cluster::check_cfs_quorum();
     };
-   
+
     return $quorate;
 }
 
@@ -358,7 +343,7 @@ sub loop_start_hook {
     my ($self) = @_;
 
     PVE::Cluster::cfs_update();
-    
+
     $self->{loop_start} = $self->get_time();
 }
 
@@ -366,7 +351,7 @@ sub loop_end_hook {
     my ($self) = @_;
 
     my $delay = $self->get_time() - $self->{loop_start};
+
     warn "loop take too long ($delay seconds)\n" if $delay > 30;
 }
 
@@ -381,7 +366,7 @@ sub watchdog_open {
        Type => SOCK_STREAM(),
        Peer => "/run/watchdog-mux.sock") ||
        die "unable to open watchdog socket - $!\n";
-      
+
     $self->log('info', "watchdog active");
 }
 
@@ -413,104 +398,22 @@ sub watchdog_close {
     }
 }
 
-sub upid_wait {
-    my ($self, $upid) = @_;
-
-    my $task = PVE::Tools::upid_decode($upid);
-
-    CORE::sleep(1);
-    while (PVE::ProcFSTools::check_process_running($task->{pid}, $task->{pstart})) {
-       $self->log('debug', "Task still active, waiting");
-       CORE::sleep(1);
-    }
-}
-
-sub exec_resource_agent {
-    my ($self, $sid, $service_config, $cmd, @params) = @_;
-
-    # setup execution environment
-    
-    $ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';
+sub after_fork {
+    my ($self) = @_;
 
+    # close inherited inotify FD from parent and reopen our own
     PVE::INotify::inotify_close();
-    
     PVE::INotify::inotify_init();
 
     PVE::Cluster::cfs_update();
-    my $nodename = $self->{nodename};
-
-    # fixme: return valid_exit code (instead of using die) ?
-
-    my ($service_type, $service_name) = PVE::HA::Tools::parse_sid($sid);
-
-    die "service type '$service_type'not implemented" if $service_type ne 'pvevm';
-
-    my $vmid = $service_name;
-
-    my $running = PVE::QemuServer::check_running($vmid, 1);
-    if ($cmd eq 'started') {
-
-       # fixme: return valid_exit code
-       die "service '$sid' not on this node" if $service_config->{node} ne $nodename;
-
-       # fixme: count failures
-       
-       return 0 if $running;
-
-       $self->log("info", "starting service $sid");
-
-       my $upid = PVE::API2::Qemu->vm_start({node => $nodename, vmid => $vmid});
-       $self->upid_wait($upid);
-
-       $running = PVE::QemuServer::check_running($vmid, 1);
-
-       if ($running) {
-           $self->log("info", "service status $sid started");
-           return 0;
-       } else {
-           $self->log("info", "unable to start service $sid");
-           return 1;
-       }
-
-    } elsif ($cmd eq 'request_stop' || $cmd eq 'stopped') {
-
-       # fixme: return valid_exit code
-       die "service '$sid' not on this node" if $service_config->{node} ne $nodename;
-
-       return 0 if !$running;
-
-       $self->log("info", "stopping service $sid");
-
-       my $timeout = 60; # fixme: make this configurable
-       
-       my $param = {
-           node => $nodename, 
-           vmid => $vmid, 
-           timeout => $timeout,
-           forceStop => 1,
-       };
-
-       my $upid = PVE::API2::Qemu->vm_shutdown($param);
-       $self->upid_wait($upid);
-
-       $running = PVE::QemuServer::check_running($vmid, 1);
-
-       if (!$running) {
-           $self->log("info", "service status $sid stopped");
-           return 0;
-       } else {
-           return 1;
-       }
+}
 
-    } elsif ($cmd eq 'migrate' || $cmd eq 'relocate') {
+sub get_max_workers {
+    my ($self) = @_;
 
-       # implement me
-       
-    }
+    my $datacenterconfig = cfs_read_file('datacenter.cfg');
 
-    die "implement me (cmd '$cmd')";
+    return $datacenterconfig->{max_workers} || 4;
 }
 
 1;