]> git.proxmox.com Git - pve-manager.git/blob - PVE/API2/Cluster.pm
add Datacenter summary
[pve-manager.git] / PVE / API2 / Cluster.pm
1 package PVE::API2::Cluster;
2
3 use strict;
4 use warnings;
5
6 use XML::Parser;
7
8 use PVE::SafeSyslog;
9 use PVE::Tools qw(extract_param);
10 use PVE::INotify;
11 use PVE::Cluster qw(cfs_register_file cfs_lock_file cfs_read_file cfs_write_file);
12 use PVE::Storage;
13 use PVE::API2::Backup;
14 use PVE::API2::HAConfig;
15 use JSON;
16 use PVE::RESTHandler;
17 use PVE::RPCEnvironment;
18
19 use base qw(PVE::RESTHandler);
20
21 __PACKAGE__->register_method ({
22 subclass => "PVE::API2::Backup",
23 path => 'backup',
24 });
25
26 __PACKAGE__->register_method ({
27 subclass => "PVE::API2::HAConfig",
28 path => 'ha',
29 });
30
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' },
69 { name => 'backup' },
70 { name => 'ha' },
71 { name => 'status' },
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
109 my $admin = $rpcenv->check($user, "/", [ 'Sys.Syslog' ]);
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,
126 properties => {
127 type => {
128 type => 'string',
129 optional => 1,
130 enum => ['vm', 'storage', 'node'],
131 },
132 },
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
150 my $nodelist = PVE::Cluster::get_nodelist();
151 my $members = PVE::Cluster::get_members();
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"
160 if (!$param->{type} || $param->{type} eq 'vm') {
161 foreach my $vmid (keys %$idlist) {
162 my $data = $idlist->{$vmid};
163
164
165 next if !$rpcenv->check($user, "/vms/$vmid", [ 'VM.Audit' ]);
166
167 my $entry = {
168 id => "$data->{type}/$vmid",
169 vmid => $vmid + 0,
170 node => $data->{node},
171 type => $data->{type},
172 };
173
174 if (my $d = $rrd->{"pve2-vm/$vmid"}) {
175
176 $entry->{uptime} = ($d->[0] || 0) + 0;
177 $entry->{name} = $d->[1];
178
179 $entry->{maxcpu} = ($d->[3] || 0) + 0;
180 $entry->{cpu} = ($d->[4] || 0) + 0;
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;
185 }
186
187 push @$res, $entry;
188 }
189 }
190
191 if (!$param->{type} || $param->{type} eq 'node') {
192 foreach my $node (@$nodelist) {
193 my $entry = {
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;
211 }
212
213 push @$res, $entry;
214 }
215 }
216
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);
224 next if !$rpcenv->check($user, "/storage/$storeid", [ 'Datastore.Audit' ]);
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 }
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
279 my $all = $rpcenv->check($user, "/", [ 'Sys.Audit' ]);
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 => {
294 path => '/',
295 privs => [ 'Sys.Audit' ],
296 },
297 parameters => {
298 additionalProperties => 0,
299 properties => {},
300 },
301 returns => {
302 type => "object",
303 properties => {},
304 },
305 code => sub {
306 my ($param) = @_;
307 return PVE::Cluster::cfs_read_file('datacenter.cfg');
308 }});
309
310 __PACKAGE__->register_method({
311 name => 'set_options',
312 path => 'options',
313 method => 'PUT',
314 description => "Set datacenter options.",
315 permissions => {
316 path => '/',
317 privs => [ 'Sys.Modify' ],
318 },
319 protected => 1,
320 parameters => {
321 additionalProperties => 0,
322 properties => $dc_properties,
323 },
324 returns => { type => "null" },
325 code => sub {
326 my ($param) = @_;
327
328 my $filename = 'datacenter.cfg';
329
330 my $delete = extract_param($param, 'delete');
331
332 my $code = sub {
333
334 my $conf = cfs_read_file($filename);
335
336 foreach my $opt (keys %$param) {
337 $conf->{$opt} = $param->{$opt};
338 }
339
340 foreach my $opt (PVE::Tools::split_list($delete)) {
341 delete $conf->{$opt};
342 };
343
344 cfs_write_file($filename, $conf);
345 };
346
347 cfs_lock_file($filename, undef, $code);
348 die $@ if $@;
349
350 return undef;
351 }});
352
353 my $parse_clustat = sub {
354 my ($clinfo, $members, $nodename, $raw) = @_;
355
356 my $createNode = sub {
357 my ($expat, $tag, %attrib) = @_;
358 my $node = { type => $tag, %attrib };
359
360 if ($tag eq 'node') {
361 my $name = $node->{name};
362 return if !$name; # just to be sure
363
364 foreach my $key (qw(estranged local qdisk rgmanager rgmanager_master state)) {
365 $node->{$key} = int($node->{$key}) if defined($node->{$key});
366 }
367 $node->{nodeid} = hex($node->{nodeid}) if defined($node->{nodeid});
368
369 # unique ID for GUI
370 $node->{id} = "node/$node->{name}";
371
372 my $pmxcfs = 0;
373 if (!$members) { # no cluster
374 if ($name eq $nodename) {
375 $pmxcfs = ($clinfo && $clinfo->{version}) ? 1 : 0; # pmxcfs online ?
376 }
377 } elsif ($members->{$name}) {
378 $pmxcfs = $members->{$name}->{online} ? 1 : 0
379 }
380 $node->{pmxcfs} = $pmxcfs;
381
382 if ($members && $members->{$name}) {
383 if (my $ip = $members->{$name}->{ip}) {
384 $node->{ip} = $ip;
385 }
386 }
387 } elsif ($tag eq 'group') {
388 my $name = $node->{name};
389 return if !$name; # just to be sure
390 # unique ID for GUI
391 $node->{id} = "group/$node->{name}";
392 } else {
393 $node->{id} = $tag;
394 }
395
396 return $node;
397 };
398
399 my $extract_tags = {
400 cluster => 1,
401 quorum => 1,
402 node => 1,
403 group => 1,
404 };
405
406 my $handlers = {
407 Init => sub {
408 my $expat = shift;
409 $expat->{NodeList} = [];
410 },
411 Final => sub {
412 my $expat = shift;
413 $expat->{NodeList};
414 },
415 Start => sub {
416 my $expat = shift;
417 my $tag = shift;
418 if ($extract_tags->{$tag}) {
419 my $node = &$createNode($expat, $tag, @_);
420 push @{$expat->{NodeList}}, $node;
421 }
422 },
423 };
424
425 my $data = [];
426 if ($raw) {
427 my $parser = new XML::Parser(Handlers => $handlers);
428 $data = $parser->parse($raw);
429 }
430 return $data;
431 };
432
433 __PACKAGE__->register_method({
434 name => 'get_status',
435 path => 'status',
436 method => 'GET',
437 description => "Get cluster status informations.",
438 permissions => { user => 'all' },
439 protected => 1,
440 parameters => {
441 additionalProperties => 0,
442 properties => {},
443 },
444 returns => {
445 type => 'array',
446 items => {
447 type => "object",
448 properties => {
449 type => {
450 type => 'string'
451 },
452 },
453 },
454 },
455 code => sub {
456 my ($param) = @_;
457
458 # we also add info from pmxcfs
459 my $clinfo = PVE::Cluster::get_clinfo();
460 my $members = PVE::Cluster::get_members();
461 my $nodename = PVE::INotify::nodename();
462
463 if ($members) {
464 my $cmd = ['clustat', '-x'];
465 my $out = '';
466 PVE::Tools::run_command($cmd, outfunc => sub { $out .= shift; });
467 return &$parse_clustat($clinfo, $members, $nodename, $out);
468 } else {
469 # fake entry for local node if no cluster defined
470 my $pmxcfs = ($clinfo && $clinfo->{version}) ? 1 : 0; # pmxcfs online ?
471 return [{
472 type => 'node',
473 id => "node/$nodename",
474 name => $nodename,
475 'local' => 1,
476 nodeid => 0,
477 pmxcfs => $pmxcfs,
478 state => 1
479 }];
480 }
481 }});
482
483 1;