]> git.proxmox.com Git - pmg-api.git/blame - PMG/API2/Cluster.pm
PMG/DBTools.pm: add helpers for database synchronization
[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
e16a9efc 23sub cluster_join {
275d6d6d 24 my ($cfg, $conn_setup) = @_;
e16a9efc
DM
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);
275d6d6d
DM
31
32 foreach my $node (@$res) {
33 $cfg->{ids}->{$node->{cid}} = $node;
34 }
35
36 $cfg->write();
e16a9efc
DM
37}
38
5ac6bd0e
DM
39__PACKAGE__->register_method({
40 name => 'index',
41 path => '',
42 method => 'GET',
fb5f2d1e
DM
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',
5ac6bd0e
DM
73 description => "Cluster node index.",
74 # alway read local file
75 parameters => {
76 additionalProperties => 0,
77 properties => {},
78 },
cba17aeb 79 permissions => { check => [ 'admin' ] },
5ac6bd0e
DM
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
3862c23d
DM
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
5ac6bd0e
DM
102 return PVE::RESTHandler::hash_to_array($cfg->{ids}, 'cid');
103 }});
104
e16a9efc
DM
105my $add_node_schema = PMG::ClusterConfig::Node->createSchema(1);
106delete $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,
275d6d6d
DM
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 },
e16a9efc
DM
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
92c560f7
DM
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}) {
275d6d6d 141 $next_cid = $cid; # allow overwrite existing node data
92c560f7
DM
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 }
e16a9efc
DM
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
e16a9efc
DM
167 $cfg->{ids}->{$node->{cid}} = $node;
168
169 $cfg->write();
e16a9efc 170
275d6d6d
DM
171 return PVE::RESTHandler::hash_to_array($cfg->{ids}, 'cid');
172 };
e16a9efc 173
275d6d6d 174 return PMG::ClusterConfig::lock_config($code, "add node failed");
e16a9efc
DM
175 }});
176
cba17aeb 177__PACKAGE__->register_method({
fb5f2d1e
DM
178 name => 'create',
179 path => 'create',
cba17aeb
DM
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 },
cfdf6608
DM
187 protected => 1,
188 returns => { type => 'string' },
cba17aeb
DM
189 code => sub {
190 my ($param) = @_;
191
cfdf6608
DM
192 my $rpcenv = PMG::RESTEnvironment->get();
193 my $authuser = $rpcenv->get_user();
194
195 my $realcmd = sub {
cba17aeb
DM
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
cfdf6608
DM
202 my $cid = 1;
203
cba17aeb 204 $info->{type} = 'master';
cba17aeb 205
cfdf6608 206 $info->{maxcid} = $cid,
cba17aeb 207
cfdf6608
DM
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;
cba17aeb
DM
235 };
236
cfdf6608
DM
237 my $code = sub {
238 return $rpcenv->fork_worker('clustercreate', undef, $authuser, $realcmd);
239 };
cba17aeb 240
cfdf6608 241 return PMG::ClusterConfig::lock_config($code, "create cluster failed");
cba17aeb
DM
242 }});
243
fb5f2d1e
DM
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 },
e16a9efc
DM
262 password => {
263 description => "Superuser password.",
264 type => 'string',
265 maxLength => 128,
266 },
fb5f2d1e
DM
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
e16a9efc
DM
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
275d6d6d 288 cluster_join($cfg, $setup);
fb5f2d1e
DM
289 };
290
291 PMG::ClusterConfig::lock_config($code, "cluster join failed");
292
293 return undef;
294 }});
295
cba17aeb 296
5ac6bd0e 2971;