]> git.proxmox.com Git - pve-ha-manager.git/blob - src/PVE/HA/FenceConfig.pm
fix #1919, #1920: improve handling zombie (without node) services
[pve-ha-manager.git] / src / PVE / HA / FenceConfig.pm
1 package PVE::HA::FenceConfig;
2
3 use strict;
4 use warnings;
5
6 use PVE::Tools;
7
8 sub parse_config {
9 my ($fn, $raw) = @_;
10
11 return {} if !$raw;
12
13 my $config = {};
14
15 my $lineno = 0;
16 my $priority = 0;
17 my $parse_errors = '';
18
19 my $parse_line = sub {
20 my ($line) = @_;
21
22 if ($line !~ m/^(device|connect)\s+(\S+)\s+(\S+)\s+(.+)$/) {
23 warn "$fn ignore line $lineno: $line\n";
24 return;
25 }
26 my ($command, $dev_name, $target) = ($1, $2, $3);
27 my $arg_array = PVE::Tools::split_args($4);
28 my $dev_number = 1; # default
29
30 # check for parallel devices
31 if ($dev_name =~ m/^(\w+)(:(\d+))?/) {
32 $dev_name = $1;
33 $dev_number = $3 if $3;
34 }
35
36 if ($command eq "device") {
37 my $dev = $config->{$dev_name} || {};
38
39 die "device '$dev_name:$dev_number' already declared\n"
40 if $dev && $dev->{sub_devs}->{$dev_number};
41
42 $dev->{sub_devs}->{$dev_number} = {
43 agent => $target,
44 args => $arg_array,
45 };
46 $dev->{priority} = $priority++ if !$dev->{priority};
47
48 $config->{$dev_name} = $dev;
49
50 } elsif ($command eq 'connect') { # connect nodes to devices
51 die "device '$dev_name' must be declared before you can connect to it\n"
52 if !$config->{$dev_name};
53
54 die "No parallel device '$dev_name:$dev_number' found\n"
55 if !$config->{$dev_name}->{sub_devs}->{$dev_number};
56
57 my $sdev = $config->{$dev_name}->{sub_devs}->{$dev_number};
58
59 my ($node) = $target =~ /node=(\w+)/;
60 die "node=nodename needed to connect device '$dev_name' to node\n"
61 if !$node;
62
63 die "node '$node' already connected to device '$dev_name:$dev_number'\n"
64 if $sdev->{node_args}->{$node};
65
66 $sdev->{node_args}->{$node} = $arg_array;
67
68 $config->{$dev_name}->{sub_devs}->{$dev_number} = $sdev;
69 # } elsif ($command eq 'fence_all') { # FIXME: TODO
70 } else {
71 die "command '$command' not implemented!";
72 }
73 };
74
75 while ($raw =~ /^\h*(.*?)\h*$/gm) {
76 my $line = $1;
77 $lineno++;
78 next if !$line || $line =~ /^#/;
79
80 eval { $parse_line->($line) };
81 if (my $err = $@) {
82 $parse_errors .= "line $lineno: $err";
83 }
84 }
85 die "Encountered error(s) on parsing '$fn':\n$parse_errors" if $parse_errors;
86
87 return $config;
88 }
89
90 sub write_config {
91 my ($fn, $data) = @_;
92
93 my $raw = '';
94
95 my $prev_priority = -1;
96
97 foreach my $dev_name (sort {$data->{$a}->{priority} <=> $data->{$b}->{priority}} keys %$data) {
98 my $d = $data->{$dev_name};
99
100 die "Device '$dev_name' reuses priority! Priorities must be unique\n"
101 if $prev_priority == $d->{priority};
102
103 $prev_priority = $d->{priority};
104
105 foreach my $sub_dev_nr (sort keys %{$d->{sub_devs}}) {
106 my $sub_dev = $d->{sub_devs}->{$sub_dev_nr};
107 my $dev_arg_str = PVE::Tools::cmd2string($sub_dev->{args});
108
109 $raw .= "\ndevice $dev_name:$sub_dev_nr $sub_dev->{agent} $dev_arg_str\n";
110
111 foreach my $node (sort keys %{$sub_dev->{node_args}}) {
112 my $node_arg_str = join (' ', @{$sub_dev->{node_args}->{$node}});
113
114 $raw .= "connect $dev_name:$sub_dev_nr node=$node $node_arg_str\n";
115 }
116 }
117 }
118
119 return $raw;
120 }
121
122
123
124 sub gen_arg_str {
125 my (@arguments) = @_;
126
127 my @shell_args = ();
128 foreach my $arg (@arguments) {
129 my ($key, $val) = split /=/, $arg;
130 # we need to differ long and short opts!
131 if (length($key) == 1) {
132 push @shell_args, "-${key}";
133 push @shell_args, PVE::Tools::shellquote($val) if defined($val);
134 } else {
135 $key .= '='. PVE::Tools::shellquote($val) if defined($val);
136 push @shell_args, "--$key";
137 }
138 }
139
140 return join (' ', @shell_args);
141 }
142
143
144 # returns command list to execute,
145 # can be more than one command if parallel devices are configured
146 # 'try' denotes the number of devices we should skip and normaly equals to
147 # failed fencing tries
148 sub get_commands {
149 my ($node, $try, $config) = @_;
150
151 return undef if !$node || !$config;
152
153 $try = 0 if !$try || $try<0;
154
155 foreach my $device (sort {$a->{priority} <=> $b->{priority}} values %$config) {
156 my @commands;
157
158 #foreach my $sub_dev (values %{$device->{sub_devs}}) {
159 foreach my $sub_dev_nr (sort keys %{$device->{sub_devs}}) {
160 my $sub_dev = $device->{sub_devs}->{$sub_dev_nr};
161
162 if (my $node_args = $sub_dev->{node_args}->{$node}) {
163 push @commands, { agent=>$sub_dev->{agent},
164 sub_dev => $sub_dev_nr,
165 param => [@{$sub_dev->{args}}, @{$node_args}]};
166 }
167
168 }
169
170 if (@commands>0) {
171 $try--;
172 return [ @commands ] if $try<0;
173 }
174 }
175
176 # out of tries or no device for this node
177 return undef;
178 }
179
180
181 sub count_devices {
182 my ($node, $config) = @_;
183
184 my $count = 0;
185
186 return 0 if !$config;
187
188 foreach my $device (values %$config) {
189 foreach my $sub_dev (values %{$device->{sub_devs}}) {
190 if ($sub_dev->{node_args}->{$node}) {
191 $count++;
192 last; # no need to count parallel devices multiple times
193 }
194 }
195 }
196
197 return $count;
198 }
199
200 1;