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