]> git.proxmox.com Git - qemu-server.git/blame - PVE/QemuServer/Drive.pm
schema: drive: use separate schema when disk allocation is possible
[qemu-server.git] / PVE / QemuServer / Drive.pm
CommitLineData
e0fd2b2f
FE
1package PVE::QemuServer::Drive;
2
3use strict;
4use warnings;
5
c1accf9d
FE
6use Storable qw(dclone);
7
e0fd2b2f
FE
8use PVE::Storage;
9use PVE::JSONSchema qw(get_standard_option);
10
11use base qw(Exporter);
12
13our @EXPORT_OK = qw(
14is_valid_drivename
15drive_is_cloudinit
16drive_is_cdrom
75748d44 17drive_is_read_only
e0fd2b2f
FE
18parse_drive
19print_drive
e0fd2b2f
FE
20);
21
22our $QEMU_FORMAT_RE = qr/raw|cow|qcow|qcow2|qed|vmdk|cloop/;
23
24PVE::JSONSchema::register_standard_option('pve-qm-image-format', {
25 type => 'string',
26 enum => [qw(raw cow qcow qed qcow2 vmdk cloop)],
27 description => "The drive's backing file's data format.",
28 optional => 1,
29});
30
31my $MAX_IDE_DISKS = 4;
32my $MAX_SCSI_DISKS = 31;
33my $MAX_VIRTIO_DISKS = 16;
34our $MAX_SATA_DISKS = 6;
35our $MAX_UNUSED_DISKS = 256;
36
37our $drivedesc_hash;
c1accf9d
FE
38# Schema when disk allocation is possible.
39our $drivedesc_hash_with_alloc = {};
e0fd2b2f
FE
40
41my %drivedesc_base = (
42 volume => { alias => 'file' },
43 file => {
44 type => 'string',
45 format => 'pve-volume-id-or-qm-path',
46 default_key => 1,
47 format_description => 'volume',
48 description => "The drive's backing volume.",
49 },
50 media => {
51 type => 'string',
52 enum => [qw(cdrom disk)],
53 description => "The drive's media type.",
54 default => 'disk',
55 optional => 1
56 },
57 cyls => {
58 type => 'integer',
59 description => "Force the drive's physical geometry to have a specific cylinder count.",
60 optional => 1
61 },
62 heads => {
63 type => 'integer',
64 description => "Force the drive's physical geometry to have a specific head count.",
65 optional => 1
66 },
67 secs => {
68 type => 'integer',
69 description => "Force the drive's physical geometry to have a specific sector count.",
70 optional => 1
71 },
72 trans => {
73 type => 'string',
74 enum => [qw(none lba auto)],
75 description => "Force disk geometry bios translation mode.",
76 optional => 1,
77 },
78 snapshot => {
79 type => 'boolean',
80 description => "Controls qemu's snapshot mode feature."
81 . " If activated, changes made to the disk are temporary and will"
82 . " be discarded when the VM is shutdown.",
83 optional => 1,
84 },
85 cache => {
86 type => 'string',
87 enum => [qw(none writethrough writeback unsafe directsync)],
88 description => "The drive's cache mode",
89 optional => 1,
90 },
91 format => get_standard_option('pve-qm-image-format'),
92 size => {
93 type => 'string',
94 format => 'disk-size',
95 format_description => 'DiskSize',
96 description => "Disk size. This is purely informational and has no effect.",
97 optional => 1,
98 },
99 backup => {
100 type => 'boolean',
101 description => "Whether the drive should be included when making backups.",
102 optional => 1,
103 },
104 replicate => {
105 type => 'boolean',
106 description => 'Whether the drive should considered for replication jobs.',
107 optional => 1,
108 default => 1,
109 },
110 rerror => {
111 type => 'string',
112 enum => [qw(ignore report stop)],
113 description => 'Read error action.',
114 optional => 1,
115 },
116 werror => {
117 type => 'string',
118 enum => [qw(enospc ignore report stop)],
119 description => 'Write error action.',
120 optional => 1,
121 },
122 aio => {
123 type => 'string',
59e59342 124 enum => [qw(native threads io_uring)],
e0fd2b2f
FE
125 description => 'AIO type to use.',
126 optional => 1,
127 },
128 discard => {
129 type => 'string',
130 enum => [qw(ignore on)],
131 description => 'Controls whether to pass discard/trim requests to the underlying storage.',
132 optional => 1,
133 },
134 detect_zeroes => {
135 type => 'boolean',
136 description => 'Controls whether to detect and try to optimize writes of zeroes.',
137 optional => 1,
138 },
139 serial => {
140 type => 'string',
141 format => 'urlencoded',
142 format_description => 'serial',
143 maxLength => 20*3, # *3 since it's %xx url enoded
144 description => "The drive's reported serial number, url-encoded, up to 20 bytes long.",
145 optional => 1,
146 },
147 shared => {
148 type => 'boolean',
149 description => 'Mark this locally-managed volume as available on all nodes',
150 verbose_description => "Mark this locally-managed volume as available on all nodes.\n\nWARNING: This option does not share the volume automatically, it assumes it is shared already!",
151 optional => 1,
152 default => 0,
153 }
154);
155
156my %iothread_fmt = ( iothread => {
157 type => 'boolean',
158 description => "Whether to use iothreads for this drive",
159 optional => 1,
160});
161
162my %model_fmt = (
163 model => {
164 type => 'string',
165 format => 'urlencoded',
166 format_description => 'model',
167 maxLength => 40*3, # *3 since it's %xx url enoded
168 description => "The drive's reported model name, url-encoded, up to 40 bytes long.",
169 optional => 1,
170 },
171);
172
173my %queues_fmt = (
174 queues => {
175 type => 'integer',
176 description => "Number of queues.",
177 minimum => 2,
178 optional => 1
179 }
180);
181
12e1d472
DC
182my %readonly_fmt = (
183 ro => {
184 type => 'boolean',
185 description => "Whether the drive is read-only.",
186 optional => 1,
187 },
188);
189
e0fd2b2f
FE
190my %scsiblock_fmt = (
191 scsiblock => {
192 type => 'boolean',
193 description => "whether to use scsi-block for full passthrough of host block device\n\nWARNING: can lead to I/O errors in combination with low memory or high memory fragmentation on host",
194 optional => 1,
195 default => 0,
196 },
197);
198
199my %ssd_fmt = (
200 ssd => {
201 type => 'boolean',
202 description => "Whether to expose this drive as an SSD, rather than a rotational hard disk.",
203 optional => 1,
204 },
205);
206
207my %wwn_fmt = (
208 wwn => {
209 type => 'string',
210 pattern => qr/^(0x)[0-9a-fA-F]{16}/,
211 format_description => 'wwn',
212 description => "The drive's worldwide name, encoded as 16 bytes hex string, prefixed by '0x'.",
213 optional => 1,
214 },
215);
216
217my $add_throttle_desc = sub {
218 my ($key, $type, $what, $unit, $longunit, $minimum) = @_;
219 my $d = {
220 type => $type,
221 format_description => $unit,
222 description => "Maximum $what in $longunit.",
223 optional => 1,
224 };
225 $d->{minimum} = $minimum if defined($minimum);
226 $drivedesc_base{$key} = $d;
227};
228# throughput: (leaky bucket)
229$add_throttle_desc->('bps', 'integer', 'r/w speed', 'bps', 'bytes per second');
230$add_throttle_desc->('bps_rd', 'integer', 'read speed', 'bps', 'bytes per second');
231$add_throttle_desc->('bps_wr', 'integer', 'write speed', 'bps', 'bytes per second');
232$add_throttle_desc->('mbps', 'number', 'r/w speed', 'mbps', 'megabytes per second');
233$add_throttle_desc->('mbps_rd', 'number', 'read speed', 'mbps', 'megabytes per second');
234$add_throttle_desc->('mbps_wr', 'number', 'write speed', 'mbps', 'megabytes per second');
235$add_throttle_desc->('iops', 'integer', 'r/w I/O', 'iops', 'operations per second');
236$add_throttle_desc->('iops_rd', 'integer', 'read I/O', 'iops', 'operations per second');
237$add_throttle_desc->('iops_wr', 'integer', 'write I/O', 'iops', 'operations per second');
238
239# pools: (pool of IO before throttling starts taking effect)
240$add_throttle_desc->('mbps_max', 'number', 'unthrottled r/w pool', 'mbps', 'megabytes per second');
241$add_throttle_desc->('mbps_rd_max', 'number', 'unthrottled read pool', 'mbps', 'megabytes per second');
242$add_throttle_desc->('mbps_wr_max', 'number', 'unthrottled write pool', 'mbps', 'megabytes per second');
243$add_throttle_desc->('iops_max', 'integer', 'unthrottled r/w I/O pool', 'iops', 'operations per second');
244$add_throttle_desc->('iops_rd_max', 'integer', 'unthrottled read I/O pool', 'iops', 'operations per second');
245$add_throttle_desc->('iops_wr_max', 'integer', 'unthrottled write I/O pool', 'iops', 'operations per second');
246
247# burst lengths
248$add_throttle_desc->('bps_max_length', 'integer', 'length of I/O bursts', 'seconds', 'seconds', 1);
249$add_throttle_desc->('bps_rd_max_length', 'integer', 'length of read I/O bursts', 'seconds', 'seconds', 1);
250$add_throttle_desc->('bps_wr_max_length', 'integer', 'length of write I/O bursts', 'seconds', 'seconds', 1);
251$add_throttle_desc->('iops_max_length', 'integer', 'length of I/O bursts', 'seconds', 'seconds', 1);
252$add_throttle_desc->('iops_rd_max_length', 'integer', 'length of read I/O bursts', 'seconds', 'seconds', 1);
253$add_throttle_desc->('iops_wr_max_length', 'integer', 'length of write I/O bursts', 'seconds', 'seconds', 1);
254
255# legacy support
256$drivedesc_base{'bps_rd_length'} = { alias => 'bps_rd_max_length' };
257$drivedesc_base{'bps_wr_length'} = { alias => 'bps_wr_max_length' };
258$drivedesc_base{'iops_rd_length'} = { alias => 'iops_rd_max_length' };
259$drivedesc_base{'iops_wr_length'} = { alias => 'iops_wr_max_length' };
260
261my $ide_fmt = {
262 %drivedesc_base,
263 %model_fmt,
264 %ssd_fmt,
265 %wwn_fmt,
266};
267PVE::JSONSchema::register_format("pve-qm-ide", $ide_fmt);
268
269my $idedesc = {
270 optional => 1,
271 type => 'string', format => $ide_fmt,
c1accf9d 272 description => "Use volume as IDE hard disk or CD-ROM (n is 0 to " .($MAX_IDE_DISKS - 1) . ").",
e0fd2b2f
FE
273};
274PVE::JSONSchema::register_standard_option("pve-qm-ide", $idedesc);
275
276my $scsi_fmt = {
277 %drivedesc_base,
278 %iothread_fmt,
279 %queues_fmt,
12e1d472 280 %readonly_fmt,
e0fd2b2f
FE
281 %scsiblock_fmt,
282 %ssd_fmt,
283 %wwn_fmt,
284};
285my $scsidesc = {
286 optional => 1,
287 type => 'string', format => $scsi_fmt,
c1accf9d 288 description => "Use volume as SCSI hard disk or CD-ROM (n is 0 to " . ($MAX_SCSI_DISKS - 1) . ").",
e0fd2b2f
FE
289};
290PVE::JSONSchema::register_standard_option("pve-qm-scsi", $scsidesc);
291
292my $sata_fmt = {
293 %drivedesc_base,
294 %ssd_fmt,
295 %wwn_fmt,
296};
297my $satadesc = {
298 optional => 1,
299 type => 'string', format => $sata_fmt,
c1accf9d 300 description => "Use volume as SATA hard disk or CD-ROM (n is 0 to " . ($MAX_SATA_DISKS - 1). ").",
e0fd2b2f
FE
301};
302PVE::JSONSchema::register_standard_option("pve-qm-sata", $satadesc);
303
304my $virtio_fmt = {
305 %drivedesc_base,
306 %iothread_fmt,
12e1d472 307 %readonly_fmt,
e0fd2b2f
FE
308};
309my $virtiodesc = {
310 optional => 1,
311 type => 'string', format => $virtio_fmt,
c1accf9d 312 description => "Use volume as VIRTIO hard disk (n is 0 to " . ($MAX_VIRTIO_DISKS - 1) . ").",
e0fd2b2f
FE
313};
314PVE::JSONSchema::register_standard_option("pve-qm-virtio", $virtiodesc);
315
b5099b4f
SR
316my %efitype_fmt = (
317 efitype => {
318 type => 'string',
319 enum => [qw(2m 4m)],
320 description => "Size and type of the OVMF EFI vars. '4m' is newer and recommended,"
321 . " and required for Secure Boot. For backwards compatibility, '2m' is used"
322 . " if not otherwise specified.",
323 optional => 1,
324 default => '2m',
325 },
a064e511 326 'pre-enrolled-keys' => {
b5099b4f 327 type => 'boolean',
a064e511
TL
328 description => "Use am EFI vars template with distribution-specific and Microsoft Standard"
329 ." keys enrolled, if used with 'efitype=4m'. Note that this will enable Secure Boot by"
330 ." default, though it can still be turned off from within the VM.",
b5099b4f
SR
331 optional => 1,
332 default => 0,
333 },
334);
335
e0fd2b2f
FE
336my $efidisk_fmt = {
337 volume => { alias => 'file' },
338 file => {
339 type => 'string',
340 format => 'pve-volume-id-or-qm-path',
341 default_key => 1,
342 format_description => 'volume',
343 description => "The drive's backing volume.",
344 },
345 format => get_standard_option('pve-qm-image-format'),
346 size => {
347 type => 'string',
348 format => 'disk-size',
349 format_description => 'DiskSize',
350 description => "Disk size. This is purely informational and has no effect.",
351 optional => 1,
352 },
b5099b4f 353 %efitype_fmt,
e0fd2b2f
FE
354};
355
356my $efidisk_desc = {
357 optional => 1,
358 type => 'string', format => $efidisk_fmt,
c1accf9d 359 description => "Configure a Disk for storing EFI vars.",
e0fd2b2f
FE
360};
361
362PVE::JSONSchema::register_standard_option("pve-qm-efidisk", $efidisk_desc);
363
f9dde219
SR
364my %tpmversion_fmt = (
365 version => {
366 type => 'string',
367 enum => [qw(v1.2 v2.0)],
2b9ee944
TL
368 description => "The TPM interface version. v2.0 is newer and should be preferred."
369 ." Note that this cannot be changed later on.",
f9dde219
SR
370 optional => 1,
371 default => 'v2.0',
372 },
373);
374my $tpmstate_fmt = {
375 volume => { alias => 'file' },
376 file => {
377 type => 'string',
378 format => 'pve-volume-id-or-qm-path',
379 default_key => 1,
380 format_description => 'volume',
381 description => "The drive's backing volume.",
382 },
383 size => {
384 type => 'string',
385 format => 'disk-size',
386 format_description => 'DiskSize',
387 description => "Disk size. This is purely informational and has no effect.",
388 optional => 1,
389 },
390 %tpmversion_fmt,
391};
392my $tpmstate_desc = {
393 optional => 1,
394 type => 'string', format => $tpmstate_fmt,
c1accf9d 395 description => "Configure a Disk for storing TPM state. The format is fixed to 'raw'.",
f9dde219
SR
396};
397use constant TPMSTATE_DISK_SIZE => 4 * 1024 * 1024;
398
399my $alldrive_fmt = {
400 %drivedesc_base,
401 %iothread_fmt,
402 %model_fmt,
403 %queues_fmt,
12e1d472 404 %readonly_fmt,
f9dde219
SR
405 %scsiblock_fmt,
406 %ssd_fmt,
407 %wwn_fmt,
408 %tpmversion_fmt,
b5099b4f 409 %efitype_fmt,
f9dde219
SR
410};
411
c1accf9d
FE
412my $alldrive_fmt_with_alloc = {
413 %$alldrive_fmt,
414};
415
43c4c7b6
FE
416my $unused_fmt = {
417 volume => { alias => 'file' },
418 file => {
419 type => 'string',
420 format => 'pve-volume-id',
421 default_key => 1,
422 format_description => 'volume',
423 description => "The drive's backing volume.",
424 },
425};
426
427my $unuseddesc = {
428 optional => 1,
429 type => 'string', format => $unused_fmt,
430 description => "Reference to unused volumes. This is used internally, and should not be modified manually.",
431};
432
c1accf9d
FE
433my $with_alloc_desc_cache = {
434 unused => $unuseddesc, # Allocation for unused is not supported currently.
435};
436my $desc_with_alloc = sub {
437 my ($type, $desc) = @_;
438
439 return $with_alloc_desc_cache->{$type} if $with_alloc_desc_cache->{$type};
440
441 my $new_desc = dclone($desc);
442
443 my $extra_note = '';
444 if ($type eq 'efidisk') {
445 $extra_note = " Note that SIZE_IN_GiB is ignored here and that the default EFI vars are ".
446 "copied to the volume instead.";
447 } elsif ($type eq 'tpmstate') {
448 $extra_note = " Note that SIZE_IN_GiB is ignored here and 4 MiB will be used instead.";
449 }
450
451 $new_desc->{description} .= " Use the special syntax STORAGE_ID:SIZE_IN_GiB to allocate a new ".
452 "volume.${extra_note}";
453
454 $with_alloc_desc_cache->{$type} = $new_desc;
455
456 return $new_desc;
457};
458
e0fd2b2f
FE
459for (my $i = 0; $i < $MAX_IDE_DISKS; $i++) {
460 $drivedesc_hash->{"ide$i"} = $idedesc;
c1accf9d 461 $drivedesc_hash_with_alloc->{"ide$i"} = $desc_with_alloc->('ide', $idedesc);
e0fd2b2f
FE
462}
463
464for (my $i = 0; $i < $MAX_SATA_DISKS; $i++) {
465 $drivedesc_hash->{"sata$i"} = $satadesc;
c1accf9d 466 $drivedesc_hash_with_alloc->{"sata$i"} = $desc_with_alloc->('sata', $satadesc);
e0fd2b2f
FE
467}
468
469for (my $i = 0; $i < $MAX_SCSI_DISKS; $i++) {
470 $drivedesc_hash->{"scsi$i"} = $scsidesc;
c1accf9d 471 $drivedesc_hash_with_alloc->{"scsi$i"} = $desc_with_alloc->('scsi', $scsidesc);
e0fd2b2f
FE
472}
473
474for (my $i = 0; $i < $MAX_VIRTIO_DISKS; $i++) {
475 $drivedesc_hash->{"virtio$i"} = $virtiodesc;
c1accf9d 476 $drivedesc_hash_with_alloc->{"virtio$i"} = $desc_with_alloc->('virtio', $virtiodesc);
e0fd2b2f
FE
477}
478
479$drivedesc_hash->{efidisk0} = $efidisk_desc;
c1accf9d
FE
480$drivedesc_hash_with_alloc->{efidisk0} = $desc_with_alloc->('efidisk', $efidisk_desc);
481
f9dde219 482$drivedesc_hash->{tpmstate0} = $tpmstate_desc;
c1accf9d 483$drivedesc_hash_with_alloc->{tpmstate0} = $desc_with_alloc->('tpmstate', $tpmstate_desc);
e0fd2b2f 484
43c4c7b6
FE
485for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
486 $drivedesc_hash->{"unused$i"} = $unuseddesc;
c1accf9d 487 $drivedesc_hash_with_alloc->{"unused$i"} = $desc_with_alloc->('unused', $unuseddesc);
43c4c7b6 488}
e0fd2b2f 489
1319908f
DC
490sub valid_drive_names_for_boot {
491 return grep { $_ ne 'efidisk0' && $_ ne 'tpmstate0' } valid_drive_names();
492}
493
e0fd2b2f
FE
494sub valid_drive_names {
495 # order is important - used to autoselect boot disk
496 return ((map { "ide$_" } (0 .. ($MAX_IDE_DISKS - 1))),
497 (map { "scsi$_" } (0 .. ($MAX_SCSI_DISKS - 1))),
498 (map { "virtio$_" } (0 .. ($MAX_VIRTIO_DISKS - 1))),
499 (map { "sata$_" } (0 .. ($MAX_SATA_DISKS - 1))),
f9dde219
SR
500 'efidisk0',
501 'tpmstate0');
e0fd2b2f
FE
502}
503
10713730
AL
504sub valid_drive_names_with_unused {
505 return (valid_drive_names(), map {"unused$_"} (0 .. ($MAX_UNUSED_DISKS - 1)));
506}
507
e0fd2b2f
FE
508sub is_valid_drivename {
509 my $dev = shift;
510
43c4c7b6 511 return defined($drivedesc_hash->{$dev}) && $dev !~ /^unused\d+$/;
e0fd2b2f
FE
512}
513
514PVE::JSONSchema::register_format('pve-qm-bootdisk', \&verify_bootdisk);
515sub verify_bootdisk {
516 my ($value, $noerr) = @_;
517
518 return $value if is_valid_drivename($value);
519
d1c1af4b 520 return if $noerr;
e0fd2b2f
FE
521
522 die "invalid boot disk '$value'\n";
523}
524
525sub drive_is_cloudinit {
526 my ($drive) = @_;
527 return $drive->{file} =~ m@[:/]vm-\d+-cloudinit(?:\.$QEMU_FORMAT_RE)?$@;
528}
529
530sub drive_is_cdrom {
531 my ($drive, $exclude_cloudinit) = @_;
532
533 return 0 if $exclude_cloudinit && drive_is_cloudinit($drive);
534
535 return $drive && $drive->{media} && ($drive->{media} eq 'cdrom');
536}
537
75748d44
FG
538sub drive_is_read_only {
539 my ($conf, $drive) = @_;
540
541 return 0 if !PVE::QemuConfig->is_template($conf);
542
543 # don't support being marked read-only
544 return $drive->{interface} ne 'sata' && $drive->{interface} ne 'ide';
545}
546
e0fd2b2f
FE
547# ideX = [volume=]volume-id[,media=d][,cyls=c,heads=h,secs=s[,trans=t]]
548# [,snapshot=on|off][,cache=on|off][,format=f][,backup=yes|no]
549# [,rerror=ignore|report|stop][,werror=enospc|ignore|report|stop]
550# [,aio=native|threads][,discard=ignore|on][,detect_zeroes=on|off]
551# [,iothread=on][,serial=serial][,model=model]
552
553sub parse_drive {
c1accf9d 554 my ($key, $data, $with_alloc) = @_;
e0fd2b2f
FE
555
556 my ($interface, $index);
557
558 if ($key =~ m/^([^\d]+)(\d+)$/) {
559 $interface = $1;
560 $index = $2;
561 } else {
d1c1af4b 562 return;
e0fd2b2f
FE
563 }
564
c1accf9d
FE
565 my $desc_hash = $with_alloc ? $drivedesc_hash_with_alloc : $drivedesc_hash;
566
567 if (!defined($desc_hash->{$key})) {
e0fd2b2f 568 warn "invalid drive key: $key\n";
d1c1af4b 569 return;
e0fd2b2f 570 }
43c4c7b6 571
c1accf9d 572 my $desc = $desc_hash->{$key}->{format};
e0fd2b2f 573 my $res = eval { PVE::JSONSchema::parse_property_string($desc, $data) };
d1c1af4b 574 return if !$res;
e0fd2b2f
FE
575 $res->{interface} = $interface;
576 $res->{index} = $index;
577
578 my $error = 0;
579 foreach my $opt (qw(bps bps_rd bps_wr)) {
580 if (my $bps = defined(delete $res->{$opt})) {
581 if (defined($res->{"m$opt"})) {
582 warn "both $opt and m$opt specified\n";
583 ++$error;
584 next;
585 }
586 $res->{"m$opt"} = sprintf("%.3f", $bps / (1024*1024.0));
587 }
588 }
589
590 # can't use the schema's 'requires' because of the mbps* => bps* "transforming aliases"
591 for my $requirement (
592 [mbps_max => 'mbps'],
593 [mbps_rd_max => 'mbps_rd'],
594 [mbps_wr_max => 'mbps_wr'],
595 [miops_max => 'miops'],
596 [miops_rd_max => 'miops_rd'],
597 [miops_wr_max => 'miops_wr'],
598 [bps_max_length => 'mbps_max'],
599 [bps_rd_max_length => 'mbps_rd_max'],
600 [bps_wr_max_length => 'mbps_wr_max'],
601 [iops_max_length => 'iops_max'],
602 [iops_rd_max_length => 'iops_rd_max'],
603 [iops_wr_max_length => 'iops_wr_max']) {
604 my ($option, $requires) = @$requirement;
605 if ($res->{$option} && !$res->{$requires}) {
606 warn "$option requires $requires\n";
607 ++$error;
608 }
609 }
610
d1c1af4b 611 return if $error;
e0fd2b2f 612
d1c1af4b
TL
613 return if $res->{mbps_rd} && $res->{mbps};
614 return if $res->{mbps_wr} && $res->{mbps};
615 return if $res->{iops_rd} && $res->{iops};
616 return if $res->{iops_wr} && $res->{iops};
e0fd2b2f
FE
617
618 if ($res->{media} && ($res->{media} eq 'cdrom')) {
d1c1af4b
TL
619 return if $res->{snapshot} || $res->{trans} || $res->{format};
620 return if $res->{heads} || $res->{secs} || $res->{cyls};
621 return if $res->{interface} eq 'virtio';
e0fd2b2f
FE
622 }
623
624 if (my $size = $res->{size}) {
d1c1af4b 625 return if !defined($res->{size} = PVE::JSONSchema::parse_size($size));
e0fd2b2f
FE
626 }
627
628 return $res;
629}
630
631sub print_drive {
c1accf9d 632 my ($drive, $with_alloc) = @_;
e0fd2b2f 633 my $skip = [ 'index', 'interface' ];
c1accf9d
FE
634 my $fmt = $with_alloc ? $alldrive_fmt_with_alloc : $alldrive_fmt;
635 return PVE::JSONSchema::print_property_string($drive, $fmt, $skip);
e0fd2b2f
FE
636}
637
2141a802
SR
638sub get_bootdisks {
639 my ($conf) = @_;
640
f7d1505b
TL
641 my $bootcfg;
642 $bootcfg = PVE::JSONSchema::parse_property_string('pve-qm-boot', $conf->{boot}) if $conf->{boot};
2141a802
SR
643
644 if (!defined($bootcfg) || $bootcfg->{legacy}) {
645 return [$conf->{bootdisk}] if $conf->{bootdisk};
646 return [];
647 }
648
649 my @list = PVE::Tools::split_list($bootcfg->{order});
650 @list = grep {is_valid_drivename($_)} @list;
651 return \@list;
652}
653
776c5f50 654sub bootdisk_size {
e0fd2b2f
FE
655 my ($storecfg, $conf) = @_;
656
2141a802 657 my $bootdisks = get_bootdisks($conf);
d1c1af4b 658 return if !@$bootdisks;
30664f14
DC
659 for my $bootdisk (@$bootdisks) {
660 next if !is_valid_drivename($bootdisk);
661 next if !$conf->{$bootdisk};
662 my $drive = parse_drive($bootdisk, $conf->{$bootdisk});
663 next if !defined($drive);
664 next if drive_is_cdrom($drive);
665 my $volid = $drive->{file};
666 next if !$volid;
667 return $drive->{size};
668 }
e0fd2b2f 669
30664f14 670 return;
e0fd2b2f
FE
671}
672
673sub update_disksize {
9b29cbd0 674 my ($drive, $newsize) = @_;
e0fd2b2f 675
d1c1af4b 676 return if !defined($newsize);
e0fd2b2f 677
63e313f3 678 my $oldsize = $drive->{size} // 0;
e0fd2b2f 679
63e313f3 680 if ($newsize != $oldsize) {
e0fd2b2f
FE
681 $drive->{size} = $newsize;
682
683 my $old_fmt = PVE::JSONSchema::format_size($oldsize);
684 my $new_fmt = PVE::JSONSchema::format_size($newsize);
685
9b29cbd0
FE
686 my $msg = "size of disk '$drive->{file}' updated from $old_fmt to $new_fmt";
687
688 return ($drive, $msg);
e0fd2b2f
FE
689 }
690
d1c1af4b 691 return;
e0fd2b2f
FE
692}
693
694sub is_volume_in_use {
695 my ($storecfg, $conf, $skip_drive, $volid) = @_;
696
697 my $path = PVE::Storage::path($storecfg, $volid);
698
699 my $scan_config = sub {
ab5b97d8 700 my ($cref) = @_;
e0fd2b2f
FE
701
702 foreach my $key (keys %$cref) {
703 my $value = $cref->{$key};
704 if (is_valid_drivename($key)) {
705 next if $skip_drive && $key eq $skip_drive;
706 my $drive = parse_drive($key, $value);
707 next if !$drive || !$drive->{file} || drive_is_cdrom($drive);
708 return 1 if $volid eq $drive->{file};
709 if ($drive->{file} =~ m!^/!) {
710 return 1 if $drive->{file} eq $path;
711 } else {
712 my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file}, 1);
713 next if !$storeid;
714 my $scfg = PVE::Storage::storage_config($storecfg, $storeid, 1);
715 next if !$scfg;
a0d8d859 716 return 1 if $path eq PVE::Storage::path($storecfg, $drive->{file});
e0fd2b2f
FE
717 }
718 }
719 }
720
721 return 0;
722 };
723
724 return 1 if &$scan_config($conf);
725
726 undef $skip_drive;
727
ab5b97d8
FE
728 for my $snap (values %{$conf->{snapshots}}) {
729 return 1 if $scan_config->($snap);
e0fd2b2f
FE
730 }
731
732 return 0;
733}
734
735sub resolve_first_disk {
5cfa9f5f 736 my ($conf, $cdrom) = @_;
1319908f 737 my @disks = valid_drive_names_for_boot();
5cfa9f5f 738 foreach my $ds (@disks) {
e0fd2b2f
FE
739 next if !$conf->{$ds};
740 my $disk = parse_drive($ds, $conf->{$ds});
5cfa9f5f
SR
741 next if drive_is_cdrom($disk) xor $cdrom;
742 return $ds;
e0fd2b2f 743 }
d1c1af4b 744 return;
e0fd2b2f
FE
745}
746
7471;