]> git.proxmox.com Git - pve-manager.git/blame - PVE/API2/Backup.pm
fix bug #118: allow to edit ethX settings
[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
b0905e3a 18cfs_register_file ('vzdump.cron',
ac27b58d
DM
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);
4baf5d13 114 my $opts = PVE::JSONSchema::get_options($vzdump_propetries, $args, 'vmid');
ac27b58d
DM
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 }
31aef761
DM
173
174 $job->{quiet} = 1; # we do not want messages from cron
7625ea19 175
31aef761 176 my $cmd = PVE::VZDump::command_line($job);
ac27b58d 177
31aef761 178 $out .= sprintf "$minute $hour * * %-11s root $cmd\n", $dow;
ac27b58d
DM
179 }
180
181 my $ejobs = $cfg->{ejobs} || [];
182 foreach my $job (@$ejobs) {
183 $out .= "$job->{line}\n" if $job->{line};
184 }
185
186 return $out;
187}
188
189__PACKAGE__->register_method({
190 name => 'index',
191 path => '',
192 method => 'GET',
193 description => "List vzdump backup schedule.",
937515d6
DM
194 permissions => {
195 check => ['perm', '/', ['Sys.Audit']],
196 },
ac27b58d
DM
197 parameters => {
198 additionalProperties => 0,
199 properties => {},
200 },
201 returns => {
202 type => 'array',
203 items => {
204 type => "object",
205 properties => {
206 id => { type => 'string' },
207 },
208 },
209 links => [ { rel => 'child', href => "{id}" } ],
210 },
211 code => sub {
212 my ($param) = @_;
213
214 my $rpcenv = PVE::RPCEnvironment::get();
215 my $user = $rpcenv->get_user();
216
b0905e3a 217 my $data = cfs_read_file('vzdump.cron');
ac27b58d
DM
218
219 my $res = $data->{jobs} || [];
220
221 return $res;
222 }});
223
224__PACKAGE__->register_method({
225 name => 'create_job',
226 path => '',
227 method => 'POST',
228 protected => 1,
229 description => "Create new vzdump backup job.",
937515d6
DM
230 permissions => {
231 check => ['perm', '/', ['Sys.Modify']],
232 },
ac27b58d
DM
233 parameters => {
234 additionalProperties => 0,
235 properties => PVE::VZDump::json_config_properties({
7625ea19
DM
236 starttime => {
237 type => 'string',
238 description => "Job Start time.",
239 pattern => '\d{1,2}:\d{1,2}',
240 typetext => 'HH:MM',
ac27b58d
DM
241 },
242 dow => {
243 type => 'string', format => 'pve-day-of-week-list',
244 optional => 1,
245 description => "Day of week selection.",
246 default => 'mon,tue,wed,thu,fri,sat,sun',
247 },
248 }),
249 },
250 returns => { type => 'null' },
251 code => sub {
252 my ($param) = @_;
253
254 my $rpcenv = PVE::RPCEnvironment::get();
255 my $user = $rpcenv->get_user();
256
b0905e3a 257 my $data = cfs_read_file('vzdump.cron');
ac27b58d 258
ac27b58d
DM
259 $param->{dow} = 'mon,tue,wed,thu,fri,sat,sun' if !defined($param->{dow});
260
31aef761 261 PVE::VZDump::verify_vzdump_parameters($param, 1);
ac27b58d
DM
262
263 push @{$data->{jobs}}, $param;
264
b0905e3a 265 cfs_write_file('vzdump.cron', $data);
ac27b58d
DM
266
267 return undef;
268 }});
269
270__PACKAGE__->register_method({
271 name => 'read_job',
272 path => '{id}',
273 method => 'GET',
274 description => "Read vzdump backup job definition.",
937515d6
DM
275 permissions => {
276 check => ['perm', '/', ['Sys.Audit']],
277 },
ac27b58d
DM
278 parameters => {
279 additionalProperties => 0,
280 properties => {
281 id => {
282 type => 'string',
283 description => "The job ID.",
284 maxLength => 50,
285 }
286 },
287 },
288 returns => {
289 type => 'object',
290 },
291 code => sub {
292 my ($param) = @_;
293
294 my $rpcenv = PVE::RPCEnvironment::get();
295 my $user = $rpcenv->get_user();
296
b0905e3a 297 my $data = cfs_read_file('vzdump.cron');
ac27b58d
DM
298
299 my $jobs = $data->{jobs} || [];
300
301 foreach my $job (@$jobs) {
302 return $job if $job->{id} eq $param->{id};
303 }
304
305 raise_param_exc({ id => "No such job '$param->{id}'" });
306
307 }});
308
309__PACKAGE__->register_method({
310 name => 'delete_job',
311 path => '{id}',
312 method => 'DELETE',
313 description => "Delete vzdump backup job definition.",
937515d6
DM
314 permissions => {
315 check => ['perm', '/', ['Sys.Modify']],
316 },
ac27b58d
DM
317 protected => 1,
318 parameters => {
319 additionalProperties => 0,
320 properties => {
321 id => {
322 type => 'string',
323 description => "The job ID.",
324 maxLength => 50,
325 }
326 },
327 },
328 returns => { type => 'null' },
329 code => sub {
330 my ($param) = @_;
331
332 my $rpcenv = PVE::RPCEnvironment::get();
333 my $user = $rpcenv->get_user();
334
b0905e3a 335 my $data = cfs_read_file('vzdump.cron');
ac27b58d
DM
336
337 my $jobs = $data->{jobs} || [];
338 my $newjobs = [];
339
340 my $found;
341 foreach my $job (@$jobs) {
342 if ($job->{id} eq $param->{id}) {
343 $found = 1;
344 } else {
345 push @$newjobs, $job;
346 }
347 }
348
349 raise_param_exc({ id => "No such job '$param->{id}'" }) if !$found;
350
351 $data->{jobs} = $newjobs;
352
b0905e3a 353 cfs_write_file('vzdump.cron', $data);
ac27b58d
DM
354
355 return undef;
356 }});
357
358__PACKAGE__->register_method({
359 name => 'update_job',
360 path => '{id}',
361 method => 'PUT',
362 protected => 1,
363 description => "Update vzdump backup job definition.",
937515d6
DM
364 permissions => {
365 check => ['perm', '/', ['Sys.Modify']],
366 },
ac27b58d
DM
367 parameters => {
368 additionalProperties => 0,
369 properties => PVE::VZDump::json_config_properties({
370 id => {
371 type => 'string',
372 description => "The job ID.",
373 maxLength => 50,
374 },
7625ea19
DM
375 starttime => {
376 type => 'string',
377 description => "Job Start time.",
378 pattern => '\d{1,2}:\d{1,2}',
379 typetext => 'HH:MM',
ac27b58d
DM
380 },
381 dow => {
382 type => 'string', format => 'pve-day-of-week-list',
383 optional => 1,
384 description => "Day of week selection.",
385 },
53c6bb6c
DM
386 delete => {
387 type => 'string', format => 'pve-configid-list',
388 description => "A list of settings you want to delete.",
389 optional => 1,
390 },
ac27b58d
DM
391 }),
392 },
393 returns => { type => 'null' },
394 code => sub {
395 my ($param) = @_;
396
397 my $rpcenv = PVE::RPCEnvironment::get();
398 my $user = $rpcenv->get_user();
399
b0905e3a 400 my $data = cfs_read_file('vzdump.cron');
ac27b58d
DM
401
402 my $jobs = $data->{jobs} || [];
403
53c6bb6c
DM
404 die "no options specified\n" if !scalar(keys %$param);
405
31aef761 406 PVE::VZDump::verify_vzdump_parameters($param);
ac27b58d 407
31aef761 408 my @delete = PVE::Tools::split_list(extract_param($param, 'delete'));
53c6bb6c 409
ac27b58d
DM
410 foreach my $job (@$jobs) {
411 if ($job->{id} eq $param->{id}) {
412
31aef761 413 foreach my $k (@delete) {
53c6bb6c
DM
414 if (!PVE::VZDump::option_exists($k)) {
415 raise_param_exc({ delete => "unknown option '$k'" });
416 }
417
418 delete $job->{$k};
419 }
420
ac27b58d
DM
421 foreach my $k (keys %$param) {
422 $job->{$k} = $param->{$k};
423 }
424
425 $job->{all} = 1 if defined($job->{exclude});
426
31aef761 427 if (defined($param->{vmid})) {
ac27b58d
DM
428 delete $job->{all};
429 delete $job->{exclude};
430 } elsif ($param->{all}) {
431 delete $job->{vmid};
432 }
433
31aef761 434 PVE::VZDump::verify_vzdump_parameters($job, 1);
ac27b58d 435
b0905e3a 436 cfs_write_file('vzdump.cron', $data);
ac27b58d
DM
437
438 return undef;
439 }
440 }
441
442 raise_param_exc({ id => "No such job '$param->{id}'" });
443
444 }});
445
4461;