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