]>
git.proxmox.com Git - pve-cluster.git/blob - data/PVE/pvecm
2cd13e5bfddc1d8d7579cd72bb4fe0d55c556e74
11 use Data
::Dumper
; # fixme: remove
18 use base
qw(PVE::CLIHandler);
20 $ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';
22 $ENV{HOME
} = '/root'; # for ssh-copy-id
24 my ($local_ip_address, $nodename);
26 if (defined($ARGV[0]) && $ARGV[0] ne 'printmanpod' && $ARGV[0] ne 'verifyapi') {
27 die "please run as root\n" if $> != 0;
29 $nodename = PVE
::INotify
::nodename
();
30 # trigger check that we have resolvable name
31 $local_ip_address = PVE
::Cluster
::remote_node_ip
($nodename);
34 my $basedir = "/etc/pve";
35 my $clusterconf = "$basedir/corosync.conf";
36 my $libdir = "/var/lib/pve-cluster";
37 my $backupdir = "/var/lib/pve-cluster/backup";
38 my $dbfile = "$libdir/config.db";
39 my $authfile = "/etc/corosync/authkey";
44 print "backup old database\n";
49 my $cmd = "echo '.dump' |";
50 $cmd .= "sqlite3 '$dbfile' |";
51 $cmd .= "gzip - >'${backupdir}/config-${ctime}.sql.gz'";
54 die "can't backup old database: $!\n";
60 foreach my $fn (<$backupdir/config-*.sql
.gz
>) {
61 if ($fn =~ m!/config-(\d+)\.sql.gz$!) {
62 push @bklist, [$fn, $1];
66 @bklist = sort { $b->[1] <=> $a->[1] } @bklist;
68 while (scalar (@bklist) >= $maxfiles) {
70 print "delete old backup '$d->[0]'\n";
75 __PACKAGE__-
>register_method ({
79 description
=> "Generate new cryptographic key for corosync.",
81 additionalProperties
=> 0,
85 description
=> "Output file name"
89 returns
=> { type
=> 'null' },
94 my $filename = $param->{filename
};
97 $> == 0 || die "Error: Authorization key must be generated as root user.\n";
98 my $dirname = dirname
($filename);
99 my $basename = basename
($filename);
101 die "key file '$filename' already exists\n" if -e
$filename;
103 File
::Path
::make_path
($dirname) if $dirname;
105 my $cmd = ['corosync-keygen', '-l', '-k', $filename];
106 PVE
::Tools
::run_command
($cmd);
111 __PACKAGE__-
>register_method ({
115 description
=> "Generate new cluster configuration.",
117 additionalProperties
=> 0,
120 description
=> "The name of the cluster.",
121 type
=> 'string', format
=> 'pve-node',
126 description
=> "Node id for this node.",
132 description
=> "Number of votes for this node.",
138 returns
=> { type
=> 'null' },
143 -f
$clusterconf && die "cluster config '$clusterconf' already exists\n";
145 PVE
::Cluster
::setup_sshd_config
();
146 PVE
::Cluster
::setup_rootsshconfig
();
147 PVE
::Cluster
::setup_ssh_keys
();
149 -f
$authfile || __PACKAGE__-
>keygen({filename
=> $authfile});
151 -f
$authfile || die "no authentication key available\n";
153 my $clustername = $param->{clustername
};
155 $param->{nodeid
} = 1 if !$param->{nodeid
};
157 $param->{votes
} = 1 if !defined($param->{votes
});
159 # No, corosync cannot deduce this on its own
160 my $ipversion = Net
::IP
::ip_is_ipv6
($local_ip_address) ?
'ipv6' : 'ipv4';
166 cluster_name: $clustername
168 ip_version: $ipversion
171 bindnetaddr: $local_ip_address
177 ring0_addr: $nodename
178 nodeid: $param->{nodeid}
179 quorum_votes: $param->{votes}
184 provider: corosync_votequorum
193 PVE
::Tools
::file_set_contents
($clusterconf, $config);
195 PVE
::Cluster
::ssh_merge_keys
();
197 PVE
::Cluster
::gen_pve_node_files
($nodename, $local_ip_address);
199 PVE
::Cluster
::ssh_merge_known_hosts
($nodename, $local_ip_address, 1);
201 PVE
::Tools
::run_command
('systemctl restart pve-cluster'); # restart
203 PVE
::Tools
::run_command
('systemctl restart corosync'); # restart
208 __PACKAGE__-
>register_method ({
212 description
=> "Adds a node to the cluster configuration.",
214 additionalProperties
=> 0,
216 node
=> PVE
::JSONSchema
::get_standard_option
('pve-node'),
219 description
=> "Node id for this node.",
225 description
=> "Number of votes for this node",
231 description
=> "Do not throw error if node already exists.",
236 returns
=> { type
=> 'null' },
241 PVE
::Cluster
::check_cfs_quorum
();
243 my $conf = PVE
::Cluster
::cfs_read_file
("corosync.conf");
245 my $nodelist = corosync_nodelist
($conf);
247 my $name = $param->{node
};
249 if (defined(my $res = $nodelist->{$name})) {
250 $param->{nodeid
} = $res->{nodeid
} if !$param->{nodeid
};
251 $param->{votes
} = $res->{quorum_votes
} if !defined($param->{votes
});
253 if ($res->{quorum_votes
} == $param->{votes
} &&
254 $res->{nodeid
} == $param->{nodeid
}) {
255 print "node $name already defined\n";
256 if ($param->{force
}) {
262 die "can't add existing node\n";
264 } elsif (!$param->{nodeid
}) {
269 foreach my $v (values %$nodelist) {
270 if ($v->{nodeid
} eq $nodeid) {
279 $param->{nodeid
} = $nodeid;
282 $param->{votes
} = 1 if !defined($param->{votes
});
284 PVE
::Cluster
::gen_local_dirs
($name);
286 eval { PVE
::Cluster
::ssh_merge_keys
(); };
289 $nodelist->{$name} = { ring0_addr
=> $name, nodeid
=> $param->{nodeid
} };
290 $nodelist->{$name}->{quorum_votes
} = $param->{votes
} if $param->{votes
};
292 corosync_update_nodelist
($conf, $nodelist);
298 __PACKAGE__-
>register_method ({
302 description
=> "Removes a node to the cluster configuration.",
304 additionalProperties
=> 0,
306 node
=> PVE
::JSONSchema
::get_standard_option
('pve-node'),
309 returns
=> { type
=> 'null' },
314 PVE
::Cluster
::check_cfs_quorum
();
316 my $conf = PVE
::Cluster
::cfs_read_file
("corosync.conf");
318 my $nodelist = corosync_nodelist
($conf);
320 my $nd = delete $nodelist->{$param->{node
}};
321 die "no such node '$param->{node}'\n" if !$nd;
323 corosync_update_nodelist
($conf, $nodelist);
328 __PACKAGE__-
>register_method ({
332 description
=> "Adds the current node to an existing cluster.",
334 additionalProperties
=> 0,
338 description
=> "Hostname (or IP) of an existing cluster member."
342 description
=> "Node id for this node.",
348 description
=> "Number of votes for this node",
354 description
=> "Do not throw error if node already exists.",
359 returns
=> { type
=> 'null' },
364 PVE
::Cluster
::setup_sshd_config
();
365 PVE
::Cluster
::setup_rootsshconfig
();
366 PVE
::Cluster
::setup_ssh_keys
();
368 my $host = $param->{hostname
};
370 if (!$param->{force
}) {
373 die "authentication key already exists\n";
376 if (-f
$clusterconf) {
377 die "cluster config '$clusterconf' already exists\n";
380 my $vmlist = PVE
::Cluster
::get_vmlist
();
381 if ($vmlist && $vmlist->{ids
} && scalar(keys %{$vmlist->{ids
}})) {
382 die "this host already contains virtual machines - please remove them first\n";
385 if (system("corosync-quorumtool >/dev/null 2>&1") == 0) {
386 die "corosync is already running\n";
390 # make sure known_hosts is on local filesystem
391 PVE
::Cluster
::ssh_unmerge_known_hosts
();
393 my $cmd = "ssh-copy-id -i /root/.ssh/id_rsa 'root\@$host' >/dev/null 2>&1";
394 system ($cmd) == 0 ||
395 die "unable to copy ssh ID\n";
397 $cmd = ['ssh', $host, '-o', 'BatchMode=yes',
398 'pvecm', 'addnode', $nodename, '--force', 1];
400 push @$cmd, '--nodeid', $param->{nodeid
} if $param->{nodeid
};
402 push @$cmd, '--votes', $param->{votes
} if defined($param->{votes
});
404 if (system (@$cmd) != 0) {
405 my $cmdtxt = join (' ', @$cmd);
406 die "unable to add node: command failed ($cmdtxt)\n";
409 my $tmpdir = "$libdir/.pvecm_add.tmp.$$";
413 print "copy corosync auth key\n";
414 $cmd = ['rsync', '--rsh=ssh -l root -o BatchMode=yes', '-lpgoq',
415 "[$host]:$authfile $clusterconf", $tmpdir];
417 system(@$cmd) == 0 || die "can't rsync data from host '$host'\n";
419 mkdir "/etc/corosync";
420 my $confbase = basename
($clusterconf);
422 $cmd = "cp '$tmpdir/$confbase' '/etc/corosync/$confbase'";
423 system($cmd) == 0 || die "can't copy cluster configuration\n";
425 my $keybase = basename
($authfile);
426 system ("cp '$tmpdir/$keybase' '$authfile'") == 0 ||
427 die "can't copy '$tmpdir/$keybase' to '$authfile'\n";
429 print "stopping pve-cluster service\n";
431 system("umount $basedir -f >/dev/null 2>&1");
432 system("systemctl stop pve-cluster") == 0 ||
433 die "can't stop pve-cluster service\n";
439 system("systemctl start pve-cluster") == 0 ||
440 die "starting pve-cluster failed\n";
442 system("systemctl start corosync");
446 while (!PVE
::Cluster
::check_cfs_quorum
(1)) {
448 print "waiting for quorum...";
454 print "OK\n" if !$printqmsg;
456 # system("systemctl start clvm");
458 print "generating node certificates\n";
459 PVE
::Cluster
::gen_pve_node_files
($nodename, $local_ip_address);
461 print "merge known_hosts file\n";
462 PVE
::Cluster
::ssh_merge_known_hosts
($nodename, $local_ip_address, 1);
464 print "restart services\n";
465 # restart pvedaemon (changed certs)
466 system("systemctl restart pvedaemon");
467 # restart pveproxy (changed certs)
468 system("systemctl restart pveproxy");
470 print "successfully added node '$nodename' to cluster.\n";
481 __PACKAGE__-
>register_method ({
485 description
=> "Displays the local view of the cluster status.",
487 additionalProperties
=> 0,
490 returns
=> { type
=> 'null' },
495 my $cmd = ['corosync-quorumtool', '-siH'];
499 exit (-1); # should not be reached
502 __PACKAGE__-
>register_method ({
506 description
=> "Displays the local view of the cluster nodes.",
508 additionalProperties
=> 0,
511 returns
=> { type
=> 'null' },
516 my $cmd = ['corosync-quorumtool', '-l'];
520 exit (-1); # should not be reached
523 __PACKAGE__-
>register_method ({
527 description
=> "Tells corosync a new value of expected votes.",
529 additionalProperties
=> 0,
533 description
=> "Expected votes",
538 returns
=> { type
=> 'null' },
543 my $cmd = ['corosync-quorumtool', '-e', $param->{expected
}];
547 exit (-1); # should not be reached
551 sub corosync_update_nodelist
{
552 my ($conf, $nodelist) = @_;
554 delete $conf->{digest
};
556 my $version = PVE
::Cluster
::corosync_conf_version
($conf);
557 PVE
::Cluster
::corosync_conf_version
($conf, undef, $version + 1);
560 foreach my $v (values %$nodelist) {
561 next if !$v->{ring0_addr
};
563 foreach my $k (keys %$v) {
564 push @$kv, { key
=> $k, value
=> $v->{$k} };
566 my $ns = { section
=> 'node', children
=> $kv };
567 push @$children, $ns;
570 foreach my $main (@{$conf->{children
}}) {
571 next if !defined($main->{section
});
572 if ($main->{section
} eq 'nodelist') {
573 $main->{children
} = $children;
579 PVE
::Cluster
::cfs_write_file
("corosync.conf.new", $conf);
581 rename("/etc/pve/corosync.conf.new", "/etc/pve/corosync.conf")
582 || die "activate corosync.conf.new failed - $!\n";
585 sub corosync_nodelist
{
592 foreach my $main (@{$conf->{children
}}) {
593 next if !defined($main->{section
});
594 if ($main->{section
} eq 'nodelist') {
595 foreach my $ne (@{$main->{children
}}) {
596 next if !defined($ne->{section
}) || ($ne->{section
} ne 'node');
597 my $node = { quorum_votes
=> 1 };
598 foreach my $child (@{$ne->{children
}}) {
599 next if !defined($child->{key
});
600 $node->{$child->{key
}} = $child->{value
};
601 if ($child->{key
} eq 'ring0_addr') {
602 $nodelist->{$child->{value
}} = $node;
612 __PACKAGE__-
>register_method ({
613 name
=> 'updatecerts',
614 path
=> 'updatecerts',
616 description
=> "Update node certificates (and generate all needed files/directories).",
618 additionalProperties
=> 0,
621 description
=> "Force generation of new SSL certifate.",
626 description
=> "Ignore errors (i.e. when cluster has no quorum).",
632 returns
=> { type
=> 'null' },
636 PVE
::Cluster
::setup_rootsshconfig
();
638 PVE
::Cluster
::gen_pve_vzdump_symlink
();
640 if (!PVE
::Cluster
::check_cfs_quorum
(1)) {
641 return undef if $param->{silent
};
642 die "no quorum - unable to update files\n";
645 PVE
::Cluster
::setup_ssh_keys
();
647 PVE
::Cluster
::gen_pve_node_files
($nodename, $local_ip_address, $param->{force
});
648 PVE
::Cluster
::ssh_merge_keys
();
649 PVE
::Cluster
::ssh_merge_known_hosts
($nodename, $local_ip_address);
650 PVE
::Cluster
::gen_pve_vzdump_files
();
657 keygen
=> [ __PACKAGE__
, 'keygen', ['filename']],
658 create
=> [ __PACKAGE__
, 'create', ['clustername']],
659 add
=> [ __PACKAGE__
, 'add', ['hostname']],
660 addnode
=> [ __PACKAGE__
, 'addnode', ['node']],
661 delnode
=> [ __PACKAGE__
, 'delnode', ['node']],
662 status
=> [ __PACKAGE__
, 'status' ],
663 nodes
=> [ __PACKAGE__
, 'nodes' ],
664 expected
=> [ __PACKAGE__
, 'expected', ['expected']],
665 updatecerts
=> [ __PACKAGE__
, 'updatecerts', []],
670 if ($cmd && $cmd ne 'printmanpod' && $cmd ne 'verifyapi') {
671 PVE
::Cluster
::check_cfs_is_mounted
();
672 PVE
::Cluster
::cfs_update
();
675 PVE
::CLIHandler
::handle_cmd
($cmddef, "pvecm", $cmd, \
@ARGV, undef, $0);
683 pvecm - Proxmox VE cluster manager toolkit
691 pvecm is a program to manage the cluster configuration. It can be used
692 to create a new cluster, join nodes to a cluster, leave the cluster,
693 get status information and do various other cluster related tasks.
695 =include pve_copyright