]> git.proxmox.com Git - pve-cluster.git/blobdiff - data/PVE/CLI/pvecm.pm
Add cluster join API version check
[pve-cluster.git] / data / PVE / CLI / pvecm.pm
index 48c110bef77a03f0fb25d6ded41c31d5978b2092..f205b8d38c04d3750252569176088d4bbbb4e89c 100755 (executable)
@@ -109,8 +109,7 @@ __PACKAGE__->register_method ({
     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) {
@@ -127,7 +126,7 @@ __PACKAGE__->register_method ({
 
        my $model = "net";
        my $algorithm = 'ffsplit';
-       if (scalar($members) & 1) {
+       if (scalar(%{$members}) & 1) {
            if ($param->{force}) {
                $algorithm = 'lms';
            } else {
@@ -266,8 +265,7 @@ __PACKAGE__->register_method ({
     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) {
@@ -318,7 +316,7 @@ __PACKAGE__->register_method ({
     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."
@@ -335,8 +333,6 @@ __PACKAGE__->register_method ({
                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,
            }),
@@ -345,7 +341,7 @@ __PACKAGE__->register_method ({
                description => "Always use SSH to join, even if peer may do it over API.",
                optional => 1,
            },
-       },
+       }),
     },
     returns => { type => 'null' },
 
@@ -353,20 +349,12 @@ __PACKAGE__->register_method ({
        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;
@@ -386,6 +374,11 @@ __PACKAGE__->register_method ({
 
            # 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();
@@ -397,15 +390,39 @@ __PACKAGE__->register_method ({
            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);
@@ -478,10 +495,7 @@ __PACKAGE__->register_method ({
            printf "\n";
        }
 
-       my $cmd = ['corosync-quorumtool', '-siH'];
-
-       exec (@$cmd);
-
+       exec ('corosync-quorumtool', '-siH');
        exit (-1); # should not be reached
     }});
 
@@ -501,10 +515,7 @@ __PACKAGE__->register_method ({
 
        PVE::Corosync::check_conf_exists();
 
-       my $cmd = ['corosync-quorumtool', '-l'];
-
-       exec (@$cmd);
-
+       exec ('corosync-quorumtool', '-l');
        exit (-1); # should not be reached
     }});
 
@@ -530,12 +541,8 @@ __PACKAGE__->register_method ({
 
        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 ({
@@ -611,11 +618,36 @@ __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";
@@ -632,7 +664,7 @@ __PACKAGE__->register_method ({
            # 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 {
@@ -658,6 +690,10 @@ __PACKAGE__->register_method ({
 
 
 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']],