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