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