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