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