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