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