]>
Commit | Line | Data |
---|---|---|
aff192e6 DM |
1 | package PVE::API2::Cluster; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
a0af0132 DM |
6 | use XML::Parser; |
7 | ||
aff192e6 DM |
8 | use PVE::SafeSyslog; |
9 | use PVE::Tools qw(extract_param); | |
a06a3eac | 10 | use PVE::INotify; |
7cdf443c | 11 | use PVE::Cluster qw(cfs_register_file cfs_lock_file cfs_read_file cfs_write_file); |
aff192e6 | 12 | use PVE::Storage; |
ac27b58d | 13 | use PVE::API2::Backup; |
a06a3eac | 14 | use PVE::API2::HAConfig; |
aff192e6 | 15 | use JSON; |
aff192e6 DM |
16 | use PVE::RESTHandler; |
17 | use PVE::RPCEnvironment; | |
18 | ||
19 | use base qw(PVE::RESTHandler); | |
20 | ||
ac27b58d DM |
21 | __PACKAGE__->register_method ({ |
22 | subclass => "PVE::API2::Backup", | |
23 | path => 'backup', | |
24 | }); | |
25 | ||
a06a3eac DM |
26 | __PACKAGE__->register_method ({ |
27 | subclass => "PVE::API2::HAConfig", | |
28 | path => 'ha', | |
29 | }); | |
30 | ||
aff192e6 DM |
31 | my $dc_schema = PVE::Cluster::get_datacenter_schema(); |
32 | my $dc_properties = { | |
33 | delete => { | |
34 | type => 'string', format => 'pve-configid-list', | |
35 | description => "A list of settings you want to delete.", | |
36 | optional => 1, | |
37 | } | |
38 | }; | |
39 | foreach my $opt (keys %{$dc_schema->{properties}}) { | |
40 | $dc_properties->{$opt} = $dc_schema->{properties}->{$opt}; | |
41 | } | |
42 | ||
43 | __PACKAGE__->register_method ({ | |
44 | name => 'index', | |
45 | path => '', | |
46 | method => 'GET', | |
47 | description => "Cluster index.", | |
48 | permissions => { user => 'all' }, | |
49 | parameters => { | |
50 | additionalProperties => 0, | |
51 | properties => {}, | |
52 | }, | |
53 | returns => { | |
54 | type => 'array', | |
55 | items => { | |
56 | type => "object", | |
57 | properties => {}, | |
58 | }, | |
59 | links => [ { rel => 'child', href => "{name}" } ], | |
60 | }, | |
61 | code => sub { | |
62 | my ($param) = @_; | |
63 | ||
64 | my $result = [ | |
65 | { name => 'log' }, | |
66 | { name => 'options' }, | |
67 | { name => 'resources' }, | |
68 | { name => 'tasks' }, | |
ac27b58d | 69 | { name => 'backup' }, |
a06a3eac | 70 | { name => 'ha' }, |
a0af0132 | 71 | { name => 'status' }, |
aff192e6 DM |
72 | ]; |
73 | ||
74 | return $result; | |
75 | }}); | |
76 | ||
77 | __PACKAGE__->register_method({ | |
78 | name => 'log', | |
79 | path => 'log', | |
80 | method => 'GET', | |
81 | description => "Read cluster log", | |
82 | permissions => { user => 'all' }, | |
83 | parameters => { | |
84 | additionalProperties => 0, | |
85 | properties => { | |
86 | max => { | |
87 | type => 'integer', | |
88 | description => "Maximum number of entries.", | |
89 | optional => 1, | |
90 | minimum => 1, | |
91 | } | |
92 | }, | |
93 | }, | |
94 | returns => { | |
95 | type => 'array', | |
96 | items => { | |
97 | type => "object", | |
98 | properties => {}, | |
99 | }, | |
100 | }, | |
101 | code => sub { | |
102 | my ($param) = @_; | |
103 | ||
104 | my $rpcenv = PVE::RPCEnvironment::get(); | |
105 | ||
106 | my $max = $param->{max} || 0; | |
107 | my $user = $rpcenv->get_user(); | |
108 | ||
e4d554ba | 109 | my $admin = $rpcenv->check($user, "/", [ 'Sys.Syslog' ], 1); |
aff192e6 DM |
110 | |
111 | my $loguser = $admin ? '' : $user; | |
112 | ||
113 | my $res = decode_json(PVE::Cluster::get_cluster_log($loguser, $max)); | |
114 | ||
115 | return $res->{data}; | |
116 | }}); | |
117 | ||
118 | __PACKAGE__->register_method({ | |
119 | name => 'resources', | |
120 | path => 'resources', | |
121 | method => 'GET', | |
122 | description => "Resources index (cluster wide).", | |
123 | permissions => { user => 'all' }, | |
124 | parameters => { | |
125 | additionalProperties => 0, | |
badcb8d1 DM |
126 | properties => { |
127 | type => { | |
128 | type => 'string', | |
129 | optional => 1, | |
130 | enum => ['vm', 'storage', 'node'], | |
131 | }, | |
132 | }, | |
aff192e6 DM |
133 | }, |
134 | returns => { | |
135 | type => 'array', | |
136 | items => { | |
137 | type => "object", | |
138 | properties => { | |
139 | }, | |
140 | }, | |
141 | }, | |
142 | code => sub { | |
143 | my ($param) = @_; | |
144 | ||
145 | my $rpcenv = PVE::RPCEnvironment::get(); | |
146 | my $user = $rpcenv->get_user(); | |
147 | ||
148 | my $res = []; | |
149 | ||
bc7bff8e DM |
150 | my $nodelist = PVE::Cluster::get_nodelist(); |
151 | my $members = PVE::Cluster::get_members(); | |
aff192e6 DM |
152 | |
153 | my $rrd = PVE::Cluster::rrd_dump(); | |
154 | ||
155 | my $vmlist = PVE::Cluster::get_vmlist() || {}; | |
156 | my $idlist = $vmlist->{ids} || {}; | |
157 | ||
158 | ||
159 | # we try to generate 'numbers' by using "$X + 0" | |
badcb8d1 DM |
160 | if (!$param->{type} || $param->{type} eq 'vm') { |
161 | foreach my $vmid (keys %$idlist) { | |
162 | my $data = $idlist->{$vmid}; | |
aff192e6 | 163 | |
aff192e6 | 164 | |
e4d554ba | 165 | next if !$rpcenv->check($user, "/vms/$vmid", [ 'VM.Audit' ], 1); |
aff192e6 | 166 | |
badcb8d1 DM |
167 | my $entry = { |
168 | id => "$data->{type}/$vmid", | |
169 | vmid => $vmid + 0, | |
170 | node => $data->{node}, | |
171 | type => $data->{type}, | |
172 | }; | |
aff192e6 | 173 | |
badcb8d1 | 174 | if (my $d = $rrd->{"pve2-vm/$vmid"}) { |
aff192e6 | 175 | |
bc7bff8e | 176 | $entry->{uptime} = ($d->[0] || 0) + 0; |
badcb8d1 DM |
177 | $entry->{name} = $d->[1]; |
178 | ||
179 | $entry->{maxcpu} = ($d->[3] || 0) + 0; | |
bc7bff8e | 180 | $entry->{cpu} = ($d->[4] || 0) + 0; |
badcb8d1 DM |
181 | $entry->{maxmem} = ($d->[5] || 0) + 0; |
182 | $entry->{mem} = ($d->[6] || 0) + 0; | |
183 | $entry->{maxdisk} = ($d->[7] || 0) + 0; | |
184 | $entry->{disk} = ($d->[8] || 0) + 0; | |
bc7bff8e | 185 | } |
badcb8d1 DM |
186 | |
187 | push @$res, $entry; | |
aff192e6 | 188 | } |
aff192e6 DM |
189 | } |
190 | ||
badcb8d1 | 191 | if (!$param->{type} || $param->{type} eq 'node') { |
bc7bff8e | 192 | foreach my $node (@$nodelist) { |
aff192e6 | 193 | my $entry = { |
badcb8d1 DM |
194 | id => "node/$node", |
195 | node => $node, | |
196 | type => "node", | |
197 | }; | |
198 | if (my $d = $rrd->{"pve2-node/$node"}) { | |
199 | ||
200 | if (!$members || # no cluster | |
201 | ($members->{$node} && $members->{$node}->{online})) { | |
202 | $entry->{uptime} = ($d->[0] || 0) + 0; | |
203 | $entry->{cpu} = ($d->[4] || 0) + 0; | |
204 | $entry->{mem} = ($d->[7] || 0) + 0; | |
205 | $entry->{disk} = ($d->[11] || 0) + 0; | |
206 | } | |
207 | ||
208 | $entry->{maxcpu} = ($d->[3] || 0) + 0; | |
209 | $entry->{maxmem} = ($d->[6] || 0) + 0; | |
210 | $entry->{maxdisk} = ($d->[10] || 0) + 0; | |
aff192e6 DM |
211 | } |
212 | ||
213 | push @$res, $entry; | |
badcb8d1 DM |
214 | } |
215 | } | |
aff192e6 | 216 | |
badcb8d1 DM |
217 | if (!$param->{type} || $param->{type} eq 'storage') { |
218 | ||
219 | my $cfg = PVE::Storage::config(); | |
220 | my @sids = PVE::Storage::storage_ids ($cfg); | |
221 | ||
222 | foreach my $storeid (@sids) { | |
223 | my $scfg = PVE::Storage::storage_config($cfg, $storeid); | |
e4d554ba | 224 | next if !$rpcenv->check($user, "/storage/$storeid", [ 'Datastore.Audit' ], 1); |
badcb8d1 DM |
225 | # we create a entry for each node |
226 | foreach my $node (@$nodelist) { | |
227 | next if !PVE::Storage::storage_check_enabled($cfg, $storeid, $node, 1); | |
228 | my $entry = { | |
229 | id => "storage/$node/$storeid", | |
230 | storage => $storeid, | |
231 | node => $node, | |
232 | type => 'storage', | |
233 | }; | |
234 | ||
235 | if (my $d = $rrd->{"pve2-storage/$node/$storeid"}) { | |
236 | $entry->{maxdisk} = ($d->[1] || 0) + 0; | |
237 | $entry->{disk} = ($d->[2] || 0) + 0; | |
238 | } | |
239 | ||
240 | push @$res, $entry; | |
241 | } | |
aff192e6 DM |
242 | } |
243 | } | |
244 | ||
245 | return $res; | |
246 | }}); | |
247 | ||
248 | __PACKAGE__->register_method({ | |
249 | name => 'tasks', | |
250 | path => 'tasks', | |
251 | method => 'GET', | |
252 | description => "List recent tasks (cluster wide).", | |
253 | permissions => { user => 'all' }, | |
254 | parameters => { | |
255 | additionalProperties => 0, | |
256 | properties => {}, | |
257 | }, | |
258 | returns => { | |
259 | type => 'array', | |
260 | items => { | |
261 | type => "object", | |
262 | properties => { | |
263 | upid => { type => 'string' }, | |
264 | }, | |
265 | }, | |
266 | }, | |
267 | code => sub { | |
268 | my ($param) = @_; | |
269 | ||
270 | my $rpcenv = PVE::RPCEnvironment::get(); | |
271 | my $user = $rpcenv->get_user(); | |
272 | ||
273 | my $tlist = PVE::Cluster::get_tasklist(); | |
274 | ||
275 | my $res = []; | |
276 | ||
277 | return $res if !$tlist; | |
278 | ||
e4d554ba | 279 | my $all = $rpcenv->check($user, "/", [ 'Sys.Audit' ], 1); |
aff192e6 DM |
280 | |
281 | foreach my $task (@$tlist) { | |
282 | push @$res, $task if $all || ($task->{user} eq $user); | |
283 | } | |
284 | ||
285 | return $res; | |
286 | }}); | |
287 | ||
288 | __PACKAGE__->register_method({ | |
289 | name => 'get_options', | |
290 | path => 'options', | |
291 | method => 'GET', | |
292 | description => "Get datacenter options.", | |
293 | permissions => { | |
7d020b42 | 294 | check => ['perm', '/', [ 'Sys.Audit' ]], |
aff192e6 DM |
295 | }, |
296 | parameters => { | |
297 | additionalProperties => 0, | |
298 | properties => {}, | |
299 | }, | |
300 | returns => { | |
301 | type => "object", | |
302 | properties => {}, | |
303 | }, | |
304 | code => sub { | |
305 | my ($param) = @_; | |
306 | return PVE::Cluster::cfs_read_file('datacenter.cfg'); | |
307 | }}); | |
308 | ||
309 | __PACKAGE__->register_method({ | |
310 | name => 'set_options', | |
311 | path => 'options', | |
312 | method => 'PUT', | |
313 | description => "Set datacenter options.", | |
314 | permissions => { | |
7d020b42 | 315 | check => ['perm', '/', [ 'Sys.Modify' ]], |
aff192e6 DM |
316 | }, |
317 | protected => 1, | |
318 | parameters => { | |
319 | additionalProperties => 0, | |
320 | properties => $dc_properties, | |
321 | }, | |
322 | returns => { type => "null" }, | |
323 | code => sub { | |
324 | my ($param) = @_; | |
325 | ||
326 | my $filename = 'datacenter.cfg'; | |
327 | ||
328 | my $delete = extract_param($param, 'delete'); | |
329 | ||
330 | my $code = sub { | |
331 | ||
332 | my $conf = cfs_read_file($filename); | |
333 | ||
334 | foreach my $opt (keys %$param) { | |
335 | $conf->{$opt} = $param->{$opt}; | |
336 | } | |
337 | ||
338 | foreach my $opt (PVE::Tools::split_list($delete)) { | |
339 | delete $conf->{$opt}; | |
340 | }; | |
341 | ||
342 | cfs_write_file($filename, $conf); | |
343 | }; | |
344 | ||
345 | cfs_lock_file($filename, undef, $code); | |
346 | die $@ if $@; | |
347 | ||
348 | return undef; | |
349 | }}); | |
350 | ||
a0af0132 DM |
351 | my $parse_clustat = sub { |
352 | my ($clinfo, $members, $nodename, $raw) = @_; | |
353 | ||
354 | my $createNode = sub { | |
355 | my ($expat, $tag, %attrib) = @_; | |
356 | my $node = { type => $tag, %attrib }; | |
357 | ||
358 | if ($tag eq 'node') { | |
359 | my $name = $node->{name}; | |
360 | return if !$name; # just to be sure | |
361 | ||
362 | foreach my $key (qw(estranged local qdisk rgmanager rgmanager_master state)) { | |
363 | $node->{$key} = int($node->{$key}) if defined($node->{$key}); | |
364 | } | |
365 | $node->{nodeid} = hex($node->{nodeid}) if defined($node->{nodeid}); | |
366 | ||
367 | # unique ID for GUI | |
368 | $node->{id} = "node/$node->{name}"; | |
369 | ||
370 | my $pmxcfs = 0; | |
371 | if (!$members) { # no cluster | |
372 | if ($name eq $nodename) { | |
373 | $pmxcfs = ($clinfo && $clinfo->{version}) ? 1 : 0; # pmxcfs online ? | |
374 | } | |
375 | } elsif ($members->{$name}) { | |
376 | $pmxcfs = $members->{$name}->{online} ? 1 : 0 | |
377 | } | |
378 | $node->{pmxcfs} = $pmxcfs; | |
379 | ||
380 | if ($members && $members->{$name}) { | |
381 | if (my $ip = $members->{$name}->{ip}) { | |
382 | $node->{ip} = $ip; | |
383 | } | |
384 | } | |
385 | } elsif ($tag eq 'group') { | |
386 | my $name = $node->{name}; | |
387 | return if !$name; # just to be sure | |
388 | # unique ID for GUI | |
389 | $node->{id} = "group/$node->{name}"; | |
390 | } else { | |
391 | $node->{id} = $tag; | |
392 | } | |
393 | ||
394 | return $node; | |
395 | }; | |
396 | ||
397 | my $extract_tags = { | |
398 | cluster => 1, | |
399 | quorum => 1, | |
400 | node => 1, | |
401 | group => 1, | |
402 | }; | |
403 | ||
404 | my $handlers = { | |
405 | Init => sub { | |
406 | my $expat = shift; | |
407 | $expat->{NodeList} = []; | |
408 | }, | |
409 | Final => sub { | |
410 | my $expat = shift; | |
411 | $expat->{NodeList}; | |
412 | }, | |
413 | Start => sub { | |
414 | my $expat = shift; | |
415 | my $tag = shift; | |
416 | if ($extract_tags->{$tag}) { | |
417 | my $node = &$createNode($expat, $tag, @_); | |
418 | push @{$expat->{NodeList}}, $node; | |
419 | } | |
420 | }, | |
421 | }; | |
422 | ||
423 | my $data = []; | |
424 | if ($raw) { | |
425 | my $parser = new XML::Parser(Handlers => $handlers); | |
426 | $data = $parser->parse($raw); | |
427 | } | |
428 | return $data; | |
429 | }; | |
430 | ||
431 | __PACKAGE__->register_method({ | |
432 | name => 'get_status', | |
433 | path => 'status', | |
434 | method => 'GET', | |
435 | description => "Get cluster status informations.", | |
436 | permissions => { user => 'all' }, | |
437 | protected => 1, | |
438 | parameters => { | |
439 | additionalProperties => 0, | |
440 | properties => {}, | |
441 | }, | |
442 | returns => { | |
443 | type => 'array', | |
444 | items => { | |
445 | type => "object", | |
446 | properties => { | |
447 | type => { | |
448 | type => 'string' | |
449 | }, | |
450 | }, | |
451 | }, | |
452 | }, | |
453 | code => sub { | |
454 | my ($param) = @_; | |
455 | ||
456 | # we also add info from pmxcfs | |
457 | my $clinfo = PVE::Cluster::get_clinfo(); | |
458 | my $members = PVE::Cluster::get_members(); | |
459 | my $nodename = PVE::INotify::nodename(); | |
460 | ||
461 | if ($members) { | |
462 | my $cmd = ['clustat', '-x']; | |
463 | my $out = ''; | |
464 | PVE::Tools::run_command($cmd, outfunc => sub { $out .= shift; }); | |
465 | return &$parse_clustat($clinfo, $members, $nodename, $out); | |
466 | } else { | |
467 | # fake entry for local node if no cluster defined | |
468 | my $pmxcfs = ($clinfo && $clinfo->{version}) ? 1 : 0; # pmxcfs online ? | |
469 | return [{ | |
470 | type => 'node', | |
471 | id => "node/$nodename", | |
472 | name => $nodename, | |
473 | 'local' => 1, | |
474 | nodeid => 0, | |
475 | pmxcfs => $pmxcfs, | |
476 | state => 1 | |
477 | }]; | |
478 | } | |
479 | }}); | |
480 | ||
aff192e6 | 481 | 1; |