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