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