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