]> git.proxmox.com Git - pve-ha-manager.git/blame - src/PVE/HA/Config.pm
fix #1919, #1920: improve handling zombie (without node) services
[pve-ha-manager.git] / src / PVE / HA / Config.pm
CommitLineData
cc32b737
DM
1package PVE::HA::Config;
2
3use strict;
4use warnings;
139a9b90 5use JSON;
cc32b737 6
139a9b90 7use PVE::HA::Tools;
cc32b737 8use PVE::HA::Groups;
139a9b90 9use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
d6edb3ea 10use PVE::HA::Resources;
cc32b737 11
139a9b90
DM
12my $manager_status_filename = "ha/manager_status";
13my $ha_groups_config = "ha/groups.cfg";
14my $ha_resources_config = "ha/resources.cfg";
15my $crm_commands_filename = "ha/crm_commands";
f83e7e6e 16my $ha_fence_config = "ha/fence.cfg";
139a9b90 17
289e4784 18cfs_register_file($crm_commands_filename,
139a9b90
DM
19 sub { my ($fn, $raw) = @_; return defined($raw) ? $raw : ''; },
20 sub { my ($fn, $raw) = @_; return $raw; });
289e4784 21cfs_register_file($ha_groups_config,
139a9b90
DM
22 sub { PVE::HA::Groups->parse_config(@_); },
23 sub { PVE::HA::Groups->write_config(@_); });
289e4784 24cfs_register_file($ha_resources_config,
139a9b90
DM
25 sub { PVE::HA::Resources->parse_config(@_); },
26 sub { PVE::HA::Resources->write_config(@_); });
289e4784
TL
27cfs_register_file($manager_status_filename,
28 \&json_reader,
139a9b90 29 \&json_writer);
f83e7e6e
TL
30cfs_register_file($ha_fence_config,
31 \&PVE::HA::FenceConfig::parse_config,
32 \&PVE::HA::FenceConfig::write_config);
139a9b90
DM
33
34sub json_reader {
35 my ($filename, $data) = @_;
36
f1817b68 37 return defined($data) && length($data) > 0 ? decode_json($data) : {};
139a9b90
DM
38}
39
40sub json_writer {
41 my ($filename, $data) = @_;
42
43 return encode_json($data);
44}
45
46sub read_lrm_status {
47 my ($node) = @_;
48
49 die "undefined node" if !defined($node);
50
6529b6a4
FG
51 my $cfs_path = "nodes/$node/lrm_status";
52
53 my $raw = PVE::Cluster::get_config($cfs_path);
d9906847
TL
54 if (!defined($raw)) {
55 # ENOENT -> possible deleted node, don't die here as it breaks our node
56 # 'gone' logic
57 warn "unable to read file '/etc/pve/$cfs_path'\n";
58 # unkown mode set explicitly as 'active' is assumed as default..
59 return { mode => 'unknown' } if ! -e "/etc/pve/$cfs_path";
60 }
139a9b90 61
6529b6a4 62 return json_reader(undef, $raw);
139a9b90
DM
63}
64
65sub write_lrm_status {
66 my ($node, $status_obj) = @_;
67
68 die "undefined node" if !defined($node);
69
70 my $filename = "/etc/pve/nodes/$node/lrm_status";
71
289e4784 72 PVE::HA::Tools::write_json_to_file($filename, $status_obj);
139a9b90
DM
73}
74
cc32b737
DM
75sub parse_groups_config {
76 my ($filename, $raw) = @_;
77
139a9b90 78 return PVE::HA::Groups->parse_config($filename, $raw);
cc32b737
DM
79}
80
ce216792 81sub parse_resources_config {
cc32b737 82 my ($filename, $raw) = @_;
289e4784 83
cc32b737
DM
84 return PVE::HA::Resources->parse_config($filename, $raw);
85}
86
139a9b90
DM
87sub read_resources_config {
88
ada4b9a8 89 return cfs_read_file($ha_resources_config);
139a9b90
DM
90}
91
85f6e9ca
TL
92# checks if resource exists and sets defaults for unset values
93sub read_and_check_resources_config {
94
ada4b9a8 95 my $res = cfs_read_file($ha_resources_config);
85f6e9ca
TL
96
97 my $vmlist = PVE::Cluster::get_vmlist();
98 my $conf = {};
99
100 foreach my $sid (keys %{$res->{ids}}) {
101 my $d = $res->{ids}->{$sid};
0087839a 102 my (undef, undef, $name) = parse_sid($sid);
bb07bd2c
TL
103 $d->{state} = 'started' if !defined($d->{state});
104 $d->{state} = 'started' if $d->{state} eq 'enabled'; # backward compatibility
85f6e9ca
TL
105 $d->{max_restart} = 1 if !defined($d->{max_restart});
106 $d->{max_relocate} = 1 if !defined($d->{max_relocate});
107 if (PVE::HA::Resources->lookup($d->{type})) {
108 if (my $vmd = $vmlist->{ids}->{$name}) {
b6da6101
TL
109 $d->{node} = $vmd->{node};
110 $conf->{$sid} = $d;
85f6e9ca 111 } else {
fbda2658
TL
112 # undef $d->{node} is handled in get_verbose_service_state and
113 # status API, don't spam logs or ignore it; allow to delete it!
114 $conf->{$sid} = $d;
85f6e9ca
TL
115 }
116 }
117 }
118
119 return $conf;
120}
121
1d9316ef
FE
122sub update_resources_config {
123 my ($sid, $param, $delete, $digest) = @_;
124
125 lock_ha_domain(
126 sub {
127 my $cfg = read_resources_config();
128 ($sid, my $type, my $name) = parse_sid($sid);
129
130 PVE::SectionConfig::assert_if_modified($cfg, $digest);
131
132 my $scfg = $cfg->{ids}->{$sid} ||
133 die "no such resource '$sid'\n";
134
135 my $plugin = PVE::HA::Resources->lookup($scfg->{type});
136 my $opts = $plugin->check_config($sid, $param, 0, 1);
137
138 foreach my $k (%$opts) {
139 $scfg->{$k} = $opts->{$k};
140 }
141
142 if ($delete) {
143 my $options = $plugin->private()->{options}->{$type};
144 foreach my $k (PVE::Tools::split_list($delete)) {
145 my $d = $options->{$k} ||
146 die "no such option '$k'\n";
147 die "unable to delete required option '$k'\n"
148 if !$d->{optional};
149 die "unable to delete fixed option '$k'\n"
150 if $d->{fixed};
151 delete $scfg->{$k};
152 }
153 }
154
155 write_resources_config($cfg);
156 }, "update resources config failed");
157}
158
0087839a
FG
159sub parse_sid {
160 my ($sid) = @_;
161
162 my ($type, $name);
163
164 if ($sid =~ m/^(\d+)$/) {
165 $name = $1;
166
167 my $vmlist = PVE::Cluster::get_vmlist();
168 if (defined($vmlist->{ids}->{$name})) {
169 my $vm_type = $vmlist->{ids}->{$name}->{type};
170 if ($vm_type eq 'lxc') {
171 $type = 'ct';
172 } elsif ($vm_type eq 'qemu') {
173 $type = 'vm';
174 } else {
175 die "internal error";
176 }
177 $sid = "$type:$name";
178 }
179 else {
180 die "unable do add resource - VM/CT $1 does not exist\n";
181 }
182 } elsif ($sid =~m/^(\S+):(\S+)$/) {
183 $name = $2;
184 $type = $1;
185 } else {
186 die "unable to parse service id '$sid'\n";
187 }
188
189 return wantarray ? ($sid, $type, $name) : $sid;
190}
191
139a9b90 192sub read_group_config {
139a9b90 193
ada4b9a8 194 return cfs_read_file($ha_groups_config);
139a9b90
DM
195}
196
9eb0f126
DM
197sub write_group_config {
198 my ($cfg) = @_;
199
200 cfs_write_file($ha_groups_config, $cfg);
201}
202
139a9b90
DM
203sub write_resources_config {
204 my ($cfg) = @_;
205
206 cfs_write_file($ha_resources_config, $cfg);
207}
208
209sub read_manager_status {
210 my () = @_;
211
ada4b9a8 212 return cfs_read_file($manager_status_filename);
139a9b90
DM
213}
214
215sub write_manager_status {
216 my ($status_obj) = @_;
f83e7e6e 217
139a9b90
DM
218 cfs_write_file($manager_status_filename, $status_obj);
219}
220
f83e7e6e
TL
221sub read_fence_config {
222 my () = @_;
223
ada4b9a8 224 cfs_read_file($ha_fence_config);
f83e7e6e
TL
225}
226
b3c07baa
TL
227sub write_fence_config {
228 my ($cfg) = @_;
229
230 cfs_write_file($ha_fence_config, $cfg);
231}
232
66c7e7ef 233sub lock_ha_domain {
139a9b90
DM
234 my ($code, $errmsg) = @_;
235
66c7e7ef 236 my $res = PVE::Cluster::cfs_lock_domain("ha", undef, $code);
139a9b90
DM
237 my $err = $@;
238 if ($err) {
239 $errmsg ? die "$errmsg: $err" : die $err;
240 }
241 return $res;
242}
243
244sub queue_crm_commands {
245 my ($cmd) = @_;
246
247 chomp $cmd;
248
249 my $code = sub {
250 my $data = cfs_read_file($crm_commands_filename);
251 $data .= "$cmd\n";
252 cfs_write_file($crm_commands_filename, $data);
253 };
254
66c7e7ef 255 return lock_ha_domain($code);
139a9b90
DM
256}
257
258sub read_crm_commands {
259
260 my $code = sub {
ada4b9a8 261 my $data = cfs_read_file($crm_commands_filename);
139a9b90
DM
262 cfs_write_file($crm_commands_filename, '');
263 return $data;
264 };
265
66c7e7ef 266 return lock_ha_domain($code);
139a9b90
DM
267}
268
0e703a14 269my $service_check_ha_state = sub {
e709d3d0
DM
270 my ($conf, $sid, $has_state) = @_;
271
272 if (my $d = $conf->{ids}->{$sid}) {
667670b2
TL
273 if (!defined($has_state)) {
274 # ignored service behave as if they were not managed by HA
275 return 0 if defined($d->{state}) && $d->{state} eq 'ignored';
276 return 1;
277 }
e709d3d0 278
54f19934
DM
279 # backward compatibility
280 $has_state = 'started' if $has_state eq 'enabled';
281
282 $d->{state} = 'started' if !defined($d->{state}) ||
283 ($d->{state} eq 'enabled');
284
e709d3d0
DM
285 return 1 if $d->{state} eq $has_state;
286 }
287
288 return undef;
289};
290
139a9b90 291sub vm_is_ha_managed {
55c82a65 292 my ($vmid, $has_state) = @_;
139a9b90
DM
293
294 my $conf = cfs_read_file($ha_resources_config);
295
8facb786 296 my $types = PVE::HA::Resources->lookup_types();
e709d3d0 297 foreach my $type ('vm', 'ct') {
0e703a14 298 return 1 if &$service_check_ha_state($conf, "$type:$vmid", $has_state);
e709d3d0 299 }
8facb786
TL
300
301 return undef;
139a9b90
DM
302}
303
9e03f787
TL
304sub service_is_ha_managed {
305 my ($sid, $has_state, $noerr) = @_;
306
307 my $conf = cfs_read_file($ha_resources_config);
308
0e703a14 309 return 1 if &$service_check_ha_state($conf, $sid, $has_state);
9e03f787
TL
310
311 die "resource '$sid' is not HA managed\n" if !$noerr;
312
313 return undef;
314}
917ea00c
TL
315
316sub get_service_status {
317 my ($sid) = @_;
318
319 my $status = { managed => 0 };
320
321 my $conf = cfs_read_file($ha_resources_config);
322
0e703a14 323 if (&$service_check_ha_state($conf, $sid)) {
917ea00c
TL
324 my $manager_status = cfs_read_file($manager_status_filename);
325
326 $status->{managed} = 1;
327 $status->{group} = $conf->{ids}->{$sid}->{group};
328 $status->{state} = $manager_status->{service_status}->{$sid}->{state};
329 }
330
331 return $status;
332}
333
cc32b737 3341;