]> git.proxmox.com Git - pve-manager.git/blame - PVE/API2/Backup.pm
ui: migrate: add storage and size information to preconditions
[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
200cef80
CE
273 my $create_job = sub {
274 my $data = cfs_read_file('vzdump.cron');
ac27b58d 275
200cef80
CE
276 $param->{dow} = 'mon,tue,wed,thu,fri,sat,sun' if !defined($param->{dow});
277 $param->{enabled} = 1 if !defined($param->{enabled});
278 PVE::VZDump::verify_vzdump_parameters($param, 1);
ac27b58d 279
200cef80 280 push @{$data->{jobs}}, $param;
ac27b58d 281
200cef80
CE
282 cfs_write_file('vzdump.cron', $data);
283 };
284 cfs_lock_file('vzdump.cron', undef, $create_job);
285 die "$@" if ($@);
ac27b58d
DM
286
287 return undef;
288 }});
289
290__PACKAGE__->register_method({
291 name => 'read_job',
292 path => '{id}',
293 method => 'GET',
294 description => "Read vzdump backup job definition.",
937515d6
DM
295 permissions => {
296 check => ['perm', '/', ['Sys.Audit']],
297 },
ac27b58d
DM
298 parameters => {
299 additionalProperties => 0,
300 properties => {
301 id => {
302 type => 'string',
303 description => "The job ID.",
304 maxLength => 50,
305 }
306 },
307 },
308 returns => {
309 type => 'object',
310 },
311 code => sub {
312 my ($param) = @_;
313
314 my $rpcenv = PVE::RPCEnvironment::get();
315 my $user = $rpcenv->get_user();
316
b0905e3a 317 my $data = cfs_read_file('vzdump.cron');
ac27b58d
DM
318
319 my $jobs = $data->{jobs} || [];
320
321 foreach my $job (@$jobs) {
322 return $job if $job->{id} eq $param->{id};
323 }
324
325 raise_param_exc({ id => "No such job '$param->{id}'" });
326
327 }});
328
329__PACKAGE__->register_method({
330 name => 'delete_job',
331 path => '{id}',
332 method => 'DELETE',
333 description => "Delete vzdump backup job definition.",
937515d6
DM
334 permissions => {
335 check => ['perm', '/', ['Sys.Modify']],
336 },
ac27b58d
DM
337 protected => 1,
338 parameters => {
339 additionalProperties => 0,
340 properties => {
341 id => {
342 type => 'string',
343 description => "The job ID.",
344 maxLength => 50,
4341db1d 345 },
ac27b58d
DM
346 },
347 },
348 returns => { type => 'null' },
349 code => sub {
350 my ($param) = @_;
351
352 my $rpcenv = PVE::RPCEnvironment::get();
353 my $user = $rpcenv->get_user();
354
200cef80
CE
355 my $delete_job = sub {
356 my $data = cfs_read_file('vzdump.cron');
ac27b58d 357
200cef80
CE
358 my $jobs = $data->{jobs} || [];
359 my $newjobs = [];
ac27b58d 360
200cef80
CE
361 my $found;
362 foreach my $job (@$jobs) {
363 if ($job->{id} eq $param->{id}) {
364 $found = 1;
365 } else {
366 push @$newjobs, $job;
367 }
ac27b58d 368 }
ac27b58d 369
200cef80 370 raise_param_exc({ id => "No such job '$param->{id}'" }) if !$found;
ac27b58d 371
200cef80 372 $data->{jobs} = $newjobs;
ac27b58d 373
200cef80
CE
374 cfs_write_file('vzdump.cron', $data);
375 };
376 cfs_lock_file('vzdump.cron', undef, $delete_job);
377 die "$@" if ($@);
ac27b58d
DM
378
379 return undef;
380 }});
381
382__PACKAGE__->register_method({
383 name => 'update_job',
384 path => '{id}',
385 method => 'PUT',
386 protected => 1,
387 description => "Update vzdump backup job definition.",
937515d6
DM
388 permissions => {
389 check => ['perm', '/', ['Sys.Modify']],
390 },
ac27b58d
DM
391 parameters => {
392 additionalProperties => 0,
393 properties => PVE::VZDump::json_config_properties({
394 id => {
395 type => 'string',
396 description => "The job ID.",
397 maxLength => 50,
398 },
7625ea19
DM
399 starttime => {
400 type => 'string',
401 description => "Job Start time.",
402 pattern => '\d{1,2}:\d{1,2}',
403 typetext => 'HH:MM',
ac27b58d
DM
404 },
405 dow => {
406 type => 'string', format => 'pve-day-of-week-list',
407 optional => 1,
408 description => "Day of week selection.",
409 },
53c6bb6c
DM
410 delete => {
411 type => 'string', format => 'pve-configid-list',
412 description => "A list of settings you want to delete.",
413 optional => 1,
414 },
4341db1d
TL
415 enabled => {
416 type => 'boolean',
417 optional => 1,
418 description => "Enable or disable the job.",
419 default => '1',
420 },
ac27b58d
DM
421 }),
422 },
423 returns => { type => 'null' },
424 code => sub {
425 my ($param) = @_;
426
427 my $rpcenv = PVE::RPCEnvironment::get();
428 my $user = $rpcenv->get_user();
429
200cef80
CE
430 my $update_job = sub {
431 my $data = cfs_read_file('vzdump.cron');
ac27b58d 432
200cef80 433 my $jobs = $data->{jobs} || [];
ac27b58d 434
200cef80 435 die "no options specified\n" if !scalar(keys %$param);
53c6bb6c 436
200cef80 437 PVE::VZDump::verify_vzdump_parameters($param);
ac27b58d 438
200cef80 439 my @delete = PVE::Tools::split_list(extract_param($param, 'delete'));
53c6bb6c 440
200cef80
CE
441 foreach my $job (@$jobs) {
442 if ($job->{id} eq $param->{id}) {
ac27b58d 443
200cef80
CE
444 foreach my $k (@delete) {
445 if (!PVE::VZDump::option_exists($k)) {
446 raise_param_exc({ delete => "unknown option '$k'" });
447 }
53c6bb6c 448
200cef80
CE
449 delete $job->{$k};
450 }
53c6bb6c 451
200cef80
CE
452 foreach my $k (keys %$param) {
453 $job->{$k} = $param->{$k};
454 }
ac27b58d 455
200cef80 456 $job->{all} = 1 if defined($job->{exclude});
ac27b58d 457
200cef80
CE
458 if (defined($param->{vmid})) {
459 delete $job->{all};
460 delete $job->{exclude};
461 } elsif ($param->{all}) {
462 delete $job->{vmid};
463 }
ac27b58d 464
200cef80 465 PVE::VZDump::verify_vzdump_parameters($job, 1);
ac27b58d 466
200cef80 467 cfs_write_file('vzdump.cron', $data);
ac27b58d 468
200cef80
CE
469 return undef;
470 }
ac27b58d 471 }
200cef80
CE
472 raise_param_exc({ id => "No such job '$param->{id}'" });
473 };
474 cfs_lock_file('vzdump.cron', undef, $update_job);
475 die "$@" if ($@);
ac27b58d
DM
476 }});
477
4781;