]> git.proxmox.com Git - pve-manager.git/blame - PVE/API2/Cluster.pm
api2 : nodes : sdn status endpoint
[pve-manager.git] / PVE / API2 / Cluster.pm
CommitLineData
aff192e6
DM
1package PVE::API2::Cluster;
2
3use strict;
4use warnings;
5
a35f2aff
TL
6use JSON;
7
8use PVE::API2Tools;
9use PVE::Cluster qw(cfs_register_file cfs_lock_file cfs_read_file cfs_write_file);
10cdf3ae 10use PVE::Exception qw(raise_param_exc);
a35f2aff
TL
11use PVE::Firewall;
12use PVE::HA::Config;
13use PVE::HA::Env::PVE2;
a06a3eac 14use PVE::INotify;
a35f2aff
TL
15use PVE::JSONSchema qw(get_standard_option);
16use PVE::RESTHandler;
17use PVE::RPCEnvironment;
18use PVE::SafeSyslog;
aff192e6 19use PVE::Storage;
a35f2aff
TL
20use PVE::Tools qw(extract_param);
21
22use PVE::API2::ACMEAccount;
ac27b58d 23use PVE::API2::Backup;
78ad23f8 24use PVE::API2::Cluster::Ceph;
ac04fb55 25use PVE::API2::ClusterConfig;
4a07fced 26use PVE::API2::Firewall::Cluster;
a35f2aff 27use PVE::API2::HAConfig;
892821fd 28use PVE::API2::ReplicationConfig;
aff192e6 29
f0f63a1c
AD
30my $have_sdn;
31eval {
32 require PVE::API2::Network::SDN;
33 $have_sdn = 1;
34};
35
aff192e6
DM
36use base qw(PVE::RESTHandler);
37
892821fd
DM
38__PACKAGE__->register_method ({
39 subclass => "PVE::API2::ReplicationConfig",
40 path => 'replication',
41});
42
ac04fb55
DM
43__PACKAGE__->register_method ({
44 subclass => "PVE::API2::ClusterConfig",
45 path => 'config',
46});
47
4a07fced
DM
48__PACKAGE__->register_method ({
49 subclass => "PVE::API2::Firewall::Cluster",
50 path => 'firewall',
51});
52
ac27b58d
DM
53__PACKAGE__->register_method ({
54 subclass => "PVE::API2::Backup",
55 path => 'backup',
56});
57
a06a3eac
DM
58__PACKAGE__->register_method ({
59 subclass => "PVE::API2::HAConfig",
60 path => 'ha',
61});
62
5c3fd6ac
FG
63__PACKAGE__->register_method ({
64 subclass => "PVE::API2::ACMEAccount",
65 path => 'acme',
66});
67
78ad23f8
TL
68__PACKAGE__->register_method ({
69 subclass => "PVE::API2::Cluster::Ceph",
70 path => 'ceph',
71});
72
f0f63a1c
AD
73if ($have_sdn) {
74 __PACKAGE__->register_method ({
75 subclass => "PVE::API2::Network::SDN",
76 path => 'sdn',
77 });
78}
79
aff192e6
DM
80my $dc_schema = PVE::Cluster::get_datacenter_schema();
81my $dc_properties = {
82 delete => {
83 type => 'string', format => 'pve-configid-list',
84 description => "A list of settings you want to delete.",
85 optional => 1,
86 }
87};
88foreach my $opt (keys %{$dc_schema->{properties}}) {
89 $dc_properties->{$opt} = $dc_schema->{properties}->{$opt};
90}
91
92__PACKAGE__->register_method ({
93 name => 'index',
94 path => '',
95 method => 'GET',
96 description => "Cluster index.",
97 permissions => { user => 'all' },
98 parameters => {
99 additionalProperties => 0,
100 properties => {},
101 },
102 returns => {
103 type => 'array',
104 items => {
105 type => "object",
106 properties => {},
107 },
108 links => [ { rel => 'child', href => "{name}" } ],
109 },
110 code => sub {
111 my ($param) = @_;
112
113 my $result = [
114 { name => 'log' },
115 { name => 'options' },
116 { name => 'resources' },
892821fd 117 { name => 'replication' },
aff192e6 118 { name => 'tasks' },
ac27b58d 119 { name => 'backup' },
a06a3eac 120 { name => 'ha' },
a0af0132 121 { name => 'status' },
10cdf3ae 122 { name => 'nextid' },
4a07fced 123 { name => 'firewall' },
ac04fb55 124 { name => 'config' },
5c3fd6ac 125 { name => 'acme' },
6e251e55 126 { name => 'ceph' },
aff192e6
DM
127 ];
128
f0f63a1c
AD
129 if ($have_sdn) {
130 push(@{$result}, { name => 'sdn' });
131 }
132
aff192e6
DM
133 return $result;
134 }});
135
136__PACKAGE__->register_method({
137 name => 'log',
138 path => 'log',
139 method => 'GET',
140 description => "Read cluster log",
141 permissions => { user => 'all' },
142 parameters => {
143 additionalProperties => 0,
144 properties => {
145 max => {
146 type => 'integer',
147 description => "Maximum number of entries.",
148 optional => 1,
149 minimum => 1,
150 }
151 },
152 },
153 returns => {
154 type => 'array',
155 items => {
156 type => "object",
157 properties => {},
158 },
159 },
160 code => sub {
161 my ($param) = @_;
162
163 my $rpcenv = PVE::RPCEnvironment::get();
164
165 my $max = $param->{max} || 0;
166 my $user = $rpcenv->get_user();
167
e4d554ba 168 my $admin = $rpcenv->check($user, "/", [ 'Sys.Syslog' ], 1);
aff192e6
DM
169
170 my $loguser = $admin ? '' : $user;
171
172 my $res = decode_json(PVE::Cluster::get_cluster_log($loguser, $max));
173
0c8d7402
DC
174 foreach my $entry (@{$res->{data}}) {
175 $entry->{id} = "$entry->{uid}:$entry->{node}";
176 }
177
aff192e6
DM
178 return $res->{data};
179 }});
180
181__PACKAGE__->register_method({
182 name => 'resources',
183 path => 'resources',
184 method => 'GET',
185 description => "Resources index (cluster wide).",
186 permissions => { user => 'all' },
187 parameters => {
188 additionalProperties => 0,
badcb8d1
DM
189 properties => {
190 type => {
191 type => 'string',
192 optional => 1,
afc237df 193 enum => ['vm', 'storage', 'node', 'sdn'],
badcb8d1
DM
194 },
195 },
aff192e6
DM
196 },
197 returns => {
198 type => 'array',
199 items => {
200 type => "object",
201 properties => {
fc6c0fdd
DM
202 id => { type => 'string' },
203 type => {
b66c604e 204 description => "Resource type.",
fc6c0fdd 205 type => 'string',
afc237df 206 enum => ['node', 'storage', 'pool', 'qemu', 'lxc', 'openvz', 'sdn'],
fc6c0fdd
DM
207 },
208 status => {
209 description => "Resource type dependent status.",
210 type => 'string',
211 optional => 1,
212 },
213 node => get_standard_option('pve-node', {
214 description => "The cluster node name (when type in node,storage,qemu,lxc).",
215 optional => 1,
216 }),
217 storage => get_standard_option('pve-storage-id', {
218 description => "The storage identifier (when type == storage).",
219 optional => 1,
220 }),
221 pool => {
222 description => "The pool name (when type in pool,qemu,lxc).",
223 type => 'string',
224 optional => 1,
225 },
226 cpu => {
227 description => "CPU utilization (when type in node,qemu,lxc).",
228 type => 'number',
229 optional => 1,
230 renderer => 'fraction_as_percentage',
231 },
232 maxcpu => {
233 description => "Number of available CPUs (when type in node,qemu,lxc).",
47f86553 234 type => 'number',
fc6c0fdd
DM
235 optional => 1,
236 },
237 mem => {
238 description => "Used memory in bytes (when type in node,qemu,lxc).",
239 type => 'string',
240 optional => 1,
241 renderer => 'bytes',
242 },
243 maxmem => {
244 description => "Number of available memory in bytes (when type in node,qemu,lxc).",
245 type => 'integer',
246 optional => 1,
247 renderer => 'bytes',
248 },
249 level => {
250 description => "Support level (when type == node).",
251 type => 'string',
252 optional => 1,
253 },
254 uptime => {
255 description => "Node uptime in seconds (when type in node,qemu,lxc).",
256 type => 'integer',
257 optional => 1,
258 renderer => 'duration',
259 },
260 hastate => {
261 description => "HA service status (for HA managed VMs).",
262 type => 'string',
263 optional => 1,
264 },
265 disk => {
266 description => "Used disk space in bytes (when type in storage), used root image spave for VMs (type in qemu,lxc).",
267 type => 'string',
268 optional => 1,
269 renderer => 'bytes',
270 },
271 maxdisk => {
272 description => "Storage size in bytes (when type in storage), root image size for VMs (type in qemu,lxc).",
273 type => 'integer',
274 optional => 1,
275 renderer => 'bytes',
276 },
aff192e6
DM
277 },
278 },
279 },
280 code => sub {
281 my ($param) = @_;
282
283 my $rpcenv = PVE::RPCEnvironment::get();
84916eb2
DM
284 my $authuser = $rpcenv->get_user();
285 my $usercfg = $rpcenv->{user_cfg};
aff192e6
DM
286
287 my $res = [];
288
bc7bff8e
DM
289 my $nodelist = PVE::Cluster::get_nodelist();
290 my $members = PVE::Cluster::get_members();
aff192e6
DM
291
292 my $rrd = PVE::Cluster::rrd_dump();
293
294 my $vmlist = PVE::Cluster::get_vmlist() || {};
295 my $idlist = $vmlist->{ids} || {};
296
b67dc872 297 my $hastatus = PVE::HA::Config::read_manager_status();
8ad1127a 298 my $haresources = PVE::HA::Config::read_resources_config();
c6e94f42
DC
299 my $hatypemap = {
300 'qemu' => 'vm',
301 'lxc' => 'ct'
302 };
303
84916eb2
DM
304 my $pooldata = {};
305 if (!$param->{type} || $param->{type} eq 'pool') {
306 foreach my $pool (keys %{$usercfg->{pools}}) {
307 my $d = $usercfg->{pools}->{$pool};
308
a285f014 309 next if !$rpcenv->check($authuser, "/pool/$pool", [ 'Pool.Allocate' ], 1);
84916eb2
DM
310
311 my $entry = {
312 id => "/pool/$pool",
313 pool => $pool,
314 type => 'pool',
315 };
316
317 $pooldata->{$pool} = $entry;
318
319 push @$res, $entry;
320 }
321 }
aff192e6
DM
322
323 # we try to generate 'numbers' by using "$X + 0"
badcb8d1 324 if (!$param->{type} || $param->{type} eq 'vm') {
f79372c0
TL
325 my $locked_vms = PVE::Cluster::get_guest_config_property('lock');
326
badcb8d1 327 foreach my $vmid (keys %$idlist) {
aff192e6 328
19a6b9f1
DM
329 my $data = $idlist->{$vmid};
330 my $entry = PVE::API2Tools::extract_vm_stats($vmid, $data, $rrd);
f79372c0
TL
331
332 if (defined(my $lock = $locked_vms->{$vmid}->{lock})) {
333 $entry->{lock} = $lock;
334 }
335
c608873a
DM
336 if (my $pool = $usercfg->{vms}->{$vmid}) {
337 $entry->{pool} = $pool;
338 if (my $pe = $pooldata->{$pool}) {
339 if ($entry->{uptime}) {
84916eb2
DM
340 $pe->{uptime} = $entry->{uptime} if !$pe->{uptime} || $entry->{uptime} > $pe->{uptime};
341 $pe->{mem} = 0 if !$pe->{mem};
342 $pe->{mem} += $entry->{mem};
343 $pe->{maxmem} = 0 if !$pe->{maxmem};
344 $pe->{maxmem} += $entry->{maxmem};
345 $pe->{cpu} = 0 if !$pe->{cpu};
c37f23f5 346 $pe->{maxcpu} = 0 if !$pe->{maxcpu};
ffe31eea
DC
347 # explanation:
348 # we do not know how much cpus there are in the cluster at this moment
349 # so we calculate the current % of the cpu
350 # but we had already the old cpu % before this vm, so:
351 # new% = (old%*oldmax + cur%*curmax) / (oldmax+curmax)
ffe31eea 352 $pe->{cpu} = (($pe->{cpu} * $pe->{maxcpu}) + ($entry->{cpu} * $entry->{maxcpu})) / ($pe->{maxcpu} + $entry->{maxcpu});
84916eb2
DM
353 $pe->{maxcpu} += $entry->{maxcpu};
354 }
355 }
bc7bff8e 356 }
badcb8d1 357
a285f014
DM
358 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
359
c6e94f42 360 # get ha status
b67dc872
DM
361 if (my $hatype = $hatypemap->{$entry->{type}}) {
362 my $sid = "$hatype:$vmid";
8ad1127a
TL
363 my $service;
364 if ($service = $hastatus->{service_status}->{$sid}) {
365 $entry->{hastate} = $service->{state};
366 } elsif ($service = $haresources->{ids}->{$sid}) {
367 $entry->{hastate} = $service->{state};
b67dc872 368 }
c6e94f42
DC
369 }
370
badcb8d1 371 push @$res, $entry;
aff192e6 372 }
aff192e6
DM
373 }
374
badcb8d1 375 if (!$param->{type} || $param->{type} eq 'node') {
bc7bff8e 376 foreach my $node (@$nodelist) {
57d56896
TL
377 my $can_audit = $rpcenv->check($authuser, "/nodes/$node", [ 'Sys.Audit' ], 1);
378 my $entry = PVE::API2Tools::extract_node_stats($node, $members, $rrd, !$can_audit);
aff192e6 379 push @$res, $entry;
badcb8d1
DM
380 }
381 }
aff192e6 382
badcb8d1
DM
383 if (!$param->{type} || $param->{type} eq 'storage') {
384
385 my $cfg = PVE::Storage::config();
386 my @sids = PVE::Storage::storage_ids ($cfg);
387
388 foreach my $storeid (@sids) {
84916eb2 389 next if !$rpcenv->check($authuser, "/storage/$storeid", [ 'Datastore.Audit' ], 1);
19a6b9f1
DM
390
391 my $scfg = PVE::Storage::storage_config($cfg, $storeid);
badcb8d1
DM
392 # we create a entry for each node
393 foreach my $node (@$nodelist) {
394 next if !PVE::Storage::storage_check_enabled($cfg, $storeid, $node, 1);
badcb8d1 395
19a6b9f1 396 my $entry = PVE::API2Tools::extract_storage_stats($storeid, $scfg, $node, $rrd);
badcb8d1
DM
397 push @$res, $entry;
398 }
aff192e6
DM
399 }
400 }
401
afc237df
AD
402 if($have_sdn) {
403 if (!$param->{type} || $param->{type} eq 'sdn') {
404
405 my $nodes = PVE::Cluster::get_node_kv("sdn");
406
407 foreach my $node (keys %{$nodes}) {
408 my $sdns = decode_json($nodes->{$node});
409
410 foreach my $id (keys %{$sdns}) {
411 my $sdn = $sdns->{$id};
412 #next if !$rpcenv->check($authuser, "/sdn/$id", [ 'SDN.Audit' ], 1);
413 my $entry = {
414 id => "sdn/$node/$id",
415 sdn => $id,
416 node => $node,
417 type => 'sdn',
418 status => $sdn->{'status'},
419 };
420 push @$res, $entry;
421 }
422 }
423 }
424 }
425
aff192e6
DM
426 return $res;
427 }});
428
429__PACKAGE__->register_method({
430 name => 'tasks',
431 path => 'tasks',
432 method => 'GET',
433 description => "List recent tasks (cluster wide).",
434 permissions => { user => 'all' },
435 parameters => {
436 additionalProperties => 0,
437 properties => {},
438 },
439 returns => {
440 type => 'array',
441 items => {
442 type => "object",
443 properties => {
444 upid => { type => 'string' },
445 },
446 },
447 },
448 code => sub {
449 my ($param) = @_;
450
451 my $rpcenv = PVE::RPCEnvironment::get();
84916eb2 452 my $authuser = $rpcenv->get_user();
aff192e6
DM
453
454 my $tlist = PVE::Cluster::get_tasklist();
455
456 my $res = [];
457
458 return $res if !$tlist;
459
84916eb2 460 my $all = $rpcenv->check($authuser, "/", [ 'Sys.Audit' ], 1);
aff192e6
DM
461
462 foreach my $task (@$tlist) {
84916eb2 463 push @$res, $task if $all || ($task->{user} eq $authuser);
aff192e6
DM
464 }
465
466 return $res;
467 }});
468
469__PACKAGE__->register_method({
470 name => 'get_options',
471 path => 'options',
472 method => 'GET',
473 description => "Get datacenter options.",
474 permissions => {
7d020b42 475 check => ['perm', '/', [ 'Sys.Audit' ]],
aff192e6
DM
476 },
477 parameters => {
478 additionalProperties => 0,
479 properties => {},
480 },
481 returns => {
482 type => "object",
483 properties => {},
484 },
485 code => sub {
486 my ($param) = @_;
449f1b5d 487
aff192e6
DM
488 return PVE::Cluster::cfs_read_file('datacenter.cfg');
489 }});
490
491__PACKAGE__->register_method({
492 name => 'set_options',
493 path => 'options',
494 method => 'PUT',
495 description => "Set datacenter options.",
496 permissions => {
7d020b42 497 check => ['perm', '/', [ 'Sys.Modify' ]],
aff192e6
DM
498 },
499 protected => 1,
500 parameters => {
501 additionalProperties => 0,
502 properties => $dc_properties,
503 },
504 returns => { type => "null" },
505 code => sub {
506 my ($param) = @_;
507
508 my $filename = 'datacenter.cfg';
509
510 my $delete = extract_param($param, 'delete');
511
512 my $code = sub {
513
514 my $conf = cfs_read_file($filename);
515
516 foreach my $opt (keys %$param) {
517 $conf->{$opt} = $param->{$opt};
518 }
519
520 foreach my $opt (PVE::Tools::split_list($delete)) {
521 delete $conf->{$opt};
522 };
523
524 cfs_write_file($filename, $conf);
525 };
526
527 cfs_lock_file($filename, undef, $code);
528 die $@ if $@;
529
530 return undef;
531 }});
532
a0af0132
DM
533__PACKAGE__->register_method({
534 name => 'get_status',
535 path => 'status',
536 method => 'GET',
76189130 537 description => "Get cluster status information.",
449f1b5d
DM
538 permissions => {
539 check => ['perm', '/', [ 'Sys.Audit' ]],
540 },
a0af0132
DM
541 protected => 1,
542 parameters => {
543 additionalProperties => 0,
544 properties => {},
545 },
546 returns => {
547 type => 'array',
548 items => {
549 type => "object",
550 properties => {
551 type => {
552 type => 'string'
553 },
554 },
555 },
556 },
557 code => sub {
558 my ($param) = @_;
559
e1c20e2a
FG
560 # make sure we get current info
561 PVE::Cluster::cfs_update();
562
a0af0132
DM
563 # we also add info from pmxcfs
564 my $clinfo = PVE::Cluster::get_clinfo();
565 my $members = PVE::Cluster::get_members();
566 my $nodename = PVE::INotify::nodename();
16b69b6c 567 my $rrd = PVE::Cluster::rrd_dump();
a0af0132
DM
568
569 if ($members) {
91d7c7aa
DM
570 my $res = [];
571
572 if (my $d = $clinfo->{cluster}) {
573 push @$res, {
574 type => 'cluster',
575 id => 'cluster',
576 nodes => $d->{nodes},
577 version => $d->{version},
578 name => $d->{name},
579 quorate => $d->{quorate},
580 };
581 }
582
583 foreach my $node (keys %$members) {
584 my $d = $members->{$node};
585 my $entry = {
586 type => 'node',
587 id => "node/$node",
588 name => $node,
589 nodeid => $d->{id},
590 ip => $d->{ip},
591 'local' => ($node eq $nodename) ? 1 : 0,
592 online => $d->{online},
593 };
594
595 if (my $d = PVE::API2Tools::extract_node_stats($node, $members, $rrd)) {
596 $entry->{level} = $d->{level};
597 }
598
599 push @$res, $entry;
600 }
601 return $res;
a0af0132
DM
602 } else {
603 # fake entry for local node if no cluster defined
604 my $pmxcfs = ($clinfo && $clinfo->{version}) ? 1 : 0; # pmxcfs online ?
16b69b6c
DM
605
606 my $subinfo = PVE::INotify::read_file('subscription');
607 my $sublevel = $subinfo->{level} || '';
608
a0af0132
DM
609 return [{
610 type => 'node',
611 id => "node/$nodename",
612 name => $nodename,
53012285 613 ip => scalar(PVE::Cluster::remote_node_ip($nodename)),
a0af0132
DM
614 'local' => 1,
615 nodeid => 0,
91d7c7aa 616 online => 1,
16b69b6c 617 level => $sublevel,
a0af0132
DM
618 }];
619 }
620 }});
621
10cdf3ae
DM
622__PACKAGE__->register_method({
623 name => 'nextid',
624 path => 'nextid',
625 method => 'GET',
626 description => "Get next free VMID. If you pass an VMID it will raise an error if the ID is already used.",
627 permissions => { user => 'all' },
628 parameters => {
629 additionalProperties => 0,
630 properties => {
631 vmid => get_standard_option('pve-vmid', {optional => 1}),
632 },
633 },
634 returns => {
635 type => 'integer',
636 description => "The next free VMID.",
637 },
638 code => sub {
639 my ($param) = @_;
640
641 my $vmlist = PVE::Cluster::get_vmlist() || {};
642 my $idlist = $vmlist->{ids} || {};
643
644 if (my $vmid = $param->{vmid}) {
645 return $vmid if !defined($idlist->{$vmid});
646 raise_param_exc({ vmid => "VM $vmid already exists" });
647 }
648
649 for (my $i = 100; $i < 10000; $i++) {
650 return $i if !defined($idlist->{$i});
651 }
652
653 die "unable to get any free VMID\n";
654 }});
655
aff192e6 6561;