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