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