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