]> git.proxmox.com Git - pmg-api.git/blame - PMG/API2/Cluster.pm
PMG/ClusterConfig.pm - minor code cleanup
[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 20
ba11e2d3
DM
21use PMG::API2::Nodes;
22
5ac6bd0e
DM
23use base qw(PVE::RESTHandler);
24
e16a9efc 25sub cluster_join {
245b527c 26 my ($cinfo, $conn_setup) = @_;
e16a9efc
DM
27
28 my $conn = PVE::APIClient::LWP->new(%$conn_setup);
29
30 my $info = PMG::Cluster::read_local_cluster_info();
31
32 my $res = $conn->post("/config/cluster/nodes", $info);
275d6d6d
DM
33
34 foreach my $node (@$res) {
245b527c 35 $cinfo->{ids}->{$node->{cid}} = $node;
275d6d6d
DM
36 }
37
5a312cf1
DM
38 eval {
39 print STDERR "stop all services accessing the database\n";
40 # stop all services accessing the database
00d8b7f7 41 PMG::Utils::service_wait_stopped(40, $PMG::Utils::db_service_list);
5a312cf1
DM
42
43 print STDERR "save new cluster configuration\n";
245b527c 44 $cinfo->write();
5a312cf1 45
245b527c 46 PMG::Cluster::update_ssh_keys($cinfo);
5a312cf1
DM
47
48 print STDERR "cluster node successfully joined\n";
49
245b527c 50 $cinfo = PMG::ClusterConfig->new(); # reload
5a312cf1 51
245b527c
DM
52 my $role = $cinfo->{'local'}->{type} // '-';
53 die "local node '$cinfo->{local}->{name}' not part of cluster\n"
5a312cf1
DM
54 if $role eq '-';
55
245b527c 56 die "got unexpected role '$role' for local node '$cinfo->{local}->{name}'\n"
809ae8f4
DM
57 if $role ne 'node';
58
245b527c 59 my $cid = $cinfo->{'local'}->{cid};
5a312cf1
DM
60
61 PMG::Cluster::create_needed_dirs($cid, 1);
62
245b527c 63 PMG::Cluster::sync_config_from_master($cinfo->{master}->{name}, $cinfo->{master}->{ip});
5a312cf1 64
245b527c 65 PMG::DBTools::init_nodedb($cinfo);
5a312cf1 66
00d8b7f7 67 my $cfg = PMG::Config->new();
245b527c
DM
68 my $ruledb = PMG::RuleDB->new();
69 my $rulecache = PMG::RuleCache->new($ruledb);
70
71 $cfg->rewrite_config($rulecache, 1);
5a312cf1
DM
72
73 print STDERR "syncing quarantine data\n";
245b527c 74 PMG::Cluster::sync_master_quar($cinfo->{master}->{ip}, $cinfo->{master}->{name});
5a312cf1
DM
75 print STDERR "syncing quarantine data finished\n";
76 };
77 my $err = $@;
78
00d8b7f7 79 foreach my $service (reverse @$PMG::Utils::db_service_list) {
5a312cf1
DM
80 eval { PVE::Tools::run_command(['systemctl', 'start', $service]); };
81 warn $@ if $@;
82 }
83
84 die $err if $err;
e16a9efc
DM
85}
86
5ac6bd0e
DM
87__PACKAGE__->register_method({
88 name => 'index',
89 path => '',
90 method => 'GET',
fb5f2d1e
DM
91 description => "Directory index.",
92 permissions => { user => 'all' },
93 parameters => {
94 additionalProperties => 0,
95 properties => {},
96 },
97 returns => {
98 type => 'array',
99 items => {
100 type => "object",
101 properties => {},
102 },
103 links => [ { rel => 'child', href => "{name}" } ],
104 },
105 code => sub {
106 my ($param) = @_;
107
108 my $result = [
109 { name => 'nodes' },
150e8421 110 { name => 'status' },
fb5f2d1e
DM
111 { name => 'create' },
112 { name => 'join' },
113 ];
114
115 return $result;
116 }});
117
118__PACKAGE__->register_method({
119 name => 'nodes',
120 path => 'nodes',
121 method => 'GET',
5ac6bd0e
DM
122 description => "Cluster node index.",
123 # alway read local file
124 parameters => {
125 additionalProperties => 0,
126 properties => {},
127 },
cba17aeb 128 permissions => { check => [ 'admin' ] },
5ac6bd0e
DM
129 returns => {
130 type => 'array',
131 items => {
132 type => "object",
133 properties => {
150e8421 134 type => { type => 'string' },
5ac6bd0e 135 cid => { type => 'integer' },
150e8421
DM
136 ip => { type => 'string' },
137 name => { type => 'string' },
138 hostrsapubkey => { type => 'string' },
139 rootrsapubkey => { type => 'string' },
140 fingerprint => { type => 'string' },
141 },
142 },
143 links => [ { rel => 'child', href => "{cid}" } ],
144 },
145 code => sub {
146 my ($param) = @_;
147
245b527c 148 my $cinfo = PMG::ClusterConfig->new();
150e8421 149
245b527c
DM
150 if (scalar(keys %{$cinfo->{ids}})) {
151 my $role = $cinfo->{local}->{type} // '-';
150e8421 152 if ($role eq '-') {
245b527c 153 die "local node '$cinfo->{local}->{name}' not part of cluster\n";
150e8421
DM
154 }
155 }
156
245b527c 157 return PVE::RESTHandler::hash_to_array($cinfo->{ids}, 'cid');
150e8421
DM
158 }});
159
160__PACKAGE__->register_method({
161 name => 'status',
162 path => 'status',
163 method => 'GET',
164 description => "Cluster node status.",
165 # alway read local file
166 parameters => {
167 additionalProperties => 0,
168 properties => {},
169 },
170 permissions => { check => [ 'admin' ] },
171 returns => {
172 type => 'array',
173 items => {
174 type => "object",
175 properties => {
176 type => { type => 'string' },
177 cid => { type => 'integer' },
178 ip => { type => 'string' },
179 name => { type => 'string' },
180 hostrsapubkey => { type => 'string' },
181 rootrsapubkey => { type => 'string' },
182 fingerprint => { type => 'string' },
5ac6bd0e
DM
183 },
184 },
185 links => [ { rel => 'child', href => "{cid}" } ],
186 },
187 code => sub {
188 my ($param) = @_;
189
245b527c 190 my $cinfo = PMG::ClusterConfig->new();
5ac6bd0e 191
245b527c
DM
192 if (scalar(keys %{$cinfo->{ids}})) {
193 my $role = $cinfo->{local}->{type} // '-';
3862c23d 194 if ($role eq '-') {
245b527c 195 die "local node '$cinfo->{local}->{name}' not part of cluster\n";
3862c23d
DM
196 }
197 }
198
245b527c 199 my $res = PVE::RESTHandler::hash_to_array($cinfo->{ids}, 'cid');
ba11e2d3
DM
200
201 my $rpcenv = PMG::RESTEnvironment->get();
202 my $authuser = $rpcenv->get_user();
203 my $ticket = $rpcenv->get_ticket();
204
205 foreach my $ni (@$res) {
206 my $info;
207 eval {
245b527c 208 if ($ni->{cid} eq $cinfo->{local}->{cid}) {
ba11e2d3
DM
209 $info = PMG::API2::NodeInfo->status({ node => PVE::INotify::nodename()});
210 } else {
211 my $conn = PVE::APIClient::LWP->new(
212 ticket => $ticket,
213 cookie_name => 'PMGAuthCookie',
214 host => $ni->{ip},
215 cached_fingerprints => {
216 $ni->{fingerprint} => 1,
217 });
218
219 $info = $conn->get("/nodes/localhost/status", {});
220 }
221 };
222 if (my $err = $@) {
15eac67d 223 $ni->{conn_error} = "$err"; # convert $err to string
ba11e2d3
DM
224 next;
225 }
226 foreach my $k (keys %$info) {
227 $ni->{$k} = $info->{$k} if !defined($ni->{$k});
228 }
229 }
230
231 return $res;
5ac6bd0e
DM
232 }});
233
e16a9efc
DM
234my $add_node_schema = PMG::ClusterConfig::Node->createSchema(1);
235delete $add_node_schema->{properties}->{cid};
236
237__PACKAGE__->register_method({
238 name => 'add_node',
239 path => 'nodes',
240 method => 'POST',
241 description => "Add an node to the cluster config.",
242 proxyto => 'master',
243 protected => 1,
244 parameters => $add_node_schema,
275d6d6d
DM
245 returns => {
246 description => "Returns the resulting node list.",
247 type => 'array',
248 items => {
249 type => "object",
250 properties => {
251 cid => { type => 'integer' },
252 },
253 },
254 },
e16a9efc
DM
255 code => sub {
256 my ($param) = @_;
257
258 my $code = sub {
245b527c 259 my $cinfo = PMG::ClusterConfig->new();
e16a9efc 260
245b527c 261 die "no cluster defined\n" if !scalar(keys %{$cinfo->{ids}});
e16a9efc 262
245b527c 263 my $master = $cinfo->{master} || die "unable to lookup master node\n";
e16a9efc 264
92c560f7 265 my $next_cid;
245b527c
DM
266 foreach my $cid (keys %{$cinfo->{ids}}) {
267 my $d = $cinfo->{ids}->{$cid};
92c560f7
DM
268
269 if ($d->{type} eq 'node' && $d->{ip} eq $param->{ip} && $d->{name} eq $param->{name}) {
275d6d6d 270 $next_cid = $cid; # allow overwrite existing node data
92c560f7
DM
271 last;
272 }
273
274 if ($d->{ip} eq $param->{ip}) {
275 die "ip address '$param->{ip}' is already used by existing node $d->{name}\n";
276 }
277
278 if ($d->{name} eq $param->{name}) {
279 die "node with name '$param->{name}' already exists\n";
280 }
281 }
282
283 if (!defined($next_cid)) {
284 $next_cid = ++$master->{maxcid};
285 }
e16a9efc
DM
286
287 my $node = {
288 type => 'node',
289 cid => $master->{maxcid},
290 };
291
292 foreach my $k (qw(ip name hostrsapubkey rootrsapubkey fingerprint)) {
293 $node->{$k} = $param->{$k};
294 }
295
245b527c 296 $cinfo->{ids}->{$node->{cid}} = $node;
e16a9efc 297
245b527c 298 $cinfo->write();
e16a9efc 299
5a312cf1
DM
300 PMG::DBTools::update_master_clusterinfo($node->{cid});
301
245b527c 302 PMG::Cluster::update_ssh_keys($cinfo);
5a312cf1 303
245b527c 304 return PVE::RESTHandler::hash_to_array($cinfo->{ids}, 'cid');
275d6d6d 305 };
e16a9efc 306
275d6d6d 307 return PMG::ClusterConfig::lock_config($code, "add node failed");
e16a9efc
DM
308 }});
309
cba17aeb 310__PACKAGE__->register_method({
fb5f2d1e
DM
311 name => 'create',
312 path => 'create',
cba17aeb
DM
313 method => 'POST',
314 description => "Create initial cluster config with current node as master.",
315 # alway read local file
316 parameters => {
317 additionalProperties => 0,
318 properties => {},
319 },
cfdf6608
DM
320 protected => 1,
321 returns => { type => 'string' },
cba17aeb
DM
322 code => sub {
323 my ($param) = @_;
324
cfdf6608
DM
325 my $rpcenv = PMG::RESTEnvironment->get();
326 my $authuser = $rpcenv->get_user();
327
328 my $realcmd = sub {
245b527c 329 my $cinfo = PMG::ClusterConfig->new();
cba17aeb 330
245b527c 331 die "cluster alreayd defined\n" if scalar(keys %{$cinfo->{ids}});
cba17aeb
DM
332
333 my $info = PMG::Cluster::read_local_cluster_info();
334
cfdf6608
DM
335 my $cid = 1;
336
cba17aeb 337 $info->{type} = 'master';
cba17aeb 338
cfdf6608 339 $info->{maxcid} = $cid,
cba17aeb 340
245b527c 341 $cinfo->{ids}->{$cid} = $info;
cfdf6608 342
cfdf6608
DM
343 eval {
344 print STDERR "stop all services accessing the database\n";
345 # stop all services accessing the database
00d8b7f7 346 PMG::Utils::service_wait_stopped(40, $PMG::Utils::db_service_list);
cfdf6608
DM
347
348 print STDERR "save new cluster configuration\n";
245b527c 349 $cinfo->write();
cfdf6608
DM
350
351 PMG::DBTools::init_masterdb($cid);
352
353 PMG::Cluster::create_needed_dirs($cid, 1);
354
355 print STDERR "cluster master successfully created\n";
356 };
357 my $err = $@;
358
00d8b7f7 359 foreach my $service (reverse @$PMG::Utils::db_service_list) {
cfdf6608
DM
360 eval { PVE::Tools::run_command(['systemctl', 'start', $service]); };
361 warn $@ if $@;
362 }
363
364 die $err if $err;
cba17aeb
DM
365 };
366
cfdf6608
DM
367 my $code = sub {
368 return $rpcenv->fork_worker('clustercreate', undef, $authuser, $realcmd);
369 };
cba17aeb 370
cfdf6608 371 return PMG::ClusterConfig::lock_config($code, "create cluster failed");
cba17aeb
DM
372 }});
373
fb5f2d1e
DM
374__PACKAGE__->register_method({
375 name => 'join',
376 path => 'join',
377 method => 'POST',
378 description => "Join local node to an existing cluster.",
379 # alway read local file
4884557f 380 protected => 1,
fb5f2d1e
DM
381 parameters => {
382 additionalProperties => 0,
383 properties => {
384 master_ip => {
385 description => "IP address.",
386 type => 'string', format => 'ip',
387 },
388 fingerprint => {
389 description => "SSL certificate fingerprint.",
390 type => 'string',
391 pattern => '^(:?[A-Z0-9][A-Z0-9]:){31}[A-Z0-9][A-Z0-9]$',
392 },
e16a9efc
DM
393 password => {
394 description => "Superuser password.",
395 type => 'string',
396 maxLength => 128,
397 },
fb5f2d1e
DM
398 },
399 },
5a312cf1 400 returns => { type => 'string' },
fb5f2d1e
DM
401 code => sub {
402 my ($param) = @_;
403
5a312cf1
DM
404 my $rpcenv = PMG::RESTEnvironment->get();
405 my $authuser = $rpcenv->get_user();
406
407 my $realcmd = sub {
245b527c 408 my $cinfo = PMG::ClusterConfig->new();
fb5f2d1e 409
245b527c 410 die "cluster alreayd defined\n" if scalar(keys %{$cinfo->{ids}});
fb5f2d1e 411
e16a9efc
DM
412 my $setup = {
413 username => 'root@pam',
414 password => $param->{password},
415 cookie_name => 'PMGAuthCookie',
416 host => $param->{master_ip},
417 cached_fingerprints => {
418 $param->{fingerprint} => 1,
419 }
420 };
421
245b527c 422 cluster_join($cinfo, $setup);
fb5f2d1e
DM
423 };
424
5a312cf1
DM
425 my $code = sub {
426 return $rpcenv->fork_worker('clusterjoin', undef, $authuser, $realcmd);
427 };
fb5f2d1e 428
5a312cf1 429 return PMG::ClusterConfig::lock_config($code, "cluster join failed");
fb5f2d1e
DM
430 }});
431
cba17aeb 432
5ac6bd0e 4331;