1 package PVE
::QemuServer
::Drive
;
7 use PVE
::JSONSchema
qw(get_standard_option);
20 our $QEMU_FORMAT_RE = qr/raw|cow|qcow|qcow2|qed|vmdk|cloop/;
22 PVE
::JSONSchema
::register_standard_option
('pve-qm-image-format', {
24 enum
=> [qw(raw cow qcow qed qcow2 vmdk cloop)],
25 description
=> "The drive's backing file's data format.",
29 my $MAX_IDE_DISKS = 4;
30 my $MAX_SCSI_DISKS = 31;
31 my $MAX_VIRTIO_DISKS = 16;
32 our $MAX_SATA_DISKS = 6;
33 our $MAX_UNUSED_DISKS = 256;
37 my %drivedesc_base = (
38 volume
=> { alias
=> 'file' },
41 format
=> 'pve-volume-id-or-qm-path',
43 format_description
=> 'volume',
44 description
=> "The drive's backing volume.",
48 enum
=> [qw(cdrom disk)],
49 description
=> "The drive's media type.",
55 description
=> "Force the drive's physical geometry to have a specific cylinder count.",
60 description
=> "Force the drive's physical geometry to have a specific head count.",
65 description
=> "Force the drive's physical geometry to have a specific sector count.",
70 enum
=> [qw(none lba auto)],
71 description
=> "Force disk geometry bios translation mode.",
76 description
=> "Controls qemu's snapshot mode feature."
77 . " If activated, changes made to the disk are temporary and will"
78 . " be discarded when the VM is shutdown.",
83 enum
=> [qw(none writethrough writeback unsafe directsync)],
84 description
=> "The drive's cache mode",
87 format
=> get_standard_option
('pve-qm-image-format'),
90 format
=> 'disk-size',
91 format_description
=> 'DiskSize',
92 description
=> "Disk size. This is purely informational and has no effect.",
97 description
=> "Whether the drive should be included when making backups.",
102 description
=> 'Whether the drive should considered for replication jobs.',
108 enum
=> [qw(ignore report stop)],
109 description
=> 'Read error action.',
114 enum
=> [qw(enospc ignore report stop)],
115 description
=> 'Write error action.',
120 enum
=> [qw(native threads io_uring)],
121 description
=> 'AIO type to use.',
126 enum
=> [qw(ignore on)],
127 description
=> 'Controls whether to pass discard/trim requests to the underlying storage.',
132 description
=> 'Controls whether to detect and try to optimize writes of zeroes.',
137 format
=> 'urlencoded',
138 format_description
=> 'serial',
139 maxLength
=> 20*3, # *3 since it's %xx url enoded
140 description
=> "The drive's reported serial number, url-encoded, up to 20 bytes long.",
145 description
=> 'Mark this locally-managed volume as available on all nodes',
146 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!",
152 my %iothread_fmt = ( iothread
=> {
154 description
=> "Whether to use iothreads for this drive",
161 format
=> 'urlencoded',
162 format_description
=> 'model',
163 maxLength
=> 40*3, # *3 since it's %xx url enoded
164 description
=> "The drive's reported model name, url-encoded, up to 40 bytes long.",
172 description
=> "Number of queues.",
181 description
=> "Whether the drive is read-only.",
186 my %scsiblock_fmt = (
189 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",
198 description
=> "Whether to expose this drive as an SSD, rather than a rotational hard disk.",
206 pattern
=> qr/^(0x)[0-9a-fA-F]{16}/,
207 format_description
=> 'wwn',
208 description
=> "The drive's worldwide name, encoded as 16 bytes hex string, prefixed by '0x'.",
213 my $add_throttle_desc = sub {
214 my ($key, $type, $what, $unit, $longunit, $minimum) = @_;
217 format_description
=> $unit,
218 description
=> "Maximum $what in $longunit.",
221 $d->{minimum
} = $minimum if defined($minimum);
222 $drivedesc_base{$key} = $d;
224 # throughput: (leaky bucket)
225 $add_throttle_desc->('bps', 'integer', 'r/w speed', 'bps', 'bytes per second');
226 $add_throttle_desc->('bps_rd', 'integer', 'read speed', 'bps', 'bytes per second');
227 $add_throttle_desc->('bps_wr', 'integer', 'write speed', 'bps', 'bytes per second');
228 $add_throttle_desc->('mbps', 'number', 'r/w speed', 'mbps', 'megabytes per second');
229 $add_throttle_desc->('mbps_rd', 'number', 'read speed', 'mbps', 'megabytes per second');
230 $add_throttle_desc->('mbps_wr', 'number', 'write speed', 'mbps', 'megabytes per second');
231 $add_throttle_desc->('iops', 'integer', 'r/w I/O', 'iops', 'operations per second');
232 $add_throttle_desc->('iops_rd', 'integer', 'read I/O', 'iops', 'operations per second');
233 $add_throttle_desc->('iops_wr', 'integer', 'write I/O', 'iops', 'operations per second');
235 # pools: (pool of IO before throttling starts taking effect)
236 $add_throttle_desc->('mbps_max', 'number', 'unthrottled r/w pool', 'mbps', 'megabytes per second');
237 $add_throttle_desc->('mbps_rd_max', 'number', 'unthrottled read pool', 'mbps', 'megabytes per second');
238 $add_throttle_desc->('mbps_wr_max', 'number', 'unthrottled write pool', 'mbps', 'megabytes per second');
239 $add_throttle_desc->('iops_max', 'integer', 'unthrottled r/w I/O pool', 'iops', 'operations per second');
240 $add_throttle_desc->('iops_rd_max', 'integer', 'unthrottled read I/O pool', 'iops', 'operations per second');
241 $add_throttle_desc->('iops_wr_max', 'integer', 'unthrottled write I/O pool', 'iops', 'operations per second');
244 $add_throttle_desc->('bps_max_length', 'integer', 'length of I/O bursts', 'seconds', 'seconds', 1);
245 $add_throttle_desc->('bps_rd_max_length', 'integer', 'length of read I/O bursts', 'seconds', 'seconds', 1);
246 $add_throttle_desc->('bps_wr_max_length', 'integer', 'length of write I/O bursts', 'seconds', 'seconds', 1);
247 $add_throttle_desc->('iops_max_length', 'integer', 'length of I/O bursts', 'seconds', 'seconds', 1);
248 $add_throttle_desc->('iops_rd_max_length', 'integer', 'length of read I/O bursts', 'seconds', 'seconds', 1);
249 $add_throttle_desc->('iops_wr_max_length', 'integer', 'length of write I/O bursts', 'seconds', 'seconds', 1);
252 $drivedesc_base{'bps_rd_length'} = { alias
=> 'bps_rd_max_length' };
253 $drivedesc_base{'bps_wr_length'} = { alias
=> 'bps_wr_max_length' };
254 $drivedesc_base{'iops_rd_length'} = { alias
=> 'iops_rd_max_length' };
255 $drivedesc_base{'iops_wr_length'} = { alias
=> 'iops_wr_max_length' };
263 PVE
::JSONSchema
::register_format
("pve-qm-ide", $ide_fmt);
265 my $ALLOCATION_SYNTAX_DESC =
266 "Use the special syntax STORAGE_ID:SIZE_IN_GiB to allocate a new volume.";
270 type
=> 'string', format
=> $ide_fmt,
271 description
=> "Use volume as IDE hard disk or CD-ROM (n is 0 to " .($MAX_IDE_DISKS -1) . "). " .
272 $ALLOCATION_SYNTAX_DESC,
274 PVE
::JSONSchema
::register_standard_option
("pve-qm-ide", $idedesc);
287 type
=> 'string', format
=> $scsi_fmt,
288 description
=> "Use volume as SCSI hard disk or CD-ROM (n is 0 to " . ($MAX_SCSI_DISKS - 1) . "). " .
289 $ALLOCATION_SYNTAX_DESC,
291 PVE
::JSONSchema
::register_standard_option
("pve-qm-scsi", $scsidesc);
300 type
=> 'string', format
=> $sata_fmt,
301 description
=> "Use volume as SATA hard disk or CD-ROM (n is 0 to " . ($MAX_SATA_DISKS - 1). "). " .
302 $ALLOCATION_SYNTAX_DESC,
304 PVE
::JSONSchema
::register_standard_option
("pve-qm-sata", $satadesc);
313 type
=> 'string', format
=> $virtio_fmt,
314 description
=> "Use volume as VIRTIO hard disk (n is 0 to " . ($MAX_VIRTIO_DISKS - 1) . "). " .
315 $ALLOCATION_SYNTAX_DESC,
317 PVE
::JSONSchema
::register_standard_option
("pve-qm-virtio", $virtiodesc);
323 description
=> "Size and type of the OVMF EFI vars. '4m' is newer and recommended,"
324 . " and required for Secure Boot. For backwards compatibility, '2m' is used"
325 . " if not otherwise specified.",
329 'pre-enrolled-keys' => {
331 description
=> "Use am EFI vars template with distribution-specific and Microsoft Standard"
332 ." keys enrolled, if used with 'efitype=4m'. Note that this will enable Secure Boot by"
333 ." default, though it can still be turned off from within the VM.",
340 volume
=> { alias
=> 'file' },
343 format
=> 'pve-volume-id-or-qm-path',
345 format_description
=> 'volume',
346 description
=> "The drive's backing volume.",
348 format
=> get_standard_option
('pve-qm-image-format'),
351 format
=> 'disk-size',
352 format_description
=> 'DiskSize',
353 description
=> "Disk size. This is purely informational and has no effect.",
361 type
=> 'string', format
=> $efidisk_fmt,
362 description
=> "Configure a Disk for storing EFI vars. " .
363 $ALLOCATION_SYNTAX_DESC . " Note that SIZE_IN_GiB is ignored here " .
364 "and that the default EFI vars are copied to the volume instead.",
367 PVE
::JSONSchema
::register_standard_option
("pve-qm-efidisk", $efidisk_desc);
369 my %tpmversion_fmt = (
372 enum
=> [qw(v1.2 v2.0)],
373 description
=> "The TPM interface version. v2.0 is newer and should be preferred."
374 ." Note that this cannot be changed later on.",
380 volume
=> { alias
=> 'file' },
383 format
=> 'pve-volume-id-or-qm-path',
385 format_description
=> 'volume',
386 description
=> "The drive's backing volume.",
390 format
=> 'disk-size',
391 format_description
=> 'DiskSize',
392 description
=> "Disk size. This is purely informational and has no effect.",
397 my $tpmstate_desc = {
399 type
=> 'string', format
=> $tpmstate_fmt,
400 description
=> "Configure a Disk for storing TPM state. " .
401 $ALLOCATION_SYNTAX_DESC . " Note that SIZE_IN_GiB is ignored here " .
402 "and that the default size of 4 MiB will always be used instead. The " .
403 "format is also fixed to 'raw'.",
405 use constant TPMSTATE_DISK_SIZE
=> 4 * 1024 * 1024;
421 volume
=> { alias
=> 'file' },
424 format
=> 'pve-volume-id',
426 format_description
=> 'volume',
427 description
=> "The drive's backing volume.",
433 type
=> 'string', format
=> $unused_fmt,
434 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually.",
437 for (my $i = 0; $i < $MAX_IDE_DISKS; $i++) {
438 $drivedesc_hash->{"ide$i"} = $idedesc;
441 for (my $i = 0; $i < $MAX_SATA_DISKS; $i++) {
442 $drivedesc_hash->{"sata$i"} = $satadesc;
445 for (my $i = 0; $i < $MAX_SCSI_DISKS; $i++) {
446 $drivedesc_hash->{"scsi$i"} = $scsidesc;
449 for (my $i = 0; $i < $MAX_VIRTIO_DISKS; $i++) {
450 $drivedesc_hash->{"virtio$i"} = $virtiodesc;
453 $drivedesc_hash->{efidisk0
} = $efidisk_desc;
454 $drivedesc_hash->{tpmstate0
} = $tpmstate_desc;
456 for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
457 $drivedesc_hash->{"unused$i"} = $unuseddesc;
460 sub valid_drive_names
{
461 # order is important - used to autoselect boot disk
462 return ((map { "ide$_" } (0 .. ($MAX_IDE_DISKS - 1))),
463 (map { "scsi$_" } (0 .. ($MAX_SCSI_DISKS - 1))),
464 (map { "virtio$_" } (0 .. ($MAX_VIRTIO_DISKS - 1))),
465 (map { "sata$_" } (0 .. ($MAX_SATA_DISKS - 1))),
470 sub valid_drive_names_with_unused
{
471 return (valid_drive_names
(), map {"unused$_"} (0 .. ($MAX_UNUSED_DISKS - 1)));
474 sub is_valid_drivename
{
477 return defined($drivedesc_hash->{$dev}) && $dev !~ /^unused\d+$/;
480 PVE
::JSONSchema
::register_format
('pve-qm-bootdisk', \
&verify_bootdisk
);
481 sub verify_bootdisk
{
482 my ($value, $noerr) = @_;
484 return $value if is_valid_drivename
($value);
488 die "invalid boot disk '$value'\n";
491 sub drive_is_cloudinit
{
493 return $drive->{file
} =~ m
@[:/]vm-\d
+-cloudinit
(?
:\
.$QEMU_FORMAT_RE)?
$@;
497 my ($drive, $exclude_cloudinit) = @_;
499 return 0 if $exclude_cloudinit && drive_is_cloudinit
($drive);
501 return $drive && $drive->{media
} && ($drive->{media
} eq 'cdrom');
504 sub drive_is_read_only
{
505 my ($conf, $drive) = @_;
507 return 0 if !PVE
::QemuConfig-
>is_template($conf);
509 # don't support being marked read-only
510 return $drive->{interface
} ne 'sata' && $drive->{interface
} ne 'ide';
513 # ideX = [volume=]volume-id[,media=d][,cyls=c,heads=h,secs=s[,trans=t]]
514 # [,snapshot=on|off][,cache=on|off][,format=f][,backup=yes|no]
515 # [,rerror=ignore|report|stop][,werror=enospc|ignore|report|stop]
516 # [,aio=native|threads][,discard=ignore|on][,detect_zeroes=on|off]
517 # [,iothread=on][,serial=serial][,model=model]
520 my ($key, $data) = @_;
522 my ($interface, $index);
524 if ($key =~ m/^([^\d]+)(\d+)$/) {
531 if (!defined($drivedesc_hash->{$key})) {
532 warn "invalid drive key: $key\n";
536 my $desc = $drivedesc_hash->{$key}->{format
};
537 my $res = eval { PVE
::JSONSchema
::parse_property_string
($desc, $data) };
539 $res->{interface
} = $interface;
540 $res->{index} = $index;
543 foreach my $opt (qw(bps bps_rd bps_wr)) {
544 if (my $bps = defined(delete $res->{$opt})) {
545 if (defined($res->{"m$opt"})) {
546 warn "both $opt and m$opt specified\n";
550 $res->{"m$opt"} = sprintf("%.3f", $bps / (1024*1024.0));
554 # can't use the schema's 'requires' because of the mbps* => bps* "transforming aliases"
555 for my $requirement (
556 [mbps_max
=> 'mbps'],
557 [mbps_rd_max
=> 'mbps_rd'],
558 [mbps_wr_max
=> 'mbps_wr'],
559 [miops_max
=> 'miops'],
560 [miops_rd_max
=> 'miops_rd'],
561 [miops_wr_max
=> 'miops_wr'],
562 [bps_max_length
=> 'mbps_max'],
563 [bps_rd_max_length
=> 'mbps_rd_max'],
564 [bps_wr_max_length
=> 'mbps_wr_max'],
565 [iops_max_length
=> 'iops_max'],
566 [iops_rd_max_length
=> 'iops_rd_max'],
567 [iops_wr_max_length
=> 'iops_wr_max']) {
568 my ($option, $requires) = @$requirement;
569 if ($res->{$option} && !$res->{$requires}) {
570 warn "$option requires $requires\n";
577 return if $res->{mbps_rd
} && $res->{mbps
};
578 return if $res->{mbps_wr
} && $res->{mbps
};
579 return if $res->{iops_rd
} && $res->{iops
};
580 return if $res->{iops_wr
} && $res->{iops
};
582 if ($res->{media
} && ($res->{media
} eq 'cdrom')) {
583 return if $res->{snapshot
} || $res->{trans
} || $res->{format
};
584 return if $res->{heads
} || $res->{secs
} || $res->{cyls
};
585 return if $res->{interface
} eq 'virtio';
588 if (my $size = $res->{size
}) {
589 return if !defined($res->{size
} = PVE
::JSONSchema
::parse_size
($size));
597 my $skip = [ 'index', 'interface' ];
598 return PVE
::JSONSchema
::print_property_string
($drive, $alldrive_fmt, $skip);
605 $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $conf->{boot
}) if $conf->{boot
};
607 if (!defined($bootcfg) || $bootcfg->{legacy
}) {
608 return [$conf->{bootdisk
}] if $conf->{bootdisk
};
612 my @list = PVE
::Tools
::split_list
($bootcfg->{order
});
613 @list = grep {is_valid_drivename
($_)} @list;
618 my ($storecfg, $conf) = @_;
620 my $bootdisks = get_bootdisks
($conf);
621 return if !@$bootdisks;
622 for my $bootdisk (@$bootdisks) {
623 next if !is_valid_drivename
($bootdisk);
624 next if !$conf->{$bootdisk};
625 my $drive = parse_drive
($bootdisk, $conf->{$bootdisk});
626 next if !defined($drive);
627 next if drive_is_cdrom
($drive);
628 my $volid = $drive->{file
};
630 return $drive->{size
};
636 sub update_disksize
{
637 my ($drive, $newsize) = @_;
639 return if !defined($newsize);
641 my $oldsize = $drive->{size
} // 0;
643 if ($newsize != $oldsize) {
644 $drive->{size
} = $newsize;
646 my $old_fmt = PVE
::JSONSchema
::format_size
($oldsize);
647 my $new_fmt = PVE
::JSONSchema
::format_size
($newsize);
649 my $msg = "size of disk '$drive->{file}' updated from $old_fmt to $new_fmt";
651 return ($drive, $msg);
657 sub is_volume_in_use
{
658 my ($storecfg, $conf, $skip_drive, $volid) = @_;
660 my $path = PVE
::Storage
::path
($storecfg, $volid);
662 my $scan_config = sub {
665 foreach my $key (keys %$cref) {
666 my $value = $cref->{$key};
667 if (is_valid_drivename
($key)) {
668 next if $skip_drive && $key eq $skip_drive;
669 my $drive = parse_drive
($key, $value);
670 next if !$drive || !$drive->{file
} || drive_is_cdrom
($drive);
671 return 1 if $volid eq $drive->{file
};
672 if ($drive->{file
} =~ m!^/!) {
673 return 1 if $drive->{file
} eq $path;
675 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($drive->{file
}, 1);
677 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid, 1);
679 return 1 if $path eq PVE
::Storage
::path
($storecfg, $drive->{file
});
687 return 1 if &$scan_config($conf);
691 for my $snap (values %{$conf->{snapshots
}}) {
692 return 1 if $scan_config->($snap);
698 sub resolve_first_disk
{
699 my ($conf, $cdrom) = @_;
700 my @disks = valid_drive_names
();
701 foreach my $ds (@disks) {
702 next if !$conf->{$ds};
703 my $disk = parse_drive
($ds, $conf->{$ds});
704 next if drive_is_cdrom
($disk) xor $cdrom;