]>
git.proxmox.com Git - qemu-server.git/blob - PVE/QemuServer/Drive.pm
b71fc9336ef1c6f575c8d78d2e289ba2c05b490d
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 );
258 type
=> 'string' , format
=> $ide_fmt,
259 description
=> "Use volume as IDE hard disk or CD-ROM (n is 0 to " .( $MAX_IDE_DISKS - 1 ) . ")." ,
261 PVE
:: JSONSchema
:: register_standard_option
( "pve-qm-ide" , $idedesc );
273 type
=> 'string' , format
=> $scsi_fmt,
274 description
=> "Use volume as SCSI hard disk or CD-ROM (n is 0 to " . ( $MAX_SCSI_DISKS - 1 ) . ")." ,
276 PVE
:: JSONSchema
:: register_standard_option
( "pve-qm-scsi" , $scsidesc );
285 type
=> 'string' , format
=> $sata_fmt,
286 description
=> "Use volume as SATA hard disk or CD-ROM (n is 0 to " . ( $MAX_SATA_DISKS - 1 ). ")." ,
288 PVE
:: JSONSchema
:: register_standard_option
( "pve-qm-sata" , $satadesc );
296 type
=> 'string' , format
=> $virtio_fmt,
297 description
=> "Use volume as VIRTIO hard disk (n is 0 to " . ( $MAX_VIRTIO_DISKS - 1 ) . ")." ,
299 PVE
:: JSONSchema
:: register_standard_option
( "pve-qm-virtio" , $virtiodesc );
312 volume
=> { alias
=> 'file' },
315 format
=> 'pve-volume-id-or-qm-path' ,
317 format_description
=> 'volume' ,
318 description
=> "The drive's backing volume." ,
320 format
=> get_standard_option
( 'pve-qm-image-format' ),
323 format
=> 'disk-size' ,
324 format_description
=> 'DiskSize' ,
325 description
=> "Disk size. This is purely informational and has no effect." ,
332 type
=> 'string' , format
=> $efidisk_fmt,
333 description
=> "Configure a Disk for storing EFI vars" ,
336 PVE
:: JSONSchema
:: register_standard_option
( "pve-qm-efidisk" , $efidisk_desc );
339 volume
=> { alias
=> 'file' },
342 format
=> 'pve-volume-id' ,
344 format_description
=> 'volume' ,
345 description
=> "The drive's backing volume." ,
351 type
=> 'string' , format
=> $unused_fmt,
352 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually." ,
355 for ( my $i = 0 ; $i < $MAX_IDE_DISKS ; $i++ ) {
356 $drivedesc_hash ->{ "ide $i " } = $idedesc ;
359 for ( my $i = 0 ; $i < $MAX_SATA_DISKS ; $i++ ) {
360 $drivedesc_hash ->{ "sata $i " } = $satadesc ;
363 for ( my $i = 0 ; $i < $MAX_SCSI_DISKS ; $i++ ) {
364 $drivedesc_hash ->{ "scsi $i " } = $scsidesc ;
367 for ( my $i = 0 ; $i < $MAX_VIRTIO_DISKS ; $i++ ) {
368 $drivedesc_hash ->{ "virtio $i " } = $virtiodesc ;
371 $drivedesc_hash ->{ efidisk0
} = $efidisk_desc ;
373 for ( my $i = 0 ; $i < $MAX_UNUSED_DISKS ; $i++ ) {
374 $drivedesc_hash ->{ "unused $i " } = $unuseddesc ;
377 sub valid_drive_names
{
378 # order is important - used to autoselect boot disk
379 return (( map { "ide $_ " } ( 0 .. ( $MAX_IDE_DISKS - 1 ))),
380 ( map { "scsi $_ " } ( 0 .. ( $MAX_SCSI_DISKS - 1 ))),
381 ( map { "virtio $_ " } ( 0 .. ( $MAX_VIRTIO_DISKS - 1 ))),
382 ( map { "sata $_ " } ( 0 .. ( $MAX_SATA_DISKS - 1 ))),
386 sub is_valid_drivename
{
389 return defined ( $drivedesc_hash ->{ $dev }) && $dev !~ /^unused\d+$/ ;
392 PVE
:: JSONSchema
:: register_format
( 'pve-qm-bootdisk' , \
& verify_bootdisk
);
393 sub verify_bootdisk
{
394 my ( $value, $noerr ) = @_ ;
396 return $value if is_valid_drivename
( $value );
398 return undef if $noerr ;
400 die "invalid boot disk ' $value ' \n " ;
403 sub drive_is_cloudinit
{
405 return $drive ->{ file
} =~ m
@[:/] vm-\d
+- cloudinit
( ?
: \
. $QEMU_FORMAT_RE ) ?
$@ ;
409 my ( $drive, $exclude_cloudinit ) = @_ ;
411 return 0 if $exclude_cloudinit && drive_is_cloudinit
( $drive );
413 return $drive && $drive ->{ media
} && ( $drive ->{ media
} eq 'cdrom' );
416 # ideX = [volume=]volume-id[,media=d][,cyls=c,heads=h,secs=s[,trans=t]]
417 # [,snapshot=on|off][,cache=on|off][,format=f][,backup=yes|no]
418 # [,rerror=ignore|report|stop][,werror=enospc|ignore|report|stop]
419 # [,aio=native|threads][,discard=ignore|on][,detect_zeroes=on|off]
420 # [,iothread=on][,serial=serial][,model=model]
423 my ( $key, $data ) = @_ ;
425 my ( $interface, $index );
427 if ( $key =~ m/^([^\d]+)(\d+)$/ ) {
434 if (! defined ( $drivedesc_hash ->{ $key })) {
435 warn "invalid drive key: $key\n " ;
439 my $desc = $drivedesc_hash ->{ $key }->{ format
};
440 my $res = eval { PVE
:: JSONSchema
:: parse_property_string
( $desc, $data ) };
441 return undef if ! $res ;
442 $res ->{ interface
} = $interface ;
443 $res ->{ index } = $index ;
446 foreach my $opt ( qw(bps bps_rd bps_wr) ) {
447 if ( my $bps = defined ( delete $res ->{ $opt })) {
448 if ( defined ( $res ->{ "m $opt " })) {
449 warn "both $opt and m $opt specified \n " ;
453 $res ->{ "m $opt " } = sprintf ( "%.3f" , $bps / ( 1024 * 1024.0 ));
457 # can't use the schema's 'requires' because of the mbps* => bps* "transforming aliases"
458 for my $requirement (
459 [ mbps_max
=> 'mbps' ],
460 [ mbps_rd_max
=> 'mbps_rd' ],
461 [ mbps_wr_max
=> 'mbps_wr' ],
462 [ miops_max
=> 'miops' ],
463 [ miops_rd_max
=> 'miops_rd' ],
464 [ miops_wr_max
=> 'miops_wr' ],
465 [ bps_max_length
=> 'mbps_max' ],
466 [ bps_rd_max_length
=> 'mbps_rd_max' ],
467 [ bps_wr_max_length
=> 'mbps_wr_max' ],
468 [ iops_max_length
=> 'iops_max' ],
469 [ iops_rd_max_length
=> 'iops_rd_max' ],
470 [ iops_wr_max_length
=> 'iops_wr_max' ]) {
471 my ( $option, $requires ) = @$requirement ;
472 if ( $res ->{ $option } && ! $res ->{ $requires }) {
473 warn " $option requires $requires\n " ;
478 return undef if $error ;
480 return undef if $res ->{ mbps_rd
} && $res ->{ mbps
};
481 return undef if $res ->{ mbps_wr
} && $res ->{ mbps
};
482 return undef if $res ->{ iops_rd
} && $res ->{ iops
};
483 return undef if $res ->{ iops_wr
} && $res ->{ iops
};
485 if ( $res ->{ media
} && ( $res ->{ media
} eq 'cdrom' )) {
486 return undef if $res ->{ snapshot
} || $res ->{ trans
} || $res ->{ format
};
487 return undef if $res ->{ heads
} || $res ->{ secs
} || $res ->{ cyls
};
488 return undef if $res ->{ interface
} eq 'virtio' ;
491 if ( my $size = $res ->{ size
}) {
492 return undef if ! defined ( $res ->{ size
} = PVE
:: JSONSchema
:: parse_size
( $size ));
500 my $skip = [ 'index' , 'interface' ];
501 return PVE
:: JSONSchema
:: print_property_string
( $drive, $alldrive_fmt, $skip );
505 my ( $storecfg, $conf ) = @_ ;
507 my $bootdisk = $conf ->{ bootdisk
};
508 return undef if ! $bootdisk ;
509 return undef if ! is_valid_drivename
( $bootdisk );
511 return undef if ! $conf ->{ $bootdisk };
513 my $drive = parse_drive
( $bootdisk, $conf ->{ $bootdisk });
514 return undef if ! defined ( $drive );
516 return undef if drive_is_cdrom
( $drive );
518 my $volid = $drive ->{ file
};
519 return undef if ! $volid ;
521 return $drive ->{ size
};
524 sub update_disksize
{
525 my ( $drive, $newsize ) = @_ ;
527 return undef if ! defined ( $newsize );
529 my $oldsize = $drive ->{ size
} // 0 ;
531 if ( $newsize != $oldsize ) {
532 $drive ->{ size
} = $newsize ;
534 my $old_fmt = PVE
:: JSONSchema
:: format_size
( $oldsize );
535 my $new_fmt = PVE
:: JSONSchema
:: format_size
( $newsize );
537 my $msg = "size of disk ' $drive ->{file}' updated from $old_fmt to $new_fmt " ;
539 return ( $drive, $msg );
545 sub is_volume_in_use
{
546 my ( $storecfg, $conf, $skip_drive, $volid ) = @_ ;
548 my $path = PVE
:: Storage
:: path
( $storecfg, $volid );
550 my $scan_config = sub {
551 my ( $cref, $snapname ) = @_ ;
553 foreach my $key ( keys %$cref ) {
554 my $value = $cref ->{ $key };
555 if ( is_valid_drivename
( $key )) {
556 next if $skip_drive && $key eq $skip_drive ;
557 my $drive = parse_drive
( $key, $value );
558 next if ! $drive || ! $drive ->{ file
} || drive_is_cdrom
( $drive );
559 return 1 if $volid eq $drive ->{ file
};
560 if ( $drive ->{ file
} =~ m!^/! ) {
561 return 1 if $drive ->{ file
} eq $path ;
563 my ( $storeid, $volname ) = PVE
:: Storage
:: parse_volume_id
( $drive ->{ file
}, 1 );
565 my $scfg = PVE
:: Storage
:: storage_config
( $storecfg, $storeid, 1 );
567 return 1 if $path eq PVE
:: Storage
:: path
( $storecfg, $drive ->{ file
}, $snapname );
575 return 1 if & $scan_config ( $conf );
579 foreach my $snapname ( keys %{ $conf ->{ snapshots
}}) {
580 return 1 if & $scan_config ( $conf ->{ snapshots
}->{ $snapname }, $snapname );
586 sub resolve_first_disk
{
587 my ( $conf, $cdrom ) = @_ ;
588 my @disks = valid_drive_names
();
589 foreach my $ds ( @disks ) {
590 next if ! $conf ->{ $ds };
591 my $disk = parse_drive
( $ds, $conf ->{ $ds });
592 next if drive_is_cdrom
( $disk ) xor $cdrom ;