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