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