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\nWARNING: 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\nWARNING: 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
{
588 my @disks = valid_drive_names
();
590 foreach my $ds (reverse @disks) {
591 next if !$conf->{$ds};
592 my $disk = parse_drive
($ds, $conf->{$ds});
593 next if drive_is_cdrom
($disk);