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