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