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