]>
Commit | Line | Data |
---|---|---|
aff192e6 DM |
1 | package PVE::API2::Cluster; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
a35f2aff TL |
6 | use JSON; |
7 | ||
8 | use PVE::API2Tools; | |
9 | use PVE::Cluster qw(cfs_register_file cfs_lock_file cfs_read_file cfs_write_file); | |
10cdf3ae | 10 | use PVE::Exception qw(raise_param_exc); |
a35f2aff TL |
11 | use PVE::Firewall; |
12 | use PVE::HA::Config; | |
13 | use PVE::HA::Env::PVE2; | |
a06a3eac | 14 | use PVE::INotify; |
a35f2aff TL |
15 | use PVE::JSONSchema qw(get_standard_option); |
16 | use PVE::RESTHandler; | |
17 | use PVE::RPCEnvironment; | |
18 | use PVE::SafeSyslog; | |
aff192e6 | 19 | use PVE::Storage; |
a35f2aff TL |
20 | use PVE::Tools qw(extract_param); |
21 | ||
22 | use PVE::API2::ACMEAccount; | |
ac27b58d | 23 | use PVE::API2::Backup; |
78ad23f8 | 24 | use PVE::API2::Cluster::Ceph; |
ac04fb55 | 25 | use PVE::API2::ClusterConfig; |
4a07fced | 26 | use PVE::API2::Firewall::Cluster; |
a35f2aff | 27 | use PVE::API2::HAConfig; |
892821fd | 28 | use PVE::API2::ReplicationConfig; |
aff192e6 | 29 | |
f0f63a1c AD |
30 | my $have_sdn; |
31 | eval { | |
32 | require PVE::API2::Network::SDN; | |
33 | $have_sdn = 1; | |
34 | }; | |
35 | ||
aff192e6 DM |
36 | use 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 |
73 | if ($have_sdn) { |
74 | __PACKAGE__->register_method ({ | |
75 | subclass => "PVE::API2::Network::SDN", | |
76 | path => 'sdn', | |
77 | }); | |
78 | } | |
79 | ||
aff192e6 DM |
80 | my $dc_schema = PVE::Cluster::get_datacenter_schema(); |
81 | my $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 | }; | |
88 | foreach 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 | 656 | 1; |