]> git.proxmox.com Git - pve-cluster.git/blob - data/PVE/Cluster.pm
add nodes/hardware-map.conf
[pve-cluster.git] / data / PVE / Cluster.pm
1 package PVE::Cluster;
2
3 use strict;
4 use warnings;
5
6 use Encode;
7 use File::stat qw();
8 use File::Path qw(make_path);
9 use JSON;
10 use Net::SSLeay;
11 use POSIX qw(ENOENT);
12 use Socket;
13 use Storable qw(dclone);
14
15 use PVE::Certificate;
16 use PVE::INotify;
17 use PVE::IPCC;
18 use PVE::JSONSchema;
19 use PVE::Network;
20 use PVE::SafeSyslog;
21 use PVE::Tools qw(run_command);
22
23 use PVE::Cluster::IPCConst;
24
25 use base 'Exporter';
26
27 our @EXPORT_OK = qw(
28 cfs_read_file
29 cfs_write_file
30 cfs_register_file
31 cfs_lock_file);
32
33 # x509 certificate utils
34
35 my $basedir = "/etc/pve";
36 my $authdir = "$basedir/priv";
37 my $lockdir = "/etc/pve/priv/lock";
38
39 # cfs and corosync files
40 my $dbfile = "/var/lib/pve-cluster/config.db";
41 my $dbbackupdir = "/var/lib/pve-cluster/backup";
42
43 # this is just a readonly copy, the relevant one is in status.c from pmxcfs
44 # observed files are the one we can get directly through IPCC, they are cached
45 # using a computed version and only those can be used by the cfs_*_file methods
46 my $observed = {
47 'vzdump.cron' => 1,
48 'jobs.cfg' => 1,
49 'storage.cfg' => 1,
50 'datacenter.cfg' => 1,
51 'replication.cfg' => 1,
52 'corosync.conf' => 1,
53 'corosync.conf.new' => 1,
54 'user.cfg' => 1,
55 'domains.cfg' => 1,
56 'priv/shadow.cfg' => 1,
57 'priv/tfa.cfg' => 1,
58 'priv/token.cfg' => 1,
59 'priv/acme/plugins.cfg' => 1,
60 'priv/ipam.db' => 1,
61 '/qemu-server/' => 1,
62 '/openvz/' => 1,
63 '/lxc/' => 1,
64 'ha/crm_commands' => 1,
65 'ha/manager_status' => 1,
66 'ha/resources.cfg' => 1,
67 'ha/groups.cfg' => 1,
68 'ha/fence.cfg' => 1,
69 'status.cfg' => 1,
70 'ceph.conf' => 1,
71 'sdn/vnets.cfg' => 1,
72 'sdn/zones.cfg' => 1,
73 'sdn/controllers.cfg' => 1,
74 'sdn/subnets.cfg' => 1,
75 'sdn/ipams.cfg' => 1,
76 'sdn/dns.cfg' => 1,
77 'sdn/.running-config' => 1,
78 'virtual-guest/cpu-models.conf' => 1,
79 'nodes/hardware-map.conf' => 1,
80 };
81
82 sub prepare_observed_file_basedirs {
83
84 if (!check_cfs_is_mounted(1)) {
85 warn "pmxcfs isn't mounted (/etc/pve), chickening out..\n";
86 return;
87 }
88
89 for my $f (sort keys %$observed) {
90 next if $f !~ m!^(.*)/[^/]+$!;
91 my $dir = "$basedir/$1";
92 next if -e $dir; # can also be a link, so just use -e xist check
93 print "creating directory '$dir' for observerd files\n";
94 make_path($dir);
95 }
96 }
97
98 sub base_dir {
99 return $basedir;
100 }
101
102 sub auth_dir {
103 return $authdir;
104 }
105
106 sub check_cfs_quorum {
107 my ($noerr) = @_;
108
109 # note: -w filename always return 1 for root, so wee need
110 # to use File::lstat here
111 my $st = File::stat::lstat("$basedir/local");
112 my $quorate = ($st && (($st->mode & 0200) != 0));
113
114 die "cluster not ready - no quorum?\n" if !$quorate && !$noerr;
115
116 return $quorate;
117 }
118
119 sub check_cfs_is_mounted {
120 my ($noerr) = @_;
121
122 my $res = -l "$basedir/local";
123
124 die "pve configuration filesystem (pmxcfs) not mounted\n" if !$res && !$noerr;
125
126 return $res;
127 }
128
129 my $versions = {};
130 my $vmlist = {};
131 my $clinfo = {};
132
133 my $ipcc_send_rec = sub {
134 my ($msgid, $data) = @_;
135
136 my $res = PVE::IPCC::ipcc_send_rec($msgid, $data);
137
138 die "ipcc_send_rec[$msgid] failed: $!\n" if !defined($res) && ($! != 0);
139
140 return $res;
141 };
142
143 my $ipcc_send_rec_json = sub {
144 my ($msgid, $data) = @_;
145
146 my $res = PVE::IPCC::ipcc_send_rec($msgid, $data);
147
148 die "ipcc_send_rec[$msgid] failed: $!\n" if !defined($res) && ($! != 0);
149
150 return decode_json($res);
151 };
152
153 my $ipcc_get_config = sub {
154 my ($path) = @_;
155
156 my $bindata = pack "Z*", $path;
157 my $res = PVE::IPCC::ipcc_send_rec(CFS_IPC_GET_CONFIG, $bindata);
158 if (!defined($res)) {
159 if ($! != 0) {
160 return undef if $! == ENOENT;
161 die "$!\n";
162 }
163 return '';
164 }
165
166 return $res;
167 };
168
169 my $ipcc_get_status = sub {
170 my ($name, $nodename) = @_;
171
172 my $bindata = pack "Z[256]Z[256]", $name, ($nodename || "");
173 return PVE::IPCC::ipcc_send_rec(CFS_IPC_GET_STATUS, $bindata);
174 };
175
176 my $ipcc_remove_status = sub {
177 my ($name) = @_;
178 # we just omit the data payload, pmxcfs takes this as hint and removes this
179 # key from the status hashtable
180 my $bindata = pack "Z[256]", $name;
181 return &$ipcc_send_rec(CFS_IPC_SET_STATUS, $bindata);
182 };
183
184 my $ipcc_update_status = sub {
185 my ($name, $data) = @_;
186
187 my $raw = ref($data) ? encode_json($data) : $data;
188 # update status
189 my $bindata = pack "Z[256]Z*", $name, $raw;
190
191 return &$ipcc_send_rec(CFS_IPC_SET_STATUS, $bindata);
192 };
193
194 my $ipcc_log = sub {
195 my ($priority, $ident, $tag, $msg) = @_;
196
197 my $bindata = pack "CCCZ*Z*Z*", $priority, bytes::length($ident) + 1,
198 bytes::length($tag) + 1, $ident, $tag, $msg;
199
200 return &$ipcc_send_rec(CFS_IPC_LOG_CLUSTER_MSG, $bindata);
201 };
202
203 my $ipcc_get_cluster_log = sub {
204 my ($user, $max) = @_;
205
206 $max = 0 if !defined($max);
207
208 my $bindata = pack "VVVVZ*", $max, 0, 0, 0, ($user || "");
209 return &$ipcc_send_rec(CFS_IPC_GET_CLUSTER_LOG, $bindata);
210 };
211
212 my $ipcc_verify_token = sub {
213 my ($full_token) = @_;
214
215 my $bindata = pack "Z*", $full_token;
216 my $res = PVE::IPCC::ipcc_send_rec(CFS_IPC_VERIFY_TOKEN, $bindata);
217
218 return 1 if $! == 0;
219 return 0 if $! == ENOENT;
220
221 die "$!\n";
222 };
223
224 my $ccache = {};
225
226 sub cfs_update {
227 my ($fail) = @_;
228 eval {
229 my $res = &$ipcc_send_rec_json(CFS_IPC_GET_FS_VERSION);
230 die "no starttime\n" if !$res->{starttime};
231
232 if (!$res->{starttime} || !$versions->{starttime} ||
233 $res->{starttime} != $versions->{starttime}) {
234 #print "detected changed starttime\n";
235 $vmlist = {};
236 $clinfo = {};
237 $ccache = {};
238 }
239
240 $versions = $res;
241 };
242 my $err = $@;
243 if ($err) {
244 $versions = {};
245 $vmlist = {};
246 $clinfo = {};
247 $ccache = {};
248 die $err if $fail;
249 warn $err;
250 }
251
252 eval {
253 if (!$clinfo->{version} || $clinfo->{version} != $versions->{clinfo}) {
254 #warn "detected new clinfo\n";
255 $clinfo = &$ipcc_send_rec_json(CFS_IPC_GET_CLUSTER_INFO);
256 }
257 };
258 $err = $@;
259 if ($err) {
260 $clinfo = {};
261 die $err if $fail;
262 warn $err;
263 }
264
265 eval {
266 if (!$vmlist->{version} || $vmlist->{version} != $versions->{vmlist}) {
267 #warn "detected new vmlist1\n";
268 $vmlist = &$ipcc_send_rec_json(CFS_IPC_GET_GUEST_LIST);
269 }
270 };
271 $err = $@;
272 if ($err) {
273 $vmlist = {};
274 die $err if $fail;
275 warn $err;
276 }
277 }
278
279 sub get_vmlist {
280 return $vmlist;
281 }
282
283 sub get_clinfo {
284 return $clinfo;
285 }
286
287 sub get_members {
288 return $clinfo->{nodelist};
289 }
290
291 sub get_nodelist {
292 my $nodelist = $clinfo->{nodelist};
293
294 my $nodename = PVE::INotify::nodename();
295
296 if (!$nodelist || !$nodelist->{$nodename}) {
297 return [ $nodename ];
298 }
299
300 return [ keys %$nodelist ];
301 }
302
303 # only stored in a in-memory hashtable inside pmxcfs, local data is gone after
304 # a restart (of pmxcfs or the node), peer data is still available then
305 # best used for status data, like running (ceph) services, package versions, ...
306 sub broadcast_node_kv {
307 my ($key, $data) = @_;
308
309 if (!defined($data)) {
310 eval { $ipcc_remove_status->("kv/$key") };
311 } else {
312 die "cannot send a reference\n" if ref($data);
313 my $size = length($data);
314 die "data for '$key' too big\n" if $size >= (32 * 1024); # limit from pmxfs
315
316 eval { $ipcc_update_status->("kv/$key", $data) };
317 }
318 warn $@ if $@;
319 }
320
321 # nodename is optional
322 sub get_node_kv {
323 my ($key, $nodename) = @_;
324
325 my $res = {};
326 my $get_node_data = sub {
327 my ($node) = @_;
328 my $raw = $ipcc_get_status->("kv/$key", $node);
329 $res->{$node} = unpack("Z*", $raw) if $raw;
330 };
331
332 if ($nodename) {
333 $get_node_data->($nodename);
334 } else {
335 for my $node (get_nodelist()->@*) {
336 $get_node_data->($node);
337 }
338 }
339
340 return $res;
341 }
342
343 # property: a config property you want to get, e.g., this is perfect to get
344 # the 'lock' entry of a guest _fast_ (>100 faster than manual parsing here)
345 # vmid: optional, if a valid is passed we only check that one, else return all
346 # NOTE: does *not* searches snapshot and PENDING entries sections!
347 sub get_guest_config_property {
348 my ($property, $vmid) = @_;
349
350 die "property is required" if !defined($property);
351
352 my $bindata = pack "VZ*", $vmid // 0, $property;
353 my $res = $ipcc_send_rec_json->(CFS_IPC_GET_GUEST_CONFIG_PROPERTY, $bindata);
354
355 return $res;
356 }
357
358 # $data must be a chronological descending ordered array of tasks
359 sub broadcast_tasklist {
360 my ($data) = @_;
361
362 # the serialized list may not get bigger than 128 KiB (CFS_MAX_STATUS_SIZE from pmxcfs)
363 # drop older items until we satisfy this constraint
364 my $size = length(encode_json($data));
365 while ($size >= (32 * 1024)) { # TODO: update to 128 KiB in PVE 8.x
366 pop @$data;
367 $size = length(encode_json($data));
368 }
369
370 eval { $ipcc_update_status->("tasklist", $data) };
371 warn $@ if $@;
372 }
373
374 my $tasklistcache = {};
375
376 sub get_tasklist {
377 my ($nodename) = @_;
378
379 my $kvstore = $versions->{kvstore} || {};
380
381 my $nodelist = get_nodelist();
382
383 my $res = [];
384 foreach my $node (@$nodelist) {
385 next if $nodename && ($nodename ne $node);
386 eval {
387 my $ver = $kvstore->{$node}->{tasklist} if $kvstore->{$node};
388 my $cache = $tasklistcache->{$node};
389 if (!$cache || !$ver || !$cache->{version} || ($cache->{version} != $ver)) {
390 my $tasks = [];
391 if (my $raw = $ipcc_get_status->("tasklist", $node)) {
392 my $json_str = unpack("Z*", $raw);
393 $tasks = decode_json($json_str);
394 }
395 push @$res, @$tasks;
396 $tasklistcache->{$node} = {
397 data => $tasks,
398 version => $ver,
399 };
400 } elsif ($cache && $cache->{data}) {
401 push @$res, $cache->{data}->@*;
402 }
403 };
404 my $err = $@;
405 syslog('err', $err) if $err;
406 }
407
408 return $res;
409 }
410
411 sub broadcast_rrd {
412 my ($rrdid, $data) = @_;
413
414 eval {
415 &$ipcc_update_status("rrd/$rrdid", $data);
416 };
417 my $err = $@;
418
419 warn $err if $err;
420 }
421
422 my $last_rrd_dump = 0;
423 my $last_rrd_data = "";
424
425 sub rrd_dump {
426
427 my $ctime = time();
428
429 my $diff = $ctime - $last_rrd_dump;
430 if ($diff < 2) {
431 return $last_rrd_data;
432 }
433
434 my $raw;
435 eval {
436 $raw = &$ipcc_send_rec(CFS_IPC_GET_RRD_DUMP);
437 };
438 my $err = $@;
439
440 if ($err) {
441 warn $err;
442 return {};
443 }
444
445 my $res = {};
446
447 if ($raw) {
448 while ($raw =~ s/^(.*)\n//) {
449 my ($key, @ela) = split(/:/, $1);
450 next if !$key;
451 next if !(scalar(@ela) > 1);
452 $res->{$key} = [ map { $_ eq 'U' ? undef : $_ } @ela ];
453 }
454 }
455
456 $last_rrd_dump = $ctime;
457 $last_rrd_data = $res;
458
459 return $res;
460 }
461
462
463 # a fast way to read files (avoid fuse overhead)
464 sub get_config {
465 my ($path) = @_;
466
467 return &$ipcc_get_config($path);
468 }
469
470 sub get_cluster_log {
471 my ($user, $max) = @_;
472
473 return &$ipcc_get_cluster_log($user, $max);
474 }
475
476 sub verify_token {
477 my ($userid, $token) = @_;
478
479 return &$ipcc_verify_token("$userid $token");
480 }
481
482 my $file_info = {};
483
484 sub cfs_register_file {
485 my ($filename, $parser, $writer) = @_;
486
487 $observed->{$filename} || die "unknown file '$filename'";
488
489 die "file '$filename' already registered" if $file_info->{$filename};
490
491 $file_info->{$filename} = {
492 parser => $parser,
493 writer => $writer,
494 };
495 }
496
497 my $ccache_read = sub {
498 my ($filename, $parser, $version) = @_;
499
500 $ccache->{$filename} = {} if !$ccache->{$filename};
501
502 my $ci = $ccache->{$filename};
503
504 if (!$ci->{version} || !$version || $ci->{version} != $version) {
505 # we always call the parser, even when the file does not exist
506 # (in that case $data is undef)
507 my $data = get_config($filename);
508 $ci->{data} = &$parser("/etc/pve/$filename", $data);
509 $ci->{version} = $version;
510 }
511
512 my $res = ref($ci->{data}) ? dclone($ci->{data}) : $ci->{data};
513
514 return $res;
515 };
516
517 sub cfs_file_version {
518 my ($filename) = @_;
519
520 my $version;
521 my $infotag;
522 if ($filename =~ m!^nodes/[^/]+/(openvz|lxc|qemu-server)/(\d+)\.conf$!) {
523 my ($type, $vmid) = ($1, $2);
524 if ($vmlist && $vmlist->{ids} && $vmlist->{ids}->{$vmid}) {
525 $version = $vmlist->{ids}->{$vmid}->{version};
526 }
527 $infotag = "/$type/";
528 } else {
529 $infotag = $filename;
530 $version = $versions->{$filename};
531 }
532
533 my $info = $file_info->{$infotag} ||
534 die "unknown file type '$filename'\n";
535
536 return wantarray ? ($version, $info) : $version;
537 }
538
539 sub cfs_read_file {
540 my ($filename) = @_;
541
542 my ($version, $info) = cfs_file_version($filename);
543 my $parser = $info->{parser};
544
545 return &$ccache_read($filename, $parser, $version);
546 }
547
548 sub cfs_write_file {
549 my ($filename, $data) = @_;
550
551 my ($version, $info) = cfs_file_version($filename);
552
553 my $writer = $info->{writer} || die "no writer defined";
554
555 my $fsname = "/etc/pve/$filename";
556
557 my $raw = &$writer($fsname, $data);
558
559 if (my $ci = $ccache->{$filename}) {
560 $ci->{version} = undef;
561 }
562
563 PVE::Tools::file_set_contents($fsname, $raw);
564 }
565
566 my $cfs_lock = sub {
567 my ($lockid, $timeout, $code, @param) = @_;
568
569 my $prev_alarm = alarm(0); # suspend outer alarm early
570
571 my $res;
572 my $got_lock = 0;
573
574 # this timeout is for acquire the lock
575 $timeout = 10 if !$timeout;
576
577 my $filename = "$lockdir/$lockid";
578
579 my $is_code_err = 0;
580 eval {
581
582 mkdir $lockdir;
583
584 if (! -d $lockdir) {
585 die "pve cluster filesystem not online.\n";
586 }
587
588 my $timeout_err = sub { die "got lock request timeout\n"; };
589 local $SIG{ALRM} = $timeout_err;
590
591 while (1) {
592 alarm ($timeout);
593 $got_lock = mkdir($filename);
594 $timeout = alarm(0) - 1; # we'll sleep for 1s, see down below
595
596 last if $got_lock;
597
598 $timeout_err->() if $timeout <= 0;
599
600 print STDERR "trying to acquire cfs lock '$lockid' ...\n";
601 utime (0, 0, $filename); # cfs unlock request
602 sleep(1);
603 }
604
605 # fixed command timeout: cfs locks have a timeout of 120
606 # using 60 gives us another 60 seconds to abort the task
607 local $SIG{ALRM} = sub { die "'$lockid'-locked command timed out - aborting\n"; };
608 alarm(60);
609
610 cfs_update(); # make sure we read latest versions inside code()
611
612 $is_code_err = 1; # allows to differ between locking and actual-work errors
613
614 $res = &$code(@param);
615
616 alarm(0);
617 };
618
619 my $err = $@;
620
621 $err = "no quorum!\n" if !$got_lock && !check_cfs_quorum(1);
622
623 rmdir $filename if $got_lock; # if we held the lock always unlock again
624
625 alarm($prev_alarm);
626
627 if ($err) {
628 if (ref($err) eq 'PVE::Exception' || $is_code_err) {
629 # re-raise defined exceptions
630 $@ = $err;
631 } else {
632 # add lock info for plain errors comming from the locking itself
633 $@ = "cfs-lock '$lockid' error: $err";
634 }
635 return undef;
636 }
637
638 $@ = undef;
639
640 return $res;
641 };
642
643 sub cfs_lock_file {
644 my ($filename, $timeout, $code, @param) = @_;
645
646 my $info = $observed->{$filename} || die "unknown file '$filename'";
647
648 my $lockid = "file-$filename";
649 $lockid =~ s/[.\/]/_/g;
650
651 &$cfs_lock($lockid, $timeout, $code, @param);
652 }
653
654 sub cfs_lock_storage {
655 my ($storeid, $timeout, $code, @param) = @_;
656
657 my $lockid = "storage-$storeid";
658
659 &$cfs_lock($lockid, $timeout, $code, @param);
660 }
661
662 sub cfs_lock_domain {
663 my ($domainname, $timeout, $code, @param) = @_;
664
665 my $lockid = "domain-$domainname";
666
667 &$cfs_lock($lockid, $timeout, $code, @param);
668 }
669
670 sub cfs_lock_acme {
671 my ($account, $timeout, $code, @param) = @_;
672
673 my $lockid = "acme-$account";
674
675 &$cfs_lock($lockid, $timeout, $code, @param);
676 }
677
678 sub cfs_lock_authkey {
679 my ($timeout, $code, @param) = @_;
680
681 $cfs_lock->('authkey', $timeout, $code, @param);
682 }
683
684 sub cfs_lock_firewall {
685 my ($scope, $timeout, $code, @param) = @_;
686
687 my $lockid = "firewall-$scope";
688
689 $cfs_lock->($lockid, $timeout, $code, @param);
690 }
691
692 my $log_levels = {
693 "emerg" => 0,
694 "alert" => 1,
695 "crit" => 2,
696 "critical" => 2,
697 "err" => 3,
698 "error" => 3,
699 "warn" => 4,
700 "warning" => 4,
701 "notice" => 5,
702 "info" => 6,
703 "debug" => 7,
704 };
705
706 sub log_msg {
707 my ($priority, $ident, $msg) = @_;
708
709 if (my $tmp = $log_levels->{$priority}) {
710 $priority = $tmp;
711 }
712
713 die "need numeric log priority" if $priority !~ /^\d+$/;
714
715 my $tag = PVE::SafeSyslog::tag();
716
717 $msg = "empty message" if !$msg;
718
719 $ident = "" if !$ident;
720 $ident = encode("ascii", $ident,
721 sub { sprintf "\\u%04x", shift });
722
723 my $ascii = encode("ascii", $msg, sub { sprintf "\\u%04x", shift });
724
725 if ($ident) {
726 syslog($priority, "<%s> %s", $ident, $ascii);
727 } else {
728 syslog($priority, "%s", $ascii);
729 }
730
731 eval { &$ipcc_log($priority, $ident, $tag, $ascii); };
732
733 syslog("err", "writing cluster log failed: $@") if $@;
734 }
735
736 sub check_vmid_unused {
737 my ($vmid, $noerr) = @_;
738
739 my $vmlist = get_vmlist();
740
741 my $d = $vmlist->{ids}->{$vmid};
742 return 1 if !defined($d);
743
744 return undef if $noerr;
745
746 my $vmtypestr = $d->{type} eq 'qemu' ? 'VM' : 'CT';
747 die "$vmtypestr $vmid already exists on node '$d->{node}'\n";
748 }
749
750 sub check_node_exists {
751 my ($nodename, $noerr) = @_;
752
753 my $nodelist = $clinfo->{nodelist};
754 return 1 if $nodelist && $nodelist->{$nodename};
755
756 return undef if $noerr;
757
758 die "no such cluster node '$nodename'\n";
759 }
760
761 # this is also used to get the IP of the local node
762 sub remote_node_ip {
763 my ($nodename, $noerr) = @_;
764
765 my $nodelist = $clinfo->{nodelist};
766 if ($nodelist && $nodelist->{$nodename}) {
767 if (my $ip = $nodelist->{$nodename}->{ip}) {
768 return $ip if !wantarray;
769 my $family = $nodelist->{$nodename}->{address_family};
770 if (!$family) {
771 $nodelist->{$nodename}->{address_family} =
772 $family =
773 PVE::Tools::get_host_address_family($ip);
774 }
775 return wantarray ? ($ip, $family) : $ip;
776 }
777 }
778
779 # fallback: try to get IP by other means
780 return PVE::Network::get_ip_from_hostname($nodename, $noerr);
781 }
782
783 sub get_node_fingerprint {
784 my ($node) = @_;
785
786 my $cert_path = "/etc/pve/nodes/$node/pve-ssl.pem";
787 my $custom_cert_path = "/etc/pve/nodes/$node/pveproxy-ssl.pem";
788
789 $cert_path = $custom_cert_path if -f $custom_cert_path;
790
791 return PVE::Certificate::get_certificate_fingerprint($cert_path);
792 }
793
794 # bash completion helpers
795
796 sub complete_next_vmid {
797
798 my $vmlist = get_vmlist() || {};
799 my $idlist = $vmlist->{ids} || {};
800
801 for (my $i = 100; $i < 10000; $i++) {
802 return [$i] if !defined($idlist->{$i});
803 }
804
805 return [];
806 }
807
808 sub complete_vmid {
809
810 my $vmlist = get_vmlist();
811 my $ids = $vmlist->{ids} || {};
812
813 return [ keys %$ids ];
814 }
815
816 sub complete_local_vmid {
817
818 my $vmlist = get_vmlist();
819 my $ids = $vmlist->{ids} || {};
820
821 my $nodename = PVE::INotify::nodename();
822
823 my $res = [];
824 foreach my $vmid (keys %$ids) {
825 my $d = $ids->{$vmid};
826 next if !$d->{node} || $d->{node} ne $nodename;
827 push @$res, $vmid;
828 }
829
830 return $res;
831 }
832
833 sub complete_migration_target {
834
835 my $res = [];
836
837 my $nodename = PVE::INotify::nodename();
838
839 my $nodelist = get_nodelist();
840 foreach my $node (@$nodelist) {
841 next if $node eq $nodename;
842 push @$res, $node;
843 }
844
845 return $res;
846 }
847
848
849 # NOTE: filesystem must be offline here, no DB changes allowed
850 sub cfs_backup_database {
851 mkdir $dbbackupdir;
852
853 my $ctime = time();
854 my $backup_fn = "$dbbackupdir/config-$ctime.sql.gz";
855
856 print "backup old database to '$backup_fn'\n";
857
858 my $cmd = [ ['sqlite3', $dbfile, '.dump'], ['gzip', '-', \ ">${backup_fn}"] ];
859 run_command($cmd, 'errmsg' => "cannot backup old database\n");
860
861 my $maxfiles = 10; # purge older backup
862 my $backups = [ sort { $b cmp $a } <$dbbackupdir/config-*.sql.gz> ];
863
864 if ((my $count = scalar(@$backups)) > $maxfiles) {
865 foreach my $f (@$backups[$maxfiles..$count-1]) {
866 next if $f !~ m/^(\S+)$/; # untaint
867 print "delete old backup '$1'\n";
868 unlink $1;
869 }
870 }
871
872 return $dbfile;
873 }
874
875 1;