]> git.proxmox.com Git - pve-cluster.git/commitdiff
Add verification and fallback to cluster join/addnode
authorStefan Reiter <s.reiter@proxmox.com>
Thu, 9 Jan 2020 15:31:35 +0000 (16:31 +0100)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Wed, 5 Feb 2020 09:38:28 +0000 (10:38 +0100)
Verify that the config of the new node is valid and compatible with the
cluster (i.e. that the links for the new node match the currently
configured nodes).

Additionally, fallback is provided via a new parameter to addnode,
'new_node_ip'. Previously, fallback was handled on the joining node, by
setting it's local IP as 'link0', however, a cluster with only one link,
but numbered 1-7 is still valid, and a fallback is possible, but the old
code would now fail.

Instead, pass the locally resolved IP via a seperate parameter
(resolving the IP on the cluster side is impractical, as IP resolution
could fail or provide a wrong IP for Corosync).

For compatibility reasons, allow fallback to occur via the old
method as well, but mark with FIXME for future removal.

Fallback fails in case the cluster has more than one link, in this case
only the user can know which NIC/IP corresponds to which cluster link.

Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
data/PVE/API2/ClusterConfig.pm
data/PVE/CLI/pvecm.pm
data/PVE/Cluster/Setup.pm

index 2c5bb0362ad5d6e3682a543d815353093649a6a4..e6b59cf6db91c7b67147f6f37e1c5765f10a03d1 100644 (file)
@@ -207,6 +207,12 @@ __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,
+           },
        }),
     },
     returns => {
@@ -268,6 +274,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) {
+               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 +553,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({
index df0b1b9ea3a788937db4a5b9a8dc4b2921417ce5..a838ff2c620a53f8199521689c5dbd3ee0b44d29 100755 (executable)
@@ -401,6 +401,13 @@ __PACKAGE__->register_method ({
                    $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;
+               print "No cluster network links passed explicitly, fallback to local node"
+                   . " IP '$local_ip_address'\n";
+           }
+
            if (system (@$cmd) != 0) {
                my $cmdtxt = join (' ', @$cmd);
                die "unable to add node: command failed ($cmdtxt)\n";
index 88e168095767354d979260dc4d80eb52211ad54f..27e240375f3236243c8d7b2d26bc6f1e241fdfc4 100644 (file)
@@ -684,6 +684,13 @@ sub join {
        $args->{"link$link"} = PVE::Corosync::print_corosync_link($links->{$link});
     }
 
+    # this will be used as fallback if no links are specified
+    if (!%$links) {
+       $args->{link0} = $local_ip_address;
+       print "No cluster network links passed explicitly, fallback to local node"
+           . " IP '$local_ip_address'\n";
+    }
+
     print "Request addition of this node\n";
     my $res = eval { $conn->post("/cluster/config/nodes/$nodename", $args); };
     if (my $err = $@) {