code => sub {
my ($param) = @_;
- die "Node not in a cluster. Aborting.\n"
- if !PVE::Corosync::check_conf_exists(1);
+ PVE::Corosync::check_conf_exists();
my $members = PVE::Cluster::get_members();
foreach my $node (sort keys %$members) {
my $model = "net";
my $algorithm = 'ffsplit';
- if (scalar($members) & 1) {
+ if (scalar(%{$members}) & 1) {
if ($param->{force}) {
$algorithm = 'lms';
} else {
code => sub {
my ($param) = @_;
- die "Node not in a cluster. Aborting.\n"
- if !PVE::Corosync::check_conf_exists(1);
+ PVE::Corosync::check_conf_exists();
my $members = PVE::Cluster::get_members();
foreach my $node (sort keys %$members) {
description => "Adds the current node to an existing cluster.",
parameters => {
additionalProperties => 0,
- properties => {
+ properties => PVE::Corosync::add_corosync_link_properties({
hostname => {
type => 'string',
description => "Hostname (or IP) of an existing cluster member."
description => "Do not throw error if node already exists.",
optional => 1,
},
- link0 => get_standard_option('corosync-link'),
- link1 => get_standard_option('corosync-link'),
fingerprint => get_standard_option('fingerprint-sha256', {
optional => 1,
}),
description => "Always use SSH to join, even if peer may do it over API.",
optional => 1,
},
- },
+ }),
},
returns => { type => 'null' },
my ($param) = @_;
my $nodename = PVE::INotify::nodename();
-
my $host = $param->{hostname};
- my $local_ip_address = PVE::Cluster::remote_node_ip($nodename);
-
- my $link0 = PVE::Cluster::parse_corosync_link($param->{link0});
- my $link1 = PVE::Cluster::parse_corosync_link($param->{link1});
-
- PVE::Cluster::Setup::assert_joinable($local_ip_address, $link0, $link1, $param->{force});
my $worker = sub {
if (!$param->{use_ssh}) {
- print "Please enter superuser (root) password for '$host':\n";
- my $password = PVE::PTY::read_password("Password for root\@$host: ");
+ my $password = PVE::PTY::read_password("Please enter superuser (root) password for '$host': ");
delete $param->{use_ssh};
$param->{password} = $password;
# allow fallback to old ssh only join if wished or needed
+ my $local_ip_address = PVE::Cluster::remote_node_ip($nodename);
+ my $links = PVE::Corosync::extract_corosync_link_args($param);
+
+ PVE::Cluster::Setup::assert_joinable($local_ip_address, $links, $param->{force});
+
PVE::Cluster::Setup::setup_sshd_config();
PVE::Cluster::Setup::setup_rootsshconfig();
PVE::Cluster::Setup::setup_ssh_keys();
run_command($cmd, 'outfunc' => sub {}, 'errfunc' => sub {},
'errmsg' => "unable to copy ssh ID");
+ $cmd = ['ssh', $host, '-o', 'BatchMode=yes', 'pvecm', 'apiver'];
+ my $remote_apiver = 0;
+ run_command($cmd, 'outfunc' => sub {
+ $remote_apiver = shift;
+ chomp $remote_apiver;
+ }, 'noerr' => 1);
+
+ if ($remote_apiver < (PVE::Cluster::Setup::JOIN_API_VERSION -
+ PVE::Cluster::Setup::JOIN_API_AGE_AS_JOINEE)) {
+ die "error: incompatible join API version on cluster ($remote_apiver,"
+ . " local has " . PVE::Cluster::Setup::JOIN_API_VERSION . "). Make"
+ . " sure all nodes are up-to-date.\n";
+ }
+
$cmd = ['ssh', $host, '-o', 'BatchMode=yes',
'pvecm', 'addnode', $nodename, '--force', 1];
push @$cmd, '--nodeid', $param->{nodeid} if $param->{nodeid};
push @$cmd, '--votes', $param->{votes} if defined($param->{votes});
- # just pass the un-parsed string through, or as we've address as
- # the default_key, we can just pass the fallback directly too
- push @$cmd, '--link0', $param->{link0} // $local_ip_address;
- push @$cmd, '--link1', $param->{link1} if defined($param->{link1});
+
+ foreach my $link (keys %$links) {
+ push @$cmd, "--link$link", PVE::JSONSchema::print_property_string(
+ $links->{$link}, get_standard_option('corosync-link'));
+ }
+
+ # this will be used as fallback if no links are specified
+ if (!%$links) {
+ push @$cmd, '--link0', $local_ip_address if $remote_apiver == 0;
+ push @$cmd, '--new_node_ip', $local_ip_address if $remote_apiver >= 1;
+
+ print "No cluster network links passed explicitly, fallback to local node"
+ . " IP '$local_ip_address'\n";
+ }
if (system (@$cmd) != 0) {
my $cmdtxt = join (' ', @$cmd);
printf "\n";
}
- my $cmd = ['corosync-quorumtool', '-siH'];
-
- exec (@$cmd);
-
+ exec ('corosync-quorumtool', '-siH');
exit (-1); # should not be reached
}});
PVE::Corosync::check_conf_exists();
- my $cmd = ['corosync-quorumtool', '-l'];
-
- exec (@$cmd);
-
+ exec ('corosync-quorumtool', '-l');
exit (-1); # should not be reached
}});
PVE::Corosync::check_conf_exists();
- my $cmd = ['corosync-quorumtool', '-e', $param->{expected}];
-
- exec (@$cmd);
-
+ exec ('corosync-quorumtool', '-e', $param->{expected});
exit (-1); # should not be reached
-
}});
__PACKAGE__->register_method ({
return undef;
}
+ my $get_local_migration_ip = sub {
+ my ($cidr) = @_;
+
+ if (!defined($cidr)) {
+ my $dc_conf = cfs_read_file('datacenter.cfg');
+ $cidr = $dc_conf->{migration}->{network}
+ if defined($dc_conf->{migration}->{network});
+ }
+
+ if (defined($cidr)) {
+ my $ips = PVE::Network::get_local_ip_from_cidr($cidr);
+
+ die "could not get migration ip: no IP address configured on local " .
+ "node for network '$cidr'\n" if scalar(@$ips) == 0;
+
+ die "could not get migration ip: multiple, different, IP address configured for " .
+ "network '$cidr'\n" if scalar(@$ips) > 1 && grep { @$ips[0] ne $_ } @$ips;
+
+ return @$ips[0];
+ }
+
+ return undef;
+ };
+
my $network = $param->{migration_network};
if ($param->{get_migration_ip}) {
die "cannot use --run-command with --get_migration_ip\n"
if $param->{'run-command'};
- if (my $ip = PVE::Cluster::get_local_migration_ip($network)) {
+
+ if (my $ip = $get_local_migration_ip->($network)) {
print "ip: '$ip'\n";
} else {
print "no ip\n";
# Get an ip address to listen on, and find a free migration port
my ($ip, $family);
if (defined($network)) {
- $ip = PVE::Cluster::get_local_migration_ip($network)
+ $ip = $get_local_migration_ip->($network)
or die "failed to get migration IP address to listen on\n";
$family = PVE::Tools::get_host_address_family($ip);
} else {
our $cmddef = {
+ apiver => [ 'PVE::API2::ClusterConfig', 'join_api_version', [], {}, sub {
+ my $apiver = shift;
+ print "$apiver\n";
+ }],
keygen => [ __PACKAGE__, 'keygen', ['filename']],
create => [ 'PVE::API2::ClusterConfig', 'create', ['clustername']],
add => [ __PACKAGE__, 'add', ['hostname']],