]>
git.proxmox.com Git - qemu-server.git/blob - PVE/QemuServer/Drive.pm
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\n WARNING: 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." ,
178 my %scsiblock_fmt = (
181 description
=> "whether to use scsi-block for full passthrough of host block device \n\n WARNING: can lead to I/O errors in combination with low memory or high memory fragmentation on host" ,
190 description
=> "Whether to expose this drive as an SSD, rather than a rotational hard disk." ,
198 pattern
=> qr/^(0x)[0-9a-fA-F]{16}/ ,
199 format_description
=> 'wwn' ,
200 description
=> "The drive's worldwide name, encoded as 16 bytes hex string, prefixed by '0x'." ,
205 my $add_throttle_desc = sub {
206 my ( $key, $type, $what, $unit, $longunit, $minimum ) = @_ ;
209 format_description
=> $unit,
210 description
=> "Maximum $what in $longunit ." ,
213 $d ->{ minimum
} = $minimum if defined ( $minimum );
214 $drivedesc_base { $key } = $d ;
216 # throughput: (leaky bucket)
217 $add_throttle_desc ->( 'bps' , 'integer' , 'r/w speed' , 'bps' , 'bytes per second' );
218 $add_throttle_desc ->( 'bps_rd' , 'integer' , 'read speed' , 'bps' , 'bytes per second' );
219 $add_throttle_desc ->( 'bps_wr' , 'integer' , 'write speed' , 'bps' , 'bytes per second' );
220 $add_throttle_desc ->( 'mbps' , 'number' , 'r/w speed' , 'mbps' , 'megabytes per second' );
221 $add_throttle_desc ->( 'mbps_rd' , 'number' , 'read speed' , 'mbps' , 'megabytes per second' );
222 $add_throttle_desc ->( 'mbps_wr' , 'number' , 'write speed' , 'mbps' , 'megabytes per second' );
223 $add_throttle_desc ->( 'iops' , 'integer' , 'r/w I/O' , 'iops' , 'operations per second' );
224 $add_throttle_desc ->( 'iops_rd' , 'integer' , 'read I/O' , 'iops' , 'operations per second' );
225 $add_throttle_desc ->( 'iops_wr' , 'integer' , 'write I/O' , 'iops' , 'operations per second' );
227 # pools: (pool of IO before throttling starts taking effect)
228 $add_throttle_desc ->( 'mbps_max' , 'number' , 'unthrottled r/w pool' , 'mbps' , 'megabytes per second' );
229 $add_throttle_desc ->( 'mbps_rd_max' , 'number' , 'unthrottled read pool' , 'mbps' , 'megabytes per second' );
230 $add_throttle_desc ->( 'mbps_wr_max' , 'number' , 'unthrottled write pool' , 'mbps' , 'megabytes per second' );
231 $add_throttle_desc ->( 'iops_max' , 'integer' , 'unthrottled r/w I/O pool' , 'iops' , 'operations per second' );
232 $add_throttle_desc ->( 'iops_rd_max' , 'integer' , 'unthrottled read I/O pool' , 'iops' , 'operations per second' );
233 $add_throttle_desc ->( 'iops_wr_max' , 'integer' , 'unthrottled write I/O pool' , 'iops' , 'operations per second' );
236 $add_throttle_desc ->( 'bps_max_length' , 'integer' , 'length of I/O bursts' , 'seconds' , 'seconds' , 1 );
237 $add_throttle_desc ->( 'bps_rd_max_length' , 'integer' , 'length of read I/O bursts' , 'seconds' , 'seconds' , 1 );
238 $add_throttle_desc ->( 'bps_wr_max_length' , 'integer' , 'length of write I/O bursts' , 'seconds' , 'seconds' , 1 );
239 $add_throttle_desc ->( 'iops_max_length' , 'integer' , 'length of I/O bursts' , 'seconds' , 'seconds' , 1 );
240 $add_throttle_desc ->( 'iops_rd_max_length' , 'integer' , 'length of read I/O bursts' , 'seconds' , 'seconds' , 1 );
241 $add_throttle_desc ->( 'iops_wr_max_length' , 'integer' , 'length of write I/O bursts' , 'seconds' , 'seconds' , 1 );
244 $drivedesc_base { 'bps_rd_length' } = { alias
=> 'bps_rd_max_length' };
245 $drivedesc_base { 'bps_wr_length' } = { alias
=> 'bps_wr_max_length' };
246 $drivedesc_base { 'iops_rd_length' } = { alias
=> 'iops_rd_max_length' };
247 $drivedesc_base { 'iops_wr_length' } = { alias
=> 'iops_wr_max_length' };
255 PVE
:: JSONSchema
:: register_format
( "pve-qm-ide" , $ide_fmt );
257 my $ALLOCATION_SYNTAX_DESC =
258 "Use the special syntax STORAGE_ID:SIZE_IN_GiB to allocate a new volume." ;
262 type
=> 'string' , format
=> $ide_fmt,
263 description
=> "Use volume as IDE hard disk or CD-ROM (n is 0 to " .( $MAX_IDE_DISKS - 1 ) . "). " .
264 $ALLOCATION_SYNTAX_DESC,
266 PVE
:: JSONSchema
:: register_standard_option
( "pve-qm-ide" , $idedesc );
278 type
=> 'string' , format
=> $scsi_fmt,
279 description
=> "Use volume as SCSI hard disk or CD-ROM (n is 0 to " . ( $MAX_SCSI_DISKS - 1 ) . "). " .
280 $ALLOCATION_SYNTAX_DESC,
282 PVE
:: JSONSchema
:: register_standard_option
( "pve-qm-scsi" , $scsidesc );
291 type
=> 'string' , format
=> $sata_fmt,
292 description
=> "Use volume as SATA hard disk or CD-ROM (n is 0 to " . ( $MAX_SATA_DISKS - 1 ). "). " .
293 $ALLOCATION_SYNTAX_DESC,
295 PVE
:: JSONSchema
:: register_standard_option
( "pve-qm-sata" , $satadesc );
303 type
=> 'string' , format
=> $virtio_fmt,
304 description
=> "Use volume as VIRTIO hard disk (n is 0 to " . ( $MAX_VIRTIO_DISKS - 1 ) . "). " .
305 $ALLOCATION_SYNTAX_DESC,
307 PVE
:: JSONSchema
:: register_standard_option
( "pve-qm-virtio" , $virtiodesc );
320 volume
=> { alias
=> 'file' },
323 format
=> 'pve-volume-id-or-qm-path' ,
325 format_description
=> 'volume' ,
326 description
=> "The drive's backing volume." ,
328 format
=> get_standard_option
( 'pve-qm-image-format' ),
331 format
=> 'disk-size' ,
332 format_description
=> 'DiskSize' ,
333 description
=> "Disk size. This is purely informational and has no effect." ,
340 type
=> 'string' , format
=> $efidisk_fmt,
341 description
=> "Configure a Disk for storing EFI vars. " .
342 $ALLOCATION_SYNTAX_DESC . " Note that SIZE_IN_GiB is ignored here " .
343 "and that the default EFI vars are copied to the volume instead." ,
346 PVE
:: JSONSchema
:: register_standard_option
( "pve-qm-efidisk" , $efidisk_desc );
349 volume
=> { alias
=> 'file' },
352 format
=> 'pve-volume-id' ,
354 format_description
=> 'volume' ,
355 description
=> "The drive's backing volume." ,
361 type
=> 'string' , format
=> $unused_fmt,
362 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually." ,
365 for ( my $i = 0 ; $i < $MAX_IDE_DISKS ; $i++ ) {
366 $drivedesc_hash ->{ "ide $i " } = $idedesc ;
369 for ( my $i = 0 ; $i < $MAX_SATA_DISKS ; $i++ ) {
370 $drivedesc_hash ->{ "sata $i " } = $satadesc ;
373 for ( my $i = 0 ; $i < $MAX_SCSI_DISKS ; $i++ ) {
374 $drivedesc_hash ->{ "scsi $i " } = $scsidesc ;
377 for ( my $i = 0 ; $i < $MAX_VIRTIO_DISKS ; $i++ ) {
378 $drivedesc_hash ->{ "virtio $i " } = $virtiodesc ;
381 $drivedesc_hash ->{ efidisk0
} = $efidisk_desc ;
383 for ( my $i = 0 ; $i < $MAX_UNUSED_DISKS ; $i++ ) {
384 $drivedesc_hash ->{ "unused $i " } = $unuseddesc ;
387 sub valid_drive_names
{
388 # order is important - used to autoselect boot disk
389 return (( map { "ide $_ " } ( 0 .. ( $MAX_IDE_DISKS - 1 ))),
390 ( map { "scsi $_ " } ( 0 .. ( $MAX_SCSI_DISKS - 1 ))),
391 ( map { "virtio $_ " } ( 0 .. ( $MAX_VIRTIO_DISKS - 1 ))),
392 ( map { "sata $_ " } ( 0 .. ( $MAX_SATA_DISKS - 1 ))),
396 sub is_valid_drivename
{
399 return defined ( $drivedesc_hash ->{ $dev }) && $dev !~ /^unused\d+$/ ;
402 PVE
:: JSONSchema
:: register_format
( 'pve-qm-bootdisk' , \
& verify_bootdisk
);
403 sub verify_bootdisk
{
404 my ( $value, $noerr ) = @_ ;
406 return $value if is_valid_drivename
( $value );
410 die "invalid boot disk ' $value ' \n " ;
413 sub drive_is_cloudinit
{
415 return $drive ->{ file
} =~ m
@[:/] vm-\d
+- cloudinit
( ?
: \
. $QEMU_FORMAT_RE ) ?
$@ ;
419 my ( $drive, $exclude_cloudinit ) = @_ ;
421 return 0 if $exclude_cloudinit && drive_is_cloudinit
( $drive );
423 return $drive && $drive ->{ media
} && ( $drive ->{ media
} eq 'cdrom' );
426 sub drive_is_read_only
{
427 my ( $conf, $drive ) = @_ ;
429 return 0 if ! PVE
:: QemuConfig-
> is_template ( $conf );
431 # don't support being marked read-only
432 return $drive ->{ interface
} ne 'sata' && $drive ->{ interface
} ne 'ide' ;
435 # ideX = [volume=]volume-id[,media=d][,cyls=c,heads=h,secs=s[,trans=t]]
436 # [,snapshot=on|off][,cache=on|off][,format=f][,backup=yes|no]
437 # [,rerror=ignore|report|stop][,werror=enospc|ignore|report|stop]
438 # [,aio=native|threads][,discard=ignore|on][,detect_zeroes=on|off]
439 # [,iothread=on][,serial=serial][,model=model]
442 my ( $key, $data ) = @_ ;
444 my ( $interface, $index );
446 if ( $key =~ m/^([^\d]+)(\d+)$/ ) {
453 if (! defined ( $drivedesc_hash ->{ $key })) {
454 warn "invalid drive key: $key\n " ;
458 my $desc = $drivedesc_hash ->{ $key }->{ format
};
459 my $res = eval { PVE
:: JSONSchema
:: parse_property_string
( $desc, $data ) };
461 $res ->{ interface
} = $interface ;
462 $res ->{ index } = $index ;
465 foreach my $opt ( qw(bps bps_rd bps_wr) ) {
466 if ( my $bps = defined ( delete $res ->{ $opt })) {
467 if ( defined ( $res ->{ "m $opt " })) {
468 warn "both $opt and m $opt specified \n " ;
472 $res ->{ "m $opt " } = sprintf ( "%.3f" , $bps / ( 1024 * 1024.0 ));
476 # can't use the schema's 'requires' because of the mbps* => bps* "transforming aliases"
477 for my $requirement (
478 [ mbps_max
=> 'mbps' ],
479 [ mbps_rd_max
=> 'mbps_rd' ],
480 [ mbps_wr_max
=> 'mbps_wr' ],
481 [ miops_max
=> 'miops' ],
482 [ miops_rd_max
=> 'miops_rd' ],
483 [ miops_wr_max
=> 'miops_wr' ],
484 [ bps_max_length
=> 'mbps_max' ],
485 [ bps_rd_max_length
=> 'mbps_rd_max' ],
486 [ bps_wr_max_length
=> 'mbps_wr_max' ],
487 [ iops_max_length
=> 'iops_max' ],
488 [ iops_rd_max_length
=> 'iops_rd_max' ],
489 [ iops_wr_max_length
=> 'iops_wr_max' ]) {
490 my ( $option, $requires ) = @$requirement ;
491 if ( $res ->{ $option } && ! $res ->{ $requires }) {
492 warn " $option requires $requires\n " ;
499 return if $res ->{ mbps_rd
} && $res ->{ mbps
};
500 return if $res ->{ mbps_wr
} && $res ->{ mbps
};
501 return if $res ->{ iops_rd
} && $res ->{ iops
};
502 return if $res ->{ iops_wr
} && $res ->{ iops
};
504 if ( $res ->{ media
} && ( $res ->{ media
} eq 'cdrom' )) {
505 return if $res ->{ snapshot
} || $res ->{ trans
} || $res ->{ format
};
506 return if $res ->{ heads
} || $res ->{ secs
} || $res ->{ cyls
};
507 return if $res ->{ interface
} eq 'virtio' ;
510 if ( my $size = $res ->{ size
}) {
511 return if ! defined ( $res ->{ size
} = PVE
:: JSONSchema
:: parse_size
( $size ));
519 my $skip = [ 'index' , 'interface' ];
520 return PVE
:: JSONSchema
:: print_property_string
( $drive, $alldrive_fmt, $skip );
527 $bootcfg = PVE
:: JSONSchema
:: parse_property_string
( 'pve-qm-boot' , $conf ->{ boot
}) if $conf ->{ boot
};
529 if (! defined ( $bootcfg ) || $bootcfg ->{ legacy
}) {
530 return [ $conf ->{ bootdisk
}] if $conf ->{ bootdisk
};
534 my @list = PVE
:: Tools
:: split_list
( $bootcfg ->{ order
});
535 @list = grep { is_valid_drivename
( $_ )} @list ;
540 my ( $storecfg, $conf ) = @_ ;
542 my $bootdisks = get_bootdisks
( $conf );
543 return if ! @$bootdisks ;
544 for my $bootdisk ( @$bootdisks ) {
545 next if ! is_valid_drivename
( $bootdisk );
546 next if ! $conf ->{ $bootdisk };
547 my $drive = parse_drive
( $bootdisk, $conf ->{ $bootdisk });
548 next if ! defined ( $drive );
549 next if drive_is_cdrom
( $drive );
550 my $volid = $drive ->{ file
};
552 return $drive ->{ size
};
558 sub update_disksize
{
559 my ( $drive, $newsize ) = @_ ;
561 return if ! defined ( $newsize );
563 my $oldsize = $drive ->{ size
} // 0 ;
565 if ( $newsize != $oldsize ) {
566 $drive ->{ size
} = $newsize ;
568 my $old_fmt = PVE
:: JSONSchema
:: format_size
( $oldsize );
569 my $new_fmt = PVE
:: JSONSchema
:: format_size
( $newsize );
571 my $msg = "size of disk ' $drive ->{file}' updated from $old_fmt to $new_fmt " ;
573 return ( $drive, $msg );
579 sub is_volume_in_use
{
580 my ( $storecfg, $conf, $skip_drive, $volid ) = @_ ;
582 my $path = PVE
:: Storage
:: path
( $storecfg, $volid );
584 my $scan_config = sub {
587 foreach my $key ( keys %$cref ) {
588 my $value = $cref ->{ $key };
589 if ( is_valid_drivename
( $key )) {
590 next if $skip_drive && $key eq $skip_drive ;
591 my $drive = parse_drive
( $key, $value );
592 next if ! $drive || ! $drive ->{ file
} || drive_is_cdrom
( $drive );
593 return 1 if $volid eq $drive ->{ file
};
594 if ( $drive ->{ file
} =~ m!^/! ) {
595 return 1 if $drive ->{ file
} eq $path ;
597 my ( $storeid, $volname ) = PVE
:: Storage
:: parse_volume_id
( $drive ->{ file
}, 1 );
599 my $scfg = PVE
:: Storage
:: storage_config
( $storecfg, $storeid, 1 );
601 return 1 if $path eq PVE
:: Storage
:: path
( $storecfg, $drive ->{ file
});
609 return 1 if & $scan_config ( $conf );
613 for my $snap ( values %{ $conf ->{ snapshots
}}) {
614 return 1 if $scan_config ->( $snap );
620 sub resolve_first_disk
{
621 my ( $conf, $cdrom ) = @_ ;
622 my @disks = valid_drive_names
();
623 foreach my $ds ( @disks ) {
624 next if ! $conf ->{ $ds };
625 my $disk = parse_drive
( $ds, $conf ->{ $ds });
626 next if drive_is_cdrom
( $disk ) xor $cdrom ;