[pve-devel] [PATCH ha-manager v4 2/4] add Fence class for external fence devices

Thomas Lamprecht t.lamprecht at proxmox.com
Wed Mar 16 13:01:28 CET 2016


This class provides methods for starting and checking the current
status of a fence job.

When a fence job is started we execute a fence agent command.
If we can fork this happens in forked worker, which can be multiple
processes also, when parallel devices are configured.

When a device fails to successfully fence a node we try the next
device configured, or if no device is left we tell the CRM and let
him decide what to do.

If one process of a parallel device fails we kill the remaining
processes (with reset_hard) and try the next device as we want to
avoid a partial fenced node.

The current running fence jobs can be picked up (if env. allows
forking) and processed by calling the process_fencing method.

If the CRM (which should handle the fencing) looses its lock
bail_out can be called to kill all currently running fencing
processes and reset the fencing status of all nodes.

Signed-off-by: Thomas Lamprecht <t.lamprecht at proxmox.com>
---

changes since v3:
* log when starting fencing and exec'ing fence agents

 src/PVE/HA/Fence.pm | 219 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/PVE/HA/Makefile |   2 +-
 2 files changed, 220 insertions(+), 1 deletion(-)
 create mode 100644 src/PVE/HA/Fence.pm

diff --git a/src/PVE/HA/Fence.pm b/src/PVE/HA/Fence.pm
new file mode 100644
index 0000000..9a6c1fb
--- /dev/null
+++ b/src/PVE/HA/Fence.pm
@@ -0,0 +1,219 @@
+package PVE::HA::Fence;
+
+use strict;
+use warnings;
+use POSIX qw( WNOHANG );
+use PVE::HA::FenceConfig;
+use Data::Dumper;
+
+
+ # pid's and additional info of fence processes
+my $fence_jobs = {};
+
+# fence state of a node
+my $fenced_nodes = {};
+
+sub has_fencing_job { # update for parallel fencing
+    my ($node) = @_;
+
+    foreach my $job (values %$fence_jobs) {
+	return 1 if ($job->{node} eq $node);
+    }
+    return undef;
+}
+
+my $virtual_pid = 0; # hack for test framework
+
+sub start_fencing {
+    my ($haenv, $node, $try) = @_;
+
+    $try = 0 if !defined($try) || $try<0;
+
+    my $fence_cfg = $haenv->read_fence_config();
+    my $commands = PVE::HA::FenceConfig::get_commands($node, $try, $fence_cfg);
+
+    if (!$commands) {
+	$haenv->log('err', "no commands for node '$node'");
+	$fenced_nodes->{$node}->{failure} = 1;
+	return 0;
+    }
+
+    $haenv->log('notice', "Start fencing of node '$node'");
+
+    my $can_fork = ($haenv->get_max_workers() > 0) ? 1 : 0;
+
+    $fenced_nodes->{$node}->{needed} = scalar @$commands;
+    $fenced_nodes->{$node}->{triggered} = 0;
+
+    for my $cmd (@$commands)
+    {
+	my $cmd_str = "$cmd->{agent} " .
+	    PVE::HA::FenceConfig::gen_arg_str(@{$cmd->{param}});
+	$haenv->log('notice', "[fence '$node'] execute fence command: $cmd_str");
+
+	if ($can_fork) {
+	    my $pid = fork();
+	    if (!defined($pid)) {
+		$haenv->log('err', "forking fence job failed");
+		return 0;
+	    } elsif ($pid==0) { # child
+		$haenv->exec_fence_agent($cmd->{agent}, $node, @{$cmd->{param}});
+		exit(-1);
+	    } else {
+		$fence_jobs->{$pid} = {cmd=>$cmd_str, node=>$node, try=>$try};
+	    }
+	} else {
+	    my $res = -1;
+	    eval {
+		$res = $haenv->exec_fence_agent($cmd->{agent}, $node, @{$cmd->{param}});
+		$res = $res << 8 if $res > 0;
+	    };
+	    if (my $err = $@) {
+		$haenv->log('err', $err);
+	    }
+
+	    $virtual_pid++;
+	    $fence_jobs->{$virtual_pid} = {cmd => $cmd_str, node => $node,
+					   try => $try, ec => $res};
+	}
+    }
+
+    return 1;
+}
+
+
+# check childs and process exit status
+my $check_jobs = sub {
+    my ($haenv) = @_;
+
+    my $succeeded = {};
+    my $failed = {};
+
+    my @finished = ();
+
+    # pick up all finsihed childs if we can fork
+    if ($haenv->get_max_workers() > 0) {
+	while((my $res = waitpid(-1, WNOHANG))>0) {
+	    $fence_jobs->{$res}->{ec} = $? if $fence_jobs->{$res};
+	    push @finished, $res;
+	}
+    } else {
+	@finished = keys %{$fence_jobs};
+    }
+
+    #    while((my $res = waitpid(-1, WNOHANG))>0) {
+    foreach my $res (@finished) {
+	if (my $job = $fence_jobs->{$res}) {
+	    my $ec = $job->{ec};
+
+	    my $status = {
+		exit_code => $ec,
+		cmd => $job->{cmd},
+		try => $job->{try}
+	    };
+
+	    if ($ec == 0) {
+		$succeeded->{$job->{node}} = $status;
+	    } else {
+		$failed->{$job->{node}} = $status;
+	    }
+
+	    delete $fence_jobs->{$res};
+
+	} else {
+	    warn "exit from unknown child (PID=$res)";
+	}
+
+    }
+
+    return ($succeeded, $failed);
+};
+
+
+my $reset_hard = sub {
+    my ($haenv, $node) = @_;
+
+    while (my ($pid, $job) = each %$fence_jobs) {
+	next if $job->{node} ne $node;
+
+	if ($haenv->max_workers() > 0) {
+	    kill KILL => $pid;
+	    # fixme maybe use an timeout even if kill should not hang?
+	    waitpid($pid, 0); # pick it up directly
+	}
+	delete $fence_jobs->{$pid};
+    }
+
+    delete $fenced_nodes->{$node} if $fenced_nodes->{$node};
+};
+
+
+# pick up jobs and process them
+sub process_fencing {
+    my ($haenv) = @_;
+
+    my $fence_cfg = $haenv->read_fence_config();
+
+    my ($succeeded, $failed) = &$check_jobs($haenv);
+
+    foreach my $node (keys %$succeeded) {
+	# count how many fence devices succeeded
+	# this is needed for parallel devices
+	$fenced_nodes->{$node}->{triggered}++;
+    }
+
+    # try next device for failed jobs
+    while(my ($node, $job) = each %$failed) {
+	$haenv->log('err', "fence job failed: '$job->{cmd}' returned '$job->{exit_code}'");
+
+	while($job->{try} < PVE::HA::FenceConfig::count_devices($node, $fence_cfg) )
+	{
+	    &$reset_hard($haenv, $node);
+	    $job->{try}++;
+
+	    return if start_fencing($node, $job->{try});
+
+	    $haenv->log('warn', "Couldn't start fence try '$job->{try}'");
+	}
+
+	    $haenv->log('err', "Tried all fence devices\n");
+	    # fixme: returnproper exit code so CRM waits for the agent lock
+    }
+}
+
+
+sub is_node_fenced {
+    my ($node) = @_;
+
+    my $state = $fenced_nodes->{$node};
+    return 0 if !$state;
+
+    return -1 if $state->{failure} && $state->{failure} == 1;
+
+    return ($state->{needed} && $state->{triggered} &&
+	   $state->{triggered} >= $state->{needed}) ? 1 : 0;
+}
+
+
+sub reset {
+    my ($node, $noerr) = @_;
+
+    delete $fenced_nodes->{$node} if $fenced_nodes->{$node};
+}
+
+
+sub bail_out {
+    my ($haenv) = @_;
+
+    if ($haenv->max_workers() > 0) {
+	foreach my $pid (keys %$fence_jobs) {
+	    kill KILL => $pid;
+	    waitpid($pid, 0); # has to come back directly
+	}
+    }
+
+    $fenced_nodes = {};
+    $fence_jobs = {};
+}
+
+1;
diff --git a/src/PVE/HA/Makefile b/src/PVE/HA/Makefile
index 839300c..436198f 100644
--- a/src/PVE/HA/Makefile
+++ b/src/PVE/HA/Makefile
@@ -1,5 +1,5 @@
 SOURCES=CRM.pm Env.pm Groups.pm Resources.pm Config.pm LRM.pm Manager.pm \
-	NodeStatus.pm Tools.pm FenceConfig.pm
+	NodeStatus.pm Tools.pm FenceConfig.pm Fence.pm
 
 .PHONY: install
 install:
-- 
2.1.4





More information about the pve-devel mailing list