]> git.proxmox.com Git - pve-manager.git/blame - PVE/API2/Backup.pm
api: backup: update: check permissions of delete params too
[pve-manager.git] / PVE / API2 / Backup.pm
CommitLineData
ac27b58d
DM
1package PVE::API2::Backup;
2
3use strict;
4use warnings;
52878b0a 5use Digest::SHA;
1c87d344 6use UUID qw(uuid);
ac27b58d
DM
7
8use PVE::SafeSyslog;
9use PVE::Tools qw(extract_param);
2424074e 10use PVE::Cluster qw(cfs_lock_file cfs_read_file cfs_write_file);
ac27b58d
DM
11use PVE::RESTHandler;
12use PVE::RPCEnvironment;
13use PVE::JSONSchema;
14use PVE::Storage;
15use PVE::Exception qw(raise_param_exc);
16use PVE::VZDump;
2424074e 17use PVE::VZDump::Common;
6f2e57c0 18use PVE::VZDump::JobBase;
305921b1 19use PVE::Jobs; # for VZDump Jobs
9ee99910 20use Proxmox::RS::CalendarEvent;
ac27b58d
DM
21
22use base qw(PVE::RESTHandler);
23
85b9ba88
DC
24use constant ALL_DAYS => 'mon,tue,wed,thu,fri,sat,sun';
25
ac27b58d
DM
26PVE::JSONSchema::register_format('pve-day-of-week', \&verify_day_of_week);
27sub verify_day_of_week {
28 my ($value, $noerr) = @_;
29
30 return $value if $value =~ m/^(mon|tue|wed|thu|fri|sat|sun)$/;
31
32 return undef if $noerr;
33
34 die "invalid day '$value'\n";
35}
36
43b2494b
SR
37my $vzdump_job_id_prop = {
38 type => 'string',
39 description => "The job ID.",
40 maxLength => 50
41};
ac27b58d 42
2b233ecc
FE
43# NOTE: also used by the vzdump API call.
44sub assert_param_permission_common {
45 my ($rpcenv, $user, $param) = @_;
2617768f
TL
46 return if $user eq 'root@pam'; # always OK
47
48 for my $key (qw(tmpdir dumpdir script)) {
49 raise_param_exc({ $key => "Only root may set this option."}) if exists $param->{$key};
50 }
2b233ecc
FE
51
52 if (defined($param->{bwlimit}) || defined($param->{ionice}) || defined($param->{performance})) {
53 $rpcenv->check($user, "/", [ 'Sys.Modify' ]);
54 }
55}
2617768f 56
9f65a584
FE
57my sub assert_param_permission_update {
58 my ($rpcenv, $user, $update, $delete) = @_;
59 return if $user eq 'root@pam'; # always OK
60
61 assert_param_permission_common($rpcenv, $user, $update);
62 assert_param_permission_common($rpcenv, $user, $delete);
63}
64
305921b1
DC
65my $convert_to_schedule = sub {
66 my ($job) = @_;
67
68 my $starttime = $job->{starttime};
de92b4d7 69
5cc0c3a0 70 return "$starttime" if !$job->{dow}; # dow is restrictive, so none means all days
305921b1 71
5cc0c3a0
TL
72 # normalize as it could be a null-separated list previously
73 my $dow = join(',', PVE::Tools::split_list($job->{dow}));
305921b1 74
5cc0c3a0 75 return $dow eq ALL_DAYS ? "$starttime" : "$dow $starttime";
305921b1
DC
76};
77
78my $schedule_param_check = sub {
81ba0803 79 my ($param, $required) = @_;
305921b1
DC
80 if (defined($param->{schedule})) {
81 if (defined($param->{starttime})) {
82 raise_param_exc({ starttime => "'starttime' and 'schedule' cannot both be set" });
83 }
84 } elsif (!defined($param->{starttime})) {
81ba0803
DC
85 raise_param_exc({ schedule => "neither 'starttime' nor 'schedule' were set" })
86 if $required;
305921b1
DC
87 } else {
88 $param->{schedule} = $convert_to_schedule->($param);
89 }
90
91 delete $param->{starttime};
92 delete $param->{dow};
93};
94
ac27b58d 95__PACKAGE__->register_method({
60e049c2
TM
96 name => 'index',
97 path => '',
ac27b58d
DM
98 method => 'GET',
99 description => "List vzdump backup schedule.",
937515d6
DM
100 permissions => {
101 check => ['perm', '/', ['Sys.Audit']],
102 },
ac27b58d
DM
103 parameters => {
104 additionalProperties => 0,
105 properties => {},
106 },
107 returns => {
108 type => 'array',
109 items => {
110 type => "object",
111 properties => {
43b2494b 112 id => $vzdump_job_id_prop
ac27b58d
DM
113 },
114 },
115 links => [ { rel => 'child', href => "{id}" } ],
116 },
117 code => sub {
118 my ($param) = @_;
119
120 my $rpcenv = PVE::RPCEnvironment::get();
121 my $user = $rpcenv->get_user();
122
b0905e3a 123 my $data = cfs_read_file('vzdump.cron');
305921b1
DC
124 my $jobs_data = cfs_read_file('jobs.cfg');
125 my $order = $jobs_data->{order};
126 my $jobs = $jobs_data->{ids};
ac27b58d
DM
127
128 my $res = $data->{jobs} || [];
305921b1
DC
129 foreach my $job (@$res) {
130 $job->{schedule} = $convert_to_schedule->($job);
131 }
132
133 foreach my $jobid (sort { $order->{$a} <=> $order->{$b} } keys %$jobs) {
134 my $job = $jobs->{$jobid};
135 next if $job->{type} ne 'vzdump';
c048ad30
DC
136
137 if (my $schedule = $job->{schedule}) {
138 # vzdump jobs are cluster wide, there maybe was no local run
139 # so simply calculate from now
140 my $last_run = time();
9ee99910 141 my $calspec = Proxmox::RS::CalendarEvent->new($schedule);
c048ad30
DC
142 my $next_run = $calspec->compute_next_event($last_run);
143 $job->{'next-run'} = $next_run if defined($next_run);
144 }
145
082e0297
FE
146 # FIXME remove in PVE 8.0?
147 # backwards compat: before moving the job registry to pve-common, id was auto-injected
148 $job->{id} = $jobid;
149
305921b1
DC
150 push @$res, $job;
151 }
ac27b58d
DM
152
153 return $res;
154 }});
155
156__PACKAGE__->register_method({
60e049c2
TM
157 name => 'create_job',
158 path => '',
ac27b58d
DM
159 method => 'POST',
160 protected => 1,
161 description => "Create new vzdump backup job.",
937515d6
DM
162 permissions => {
163 check => ['perm', '/', ['Sys.Modify']],
f0bbc084 164 description => "The 'tmpdir', 'dumpdir' and 'script' parameters are additionally restricted to the 'root\@pam' user.",
937515d6 165 },
ac27b58d
DM
166 parameters => {
167 additionalProperties => 0,
2424074e 168 properties => PVE::VZDump::Common::json_config_properties({
305921b1
DC
169 id => {
170 type => 'string',
171 description => "Job ID (will be autogenerated).",
172 format => 'pve-configid',
173 optional => 1, # FIXME: make required on 8.0
174 },
175 schedule => {
176 description => "Backup schedule. The format is a subset of `systemd` calendar events.",
177 type => 'string', format => 'pve-calendar-event',
178 maxLength => 128,
179 optional => 1,
180 },
7625ea19
DM
181 starttime => {
182 type => 'string',
183 description => "Job Start time.",
184 pattern => '\d{1,2}:\d{1,2}',
185 typetext => 'HH:MM',
305921b1 186 optional => 1,
ac27b58d
DM
187 },
188 dow => {
189 type => 'string', format => 'pve-day-of-week-list',
190 optional => 1,
191 description => "Day of week selection.",
305921b1 192 requires => 'starttime',
85b9ba88 193 default => ALL_DAYS,
ac27b58d 194 },
4341db1d
TL
195 enabled => {
196 type => 'boolean',
197 optional => 1,
198 description => "Enable or disable the job.",
199 default => '1',
200 },
c61c192e
DC
201 'repeat-missed' => {
202 optional => 1,
203 type => 'boolean',
204 description => "If true, the job will be run as soon as possible if it was missed".
205 " while the scheduler was not running.",
206 default => 0,
207 },
998b61fb
DC
208 comment => {
209 optional => 1,
210 type => 'string',
211 description => "Description for the Job.",
212 maxLength => 512,
213 },
ac27b58d
DM
214 }),
215 },
216 returns => { type => 'null' },
217 code => sub {
218 my ($param) = @_;
219
220 my $rpcenv = PVE::RPCEnvironment::get();
221 my $user = $rpcenv->get_user();
222
2b233ecc 223 assert_param_permission_common($rpcenv, $user, $param);
f0bbc084 224
c92c54d5
TL
225 if (my $pool = $param->{pool}) {
226 $rpcenv->check_pool_exist($pool);
227 $rpcenv->check($user, "/pool/$pool", ['VM.Backup']);
228 }
229
81ba0803 230 $schedule_param_check->($param, 1);
c92c54d5 231
305921b1
DC
232 $param->{enabled} = 1 if !defined($param->{enabled});
233
234 # autogenerate id for api compatibility FIXME remove with 8.0
ca62f164 235 my $id = extract_param($param, 'id') // UUID::uuid();
305921b1
DC
236
237 cfs_lock_file('jobs.cfg', undef, sub {
238 my $data = cfs_read_file('jobs.cfg');
239
240 die "Job '$id' already exists\n"
241 if $data->{ids}->{$id};
ac27b58d 242
200cef80 243 PVE::VZDump::verify_vzdump_parameters($param, 1);
6f2e57c0 244 my $opts = PVE::VZDump::JobBase->check_config($id, $param, 1, 1);
ac27b58d 245
305921b1 246 $data->{ids}->{$id} = $opts;
ac27b58d 247
2cf7706e 248 PVE::Jobs::create_job($id, 'vzdump', $opts);
305921b1
DC
249
250 cfs_write_file('jobs.cfg', $data);
251 });
200cef80 252 die "$@" if ($@);
ac27b58d
DM
253
254 return undef;
255 }});
256
257__PACKAGE__->register_method({
60e049c2
TM
258 name => 'read_job',
259 path => '{id}',
ac27b58d
DM
260 method => 'GET',
261 description => "Read vzdump backup job definition.",
937515d6
DM
262 permissions => {
263 check => ['perm', '/', ['Sys.Audit']],
264 },
ac27b58d
DM
265 parameters => {
266 additionalProperties => 0,
267 properties => {
43b2494b 268 id => $vzdump_job_id_prop
ac27b58d
DM
269 },
270 },
271 returns => {
272 type => 'object',
273 },
274 code => sub {
275 my ($param) = @_;
276
277 my $rpcenv = PVE::RPCEnvironment::get();
278 my $user = $rpcenv->get_user();
279
b0905e3a 280 my $data = cfs_read_file('vzdump.cron');
ac27b58d
DM
281
282 my $jobs = $data->{jobs} || [];
283
284 foreach my $job (@$jobs) {
305921b1
DC
285 if ($job->{id} eq $param->{id}) {
286 $job->{schedule} = $convert_to_schedule->($job);
287 return $job;
288 }
ac27b58d
DM
289 }
290
305921b1
DC
291 my $jobs_data = cfs_read_file('jobs.cfg');
292 my $job = $jobs_data->{ids}->{$param->{id}};
082e0297
FE
293 if ($job && $job->{type} eq 'vzdump') {
294 # FIXME remove in PVE 8.0?
295 # backwards compat: before moving the job registry to pve-common, id was auto-injected
296 $job->{id} = $param->{id};
297 return $job;
298 }
305921b1 299
ac27b58d
DM
300 raise_param_exc({ id => "No such job '$param->{id}'" });
301
302 }});
303
304__PACKAGE__->register_method({
60e049c2
TM
305 name => 'delete_job',
306 path => '{id}',
ac27b58d
DM
307 method => 'DELETE',
308 description => "Delete vzdump backup job definition.",
937515d6
DM
309 permissions => {
310 check => ['perm', '/', ['Sys.Modify']],
311 },
ac27b58d
DM
312 protected => 1,
313 parameters => {
314 additionalProperties => 0,
315 properties => {
43b2494b 316 id => $vzdump_job_id_prop
ac27b58d
DM
317 },
318 },
319 returns => { type => 'null' },
320 code => sub {
321 my ($param) = @_;
322
323 my $rpcenv = PVE::RPCEnvironment::get();
324 my $user = $rpcenv->get_user();
325
305921b1
DC
326 my $id = $param->{id};
327
200cef80
CE
328 my $delete_job = sub {
329 my $data = cfs_read_file('vzdump.cron');
ac27b58d 330
200cef80
CE
331 my $jobs = $data->{jobs} || [];
332 my $newjobs = [];
ac27b58d 333
200cef80
CE
334 my $found;
335 foreach my $job (@$jobs) {
305921b1 336 if ($job->{id} eq $id) {
200cef80
CE
337 $found = 1;
338 } else {
339 push @$newjobs, $job;
340 }
ac27b58d 341 }
ac27b58d 342
305921b1
DC
343 if (!$found) {
344 cfs_lock_file('jobs.cfg', undef, sub {
345 my $jobs_data = cfs_read_file('jobs.cfg');
346
347 if (!defined($jobs_data->{ids}->{$id})) {
348 raise_param_exc({ id => "No such job '$id'" });
349 }
350 delete $jobs_data->{ids}->{$id};
351
352 PVE::Jobs::remove_job($id, 'vzdump');
ac27b58d 353
305921b1
DC
354 cfs_write_file('jobs.cfg', $jobs_data);
355 });
356 die "$@" if $@;
357 } else {
358 $data->{jobs} = $newjobs;
ac27b58d 359
305921b1
DC
360 cfs_write_file('vzdump.cron', $data);
361 }
200cef80
CE
362 };
363 cfs_lock_file('vzdump.cron', undef, $delete_job);
364 die "$@" if ($@);
ac27b58d
DM
365
366 return undef;
367 }});
368
369__PACKAGE__->register_method({
60e049c2
TM
370 name => 'update_job',
371 path => '{id}',
ac27b58d
DM
372 method => 'PUT',
373 protected => 1,
374 description => "Update vzdump backup job definition.",
937515d6
DM
375 permissions => {
376 check => ['perm', '/', ['Sys.Modify']],
e6d963ca 377 description => "The 'tmpdir', 'dumpdir' and 'script' parameters are additionally restricted to the 'root\@pam' user.",
937515d6 378 },
ac27b58d
DM
379 parameters => {
380 additionalProperties => 0,
2424074e 381 properties => PVE::VZDump::Common::json_config_properties({
43b2494b 382 id => $vzdump_job_id_prop,
305921b1
DC
383 schedule => {
384 description => "Backup schedule. The format is a subset of `systemd` calendar events.",
385 type => 'string', format => 'pve-calendar-event',
386 maxLength => 128,
387 optional => 1,
388 },
7625ea19
DM
389 starttime => {
390 type => 'string',
391 description => "Job Start time.",
392 pattern => '\d{1,2}:\d{1,2}',
393 typetext => 'HH:MM',
305921b1 394 optional => 1,
ac27b58d
DM
395 },
396 dow => {
397 type => 'string', format => 'pve-day-of-week-list',
398 optional => 1,
305921b1 399 requires => 'starttime',
ac27b58d
DM
400 description => "Day of week selection.",
401 },
53c6bb6c
DM
402 delete => {
403 type => 'string', format => 'pve-configid-list',
404 description => "A list of settings you want to delete.",
405 optional => 1,
406 },
4341db1d
TL
407 enabled => {
408 type => 'boolean',
409 optional => 1,
410 description => "Enable or disable the job.",
411 default => '1',
412 },
c61c192e
DC
413 'repeat-missed' => {
414 optional => 1,
415 type => 'boolean',
416 description => "If true, the job will be run as soon as possible if it was missed".
417 " while the scheduler was not running.",
418 default => 0,
419 },
998b61fb
DC
420 comment => {
421 optional => 1,
422 type => 'string',
423 description => "Description for the Job.",
424 maxLength => 512,
425 },
ac27b58d
DM
426 }),
427 },
428 returns => { type => 'null' },
429 code => sub {
430 my ($param) = @_;
431
432 my $rpcenv = PVE::RPCEnvironment::get();
433 my $user = $rpcenv->get_user();
434
16f5b283
TL
435 if (my $pool = $param->{pool}) {
436 $rpcenv->check_pool_exist($pool);
437 $rpcenv->check($user, "/pool/$pool", ['VM.Backup']);
438 }
439
305921b1
DC
440 $schedule_param_check->($param);
441
442 my $id = extract_param($param, 'id');
443 my $delete = extract_param($param, 'delete');
bda3f2aa 444 $delete = { map { $_ => 1 } PVE::Tools::split_list($delete) } if $delete;
305921b1 445
9f65a584
FE
446 assert_param_permission_update($rpcenv, $user, $param, $delete);
447
200cef80
CE
448 my $update_job = sub {
449 my $data = cfs_read_file('vzdump.cron');
305921b1 450 my $jobs_data = cfs_read_file('jobs.cfg');
ac27b58d 451
200cef80 452 my $jobs = $data->{jobs} || [];
ac27b58d 453
659032f4 454 die "no options specified\n" if !scalar(keys $param->%*) && !scalar(keys $delete->%*);
53c6bb6c 455
200cef80 456 PVE::VZDump::verify_vzdump_parameters($param);
6f2e57c0 457 my $opts = PVE::VZDump::JobBase->check_config($id, $param, 0, 1);
305921b1
DC
458
459 # try to find it in old vzdump.cron and convert it to a job
460 my ($idx) = grep { $jobs->[$_]->{id} eq $id } (0 .. scalar(@$jobs) - 1);
461
462 my $job;
463 if (defined($idx)) {
464 $job = splice @$jobs, $idx, 1;
465 $job->{schedule} = $convert_to_schedule->($job);
466 delete $job->{starttime};
467 delete $job->{dow};
468 delete $job->{id};
469 $job->{type} = 'vzdump';
470 $jobs_data->{ids}->{$id} = $job;
471 } else {
472 $job = $jobs_data->{ids}->{$id};
473 die "no such vzdump job\n" if !$job || $job->{type} ne 'vzdump';
474 }
ac27b58d 475
c61c192e
DC
476 my $deletable = {
477 comment => 1,
478 'repeat-missed' => 1,
479 };
480
bda3f2aa 481 for my $k (keys $delete->%*) {
c61c192e 482 if (!PVE::VZDump::option_exists($k) && !$deletable->{$k}) {
305921b1
DC
483 raise_param_exc({ delete => "unknown option '$k'" });
484 }
ac27b58d 485
305921b1
DC
486 delete $job->{$k};
487 }
53c6bb6c 488
305921b1
DC
489 foreach my $k (keys %$param) {
490 $job->{$k} = $param->{$k};
491 }
ac27b58d 492
305921b1
DC
493 $job->{all} = 1 if (defined($job->{exclude}) && !defined($job->{pool}));
494
495 if (defined($param->{vmid})) {
496 delete $job->{all};
497 delete $job->{exclude};
498 delete $job->{pool};
499 } elsif ($param->{all}) {
500 delete $job->{vmid};
501 delete $job->{pool};
502 } elsif ($job->{pool}) {
503 delete $job->{vmid};
504 delete $job->{all};
505 delete $job->{exclude};
506 }
ac27b58d 507
305921b1 508 PVE::VZDump::verify_vzdump_parameters($job, 1);
ac27b58d 509
305921b1
DC
510 if (defined($idx)) {
511 cfs_write_file('vzdump.cron', $data);
ac27b58d 512 }
305921b1 513 cfs_write_file('jobs.cfg', $jobs_data);
2cf7706e
DC
514
515 PVE::Jobs::detect_changed_runtime_props($id, 'vzdump', $job);
516
305921b1 517 return;
200cef80 518 };
305921b1
DC
519 cfs_lock_file('vzdump.cron', undef, sub {
520 cfs_lock_file('jobs.cfg', undef, $update_job);
521 die "$@" if ($@);
522 });
200cef80 523 die "$@" if ($@);
ac27b58d
DM
524 }});
525
ac0fe8b6
AL
526__PACKAGE__->register_method({
527 name => 'get_volume_backup_included',
528 path => '{id}/included_volumes',
529 method => 'GET',
530 protected => 1,
531 description => "Returns included guests and the backup status of their disks. Optimized to be used in ExtJS tree views.",
532 permissions => {
533 check => ['perm', '/', ['Sys.Audit']],
534 },
535 parameters => {
536 additionalProperties => 0,
537 properties => {
538 id => $vzdump_job_id_prop
539 },
540 },
541 returns => {
542 type => 'object',
543 description => 'Root node of the tree object. Children represent guests, grandchildren represent volumes of that guest.',
544 properties => {
545 children => {
546 type => 'array',
547 items => {
548 type => 'object',
549 properties => {
550 id => {
551 type => 'integer',
552 description => 'VMID of the guest.',
553 },
554 name => {
555 type => 'string',
556 description => 'Name of the guest',
557 optional => 1,
558 },
559 type => {
560 type => 'string',
561 description => 'Type of the guest, VM, CT or unknown for removed but not purged guests.',
562 enum => ['qemu', 'lxc', 'unknown'],
563 },
564 children => {
565 type => 'array',
566 optional => 1,
567 description => 'The volumes of the guest with the information if they will be included in backups.',
568 items => {
569 type => 'object',
570 properties => {
571 id => {
572 type => 'string',
573 description => 'Configuration key of the volume.',
574 },
575 name => {
576 type => 'string',
577 description => 'Name of the volume.',
578 },
579 included => {
580 type => 'boolean',
581 description => 'Whether the volume is included in the backup or not.',
582 },
583 reason => {
584 type => 'string',
585 description => 'The reason why the volume is included (or excluded).',
586 },
587 },
588 },
589 },
590 },
591 },
592 },
593 },
594 },
595 code => sub {
596 my ($param) = @_;
597
598 my $rpcenv = PVE::RPCEnvironment::get();
599
600 my $user = $rpcenv->get_user();
601
602 my $vzconf = cfs_read_file('vzdump.cron');
603 my $all_jobs = $vzconf->{jobs} || [];
604 my $job;
605 my $rrd = PVE::Cluster::rrd_dump();
606
607 for my $j (@$all_jobs) {
608 if ($j->{id} eq $param->{id}) {
609 $job = $j;
610 last;
611 }
612 }
305921b1
DC
613 if (!$job) {
614 my $jobs_data = cfs_read_file('jobs.cfg');
615 my $j = $jobs_data->{ids}->{$param->{id}};
616 if ($j && $j->{type} eq 'vzdump') {
617 $job = $j;
618 }
619 }
ac0fe8b6
AL
620 raise_param_exc({ id => "No such job '$param->{id}'" }) if !$job;
621
622 my $vmlist = PVE::Cluster::get_vmlist();
623
624 my @job_vmids;
625
626 my $included_guests = PVE::VZDump::get_included_guests($job);
627
628 for my $node (keys %{$included_guests}) {
629 my $node_vmids = $included_guests->{$node};
630 push(@job_vmids, @{$node_vmids});
631 }
632
633 # remove VMIDs to which the user has no permission to not leak infos
634 # like the guest name
635 my @allowed_vmids = grep {
636 $rpcenv->check($user, "/vms/$_", [ 'VM.Audit' ], 1);
637 } @job_vmids;
638
639 my $result = {
640 children => [],
641 };
642
643 for my $vmid (@allowed_vmids) {
644
645 my $children = [];
646
647 # It's possible that a job has VMIDs configured that are not in
648 # vmlist. This could be because a guest was removed but not purged.
649 # Since there is no more data available we can only deliver the VMID
650 # and no volumes.
651 if (!defined $vmlist->{ids}->{$vmid}) {
652 push(@{$result->{children}}, {
653 id => int($vmid),
654 type => 'unknown',
655 leaf => 1,
656 });
657 next;
658 }
659
660 my $type = $vmlist->{ids}->{$vmid}->{type};
661 my $node = $vmlist->{ids}->{$vmid}->{node};
662
663 my $conf;
664 my $volumes;
665 my $name = "";
666
667 if ($type eq 'qemu') {
668 $conf = PVE::QemuConfig->load_config($vmid, $node);
669 $volumes = PVE::QemuConfig->get_backup_volumes($conf);
670 $name = $conf->{name};
671 } elsif ($type eq 'lxc') {
672 $conf = PVE::LXC::Config->load_config($vmid, $node);
673 $volumes = PVE::LXC::Config->get_backup_volumes($conf);
674 $name = $conf->{hostname};
675 } else {
676 die "VMID $vmid is neither Qemu nor LXC guest\n";
677 }
678
679 foreach my $volume (@$volumes) {
680 my $disk = {
681 # id field must be unique for ExtJS tree view
682 id => "$vmid:$volume->{key}",
683 name => $volume->{volume_config}->{file} // $volume->{volume_config}->{volume},
684 included=> $volume->{included},
685 reason => $volume->{reason},
686 leaf => 1,
687 };
688 push(@{$children}, $disk);
689 }
690
691 my $leaf = 0;
692 # it's possible for a guest to have no volumes configured
693 $leaf = 1 if !@{$children};
694
695 push(@{$result->{children}}, {
696 id => int($vmid),
697 type => $type,
698 name => $name,
699 children => $children,
700 leaf => $leaf,
701 });
702 }
703
704 return $result;
705 }});
706
ac27b58d 7071;