]> git.proxmox.com Git - pmg-api.git/blame - PMG/API2/Cluster.pm
PMG/API2/Cluster.pm: improve cluster join
[pmg-api.git] / PMG / API2 / Cluster.pm
CommitLineData
fb5f2d1e 1package PMG::API2::Cluster;
5ac6bd0e
DM
2
3use strict;
4use warnings;
5use Data::Dumper;
6
7use PVE::SafeSyslog;
8use PVE::Tools qw(extract_param);
9use HTTP::Status qw(:constants);
10use Storable qw(dclone);
11use PVE::JSONSchema qw(get_standard_option);
12use PVE::RESTHandler;
13use PVE::INotify;
e16a9efc 14use PVE::APIClient::LWP;
5ac6bd0e 15
cfdf6608 16use PMG::RESTEnvironment;
5ac6bd0e 17use PMG::ClusterConfig;
e16a9efc 18use PMG::Cluster;
cfdf6608 19use PMG::DBTools;
5ac6bd0e
DM
20
21use base qw(PVE::RESTHandler);
22
5a312cf1
DM
23# fixme:
24#my $db_service_list = [ 'pmgpolicy', 'pmgmirror', 'pmgtunnel', 'pmg-smtp-filter' ];
25my $db_service_list = [ 'pmgpolicy', 'pmg-smtp-filter' ];
26
e16a9efc 27sub cluster_join {
275d6d6d 28 my ($cfg, $conn_setup) = @_;
e16a9efc
DM
29
30 my $conn = PVE::APIClient::LWP->new(%$conn_setup);
31
32 my $info = PMG::Cluster::read_local_cluster_info();
33
34 my $res = $conn->post("/config/cluster/nodes", $info);
275d6d6d
DM
35
36 foreach my $node (@$res) {
37 $cfg->{ids}->{$node->{cid}} = $node;
38 }
39
5a312cf1
DM
40 eval {
41 print STDERR "stop all services accessing the database\n";
42 # stop all services accessing the database
43 PMG::Utils::service_wait_stopped(40, $db_service_list);
44
45 print STDERR "save new cluster configuration\n";
46 $cfg->write();
47
48 PMG::Cluster::update_ssh_keys($cfg);
49
50 print STDERR "cluster node successfully joined\n";
51
52 $cfg = PMG::ClusterConfig->new(); # reload
53
54 my $role = $cfg->{'local'}->{type} // '-';
55 die "local node '$cfg->{local}->{name}' not part of cluster\n"
56 if $role eq '-';
57
58 my $cid = $cfg->{'local'}->{cid};
59
60 PMG::Cluster::create_needed_dirs($cid, 1);
61
62 PMG::Cluster::sync_config_from_master($cfg, $cfg->{master}->{ip});
63
64 $cfg = PMG::ClusterConfig->new(); # reload
65
66 PMG::DBTools::init_nodedb($cfg);
67
68 print STDERR "syncing quarantine data\n";
69 # fixme:Proxmox::Cluster::sync_master_quar($cinfo->{master}->{ip});
70 print STDERR "syncing quarantine data finished\n";
71 };
72 my $err = $@;
73
74 foreach my $service (reverse @$db_service_list) {
75 eval { PVE::Tools::run_command(['systemctl', 'start', $service]); };
76 warn $@ if $@;
77 }
78
79 die $err if $err;
e16a9efc
DM
80}
81
5ac6bd0e
DM
82__PACKAGE__->register_method({
83 name => 'index',
84 path => '',
85 method => 'GET',
fb5f2d1e
DM
86 description => "Directory index.",
87 permissions => { user => 'all' },
88 parameters => {
89 additionalProperties => 0,
90 properties => {},
91 },
92 returns => {
93 type => 'array',
94 items => {
95 type => "object",
96 properties => {},
97 },
98 links => [ { rel => 'child', href => "{name}" } ],
99 },
100 code => sub {
101 my ($param) = @_;
102
103 my $result = [
104 { name => 'nodes' },
105 { name => 'create' },
106 { name => 'join' },
107 ];
108
109 return $result;
110 }});
111
112__PACKAGE__->register_method({
113 name => 'nodes',
114 path => 'nodes',
115 method => 'GET',
5ac6bd0e
DM
116 description => "Cluster node index.",
117 # alway read local file
118 parameters => {
119 additionalProperties => 0,
120 properties => {},
121 },
cba17aeb 122 permissions => { check => [ 'admin' ] },
5ac6bd0e
DM
123 returns => {
124 type => 'array',
125 items => {
126 type => "object",
127 properties => {
128 cid => { type => 'integer' },
129 },
130 },
131 links => [ { rel => 'child', href => "{cid}" } ],
132 },
133 code => sub {
134 my ($param) = @_;
135
136 my $cfg = PVE::INotify::read_file('cluster.conf');
137
3862c23d
DM
138 if (scalar(keys %{$cfg->{ids}})) {
139 my $role = $cfg->{local}->{type} // '-';
140 if ($role eq '-') {
141 die "local node '$cfg->{local}->{name}' not part of cluster\n";
142 }
143 }
144
5ac6bd0e
DM
145 return PVE::RESTHandler::hash_to_array($cfg->{ids}, 'cid');
146 }});
147
e16a9efc
DM
148my $add_node_schema = PMG::ClusterConfig::Node->createSchema(1);
149delete $add_node_schema->{properties}->{cid};
150
151__PACKAGE__->register_method({
152 name => 'add_node',
153 path => 'nodes',
154 method => 'POST',
155 description => "Add an node to the cluster config.",
156 proxyto => 'master',
157 protected => 1,
158 parameters => $add_node_schema,
275d6d6d
DM
159 returns => {
160 description => "Returns the resulting node list.",
161 type => 'array',
162 items => {
163 type => "object",
164 properties => {
165 cid => { type => 'integer' },
166 },
167 },
168 },
e16a9efc
DM
169 code => sub {
170 my ($param) = @_;
171
172 my $code = sub {
173 my $cfg = PMG::ClusterConfig->new();
174
175 die "no cluster defined\n" if !scalar(keys %{$cfg->{ids}});
176
177 my $master = $cfg->{master} || die "unable to lookup master node\n";
178
92c560f7
DM
179 my $next_cid;
180 foreach my $cid (keys %{$cfg->{ids}}) {
181 my $d = $cfg->{ids}->{$cid};
182
183 if ($d->{type} eq 'node' && $d->{ip} eq $param->{ip} && $d->{name} eq $param->{name}) {
275d6d6d 184 $next_cid = $cid; # allow overwrite existing node data
92c560f7
DM
185 last;
186 }
187
188 if ($d->{ip} eq $param->{ip}) {
189 die "ip address '$param->{ip}' is already used by existing node $d->{name}\n";
190 }
191
192 if ($d->{name} eq $param->{name}) {
193 die "node with name '$param->{name}' already exists\n";
194 }
195 }
196
197 if (!defined($next_cid)) {
198 $next_cid = ++$master->{maxcid};
199 }
e16a9efc
DM
200
201 my $node = {
202 type => 'node',
203 cid => $master->{maxcid},
204 };
205
206 foreach my $k (qw(ip name hostrsapubkey rootrsapubkey fingerprint)) {
207 $node->{$k} = $param->{$k};
208 }
209
e16a9efc
DM
210 $cfg->{ids}->{$node->{cid}} = $node;
211
212 $cfg->write();
e16a9efc 213
5a312cf1
DM
214 PMG::DBTools::update_master_clusterinfo($node->{cid});
215
216 PMG::Cluster::update_ssh_keys($cfg);
217
275d6d6d
DM
218 return PVE::RESTHandler::hash_to_array($cfg->{ids}, 'cid');
219 };
e16a9efc 220
275d6d6d 221 return PMG::ClusterConfig::lock_config($code, "add node failed");
e16a9efc
DM
222 }});
223
cba17aeb 224__PACKAGE__->register_method({
fb5f2d1e
DM
225 name => 'create',
226 path => 'create',
cba17aeb
DM
227 method => 'POST',
228 description => "Create initial cluster config with current node as master.",
229 # alway read local file
230 parameters => {
231 additionalProperties => 0,
232 properties => {},
233 },
cfdf6608
DM
234 protected => 1,
235 returns => { type => 'string' },
cba17aeb
DM
236 code => sub {
237 my ($param) = @_;
238
cfdf6608
DM
239 my $rpcenv = PMG::RESTEnvironment->get();
240 my $authuser = $rpcenv->get_user();
241
242 my $realcmd = sub {
cba17aeb
DM
243 my $cfg = PMG::ClusterConfig->new();
244
245 die "cluster alreayd defined\n" if scalar(keys %{$cfg->{ids}});
246
247 my $info = PMG::Cluster::read_local_cluster_info();
248
cfdf6608
DM
249 my $cid = 1;
250
cba17aeb 251 $info->{type} = 'master';
cba17aeb 252
cfdf6608 253 $info->{maxcid} = $cid,
cba17aeb 254
cfdf6608
DM
255 $cfg->{ids}->{$cid} = $info;
256
cfdf6608
DM
257 eval {
258 print STDERR "stop all services accessing the database\n";
259 # stop all services accessing the database
5a312cf1 260 PMG::Utils::service_wait_stopped(40, $db_service_list);
cfdf6608
DM
261
262 print STDERR "save new cluster configuration\n";
263 $cfg->write();
264
265 PMG::DBTools::init_masterdb($cid);
266
267 PMG::Cluster::create_needed_dirs($cid, 1);
268
269 print STDERR "cluster master successfully created\n";
270 };
271 my $err = $@;
272
5a312cf1 273 foreach my $service (reverse @$db_service_list) {
cfdf6608
DM
274 eval { PVE::Tools::run_command(['systemctl', 'start', $service]); };
275 warn $@ if $@;
276 }
277
278 die $err if $err;
cba17aeb
DM
279 };
280
cfdf6608
DM
281 my $code = sub {
282 return $rpcenv->fork_worker('clustercreate', undef, $authuser, $realcmd);
283 };
cba17aeb 284
cfdf6608 285 return PMG::ClusterConfig::lock_config($code, "create cluster failed");
cba17aeb
DM
286 }});
287
fb5f2d1e
DM
288__PACKAGE__->register_method({
289 name => 'join',
290 path => 'join',
291 method => 'POST',
292 description => "Join local node to an existing cluster.",
293 # alway read local file
294 parameters => {
295 additionalProperties => 0,
296 properties => {
297 master_ip => {
298 description => "IP address.",
299 type => 'string', format => 'ip',
300 },
301 fingerprint => {
302 description => "SSL certificate fingerprint.",
303 type => 'string',
304 pattern => '^(:?[A-Z0-9][A-Z0-9]:){31}[A-Z0-9][A-Z0-9]$',
305 },
e16a9efc
DM
306 password => {
307 description => "Superuser password.",
308 type => 'string',
309 maxLength => 128,
310 },
fb5f2d1e
DM
311 },
312 },
5a312cf1 313 returns => { type => 'string' },
fb5f2d1e
DM
314 code => sub {
315 my ($param) = @_;
316
5a312cf1
DM
317 my $rpcenv = PMG::RESTEnvironment->get();
318 my $authuser = $rpcenv->get_user();
319
320 my $realcmd = sub {
fb5f2d1e
DM
321 my $cfg = PMG::ClusterConfig->new();
322
323 die "cluster alreayd defined\n" if scalar(keys %{$cfg->{ids}});
324
e16a9efc
DM
325 my $setup = {
326 username => 'root@pam',
327 password => $param->{password},
328 cookie_name => 'PMGAuthCookie',
329 host => $param->{master_ip},
330 cached_fingerprints => {
331 $param->{fingerprint} => 1,
332 }
333 };
334
275d6d6d 335 cluster_join($cfg, $setup);
fb5f2d1e
DM
336 };
337
5a312cf1
DM
338 my $code = sub {
339 return $rpcenv->fork_worker('clusterjoin', undef, $authuser, $realcmd);
340 };
fb5f2d1e 341
5a312cf1 342 return PMG::ClusterConfig::lock_config($code, "cluster join failed");
fb5f2d1e
DM
343 }});
344
cba17aeb 345
5ac6bd0e 3461;