]> git.proxmox.com Git - pve-ha-manager.git/blob - src/PVE/HA/Env/PVE2.pm
add service state
[pve-ha-manager.git] / src / PVE / HA / Env / PVE2.pm
1 package PVE::HA::Env::PVE2;
2
3 use strict;
4 use warnings;
5 use POSIX qw(:errno_h :fcntl_h);
6 use IO::File;
7 use IO::Socket::UNIX;
8
9 use PVE::SafeSyslog;
10 use PVE::Tools;
11 use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_lock_file);
12
13 use PVE::HA::Tools;
14 use PVE::HA::Env;
15 use PVE::HA::Config;
16
17 my $lockdir = "/etc/pve/priv/lock";
18
19 my $manager_status_filename = "/etc/pve/ha/manager_status";
20 my $ha_groups_config = "/etc/pve/ha/groups.cfg";
21 my $ha_resources_config = "/etc/pve/ha/resources.cfg";
22
23 #cfs_register_file($ha_groups_config,
24 # sub { PVE::HA::Groups->parse_config(@_); },
25 # sub { PVE::HA::Groups->write_config(@_); });
26
27 sub new {
28 my ($this, $nodename) = @_;
29
30 die "missing nodename" if !$nodename;
31
32 my $class = ref($this) || $this;
33
34 my $self = bless {}, $class;
35
36 $self->{nodename} = $nodename;
37
38 return $self;
39 }
40
41 sub nodename {
42 my ($self) = @_;
43
44 return $self->{nodename};
45 }
46
47 sub read_manager_status {
48 my ($self) = @_;
49
50 my $filename = $manager_status_filename;
51
52 return PVE::HA::Tools::read_json_from_file($filename, {});
53 }
54
55 sub write_manager_status {
56 my ($self, $status_obj) = @_;
57
58 my $filename = $manager_status_filename;
59
60 PVE::HA::Tools::write_json_to_file($filename, $status_obj);
61 }
62
63 sub read_lrm_status {
64 my ($self, $node) = @_;
65
66 $node = $self->{nodename} if !defined($node);
67
68 my $filename = "/etc/pve/nodes/$node/lrm_status";
69
70 return PVE::HA::Tools::read_json_from_file($filename, {});
71 }
72
73 sub write_lrm_status {
74 my ($self, $status_obj) = @_;
75
76 my $node = $self->{nodename};
77
78 my $filename = "/etc/pve/nodes/$node/lrm_status";
79
80 PVE::HA::Tools::write_json_to_file($filename, $status_obj);
81 }
82
83 sub manager_status_exists {
84 my ($self) = @_;
85
86 return -f $manager_status_filename ? 1 : 0;
87 }
88
89 sub read_service_config {
90 my ($self) = @_;
91
92 # fixme: use cfs_read_file
93
94 my $raw = '';
95
96 $raw = PVE::Tools::file_get_contents($ha_resources_config)
97 if -f $ha_resources_config;
98
99 my $res = PVE::HA::Config::parse_resources_config($ha_resources_config, $raw);
100
101 my $vmlist = PVE::Cluster::get_vmlist();
102 my $conf = {};
103
104 foreach my $sid (keys %{$res->{ids}}) {
105 my $d = $res->{ids}->{$sid};
106 $d->{state} = 'enabled' if !defined($d->{state});
107 if ($d->{type} eq 'pvevm') {
108 if (my $vmd = $vmlist->{ids}->{$d->{name}}) {
109 if (!$vmd) {
110 warn "no such VM '$d->{name}'\n";
111 } else {
112 $d->{node} = $vmd->{node};
113 $conf->{$sid} = $d;
114 }
115 } else {
116 if (defined($d->{node})) {
117 $conf->{$sid} = $d;
118 } else {
119 warn "service '$sid' without node\n";
120 }
121 }
122 }
123 }
124
125 return $conf;
126 }
127
128 sub change_service_location {
129 my ($self, $sid, $node) = @_;
130
131 die "implement me";
132 }
133
134 sub read_group_config {
135 my ($self) = @_;
136
137 # fixme: use cfs_read_file
138
139 my $raw = '';
140
141 $raw = PVE::Tools::file_get_contents($ha_groups_config)
142 if -f $ha_groups_config;
143
144 return PVE::HA::Config::parse_groups_config($ha_groups_config, $raw);
145 }
146
147 sub queue_crm_commands {
148 my ($self, $cmd) = @_;
149
150 chomp $cmd;
151
152 my $code = sub {
153 my $data = '';
154 my $filename = "/etc/pve/ha/crm_commands";
155 if (-f $filename) {
156 $data = PVE::Tools::file_get_contents($filename);
157 }
158 $data .= "$cmd\n";
159 PVE::Tools::file_set_contents($filename, $data);
160 };
161
162 # fixme: do not use cfs_lock_storage (replace with cfs_lock_ha)
163 my $res = PVE::Cluster::cfs_lock_storage("_ha_crm_commands", undef, $code);
164 die $@ if $@;
165 return $res;
166 }
167
168 sub read_crm_commands {
169 my ($self) = @_;
170
171 my $code = sub {
172 my $data = '';
173
174 my $filename = "/etc/pve/ha/crm_commands";
175 if (-f $filename) {
176 $data = PVE::Tools::file_get_contents($filename);
177 PVE::Tools::file_set_contents($filename, '');
178 }
179
180 return $data;
181 };
182
183 # fixme: do not use cfs_lock_storage (replace with cfs_lock_ha)
184 my $res = PVE::Cluster::cfs_lock_storage("_ha_crm_commands", undef, $code);
185 die $@ if $@;
186 return $res;
187 }
188
189 # this should return a hash containing info
190 # what nodes are members and online.
191 sub get_node_info {
192 my ($self) = @_;
193
194 my ($node_info, $quorate) = ({}, 0);
195
196 my $nodename = $self->{nodename};
197
198 $quorate = PVE::Cluster::check_cfs_quorum(1) || 0;
199
200 my $members = PVE::Cluster::get_members();
201
202 foreach my $node (keys %$members) {
203 my $d = $members->{$node};
204 $node_info->{$node}->{online} = $d->{online};
205 }
206
207 $node_info->{$nodename}->{online} = 1; # local node is always up
208
209 return ($node_info, $quorate);
210 }
211
212 sub log {
213 my ($self, $level, $msg) = @_;
214
215 chomp $msg;
216
217 syslog($level, $msg);
218 }
219
220 my $last_lock_status = {};
221
222 sub get_pve_lock {
223 my ($self, $lockid) = @_;
224
225 my $got_lock = 0;
226
227 my $filename = "$lockdir/$lockid";
228
229 my $last = $last_lock_status->{$lockid} || 0;
230
231 my $ctime = time();
232
233 eval {
234
235 mkdir $lockdir;
236
237 # pve cluster filesystem not online
238 die "can't create '$lockdir' (pmxcfs not mounted?)\n" if ! -d $lockdir;
239
240 if ($last && (($ctime - $last) < 100)) { # fixme: what timeout
241 utime(0, $ctime, $filename) || # cfs lock update request
242 die "cfs lock update failed - $!\n";
243 } else {
244
245 # fixme: wait some time?
246 if (!(mkdir $filename)) {
247 utime 0, 0, $filename; # cfs unlock request
248 die "can't get cfs lock\n";
249 }
250 }
251
252 $got_lock = 1;
253 };
254
255 my $err = $@;
256
257 $last_lock_status->{$lockid} = $got_lock ? $ctime : 0;
258
259 if (!!$got_lock != !!$last) {
260 if ($got_lock) {
261 $self->log('info', "successfully aquired lock '$lockid'");
262 } else {
263 my $msg = "lost lock '$lockid";
264 $msg .= " - $err" if $err;
265 $self->log('err', $msg);
266 }
267 }
268
269 return $got_lock;
270 }
271
272 sub get_ha_manager_lock {
273 my ($self) = @_;
274
275 return $self->get_pve_lock("ha_manager_lock");
276 }
277
278 sub get_ha_agent_lock {
279 my ($self) = @_;
280
281 my $node = $self->nodename();
282
283 return $self->get_pve_lock("ha_agent_${node}_lock");
284 }
285
286 sub test_ha_agent_lock {
287 my ($self, $node) = @_;
288
289 my $lockid = "ha_agent_${node}_lock";
290 my $filename = "$lockdir/$lockid";
291 my $res = $self->get_pve_lock($lockid);
292 rmdir $filename if $res; # cfs unlock
293
294 return $res;
295 }
296
297 sub quorate {
298 my ($self) = @_;
299
300 my $quorate = 0;
301 eval {
302 $quorate = PVE::Cluster::check_cfs_quorum();
303 };
304
305 return $quorate;
306 }
307
308 sub get_time {
309 my ($self) = @_;
310
311 return time();
312 }
313
314 sub sleep {
315 my ($self, $delay) = @_;
316
317 CORE::sleep($delay);
318 }
319
320 sub sleep_until {
321 my ($self, $end_time) = @_;
322
323 for (;;) {
324 my $cur_time = time();
325
326 last if $cur_time >= $end_time;
327
328 $self->sleep(1);
329 }
330 }
331
332 sub loop_start_hook {
333 my ($self) = @_;
334
335 PVE::Cluster::cfs_update();
336
337 $self->{loop_start} = $self->get_time();
338 }
339
340 sub loop_end_hook {
341 my ($self) = @_;
342
343 my $delay = $self->get_time() - $self->{loop_start};
344
345 warn "loop take too long ($delay seconds)\n" if $delay > 30;
346 }
347
348 my $watchdog_fh;
349
350 sub watchdog_open {
351 my ($self) = @_;
352
353 die "watchdog already open\n" if defined($watchdog_fh);
354
355 $watchdog_fh = IO::Socket::UNIX->new(
356 Type => SOCK_STREAM(),
357 Peer => "/run/watchdog-mux.sock") ||
358 die "unable to open watchdog socket - $!\n";
359
360 $self->log('info', "watchdog active");
361 }
362
363 sub watchdog_update {
364 my ($self, $wfh) = @_;
365
366 my $res = $watchdog_fh->syswrite("\0", 1);
367 if (!defined($res)) {
368 $self->log('err', "watchdog update failed - $!\n");
369 return 0;
370 }
371 if ($res != 1) {
372 $self->log('err', "watchdog update failed - write $res bytes\n");
373 return 0;
374 }
375
376 return 1;
377 }
378
379 sub watchdog_close {
380 my ($self, $wfh) = @_;
381
382 $watchdog_fh->syswrite("V", 1); # magic watchdog close
383 if (!$watchdog_fh->close()) {
384 $self->log('err', "watchdog close failed - $!");
385 } else {
386 $watchdog_fh = undef;
387 $self->log('info', "watchdog closed (disabled)");
388 }
389 }
390
391 sub exec_resource_agent {
392 my ($self, $sid, $cmd, @params) = @_;
393
394 die "implement me";
395 }
396
397 1;