From 6ed49eb1c5a6f4d8a1b01eefd8fe09197a6402b2 Mon Sep 17 00:00:00 2001 From: Thomas Lamprecht Date: Mon, 27 Nov 2017 10:55:14 +0100 Subject: [PATCH] api/cluster: add join endpoint Add an endpoint to the API which allows to join an existing PVE cluster by only using the API instead of CLI tools (pvecm). Use a worker as this operation may need longer than 30 seconds. With the worker we also get a task log entry/window for an UI for free, allowing to give better feedback. The join helper will be reused by the CLI handler in a later patch. It is based on its behaviour, but swapped out the ssh parts with API calls. Signed-off-by: Thomas Lamprecht --- data/PVE/API2/ClusterConfig.pm | 70 +++++++++++++++++++++++++++++++++- data/PVE/Cluster.pm | 58 ++++++++++++++++++++++++++++ debian/control.in | 4 +- 3 files changed, 129 insertions(+), 3 deletions(-) diff --git a/data/PVE/API2/ClusterConfig.pm b/data/PVE/API2/ClusterConfig.pm index 5bdf970..6b2f6c1 100644 --- a/data/PVE/API2/ClusterConfig.pm +++ b/data/PVE/API2/ClusterConfig.pm @@ -9,6 +9,7 @@ use PVE::RESTHandler; use PVE::RPCEnvironment; use PVE::JSONSchema qw(get_standard_option); use PVE::Cluster; +use PVE::APIClient::LWP; use PVE::Corosync; use base qw(PVE::RESTHandler); @@ -39,7 +40,8 @@ __PACKAGE__->register_method({ my $result = [ { name => 'nodes' }, { name => 'totem' }, - ]; + { name => 'join' }, + ]; return $result; }}); @@ -94,7 +96,6 @@ my $config_change_lock = sub { }); }; - __PACKAGE__->register_method ({ name => 'addnode', path => 'nodes/{node}', @@ -305,6 +306,71 @@ __PACKAGE__->register_method ({ return undef; }}); +__PACKAGE__->register_method ({ + name => 'join', + path => 'join', + method => 'POST', + protected => 1, + description => "Joins this node into an existing cluster.", + parameters => { + additionalProperties => 0, + properties => { + hostname => { + type => 'string', + description => "Hostname (or IP) of an existing cluster member." + }, + nodeid => { + type => 'integer', + description => "Node id for this node.", + minimum => 1, + optional => 1, + }, + votes => { + type => 'integer', + description => "Number of votes for this node", + minimum => 0, + optional => 1, + }, + force => { + type => 'boolean', + description => "Do not throw error if node already exists.", + optional => 1, + }, + ring0_addr => { + type => 'string', format => 'address', + description => "Hostname (or IP) of the corosync ring0 address of this node.". + " Defaults to nodes hostname.", + optional => 1, + }, + ring1_addr => { + type => 'string', format => 'address', + description => "Hostname (or IP) of the corosync ring1 address, this". + " needs an valid configured ring 1 interface in the cluster.", + optional => 1, + }, + fingerprint => get_standard_option('fingerprint-sha256'), + password => { + description => "Superuser (root) password of peer node.", + type => 'string', + maxLength => 128, + }, + }, + }, + returns => { type => 'string' }, + code => sub { + my ($param) = @_; + + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + my $worker = sub { + PVE::Cluster::join($param); + }; + + return $rpcenv->fork_worker('clusterjoin', '', $authuser, $worker); + }}); + + __PACKAGE__->register_method({ name => 'totem', path => 'totem', diff --git a/data/PVE/Cluster.pm b/data/PVE/Cluster.pm index c3677f4..c515fa9 100644 --- a/data/PVE/Cluster.pm +++ b/data/PVE/Cluster.pm @@ -1775,6 +1775,64 @@ my $backup_cfs_database = sub { } }; +sub join { + my ($param) = @_; + + my $nodename = PVE::INotify::nodename(); + + setup_sshd_config(); + setup_rootsshconfig(); + setup_ssh_keys(); + + # check if we can join with the given parameters and current node state + my ($ring0_addr, $ring1_addr) = $param->@{'ring0_addr', 'ring1_addr'}; + assert_joinable($ring0_addr, $ring1_addr, $param->{force}); + + # make sure known_hosts is on local filesystem + ssh_unmerge_known_hosts(); + + my $host = $param->{hostname}; + + my $conn_args = { + username => 'root@pam', + password => $param->{password}, + cookie_name => 'PVEAuthCookie', + protocol => 'https', + host => $host, + port => 8006, + }; + + if (my $fp = $param->{fingerprint}) { + $conn_args->{cached_fingerprints} = { uc($fp) => 1 }; + } else { + # API schema ensures that we can only get here from CLI handler + $conn_args->{manual_verification} = 1; + } + + print "Etablishing API connection with host '$host'\n"; + + my $conn = PVE::APIClient::LWP->new(%$conn_args); + $conn->login(); + + # login raises an exception on failure, so if we get here we're good + print "Login succeeded.\n"; + + my $args = {}; + $args->{force} = $param->{force} if defined($param->{force}); + $args->{nodeid} = $param->{nodeid} if $param->{nodeid}; + $args->{votes} = $param->{votes} if defined($param->{votes}); + $args->{ring0_addr} = $ring0_addr if defined($ring0_addr); + $args->{ring1_addr} = $ring1_addr if defined($ring1_addr); + + print "Request addition of this node\n"; + my $res = $conn->post("/cluster/config/nodes/$nodename", $args); + + print "Join request OK, finishing setup locally\n"; + + # added successfuly - now prepare local node + finish_join($nodename, $res->{corosync_conf}, $res->{corosync_authkey}); +} + sub finish_join { my ($nodename, $corosync_conf, $corosync_authkey) = @_; diff --git a/debian/control.in b/debian/control.in index e6ccc90..1699be6 100644 --- a/debian/control.in +++ b/debian/control.in @@ -21,6 +21,7 @@ Build-Depends: debhelper (>= 7), libdigest-hmac-perl, dh-systemd, pve-doc-generator, + libpve-apiclient-perl, libuuid-perl Standards-Version: 3.7.3 @@ -35,7 +36,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}, @PERLAPI@, fuse, corosync (>= 2.3.4-1), libqb0 (>= 0.17.1-1), - libpve-common-perl, + libpve-common-perl (>= 5.0-27), libglib2.0-0 (>= 2.42.1-1), rsyslog, openssl, @@ -47,6 +48,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}, @PERLAPI@, faketime, libcrypt-ssleay-perl, libuuid-perl, + libpve-apiclient-perl, lsb-base Breaks: pve-ha-manager (<<2.0-4) Description: Cluster Infrastructure for Proxmox Virtual Environment -- 2.39.2