]> git.proxmox.com Git - pve-manager.git/blob - PVE/API2/Cluster.pm
0d2d3b3dfd3b1519548ec1970db7f8aae8f91055
[pve-manager.git] / PVE / API2 / Cluster.pm
1 package PVE::API2::Cluster;
2
3 use strict;
4 use warnings;
5
6 use PVE::SafeSyslog;
7 use PVE::Tools qw(extract_param);
8 use PVE::Cluster qw(cfs_register_file cfs_lock_file cfs_read_file cfs_write_file);
9 use PVE::Storage;
10 use JSON;
11 use PVE::API2::VZDump;
12
13
14 use Data::Dumper; # fixme: remove
15
16 use Apache2::Const qw(:http);
17
18 use PVE::RESTHandler;
19 use PVE::RPCEnvironment;
20
21 use base qw(PVE::RESTHandler);
22
23 my $dc_schema = PVE::Cluster::get_datacenter_schema();
24 my $dc_properties = {
25 delete => {
26 type => 'string', format => 'pve-configid-list',
27 description => "A list of settings you want to delete.",
28 optional => 1,
29 }
30 };
31 foreach my $opt (keys %{$dc_schema->{properties}}) {
32 $dc_properties->{$opt} = $dc_schema->{properties}->{$opt};
33 }
34
35 __PACKAGE__->register_method ({
36 name => 'index',
37 path => '',
38 method => 'GET',
39 description => "Cluster index.",
40 permissions => { user => 'all' },
41 parameters => {
42 additionalProperties => 0,
43 properties => {},
44 },
45 returns => {
46 type => 'array',
47 items => {
48 type => "object",
49 properties => {},
50 },
51 links => [ { rel => 'child', href => "{name}" } ],
52 },
53 code => sub {
54 my ($param) = @_;
55
56 my $result = [
57 { name => 'log' },
58 { name => 'options' },
59 { name => 'resources' },
60 { name => 'tasks' },
61 { name => 'vzdump' },
62 ];
63
64 return $result;
65 }});
66
67 __PACKAGE__->register_method({
68 name => 'log',
69 path => 'log',
70 method => 'GET',
71 description => "Read cluster log",
72 permissions => { user => 'all' },
73 parameters => {
74 additionalProperties => 0,
75 properties => {
76 max => {
77 type => 'integer',
78 description => "Maximum number of entries.",
79 optional => 1,
80 minimum => 1,
81 }
82 },
83 },
84 returns => {
85 type => 'array',
86 items => {
87 type => "object",
88 properties => {},
89 },
90 },
91 code => sub {
92 my ($param) = @_;
93
94 my $rpcenv = PVE::RPCEnvironment::get();
95
96 my $max = $param->{max} || 0;
97 my $user = $rpcenv->get_user();
98
99 my $admin = $rpcenv->check($user, "/", [ 'Sys.Syslog' ]);
100
101 my $loguser = $admin ? '' : $user;
102
103 my $res = decode_json(PVE::Cluster::get_cluster_log($loguser, $max));
104
105 return $res->{data};
106 }});
107
108 __PACKAGE__->register_method({
109 name => 'resources',
110 path => 'resources',
111 method => 'GET',
112 description => "Resources index (cluster wide).",
113 permissions => { user => 'all' },
114 parameters => {
115 additionalProperties => 0,
116 properties => {},
117 },
118 returns => {
119 type => 'array',
120 items => {
121 type => "object",
122 properties => {
123 },
124 },
125 },
126 code => sub {
127 my ($param) = @_;
128
129 my $rpcenv = PVE::RPCEnvironment::get();
130 my $user = $rpcenv->get_user();
131
132 my $res = [];
133
134 my $nodelist = PVE::Cluster::get_nodelist();
135 my $members = PVE::Cluster::get_members();
136
137 my $rrd = PVE::Cluster::rrd_dump();
138
139 my $vmlist = PVE::Cluster::get_vmlist() || {};
140 my $idlist = $vmlist->{ids} || {};
141
142
143 # we try to generate 'numbers' by using "$X + 0"
144 foreach my $vmid (keys %$idlist) {
145 my $data = $idlist->{$vmid};
146
147 next if !$rpcenv->check($user, "/vms/$vmid", [ 'VM.Audit' ]);
148
149 my $entry = {
150 id => "$data->{type}/$vmid",
151 vmid => $vmid + 0,
152 node => $data->{node},
153 type => $data->{type},
154 };
155
156 if (my $d = $rrd->{"pve2-vm/$vmid"}) {
157
158 $entry->{uptime} = ($d->[0] || 0) + 0;
159 $entry->{name} = $d->[1];
160
161 $entry->{maxcpu} = ($d->[3] || 0) + 0;
162 $entry->{cpu} = ($d->[4] || 0) + 0;
163 $entry->{maxmem} = ($d->[5] || 0) + 0;
164 $entry->{mem} = ($d->[6] || 0) + 0;
165 $entry->{maxdisk} = ($d->[7] || 0) + 0;
166 $entry->{disk} = ($d->[8] || 0) + 0;
167 }
168
169 push @$res, $entry;
170 }
171
172 foreach my $node (@$nodelist) {
173 my $entry = {
174 id => "node/$node",
175 node => $node,
176 type => "node",
177 };
178 if (my $d = $rrd->{"pve2-node/$node"}) {
179
180 if (!$members || # no cluster
181 ($members->{$node} && $members->{$node}->{online})) {
182 $entry->{uptime} = ($d->[0] || 0) + 0;
183 $entry->{cpu} = ($d->[4] || 0) + 0;
184 $entry->{mem} = ($d->[7] || 0) + 0;
185 $entry->{disk} = ($d->[11] || 0) + 0;
186 }
187
188 $entry->{maxcpu} = ($d->[3] || 0) + 0;
189 $entry->{maxmem} = ($d->[6] || 0) + 0;
190 $entry->{maxdisk} = ($d->[10] || 0) + 0;
191 }
192
193
194 push @$res, $entry;
195 }
196
197 my $cfg = PVE::Storage::config();
198 my @sids = PVE::Storage::storage_ids ($cfg);
199
200 foreach my $storeid (@sids) {
201 my $scfg = PVE::Storage::storage_config($cfg, $storeid);
202 next if !$rpcenv->check($user, "/storage/$storeid", [ 'Datastore.Audit' ]);
203 # we create a entry for each node
204 foreach my $node (@$nodelist) {
205 next if !PVE::Storage::storage_check_enabled($cfg, $storeid, $node, 1);
206 my $entry = {
207 id => "storage/$node/$storeid",
208 storage => $storeid,
209 node => $node,
210 type => 'storage',
211 };
212
213 if (my $d = $rrd->{"pve2-storage/$node/$storeid"}) {
214 $entry->{maxdisk} = ($d->[1] || 0) + 0;
215 $entry->{disk} = ($d->[2] || 0) + 0;
216 }
217
218 push @$res, $entry;
219
220 }
221 }
222
223 return $res;
224 }});
225
226 __PACKAGE__->register_method({
227 name => 'tasks',
228 path => 'tasks',
229 method => 'GET',
230 description => "List recent tasks (cluster wide).",
231 permissions => { user => 'all' },
232 parameters => {
233 additionalProperties => 0,
234 properties => {},
235 },
236 returns => {
237 type => 'array',
238 items => {
239 type => "object",
240 properties => {
241 upid => { type => 'string' },
242 },
243 },
244 },
245 code => sub {
246 my ($param) = @_;
247
248 my $rpcenv = PVE::RPCEnvironment::get();
249 my $user = $rpcenv->get_user();
250
251 my $tlist = PVE::Cluster::get_tasklist();
252
253 my $res = [];
254
255 return $res if !$tlist;
256
257 my $all = $rpcenv->check($user, "/", [ 'Sys.Audit' ]);
258
259 foreach my $task (@$tlist) {
260 push @$res, $task if $all || ($task->{user} eq $user);
261 }
262
263 return $res;
264 }});
265
266 __PACKAGE__->register_method({
267 name => 'get_options',
268 path => 'options',
269 method => 'GET',
270 description => "Get datacenter options.",
271 permissions => {
272 path => '/',
273 privs => [ 'Sys.Audit' ],
274 },
275 parameters => {
276 additionalProperties => 0,
277 properties => {},
278 },
279 returns => {
280 type => "object",
281 properties => {},
282 },
283 code => sub {
284 my ($param) = @_;
285 return PVE::Cluster::cfs_read_file('datacenter.cfg');
286 }});
287
288 __PACKAGE__->register_method({
289 name => 'set_options',
290 path => 'options',
291 method => 'PUT',
292 description => "Set datacenter options.",
293 permissions => {
294 path => '/',
295 privs => [ 'Sys.Modify' ],
296 },
297 protected => 1,
298 parameters => {
299 additionalProperties => 0,
300 properties => $dc_properties,
301 },
302 returns => { type => "null" },
303 code => sub {
304 my ($param) = @_;
305
306 my $filename = 'datacenter.cfg';
307
308 my $delete = extract_param($param, 'delete');
309
310 my $code = sub {
311
312 my $conf = cfs_read_file($filename);
313
314 foreach my $opt (keys %$param) {
315 $conf->{$opt} = $param->{$opt};
316 }
317
318 foreach my $opt (PVE::Tools::split_list($delete)) {
319 delete $conf->{$opt};
320 };
321
322 cfs_write_file($filename, $conf);
323 };
324
325 cfs_lock_file($filename, undef, $code);
326 die $@ if $@;
327
328 return undef;
329 }});
330
331 cfs_register_file ('vzdump',
332 \&parse_config,
333 \&write_config);
334
335 my $vzdump_method_info = PVE::API2::VZDump->map_method_by_name('vzdump');
336
337 my $dowhash_to_dow = sub {
338 my ($d, $num) = @_;
339
340 my @da = ();
341 push @da, $num ? 1 : 'mon' if $d->{mon};
342 push @da, $num ? 2 : 'tue' if $d->{tue};
343 push @da, $num ? 3 : 'wed' if $d->{wed};
344 push @da, $num ? 4 : 'thu' if $d->{thu};
345 push @da, $num ? 5 : 'fri' if $d->{fri};
346 push @da, $num ? 6 : 'sat' if $d->{sat};
347 push @da, $num ? 7 : 'sun' if $d->{sun};
348
349 return join ',', @da;
350 };
351
352 sub parse_dow {
353 my ($dowstr, $noerr) = @_;
354
355 my $dowmap = {mon => 1, tue => 2, wed => 3, thu => 4,
356 fri => 5, sat => 6, sun => 7};
357 my $rdowmap = { '1' => 'mon', '2' => 'tue', '3' => 'wed', '4' => 'thu',
358 '5' => 'fri', '6' => 'sat', '7' => 'sun', '0' => 'sun'};
359
360 my $res = {};
361
362 $dowstr = '1,2,3,4,5,6,7' if $dowstr eq '*';
363
364 foreach my $day (split (/,/, $dowstr)) {
365 if ($day =~ m/^(mon|tue|wed|thu|fri|sat|sun)-(mon|tue|wed|thu|fri|sat|sun)$/i) {
366 for (my $i = $dowmap->{lc($1)}; $i <= $dowmap->{lc($2)}; $i++) {
367 my $r = $rdowmap->{$i};
368 $res->{$r} = 1;
369 }
370 } elsif ($day =~ m/^(mon|tue|wed|thu|fri|sat|sun|[0-7])$/i) {
371 $day = $rdowmap->{$day} if $day =~ m/\d/;
372 $res->{lc($day)} = 1;
373 } else {
374 return undef if $noerr;
375 die "unable to parse day of week '$dowstr'\n";
376 }
377 }
378
379 return $res;
380 };
381
382 sub parse_config {
383 my ($filename, $raw) = @_;
384
385 my $jobs = []; # correct jobs
386
387 my $ejobs = []; # mailfomerd lines
388
389 my $jid = 1; # we start at 1
390
391 my $digest = Digest::SHA1::sha1_hex(defined($raw) ? $raw : '');
392
393 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
394 my $line = $1;
395
396 next if $line =~ m/^\#/;
397 next if $line =~ m/^\s*$/;
398 next if $line =~ m/^PATH\s*=/; # we always overwrite path
399
400 if ($line =~ m|^(\d+)\s+(\d+)\s+\*\s+\*\s+(\S+)\s+root\s+(/\S+/)?vzdump(\s+(.*))?$|) {
401 eval {
402 my $minute = int($1);
403 my $hour = int($2);
404 my $dow = $3;
405 my $param = $6;
406
407 my $dowhash = parse_dow($dow, 1);
408 die "unable to parse day of week '$dow' in '$filename'\n" if !$dowhash;
409
410 my $args = [ split(/\s+/, $param)];
411
412 my $opts = PVE::JSONSchema::get_options($vzdump_method_info->{parameters},
413 $args, undef, undef, 'vmid');
414
415 $opts->{id} = "$digest:$jid";
416 $jid++;
417 $opts->{hour} = $hour;
418 $opts->{minute} = $minute;
419 $opts->{dow} = &$dowhash_to_dow($dowhash);
420
421 push @$jobs, $opts;
422 };
423 my $err = $@;
424 if ($err) {
425 syslog ('err', "parse error in '$filename': $err");
426 push @$ejobs, { line => $line };
427 }
428 } elsif ($line =~ m|^\S+\s+(\S+)\s+\S+\s+\S+\s+\S+\s+\S+\s+(\S.*)$|) {
429 syslog ('err', "warning: malformed line in '$filename'");
430 push @$ejobs, { line => $line };
431 } else {
432 syslog ('err', "ignoring malformed line in '$filename'");
433 }
434 }
435
436 my $res = {};
437 $res->{digest} = $digest;
438 $res->{jobs} = $jobs;
439 $res->{ejobs} = $ejobs;
440
441 return $res;
442 }
443
444 sub write_config {
445 my ($filename, $cfg) = @_;
446
447 my $out = "# cluster wide vzdump cron schedule\n";
448 $out .= "# Atomatically generated file - do not edit\n\n";
449 $out .= "PATH=\"/usr/sbin:/usr/bin:/sbin:/bin\"\n\n";
450
451 my $jobs = $cfg->{jobs} || [];
452 foreach my $job (@$jobs) {
453 my $dh = parse_dow($job->{dow});
454 my $dow;
455 if ($dh->{mon} && $dh->{tue} && $dh->{wed} && $dh->{thu} &&
456 $dh->{fri} && $dh->{sat} && $dh->{sun}) {
457 $dow = '*';
458 } else {
459 $dow = &$dowhash_to_dow($dh, 1);
460 $dow = '*' if !$dow;
461 }
462
463 my $param = "";
464 foreach my $p (keys %$job) {
465 next if $p eq 'id' || $p eq 'vmid' || $p eq 'hour' ||
466 $p eq 'minute' || $p eq 'dow';
467 $param .= " --$p " . $job->{$p};
468 }
469
470 $param .= $job->{vmid} if $job->{vmid};
471
472 $out .= sprintf "$job->{minute} $job->{hour} * * %-11s root vzdump$param\n", $dow;
473 }
474
475 my $ejobs = $cfg->{ejobs} || [];
476 foreach my $job (@$ejobs) {
477 $out .= "$job->{line}\n" if $job->{line};
478 }
479
480 return $out;
481 }
482
483 __PACKAGE__->register_method({
484 name => 'vzdump',
485 path => 'vzdump',
486 method => 'GET',
487 description => "List vzdump backup schedule.",
488 parameters => {
489 additionalProperties => 0,
490 properties => {},
491 },
492 returns => {
493 type => 'array',
494 items => {
495 type => "object",
496 properties => {
497 id => { type => 'string' },
498 },
499 },
500 links => [ { rel => 'child', href => "{id}" } ],
501 },
502 code => sub {
503 my ($param) = @_;
504
505 my $rpcenv = PVE::RPCEnvironment::get();
506 my $user = $rpcenv->get_user();
507
508 my $data = cfs_read_file('vzdump');
509
510 my $res = $data->{jobs} || [];
511
512 return $res;
513 }});
514
515 1;