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