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.rei...@proxmox.com> --- data/PVE/API2/ClusterConfig.pm | 63 +++++++++++++++++++++++++++++++++- data/PVE/CLI/pvecm.pm | 7 ++++ data/PVE/Cluster/Setup.pm | 7 ++++ 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/data/PVE/API2/ClusterConfig.pm b/data/PVE/API2/ClusterConfig.pm index 2c5bb03..e6b59cf 100644 --- a/data/PVE/API2/ClusterConfig.pm +++ b/data/PVE/API2/ClusterConfig.pm @@ -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({ diff --git a/data/PVE/CLI/pvecm.pm b/data/PVE/CLI/pvecm.pm index df0b1b9..a838ff2 100755 --- a/data/PVE/CLI/pvecm.pm +++ b/data/PVE/CLI/pvecm.pm @@ -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"; diff --git a/data/PVE/Cluster/Setup.pm b/data/PVE/Cluster/Setup.pm index 88e1680..27e2403 100644 --- a/data/PVE/Cluster/Setup.pm +++ b/data/PVE/Cluster/Setup.pm @@ -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 = $@) { -- 2.20.1 _______________________________________________ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel