]> git.proxmox.com Git - pve-manager.git/blame - PVE/API2/Backup.pm
finnish and cleanup vzdump API
[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
62 foreach my $day (split (/,/, $dowstr)) {
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
113 my $args = [ split(/\s+/, $param)];
114
115 my $opts = PVE::JSONSchema::get_options($vzdump_propetries, $args, undef, undef, 'vmid');
116
117 $opts->{id} = "$digest:$jid";
118 $jid++;
119 $opts->{hour} = $hour;
120 $opts->{minute} = $minute;
121 $opts->{dow} = &$dowhash_to_dow($dowhash);
122
123 push @$jobs, $opts;
124 };
125 my $err = $@;
126 if ($err) {
127 syslog ('err', "parse error in '$filename': $err");
128 push @$ejobs, { line => $line };
129 }
130 } elsif ($line =~ m|^\S+\s+(\S+)\s+\S+\s+\S+\s+\S+\s+\S+\s+(\S.*)$|) {
131 syslog ('err', "warning: malformed line in '$filename'");
132 push @$ejobs, { line => $line };
133 } else {
134 syslog ('err', "ignoring malformed line in '$filename'");
135 }
136 }
137
138 my $res = {};
139 $res->{digest} = $digest;
140 $res->{jobs} = $jobs;
141 $res->{ejobs} = $ejobs;
142
143 return $res;
144}
145
146sub write_vzdump_cron_config {
147 my ($filename, $cfg) = @_;
148
149 my $out = "# cluster wide vzdump cron schedule\n";
150 $out .= "# Atomatically generated file - do not edit\n\n";
151 $out .= "PATH=\"/usr/sbin:/usr/bin:/sbin:/bin\"\n\n";
152
153 my $jobs = $cfg->{jobs} || [];
154 foreach my $job (@$jobs) {
155 my $dh = parse_dow($job->{dow});
156 my $dow;
157 if ($dh->{mon} && $dh->{tue} && $dh->{wed} && $dh->{thu} &&
158 $dh->{fri} && $dh->{sat} && $dh->{sun}) {
159 $dow = '*';
160 } else {
161 $dow = &$dowhash_to_dow($dh, 1);
162 $dow = '*' if !$dow;
163 }
164
165 my $param = "";
166 foreach my $p (keys %$job) {
167 next if $p eq 'id' || $p eq 'vmid' || $p eq 'hour' ||
168 $p eq 'minute' || $p eq 'dow';
169 $param .= " --$p " . $job->{$p};
170 }
171
172 $param .= " $job->{vmid}" if $job->{vmid};
173
174 $out .= sprintf "$job->{minute} $job->{hour} * * %-11s root vzdump$param\n", $dow;
175 }
176
177 my $ejobs = $cfg->{ejobs} || [];
178 foreach my $job (@$ejobs) {
179 $out .= "$job->{line}\n" if $job->{line};
180 }
181
182 return $out;
183}
184
185__PACKAGE__->register_method({
186 name => 'index',
187 path => '',
188 method => 'GET',
189 description => "List vzdump backup schedule.",
190 parameters => {
191 additionalProperties => 0,
192 properties => {},
193 },
194 returns => {
195 type => 'array',
196 items => {
197 type => "object",
198 properties => {
199 id => { type => 'string' },
200 },
201 },
202 links => [ { rel => 'child', href => "{id}" } ],
203 },
204 code => sub {
205 my ($param) = @_;
206
207 my $rpcenv = PVE::RPCEnvironment::get();
208 my $user = $rpcenv->get_user();
209
210 my $data = cfs_read_file('vzdump');
211
212 my $res = $data->{jobs} || [];
213
214 return $res;
215 }});
216
217__PACKAGE__->register_method({
218 name => 'create_job',
219 path => '',
220 method => 'POST',
221 protected => 1,
222 description => "Create new vzdump backup job.",
223 parameters => {
224 additionalProperties => 0,
225 properties => PVE::VZDump::json_config_properties({
226 hour => {
227 type => 'integer',
228 description => "Start time (hour).",
229 minimum => 0,
230 maximum => 23,
231 },
232 minute => {
233 type => 'integer',
234 optional => 1,
235 description => "Start time (minute).",
236 minimum => 0,
237 maximum => 59,
238 default => 0,
239 },
240 dow => {
241 type => 'string', format => 'pve-day-of-week-list',
242 optional => 1,
243 description => "Day of week selection.",
244 default => 'mon,tue,wed,thu,fri,sat,sun',
245 },
246 }),
247 },
248 returns => { type => 'null' },
249 code => sub {
250 my ($param) = @_;
251
252 my $rpcenv = PVE::RPCEnvironment::get();
253 my $user = $rpcenv->get_user();
254
255 my $data = cfs_read_file('vzdump');
256
257 $param->{minute} = 0 if !defined($param->{minute});
258 $param->{dow} = 'mon,tue,wed,thu,fri,sat,sun' if !defined($param->{dow});
259
260 $param->{all} = 1 if defined($param->{exclude});
261 raise_param_exc({ all => "option conflicts with option 'vmid'"})
262 if $param->{all} && $param->{vmid};
263
264 raise_param_exc({ vmid => "property is missing"})
265 if !$param->{all} && !$param->{vmid};
266
267 push @{$data->{jobs}}, $param;
268
269 cfs_write_file('vzdump', $data);
270
271 return undef;
272 }});
273
274__PACKAGE__->register_method({
275 name => 'read_job',
276 path => '{id}',
277 method => 'GET',
278 description => "Read vzdump backup job definition.",
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
298 my $data = cfs_read_file('vzdump');
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.",
315 protected => 1,
316 parameters => {
317 additionalProperties => 0,
318 properties => {
319 id => {
320 type => 'string',
321 description => "The job ID.",
322 maxLength => 50,
323 }
324 },
325 },
326 returns => { type => 'null' },
327 code => sub {
328 my ($param) = @_;
329
330 my $rpcenv = PVE::RPCEnvironment::get();
331 my $user = $rpcenv->get_user();
332
333 my $data = cfs_read_file('vzdump');
334
335 my $jobs = $data->{jobs} || [];
336 my $newjobs = [];
337
338 my $found;
339 foreach my $job (@$jobs) {
340 if ($job->{id} eq $param->{id}) {
341 $found = 1;
342 } else {
343 push @$newjobs, $job;
344 }
345 }
346
347 raise_param_exc({ id => "No such job '$param->{id}'" }) if !$found;
348
349 $data->{jobs} = $newjobs;
350
351 cfs_write_file('vzdump', $data);
352
353 return undef;
354 }});
355
356__PACKAGE__->register_method({
357 name => 'update_job',
358 path => '{id}',
359 method => 'PUT',
360 protected => 1,
361 description => "Update vzdump backup job definition.",
362 parameters => {
363 additionalProperties => 0,
364 properties => PVE::VZDump::json_config_properties({
365 id => {
366 type => 'string',
367 description => "The job ID.",
368 maxLength => 50,
369 },
370 hour => {
371 type => 'integer',
372 optional => 1,
373 description => "Start time (hour).",
374 minimum => 0,
375 maximum => 23,
376 },
377 minute => {
378 type => 'integer',
379 optional => 1,
380 description => "Start time (minute).",
381 minimum => 0,
382 maximum => 59,
383 },
384 dow => {
385 type => 'string', format => 'pve-day-of-week-list',
386 optional => 1,
387 description => "Day of week selection.",
388 },
389 }),
390 },
391 returns => { type => 'null' },
392 code => sub {
393 my ($param) = @_;
394
395 my $rpcenv = PVE::RPCEnvironment::get();
396 my $user = $rpcenv->get_user();
397
398 my $data = cfs_read_file('vzdump');
399
400 my $jobs = $data->{jobs} || [];
401
402 raise_param_exc({ all => "option conflicts with option 'vmid'"})
403 if $param->{all} && $param->{vmid};
404
405 foreach my $job (@$jobs) {
406 if ($job->{id} eq $param->{id}) {
407
408 foreach my $k (keys %$param) {
409 $job->{$k} = $param->{$k};
410 }
411
412 $job->{all} = 1 if defined($job->{exclude});
413
414 if ($param->{vmid}) {
415 delete $job->{all};
416 delete $job->{exclude};
417 } elsif ($param->{all}) {
418 delete $job->{vmid};
419 }
420
421 raise_param_exc({ all => "option conflicts with option 'vmid'"})
422 if $job->{all} && $job->{vmid};
423
424 raise_param_exc({ vmid => "property is missing"})
425 if !$job->{all} && !$job->{vmid};
426
427 cfs_write_file('vzdump', $data);
428
429 return undef;
430 }
431 }
432
433 raise_param_exc({ id => "No such job '$param->{id}'" });
434
435 }});
436
4371;