]> git.proxmox.com Git - pve-cluster.git/blame - data/PVE/CLI/pvecm.pm
fix file permission check in chmod
[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;
c53b111f 11use Data::Dumper; # fixme: remove
13d44dc5
DM
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;
c53b111f 34
13d44dc5 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 }
c53b111f 53
13d44dc5
DM
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 ({
c53b111f 64 name => 'keygen',
13d44dc5
DM
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' },
c53b111f 78
13d44dc5
DM
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];
c53b111f 94 PVE::Tools::run_command($cmd);
13d44dc5
DM
95
96 return undef;
97 }});
98
99__PACKAGE__->register_method ({
c53b111f 100 name => 'create',
13d44dc5
DM
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 },
14d0000a
TL
136 bindnet1_addr => {
137 type => 'string', format => 'ip',
138 description => "This specifies the network address the corosync ring 1".
139 " executive should bind to and is optional.",
140 optional => 1,
141 },
142 ring1_addr => {
143 type => 'string', format => 'address',
144 description => "Hostname (or IP) of the corosync ring1 address, this".
145 " needs an valid bindnet1_addr.",
146 optional => 1,
147 },
148 },
13d44dc5
DM
149 },
150 returns => { type => 'null' },
c53b111f 151
13d44dc5
DM
152 code => sub {
153 my ($param) = @_;
154
155 -f $clusterconf && die "cluster config '$clusterconf' already exists\n";
156
6c0e95b3 157 PVE::Cluster::setup_sshd_config(1);
13d44dc5
DM
158 PVE::Cluster::setup_rootsshconfig();
159 PVE::Cluster::setup_ssh_keys();
160
161 -f $authfile || __PACKAGE__->keygen({filename => $authfile});
162
163 -f $authfile || die "no authentication key available\n";
164
165 my $clustername = $param->{clustername};
166
167 $param->{nodeid} = 1 if !$param->{nodeid};
168
169 $param->{votes} = 1 if !defined($param->{votes});
170
171 my $nodename = PVE::INotify::nodename();
c53b111f 172
13d44dc5
DM
173 my $local_ip_address = PVE::Cluster::remote_node_ip($nodename);
174
14d0000a
TL
175 $param->{bindnet0_addr} = $local_ip_address
176 if !defined($param->{bindnet0_addr});
177
178 $param->{ring0_addr} = $nodename if !defined($param->{ring0_addr});
179
180 die "Param bindnet1_addr and ring1_addr are dependend, use both or none!\n"
181 if (defined($param->{bindnet1_addr}) != defined($param->{ring1_addr}));
182
183 my $bind_is_ipv6 = Net::IP::ip_is_ipv6($param->{bindnet0_addr});
184
185 # use string as here-doc format distracts more
186 my $interfaces = "interface {\n ringnumber: 0\n" .
187 " bindnetaddr: $param->{bindnet0_addr}\n }";
188
189 my $ring_addresses = "ring0_addr: $param->{ring0_addr}" ;
190
191 # allow use of multiple rings (rrp) at cluster creation time
192 if ($param->{bindnet1_addr}) {
193 die "IPv6 and IPv4 cannot be mixed, use one or the other!\n"
194 if Net::IP::ip_is_ipv6($param->{bindnet1_addr}) != $bind_is_ipv6;
195
14d0000a
TL
196 $interfaces .= "\n interface {\n ringnumber: 1\n" .
197 " bindnetaddr: $param->{bindnet1_addr}\n }\n";
198
606a8904
TL
199 $interfaces .= "rrp_mode: passive\n"; # only passive is stable and tested
200
14d0000a
TL
201 $ring_addresses .= "\n ring1_addr: $param->{ring1_addr}";
202
203 } elsif($param->{rrp_mode} && $param->{rrp_mode} ne 'none') {
204
205 warn "rrp_mode '$param->{rrp_mode}' useless when using only one".
206 " ring, using 'none' instead";
207 # corosync defaults to none if only one interface is configured
208 $param->{rrp_mode} = undef;
209
210 }
211
13d44dc5 212 # No, corosync cannot deduce this on its own
14d0000a 213 my $ipversion = $bind_is_ipv6 ? 'ipv6' : 'ipv4';
13d44dc5
DM
214
215 my $config = <<_EOD;
216totem {
217 version: 2
218 secauth: on
219 cluster_name: $clustername
220 config_version: 1
221 ip_version: $ipversion
14d0000a 222 $interfaces
13d44dc5
DM
223}
224
225nodelist {
226 node {
14d0000a
TL
227 $ring_addresses
228 name: $nodename
13d44dc5
DM
229 nodeid: $param->{nodeid}
230 quorum_votes: $param->{votes}
231 }
232}
14d0000a 233
13d44dc5
DM
234quorum {
235 provider: corosync_votequorum
236}
237
238logging {
239 to_syslog: yes
240 debug: off
241}
242_EOD
14d0000a 243;
13d44dc5
DM
244 PVE::Tools::file_set_contents($clusterconf, $config);
245
246 PVE::Cluster::ssh_merge_keys();
247
248 PVE::Cluster::gen_pve_node_files($nodename, $local_ip_address);
249
250 PVE::Cluster::ssh_merge_known_hosts($nodename, $local_ip_address, 1);
251
252 PVE::Tools::run_command('systemctl restart pve-cluster'); # restart
253
254 PVE::Tools::run_command('systemctl restart corosync'); # restart
c53b111f 255
13d44dc5
DM
256 return undef;
257}});
258
259__PACKAGE__->register_method ({
c53b111f 260 name => 'addnode',
13d44dc5
DM
261 path => 'addnode',
262 method => 'PUT',
263 description => "Adds a node to the cluster configuration.",
264 parameters => {
265 additionalProperties => 0,
266 properties => {
267 node => PVE::JSONSchema::get_standard_option('pve-node'),
268 nodeid => {
269 type => 'integer',
270 description => "Node id for this node.",
271 minimum => 1,
272 optional => 1,
273 },
274 votes => {
275 type => 'integer',
276 description => "Number of votes for this node",
277 minimum => 0,
278 optional => 1,
279 },
280 force => {
281 type => 'boolean',
282 description => "Do not throw error if node already exists.",
283 optional => 1,
284 },
14d0000a
TL
285 ring0_addr => {
286 type => 'string', format => 'address',
287 description => "Hostname (or IP) of the corosync ring0 address of this node.".
288 " Defaults to nodes hostname.",
289 optional => 1,
290 },
291 ring1_addr => {
292 type => 'string', format => 'address',
293 description => "Hostname (or IP) of the corosync ring1 address, this".
294 " needs an valid bindnet1_addr.",
295 optional => 1,
296 },
13d44dc5
DM
297 },
298 },
299 returns => { type => 'null' },
c53b111f 300
13d44dc5
DM
301 code => sub {
302 my ($param) = @_;
303
e73b85c0
TL
304 if (!$param->{force} && (-t STDIN || -t STDOUT)) {
305 die "error: `addnode` should not get called interactively!\nUse ".
306 "`pvecm add <cluster-node>` to add a node to a cluster!\n";
307 }
308
13d44dc5
DM
309 PVE::Cluster::check_cfs_quorum();
310
311 my $conf = PVE::Cluster::cfs_read_file("corosync.conf");
312
4ed5e654 313 my $nodelist = PVE::Cluster::corosync_nodelist($conf);
13d44dc5 314
4ed5e654 315 my $totem_cfg = PVE::Cluster::corosync_totem_config($conf);
14d0000a 316
13d44dc5
DM
317 my $name = $param->{node};
318
77a1c918
TL
319 # ensure we do not reuse an address, that can crash the whole cluster!
320 my $check_duplicate_addr = sub {
321 my $addr = shift;
322 return if !defined($addr);
323
324 while (my ($k, $v) = each %$nodelist) {
325 next if $k eq $name; # allows re-adding a node if force is set
326 if ($v->{ring0_addr} eq $addr || ($v->{ring1_addr} && $v->{ring1_addr} eq $addr)) {
327 die "corosync: address '$addr' already defined by node '$k'\n";
328 }
329 }
330 };
331
332 &$check_duplicate_addr($param->{ring0_addr});
333 &$check_duplicate_addr($param->{ring1_addr});
334
14d0000a
TL
335 $param->{ring0_addr} = $name if !$param->{ring0_addr};
336
bb8583d0 337 die "corosync: using 'ring1_addr' parameter needs a configured ring 1 interface!\n"
14d0000a
TL
338 if $param->{ring1_addr} && !defined($totem_cfg->{interface}->{1});
339
bb8583d0
TL
340 die "corosync: ring 1 interface configured but 'ring1_addr' parameter not defined!\n"
341 if defined($totem_cfg->{interface}->{1}) && !defined($param->{ring1_addr});
342
13d44dc5
DM
343 if (defined(my $res = $nodelist->{$name})) {
344 $param->{nodeid} = $res->{nodeid} if !$param->{nodeid};
345 $param->{votes} = $res->{quorum_votes} if !defined($param->{votes});
346
347 if ($res->{quorum_votes} == $param->{votes} &&
348 $res->{nodeid} == $param->{nodeid}) {
349 print "node $name already defined\n";
350 if ($param->{force}) {
351 exit (0);
352 } else {
353 exit (-1);
354 }
355 } else {
356 die "can't add existing node\n";
357 }
358 } elsif (!$param->{nodeid}) {
359 my $nodeid = 1;
c53b111f 360
13d44dc5 361 while(1) {
c53b111f 362 my $found = 0;
13d44dc5
DM
363 foreach my $v (values %$nodelist) {
364 if ($v->{nodeid} eq $nodeid) {
365 $found = 1;
366 $nodeid++;
367 last;
368 }
369 }
370 last if !$found;
371 };
372
373 $param->{nodeid} = $nodeid;
374 }
375
376 $param->{votes} = 1 if !defined($param->{votes});
377
378 PVE::Cluster::gen_local_dirs($name);
379
380 eval { PVE::Cluster::ssh_merge_keys(); };
381 warn $@ if $@;
382
14d0000a
TL
383 $nodelist->{$name} = {
384 ring0_addr => $param->{ring0_addr},
385 nodeid => $param->{nodeid},
386 name => $name,
387 };
388 $nodelist->{$name}->{ring1_addr} = $param->{ring1_addr} if $param->{ring1_addr};
13d44dc5 389 $nodelist->{$name}->{quorum_votes} = $param->{votes} if $param->{votes};
c53b111f 390
4ed5e654 391 PVE::Cluster::corosync_update_nodelist($conf, $nodelist);
c53b111f 392
13d44dc5
DM
393 exit (0);
394 }});
395
396
397__PACKAGE__->register_method ({
c53b111f 398 name => 'delnode',
13d44dc5
DM
399 path => 'delnode',
400 method => 'PUT',
401 description => "Removes a node to the cluster configuration.",
402 parameters => {
403 additionalProperties => 0,
404 properties => {
7aed8248
WL
405 node => {
406 type => 'string',
407 description => "Hostname or IP of the corosync ring0 address of this node.",
408 },
13d44dc5
DM
409 },
410 },
411 returns => { type => 'null' },
c53b111f 412
13d44dc5
DM
413 code => sub {
414 my ($param) = @_;
415
416 PVE::Cluster::check_cfs_quorum();
417
418 my $conf = PVE::Cluster::cfs_read_file("corosync.conf");
419
4ed5e654 420 my $nodelist = PVE::Cluster::corosync_nodelist($conf);
13d44dc5 421
7aed8248 422 my $node;
d09373b4 423 my $nodeid;
7aed8248
WL
424
425 foreach my $tmp_node (keys %$nodelist) {
d09373b4
DM
426 my $d = $nodelist->{$tmp_node};
427 my $ring0_addr = $d->{ring0_addr};
428 my $ring1_addr = $d->{ring1_addr};
7aed8248
WL
429 if (($tmp_node eq $param->{node}) ||
430 (defined($ring0_addr) && ($ring0_addr eq $param->{node})) ||
431 (defined($ring1_addr) && ($ring1_addr eq $param->{node}))) {
432 $node = $tmp_node;
d09373b4 433 $nodeid = $d->{nodeid};
7aed8248
WL
434 last;
435 }
436 }
437
438 die "Node/IP: $param->{node} is not a known host of the cluster.\n"
439 if !defined($node);
440
441 delete $nodelist->{$node};
442
4ed5e654 443 PVE::Cluster::corosync_update_nodelist($conf, $nodelist);
13d44dc5 444
d09373b4
DM
445 PVE::Tools::run_command(['corosync-cfgtool','-k', $nodeid])
446 if defined($nodeid);
447
13d44dc5
DM
448 return undef;
449 }});
450
451__PACKAGE__->register_method ({
c53b111f 452 name => 'add',
13d44dc5
DM
453 path => 'add',
454 method => 'PUT',
455 description => "Adds the current node to an existing cluster.",
456 parameters => {
457 additionalProperties => 0,
458 properties => {
459 hostname => {
460 type => 'string',
461 description => "Hostname (or IP) of an existing cluster member."
462 },
463 nodeid => {
464 type => 'integer',
465 description => "Node id for this node.",
466 minimum => 1,
467 optional => 1,
468 },
469 votes => {
470 type => 'integer',
471 description => "Number of votes for this node",
472 minimum => 0,
473 optional => 1,
474 },
475 force => {
476 type => 'boolean',
477 description => "Do not throw error if node already exists.",
478 optional => 1,
479 },
14d0000a
TL
480 ring0_addr => {
481 type => 'string', format => 'address',
482 description => "Hostname (or IP) of the corosync ring0 address of this node.".
483 " Defaults to nodes hostname.",
484 optional => 1,
485 },
486 ring1_addr => {
487 type => 'string', format => 'address',
488 description => "Hostname (or IP) of the corosync ring1 address, this".
489 " needs an valid configured ring 1 interface in the cluster.",
490 optional => 1,
491 },
13d44dc5
DM
492 },
493 },
494 returns => { type => 'null' },
c53b111f 495
13d44dc5
DM
496 code => sub {
497 my ($param) = @_;
498
499 my $nodename = PVE::INotify::nodename();
500
6c0e95b3 501 PVE::Cluster::setup_sshd_config(1);
13d44dc5
DM
502 PVE::Cluster::setup_rootsshconfig();
503 PVE::Cluster::setup_ssh_keys();
504
505 my $host = $param->{hostname};
506
5a630d8f
TL
507 my ($errors, $warnings) = ('', '');
508
509 my $error = sub {
510 my ($msg, $suppress) = @_;
511
512 if ($suppress) {
513 $warnings .= "* $msg\n";
514 } else {
515 $errors .= "* $msg\n";
516 }
517 };
518
13d44dc5 519 if (!$param->{force}) {
c53b111f 520
13d44dc5 521 if (-f $authfile) {
5a630d8f 522 &$error("authentication key '$authfile' already exists", $param->{force});
13d44dc5
DM
523 }
524
525 if (-f $clusterconf) {
5a630d8f 526 &$error("cluster config '$clusterconf' already exists", $param->{force});
13d44dc5
DM
527 }
528
529 my $vmlist = PVE::Cluster::get_vmlist();
530 if ($vmlist && $vmlist->{ids} && scalar(keys %{$vmlist->{ids}})) {
5a630d8f 531 &$error("this host already contains virtual guests", $param->{force});
13d44dc5
DM
532 }
533
cecd0323 534 if (system("corosync-quorumtool -l >/dev/null 2>&1") == 0) {
5a630d8f 535 &$error("corosync is already running, is this node already in a cluster?!", $param->{force});
13d44dc5
DM
536 }
537 }
538
f566b424
TL
539 # check if corosync ring IPs are configured on the current nodes interfaces
540 my $check_ip = sub {
541 my $ip = shift;
542 if (defined($ip)) {
543 my $cidr = (Net::IP::ip_is_ipv6($ip)) ? "$ip/128" : "$ip/32";
544 my $configured_ips = PVE::Network::get_local_ip_from_cidr($cidr);
545
546 &$error("cannot use IP '$ip', it must be configured exactly once on local node!\n")
547 if (scalar(@$configured_ips) != 1);
548 }
549 };
550
551 &$check_ip($param->{ring0_addr});
552 &$check_ip($param->{ring1_addr});
553
5a630d8f
TL
554 warn "warning, ignore the following errors:\n$warnings" if $warnings;
555 die "detected the following error(s):\n$errors" if $errors;
556
13d44dc5
DM
557 # make sure known_hosts is on local filesystem
558 PVE::Cluster::ssh_unmerge_known_hosts();
559
de4b4155
FG
560 my $cmd = ['ssh-copy-id', '-i', '/root/.ssh/id_rsa', "root\@$host"];
561 PVE::Tools::run_command($cmd, 'outfunc' => sub {}, 'errfunc' => sub {},
562 'errmsg' => "unable to copy ssh ID");
13d44dc5
DM
563
564 $cmd = ['ssh', $host, '-o', 'BatchMode=yes',
565 'pvecm', 'addnode', $nodename, '--force', 1];
566
567 push @$cmd, '--nodeid', $param->{nodeid} if $param->{nodeid};
568
569 push @$cmd, '--votes', $param->{votes} if defined($param->{votes});
570
14d0000a
TL
571 push @$cmd, '--ring0_addr', $param->{ring0_addr} if defined($param->{ring0_addr});
572
573 push @$cmd, '--ring1_addr', $param->{ring1_addr} if defined($param->{ring1_addr});
574
13d44dc5
DM
575 if (system (@$cmd) != 0) {
576 my $cmdtxt = join (' ', @$cmd);
577 die "unable to add node: command failed ($cmdtxt)\n";
578 }
579
580 my $tmpdir = "$libdir/.pvecm_add.tmp.$$";
581 mkdir $tmpdir;
582
583 eval {
584 print "copy corosync auth key\n";
c53b111f 585 $cmd = ['rsync', '--rsh=ssh -l root -o BatchMode=yes', '-lpgoq',
13d44dc5
DM
586 "[$host]:$authfile $clusterconf", $tmpdir];
587
588 system(@$cmd) == 0 || die "can't rsync data from host '$host'\n";
589
590 mkdir "/etc/corosync";
591 my $confbase = basename($clusterconf);
592
593 $cmd = "cp '$tmpdir/$confbase' '/etc/corosync/$confbase'";
594 system($cmd) == 0 || die "can't copy cluster configuration\n";
595
596 my $keybase = basename($authfile);
597 system ("cp '$tmpdir/$keybase' '$authfile'") == 0 ||
598 die "can't copy '$tmpdir/$keybase' to '$authfile'\n";
599
600 print "stopping pve-cluster service\n";
601
602 system("umount $basedir -f >/dev/null 2>&1");
603 system("systemctl stop pve-cluster") == 0 ||
604 die "can't stop pve-cluster service\n";
605
606 backup_database();
607
608 unlink $dbfile;
609
610 system("systemctl start pve-cluster") == 0 ||
611 die "starting pve-cluster failed\n";
612
613 system("systemctl start corosync");
614
615 # wait for quorum
616 my $printqmsg = 1;
617 while (!PVE::Cluster::check_cfs_quorum(1)) {
618 if ($printqmsg) {
619 print "waiting for quorum...";
620 STDOUT->flush();
621 $printqmsg = 0;
622 }
623 sleep(1);
624 }
625 print "OK\n" if !$printqmsg;
626
13d44dc5
DM
627 my $local_ip_address = PVE::Cluster::remote_node_ip($nodename);
628
629 print "generating node certificates\n";
c53b111f 630 PVE::Cluster::gen_pve_node_files($nodename, $local_ip_address);
13d44dc5
DM
631
632 print "merge known_hosts file\n";
633 PVE::Cluster::ssh_merge_known_hosts($nodename, $local_ip_address, 1);
634
635 print "restart services\n";
636 # restart pvedaemon (changed certs)
637 system("systemctl restart pvedaemon");
638 # restart pveproxy (changed certs)
639 system("systemctl restart pveproxy");
640
641 print "successfully added node '$nodename' to cluster.\n";
642 };
643 my $err = $@;
644
645 rmtree $tmpdir;
646
647 die $err if $err;
648
649 return undef;
650 }});
651
652__PACKAGE__->register_method ({
c53b111f 653 name => 'status',
13d44dc5
DM
654 path => 'status',
655 method => 'GET',
656 description => "Displays the local view of the cluster status.",
657 parameters => {
658 additionalProperties => 0,
659 properties => {},
660 },
661 returns => { type => 'null' },
c53b111f 662
13d44dc5
DM
663 code => sub {
664 my ($param) = @_;
665
eb51b829
FG
666 PVE::Cluster::check_corosync_conf_exists();
667
13d44dc5
DM
668 my $cmd = ['corosync-quorumtool', '-siH'];
669
670 exec (@$cmd);
671
672 exit (-1); # should not be reached
673 }});
674
675__PACKAGE__->register_method ({
c53b111f 676 name => 'nodes',
13d44dc5
DM
677 path => 'nodes',
678 method => 'GET',
679 description => "Displays the local view of the cluster nodes.",
680 parameters => {
681 additionalProperties => 0,
682 properties => {},
683 },
684 returns => { type => 'null' },
c53b111f 685
13d44dc5
DM
686 code => sub {
687 my ($param) = @_;
688
eb51b829
FG
689 PVE::Cluster::check_corosync_conf_exists();
690
13d44dc5
DM
691 my $cmd = ['corosync-quorumtool', '-l'];
692
693 exec (@$cmd);
694
695 exit (-1); # should not be reached
696 }});
697
698__PACKAGE__->register_method ({
c53b111f 699 name => 'expected',
13d44dc5
DM
700 path => 'expected',
701 method => 'PUT',
702 description => "Tells corosync a new value of expected votes.",
703 parameters => {
704 additionalProperties => 0,
705 properties => {
706 expected => {
707 type => 'integer',
708 description => "Expected votes",
709 minimum => 1,
710 },
711 },
712 },
713 returns => { type => 'null' },
c53b111f 714
13d44dc5
DM
715 code => sub {
716 my ($param) = @_;
717
eb51b829
FG
718 PVE::Cluster::check_corosync_conf_exists();
719
13d44dc5
DM
720 my $cmd = ['corosync-quorumtool', '-e', $param->{expected}];
721
722 exec (@$cmd);
723
724 exit (-1); # should not be reached
725
726 }});
727
13d44dc5 728__PACKAGE__->register_method ({
c53b111f 729 name => 'updatecerts',
13d44dc5
DM
730 path => 'updatecerts',
731 method => 'PUT',
732 description => "Update node certificates (and generate all needed files/directories).",
733 parameters => {
734 additionalProperties => 0,
735 properties => {
736 force => {
737 description => "Force generation of new SSL certifate.",
738 type => 'boolean',
739 optional => 1,
740 },
741 silent => {
742 description => "Ignore errors (i.e. when cluster has no quorum).",
743 type => 'boolean',
744 optional => 1,
745 },
746 },
747 },
748 returns => { type => 'null' },
749 code => sub {
750 my ($param) = @_;
751
6c0e95b3 752 PVE::Cluster::setup_sshd_config(0);
13d44dc5
DM
753 PVE::Cluster::setup_rootsshconfig();
754
755 PVE::Cluster::gen_pve_vzdump_symlink();
756
757 if (!PVE::Cluster::check_cfs_quorum(1)) {
758 return undef if $param->{silent};
759 die "no quorum - unable to update files\n";
760 }
761
762 PVE::Cluster::setup_ssh_keys();
763
764 my $nodename = PVE::INotify::nodename();
765
766 my $local_ip_address = PVE::Cluster::remote_node_ip($nodename);
767
768 PVE::Cluster::gen_pve_node_files($nodename, $local_ip_address, $param->{force});
769 PVE::Cluster::ssh_merge_keys();
770 PVE::Cluster::ssh_merge_known_hosts($nodename, $local_ip_address);
771 PVE::Cluster::gen_pve_vzdump_files();
772
773 return undef;
774 }});
775
3a966e22
TL
776__PACKAGE__->register_method ({
777 name => 'mtunnel',
778 path => 'mtunnel',
779 method => 'POST',
780 description => "Used by VM/CT migration - do not use manually.",
781 parameters => {
782 additionalProperties => 0,
f83d8153
TL
783 properties => {
784 get_migration_ip => {
785 type => 'boolean',
786 default => 0,
787 description => 'return the migration IP, if configured',
788 optional => 1,
789 },
790 migration_network => {
791 type => 'string',
792 format => 'CIDR',
793 description => 'the migration network used to detect the local migration IP',
794 optional => 1,
795 },
796 },
3a966e22
TL
797 },
798 returns => { type => 'null'},
799 code => sub {
800 my ($param) = @_;
801
802 if (!PVE::Cluster::check_cfs_quorum(1)) {
803 print "no quorum\n";
804 return undef;
805 }
806
f83d8153
TL
807 if ($param->{get_migration_ip}) {
808 my $network = $param->{migration_network};
43f4c5f6 809 if (my $ip = PVE::Cluster::get_local_migration_ip($network)) {
f83d8153
TL
810 print "ip: '$ip'\n";
811 } else {
43f4c5f6 812 print "no ip\n";
f83d8153
TL
813 }
814 # do not keep tunnel open when asked for migration ip
815 return undef;
816 }
817
3a966e22
TL
818 print "tunnel online\n";
819 *STDOUT->flush();
820
821 while (my $line = <>) {
822 chomp $line;
823 last if $line =~ m/^quit$/;
824 }
825
826 return undef;
827 }});
828
13d44dc5
DM
829
830our $cmddef = {
831 keygen => [ __PACKAGE__, 'keygen', ['filename']],
832 create => [ __PACKAGE__, 'create', ['clustername']],
833 add => [ __PACKAGE__, 'add', ['hostname']],
834 addnode => [ __PACKAGE__, 'addnode', ['node']],
835 delnode => [ __PACKAGE__, 'delnode', ['node']],
836 status => [ __PACKAGE__, 'status' ],
837 nodes => [ __PACKAGE__, 'nodes' ],
838 expected => [ __PACKAGE__, 'expected', ['expected']],
839 updatecerts => [ __PACKAGE__, 'updatecerts', []],
3a966e22 840 mtunnel => [ __PACKAGE__, 'mtunnel', []],
13d44dc5
DM
841};
842
8431;