]>
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) ;
19 our $QEMU_FORMAT_RE = qr/raw|cow|qcow|qcow2|qed|vmdk|cloop/ ;
21 PVE
:: JSONSchema
:: register_standard_option
( 'pve-qm-image-format' , {
23 enum
=> [ qw(raw cow qcow qed qcow2 vmdk cloop) ],
24 description
=> "The drive's backing file's data format." ,
28 my $MAX_IDE_DISKS = 4 ;
29 my $MAX_SCSI_DISKS = 31 ;
30 my $MAX_VIRTIO_DISKS = 16 ;
31 our $MAX_SATA_DISKS = 6 ;
32 our $MAX_UNUSED_DISKS = 256 ;
36 my %drivedesc_base = (
37 volume
=> { alias
=> 'file' },
40 format
=> 'pve-volume-id-or-qm-path' ,
42 format_description
=> 'volume' ,
43 description
=> "The drive's backing volume." ,
47 enum
=> [ qw(cdrom disk) ],
48 description
=> "The drive's media type." ,
54 description
=> "Force the drive's physical geometry to have a specific cylinder count." ,
59 description
=> "Force the drive's physical geometry to have a specific head count." ,
64 description
=> "Force the drive's physical geometry to have a specific sector count." ,
69 enum
=> [ qw(none lba auto) ],
70 description
=> "Force disk geometry bios translation mode." ,
75 description
=> "Controls qemu's snapshot mode feature."
76 . " If activated, changes made to the disk are temporary and will"
77 . " be discarded when the VM is shutdown." ,
82 enum
=> [ qw(none writethrough writeback unsafe directsync) ],
83 description
=> "The drive's cache mode" ,
86 format
=> get_standard_option
( 'pve-qm-image-format' ),
89 format
=> 'disk-size' ,
90 format_description
=> 'DiskSize' ,
91 description
=> "Disk size. This is purely informational and has no effect." ,
96 description
=> "Whether the drive should be included when making backups." ,
101 description
=> 'Whether the drive should considered for replication jobs.' ,
107 enum
=> [ qw(ignore report stop) ],
108 description
=> 'Read error action.' ,
113 enum
=> [ qw(enospc ignore report stop) ],
114 description
=> 'Write error action.' ,
119 enum
=> [ qw(native threads) ],
120 description
=> 'AIO type to use.' ,
125 enum
=> [ qw(ignore on) ],
126 description
=> 'Controls whether to pass discard/trim requests to the underlying storage.' ,
131 description
=> 'Controls whether to detect and try to optimize writes of zeroes.' ,
136 format
=> 'urlencoded' ,
137 format_description
=> 'serial' ,
138 maxLength
=> 20 * 3 , # *3 since it's %xx url enoded
139 description
=> "The drive's reported serial number, url-encoded, up to 20 bytes long." ,
144 description
=> 'Mark this locally-managed volume as available on all nodes' ,
145 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!" ,
151 my %iothread_fmt = ( iothread
=> {
153 description
=> "Whether to use iothreads for this drive" ,
160 format
=> 'urlencoded' ,
161 format_description
=> 'model' ,
162 maxLength
=> 40 * 3 , # *3 since it's %xx url enoded
163 description
=> "The drive's reported model name, url-encoded, up to 40 bytes long." ,
171 description
=> "Number of queues." ,
177 my %scsiblock_fmt = (
180 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" ,
189 description
=> "Whether to expose this drive as an SSD, rather than a rotational hard disk." ,
197 pattern
=> qr/^(0x)[0-9a-fA-F]{16}/ ,
198 format_description
=> 'wwn' ,
199 description
=> "The drive's worldwide name, encoded as 16 bytes hex string, prefixed by '0x'." ,
204 my $add_throttle_desc = sub {
205 my ( $key, $type, $what, $unit, $longunit, $minimum ) = @_ ;
208 format_description
=> $unit,
209 description
=> "Maximum $what in $longunit ." ,
212 $d ->{ minimum
} = $minimum if defined ( $minimum );
213 $drivedesc_base { $key } = $d ;
215 # throughput: (leaky bucket)
216 $add_throttle_desc ->( 'bps' , 'integer' , 'r/w speed' , 'bps' , 'bytes per second' );
217 $add_throttle_desc ->( 'bps_rd' , 'integer' , 'read speed' , 'bps' , 'bytes per second' );
218 $add_throttle_desc ->( 'bps_wr' , 'integer' , 'write speed' , 'bps' , 'bytes per second' );
219 $add_throttle_desc ->( 'mbps' , 'number' , 'r/w speed' , 'mbps' , 'megabytes per second' );
220 $add_throttle_desc ->( 'mbps_rd' , 'number' , 'read speed' , 'mbps' , 'megabytes per second' );
221 $add_throttle_desc ->( 'mbps_wr' , 'number' , 'write speed' , 'mbps' , 'megabytes per second' );
222 $add_throttle_desc ->( 'iops' , 'integer' , 'r/w I/O' , 'iops' , 'operations per second' );
223 $add_throttle_desc ->( 'iops_rd' , 'integer' , 'read I/O' , 'iops' , 'operations per second' );
224 $add_throttle_desc ->( 'iops_wr' , 'integer' , 'write I/O' , 'iops' , 'operations per second' );
226 # pools: (pool of IO before throttling starts taking effect)
227 $add_throttle_desc ->( 'mbps_max' , 'number' , 'unthrottled r/w pool' , 'mbps' , 'megabytes per second' );
228 $add_throttle_desc ->( 'mbps_rd_max' , 'number' , 'unthrottled read pool' , 'mbps' , 'megabytes per second' );
229 $add_throttle_desc ->( 'mbps_wr_max' , 'number' , 'unthrottled write pool' , 'mbps' , 'megabytes per second' );
230 $add_throttle_desc ->( 'iops_max' , 'integer' , 'unthrottled r/w I/O pool' , 'iops' , 'operations per second' );
231 $add_throttle_desc ->( 'iops_rd_max' , 'integer' , 'unthrottled read I/O pool' , 'iops' , 'operations per second' );
232 $add_throttle_desc ->( 'iops_wr_max' , 'integer' , 'unthrottled write I/O pool' , 'iops' , 'operations per second' );
235 $add_throttle_desc ->( 'bps_max_length' , 'integer' , 'length of I/O bursts' , 'seconds' , 'seconds' , 1 );
236 $add_throttle_desc ->( 'bps_rd_max_length' , 'integer' , 'length of read I/O bursts' , 'seconds' , 'seconds' , 1 );
237 $add_throttle_desc ->( 'bps_wr_max_length' , 'integer' , 'length of write I/O bursts' , 'seconds' , 'seconds' , 1 );
238 $add_throttle_desc ->( 'iops_max_length' , 'integer' , 'length of I/O bursts' , 'seconds' , 'seconds' , 1 );
239 $add_throttle_desc ->( 'iops_rd_max_length' , 'integer' , 'length of read I/O bursts' , 'seconds' , 'seconds' , 1 );
240 $add_throttle_desc ->( 'iops_wr_max_length' , 'integer' , 'length of write I/O bursts' , 'seconds' , 'seconds' , 1 );
243 $drivedesc_base { 'bps_rd_length' } = { alias
=> 'bps_rd_max_length' };
244 $drivedesc_base { 'bps_wr_length' } = { alias
=> 'bps_wr_max_length' };
245 $drivedesc_base { 'iops_rd_length' } = { alias
=> 'iops_rd_max_length' };
246 $drivedesc_base { 'iops_wr_length' } = { alias
=> 'iops_wr_max_length' };
254 PVE
:: JSONSchema
:: register_format
( "pve-qm-ide" , $ide_fmt );
256 my $ALLOCATION_SYNTAX_DESC =
257 "Use the special syntax STORAGE_ID:SIZE_IN_GiB to allocate a new volume." ;
261 type
=> 'string' , format
=> $ide_fmt,
262 description
=> "Use volume as IDE hard disk or CD-ROM (n is 0 to " .( $MAX_IDE_DISKS - 1 ) . "). " .
263 $ALLOCATION_SYNTAX_DESC,
265 PVE
:: JSONSchema
:: register_standard_option
( "pve-qm-ide" , $idedesc );
277 type
=> 'string' , format
=> $scsi_fmt,
278 description
=> "Use volume as SCSI hard disk or CD-ROM (n is 0 to " . ( $MAX_SCSI_DISKS - 1 ) . "). " .
279 $ALLOCATION_SYNTAX_DESC,
281 PVE
:: JSONSchema
:: register_standard_option
( "pve-qm-scsi" , $scsidesc );
290 type
=> 'string' , format
=> $sata_fmt,
291 description
=> "Use volume as SATA hard disk or CD-ROM (n is 0 to " . ( $MAX_SATA_DISKS - 1 ). "). " .
292 $ALLOCATION_SYNTAX_DESC,
294 PVE
:: JSONSchema
:: register_standard_option
( "pve-qm-sata" , $satadesc );
302 type
=> 'string' , format
=> $virtio_fmt,
303 description
=> "Use volume as VIRTIO hard disk (n is 0 to " . ( $MAX_VIRTIO_DISKS - 1 ) . "). " .
304 $ALLOCATION_SYNTAX_DESC,
306 PVE
:: JSONSchema
:: register_standard_option
( "pve-qm-virtio" , $virtiodesc );
319 volume
=> { alias
=> 'file' },
322 format
=> 'pve-volume-id-or-qm-path' ,
324 format_description
=> 'volume' ,
325 description
=> "The drive's backing volume." ,
327 format
=> get_standard_option
( 'pve-qm-image-format' ),
330 format
=> 'disk-size' ,
331 format_description
=> 'DiskSize' ,
332 description
=> "Disk size. This is purely informational and has no effect." ,
339 type
=> 'string' , format
=> $efidisk_fmt,
340 description
=> "Configure a Disk for storing EFI vars. " .
341 $ALLOCATION_SYNTAX_DESC . " Note that SIZE_IN_GiB is ignored here " .
342 "and that the default EFI vars are copied to the volume instead." ,
345 PVE
:: JSONSchema
:: register_standard_option
( "pve-qm-efidisk" , $efidisk_desc );
348 volume
=> { alias
=> 'file' },
351 format
=> 'pve-volume-id' ,
353 format_description
=> 'volume' ,
354 description
=> "The drive's backing volume." ,
360 type
=> 'string' , format
=> $unused_fmt,
361 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually." ,
364 for ( my $i = 0 ; $i < $MAX_IDE_DISKS ; $i++ ) {
365 $drivedesc_hash ->{ "ide $i " } = $idedesc ;
368 for ( my $i = 0 ; $i < $MAX_SATA_DISKS ; $i++ ) {
369 $drivedesc_hash ->{ "sata $i " } = $satadesc ;
372 for ( my $i = 0 ; $i < $MAX_SCSI_DISKS ; $i++ ) {
373 $drivedesc_hash ->{ "scsi $i " } = $scsidesc ;
376 for ( my $i = 0 ; $i < $MAX_VIRTIO_DISKS ; $i++ ) {
377 $drivedesc_hash ->{ "virtio $i " } = $virtiodesc ;
380 $drivedesc_hash ->{ efidisk0
} = $efidisk_desc ;
382 for ( my $i = 0 ; $i < $MAX_UNUSED_DISKS ; $i++ ) {
383 $drivedesc_hash ->{ "unused $i " } = $unuseddesc ;
386 sub valid_drive_names
{
387 # order is important - used to autoselect boot disk
388 return (( map { "ide $_ " } ( 0 .. ( $MAX_IDE_DISKS - 1 ))),
389 ( map { "scsi $_ " } ( 0 .. ( $MAX_SCSI_DISKS - 1 ))),
390 ( map { "virtio $_ " } ( 0 .. ( $MAX_VIRTIO_DISKS - 1 ))),
391 ( map { "sata $_ " } ( 0 .. ( $MAX_SATA_DISKS - 1 ))),
395 sub is_valid_drivename
{
398 return defined ( $drivedesc_hash ->{ $dev }) && $dev !~ /^unused\d+$/ ;
401 PVE
:: JSONSchema
:: register_format
( 'pve-qm-bootdisk' , \
& verify_bootdisk
);
402 sub verify_bootdisk
{
403 my ( $value, $noerr ) = @_ ;
405 return $value if is_valid_drivename
( $value );
409 die "invalid boot disk ' $value ' \n " ;
412 sub drive_is_cloudinit
{
414 return $drive ->{ file
} =~ m
@[:/] vm-\d
+- cloudinit
( ?
: \
. $QEMU_FORMAT_RE ) ?
$@ ;
418 my ( $drive, $exclude_cloudinit ) = @_ ;
420 return 0 if $exclude_cloudinit && drive_is_cloudinit
( $drive );
422 return $drive && $drive ->{ media
} && ( $drive ->{ media
} eq 'cdrom' );
425 # ideX = [volume=]volume-id[,media=d][,cyls=c,heads=h,secs=s[,trans=t]]
426 # [,snapshot=on|off][,cache=on|off][,format=f][,backup=yes|no]
427 # [,rerror=ignore|report|stop][,werror=enospc|ignore|report|stop]
428 # [,aio=native|threads][,discard=ignore|on][,detect_zeroes=on|off]
429 # [,iothread=on][,serial=serial][,model=model]
432 my ( $key, $data ) = @_ ;
434 my ( $interface, $index );
436 if ( $key =~ m/^([^\d]+)(\d+)$/ ) {
443 if (! defined ( $drivedesc_hash ->{ $key })) {
444 warn "invalid drive key: $key\n " ;
448 my $desc = $drivedesc_hash ->{ $key }->{ format
};
449 my $res = eval { PVE
:: JSONSchema
:: parse_property_string
( $desc, $data ) };
451 $res ->{ interface
} = $interface ;
452 $res ->{ index } = $index ;
455 foreach my $opt ( qw(bps bps_rd bps_wr) ) {
456 if ( my $bps = defined ( delete $res ->{ $opt })) {
457 if ( defined ( $res ->{ "m $opt " })) {
458 warn "both $opt and m $opt specified \n " ;
462 $res ->{ "m $opt " } = sprintf ( "%.3f" , $bps / ( 1024 * 1024.0 ));
466 # can't use the schema's 'requires' because of the mbps* => bps* "transforming aliases"
467 for my $requirement (
468 [ mbps_max
=> 'mbps' ],
469 [ mbps_rd_max
=> 'mbps_rd' ],
470 [ mbps_wr_max
=> 'mbps_wr' ],
471 [ miops_max
=> 'miops' ],
472 [ miops_rd_max
=> 'miops_rd' ],
473 [ miops_wr_max
=> 'miops_wr' ],
474 [ bps_max_length
=> 'mbps_max' ],
475 [ bps_rd_max_length
=> 'mbps_rd_max' ],
476 [ bps_wr_max_length
=> 'mbps_wr_max' ],
477 [ iops_max_length
=> 'iops_max' ],
478 [ iops_rd_max_length
=> 'iops_rd_max' ],
479 [ iops_wr_max_length
=> 'iops_wr_max' ]) {
480 my ( $option, $requires ) = @$requirement ;
481 if ( $res ->{ $option } && ! $res ->{ $requires }) {
482 warn " $option requires $requires\n " ;
489 return if $res ->{ mbps_rd
} && $res ->{ mbps
};
490 return if $res ->{ mbps_wr
} && $res ->{ mbps
};
491 return if $res ->{ iops_rd
} && $res ->{ iops
};
492 return if $res ->{ iops_wr
} && $res ->{ iops
};
494 if ( $res ->{ media
} && ( $res ->{ media
} eq 'cdrom' )) {
495 return if $res ->{ snapshot
} || $res ->{ trans
} || $res ->{ format
};
496 return if $res ->{ heads
} || $res ->{ secs
} || $res ->{ cyls
};
497 return if $res ->{ interface
} eq 'virtio' ;
500 if ( my $size = $res ->{ size
}) {
501 return if ! defined ( $res ->{ size
} = PVE
:: JSONSchema
:: parse_size
( $size ));
509 my $skip = [ 'index' , 'interface' ];
510 return PVE
:: JSONSchema
:: print_property_string
( $drive, $alldrive_fmt, $skip );
517 $bootcfg = PVE
:: JSONSchema
:: parse_property_string
( 'pve-qm-boot' , $conf ->{ boot
}) if $conf ->{ boot
};
519 if (! defined ( $bootcfg ) || $bootcfg ->{ legacy
}) {
520 return [ $conf ->{ bootdisk
}] if $conf ->{ bootdisk
};
524 my @list = PVE
:: Tools
:: split_list
( $bootcfg ->{ order
});
525 @list = grep { is_valid_drivename
( $_ )} @list ;
530 my ( $storecfg, $conf ) = @_ ;
532 my $bootdisks = get_bootdisks
( $conf );
533 return if ! @$bootdisks ;
534 my $bootdisk = $bootdisks ->[ 0 ];
535 return if ! is_valid_drivename
( $bootdisk );
537 return if ! $conf ->{ $bootdisk };
539 my $drive = parse_drive
( $bootdisk, $conf ->{ $bootdisk });
540 return if ! defined ( $drive );
542 return if drive_is_cdrom
( $drive );
544 my $volid = $drive ->{ file
};
547 return $drive ->{ size
};
550 sub update_disksize
{
551 my ( $drive, $newsize ) = @_ ;
553 return if ! defined ( $newsize );
555 my $oldsize = $drive ->{ size
} // 0 ;
557 if ( $newsize != $oldsize ) {
558 $drive ->{ size
} = $newsize ;
560 my $old_fmt = PVE
:: JSONSchema
:: format_size
( $oldsize );
561 my $new_fmt = PVE
:: JSONSchema
:: format_size
( $newsize );
563 my $msg = "size of disk ' $drive ->{file}' updated from $old_fmt to $new_fmt " ;
565 return ( $drive, $msg );
571 sub is_volume_in_use
{
572 my ( $storecfg, $conf, $skip_drive, $volid ) = @_ ;
574 my $path = PVE
:: Storage
:: path
( $storecfg, $volid );
576 my $scan_config = sub {
577 my ( $cref, $snapname ) = @_ ;
579 foreach my $key ( keys %$cref ) {
580 my $value = $cref ->{ $key };
581 if ( is_valid_drivename
( $key )) {
582 next if $skip_drive && $key eq $skip_drive ;
583 my $drive = parse_drive
( $key, $value );
584 next if ! $drive || ! $drive ->{ file
} || drive_is_cdrom
( $drive );
585 return 1 if $volid eq $drive ->{ file
};
586 if ( $drive ->{ file
} =~ m!^/! ) {
587 return 1 if $drive ->{ file
} eq $path ;
589 my ( $storeid, $volname ) = PVE
:: Storage
:: parse_volume_id
( $drive ->{ file
}, 1 );
591 my $scfg = PVE
:: Storage
:: storage_config
( $storecfg, $storeid, 1 );
593 return 1 if $path eq PVE
:: Storage
:: path
( $storecfg, $drive ->{ file
}, $snapname );
601 return 1 if & $scan_config ( $conf );
605 foreach my $snapname ( keys %{ $conf ->{ snapshots
}}) {
606 return 1 if & $scan_config ( $conf ->{ snapshots
}->{ $snapname }, $snapname );
612 sub resolve_first_disk
{
613 my ( $conf, $cdrom ) = @_ ;
614 my @disks = valid_drive_names
();
615 foreach my $ds ( @disks ) {
616 next if ! $conf ->{ $ds };
617 my $disk = parse_drive
( $ds, $conf ->{ $ds });
618 next if drive_is_cdrom
( $disk ) xor $cdrom ;