]> git.proxmox.com Git - pve-cluster.git/blobdiff - data/PVE/API2/ClusterConfig.pm
Add cluster join API version check
[pve-cluster.git] / data / PVE / API2 / ClusterConfig.pm
index 2c5bb0362ad5d6e3682a543d815353093649a6a4..205252fa06e52ffac39f6a901913aa1eed247202 100644 (file)
@@ -58,11 +58,33 @@ __PACKAGE__->register_method({
            { name => 'totem' },
            { name => 'join' },
            { name => 'qdevice' },
+           { name => 'apiversion' },
        ];
 
        return $result;
     }});
 
+__PACKAGE__->register_method ({
+    name => 'join_api_version',
+    path => 'apiversion',
+    method => 'GET',
+    description => "Return the version of the cluster join API available on this node.",
+    permissions => {
+       check => ['perm', '/', [ 'Sys.Audit' ]],
+    },
+    parameters => {
+       additionalProperties => 0,
+       properties => {},
+    },
+    returns => {
+       type => 'integer',
+       minimum => 0,
+       description => "Cluster Join API version, currently " . PVE::Cluster::Setup::JOIN_API_VERSION,
+    },
+    code => sub {
+       return PVE::Cluster::Setup::JOIN_API_VERSION;
+    }});
+
 __PACKAGE__->register_method ({
     name => 'create',
     path => '',
@@ -207,6 +229,17 @@ __PACKAGE__->register_method ({
                description => "Do not throw error if node already exists.",
                optional => 1,
            },
+           new_node_ip => {
+               type => 'string',
+               description => "IP Address of node to add. Used as fallback if no links are given.",
+               format => 'ip',
+               optional => 1,
+           },
+           apiversion => {
+               type => 'integer',
+               description => 'The JOIN_API_VERSION of the new node.',
+               optional => 1,
+           },
        }),
     },
     returns => {
@@ -229,6 +262,14 @@ __PACKAGE__->register_method ({
     code => sub {
        my ($param) = @_;
 
+       $param->{apiversion} //= 0;
+       if ($param->{apiversion} < (PVE::Cluster::Setup::JOIN_API_VERSION -
+           PVE::Cluster::Setup::JOIN_API_AGE_AS_CLUSTER)) {
+           die "unsupported old API version on joining node ($param->{apiversion},"
+               . " cluster node has " . PVE::Cluster::Setup::JOIN_API_VERSION
+               . "), please upgrade before joining\n";
+       }
+
        PVE::Cluster::check_cfs_quorum();
 
        my $vc_errors;
@@ -268,6 +309,59 @@ __PACKAGE__->register_method ({
                $check_duplicate_addr->($link);
            }
 
+           # Make sure that the newly added node has links compatible with the
+           # rest of the cluster. To start, extract all links that currently
+           # exist. Check any node, all have the same links here (because of
+           # verify_conf above).
+           my $node_options = (values %$nodelist)[0];
+           my $cluster_links = {};
+           foreach my $opt (keys %$node_options) {
+               my ($linktype, $linkid) = PVE::Corosync::parse_link_entry($opt);
+               next if !defined($linktype);
+               $cluster_links->{$linkid} = $node_options->{$opt};
+           }
+
+           # in case no fallback IP was passed, but instead only a single link,
+           # treat it as fallback to allow link-IDs to be matched automatically
+           # FIXME: remove in 8.0 or when joining an old node not supporting
+           # new_node_ip becomes infeasible otherwise
+           my $legacy_fallback = 0;
+           if (!$param->{new_node_ip} && scalar(%$links) == 1 && $param->{apiversion} == 0) {
+               my $passed_link_id = (keys %$links)[0];
+               my $passed_link = delete $links->{$passed_link_id};
+               $param->{new_node_ip} = $passed_link->{address};
+               $legacy_fallback = 1;
+           }
+
+           if (scalar(%$links)) {
+               # verify specified links exist and none are missing
+               for my $linknum (0..PVE::Corosync::MAX_LINK_INDEX) {
+                   my $have_cluster_link = defined($cluster_links->{$linknum});
+                   my $have_new_link = defined($links->{$linknum});
+
+                   die "corosync: link $linknum exists in cluster config but wasn't specified for new node\n"
+                       if $have_cluster_link && !$have_new_link;
+                   die "corosync: link $linknum specified for new node doesn't exist in cluster config\n"
+                       if !$have_cluster_link && $have_new_link;
+               }
+           } else {
+               # when called without any link parameters, fall back to
+               # new_node_ip, if all existing nodes only have a single link too
+               die "no links and no fallback ip (new_node_ip) given, cannot join cluster\n"
+                   if !$param->{new_node_ip};
+
+               my $cluster_link_count = scalar(%$cluster_links);
+               if ($cluster_link_count == 1) {
+                   my $linknum = (keys %$cluster_links)[0];
+                   $links->{$linknum} = { address => $param->{new_node_ip} };
+               } else {
+                   die "cluster has $cluster_link_count links, but only 1 given"
+                       if $legacy_fallback;
+                   die "no links given but multiple links found on other nodes,"
+                     . " fallback only supported on single-link clusters\n";
+               }
+           }
+
            if (defined(my $res = $nodelist->{$name})) {
                $param->{nodeid} = $res->{nodeid} if !$param->{nodeid};
                $param->{votes} = $res->{quorum_votes} if !defined($param->{votes});
@@ -494,7 +588,9 @@ __PACKAGE__->register_method ({
     path => 'join',
     method => 'POST',
     protected => 1,
-    description => "Joins this node into an existing cluster.",
+    description => "Joins this node into an existing cluster. If no links are"
+                . " given, default to IP resolved by node's hostname on single"
+                . " link (fallback fails for clusters with multiple links).",
     parameters => {
        additionalProperties => 0,
        properties => PVE::Corosync::add_corosync_link_properties({