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