]> git.proxmox.com Git - pve-manager.git/blob - PVE/API2/Backup.pm
api/backup: refactor string for all days
[pve-manager.git] / PVE / API2 / Backup.pm
1 package PVE::API2::Backup;
2
3 use strict;
4 use warnings;
5 use Digest::SHA;
6
7 use PVE::SafeSyslog;
8 use PVE::Tools qw(extract_param);
9 use PVE::Cluster qw(cfs_lock_file cfs_read_file cfs_write_file);
10 use PVE::RESTHandler;
11 use PVE::RPCEnvironment;
12 use PVE::JSONSchema;
13 use PVE::Storage;
14 use PVE::Exception qw(raise_param_exc);
15 use PVE::VZDump;
16 use PVE::VZDump::Common;
17
18 use base qw(PVE::RESTHandler);
19
20 use constant ALL_DAYS => 'mon,tue,wed,thu,fri,sat,sun';
21
22 PVE::JSONSchema::register_format('pve-day-of-week', \&verify_day_of_week);
23 sub 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 my $vzdump_job_id_prop = {
34 type => 'string',
35 description => "The job ID.",
36 maxLength => 50
37 };
38
39 my $assert_param_permission = sub {
40 my ($param, $user) = @_;
41 return if $user eq 'root@pam'; # always OK
42
43 for my $key (qw(tmpdir dumpdir script)) {
44 raise_param_exc({ $key => "Only root may set this option."}) if exists $param->{$key};
45 }
46 };
47
48 __PACKAGE__->register_method({
49 name => 'index',
50 path => '',
51 method => 'GET',
52 description => "List vzdump backup schedule.",
53 permissions => {
54 check => ['perm', '/', ['Sys.Audit']],
55 },
56 parameters => {
57 additionalProperties => 0,
58 properties => {},
59 },
60 returns => {
61 type => 'array',
62 items => {
63 type => "object",
64 properties => {
65 id => $vzdump_job_id_prop
66 },
67 },
68 links => [ { rel => 'child', href => "{id}" } ],
69 },
70 code => sub {
71 my ($param) = @_;
72
73 my $rpcenv = PVE::RPCEnvironment::get();
74 my $user = $rpcenv->get_user();
75
76 my $data = cfs_read_file('vzdump.cron');
77
78 my $res = $data->{jobs} || [];
79
80 return $res;
81 }});
82
83 __PACKAGE__->register_method({
84 name => 'create_job',
85 path => '',
86 method => 'POST',
87 protected => 1,
88 description => "Create new vzdump backup job.",
89 permissions => {
90 check => ['perm', '/', ['Sys.Modify']],
91 description => "The 'tmpdir', 'dumpdir' and 'script' parameters are additionally restricted to the 'root\@pam' user.",
92 },
93 parameters => {
94 additionalProperties => 0,
95 properties => PVE::VZDump::Common::json_config_properties({
96 starttime => {
97 type => 'string',
98 description => "Job Start time.",
99 pattern => '\d{1,2}:\d{1,2}',
100 typetext => 'HH:MM',
101 },
102 dow => {
103 type => 'string', format => 'pve-day-of-week-list',
104 optional => 1,
105 description => "Day of week selection.",
106 default => ALL_DAYS,
107 },
108 enabled => {
109 type => 'boolean',
110 optional => 1,
111 description => "Enable or disable the job.",
112 default => '1',
113 },
114 }),
115 },
116 returns => { type => 'null' },
117 code => sub {
118 my ($param) = @_;
119
120 my $rpcenv = PVE::RPCEnvironment::get();
121 my $user = $rpcenv->get_user();
122
123 $assert_param_permission->($param, $user);
124
125 if (my $pool = $param->{pool}) {
126 $rpcenv->check_pool_exist($pool);
127 $rpcenv->check($user, "/pool/$pool", ['VM.Backup']);
128 }
129
130
131 my $create_job = sub {
132 my $data = cfs_read_file('vzdump.cron');
133
134 $param->{dow} = ALL_DAYS if !defined($param->{dow});
135 $param->{enabled} = 1 if !defined($param->{enabled});
136 PVE::VZDump::verify_vzdump_parameters($param, 1);
137
138 push @{$data->{jobs}}, $param;
139
140 cfs_write_file('vzdump.cron', $data);
141 };
142 cfs_lock_file('vzdump.cron', undef, $create_job);
143 die "$@" if ($@);
144
145 return undef;
146 }});
147
148 __PACKAGE__->register_method({
149 name => 'read_job',
150 path => '{id}',
151 method => 'GET',
152 description => "Read vzdump backup job definition.",
153 permissions => {
154 check => ['perm', '/', ['Sys.Audit']],
155 },
156 parameters => {
157 additionalProperties => 0,
158 properties => {
159 id => $vzdump_job_id_prop
160 },
161 },
162 returns => {
163 type => 'object',
164 },
165 code => sub {
166 my ($param) = @_;
167
168 my $rpcenv = PVE::RPCEnvironment::get();
169 my $user = $rpcenv->get_user();
170
171 my $data = cfs_read_file('vzdump.cron');
172
173 my $jobs = $data->{jobs} || [];
174
175 foreach my $job (@$jobs) {
176 return $job if $job->{id} eq $param->{id};
177 }
178
179 raise_param_exc({ id => "No such job '$param->{id}'" });
180
181 }});
182
183 __PACKAGE__->register_method({
184 name => 'delete_job',
185 path => '{id}',
186 method => 'DELETE',
187 description => "Delete vzdump backup job definition.",
188 permissions => {
189 check => ['perm', '/', ['Sys.Modify']],
190 },
191 protected => 1,
192 parameters => {
193 additionalProperties => 0,
194 properties => {
195 id => $vzdump_job_id_prop
196 },
197 },
198 returns => { type => 'null' },
199 code => sub {
200 my ($param) = @_;
201
202 my $rpcenv = PVE::RPCEnvironment::get();
203 my $user = $rpcenv->get_user();
204
205 my $delete_job = sub {
206 my $data = cfs_read_file('vzdump.cron');
207
208 my $jobs = $data->{jobs} || [];
209 my $newjobs = [];
210
211 my $found;
212 foreach my $job (@$jobs) {
213 if ($job->{id} eq $param->{id}) {
214 $found = 1;
215 } else {
216 push @$newjobs, $job;
217 }
218 }
219
220 raise_param_exc({ id => "No such job '$param->{id}'" }) if !$found;
221
222 $data->{jobs} = $newjobs;
223
224 cfs_write_file('vzdump.cron', $data);
225 };
226 cfs_lock_file('vzdump.cron', undef, $delete_job);
227 die "$@" if ($@);
228
229 return undef;
230 }});
231
232 __PACKAGE__->register_method({
233 name => 'update_job',
234 path => '{id}',
235 method => 'PUT',
236 protected => 1,
237 description => "Update vzdump backup job definition.",
238 permissions => {
239 check => ['perm', '/', ['Sys.Modify']],
240 description => "The 'tmpdir', 'dumpdir' and 'script' parameters are additionally restricted to the 'root\@pam' user.",
241 },
242 parameters => {
243 additionalProperties => 0,
244 properties => PVE::VZDump::Common::json_config_properties({
245 id => $vzdump_job_id_prop,
246 starttime => {
247 type => 'string',
248 description => "Job Start time.",
249 pattern => '\d{1,2}:\d{1,2}',
250 typetext => 'HH:MM',
251 },
252 dow => {
253 type => 'string', format => 'pve-day-of-week-list',
254 optional => 1,
255 description => "Day of week selection.",
256 },
257 delete => {
258 type => 'string', format => 'pve-configid-list',
259 description => "A list of settings you want to delete.",
260 optional => 1,
261 },
262 enabled => {
263 type => 'boolean',
264 optional => 1,
265 description => "Enable or disable the job.",
266 default => '1',
267 },
268 }),
269 },
270 returns => { type => 'null' },
271 code => sub {
272 my ($param) = @_;
273
274 my $rpcenv = PVE::RPCEnvironment::get();
275 my $user = $rpcenv->get_user();
276
277 $assert_param_permission->($param, $user);
278
279 if (my $pool = $param->{pool}) {
280 $rpcenv->check_pool_exist($pool);
281 $rpcenv->check($user, "/pool/$pool", ['VM.Backup']);
282 }
283
284 my $update_job = sub {
285 my $data = cfs_read_file('vzdump.cron');
286
287 my $jobs = $data->{jobs} || [];
288
289 die "no options specified\n" if !scalar(keys %$param);
290
291 PVE::VZDump::verify_vzdump_parameters($param);
292
293 my @delete = PVE::Tools::split_list(extract_param($param, 'delete'));
294
295 foreach my $job (@$jobs) {
296 if ($job->{id} eq $param->{id}) {
297
298 foreach my $k (@delete) {
299 if (!PVE::VZDump::option_exists($k)) {
300 raise_param_exc({ delete => "unknown option '$k'" });
301 }
302
303 delete $job->{$k};
304 }
305
306 foreach my $k (keys %$param) {
307 $job->{$k} = $param->{$k};
308 }
309
310 $job->{all} = 1 if (defined($job->{exclude}) && !defined($job->{pool}));
311
312 if (defined($param->{vmid})) {
313 delete $job->{all};
314 delete $job->{exclude};
315 delete $job->{pool};
316 } elsif ($param->{all}) {
317 delete $job->{vmid};
318 delete $job->{pool};
319 } elsif ($job->{pool}) {
320 delete $job->{vmid};
321 delete $job->{all};
322 delete $job->{exclude};
323 }
324
325 PVE::VZDump::verify_vzdump_parameters($job, 1);
326
327 cfs_write_file('vzdump.cron', $data);
328
329 return undef;
330 }
331 }
332 raise_param_exc({ id => "No such job '$param->{id}'" });
333 };
334 cfs_lock_file('vzdump.cron', undef, $update_job);
335 die "$@" if ($@);
336 }});
337
338 __PACKAGE__->register_method({
339 name => 'get_volume_backup_included',
340 path => '{id}/included_volumes',
341 method => 'GET',
342 protected => 1,
343 description => "Returns included guests and the backup status of their disks. Optimized to be used in ExtJS tree views.",
344 permissions => {
345 check => ['perm', '/', ['Sys.Audit']],
346 },
347 parameters => {
348 additionalProperties => 0,
349 properties => {
350 id => $vzdump_job_id_prop
351 },
352 },
353 returns => {
354 type => 'object',
355 description => 'Root node of the tree object. Children represent guests, grandchildren represent volumes of that guest.',
356 properties => {
357 children => {
358 type => 'array',
359 items => {
360 type => 'object',
361 properties => {
362 id => {
363 type => 'integer',
364 description => 'VMID of the guest.',
365 },
366 name => {
367 type => 'string',
368 description => 'Name of the guest',
369 optional => 1,
370 },
371 type => {
372 type => 'string',
373 description => 'Type of the guest, VM, CT or unknown for removed but not purged guests.',
374 enum => ['qemu', 'lxc', 'unknown'],
375 },
376 children => {
377 type => 'array',
378 optional => 1,
379 description => 'The volumes of the guest with the information if they will be included in backups.',
380 items => {
381 type => 'object',
382 properties => {
383 id => {
384 type => 'string',
385 description => 'Configuration key of the volume.',
386 },
387 name => {
388 type => 'string',
389 description => 'Name of the volume.',
390 },
391 included => {
392 type => 'boolean',
393 description => 'Whether the volume is included in the backup or not.',
394 },
395 reason => {
396 type => 'string',
397 description => 'The reason why the volume is included (or excluded).',
398 },
399 },
400 },
401 },
402 },
403 },
404 },
405 },
406 },
407 code => sub {
408 my ($param) = @_;
409
410 my $rpcenv = PVE::RPCEnvironment::get();
411
412 my $user = $rpcenv->get_user();
413
414 my $vzconf = cfs_read_file('vzdump.cron');
415 my $all_jobs = $vzconf->{jobs} || [];
416 my $job;
417 my $rrd = PVE::Cluster::rrd_dump();
418
419 for my $j (@$all_jobs) {
420 if ($j->{id} eq $param->{id}) {
421 $job = $j;
422 last;
423 }
424 }
425 raise_param_exc({ id => "No such job '$param->{id}'" }) if !$job;
426
427 my $vmlist = PVE::Cluster::get_vmlist();
428
429 my @job_vmids;
430
431 my $included_guests = PVE::VZDump::get_included_guests($job);
432
433 for my $node (keys %{$included_guests}) {
434 my $node_vmids = $included_guests->{$node};
435 push(@job_vmids, @{$node_vmids});
436 }
437
438 # remove VMIDs to which the user has no permission to not leak infos
439 # like the guest name
440 my @allowed_vmids = grep {
441 $rpcenv->check($user, "/vms/$_", [ 'VM.Audit' ], 1);
442 } @job_vmids;
443
444 my $result = {
445 children => [],
446 };
447
448 for my $vmid (@allowed_vmids) {
449
450 my $children = [];
451
452 # It's possible that a job has VMIDs configured that are not in
453 # vmlist. This could be because a guest was removed but not purged.
454 # Since there is no more data available we can only deliver the VMID
455 # and no volumes.
456 if (!defined $vmlist->{ids}->{$vmid}) {
457 push(@{$result->{children}}, {
458 id => int($vmid),
459 type => 'unknown',
460 leaf => 1,
461 });
462 next;
463 }
464
465 my $type = $vmlist->{ids}->{$vmid}->{type};
466 my $node = $vmlist->{ids}->{$vmid}->{node};
467
468 my $conf;
469 my $volumes;
470 my $name = "";
471
472 if ($type eq 'qemu') {
473 $conf = PVE::QemuConfig->load_config($vmid, $node);
474 $volumes = PVE::QemuConfig->get_backup_volumes($conf);
475 $name = $conf->{name};
476 } elsif ($type eq 'lxc') {
477 $conf = PVE::LXC::Config->load_config($vmid, $node);
478 $volumes = PVE::LXC::Config->get_backup_volumes($conf);
479 $name = $conf->{hostname};
480 } else {
481 die "VMID $vmid is neither Qemu nor LXC guest\n";
482 }
483
484 foreach my $volume (@$volumes) {
485 my $disk = {
486 # id field must be unique for ExtJS tree view
487 id => "$vmid:$volume->{key}",
488 name => $volume->{volume_config}->{file} // $volume->{volume_config}->{volume},
489 included=> $volume->{included},
490 reason => $volume->{reason},
491 leaf => 1,
492 };
493 push(@{$children}, $disk);
494 }
495
496 my $leaf = 0;
497 # it's possible for a guest to have no volumes configured
498 $leaf = 1 if !@{$children};
499
500 push(@{$result->{children}}, {
501 id => int($vmid),
502 type => $type,
503 name => $name,
504 children => $children,
505 leaf => $leaf,
506 });
507 }
508
509 return $result;
510 }});
511
512 1;