]>
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); | |
3ac3653e | 10 | use PVE::DataCenterConfig; |
10cdf3ae | 11 | use PVE::Exception qw(raise_param_exc); |
a35f2aff TL |
12 | use PVE::Firewall; |
13 | use PVE::HA::Config; | |
14 | use PVE::HA::Env::PVE2; | |
a06a3eac | 15 | use PVE::INotify; |
a35f2aff TL |
16 | use PVE::JSONSchema qw(get_standard_option); |
17 | use PVE::RESTHandler; | |
18 | use PVE::RPCEnvironment; | |
19 | use PVE::SafeSyslog; | |
aff192e6 | 20 | use PVE::Storage; |
a35f2aff TL |
21 | use PVE::Tools qw(extract_param); |
22 | ||
23 | use PVE::API2::ACMEAccount; | |
b227e9d2 | 24 | use PVE::API2::ACMEPlugin; |
ac27b58d | 25 | use PVE::API2::Backup; |
f26c7b54 | 26 | use PVE::API2::BackupInfo; |
78ad23f8 | 27 | use PVE::API2::Cluster::Ceph; |
ac04fb55 | 28 | use PVE::API2::ClusterConfig; |
4a07fced | 29 | use PVE::API2::Firewall::Cluster; |
a35f2aff | 30 | use PVE::API2::HAConfig; |
892821fd | 31 | use PVE::API2::ReplicationConfig; |
aff192e6 | 32 | |
f0f63a1c AD |
33 | my $have_sdn; |
34 | eval { | |
35 | require PVE::API2::Network::SDN; | |
36 | $have_sdn = 1; | |
37 | }; | |
38 | ||
aff192e6 DM |
39 | use 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 |
81 | if ($have_sdn) { |
82 | __PACKAGE__->register_method ({ | |
83 | subclass => "PVE::API2::Network::SDN", | |
84 | path => 'sdn', | |
85 | }); | |
86 | } | |
87 | ||
3ac3653e | 88 | my $dc_schema = PVE::DataCenterConfig::get_datacenter_schema(); |
cd8d0de2 | 89 | my $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 | }; | |
96 | foreach 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 | 718 | 1; |