]> git.proxmox.com Git - pve-cluster.git/blame - data/PVE/CLI/pvecm.pm
setup_sshd_config: add start_sshd flag
[pve-cluster.git] / data / PVE / CLI / pvecm.pm
CommitLineData
13d44dc5
DM
1package PVE::CLI::pvecm;
2
3use strict;
4use warnings;
5use Getopt::Long;
6use Socket;
7use IO::File;
8use Net::IP;
9use File::Path;
10use File::Basename;
11use Data::Dumper; # fixme: remove
12use PVE::Tools;
13use PVE::Cluster;
14use PVE::INotify;
15use PVE::JSONSchema;
13d44dc5
DM
16use PVE::CLIHandler;
17
18use base qw(PVE::CLIHandler);
19
20$ENV{HOME} = '/root'; # for ssh-copy-id
21
22my $basedir = "/etc/pve";
23my $clusterconf = "$basedir/corosync.conf";
24my $libdir = "/var/lib/pve-cluster";
25my $backupdir = "/var/lib/pve-cluster/backup";
26my $dbfile = "$libdir/config.db";
27my $authfile = "/etc/corosync/authkey";
28
29sub backup_database {
30
31 print "backup old database\n";
32
33 mkdir $backupdir;
34
35 my $ctime = time();
de4b4155
FG
36 my $cmd = [
37 ['echo', '.dump'],
38 ['sqlite3', $dbfile],
7eee3d64 39 ['gzip', '-', \ ">${backupdir}/config-${ctime}.sql.gz"],
de4b4155 40 ];
13d44dc5 41
0c17b64f 42 PVE::Tools::run_command($cmd, 'errmsg' => "cannot backup old database\n");
13d44dc5
DM
43
44 # purge older backup
45 my $maxfiles = 10;
46
47 my @bklist = ();
48 foreach my $fn (<$backupdir/config-*.sql.gz>) {
49 if ($fn =~ m!/config-(\d+)\.sql.gz$!) {
50 push @bklist, [$fn, $1];
51 }
52 }
53
54 @bklist = sort { $b->[1] <=> $a->[1] } @bklist;
55
56 while (scalar (@bklist) >= $maxfiles) {
57 my $d = pop @bklist;
58 print "delete old backup '$d->[0]'\n";
59 unlink $d->[0];
60 }
61}
62
63__PACKAGE__->register_method ({
64 name => 'keygen',
65 path => 'keygen',
66 method => 'PUT',
67 description => "Generate new cryptographic key for corosync.",
68 parameters => {
69 additionalProperties => 0,
70 properties => {
71 filename => {
72 type => 'string',
73 description => "Output file name"
74 }
75 },
76 },
77 returns => { type => 'null' },
78
79 code => sub {
80 my ($param) = @_;
81
82 my $filename = $param->{filename};
83
84 # test EUID
85 $> == 0 || die "Error: Authorization key must be generated as root user.\n";
86 my $dirname = dirname($filename);
87 my $basename = basename($filename);
88
89 die "key file '$filename' already exists\n" if -e $filename;
90
91 File::Path::make_path($dirname) if $dirname;
92
93 my $cmd = ['corosync-keygen', '-l', '-k', $filename];
94 PVE::Tools::run_command($cmd);
95
96 return undef;
97 }});
98
99__PACKAGE__->register_method ({
100 name => 'create',
101 path => 'create',
102 method => 'PUT',
103 description => "Generate new cluster configuration.",
104 parameters => {
105 additionalProperties => 0,
106 properties => {
107 clustername => {
108 description => "The name of the cluster.",
109 type => 'string', format => 'pve-node',
110 maxLength => 15,
111 },
112 nodeid => {
113 type => 'integer',
114 description => "Node id for this node.",
115 minimum => 1,
116 optional => 1,
117 },
118 votes => {
119 type => 'integer',
120 description => "Number of votes for this node.",
121 minimum => 1,
122 optional => 1,
123 },
14d0000a
TL
124 bindnet0_addr => {
125 type => 'string', format => 'ip',
126 description => "This specifies the network address the corosync ring 0".
127 " executive should bind to and defaults to the local IP address of the node.",
128 optional => 1,
129 },
130 ring0_addr => {
131 type => 'string', format => 'address',
132 description => "Hostname (or IP) of the corosync ring0 address of this node.".
133 " Defaults to the hostname of the node.",
134 optional => 1,
135 },
136 rrp_mode => {
137 type => 'string',
138 enum => ['none', 'active', 'passive'],
139 description => "This specifies the mode of redundant ring, which" .
140 " may be none, active or passive. Using multiple interfaces".
141 " only allows 'active' or 'passive'.",
142 default => 'none',
143 optional => 1,
144 },
145 bindnet1_addr => {
146 type => 'string', format => 'ip',
147 description => "This specifies the network address the corosync ring 1".
148 " executive should bind to and is optional.",
149 optional => 1,
150 },
151 ring1_addr => {
152 type => 'string', format => 'address',
153 description => "Hostname (or IP) of the corosync ring1 address, this".
154 " needs an valid bindnet1_addr.",
155 optional => 1,
156 },
157 },
13d44dc5
DM
158 },
159 returns => { type => 'null' },
160
161 code => sub {
162 my ($param) = @_;
163
164 -f $clusterconf && die "cluster config '$clusterconf' already exists\n";
165
6c0e95b3 166 PVE::Cluster::setup_sshd_config(1);
13d44dc5
DM
167 PVE::Cluster::setup_rootsshconfig();
168 PVE::Cluster::setup_ssh_keys();
169
170 -f $authfile || __PACKAGE__->keygen({filename => $authfile});
171
172 -f $authfile || die "no authentication key available\n";
173
174 my $clustername = $param->{clustername};
175
176 $param->{nodeid} = 1 if !$param->{nodeid};
177
178 $param->{votes} = 1 if !defined($param->{votes});
179
180 my $nodename = PVE::INotify::nodename();
181
182 my $local_ip_address = PVE::Cluster::remote_node_ip($nodename);
183
14d0000a
TL
184 $param->{bindnet0_addr} = $local_ip_address
185 if !defined($param->{bindnet0_addr});
186
187 $param->{ring0_addr} = $nodename if !defined($param->{ring0_addr});
188
189 die "Param bindnet1_addr and ring1_addr are dependend, use both or none!\n"
190 if (defined($param->{bindnet1_addr}) != defined($param->{ring1_addr}));
191
192 my $bind_is_ipv6 = Net::IP::ip_is_ipv6($param->{bindnet0_addr});
193
194 # use string as here-doc format distracts more
195 my $interfaces = "interface {\n ringnumber: 0\n" .
196 " bindnetaddr: $param->{bindnet0_addr}\n }";
197
198 my $ring_addresses = "ring0_addr: $param->{ring0_addr}" ;
199
200 # allow use of multiple rings (rrp) at cluster creation time
201 if ($param->{bindnet1_addr}) {
202 die "IPv6 and IPv4 cannot be mixed, use one or the other!\n"
203 if Net::IP::ip_is_ipv6($param->{bindnet1_addr}) != $bind_is_ipv6;
204
205 die "rrp_mode 'none' is not allowed when using multiple interfaces,".
206 " use 'active' or 'passive'!\n"
207 if !$param->{rrp_mode} || $param->{rrp_mode} eq 'none';
208
209 $interfaces .= "\n interface {\n ringnumber: 1\n" .
210 " bindnetaddr: $param->{bindnet1_addr}\n }\n";
211
212 $ring_addresses .= "\n ring1_addr: $param->{ring1_addr}";
213
214 } elsif($param->{rrp_mode} && $param->{rrp_mode} ne 'none') {
215
216 warn "rrp_mode '$param->{rrp_mode}' useless when using only one".
217 " ring, using 'none' instead";
218 # corosync defaults to none if only one interface is configured
219 $param->{rrp_mode} = undef;
220
221 }
222
223 $interfaces = "rrp_mode: $param->{rrp_mode}\n " . $interfaces
224 if $param->{rrp_mode};
225
13d44dc5 226 # No, corosync cannot deduce this on its own
14d0000a 227 my $ipversion = $bind_is_ipv6 ? 'ipv6' : 'ipv4';
13d44dc5
DM
228
229 my $config = <<_EOD;
230totem {
231 version: 2
232 secauth: on
233 cluster_name: $clustername
234 config_version: 1
235 ip_version: $ipversion
14d0000a 236 $interfaces
13d44dc5
DM
237}
238
239nodelist {
240 node {
14d0000a
TL
241 $ring_addresses
242 name: $nodename
13d44dc5
DM
243 nodeid: $param->{nodeid}
244 quorum_votes: $param->{votes}
245 }
246}
14d0000a 247
13d44dc5
DM
248quorum {
249 provider: corosync_votequorum
250}
251
252logging {
253 to_syslog: yes
254 debug: off
255}
256_EOD
14d0000a 257;
13d44dc5
DM
258 PVE::Tools::file_set_contents($clusterconf, $config);
259
260 PVE::Cluster::ssh_merge_keys();
261
262 PVE::Cluster::gen_pve_node_files($nodename, $local_ip_address);
263
264 PVE::Cluster::ssh_merge_known_hosts($nodename, $local_ip_address, 1);
265
266 PVE::Tools::run_command('systemctl restart pve-cluster'); # restart
267
268 PVE::Tools::run_command('systemctl restart corosync'); # restart
269
270 return undef;
271}});
272
273__PACKAGE__->register_method ({
274 name => 'addnode',
275 path => 'addnode',
276 method => 'PUT',
277 description => "Adds a node to the cluster configuration.",
278 parameters => {
279 additionalProperties => 0,
280 properties => {
281 node => PVE::JSONSchema::get_standard_option('pve-node'),
282 nodeid => {
283 type => 'integer',
284 description => "Node id for this node.",
285 minimum => 1,
286 optional => 1,
287 },
288 votes => {
289 type => 'integer',
290 description => "Number of votes for this node",
291 minimum => 0,
292 optional => 1,
293 },
294 force => {
295 type => 'boolean',
296 description => "Do not throw error if node already exists.",
297 optional => 1,
298 },
14d0000a
TL
299 ring0_addr => {
300 type => 'string', format => 'address',
301 description => "Hostname (or IP) of the corosync ring0 address of this node.".
302 " Defaults to nodes hostname.",
303 optional => 1,
304 },
305 ring1_addr => {
306 type => 'string', format => 'address',
307 description => "Hostname (or IP) of the corosync ring1 address, this".
308 " needs an valid bindnet1_addr.",
309 optional => 1,
310 },
13d44dc5
DM
311 },
312 },
313 returns => { type => 'null' },
314
315 code => sub {
316 my ($param) = @_;
317
318 PVE::Cluster::check_cfs_quorum();
319
320 my $conf = PVE::Cluster::cfs_read_file("corosync.conf");
321
322 my $nodelist = corosync_nodelist($conf);
323
14d0000a
TL
324 my $totem_cfg = corosync_totem_config($conf);
325
13d44dc5
DM
326 my $name = $param->{node};
327
14d0000a
TL
328 $param->{ring0_addr} = $name if !$param->{ring0_addr};
329
330 die " ring1_addr needs a configured ring 1 interface!\n"
331 if $param->{ring1_addr} && !defined($totem_cfg->{interface}->{1});
332
13d44dc5
DM
333 if (defined(my $res = $nodelist->{$name})) {
334 $param->{nodeid} = $res->{nodeid} if !$param->{nodeid};
335 $param->{votes} = $res->{quorum_votes} if !defined($param->{votes});
336
337 if ($res->{quorum_votes} == $param->{votes} &&
338 $res->{nodeid} == $param->{nodeid}) {
339 print "node $name already defined\n";
340 if ($param->{force}) {
341 exit (0);
342 } else {
343 exit (-1);
344 }
345 } else {
346 die "can't add existing node\n";
347 }
348 } elsif (!$param->{nodeid}) {
349 my $nodeid = 1;
350
351 while(1) {
352 my $found = 0;
353 foreach my $v (values %$nodelist) {
354 if ($v->{nodeid} eq $nodeid) {
355 $found = 1;
356 $nodeid++;
357 last;
358 }
359 }
360 last if !$found;
361 };
362
363 $param->{nodeid} = $nodeid;
364 }
365
366 $param->{votes} = 1 if !defined($param->{votes});
367
368 PVE::Cluster::gen_local_dirs($name);
369
370 eval { PVE::Cluster::ssh_merge_keys(); };
371 warn $@ if $@;
372
14d0000a
TL
373 $nodelist->{$name} = {
374 ring0_addr => $param->{ring0_addr},
375 nodeid => $param->{nodeid},
376 name => $name,
377 };
378 $nodelist->{$name}->{ring1_addr} = $param->{ring1_addr} if $param->{ring1_addr};
13d44dc5
DM
379 $nodelist->{$name}->{quorum_votes} = $param->{votes} if $param->{votes};
380
381 corosync_update_nodelist($conf, $nodelist);
382
383 exit (0);
384 }});
385
386
387__PACKAGE__->register_method ({
388 name => 'delnode',
389 path => 'delnode',
390 method => 'PUT',
391 description => "Removes a node to the cluster configuration.",
392 parameters => {
393 additionalProperties => 0,
394 properties => {
7aed8248
WL
395 node => {
396 type => 'string',
397 description => "Hostname or IP of the corosync ring0 address of this node.",
398 },
13d44dc5
DM
399 },
400 },
401 returns => { type => 'null' },
402
403 code => sub {
404 my ($param) = @_;
405
406 PVE::Cluster::check_cfs_quorum();
407
408 my $conf = PVE::Cluster::cfs_read_file("corosync.conf");
409
410 my $nodelist = corosync_nodelist($conf);
411
7aed8248 412 my $node;
d09373b4 413 my $nodeid;
7aed8248
WL
414
415 foreach my $tmp_node (keys %$nodelist) {
d09373b4
DM
416 my $d = $nodelist->{$tmp_node};
417 my $ring0_addr = $d->{ring0_addr};
418 my $ring1_addr = $d->{ring1_addr};
7aed8248
WL
419 if (($tmp_node eq $param->{node}) ||
420 (defined($ring0_addr) && ($ring0_addr eq $param->{node})) ||
421 (defined($ring1_addr) && ($ring1_addr eq $param->{node}))) {
422 $node = $tmp_node;
d09373b4 423 $nodeid = $d->{nodeid};
7aed8248
WL
424 last;
425 }
426 }
427
428 die "Node/IP: $param->{node} is not a known host of the cluster.\n"
429 if !defined($node);
430
431 delete $nodelist->{$node};
432
13d44dc5
DM
433 corosync_update_nodelist($conf, $nodelist);
434
d09373b4
DM
435 PVE::Tools::run_command(['corosync-cfgtool','-k', $nodeid])
436 if defined($nodeid);
437
13d44dc5
DM
438 return undef;
439 }});
440
441__PACKAGE__->register_method ({
442 name => 'add',
443 path => 'add',
444 method => 'PUT',
445 description => "Adds the current node to an existing cluster.",
446 parameters => {
447 additionalProperties => 0,
448 properties => {
449 hostname => {
450 type => 'string',
451 description => "Hostname (or IP) of an existing cluster member."
452 },
453 nodeid => {
454 type => 'integer',
455 description => "Node id for this node.",
456 minimum => 1,
457 optional => 1,
458 },
459 votes => {
460 type => 'integer',
461 description => "Number of votes for this node",
462 minimum => 0,
463 optional => 1,
464 },
465 force => {
466 type => 'boolean',
467 description => "Do not throw error if node already exists.",
468 optional => 1,
469 },
14d0000a
TL
470 ring0_addr => {
471 type => 'string', format => 'address',
472 description => "Hostname (or IP) of the corosync ring0 address of this node.".
473 " Defaults to nodes hostname.",
474 optional => 1,
475 },
476 ring1_addr => {
477 type => 'string', format => 'address',
478 description => "Hostname (or IP) of the corosync ring1 address, this".
479 " needs an valid configured ring 1 interface in the cluster.",
480 optional => 1,
481 },
13d44dc5
DM
482 },
483 },
484 returns => { type => 'null' },
485
486 code => sub {
487 my ($param) = @_;
488
489 my $nodename = PVE::INotify::nodename();
490
6c0e95b3 491 PVE::Cluster::setup_sshd_config(1);
13d44dc5
DM
492 PVE::Cluster::setup_rootsshconfig();
493 PVE::Cluster::setup_ssh_keys();
494
495 my $host = $param->{hostname};
496
497 if (!$param->{force}) {
498
499 if (-f $authfile) {
500 die "authentication key already exists\n";
501 }
502
503 if (-f $clusterconf) {
504 die "cluster config '$clusterconf' already exists\n";
505 }
506
507 my $vmlist = PVE::Cluster::get_vmlist();
508 if ($vmlist && $vmlist->{ids} && scalar(keys %{$vmlist->{ids}})) {
509 die "this host already contains virtual machines - please remove them first\n";
510 }
511
512 if (system("corosync-quorumtool >/dev/null 2>&1") == 0) {
513 die "corosync is already running\n";
514 }
515 }
516
517 # make sure known_hosts is on local filesystem
518 PVE::Cluster::ssh_unmerge_known_hosts();
519
de4b4155
FG
520 my $cmd = ['ssh-copy-id', '-i', '/root/.ssh/id_rsa', "root\@$host"];
521 PVE::Tools::run_command($cmd, 'outfunc' => sub {}, 'errfunc' => sub {},
522 'errmsg' => "unable to copy ssh ID");
13d44dc5
DM
523
524 $cmd = ['ssh', $host, '-o', 'BatchMode=yes',
525 'pvecm', 'addnode', $nodename, '--force', 1];
526
527 push @$cmd, '--nodeid', $param->{nodeid} if $param->{nodeid};
528
529 push @$cmd, '--votes', $param->{votes} if defined($param->{votes});
530
14d0000a
TL
531 push @$cmd, '--ring0_addr', $param->{ring0_addr} if defined($param->{ring0_addr});
532
533 push @$cmd, '--ring1_addr', $param->{ring1_addr} if defined($param->{ring1_addr});
534
13d44dc5
DM
535 if (system (@$cmd) != 0) {
536 my $cmdtxt = join (' ', @$cmd);
537 die "unable to add node: command failed ($cmdtxt)\n";
538 }
539
540 my $tmpdir = "$libdir/.pvecm_add.tmp.$$";
541 mkdir $tmpdir;
542
543 eval {
544 print "copy corosync auth key\n";
545 $cmd = ['rsync', '--rsh=ssh -l root -o BatchMode=yes', '-lpgoq',
546 "[$host]:$authfile $clusterconf", $tmpdir];
547
548 system(@$cmd) == 0 || die "can't rsync data from host '$host'\n";
549
550 mkdir "/etc/corosync";
551 my $confbase = basename($clusterconf);
552
553 $cmd = "cp '$tmpdir/$confbase' '/etc/corosync/$confbase'";
554 system($cmd) == 0 || die "can't copy cluster configuration\n";
555
556 my $keybase = basename($authfile);
557 system ("cp '$tmpdir/$keybase' '$authfile'") == 0 ||
558 die "can't copy '$tmpdir/$keybase' to '$authfile'\n";
559
560 print "stopping pve-cluster service\n";
561
562 system("umount $basedir -f >/dev/null 2>&1");
563 system("systemctl stop pve-cluster") == 0 ||
564 die "can't stop pve-cluster service\n";
565
566 backup_database();
567
568 unlink $dbfile;
569
570 system("systemctl start pve-cluster") == 0 ||
571 die "starting pve-cluster failed\n";
572
573 system("systemctl start corosync");
574
575 # wait for quorum
576 my $printqmsg = 1;
577 while (!PVE::Cluster::check_cfs_quorum(1)) {
578 if ($printqmsg) {
579 print "waiting for quorum...";
580 STDOUT->flush();
581 $printqmsg = 0;
582 }
583 sleep(1);
584 }
585 print "OK\n" if !$printqmsg;
586
587 # system("systemctl start clvm");
588
589 my $local_ip_address = PVE::Cluster::remote_node_ip($nodename);
590
591 print "generating node certificates\n";
592 PVE::Cluster::gen_pve_node_files($nodename, $local_ip_address);
593
594 print "merge known_hosts file\n";
595 PVE::Cluster::ssh_merge_known_hosts($nodename, $local_ip_address, 1);
596
597 print "restart services\n";
598 # restart pvedaemon (changed certs)
599 system("systemctl restart pvedaemon");
600 # restart pveproxy (changed certs)
601 system("systemctl restart pveproxy");
602
603 print "successfully added node '$nodename' to cluster.\n";
604 };
605 my $err = $@;
606
607 rmtree $tmpdir;
608
609 die $err if $err;
610
611 return undef;
612 }});
613
614__PACKAGE__->register_method ({
615 name => 'status',
616 path => 'status',
617 method => 'GET',
618 description => "Displays the local view of the cluster status.",
619 parameters => {
620 additionalProperties => 0,
621 properties => {},
622 },
623 returns => { type => 'null' },
624
625 code => sub {
626 my ($param) = @_;
627
eb51b829
FG
628 PVE::Cluster::check_corosync_conf_exists();
629
13d44dc5
DM
630 my $cmd = ['corosync-quorumtool', '-siH'];
631
632 exec (@$cmd);
633
634 exit (-1); # should not be reached
635 }});
636
637__PACKAGE__->register_method ({
638 name => 'nodes',
639 path => 'nodes',
640 method => 'GET',
641 description => "Displays the local view of the cluster nodes.",
642 parameters => {
643 additionalProperties => 0,
644 properties => {},
645 },
646 returns => { type => 'null' },
647
648 code => sub {
649 my ($param) = @_;
650
eb51b829
FG
651 PVE::Cluster::check_corosync_conf_exists();
652
13d44dc5
DM
653 my $cmd = ['corosync-quorumtool', '-l'];
654
655 exec (@$cmd);
656
657 exit (-1); # should not be reached
658 }});
659
660__PACKAGE__->register_method ({
661 name => 'expected',
662 path => 'expected',
663 method => 'PUT',
664 description => "Tells corosync a new value of expected votes.",
665 parameters => {
666 additionalProperties => 0,
667 properties => {
668 expected => {
669 type => 'integer',
670 description => "Expected votes",
671 minimum => 1,
672 },
673 },
674 },
675 returns => { type => 'null' },
676
677 code => sub {
678 my ($param) = @_;
679
eb51b829
FG
680 PVE::Cluster::check_corosync_conf_exists();
681
13d44dc5
DM
682 my $cmd = ['corosync-quorumtool', '-e', $param->{expected}];
683
684 exec (@$cmd);
685
686 exit (-1); # should not be reached
687
688 }});
689
690sub corosync_update_nodelist {
691 my ($conf, $nodelist) = @_;
692
693 delete $conf->{digest};
694
695 my $version = PVE::Cluster::corosync_conf_version($conf);
696 PVE::Cluster::corosync_conf_version($conf, undef, $version + 1);
697
698 my $children = [];
699 foreach my $v (values %$nodelist) {
baf39b62 700 next if !($v->{ring0_addr} || $v->{name});
13d44dc5
DM
701 my $kv = [];
702 foreach my $k (keys %$v) {
703 push @$kv, { key => $k, value => $v->{$k} };
704 }
705 my $ns = { section => 'node', children => $kv };
706 push @$children, $ns;
707 }
708
709 foreach my $main (@{$conf->{children}}) {
710 next if !defined($main->{section});
711 if ($main->{section} eq 'nodelist') {
712 $main->{children} = $children;
713 last;
714 }
715 }
716
717
718 PVE::Cluster::cfs_write_file("corosync.conf.new", $conf);
719
720 rename("/etc/pve/corosync.conf.new", "/etc/pve/corosync.conf")
721 || die "activate corosync.conf.new failed - $!\n";
722}
723
724sub corosync_nodelist {
725 my ($conf) = @_;
13d44dc5
DM
726
727 my $nodelist = {};
baf39b62 728
13d44dc5
DM
729 foreach my $main (@{$conf->{children}}) {
730 next if !defined($main->{section});
731 if ($main->{section} eq 'nodelist') {
732 foreach my $ne (@{$main->{children}}) {
733 next if !defined($ne->{section}) || ($ne->{section} ne 'node');
734 my $node = { quorum_votes => 1 };
baf39b62 735 my $name;
13d44dc5
DM
736 foreach my $child (@{$ne->{children}}) {
737 next if !defined($child->{key});
738 $node->{$child->{key}} = $child->{value};
baf39b62
TL
739 # use 'name' over 'ring0_addr' if set
740 if ($child->{key} eq 'name') {
741 delete $nodelist->{$name} if $name;
742 $name = $child->{value};
743 $nodelist->{$name} = $node;
744 } elsif(!$name && $child->{key} eq 'ring0_addr') {
745 $name = $child->{value};
746 $nodelist->{$name} = $node;
13d44dc5
DM
747 }
748 }
749 }
750 }
751 }
752
753 return $nodelist;
754}
755
baf39b62
TL
756# get a hash representation of the corosync config totem section
757sub corosync_totem_config {
758 my ($conf) = @_;
759
760 my $res = {};
761
762 foreach my $main (@{$conf->{children}}) {
763 next if !defined($main->{section}) ||
764 $main->{section} ne 'totem';
765
766 foreach my $e (@{$main->{children}}) {
767
768 if ($e->{section} && $e->{section} eq 'interface') {
769 my $entry = {};
770
771 $res->{interface} = {};
772
773 foreach my $child (@{$e->{children}}) {
774 next if !defined($child->{key});
775 $entry->{$child->{key}} = $child->{value};
776 if($child->{key} eq 'ringnumber') {
777 $res->{interface}->{$child->{value}} = $entry;
778 }
779 }
780
781 } elsif ($e->{key}) {
782 $res->{$e->{key}} = $e->{value};
783 }
784 }
785 }
786
787 return $res;
788}
789
13d44dc5
DM
790__PACKAGE__->register_method ({
791 name => 'updatecerts',
792 path => 'updatecerts',
793 method => 'PUT',
794 description => "Update node certificates (and generate all needed files/directories).",
795 parameters => {
796 additionalProperties => 0,
797 properties => {
798 force => {
799 description => "Force generation of new SSL certifate.",
800 type => 'boolean',
801 optional => 1,
802 },
803 silent => {
804 description => "Ignore errors (i.e. when cluster has no quorum).",
805 type => 'boolean',
806 optional => 1,
807 },
808 },
809 },
810 returns => { type => 'null' },
811 code => sub {
812 my ($param) = @_;
813
6c0e95b3 814 PVE::Cluster::setup_sshd_config(0);
13d44dc5
DM
815 PVE::Cluster::setup_rootsshconfig();
816
817 PVE::Cluster::gen_pve_vzdump_symlink();
818
819 if (!PVE::Cluster::check_cfs_quorum(1)) {
820 return undef if $param->{silent};
821 die "no quorum - unable to update files\n";
822 }
823
824 PVE::Cluster::setup_ssh_keys();
825
826 my $nodename = PVE::INotify::nodename();
827
828 my $local_ip_address = PVE::Cluster::remote_node_ip($nodename);
829
830 PVE::Cluster::gen_pve_node_files($nodename, $local_ip_address, $param->{force});
831 PVE::Cluster::ssh_merge_keys();
832 PVE::Cluster::ssh_merge_known_hosts($nodename, $local_ip_address);
833 PVE::Cluster::gen_pve_vzdump_files();
834
835 return undef;
836 }});
837
3a966e22
TL
838__PACKAGE__->register_method ({
839 name => 'mtunnel',
840 path => 'mtunnel',
841 method => 'POST',
842 description => "Used by VM/CT migration - do not use manually.",
843 parameters => {
844 additionalProperties => 0,
f83d8153
TL
845 properties => {
846 get_migration_ip => {
847 type => 'boolean',
848 default => 0,
849 description => 'return the migration IP, if configured',
850 optional => 1,
851 },
852 migration_network => {
853 type => 'string',
854 format => 'CIDR',
855 description => 'the migration network used to detect the local migration IP',
856 optional => 1,
857 },
858 },
3a966e22
TL
859 },
860 returns => { type => 'null'},
861 code => sub {
862 my ($param) = @_;
863
864 if (!PVE::Cluster::check_cfs_quorum(1)) {
865 print "no quorum\n";
866 return undef;
867 }
868
f83d8153
TL
869 if ($param->{get_migration_ip}) {
870 my $network = $param->{migration_network};
43f4c5f6 871 if (my $ip = PVE::Cluster::get_local_migration_ip($network)) {
f83d8153
TL
872 print "ip: '$ip'\n";
873 } else {
43f4c5f6 874 print "no ip\n";
f83d8153
TL
875 }
876 # do not keep tunnel open when asked for migration ip
877 return undef;
878 }
879
3a966e22
TL
880 print "tunnel online\n";
881 *STDOUT->flush();
882
883 while (my $line = <>) {
884 chomp $line;
885 last if $line =~ m/^quit$/;
886 }
887
888 return undef;
889 }});
890
13d44dc5
DM
891
892our $cmddef = {
893 keygen => [ __PACKAGE__, 'keygen', ['filename']],
894 create => [ __PACKAGE__, 'create', ['clustername']],
895 add => [ __PACKAGE__, 'add', ['hostname']],
896 addnode => [ __PACKAGE__, 'addnode', ['node']],
897 delnode => [ __PACKAGE__, 'delnode', ['node']],
898 status => [ __PACKAGE__, 'status' ],
899 nodes => [ __PACKAGE__, 'nodes' ],
900 expected => [ __PACKAGE__, 'expected', ['expected']],
901 updatecerts => [ __PACKAGE__, 'updatecerts', []],
3a966e22 902 mtunnel => [ __PACKAGE__, 'mtunnel', []],
13d44dc5
DM
903};
904
9051;