[pve-devel] [RFC ha-manager v2 1/7] add FenceConfig class for external fencing devices

Thomas Lamprecht t.lamprecht at proxmox.com
Fri Mar 11 16:57:10 CET 2016


Add a FenceConfig class which includes methods to parse a config
file for fence devices in the format specified by dlm.conf, see the
Fencing section in the dlm.conf manpage for more details regarding
this format.

With this we can generate commands for fencing a node from the
parsed config file.

We assume that the fence config resides in the pve cluster fs under
/etc/pve/ha/fence_devices.cfg for the PVE2 environment.
But we can give parse_config and arbitary raw string, this allows
to use fencing also in (regression) tests and simultaion.

A simple regression testing script for the config generation was
also added. This test mainly the parse_config and get_commands
methods. A config file can be passed as argument, else we cycle
through the *.cfg files in the fence_cfgs folder.

Example configs for regression testing are located in
src/test/fence_cfgs directory
Note that not all files are valid examples as some are used to check
the error handling of the parser!
---

It has to be said that we allow currently params which may be
incompatible when parsed to a command and executed regarding
escaping in some corner cases. For now this is defend by the reason
that:
 a) only root can set this up and should recheck everything ten times
 b) I want some opinion on the whole series, so some minor flaws
    are accepted
 c) this is experimental and it will be fixed  up when this gets
    stabler and integrated in the API

@wolfgang.b: if you could skim over the parser and give me your
harsh but honest opinion why what is flawed I would be thankful! :-)

 src/PVE/HA/Config.pm                               |  12 +-
 src/PVE/HA/FenceConfig.pm                          | 191 +++++++++++++++++++++
 src/PVE/HA/Makefile                                |   2 +-
 src/test/Makefile                                  |  10 +-
 src/test/fence_cfgs/complex-params-1.cfg           |   5 +
 src/test/fence_cfgs/complex-params-1.cfg.expect    |   8 +
 src/test/fence_cfgs/connect-before-device-1.cfg    |   9 +
 .../fence_cfgs/connect-before-device-1.cfg.expect  |   1 +
 src/test/fence_cfgs/double-device-1.cfg            |   8 +
 src/test/fence_cfgs/double-device-1.cfg.expect     |   1 +
 src/test/fence_cfgs/parallel-1.cfg                 |  29 ++++
 src/test/fence_cfgs/parallel-1.cfg.expect          |  21 +++
 src/test/fence_cfgs/simple-1.cfg                   |   4 +
 src/test/fence_cfgs/simple-1.cfg.expect            |   8 +
 src/test/fence_cfgs/simple-2.cfg                   |  15 ++
 src/test/fence_cfgs/simple-2.cfg.expect            |  16 ++
 src/test/test_fence_config.pl                      | 126 ++++++++++++++
 17 files changed, 460 insertions(+), 6 deletions(-)
 create mode 100644 src/PVE/HA/FenceConfig.pm
 create mode 100644 src/test/fence_cfgs/complex-params-1.cfg
 create mode 100644 src/test/fence_cfgs/complex-params-1.cfg.expect
 create mode 100644 src/test/fence_cfgs/connect-before-device-1.cfg
 create mode 100644 src/test/fence_cfgs/connect-before-device-1.cfg.expect
 create mode 100644 src/test/fence_cfgs/double-device-1.cfg
 create mode 100644 src/test/fence_cfgs/double-device-1.cfg.expect
 create mode 100644 src/test/fence_cfgs/parallel-1.cfg
 create mode 100644 src/test/fence_cfgs/parallel-1.cfg.expect
 create mode 100644 src/test/fence_cfgs/simple-1.cfg
 create mode 100644 src/test/fence_cfgs/simple-1.cfg.expect
 create mode 100644 src/test/fence_cfgs/simple-2.cfg
 create mode 100644 src/test/fence_cfgs/simple-2.cfg.expect
 create mode 100755 src/test/test_fence_config.pl

diff --git a/src/PVE/HA/Config.pm b/src/PVE/HA/Config.pm
index 244aef2..f7440c0 100644
--- a/src/PVE/HA/Config.pm
+++ b/src/PVE/HA/Config.pm
@@ -17,6 +17,7 @@ my $manager_status_filename = "ha/manager_status";
 my $ha_groups_config = "ha/groups.cfg";
 my $ha_resources_config = "ha/resources.cfg";
 my $crm_commands_filename = "ha/crm_commands";
+my $ha_fence_config = "ha/fence.cfg";
 
 cfs_register_file($crm_commands_filename, 
 		  sub { my ($fn, $raw) = @_; return defined($raw) ? $raw : ''; },
@@ -30,6 +31,9 @@ cfs_register_file($ha_resources_config,
 cfs_register_file($manager_status_filename, 
 		  \&json_reader, 
 		  \&json_writer);
+cfs_register_file($ha_fence_config,
+		  \&PVE::HA::FenceConfig::parse_config,
+		  \&PVE::HA::FenceConfig::write_config);
 
 sub json_reader {
     my ($filename, $data) = @_;
@@ -105,10 +109,16 @@ sub read_manager_status {
 
 sub write_manager_status {
     my ($status_obj) = @_;
-    
+
     cfs_write_file($manager_status_filename, $status_obj);
 }
 
+sub read_fence_config {
+    my () = @_;
+
+    cfs_read_file($ha_fence_config);
+}
+
 sub lock_ha_domain {
     my ($code, $errmsg) = @_;
 
diff --git a/src/PVE/HA/FenceConfig.pm b/src/PVE/HA/FenceConfig.pm
new file mode 100644
index 0000000..793a8ab
--- /dev/null
+++ b/src/PVE/HA/FenceConfig.pm
@@ -0,0 +1,191 @@
+package PVE::HA::FenceConfig;
+
+use strict;
+use warnings;
+use PVE::Tools;
+use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file);
+use Data::Dumper;
+
+my $__die = sub {
+    my ($fn, $lineno, $error_message) = @_;
+
+    die "error in '$fn' at $lineno: $error_message\n";
+};
+
+sub parse_config {
+    my ($fn, $raw) = @_;
+
+    $raw = '' if !$raw;
+
+    my $config = {};
+
+    my $lineno = 0;
+    my $priority = 0;
+
+    while ($raw =~ /^\h*(.*?)\h*$/gm) {
+	my $line = $1;
+	$lineno++;
+
+	next if !$line || $line =~ /^#/;
+
+	if ($line =~ m/^(device|connect)\s+(\S+)\s+(\S+)\s+(.+)$/) {
+	    my ($command, $dev_name, $target) = ($1, $2, $3);
+
+	    # allow spaces, and other special chars inside of quoted strings
+	    # with escape support
+	    my @arg_array = $4 =~ /(\w+(?:=(?:(?:\"(?:[^"\\]|\\.)*\")|\S+))?)/g;
+
+	    my $dev_number = 1; # default
+
+	    # check for parallel devices
+	    if ($dev_name =~ m/^(\w+)(:(\d)+)?/) {
+		$dev_name = $1;
+		$dev_number = $3 if $3;
+	    }
+
+	    if ($command eq "device") {
+
+		my $dev = $config->{$dev_name} || {};
+
+		&$__die($fn, $lineno, "device '$dev_name:$dev_number' already declared")
+		    if ($dev && $dev->{sub_devs}->{$dev_number});
+
+		$dev->{sub_devs}->{$dev_number} = {
+		    agent => $target,
+		    args => [ @arg_array ]
+		};
+		$dev->{priority} = $priority++ if !$dev->{priority};
+
+		$config->{$dev_name} = $dev;
+
+	    } else { # connect nodes to devices
+
+		&$__die($fn, $lineno, "device '$dev_name' must be declared before" .
+			       " you can connect to it") if !$config->{$dev_name};
+
+		&$__die($fn, $lineno, "No parallel device '$dev_name:$dev_number' found")
+		    if !$config->{$dev_name}->{sub_devs}->{$dev_number};
+
+		my $sdev = $config->{$dev_name}->{sub_devs}->{$dev_number};
+
+		my ($node) = $target =~ /node=(\w+)/;
+		&$__die($fn, $lineno, "node=nodename needed to connect device ".
+			  "'$dev_name' to node") if !$node;
+
+		&$__die($fn, $lineno, "node '$node' already connected to device ".
+			  "'$dev_name:$dev_number'") if $sdev->{node_args}->{$node};
+
+		$sdev->{node_args}->{$node} = [ @arg_array ];
+
+		$config->{$dev_name}->{sub_devs}->{$dev_number} = $sdev;
+
+	    }
+
+	} else {
+	    warn "$fn ignore line $lineno: $line\n"
+	}
+    }
+
+    return $config;
+
+}
+
+sub write_config {
+    my ($fn, $data) = @_;
+
+    my $raw = '';
+
+    foreach my $dev_name (sort {$a->{priority} <=> $b->{priority}} keys %$data) {
+	my $d = $data->{$dev_name};
+
+	foreach my $sub_dev_nr (sort keys %{$d->{sub_devs}}) {
+	    my $sub_dev = $d->{sub_devs}->{$sub_dev_nr};
+
+	    my $dev_arg_str = gen_arg_str(@{$sub_dev->{args}});
+
+	    $raw .= "\ndevice $dev_name:$sub_dev_nr $sub_dev->{agent} $dev_arg_str\n";
+	    foreach my $node (sort keys %{$sub_dev->{node_args}}) {
+		my $node_args = $sub_dev->{node_args}->{$node};
+		my $node_arg_str = gen_arg_str(@{$node_args});
+
+		$raw .= "connect $dev_name:$sub_dev_nr $node_arg_str\n";
+	    }
+	}
+    }
+
+    return $raw;
+}
+
+
+
+sub gen_arg_str {
+    my (@arguments) = @_;
+
+    my @shell_args = ();
+    foreach my $arg (@arguments) {
+	# we need to differ long and short opts!
+	my $prefix = (length($arg) == 1) ? '-' : '--';
+	push @shell_args, "$prefix$arg";
+    }
+
+    return join (' ', @shell_args);
+}
+
+
+# returns command list to execute,
+# can be more than one command if parallel devices are configured
+# 'try' denotes the number of devices we should skip and normaly equals to
+# failed fencing tries
+sub get_commands {
+    my ($node, $try, $config) = @_;
+
+    return undef if !$node || !$config;
+
+    $try = 0 if !$try || $try<0;
+
+    foreach my $device (sort {$a->{priority} <=> $b->{priority}} values %$config) {
+	my @commands;
+
+	#foreach my $sub_dev (values %{$device->{sub_devs}}) {
+	foreach my $sub_dev_nr (sort keys %{$device->{sub_devs}}) {
+	    my $sub_dev = $device->{sub_devs}->{$sub_dev_nr};
+
+	    if (my $node_args = $sub_dev->{node_args}->{$node}) {
+		push @commands, { agent=>$sub_dev->{agent},
+				  sub_dev => $sub_dev_nr,
+				  param => [@{$sub_dev->{args}}, @{$node_args}]};
+	    }
+
+	}
+
+	if (@commands>0) {
+	    $try--;
+	    return [ @commands ] if $try<0;
+	}
+    }
+
+    # out of tries or no device for this node
+    return undef;
+}
+
+
+sub count_devices {
+    my ($node, $config) = @_;
+
+    my $count = 0;
+
+    return 0 if !$config;
+
+    foreach my $device (values %$config) {
+	foreach my $sub_dev (values %{$device->{sub_devs}}) {
+	    if ($sub_dev->{node_args}->{$node}) {
+		$count++;
+		last; # no need to count parallel devices
+	    }
+	}
+    }
+
+    return $count;
+}
+
+1;
diff --git a/src/PVE/HA/Makefile b/src/PVE/HA/Makefile
index 00731e2..43e37cb 100644
--- a/src/PVE/HA/Makefile
+++ b/src/PVE/HA/Makefile
@@ -1,4 +1,4 @@
-SOURCES=CRM.pm Env.pm Groups.pm Resources.pm Config.pm LRM.pm Manager.pm NodeStatus.pm Tools.pm
+SOURCES=CRM.pm Env.pm Groups.pm Resources.pm Config.pm LRM.pm Manager.pm NodeStatus.pm Tools.pm FenceConfig.pm
 
 .PHONY: install
 install:
diff --git a/src/test/Makefile b/src/test/Makefile
index c772c69..6b1d4f9 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -3,11 +3,13 @@ all:
 
 .PHONY: test
 test:
-	echo "running regression tests"
+	@echo "-- start regression tests --"
 	./test_failover1.pl
-	./ha-tester.pl	
-	echo "end regression tests (success)"
+	./ha-tester.pl
+	./test_fence_config.pl
+	@echo "-- end regression tests (success) --"
 
 .PHONY: clean
 clean:
-	rm -rf *~ test-*/log  test-*/*~ test-*/status
+	rm -rf *~ test-*/log  test-*/*~ test-*/status \
+	fence_cfgs/*.cfg.commands
diff --git a/src/test/fence_cfgs/complex-params-1.cfg b/src/test/fence_cfgs/complex-params-1.cfg
new file mode 100644
index 0000000..8866841
--- /dev/null
+++ b/src/test/fence_cfgs/complex-params-1.cfg
@@ -0,0 +1,5 @@
+# test escaping, special chars, ...
+device pve_nina fence_pve ip="192.168.15.38" username="fäöµ€" password="12\"345"
+# test shortopt
+connect pve_nina node=node1 plug=500 x
+connect pve_nina node=node2 plug=501
diff --git a/src/test/fence_cfgs/complex-params-1.cfg.expect b/src/test/fence_cfgs/complex-params-1.cfg.expect
new file mode 100644
index 0000000..077c795
--- /dev/null
+++ b/src/test/fence_cfgs/complex-params-1.cfg.expect
@@ -0,0 +1,8 @@
+ [try] 0
+ [node1-cmd] fence_pve --ip="192.168.15.38" --username="fäöµ€" --password="12\"345" --plug=500 -x
+ [node2-cmd] fence_pve --ip="192.168.15.38" --username="fäöµ€" --password="12\"345" --plug=501
+ [node3-cmd] none
+ [try] 1
+ [node1-cmd] none
+ [node2-cmd] none
+ [node3-cmd] none
diff --git a/src/test/fence_cfgs/connect-before-device-1.cfg b/src/test/fence_cfgs/connect-before-device-1.cfg
new file mode 100644
index 0000000..ee0a5aa
--- /dev/null
+++ b/src/test/fence_cfgs/connect-before-device-1.cfg
@@ -0,0 +1,9 @@
+# must fail as we try to connect a node with a device which isn't declared yet
+
+connect first_dev node=node1 plug=100
+connect first_dev node=node2 plug=101
+
+# only now declare the device
+device first_dev fence_pve ip="192.168.XX.XX" password="12345" action=off
+
+connect first_dev node=node3 plug=102
diff --git a/src/test/fence_cfgs/connect-before-device-1.cfg.expect b/src/test/fence_cfgs/connect-before-device-1.cfg.expect
new file mode 100644
index 0000000..49199a5
--- /dev/null
+++ b/src/test/fence_cfgs/connect-before-device-1.cfg.expect
@@ -0,0 +1 @@
+ [FenceConfig] error in 'fence_cfgs/connect-before-device-1.cfg' at 3: device 'first_dev' must be declared before you can connect to it
diff --git a/src/test/fence_cfgs/double-device-1.cfg b/src/test/fence_cfgs/double-device-1.cfg
new file mode 100644
index 0000000..a2d65e1
--- /dev/null
+++ b/src/test/fence_cfgs/double-device-1.cfg
@@ -0,0 +1,8 @@
+# must fail as we try to add a device which is already defined
+device unique_dev fence_pve ip="192.168.XX.XX" password="12345" action=off
+connect unique_dev node=node1 plug=100
+connect unique_dev node=node2 plug=101
+connect unique_dev node=node3 plug=102
+
+device unique_dev fence_pve ip="192.168.XX.XX" password="12345" action=off
+connect unique_dev node=node1 plug=100
diff --git a/src/test/fence_cfgs/double-device-1.cfg.expect b/src/test/fence_cfgs/double-device-1.cfg.expect
new file mode 100644
index 0000000..2d346bc
--- /dev/null
+++ b/src/test/fence_cfgs/double-device-1.cfg.expect
@@ -0,0 +1 @@
+ [FenceConfig] error in 'fence_cfgs/double-device-1.cfg' at 7: device 'unique_dev:1' already declared
diff --git a/src/test/fence_cfgs/parallel-1.cfg b/src/test/fence_cfgs/parallel-1.cfg
new file mode 100644
index 0000000..c238e4d
--- /dev/null
+++ b/src/test/fence_cfgs/parallel-1.cfg
@@ -0,0 +1,29 @@
+# parallel devices
+device power:1 fence_apc ip=192.168.XX.XY username=fencing password=12345 action=off ssh
+device power:2 fence_apc ip=192.168.YY.YX username=fencing password=12345 action=off ssh delay=5
+
+connect power:1 node=node1 plug=1 switch=1
+connect power:2 node=node1 plug=1 switch=1
+
+connect power:1 node=node2 plug=2 switch=1
+connect power:2 node=node2 plug=2 switch=1
+
+connect power:1 node=node3 plug=3 switch=1
+connect power:2 node=node3 plug=3 switch=1
+
+# test comment
+device switch:1 fence_cisco_mds ip=192.168.XYZ.ZXY username=fencing password=12345 action=off ssh
+device switch:2 fence_cisco_mds ip=192.168.XYZ.ZYX username=fencing password=12345 action=reboot ssh
+device switch:3 fence_cisco_mds ip=192.168.XYZ.YXZ username=fencing password=12345 action=off
+
+connect switch:1 node=node1 plug=10 switch=1
+connect switch:2 node=node1 plug=10 switch=1
+connect switch:3 node=node1 plug=10 switch=1
+
+connect switch:1 node=node2 plug=20 switch=1
+connect switch:2 node=node2 plug=20 switch=1
+connect switch:3 node=node2 plug=20 switch=1
+
+connect switch:1 node=node3 plug=30 switch=1
+connect switch:2 node=node3 plug=30 switch=1
+connect switch:3 node=node3 plug=30 switch=1
diff --git a/src/test/fence_cfgs/parallel-1.cfg.expect b/src/test/fence_cfgs/parallel-1.cfg.expect
new file mode 100644
index 0000000..d507086
--- /dev/null
+++ b/src/test/fence_cfgs/parallel-1.cfg.expect
@@ -0,0 +1,21 @@
+ [try] 0
+ [node1-cmd] fence_apc --ip=192.168.XX.XY --username=fencing --password=12345 --action=off --ssh --plug=1 --switch=1
+ [node1-cmd] fence_apc --ip=192.168.YY.YX --username=fencing --password=12345 --action=off --ssh --delay=5 --plug=1 --switch=1
+ [node2-cmd] fence_apc --ip=192.168.XX.XY --username=fencing --password=12345 --action=off --ssh --plug=2 --switch=1
+ [node2-cmd] fence_apc --ip=192.168.YY.YX --username=fencing --password=12345 --action=off --ssh --delay=5 --plug=2 --switch=1
+ [node3-cmd] fence_apc --ip=192.168.XX.XY --username=fencing --password=12345 --action=off --ssh --plug=3 --switch=1
+ [node3-cmd] fence_apc --ip=192.168.YY.YX --username=fencing --password=12345 --action=off --ssh --delay=5 --plug=3 --switch=1
+ [try] 1
+ [node1-cmd] fence_cisco_mds --ip=192.168.XYZ.ZXY --username=fencing --password=12345 --action=off --ssh --plug=10 --switch=1
+ [node1-cmd] fence_cisco_mds --ip=192.168.XYZ.ZYX --username=fencing --password=12345 --action=reboot --ssh --plug=10 --switch=1
+ [node1-cmd] fence_cisco_mds --ip=192.168.XYZ.YXZ --username=fencing --password=12345 --action=off --plug=10 --switch=1
+ [node2-cmd] fence_cisco_mds --ip=192.168.XYZ.ZXY --username=fencing --password=12345 --action=off --ssh --plug=20 --switch=1
+ [node2-cmd] fence_cisco_mds --ip=192.168.XYZ.ZYX --username=fencing --password=12345 --action=reboot --ssh --plug=20 --switch=1
+ [node2-cmd] fence_cisco_mds --ip=192.168.XYZ.YXZ --username=fencing --password=12345 --action=off --plug=20 --switch=1
+ [node3-cmd] fence_cisco_mds --ip=192.168.XYZ.ZXY --username=fencing --password=12345 --action=off --ssh --plug=30 --switch=1
+ [node3-cmd] fence_cisco_mds --ip=192.168.XYZ.ZYX --username=fencing --password=12345 --action=reboot --ssh --plug=30 --switch=1
+ [node3-cmd] fence_cisco_mds --ip=192.168.XYZ.YXZ --username=fencing --password=12345 --action=off --plug=30 --switch=1
+ [try] 2
+ [node1-cmd] none
+ [node2-cmd] none
+ [node3-cmd] none
diff --git a/src/test/fence_cfgs/simple-1.cfg b/src/test/fence_cfgs/simple-1.cfg
new file mode 100644
index 0000000..6bf9545
--- /dev/null
+++ b/src/test/fence_cfgs/simple-1.cfg
@@ -0,0 +1,4 @@
+device pve_nina fence_pve ip="192.168.15.38" username=fence password="12345" action=off
+connect pve_nina node=node1 plug=500
+connect pve_nina node=node2 plug=501
+connect pve_nina node=node3 plug=502
diff --git a/src/test/fence_cfgs/simple-1.cfg.expect b/src/test/fence_cfgs/simple-1.cfg.expect
new file mode 100644
index 0000000..14d38bb
--- /dev/null
+++ b/src/test/fence_cfgs/simple-1.cfg.expect
@@ -0,0 +1,8 @@
+ [try] 0
+ [node1-cmd] fence_pve --ip="192.168.15.38" --username=fence --password="12345" --action=off --plug=500
+ [node2-cmd] fence_pve --ip="192.168.15.38" --username=fence --password="12345" --action=off --plug=501
+ [node3-cmd] fence_pve --ip="192.168.15.38" --username=fence --password="12345" --action=off --plug=502
+ [try] 1
+ [node1-cmd] none
+ [node2-cmd] none
+ [node3-cmd] none
diff --git a/src/test/fence_cfgs/simple-2.cfg b/src/test/fence_cfgs/simple-2.cfg
new file mode 100644
index 0000000..ad13504
--- /dev/null
+++ b/src/test/fence_cfgs/simple-2.cfg
@@ -0,0 +1,15 @@
+# simple devices
+device first_dev fence_pve ip="192.168.XX.XX" password="12345" action=off
+connect first_dev node=node1 plug=100
+connect first_dev node=node2 plug=101
+connect first_dev node=node3 plug=102
+
+device second_dev fence_ilo ip="192.168.XY.XY" password="54321" action=off
+connect second_dev node=node1 plug=100
+connect second_dev node=node2 plug=101
+connect second_dev node=node3 plug=102
+
+device third_dev fence_apc_snmp ip="192.168.XY.YXY" password="12345" action=off
+connect third_dev node=node1 plug=100
+connect third_dev node=node2 plug=101
+connect third_dev node=node3 plug=102
\ No newline at end of file
diff --git a/src/test/fence_cfgs/simple-2.cfg.expect b/src/test/fence_cfgs/simple-2.cfg.expect
new file mode 100644
index 0000000..1c322d6
--- /dev/null
+++ b/src/test/fence_cfgs/simple-2.cfg.expect
@@ -0,0 +1,16 @@
+ [try] 0
+ [node1-cmd] fence_pve --ip="192.168.XX.XX" --password="12345" --action=off --plug=100
+ [node2-cmd] fence_pve --ip="192.168.XX.XX" --password="12345" --action=off --plug=101
+ [node3-cmd] fence_pve --ip="192.168.XX.XX" --password="12345" --action=off --plug=102
+ [try] 1
+ [node1-cmd] fence_ilo --ip="192.168.XY.XY" --password="54321" --action=off --plug=100
+ [node2-cmd] fence_ilo --ip="192.168.XY.XY" --password="54321" --action=off --plug=101
+ [node3-cmd] fence_ilo --ip="192.168.XY.XY" --password="54321" --action=off --plug=102
+ [try] 2
+ [node1-cmd] fence_apc_snmp --ip="192.168.XY.YXY" --password="12345" --action=off --plug=100
+ [node2-cmd] fence_apc_snmp --ip="192.168.XY.YXY" --password="12345" --action=off --plug=101
+ [node3-cmd] fence_apc_snmp --ip="192.168.XY.YXY" --password="12345" --action=off --plug=102
+ [try] 3
+ [node1-cmd] none
+ [node2-cmd] none
+ [node3-cmd] none
diff --git a/src/test/test_fence_config.pl b/src/test/test_fence_config.pl
new file mode 100755
index 0000000..1071600
--- /dev/null
+++ b/src/test/test_fence_config.pl
@@ -0,0 +1,126 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Getopt::Long;
+
+use File::Path qw(make_path remove_tree);
+
+use lib '..';
+
+use PVE::Tools;
+use PVE::HA::FenceConfig;
+
+use Data::Dumper;
+
+my $opt_nodiff;
+
+if (!GetOptions ("nodiff"   => \$opt_nodiff)) {
+    print "usage: $0 testdir [--nodiff]\n";
+    exit -1;
+}
+
+my $log = sub {
+    my ($fh, $source, $message) = @_;
+
+    my $msg = '';
+
+    if($source) {
+	chomp $message;
+	$msg = " [$source] $message";
+    }
+
+    print "$msg\n";
+
+    $fh->print("$msg\n");
+    $fh->flush();
+};
+
+my $get_status = sub {
+    # fixme? use status file so different node setups
+    # per test are possible
+    return ('node1', 'node2', 'node3');
+};
+
+my $check_cfg = sub {
+    my ($cfg_fn, $outfile) = @_;
+
+    my $raw = PVE::Tools::file_get_contents($cfg_fn);
+
+    my $log_fh = IO::File->new(">$outfile") ||
+	die "unable to open '$outfile' - $!";
+
+    my $config;
+    eval {
+	$config = PVE::HA::FenceConfig::parse_config($cfg_fn, $raw);
+    };
+    if (my $err = $@) {
+	&$log($log_fh, 'FenceConfig', $err);
+	return;
+    }
+
+    my @nodes = &$get_status();
+
+    # cycle through all nodes with some tries
+    for (my $i=0; 1; $i++) {
+	&$log($log_fh, "try", $i);
+
+	my $node_has_cmd = 0;
+	foreach my $node (@nodes) {
+
+	    my $commands = PVE::HA::FenceConfig::get_commands($node, $i, $config);
+	    if($commands) {
+		$node_has_cmd = 1;
+		foreach my $cmd (sort { $a->{sub_dev} <=> $b->{sub_dev} }  @$commands) {
+		    my $cmd_str = "$cmd->{agent} " .
+		       PVE::HA::FenceConfig::gen_arg_str(@{$cmd->{param}});
+		    &$log($log_fh, "$node-cmd", "$cmd_str");
+		}
+	    } else {
+		&$log($log_fh, "$node-cmd", "none");
+	    }
+
+	}
+	# end if no node has a device left
+	last if !$node_has_cmd;
+    }
+};
+
+sub run_test {
+    my $cfg_fn = shift;
+
+    print "check: $cfg_fn\n";
+
+    my $outfile = "$cfg_fn.commands";
+    my $expect = "$cfg_fn.expect";
+
+    eval {
+	&$check_cfg($cfg_fn, $outfile);
+    };
+    if (my $err = $@) {
+	die "Test '$cfg_fn' failed:\n$err\n";
+    }
+
+    return if $opt_nodiff;
+
+    my $res;
+
+    if (-f $expect) {
+	my $cmd = ['diff', '-u', $expect, $outfile];
+	$res = system(@$cmd);
+	die "test '$cfg_fn' failed\n" if $res != 0;
+    } else {
+	$res = system('cp', $outfile, $expect);
+	die "test '$cfg_fn' failed\n" if $res != 0;
+    }
+    print "end: $cfg_fn (success)\n";
+}
+
+
+if (my $testcfg = shift) {
+    run_test($testcfg);
+} else {
+    foreach my $cfg (<fence_cfgs/*.cfg>) {
+	run_test($cfg);
+    }
+}
-- 
2.1.4





More information about the pve-devel mailing list