]> git.proxmox.com Git - pve-manager.git/blame - PVE/API2/Cluster.pm
fix #1030: calculate correct cpu usage of pools
[pve-manager.git] / PVE / API2 / Cluster.pm
CommitLineData
aff192e6
DM
1package PVE::API2::Cluster;
2
3use strict;
4use warnings;
5
a0af0132
DM
6use XML::Parser;
7
aff192e6
DM
8use PVE::SafeSyslog;
9use PVE::Tools qw(extract_param);
10cdf3ae 10use PVE::Exception qw(raise_param_exc);
a06a3eac 11use PVE::INotify;
7cdf443c 12use PVE::Cluster qw(cfs_register_file cfs_lock_file cfs_read_file cfs_write_file);
aff192e6 13use PVE::Storage;
19a6b9f1 14use PVE::API2Tools;
ac27b58d 15use PVE::API2::Backup;
a06a3eac 16use PVE::API2::HAConfig;
aff192e6 17use JSON;
aff192e6
DM
18use PVE::RESTHandler;
19use PVE::RPCEnvironment;
10cdf3ae 20use PVE::JSONSchema qw(get_standard_option);
4a07fced
DM
21use PVE::Firewall;
22use PVE::API2::Firewall::Cluster;
aff192e6
DM
23
24use base qw(PVE::RESTHandler);
25
4a07fced
DM
26__PACKAGE__->register_method ({
27 subclass => "PVE::API2::Firewall::Cluster",
28 path => 'firewall',
29});
30
ac27b58d
DM
31__PACKAGE__->register_method ({
32 subclass => "PVE::API2::Backup",
33 path => 'backup',
34});
35
a06a3eac
DM
36__PACKAGE__->register_method ({
37 subclass => "PVE::API2::HAConfig",
38 path => 'ha',
39});
40
aff192e6
DM
41my $dc_schema = PVE::Cluster::get_datacenter_schema();
42my $dc_properties = {
43 delete => {
44 type => 'string', format => 'pve-configid-list',
45 description => "A list of settings you want to delete.",
46 optional => 1,
47 }
48};
49foreach my $opt (keys %{$dc_schema->{properties}}) {
50 $dc_properties->{$opt} = $dc_schema->{properties}->{$opt};
51}
52
53__PACKAGE__->register_method ({
54 name => 'index',
55 path => '',
56 method => 'GET',
57 description => "Cluster index.",
58 permissions => { user => 'all' },
59 parameters => {
60 additionalProperties => 0,
61 properties => {},
62 },
63 returns => {
64 type => 'array',
65 items => {
66 type => "object",
67 properties => {},
68 },
69 links => [ { rel => 'child', href => "{name}" } ],
70 },
71 code => sub {
72 my ($param) = @_;
73
74 my $result = [
75 { name => 'log' },
76 { name => 'options' },
77 { name => 'resources' },
78 { name => 'tasks' },
ac27b58d 79 { name => 'backup' },
a06a3eac 80 { name => 'ha' },
a0af0132 81 { name => 'status' },
10cdf3ae 82 { name => 'nextid' },
4a07fced 83 { name => 'firewall' },
aff192e6
DM
84 ];
85
86 return $result;
87 }});
88
89__PACKAGE__->register_method({
90 name => 'log',
91 path => 'log',
92 method => 'GET',
93 description => "Read cluster log",
94 permissions => { user => 'all' },
95 parameters => {
96 additionalProperties => 0,
97 properties => {
98 max => {
99 type => 'integer',
100 description => "Maximum number of entries.",
101 optional => 1,
102 minimum => 1,
103 }
104 },
105 },
106 returns => {
107 type => 'array',
108 items => {
109 type => "object",
110 properties => {},
111 },
112 },
113 code => sub {
114 my ($param) = @_;
115
116 my $rpcenv = PVE::RPCEnvironment::get();
117
118 my $max = $param->{max} || 0;
119 my $user = $rpcenv->get_user();
120
e4d554ba 121 my $admin = $rpcenv->check($user, "/", [ 'Sys.Syslog' ], 1);
aff192e6
DM
122
123 my $loguser = $admin ? '' : $user;
124
125 my $res = decode_json(PVE::Cluster::get_cluster_log($loguser, $max));
126
127 return $res->{data};
128 }});
129
130__PACKAGE__->register_method({
131 name => 'resources',
132 path => 'resources',
133 method => 'GET',
134 description => "Resources index (cluster wide).",
135 permissions => { user => 'all' },
136 parameters => {
137 additionalProperties => 0,
badcb8d1
DM
138 properties => {
139 type => {
140 type => 'string',
141 optional => 1,
142 enum => ['vm', 'storage', 'node'],
143 },
144 },
aff192e6
DM
145 },
146 returns => {
147 type => 'array',
148 items => {
149 type => "object",
150 properties => {
151 },
152 },
153 },
154 code => sub {
155 my ($param) = @_;
156
157 my $rpcenv = PVE::RPCEnvironment::get();
84916eb2
DM
158 my $authuser = $rpcenv->get_user();
159 my $usercfg = $rpcenv->{user_cfg};
aff192e6
DM
160
161 my $res = [];
162
bc7bff8e
DM
163 my $nodelist = PVE::Cluster::get_nodelist();
164 my $members = PVE::Cluster::get_members();
aff192e6
DM
165
166 my $rrd = PVE::Cluster::rrd_dump();
167
168 my $vmlist = PVE::Cluster::get_vmlist() || {};
169 my $idlist = $vmlist->{ids} || {};
170
84916eb2
DM
171 my $pooldata = {};
172 if (!$param->{type} || $param->{type} eq 'pool') {
173 foreach my $pool (keys %{$usercfg->{pools}}) {
174 my $d = $usercfg->{pools}->{$pool};
175
a285f014 176 next if !$rpcenv->check($authuser, "/pool/$pool", [ 'Pool.Allocate' ], 1);
84916eb2
DM
177
178 my $entry = {
179 id => "/pool/$pool",
180 pool => $pool,
181 type => 'pool',
182 };
183
184 $pooldata->{$pool} = $entry;
185
186 push @$res, $entry;
187 }
188 }
aff192e6
DM
189
190 # we try to generate 'numbers' by using "$X + 0"
badcb8d1
DM
191 if (!$param->{type} || $param->{type} eq 'vm') {
192 foreach my $vmid (keys %$idlist) {
aff192e6 193
19a6b9f1
DM
194 my $data = $idlist->{$vmid};
195 my $entry = PVE::API2Tools::extract_vm_stats($vmid, $data, $rrd);
c608873a
DM
196 if (my $pool = $usercfg->{vms}->{$vmid}) {
197 $entry->{pool} = $pool;
198 if (my $pe = $pooldata->{$pool}) {
199 if ($entry->{uptime}) {
84916eb2
DM
200 $pe->{uptime} = $entry->{uptime} if !$pe->{uptime} || $entry->{uptime} > $pe->{uptime};
201 $pe->{mem} = 0 if !$pe->{mem};
202 $pe->{mem} += $entry->{mem};
203 $pe->{maxmem} = 0 if !$pe->{maxmem};
204 $pe->{maxmem} += $entry->{maxmem};
205 $pe->{cpu} = 0 if !$pe->{cpu};
ffe31eea
DC
206 # explanation:
207 # we do not know how much cpus there are in the cluster at this moment
208 # so we calculate the current % of the cpu
209 # but we had already the old cpu % before this vm, so:
210 # new% = (old%*oldmax + cur%*curmax) / (oldmax+curmax)
211 $pe->{cpu} = $entry->{cpu} if !$pe->{maxcpu};
212 $pe->{cpu} = (($pe->{cpu} * $pe->{maxcpu}) + ($entry->{cpu} * $entry->{maxcpu})) / ($pe->{maxcpu} + $entry->{maxcpu});
84916eb2
DM
213 $pe->{maxcpu} = 0 if !$pe->{maxcpu};
214 $pe->{maxcpu} += $entry->{maxcpu};
215 }
216 }
bc7bff8e 217 }
badcb8d1 218
a285f014
DM
219 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
220
badcb8d1 221 push @$res, $entry;
aff192e6 222 }
aff192e6
DM
223 }
224
badcb8d1 225 if (!$param->{type} || $param->{type} eq 'node') {
bc7bff8e 226 foreach my $node (@$nodelist) {
16b69b6c 227 my $entry = PVE::API2Tools::extract_node_stats($node, $members, $rrd);
aff192e6 228 push @$res, $entry;
badcb8d1
DM
229 }
230 }
aff192e6 231
badcb8d1
DM
232 if (!$param->{type} || $param->{type} eq 'storage') {
233
234 my $cfg = PVE::Storage::config();
235 my @sids = PVE::Storage::storage_ids ($cfg);
236
237 foreach my $storeid (@sids) {
84916eb2 238 next if !$rpcenv->check($authuser, "/storage/$storeid", [ 'Datastore.Audit' ], 1);
19a6b9f1
DM
239
240 my $scfg = PVE::Storage::storage_config($cfg, $storeid);
badcb8d1
DM
241 # we create a entry for each node
242 foreach my $node (@$nodelist) {
243 next if !PVE::Storage::storage_check_enabled($cfg, $storeid, $node, 1);
badcb8d1 244
19a6b9f1 245 my $entry = PVE::API2Tools::extract_storage_stats($storeid, $scfg, $node, $rrd);
badcb8d1
DM
246 push @$res, $entry;
247 }
aff192e6
DM
248 }
249 }
250
251 return $res;
252 }});
253
254__PACKAGE__->register_method({
255 name => 'tasks',
256 path => 'tasks',
257 method => 'GET',
258 description => "List recent tasks (cluster wide).",
259 permissions => { user => 'all' },
260 parameters => {
261 additionalProperties => 0,
262 properties => {},
263 },
264 returns => {
265 type => 'array',
266 items => {
267 type => "object",
268 properties => {
269 upid => { type => 'string' },
270 },
271 },
272 },
273 code => sub {
274 my ($param) = @_;
275
276 my $rpcenv = PVE::RPCEnvironment::get();
84916eb2 277 my $authuser = $rpcenv->get_user();
aff192e6
DM
278
279 my $tlist = PVE::Cluster::get_tasklist();
280
281 my $res = [];
282
283 return $res if !$tlist;
284
84916eb2 285 my $all = $rpcenv->check($authuser, "/", [ 'Sys.Audit' ], 1);
aff192e6
DM
286
287 foreach my $task (@$tlist) {
84916eb2 288 push @$res, $task if $all || ($task->{user} eq $authuser);
aff192e6
DM
289 }
290
291 return $res;
292 }});
293
294__PACKAGE__->register_method({
295 name => 'get_options',
296 path => 'options',
297 method => 'GET',
298 description => "Get datacenter options.",
299 permissions => {
7d020b42 300 check => ['perm', '/', [ 'Sys.Audit' ]],
aff192e6
DM
301 },
302 parameters => {
303 additionalProperties => 0,
304 properties => {},
305 },
306 returns => {
307 type => "object",
308 properties => {},
309 },
310 code => sub {
311 my ($param) = @_;
449f1b5d 312
aff192e6
DM
313 return PVE::Cluster::cfs_read_file('datacenter.cfg');
314 }});
315
316__PACKAGE__->register_method({
317 name => 'set_options',
318 path => 'options',
319 method => 'PUT',
320 description => "Set datacenter options.",
321 permissions => {
7d020b42 322 check => ['perm', '/', [ 'Sys.Modify' ]],
aff192e6
DM
323 },
324 protected => 1,
325 parameters => {
326 additionalProperties => 0,
327 properties => $dc_properties,
328 },
329 returns => { type => "null" },
330 code => sub {
331 my ($param) = @_;
332
333 my $filename = 'datacenter.cfg';
334
335 my $delete = extract_param($param, 'delete');
336
337 my $code = sub {
338
339 my $conf = cfs_read_file($filename);
340
341 foreach my $opt (keys %$param) {
342 $conf->{$opt} = $param->{$opt};
343 }
344
345 foreach my $opt (PVE::Tools::split_list($delete)) {
346 delete $conf->{$opt};
347 };
348
349 cfs_write_file($filename, $conf);
350 };
351
352 cfs_lock_file($filename, undef, $code);
353 die $@ if $@;
354
355 return undef;
356 }});
357
a0af0132
DM
358__PACKAGE__->register_method({
359 name => 'get_status',
360 path => 'status',
361 method => 'GET',
362 description => "Get cluster status informations.",
449f1b5d
DM
363 permissions => {
364 check => ['perm', '/', [ 'Sys.Audit' ]],
365 },
a0af0132
DM
366 protected => 1,
367 parameters => {
368 additionalProperties => 0,
369 properties => {},
370 },
371 returns => {
372 type => 'array',
373 items => {
374 type => "object",
375 properties => {
376 type => {
377 type => 'string'
378 },
379 },
380 },
381 },
382 code => sub {
383 my ($param) = @_;
384
385 # we also add info from pmxcfs
386 my $clinfo = PVE::Cluster::get_clinfo();
387 my $members = PVE::Cluster::get_members();
388 my $nodename = PVE::INotify::nodename();
16b69b6c 389 my $rrd = PVE::Cluster::rrd_dump();
a0af0132
DM
390
391 if ($members) {
91d7c7aa
DM
392 my $res = [];
393
394 if (my $d = $clinfo->{cluster}) {
395 push @$res, {
396 type => 'cluster',
397 id => 'cluster',
398 nodes => $d->{nodes},
399 version => $d->{version},
400 name => $d->{name},
401 quorate => $d->{quorate},
402 };
403 }
404
405 foreach my $node (keys %$members) {
406 my $d = $members->{$node};
407 my $entry = {
408 type => 'node',
409 id => "node/$node",
410 name => $node,
411 nodeid => $d->{id},
412 ip => $d->{ip},
413 'local' => ($node eq $nodename) ? 1 : 0,
414 online => $d->{online},
415 };
416
417 if (my $d = PVE::API2Tools::extract_node_stats($node, $members, $rrd)) {
418 $entry->{level} = $d->{level};
419 }
420
421 push @$res, $entry;
422 }
423 return $res;
a0af0132
DM
424 } else {
425 # fake entry for local node if no cluster defined
426 my $pmxcfs = ($clinfo && $clinfo->{version}) ? 1 : 0; # pmxcfs online ?
16b69b6c
DM
427
428 my $subinfo = PVE::INotify::read_file('subscription');
429 my $sublevel = $subinfo->{level} || '';
430
a0af0132
DM
431 return [{
432 type => 'node',
433 id => "node/$nodename",
434 name => $nodename,
53012285 435 ip => scalar(PVE::Cluster::remote_node_ip($nodename)),
a0af0132
DM
436 'local' => 1,
437 nodeid => 0,
91d7c7aa 438 online => 1,
16b69b6c 439 level => $sublevel,
a0af0132
DM
440 }];
441 }
442 }});
443
10cdf3ae
DM
444__PACKAGE__->register_method({
445 name => 'nextid',
446 path => 'nextid',
447 method => 'GET',
448 description => "Get next free VMID. If you pass an VMID it will raise an error if the ID is already used.",
449 permissions => { user => 'all' },
450 parameters => {
451 additionalProperties => 0,
452 properties => {
453 vmid => get_standard_option('pve-vmid', {optional => 1}),
454 },
455 },
456 returns => {
457 type => 'integer',
458 description => "The next free VMID.",
459 },
460 code => sub {
461 my ($param) = @_;
462
463 my $vmlist = PVE::Cluster::get_vmlist() || {};
464 my $idlist = $vmlist->{ids} || {};
465
466 if (my $vmid = $param->{vmid}) {
467 return $vmid if !defined($idlist->{$vmid});
468 raise_param_exc({ vmid => "VM $vmid already exists" });
469 }
470
471 for (my $i = 100; $i < 10000; $i++) {
472 return $i if !defined($idlist->{$i});
473 }
474
475 die "unable to get any free VMID\n";
476 }});
477
aff192e6 4781;