]>
git.proxmox.com Git - qemu-server.git/blob - PVE/QemuServer/Drive.pm
e927abf1c79419757c8d6e550c2ae694c6e070af
1 package PVE
:: QemuServer
:: Drive
;
7 use PVE
:: JSONSchema
qw(get_standard_option) ;
21 our $QEMU_FORMAT_RE = qr/raw|cow|qcow|qcow2|qed|vmdk|cloop/ ;
23 PVE
:: JSONSchema
:: register_standard_option
( 'pve-qm-image-format' , {
25 enum
=> [ qw(raw cow qcow qed qcow2 vmdk cloop) ],
26 description
=> "The drive's backing file's data format." ,
30 my $MAX_IDE_DISKS = 4 ;
31 my $MAX_SCSI_DISKS = 31 ;
32 my $MAX_VIRTIO_DISKS = 16 ;
33 our $MAX_SATA_DISKS = 6 ;
34 our $MAX_UNUSED_DISKS = 256 ;
38 my %drivedesc_base = (
39 volume
=> { alias
=> 'file' },
42 format
=> 'pve-volume-id-or-qm-path' ,
44 format_description
=> 'volume' ,
45 description
=> "The drive's backing volume." ,
49 enum
=> [ qw(cdrom disk) ],
50 description
=> "The drive's media type." ,
56 description
=> "Force the drive's physical geometry to have a specific cylinder count." ,
61 description
=> "Force the drive's physical geometry to have a specific head count." ,
66 description
=> "Force the drive's physical geometry to have a specific sector count." ,
71 enum
=> [ qw(none lba auto) ],
72 description
=> "Force disk geometry bios translation mode." ,
77 description
=> "Controls qemu's snapshot mode feature."
78 . " If activated, changes made to the disk are temporary and will"
79 . " be discarded when the VM is shutdown." ,
84 enum
=> [ qw(none writethrough writeback unsafe directsync) ],
85 description
=> "The drive's cache mode" ,
88 format
=> get_standard_option
( 'pve-qm-image-format' ),
91 format
=> 'disk-size' ,
92 format_description
=> 'DiskSize' ,
93 description
=> "Disk size. This is purely informational and has no effect." ,
98 description
=> "Whether the drive should be included when making backups." ,
103 description
=> 'Whether the drive should considered for replication jobs.' ,
109 enum
=> [ qw(ignore report stop) ],
110 description
=> 'Read error action.' ,
115 enum
=> [ qw(enospc ignore report stop) ],
116 description
=> 'Write error action.' ,
121 enum
=> [ qw(native threads) ],
122 description
=> 'AIO type to use.' ,
127 enum
=> [ qw(ignore on) ],
128 description
=> 'Controls whether to pass discard/trim requests to the underlying storage.' ,
133 description
=> 'Controls whether to detect and try to optimize writes of zeroes.' ,
138 format
=> 'urlencoded' ,
139 format_description
=> 'serial' ,
140 maxLength
=> 20 * 3 , # *3 since it's %xx url enoded
141 description
=> "The drive's reported serial number, url-encoded, up to 20 bytes long." ,
146 description
=> 'Mark this locally-managed volume as available on all nodes' ,
147 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!" ,
153 my %iothread_fmt = ( iothread
=> {
155 description
=> "Whether to use iothreads for this drive" ,
162 format
=> 'urlencoded' ,
163 format_description
=> 'model' ,
164 maxLength
=> 40 * 3 , # *3 since it's %xx url enoded
165 description
=> "The drive's reported model name, url-encoded, up to 40 bytes long." ,
173 description
=> "Number of queues." ,
179 my %scsiblock_fmt = (
182 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" ,
191 description
=> "Whether to expose this drive as an SSD, rather than a rotational hard disk." ,
199 pattern
=> qr/^(0x)[0-9a-fA-F]{16}/ ,
200 format_description
=> 'wwn' ,
201 description
=> "The drive's worldwide name, encoded as 16 bytes hex string, prefixed by '0x'." ,
206 my $add_throttle_desc = sub {
207 my ( $key, $type, $what, $unit, $longunit, $minimum ) = @_ ;
210 format_description
=> $unit,
211 description
=> "Maximum $what in $longunit ." ,
214 $d ->{ minimum
} = $minimum if defined ( $minimum );
215 $drivedesc_base { $key } = $d ;
217 # throughput: (leaky bucket)
218 $add_throttle_desc ->( 'bps' , 'integer' , 'r/w speed' , 'bps' , 'bytes per second' );
219 $add_throttle_desc ->( 'bps_rd' , 'integer' , 'read speed' , 'bps' , 'bytes per second' );
220 $add_throttle_desc ->( 'bps_wr' , 'integer' , 'write speed' , 'bps' , 'bytes per second' );
221 $add_throttle_desc ->( 'mbps' , 'number' , 'r/w speed' , 'mbps' , 'megabytes per second' );
222 $add_throttle_desc ->( 'mbps_rd' , 'number' , 'read speed' , 'mbps' , 'megabytes per second' );
223 $add_throttle_desc ->( 'mbps_wr' , 'number' , 'write speed' , 'mbps' , 'megabytes per second' );
224 $add_throttle_desc ->( 'iops' , 'integer' , 'r/w I/O' , 'iops' , 'operations per second' );
225 $add_throttle_desc ->( 'iops_rd' , 'integer' , 'read I/O' , 'iops' , 'operations per second' );
226 $add_throttle_desc ->( 'iops_wr' , 'integer' , 'write I/O' , 'iops' , 'operations per second' );
228 # pools: (pool of IO before throttling starts taking effect)
229 $add_throttle_desc ->( 'mbps_max' , 'number' , 'unthrottled r/w pool' , 'mbps' , 'megabytes per second' );
230 $add_throttle_desc ->( 'mbps_rd_max' , 'number' , 'unthrottled read pool' , 'mbps' , 'megabytes per second' );
231 $add_throttle_desc ->( 'mbps_wr_max' , 'number' , 'unthrottled write pool' , 'mbps' , 'megabytes per second' );
232 $add_throttle_desc ->( 'iops_max' , 'integer' , 'unthrottled r/w I/O pool' , 'iops' , 'operations per second' );
233 $add_throttle_desc ->( 'iops_rd_max' , 'integer' , 'unthrottled read I/O pool' , 'iops' , 'operations per second' );
234 $add_throttle_desc ->( 'iops_wr_max' , 'integer' , 'unthrottled write I/O pool' , 'iops' , 'operations per second' );
237 $add_throttle_desc ->( 'bps_max_length' , 'integer' , 'length of I/O bursts' , 'seconds' , 'seconds' , 1 );
238 $add_throttle_desc ->( 'bps_rd_max_length' , 'integer' , 'length of read I/O bursts' , 'seconds' , 'seconds' , 1 );
239 $add_throttle_desc ->( 'bps_wr_max_length' , 'integer' , 'length of write I/O bursts' , 'seconds' , 'seconds' , 1 );
240 $add_throttle_desc ->( 'iops_max_length' , 'integer' , 'length of I/O bursts' , 'seconds' , 'seconds' , 1 );
241 $add_throttle_desc ->( 'iops_rd_max_length' , 'integer' , 'length of read I/O bursts' , 'seconds' , 'seconds' , 1 );
242 $add_throttle_desc ->( 'iops_wr_max_length' , 'integer' , 'length of write I/O bursts' , 'seconds' , 'seconds' , 1 );
245 $drivedesc_base { 'bps_rd_length' } = { alias
=> 'bps_rd_max_length' };
246 $drivedesc_base { 'bps_wr_length' } = { alias
=> 'bps_wr_max_length' };
247 $drivedesc_base { 'iops_rd_length' } = { alias
=> 'iops_rd_max_length' };
248 $drivedesc_base { 'iops_wr_length' } = { alias
=> 'iops_wr_max_length' };
256 PVE
:: JSONSchema
:: register_format
( "pve-qm-ide" , $ide_fmt );
260 type
=> 'string' , format
=> $ide_fmt,
261 description
=> "Use volume as IDE hard disk or CD-ROM (n is 0 to " .( $MAX_IDE_DISKS - 1 ) . ")." ,
263 PVE
:: JSONSchema
:: register_standard_option
( "pve-qm-ide" , $idedesc );
275 type
=> 'string' , format
=> $scsi_fmt,
276 description
=> "Use volume as SCSI hard disk or CD-ROM (n is 0 to " . ( $MAX_SCSI_DISKS - 1 ) . ")." ,
278 PVE
:: JSONSchema
:: register_standard_option
( "pve-qm-scsi" , $scsidesc );
287 type
=> 'string' , format
=> $sata_fmt,
288 description
=> "Use volume as SATA hard disk or CD-ROM (n is 0 to " . ( $MAX_SATA_DISKS - 1 ). ")." ,
290 PVE
:: JSONSchema
:: register_standard_option
( "pve-qm-sata" , $satadesc );
298 type
=> 'string' , format
=> $virtio_fmt,
299 description
=> "Use volume as VIRTIO hard disk (n is 0 to " . ( $MAX_VIRTIO_DISKS - 1 ) . ")." ,
301 PVE
:: JSONSchema
:: register_standard_option
( "pve-qm-virtio" , $virtiodesc );
314 volume
=> { alias
=> 'file' },
317 format
=> 'pve-volume-id-or-qm-path' ,
319 format_description
=> 'volume' ,
320 description
=> "The drive's backing volume." ,
322 format
=> get_standard_option
( 'pve-qm-image-format' ),
325 format
=> 'disk-size' ,
326 format_description
=> 'DiskSize' ,
327 description
=> "Disk size. This is purely informational and has no effect." ,
334 type
=> 'string' , format
=> $efidisk_fmt,
335 description
=> "Configure a Disk for storing EFI vars" ,
338 PVE
:: JSONSchema
:: register_standard_option
( "pve-qm-efidisk" , $efidisk_desc );
340 for ( my $i = 0 ; $i < $MAX_IDE_DISKS ; $i++ ) {
341 $drivedesc_hash ->{ "ide $i " } = $idedesc ;
344 for ( my $i = 0 ; $i < $MAX_SATA_DISKS ; $i++ ) {
345 $drivedesc_hash ->{ "sata $i " } = $satadesc ;
348 for ( my $i = 0 ; $i < $MAX_SCSI_DISKS ; $i++ ) {
349 $drivedesc_hash ->{ "scsi $i " } = $scsidesc ;
352 for ( my $i = 0 ; $i < $MAX_VIRTIO_DISKS ; $i++ ) {
353 $drivedesc_hash ->{ "virtio $i " } = $virtiodesc ;
356 $drivedesc_hash ->{ efidisk0
} = $efidisk_desc ;
360 type
=> 'string' , format
=> 'pve-volume-id' ,
361 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually." ,
364 sub valid_drive_names
{
365 # order is important - used to autoselect boot disk
366 return (( map { "ide $_ " } ( 0 .. ( $MAX_IDE_DISKS - 1 ))),
367 ( map { "scsi $_ " } ( 0 .. ( $MAX_SCSI_DISKS - 1 ))),
368 ( map { "virtio $_ " } ( 0 .. ( $MAX_VIRTIO_DISKS - 1 ))),
369 ( map { "sata $_ " } ( 0 .. ( $MAX_SATA_DISKS - 1 ))),
373 sub is_valid_drivename
{
376 return defined ( $drivedesc_hash ->{ $dev });
379 PVE
:: JSONSchema
:: register_format
( 'pve-qm-bootdisk' , \
& verify_bootdisk
);
380 sub verify_bootdisk
{
381 my ( $value, $noerr ) = @_ ;
383 return $value if is_valid_drivename
( $value );
385 return undef if $noerr ;
387 die "invalid boot disk ' $value ' \n " ;
390 sub drive_is_cloudinit
{
392 return $drive ->{ file
} =~ m
@[:/] vm-\d
+- cloudinit
( ?
: \
. $QEMU_FORMAT_RE ) ?
$@ ;
396 my ( $drive, $exclude_cloudinit ) = @_ ;
398 return 0 if $exclude_cloudinit && drive_is_cloudinit
( $drive );
400 return $drive && $drive ->{ media
} && ( $drive ->{ media
} eq 'cdrom' );
403 # ideX = [volume=]volume-id[,media=d][,cyls=c,heads=h,secs=s[,trans=t]]
404 # [,snapshot=on|off][,cache=on|off][,format=f][,backup=yes|no]
405 # [,rerror=ignore|report|stop][,werror=enospc|ignore|report|stop]
406 # [,aio=native|threads][,discard=ignore|on][,detect_zeroes=on|off]
407 # [,iothread=on][,serial=serial][,model=model]
410 my ( $key, $data ) = @_ ;
412 my ( $interface, $index );
414 if ( $key =~ m/^([^\d]+)(\d+)$/ ) {
421 my $desc = $key =~ /^unused\d+$/ ?
$alldrive_fmt
422 : $drivedesc_hash ->{ $key }->{ format
};
424 warn "invalid drive key: $key\n " ;
427 my $res = eval { PVE
:: JSONSchema
:: parse_property_string
( $desc, $data ) };
428 return undef if ! $res ;
429 $res ->{ interface
} = $interface ;
430 $res ->{ index } = $index ;
433 foreach my $opt ( qw(bps bps_rd bps_wr) ) {
434 if ( my $bps = defined ( delete $res ->{ $opt })) {
435 if ( defined ( $res ->{ "m $opt " })) {
436 warn "both $opt and m $opt specified \n " ;
440 $res ->{ "m $opt " } = sprintf ( "%.3f" , $bps / ( 1024 * 1024.0 ));
444 # can't use the schema's 'requires' because of the mbps* => bps* "transforming aliases"
445 for my $requirement (
446 [ mbps_max
=> 'mbps' ],
447 [ mbps_rd_max
=> 'mbps_rd' ],
448 [ mbps_wr_max
=> 'mbps_wr' ],
449 [ miops_max
=> 'miops' ],
450 [ miops_rd_max
=> 'miops_rd' ],
451 [ miops_wr_max
=> 'miops_wr' ],
452 [ bps_max_length
=> 'mbps_max' ],
453 [ bps_rd_max_length
=> 'mbps_rd_max' ],
454 [ bps_wr_max_length
=> 'mbps_wr_max' ],
455 [ iops_max_length
=> 'iops_max' ],
456 [ iops_rd_max_length
=> 'iops_rd_max' ],
457 [ iops_wr_max_length
=> 'iops_wr_max' ]) {
458 my ( $option, $requires ) = @$requirement ;
459 if ( $res ->{ $option } && ! $res ->{ $requires }) {
460 warn " $option requires $requires\n " ;
465 return undef if $error ;
467 return undef if $res ->{ mbps_rd
} && $res ->{ mbps
};
468 return undef if $res ->{ mbps_wr
} && $res ->{ mbps
};
469 return undef if $res ->{ iops_rd
} && $res ->{ iops
};
470 return undef if $res ->{ iops_wr
} && $res ->{ iops
};
472 if ( $res ->{ media
} && ( $res ->{ media
} eq 'cdrom' )) {
473 return undef if $res ->{ snapshot
} || $res ->{ trans
} || $res ->{ format
};
474 return undef if $res ->{ heads
} || $res ->{ secs
} || $res ->{ cyls
};
475 return undef if $res ->{ interface
} eq 'virtio' ;
478 if ( my $size = $res ->{ size
}) {
479 return undef if ! defined ( $res ->{ size
} = PVE
:: JSONSchema
:: parse_size
( $size ));
487 my $skip = [ 'index' , 'interface' ];
488 return PVE
:: JSONSchema
:: print_property_string
( $drive, $alldrive_fmt, $skip );
492 my ( $conf, $func, @param ) = @_ ;
494 foreach my $ds ( valid_drive_names
()) {
495 next if ! defined ( $conf ->{ $ds });
497 my $drive = parse_drive
( $ds, $conf ->{ $ds });
500 & $func ( $ds, $drive, @param );
505 my ( $conf, $func, @param ) = @_ ;
509 my $test_volid = sub {
510 my ( $volid, $is_cdrom, $replicate, $shared, $snapname, $size ) = @_ ;
514 $volhash ->{ $volid }->{ cdrom
} // = 1 ;
515 $volhash ->{ $volid }->{ cdrom
} = 0 if ! $is_cdrom ;
517 $volhash ->{ $volid }->{ replicate
} // = 0 ;
518 $volhash ->{ $volid }->{ replicate
} = 1 if $replicate ;
520 $volhash ->{ $volid }->{ shared
} // = 0 ;
521 $volhash ->{ $volid }->{ shared
} = 1 if $shared ;
523 $volhash ->{ $volid }->{ referenced_in_config
} // = 0 ;
524 $volhash ->{ $volid }->{ referenced_in_config
} = 1 if ! defined ( $snapname );
526 $volhash ->{ $volid }->{ referenced_in_snapshot
}->{ $snapname } = 1
527 if defined ( $snapname );
528 $volhash ->{ $volid }->{ size
} = $size if $size ;
531 foreach_drive
( $conf, sub {
532 my ( $ds, $drive ) = @_ ;
533 $test_volid ->( $drive ->{ file
}, drive_is_cdrom
( $drive ), $drive ->{ replicate
} // 1 , $drive ->{ shared
}, undef , $drive ->{ size
});
536 foreach my $snapname ( keys %{ $conf ->{ snapshots
}}) {
537 my $snap = $conf ->{ snapshots
}->{ $snapname };
538 $test_volid ->( $snap ->{ vmstate
}, 0 , 1 , $snapname );
539 foreach_drive
( $snap, sub {
540 my ( $ds, $drive ) = @_ ;
541 $test_volid ->( $drive ->{ file
}, drive_is_cdrom
( $drive ), $drive ->{ replicate
} // 1 , $drive ->{ shared
}, $snapname );
545 foreach my $volid ( keys %$volhash ) {
546 & $func ( $volid, $volhash ->{ $volid }, @param );
551 my ( $storecfg, $conf ) = @_ ;
553 my $bootdisk = $conf ->{ bootdisk
};
554 return undef if ! $bootdisk ;
555 return undef if ! is_valid_drivename
( $bootdisk );
557 return undef if ! $conf ->{ $bootdisk };
559 my $drive = parse_drive
( $bootdisk, $conf ->{ $bootdisk });
560 return undef if ! defined ( $drive );
562 return undef if drive_is_cdrom
( $drive );
564 my $volid = $drive ->{ file
};
565 return undef if ! $volid ;
567 return $drive ->{ size
};
570 sub update_disksize
{
571 my ( $drive, $volid_hash ) = @_ ;
573 my $volid = $drive ->{ file
};
574 return undef if ! defined ( $volid );
575 return undef if ! defined ( $volid_hash ->{ $volid }) || ! defined ( $volid_hash ->{ $volid }->{ size
});
577 my $oldsize = $drive ->{ size
} // 0 ;
578 my $newsize = $volid_hash ->{ $volid }->{ size
};
580 if ( $newsize != $oldsize ) {
581 $drive ->{ size
} = $newsize ;
583 my $old_fmt = PVE
:: JSONSchema
:: format_size
( $oldsize );
584 my $new_fmt = PVE
:: JSONSchema
:: format_size
( $newsize );
586 return wantarray ?
( $drive, $old_fmt, $new_fmt ) : $drive ;
592 sub is_volume_in_use
{
593 my ( $storecfg, $conf, $skip_drive, $volid ) = @_ ;
595 my $path = PVE
:: Storage
:: path
( $storecfg, $volid );
597 my $scan_config = sub {
598 my ( $cref, $snapname ) = @_ ;
600 foreach my $key ( keys %$cref ) {
601 my $value = $cref ->{ $key };
602 if ( is_valid_drivename
( $key )) {
603 next if $skip_drive && $key eq $skip_drive ;
604 my $drive = parse_drive
( $key, $value );
605 next if ! $drive || ! $drive ->{ file
} || drive_is_cdrom
( $drive );
606 return 1 if $volid eq $drive ->{ file
};
607 if ( $drive ->{ file
} =~ m!^/! ) {
608 return 1 if $drive ->{ file
} eq $path ;
610 my ( $storeid, $volname ) = PVE
:: Storage
:: parse_volume_id
( $drive ->{ file
}, 1 );
612 my $scfg = PVE
:: Storage
:: storage_config
( $storecfg, $storeid, 1 );
614 return 1 if $path eq PVE
:: Storage
:: path
( $storecfg, $drive ->{ file
}, $snapname );
622 return 1 if & $scan_config ( $conf );
626 foreach my $snapname ( keys %{ $conf ->{ snapshots
}}) {
627 return 1 if & $scan_config ( $conf ->{ snapshots
}->{ $snapname }, $snapname );
633 sub resolve_first_disk
{
635 my @disks = valid_drive_names
();
637 foreach my $ds ( reverse @disks ) {
638 next if ! $conf ->{ $ds };
639 my $disk = parse_drive
( $ds, $conf ->{ $ds });
640 next if drive_is_cdrom
( $disk );