]> git.proxmox.com Git - pve-manager.git/blame - PVE/API2/Backup.pm
Change Ct restore rest call
[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
104 if ($line =~ m|^(\d+)\s+(\d+)\s+\*\s+\*\s+(\S+)\s+root\s+(/\S+/)?vzdump(\s+(.*))?$|) {
105 eval {
106 my $minute = int($1);
107 my $hour = int($2);
108 my $dow = $3;
109 my $param = $6;
110
111 my $dowhash = parse_dow($dow, 1);
112 die "unable to parse day of week '$dow' in '$filename'\n" if !$dowhash;
113
063bb19b 114 my $args = PVE::Tools::split_args($param);
f9adc776 115 my $opts = PVE::JSONSchema::get_options($vzdump_properties, $args, 'vmid');
ac27b58d
DM
116
117 $opts->{id} = "$digest:$jid";
118 $jid++;
7625ea19 119 $opts->{starttime} = sprintf "%02d:%02d", $hour, $minute;
ac27b58d
DM
120 $opts->{dow} = &$dowhash_to_dow($dowhash);
121
122 push @$jobs, $opts;
123 };
124 my $err = $@;
125 if ($err) {
126 syslog ('err', "parse error in '$filename': $err");
127 push @$ejobs, { line => $line };
128 }
129 } elsif ($line =~ m|^\S+\s+(\S+)\s+\S+\s+\S+\s+\S+\s+\S+\s+(\S.*)$|) {
130 syslog ('err', "warning: malformed line in '$filename'");
131 push @$ejobs, { line => $line };
132 } else {
133 syslog ('err', "ignoring malformed line in '$filename'");
134 }
135 }
136
137 my $res = {};
138 $res->{digest} = $digest;
139 $res->{jobs} = $jobs;
140 $res->{ejobs} = $ejobs;
141
142 return $res;
143}
144
145sub write_vzdump_cron_config {
146 my ($filename, $cfg) = @_;
147
148 my $out = "# cluster wide vzdump cron schedule\n";
94c47be7 149 $out .= "# Automatically generated file - do not edit\n\n";
ac27b58d
DM
150 $out .= "PATH=\"/usr/sbin:/usr/bin:/sbin:/bin\"\n\n";
151
152 my $jobs = $cfg->{jobs} || [];
153 foreach my $job (@$jobs) {
154 my $dh = parse_dow($job->{dow});
155 my $dow;
156 if ($dh->{mon} && $dh->{tue} && $dh->{wed} && $dh->{thu} &&
157 $dh->{fri} && $dh->{sat} && $dh->{sun}) {
158 $dow = '*';
159 } else {
160 $dow = &$dowhash_to_dow($dh, 1);
161 $dow = '*' if !$dow;
162 }
163
7625ea19
DM
164 my ($hour, $minute);
165
166 die "no job start time specified\n" if !$job->{starttime};
167 if ($job->{starttime} =~ m/^(\d{1,2}):(\d{1,2})$/) {
168 ($hour, $minute) = (int($1), int($2));
169 die "hour '$hour' out of range\n" if $hour < 0 || $hour > 23;
170 die "minute '$minute' out of range\n" if $minute < 0 || $minute > 59;
171 } else {
172 die "unable to parse job start time\n";
173 }
31aef761
DM
174
175 $job->{quiet} = 1; # we do not want messages from cron
7625ea19 176
31aef761 177 my $cmd = PVE::VZDump::command_line($job);
ac27b58d 178
31aef761 179 $out .= sprintf "$minute $hour * * %-11s root $cmd\n", $dow;
ac27b58d
DM
180 }
181
182 my $ejobs = $cfg->{ejobs} || [];
183 foreach my $job (@$ejobs) {
184 $out .= "$job->{line}\n" if $job->{line};
185 }
186
187 return $out;
188}
189
190__PACKAGE__->register_method({
191 name => 'index',
192 path => '',
193 method => 'GET',
194 description => "List vzdump backup schedule.",
937515d6
DM
195 permissions => {
196 check => ['perm', '/', ['Sys.Audit']],
197 },
ac27b58d
DM
198 parameters => {
199 additionalProperties => 0,
200 properties => {},
201 },
202 returns => {
203 type => 'array',
204 items => {
205 type => "object",
206 properties => {
207 id => { type => 'string' },
208 },
209 },
210 links => [ { rel => 'child', href => "{id}" } ],
211 },
212 code => sub {
213 my ($param) = @_;
214
215 my $rpcenv = PVE::RPCEnvironment::get();
216 my $user = $rpcenv->get_user();
217
b0905e3a 218 my $data = cfs_read_file('vzdump.cron');
ac27b58d
DM
219
220 my $res = $data->{jobs} || [];
221
222 return $res;
223 }});
224
225__PACKAGE__->register_method({
226 name => 'create_job',
227 path => '',
228 method => 'POST',
229 protected => 1,
230 description => "Create new vzdump backup job.",
937515d6
DM
231 permissions => {
232 check => ['perm', '/', ['Sys.Modify']],
233 },
ac27b58d
DM
234 parameters => {
235 additionalProperties => 0,
236 properties => PVE::VZDump::json_config_properties({
7625ea19
DM
237 starttime => {
238 type => 'string',
239 description => "Job Start time.",
240 pattern => '\d{1,2}:\d{1,2}',
241 typetext => 'HH:MM',
ac27b58d
DM
242 },
243 dow => {
244 type => 'string', format => 'pve-day-of-week-list',
245 optional => 1,
246 description => "Day of week selection.",
247 default => 'mon,tue,wed,thu,fri,sat,sun',
248 },
249 }),
250 },
251 returns => { type => 'null' },
252 code => sub {
253 my ($param) = @_;
254
255 my $rpcenv = PVE::RPCEnvironment::get();
256 my $user = $rpcenv->get_user();
257
b0905e3a 258 my $data = cfs_read_file('vzdump.cron');
ac27b58d 259
ac27b58d
DM
260 $param->{dow} = 'mon,tue,wed,thu,fri,sat,sun' if !defined($param->{dow});
261
31aef761 262 PVE::VZDump::verify_vzdump_parameters($param, 1);
ac27b58d
DM
263
264 push @{$data->{jobs}}, $param;
265
b0905e3a 266 cfs_write_file('vzdump.cron', $data);
ac27b58d
DM
267
268 return undef;
269 }});
270
271__PACKAGE__->register_method({
272 name => 'read_job',
273 path => '{id}',
274 method => 'GET',
275 description => "Read vzdump backup job definition.",
937515d6
DM
276 permissions => {
277 check => ['perm', '/', ['Sys.Audit']],
278 },
ac27b58d
DM
279 parameters => {
280 additionalProperties => 0,
281 properties => {
282 id => {
283 type => 'string',
284 description => "The job ID.",
285 maxLength => 50,
286 }
287 },
288 },
289 returns => {
290 type => 'object',
291 },
292 code => sub {
293 my ($param) = @_;
294
295 my $rpcenv = PVE::RPCEnvironment::get();
296 my $user = $rpcenv->get_user();
297
b0905e3a 298 my $data = cfs_read_file('vzdump.cron');
ac27b58d
DM
299
300 my $jobs = $data->{jobs} || [];
301
302 foreach my $job (@$jobs) {
303 return $job if $job->{id} eq $param->{id};
304 }
305
306 raise_param_exc({ id => "No such job '$param->{id}'" });
307
308 }});
309
310__PACKAGE__->register_method({
311 name => 'delete_job',
312 path => '{id}',
313 method => 'DELETE',
314 description => "Delete vzdump backup job definition.",
937515d6
DM
315 permissions => {
316 check => ['perm', '/', ['Sys.Modify']],
317 },
ac27b58d
DM
318 protected => 1,
319 parameters => {
320 additionalProperties => 0,
321 properties => {
322 id => {
323 type => 'string',
324 description => "The job ID.",
325 maxLength => 50,
326 }
327 },
328 },
329 returns => { type => 'null' },
330 code => sub {
331 my ($param) = @_;
332
333 my $rpcenv = PVE::RPCEnvironment::get();
334 my $user = $rpcenv->get_user();
335
b0905e3a 336 my $data = cfs_read_file('vzdump.cron');
ac27b58d
DM
337
338 my $jobs = $data->{jobs} || [];
339 my $newjobs = [];
340
341 my $found;
342 foreach my $job (@$jobs) {
343 if ($job->{id} eq $param->{id}) {
344 $found = 1;
345 } else {
346 push @$newjobs, $job;
347 }
348 }
349
350 raise_param_exc({ id => "No such job '$param->{id}'" }) if !$found;
351
352 $data->{jobs} = $newjobs;
353
b0905e3a 354 cfs_write_file('vzdump.cron', $data);
ac27b58d
DM
355
356 return undef;
357 }});
358
359__PACKAGE__->register_method({
360 name => 'update_job',
361 path => '{id}',
362 method => 'PUT',
363 protected => 1,
364 description => "Update vzdump backup job definition.",
937515d6
DM
365 permissions => {
366 check => ['perm', '/', ['Sys.Modify']],
367 },
ac27b58d
DM
368 parameters => {
369 additionalProperties => 0,
370 properties => PVE::VZDump::json_config_properties({
371 id => {
372 type => 'string',
373 description => "The job ID.",
374 maxLength => 50,
375 },
7625ea19
DM
376 starttime => {
377 type => 'string',
378 description => "Job Start time.",
379 pattern => '\d{1,2}:\d{1,2}',
380 typetext => 'HH:MM',
ac27b58d
DM
381 },
382 dow => {
383 type => 'string', format => 'pve-day-of-week-list',
384 optional => 1,
385 description => "Day of week selection.",
386 },
53c6bb6c
DM
387 delete => {
388 type => 'string', format => 'pve-configid-list',
389 description => "A list of settings you want to delete.",
390 optional => 1,
391 },
ac27b58d
DM
392 }),
393 },
394 returns => { type => 'null' },
395 code => sub {
396 my ($param) = @_;
397
398 my $rpcenv = PVE::RPCEnvironment::get();
399 my $user = $rpcenv->get_user();
400
b0905e3a 401 my $data = cfs_read_file('vzdump.cron');
ac27b58d
DM
402
403 my $jobs = $data->{jobs} || [];
404
53c6bb6c
DM
405 die "no options specified\n" if !scalar(keys %$param);
406
31aef761 407 PVE::VZDump::verify_vzdump_parameters($param);
ac27b58d 408
31aef761 409 my @delete = PVE::Tools::split_list(extract_param($param, 'delete'));
53c6bb6c 410
ac27b58d
DM
411 foreach my $job (@$jobs) {
412 if ($job->{id} eq $param->{id}) {
413
31aef761 414 foreach my $k (@delete) {
53c6bb6c
DM
415 if (!PVE::VZDump::option_exists($k)) {
416 raise_param_exc({ delete => "unknown option '$k'" });
417 }
418
419 delete $job->{$k};
420 }
421
ac27b58d
DM
422 foreach my $k (keys %$param) {
423 $job->{$k} = $param->{$k};
424 }
425
426 $job->{all} = 1 if defined($job->{exclude});
427
31aef761 428 if (defined($param->{vmid})) {
ac27b58d
DM
429 delete $job->{all};
430 delete $job->{exclude};
431 } elsif ($param->{all}) {
432 delete $job->{vmid};
433 }
434
31aef761 435 PVE::VZDump::verify_vzdump_parameters($job, 1);
ac27b58d 436
b0905e3a 437 cfs_write_file('vzdump.cron', $data);
ac27b58d
DM
438
439 return undef;
440 }
441 }
442
443 raise_param_exc({ id => "No such job '$param->{id}'" });
444
445 }});
446
4471;