]> git.proxmox.com Git - pve-manager.git/blob - PVE/API2/Cluster.pm
depend on pve-firewall, connect firewall API
[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::Exception qw(raise_param_exc);
11 use PVE::INotify;
12 use PVE::Cluster qw(cfs_register_file cfs_lock_file cfs_read_file cfs_write_file);
13 use PVE::Storage;
14 use PVE::API2Tools;
15 use PVE::API2::Backup;
16 use PVE::API2::HAConfig;
17 use JSON;
18 use PVE::RESTHandler;
19 use PVE::RPCEnvironment;
20 use PVE::JSONSchema qw(get_standard_option);
21 use PVE::Firewall;
22 use PVE::API2::Firewall::Cluster;
23
24 use base qw(PVE::RESTHandler);
25
26 __PACKAGE__->register_method ({
27 subclass => "PVE::API2::Firewall::Cluster",
28 path => 'firewall',
29 });
30
31 __PACKAGE__->register_method ({
32 subclass => "PVE::API2::Backup",
33 path => 'backup',
34 });
35
36 __PACKAGE__->register_method ({
37 subclass => "PVE::API2::HAConfig",
38 path => 'ha',
39 });
40
41 my $dc_schema = PVE::Cluster::get_datacenter_schema();
42 my $dc_properties = {
43 delete => {
44 type => 'string', format => 'pve-configid-list',
45 description => "A list of settings you want to delete.",
46 optional => 1,
47 }
48 };
49 foreach my $opt (keys %{$dc_schema->{properties}}) {
50 $dc_properties->{$opt} = $dc_schema->{properties}->{$opt};
51 }
52
53 __PACKAGE__->register_method ({
54 name => 'index',
55 path => '',
56 method => 'GET',
57 description => "Cluster index.",
58 permissions => { user => 'all' },
59 parameters => {
60 additionalProperties => 0,
61 properties => {},
62 },
63 returns => {
64 type => 'array',
65 items => {
66 type => "object",
67 properties => {},
68 },
69 links => [ { rel => 'child', href => "{name}" } ],
70 },
71 code => sub {
72 my ($param) = @_;
73
74 my $result = [
75 { name => 'log' },
76 { name => 'options' },
77 { name => 'resources' },
78 { name => 'tasks' },
79 { name => 'backup' },
80 { name => 'ha' },
81 { name => 'status' },
82 { name => 'nextid' },
83 { name => 'firewall' },
84 ];
85
86 return $result;
87 }});
88
89 __PACKAGE__->register_method({
90 name => 'log',
91 path => 'log',
92 method => 'GET',
93 description => "Read cluster log",
94 permissions => { user => 'all' },
95 parameters => {
96 additionalProperties => 0,
97 properties => {
98 max => {
99 type => 'integer',
100 description => "Maximum number of entries.",
101 optional => 1,
102 minimum => 1,
103 }
104 },
105 },
106 returns => {
107 type => 'array',
108 items => {
109 type => "object",
110 properties => {},
111 },
112 },
113 code => sub {
114 my ($param) = @_;
115
116 my $rpcenv = PVE::RPCEnvironment::get();
117
118 my $max = $param->{max} || 0;
119 my $user = $rpcenv->get_user();
120
121 my $admin = $rpcenv->check($user, "/", [ 'Sys.Syslog' ], 1);
122
123 my $loguser = $admin ? '' : $user;
124
125 my $res = decode_json(PVE::Cluster::get_cluster_log($loguser, $max));
126
127 return $res->{data};
128 }});
129
130 __PACKAGE__->register_method({
131 name => 'resources',
132 path => 'resources',
133 method => 'GET',
134 description => "Resources index (cluster wide).",
135 permissions => { user => 'all' },
136 parameters => {
137 additionalProperties => 0,
138 properties => {
139 type => {
140 type => 'string',
141 optional => 1,
142 enum => ['vm', 'storage', 'node'],
143 },
144 },
145 },
146 returns => {
147 type => 'array',
148 items => {
149 type => "object",
150 properties => {
151 },
152 },
153 },
154 code => sub {
155 my ($param) = @_;
156
157 my $rpcenv = PVE::RPCEnvironment::get();
158 my $authuser = $rpcenv->get_user();
159 my $usercfg = $rpcenv->{user_cfg};
160
161 my $res = [];
162
163 my $nodelist = PVE::Cluster::get_nodelist();
164 my $members = PVE::Cluster::get_members();
165
166 my $rrd = PVE::Cluster::rrd_dump();
167
168 my $vmlist = PVE::Cluster::get_vmlist() || {};
169 my $idlist = $vmlist->{ids} || {};
170
171 my $pooldata = {};
172 if (!$param->{type} || $param->{type} eq 'pool') {
173 foreach my $pool (keys %{$usercfg->{pools}}) {
174 my $d = $usercfg->{pools}->{$pool};
175
176 next if !$rpcenv->check($authuser, "/pool/$pool", [ 'Pool.Allocate' ], 1);
177
178 my $entry = {
179 id => "/pool/$pool",
180 pool => $pool,
181 type => 'pool',
182 };
183
184 $pooldata->{$pool} = $entry;
185
186 push @$res, $entry;
187 }
188 }
189
190 # we try to generate 'numbers' by using "$X + 0"
191 if (!$param->{type} || $param->{type} eq 'vm') {
192 foreach my $vmid (keys %$idlist) {
193
194 my $data = $idlist->{$vmid};
195 my $entry = PVE::API2Tools::extract_vm_stats($vmid, $data, $rrd);
196 if ($entry->{uptime}) {
197 if (my $pool = $usercfg->{vms}->{$vmid}) {
198 if (my $pe = $pooldata->{$pool}) {
199 $pe->{uptime} = $entry->{uptime} if !$pe->{uptime} || $entry->{uptime} > $pe->{uptime};
200 $pe->{mem} = 0 if !$pe->{mem};
201 $pe->{mem} += $entry->{mem};
202 $pe->{maxmem} = 0 if !$pe->{maxmem};
203 $pe->{maxmem} += $entry->{maxmem};
204 $pe->{cpu} = 0 if !$pe->{cpu};
205 $pe->{cpu} += $entry->{cpu};
206 $pe->{maxcpu} = 0 if !$pe->{maxcpu};
207 $pe->{maxcpu} += $entry->{maxcpu};
208 }
209 }
210 }
211
212 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
213
214 push @$res, $entry;
215 }
216 }
217
218 if (!$param->{type} || $param->{type} eq 'node') {
219 foreach my $node (@$nodelist) {
220 my $entry = PVE::API2Tools::extract_node_stats($node, $members, $rrd);
221 push @$res, $entry;
222 }
223 }
224
225 if (!$param->{type} || $param->{type} eq 'storage') {
226
227 my $cfg = PVE::Storage::config();
228 my @sids = PVE::Storage::storage_ids ($cfg);
229
230 foreach my $storeid (@sids) {
231 next if !$rpcenv->check($authuser, "/storage/$storeid", [ 'Datastore.Audit' ], 1);
232
233 my $scfg = PVE::Storage::storage_config($cfg, $storeid);
234 # we create a entry for each node
235 foreach my $node (@$nodelist) {
236 next if !PVE::Storage::storage_check_enabled($cfg, $storeid, $node, 1);
237
238 my $entry = PVE::API2Tools::extract_storage_stats($storeid, $scfg, $node, $rrd);
239 push @$res, $entry;
240 }
241 }
242 }
243
244 return $res;
245 }});
246
247 __PACKAGE__->register_method({
248 name => 'tasks',
249 path => 'tasks',
250 method => 'GET',
251 description => "List recent tasks (cluster wide).",
252 permissions => { user => 'all' },
253 parameters => {
254 additionalProperties => 0,
255 properties => {},
256 },
257 returns => {
258 type => 'array',
259 items => {
260 type => "object",
261 properties => {
262 upid => { type => 'string' },
263 },
264 },
265 },
266 code => sub {
267 my ($param) = @_;
268
269 my $rpcenv = PVE::RPCEnvironment::get();
270 my $authuser = $rpcenv->get_user();
271
272 my $tlist = PVE::Cluster::get_tasklist();
273
274 my $res = [];
275
276 return $res if !$tlist;
277
278 my $all = $rpcenv->check($authuser, "/", [ 'Sys.Audit' ], 1);
279
280 foreach my $task (@$tlist) {
281 push @$res, $task if $all || ($task->{user} eq $authuser);
282 }
283
284 return $res;
285 }});
286
287 __PACKAGE__->register_method({
288 name => 'get_options',
289 path => 'options',
290 method => 'GET',
291 description => "Get datacenter options.",
292 permissions => {
293 check => ['perm', '/', [ 'Sys.Audit' ]],
294 },
295 parameters => {
296 additionalProperties => 0,
297 properties => {},
298 },
299 returns => {
300 type => "object",
301 properties => {},
302 },
303 code => sub {
304 my ($param) = @_;
305
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 => {
315 check => ['perm', '/', [ 'Sys.Modify' ]],
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
351 my $parse_clustat = sub {
352 my ($clinfo, $members, $rrd, $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
386 if (my $entry = PVE::API2Tools::extract_node_stats($name, $members, $rrd)) {
387 $node->{level} = $entry->{level} || '';
388 }
389
390 } elsif ($tag eq 'group') {
391 my $name = $node->{name};
392 return if !$name; # just to be sure
393 # unique ID for GUI
394 $node->{id} = "group/$node->{name}";
395 } else {
396 $node->{id} = $tag;
397 }
398
399 return $node;
400 };
401
402 my $extract_tags = {
403 cluster => 1,
404 quorum => 1,
405 node => 1,
406 group => 1,
407 };
408
409 my $handlers = {
410 Init => sub {
411 my $expat = shift;
412 $expat->{NodeList} = [];
413 },
414 Final => sub {
415 my $expat = shift;
416 $expat->{NodeList};
417 },
418 Start => sub {
419 my $expat = shift;
420 my $tag = shift;
421 if ($extract_tags->{$tag}) {
422 my $node = &$createNode($expat, $tag, @_);
423 push @{$expat->{NodeList}}, $node;
424 }
425 },
426 };
427
428 my $data = [];
429 if ($raw) {
430 my $parser = new XML::Parser(Handlers => $handlers);
431 $data = $parser->parse($raw);
432 }
433 return $data;
434 };
435
436 __PACKAGE__->register_method({
437 name => 'get_status',
438 path => 'status',
439 method => 'GET',
440 description => "Get cluster status informations.",
441 permissions => {
442 check => ['perm', '/', [ 'Sys.Audit' ]],
443 },
444 protected => 1,
445 parameters => {
446 additionalProperties => 0,
447 properties => {},
448 },
449 returns => {
450 type => 'array',
451 items => {
452 type => "object",
453 properties => {
454 type => {
455 type => 'string'
456 },
457 },
458 },
459 },
460 code => sub {
461 my ($param) = @_;
462
463 # we also add info from pmxcfs
464 my $clinfo = PVE::Cluster::get_clinfo();
465 my $members = PVE::Cluster::get_members();
466 my $nodename = PVE::INotify::nodename();
467 my $rrd = PVE::Cluster::rrd_dump();
468
469 if ($members) {
470 my $cmd = ['clustat', '-x'];
471 my $out = '';
472 PVE::Tools::run_command($cmd, outfunc => sub { $out .= shift; });
473 return &$parse_clustat($clinfo, $members, $rrd, $nodename, $out);
474 } else {
475 # fake entry for local node if no cluster defined
476 my $pmxcfs = ($clinfo && $clinfo->{version}) ? 1 : 0; # pmxcfs online ?
477
478 my $subinfo = PVE::INotify::read_file('subscription');
479 my $sublevel = $subinfo->{level} || '';
480
481 return [{
482 type => 'node',
483 id => "node/$nodename",
484 name => $nodename,
485 'local' => 1,
486 nodeid => 0,
487 pmxcfs => $pmxcfs,
488 state => 1,
489 level => $sublevel,
490 }];
491 }
492 }});
493
494 __PACKAGE__->register_method({
495 name => 'nextid',
496 path => 'nextid',
497 method => 'GET',
498 description => "Get next free VMID. If you pass an VMID it will raise an error if the ID is already used.",
499 permissions => { user => 'all' },
500 parameters => {
501 additionalProperties => 0,
502 properties => {
503 vmid => get_standard_option('pve-vmid', {optional => 1}),
504 },
505 },
506 returns => {
507 type => 'integer',
508 description => "The next free VMID.",
509 },
510 code => sub {
511 my ($param) = @_;
512
513 my $vmlist = PVE::Cluster::get_vmlist() || {};
514 my $idlist = $vmlist->{ids} || {};
515
516 if (my $vmid = $param->{vmid}) {
517 return $vmid if !defined($idlist->{$vmid});
518 raise_param_exc({ vmid => "VM $vmid already exists" });
519 }
520
521 for (my $i = 100; $i < 10000; $i++) {
522 return $i if !defined($idlist->{$i});
523 }
524
525 die "unable to get any free VMID\n";
526 }});
527
528 1;