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