]> git.proxmox.com Git - pve-manager.git/blame - PVE/API2/Backup.pm
ui: workspace: cope better with upgrade related false positive 401 HTTP codes
[pve-manager.git] / PVE / API2 / Backup.pm
CommitLineData
ac27b58d
DM
1package PVE::API2::Backup;
2
3use strict;
4use warnings;
52878b0a 5use Digest::SHA;
ac27b58d
DM
6
7use PVE::SafeSyslog;
8use PVE::Tools qw(extract_param);
9use PVE::Cluster qw(cfs_register_file cfs_lock_file cfs_read_file cfs_write_file);
10use PVE::RESTHandler;
11use PVE::RPCEnvironment;
12use PVE::JSONSchema;
13use PVE::Storage;
14use PVE::Exception qw(raise_param_exc);
15use PVE::VZDump;
16
17use base qw(PVE::RESTHandler);
18
b0905e3a 19cfs_register_file ('vzdump.cron',
ac27b58d
DM
20 \&parse_vzdump_cron_config,
21 \&write_vzdump_cron_config);
22
23PVE::JSONSchema::register_format('pve-day-of-week', \&verify_day_of_week);
24sub verify_day_of_week {
25 my ($value, $noerr) = @_;
26
27 return $value if $value =~ m/^(mon|tue|wed|thu|fri|sat|sun)$/;
28
29 return undef if $noerr;
30
31 die "invalid day '$value'\n";
32}
33
34
35my $dowhash_to_dow = sub {
36 my ($d, $num) = @_;
37
38 my @da = ();
39 push @da, $num ? 1 : 'mon' if $d->{mon};
40 push @da, $num ? 2 : 'tue' if $d->{tue};
41 push @da, $num ? 3 : 'wed' if $d->{wed};
42 push @da, $num ? 4 : 'thu' if $d->{thu};
43 push @da, $num ? 5 : 'fri' if $d->{fri};
44 push @da, $num ? 6 : 'sat' if $d->{sat};
45 push @da, $num ? 7 : 'sun' if $d->{sun};
46
47 return join ',', @da;
48};
49
50# parse crontab style day of week
51sub parse_dow {
52 my ($dowstr, $noerr) = @_;
53
54 my $dowmap = {mon => 1, tue => 2, wed => 3, thu => 4,
55 fri => 5, sat => 6, sun => 7};
56 my $rdowmap = { '1' => 'mon', '2' => 'tue', '3' => 'wed', '4' => 'thu',
57 '5' => 'fri', '6' => 'sat', '7' => 'sun', '0' => 'sun'};
58
59 my $res = {};
60
61 $dowstr = '1,2,3,4,5,6,7' if $dowstr eq '*';
62
7625ea19 63 foreach my $day (PVE::Tools::split_list($dowstr)) {
ac27b58d
DM
64 if ($day =~ m/^(mon|tue|wed|thu|fri|sat|sun)-(mon|tue|wed|thu|fri|sat|sun)$/i) {
65 for (my $i = $dowmap->{lc($1)}; $i <= $dowmap->{lc($2)}; $i++) {
66 my $r = $rdowmap->{$i};
67 $res->{$r} = 1;
68 }
69 } elsif ($day =~ m/^(mon|tue|wed|thu|fri|sat|sun|[0-7])$/i) {
70 $day = $rdowmap->{$day} if $day =~ m/\d/;
71 $res->{lc($day)} = 1;
72 } else {
73 return undef if $noerr;
74 die "unable to parse day of week '$dowstr'\n";
75 }
76 }
77
78 return $res;
79};
80
f9adc776 81my $vzdump_properties = {
ac27b58d
DM
82 additionalProperties => 0,
83 properties => PVE::VZDump::json_config_properties({}),
84};
85
86sub parse_vzdump_cron_config {
87 my ($filename, $raw) = @_;
88
89 my $jobs = []; # correct jobs
90
91 my $ejobs = []; # mailfomerd lines
92
93 my $jid = 1; # we start at 1
94
52878b0a 95 my $digest = Digest::SHA::sha1_hex(defined($raw) ? $raw : '');
ac27b58d
DM
96
97 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
98 my $line = $1;
99
100 next if $line =~ m/^\#/;
101 next if $line =~ m/^\s*$/;
102 next if $line =~ m/^PATH\s*=/; # we always overwrite path
103
4341db1d 104 if ($line =~ m|^(\d+)\s+(\d+)\s+\*\s+\*\s+(\S+)\s+root\s+(/\S+/)?(#)?vzdump(\s+(.*))?$|) {
ac27b58d
DM
105 eval {
106 my $minute = int($1);
107 my $hour = int($2);
108 my $dow = $3;
4341db1d
TL
109 my $param = $7;
110 my $enabled = $5;
ac27b58d
DM
111
112 my $dowhash = parse_dow($dow, 1);
113 die "unable to parse day of week '$dow' in '$filename'\n" if !$dowhash;
114
063bb19b 115 my $args = PVE::Tools::split_args($param);
f9adc776 116 my $opts = PVE::JSONSchema::get_options($vzdump_properties, $args, 'vmid');
ac27b58d 117
4341db1d 118 $opts->{enabled} = !defined($enabled);
ac27b58d
DM
119 $opts->{id} = "$digest:$jid";
120 $jid++;
7625ea19 121 $opts->{starttime} = sprintf "%02d:%02d", $hour, $minute;
ac27b58d
DM
122 $opts->{dow} = &$dowhash_to_dow($dowhash);
123
124 push @$jobs, $opts;
125 };
126 my $err = $@;
127 if ($err) {
128 syslog ('err', "parse error in '$filename': $err");
129 push @$ejobs, { line => $line };
130 }
131 } elsif ($line =~ m|^\S+\s+(\S+)\s+\S+\s+\S+\s+\S+\s+\S+\s+(\S.*)$|) {
132 syslog ('err', "warning: malformed line in '$filename'");
133 push @$ejobs, { line => $line };
134 } else {
135 syslog ('err', "ignoring malformed line in '$filename'");
136 }
137 }
138
139 my $res = {};
140 $res->{digest} = $digest;
141 $res->{jobs} = $jobs;
142 $res->{ejobs} = $ejobs;
143
144 return $res;
145}
146
147sub write_vzdump_cron_config {
148 my ($filename, $cfg) = @_;
149
150 my $out = "# cluster wide vzdump cron schedule\n";
94c47be7 151 $out .= "# Automatically generated file - do not edit\n\n";
ac27b58d
DM
152 $out .= "PATH=\"/usr/sbin:/usr/bin:/sbin:/bin\"\n\n";
153
154 my $jobs = $cfg->{jobs} || [];
155 foreach my $job (@$jobs) {
4341db1d 156 my $enabled = ($job->{enabled}) ? '' : '#';
ac27b58d
DM
157 my $dh = parse_dow($job->{dow});
158 my $dow;
159 if ($dh->{mon} && $dh->{tue} && $dh->{wed} && $dh->{thu} &&
160 $dh->{fri} && $dh->{sat} && $dh->{sun}) {
161 $dow = '*';
162 } else {
163 $dow = &$dowhash_to_dow($dh, 1);
164 $dow = '*' if !$dow;
165 }
166
7625ea19
DM
167 my ($hour, $minute);
168
169 die "no job start time specified\n" if !$job->{starttime};
170 if ($job->{starttime} =~ m/^(\d{1,2}):(\d{1,2})$/) {
171 ($hour, $minute) = (int($1), int($2));
172 die "hour '$hour' out of range\n" if $hour < 0 || $hour > 23;
173 die "minute '$minute' out of range\n" if $minute < 0 || $minute > 59;
174 } else {
175 die "unable to parse job start time\n";
176 }
31aef761
DM
177
178 $job->{quiet} = 1; # we do not want messages from cron
7625ea19 179
31aef761 180 my $cmd = PVE::VZDump::command_line($job);
ac27b58d 181
4341db1d 182 $out .= sprintf "$minute $hour * * %-11s root $enabled$cmd\n", $dow;
ac27b58d
DM
183 }
184
185 my $ejobs = $cfg->{ejobs} || [];
186 foreach my $job (@$ejobs) {
187 $out .= "$job->{line}\n" if $job->{line};
188 }
189
190 return $out;
191}
192
193__PACKAGE__->register_method({
194 name => 'index',
195 path => '',
196 method => 'GET',
197 description => "List vzdump backup schedule.",
937515d6
DM
198 permissions => {
199 check => ['perm', '/', ['Sys.Audit']],
200 },
ac27b58d
DM
201 parameters => {
202 additionalProperties => 0,
203 properties => {},
204 },
205 returns => {
206 type => 'array',
207 items => {
208 type => "object",
209 properties => {
210 id => { type => 'string' },
211 },
212 },
213 links => [ { rel => 'child', href => "{id}" } ],
214 },
215 code => sub {
216 my ($param) = @_;
217
218 my $rpcenv = PVE::RPCEnvironment::get();
219 my $user = $rpcenv->get_user();
220
b0905e3a 221 my $data = cfs_read_file('vzdump.cron');
ac27b58d
DM
222
223 my $res = $data->{jobs} || [];
224
225 return $res;
226 }});
227
228__PACKAGE__->register_method({
229 name => 'create_job',
230 path => '',
231 method => 'POST',
232 protected => 1,
233 description => "Create new vzdump backup job.",
937515d6
DM
234 permissions => {
235 check => ['perm', '/', ['Sys.Modify']],
f0bbc084 236 description => "The 'tmpdir', 'dumpdir' and 'script' parameters are additionally restricted to the 'root\@pam' user.",
937515d6 237 },
ac27b58d
DM
238 parameters => {
239 additionalProperties => 0,
240 properties => PVE::VZDump::json_config_properties({
7625ea19
DM
241 starttime => {
242 type => 'string',
243 description => "Job Start time.",
244 pattern => '\d{1,2}:\d{1,2}',
245 typetext => 'HH:MM',
ac27b58d
DM
246 },
247 dow => {
248 type => 'string', format => 'pve-day-of-week-list',
249 optional => 1,
250 description => "Day of week selection.",
251 default => 'mon,tue,wed,thu,fri,sat,sun',
252 },
4341db1d
TL
253 enabled => {
254 type => 'boolean',
255 optional => 1,
256 description => "Enable or disable the job.",
257 default => '1',
258 },
ac27b58d
DM
259 }),
260 },
261 returns => { type => 'null' },
262 code => sub {
263 my ($param) = @_;
264
265 my $rpcenv = PVE::RPCEnvironment::get();
266 my $user = $rpcenv->get_user();
267
f0bbc084
FG
268 foreach my $key (qw(tmpdir dumpdir script)) {
269 raise_param_exc({ $key => "Only root may set this option."})
270 if defined($param->{$key}) && ($user ne 'root@pam');
271 }
272
b0905e3a 273 my $data = cfs_read_file('vzdump.cron');
ac27b58d 274
ac27b58d 275 $param->{dow} = 'mon,tue,wed,thu,fri,sat,sun' if !defined($param->{dow});
4341db1d 276 $param->{enabled} = 1 if !defined($param->{enabled});
31aef761 277 PVE::VZDump::verify_vzdump_parameters($param, 1);
ac27b58d
DM
278
279 push @{$data->{jobs}}, $param;
280
b0905e3a 281 cfs_write_file('vzdump.cron', $data);
ac27b58d
DM
282
283 return undef;
284 }});
285
286__PACKAGE__->register_method({
287 name => 'read_job',
288 path => '{id}',
289 method => 'GET',
290 description => "Read vzdump backup job definition.",
937515d6
DM
291 permissions => {
292 check => ['perm', '/', ['Sys.Audit']],
293 },
ac27b58d
DM
294 parameters => {
295 additionalProperties => 0,
296 properties => {
297 id => {
298 type => 'string',
299 description => "The job ID.",
300 maxLength => 50,
301 }
302 },
303 },
304 returns => {
305 type => 'object',
306 },
307 code => sub {
308 my ($param) = @_;
309
310 my $rpcenv = PVE::RPCEnvironment::get();
311 my $user = $rpcenv->get_user();
312
b0905e3a 313 my $data = cfs_read_file('vzdump.cron');
ac27b58d
DM
314
315 my $jobs = $data->{jobs} || [];
316
317 foreach my $job (@$jobs) {
318 return $job if $job->{id} eq $param->{id};
319 }
320
321 raise_param_exc({ id => "No such job '$param->{id}'" });
322
323 }});
324
325__PACKAGE__->register_method({
326 name => 'delete_job',
327 path => '{id}',
328 method => 'DELETE',
329 description => "Delete vzdump backup job definition.",
937515d6
DM
330 permissions => {
331 check => ['perm', '/', ['Sys.Modify']],
332 },
ac27b58d
DM
333 protected => 1,
334 parameters => {
335 additionalProperties => 0,
336 properties => {
337 id => {
338 type => 'string',
339 description => "The job ID.",
340 maxLength => 50,
4341db1d 341 },
ac27b58d
DM
342 },
343 },
344 returns => { type => 'null' },
345 code => sub {
346 my ($param) = @_;
347
348 my $rpcenv = PVE::RPCEnvironment::get();
349 my $user = $rpcenv->get_user();
350
b0905e3a 351 my $data = cfs_read_file('vzdump.cron');
ac27b58d
DM
352
353 my $jobs = $data->{jobs} || [];
354 my $newjobs = [];
355
356 my $found;
357 foreach my $job (@$jobs) {
358 if ($job->{id} eq $param->{id}) {
359 $found = 1;
360 } else {
361 push @$newjobs, $job;
362 }
363 }
364
365 raise_param_exc({ id => "No such job '$param->{id}'" }) if !$found;
366
367 $data->{jobs} = $newjobs;
368
b0905e3a 369 cfs_write_file('vzdump.cron', $data);
ac27b58d
DM
370
371 return undef;
372 }});
373
374__PACKAGE__->register_method({
375 name => 'update_job',
376 path => '{id}',
377 method => 'PUT',
378 protected => 1,
379 description => "Update vzdump backup job definition.",
937515d6
DM
380 permissions => {
381 check => ['perm', '/', ['Sys.Modify']],
382 },
ac27b58d
DM
383 parameters => {
384 additionalProperties => 0,
385 properties => PVE::VZDump::json_config_properties({
386 id => {
387 type => 'string',
388 description => "The job ID.",
389 maxLength => 50,
390 },
7625ea19
DM
391 starttime => {
392 type => 'string',
393 description => "Job Start time.",
394 pattern => '\d{1,2}:\d{1,2}',
395 typetext => 'HH:MM',
ac27b58d
DM
396 },
397 dow => {
398 type => 'string', format => 'pve-day-of-week-list',
399 optional => 1,
400 description => "Day of week selection.",
401 },
53c6bb6c
DM
402 delete => {
403 type => 'string', format => 'pve-configid-list',
404 description => "A list of settings you want to delete.",
405 optional => 1,
406 },
4341db1d
TL
407 enabled => {
408 type => 'boolean',
409 optional => 1,
410 description => "Enable or disable the job.",
411 default => '1',
412 },
ac27b58d
DM
413 }),
414 },
415 returns => { type => 'null' },
416 code => sub {
417 my ($param) = @_;
418
419 my $rpcenv = PVE::RPCEnvironment::get();
420 my $user = $rpcenv->get_user();
421
b0905e3a 422 my $data = cfs_read_file('vzdump.cron');
ac27b58d
DM
423
424 my $jobs = $data->{jobs} || [];
425
53c6bb6c
DM
426 die "no options specified\n" if !scalar(keys %$param);
427
31aef761 428 PVE::VZDump::verify_vzdump_parameters($param);
ac27b58d 429
31aef761 430 my @delete = PVE::Tools::split_list(extract_param($param, 'delete'));
53c6bb6c 431
ac27b58d
DM
432 foreach my $job (@$jobs) {
433 if ($job->{id} eq $param->{id}) {
434
31aef761 435 foreach my $k (@delete) {
53c6bb6c
DM
436 if (!PVE::VZDump::option_exists($k)) {
437 raise_param_exc({ delete => "unknown option '$k'" });
438 }
439
440 delete $job->{$k};
441 }
442
ac27b58d
DM
443 foreach my $k (keys %$param) {
444 $job->{$k} = $param->{$k};
445 }
446
447 $job->{all} = 1 if defined($job->{exclude});
448
31aef761 449 if (defined($param->{vmid})) {
ac27b58d
DM
450 delete $job->{all};
451 delete $job->{exclude};
452 } elsif ($param->{all}) {
453 delete $job->{vmid};
454 }
455
31aef761 456 PVE::VZDump::verify_vzdump_parameters($job, 1);
ac27b58d 457
b0905e3a 458 cfs_write_file('vzdump.cron', $data);
ac27b58d
DM
459
460 return undef;
461 }
462 }
463
464 raise_param_exc({ id => "No such job '$param->{id}'" });
465
466 }});
467
4681;