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