]> git.proxmox.com Git - qemu-server.git/blame - PVE/QemuServer/Drive.pm
tree wide cleanup of s/return undef/return/
[qemu-server.git] / PVE / QemuServer / Drive.pm
CommitLineData
e0fd2b2f
FE
1package PVE::QemuServer::Drive;
2
3use strict;
4use warnings;
5
6use PVE::Storage;
7use PVE::JSONSchema qw(get_standard_option);
8
9use base qw(Exporter);
10
11our @EXPORT_OK = qw(
12is_valid_drivename
13drive_is_cloudinit
14drive_is_cdrom
15parse_drive
16print_drive
e0fd2b2f
FE
17);
18
19our $QEMU_FORMAT_RE = qr/raw|cow|qcow|qcow2|qed|vmdk|cloop/;
20
21PVE::JSONSchema::register_standard_option('pve-qm-image-format', {
22 type => 'string',
23 enum => [qw(raw cow qcow qed qcow2 vmdk cloop)],
24 description => "The drive's backing file's data format.",
25 optional => 1,
26});
27
28my $MAX_IDE_DISKS = 4;
29my $MAX_SCSI_DISKS = 31;
30my $MAX_VIRTIO_DISKS = 16;
31our $MAX_SATA_DISKS = 6;
32our $MAX_UNUSED_DISKS = 256;
33
34our $drivedesc_hash;
35
36my %drivedesc_base = (
37 volume => { alias => 'file' },
38 file => {
39 type => 'string',
40 format => 'pve-volume-id-or-qm-path',
41 default_key => 1,
42 format_description => 'volume',
43 description => "The drive's backing volume.",
44 },
45 media => {
46 type => 'string',
47 enum => [qw(cdrom disk)],
48 description => "The drive's media type.",
49 default => 'disk',
50 optional => 1
51 },
52 cyls => {
53 type => 'integer',
54 description => "Force the drive's physical geometry to have a specific cylinder count.",
55 optional => 1
56 },
57 heads => {
58 type => 'integer',
59 description => "Force the drive's physical geometry to have a specific head count.",
60 optional => 1
61 },
62 secs => {
63 type => 'integer',
64 description => "Force the drive's physical geometry to have a specific sector count.",
65 optional => 1
66 },
67 trans => {
68 type => 'string',
69 enum => [qw(none lba auto)],
70 description => "Force disk geometry bios translation mode.",
71 optional => 1,
72 },
73 snapshot => {
74 type => 'boolean',
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.",
78 optional => 1,
79 },
80 cache => {
81 type => 'string',
82 enum => [qw(none writethrough writeback unsafe directsync)],
83 description => "The drive's cache mode",
84 optional => 1,
85 },
86 format => get_standard_option('pve-qm-image-format'),
87 size => {
88 type => 'string',
89 format => 'disk-size',
90 format_description => 'DiskSize',
91 description => "Disk size. This is purely informational and has no effect.",
92 optional => 1,
93 },
94 backup => {
95 type => 'boolean',
96 description => "Whether the drive should be included when making backups.",
97 optional => 1,
98 },
99 replicate => {
100 type => 'boolean',
101 description => 'Whether the drive should considered for replication jobs.',
102 optional => 1,
103 default => 1,
104 },
105 rerror => {
106 type => 'string',
107 enum => [qw(ignore report stop)],
108 description => 'Read error action.',
109 optional => 1,
110 },
111 werror => {
112 type => 'string',
113 enum => [qw(enospc ignore report stop)],
114 description => 'Write error action.',
115 optional => 1,
116 },
117 aio => {
118 type => 'string',
119 enum => [qw(native threads)],
120 description => 'AIO type to use.',
121 optional => 1,
122 },
123 discard => {
124 type => 'string',
125 enum => [qw(ignore on)],
126 description => 'Controls whether to pass discard/trim requests to the underlying storage.',
127 optional => 1,
128 },
129 detect_zeroes => {
130 type => 'boolean',
131 description => 'Controls whether to detect and try to optimize writes of zeroes.',
132 optional => 1,
133 },
134 serial => {
135 type => 'string',
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.",
140 optional => 1,
141 },
142 shared => {
143 type => 'boolean',
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!",
146 optional => 1,
147 default => 0,
148 }
149);
150
151my %iothread_fmt = ( iothread => {
152 type => 'boolean',
153 description => "Whether to use iothreads for this drive",
154 optional => 1,
155});
156
157my %model_fmt = (
158 model => {
159 type => 'string',
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.",
164 optional => 1,
165 },
166);
167
168my %queues_fmt = (
169 queues => {
170 type => 'integer',
171 description => "Number of queues.",
172 minimum => 2,
173 optional => 1
174 }
175);
176
177my %scsiblock_fmt = (
178 scsiblock => {
179 type => 'boolean',
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",
181 optional => 1,
182 default => 0,
183 },
184);
185
186my %ssd_fmt = (
187 ssd => {
188 type => 'boolean',
189 description => "Whether to expose this drive as an SSD, rather than a rotational hard disk.",
190 optional => 1,
191 },
192);
193
194my %wwn_fmt = (
195 wwn => {
196 type => 'string',
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'.",
200 optional => 1,
201 },
202);
203
204my $add_throttle_desc = sub {
205 my ($key, $type, $what, $unit, $longunit, $minimum) = @_;
206 my $d = {
207 type => $type,
208 format_description => $unit,
209 description => "Maximum $what in $longunit.",
210 optional => 1,
211 };
212 $d->{minimum} = $minimum if defined($minimum);
213 $drivedesc_base{$key} = $d;
214};
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');
225
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');
233
234# burst lengths
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);
241
242# legacy support
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' };
247
248my $ide_fmt = {
249 %drivedesc_base,
250 %model_fmt,
251 %ssd_fmt,
252 %wwn_fmt,
253};
254PVE::JSONSchema::register_format("pve-qm-ide", $ide_fmt);
255
256my $idedesc = {
257 optional => 1,
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) . ").",
260};
261PVE::JSONSchema::register_standard_option("pve-qm-ide", $idedesc);
262
263my $scsi_fmt = {
264 %drivedesc_base,
265 %iothread_fmt,
266 %queues_fmt,
267 %scsiblock_fmt,
268 %ssd_fmt,
269 %wwn_fmt,
270};
271my $scsidesc = {
272 optional => 1,
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) . ").",
275};
276PVE::JSONSchema::register_standard_option("pve-qm-scsi", $scsidesc);
277
278my $sata_fmt = {
279 %drivedesc_base,
280 %ssd_fmt,
281 %wwn_fmt,
282};
283my $satadesc = {
284 optional => 1,
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). ").",
287};
288PVE::JSONSchema::register_standard_option("pve-qm-sata", $satadesc);
289
290my $virtio_fmt = {
291 %drivedesc_base,
292 %iothread_fmt,
293};
294my $virtiodesc = {
295 optional => 1,
296 type => 'string', format => $virtio_fmt,
297 description => "Use volume as VIRTIO hard disk (n is 0 to " . ($MAX_VIRTIO_DISKS - 1) . ").",
298};
299PVE::JSONSchema::register_standard_option("pve-qm-virtio", $virtiodesc);
300
301my $alldrive_fmt = {
302 %drivedesc_base,
303 %iothread_fmt,
304 %model_fmt,
305 %queues_fmt,
306 %scsiblock_fmt,
307 %ssd_fmt,
308 %wwn_fmt,
309};
310
311my $efidisk_fmt = {
312 volume => { alias => 'file' },
313 file => {
314 type => 'string',
315 format => 'pve-volume-id-or-qm-path',
316 default_key => 1,
317 format_description => 'volume',
318 description => "The drive's backing volume.",
319 },
320 format => get_standard_option('pve-qm-image-format'),
321 size => {
322 type => 'string',
323 format => 'disk-size',
324 format_description => 'DiskSize',
325 description => "Disk size. This is purely informational and has no effect.",
326 optional => 1,
327 },
328};
329
330my $efidisk_desc = {
331 optional => 1,
332 type => 'string', format => $efidisk_fmt,
333 description => "Configure a Disk for storing EFI vars",
334};
335
336PVE::JSONSchema::register_standard_option("pve-qm-efidisk", $efidisk_desc);
337
43c4c7b6
FE
338my $unused_fmt = {
339 volume => { alias => 'file' },
340 file => {
341 type => 'string',
342 format => 'pve-volume-id',
343 default_key => 1,
344 format_description => 'volume',
345 description => "The drive's backing volume.",
346 },
347};
348
349my $unuseddesc = {
350 optional => 1,
351 type => 'string', format => $unused_fmt,
352 description => "Reference to unused volumes. This is used internally, and should not be modified manually.",
353};
354
e0fd2b2f
FE
355for (my $i = 0; $i < $MAX_IDE_DISKS; $i++) {
356 $drivedesc_hash->{"ide$i"} = $idedesc;
357}
358
359for (my $i = 0; $i < $MAX_SATA_DISKS; $i++) {
360 $drivedesc_hash->{"sata$i"} = $satadesc;
361}
362
363for (my $i = 0; $i < $MAX_SCSI_DISKS; $i++) {
364 $drivedesc_hash->{"scsi$i"} = $scsidesc;
365}
366
367for (my $i = 0; $i < $MAX_VIRTIO_DISKS; $i++) {
368 $drivedesc_hash->{"virtio$i"} = $virtiodesc;
369}
370
371$drivedesc_hash->{efidisk0} = $efidisk_desc;
372
43c4c7b6
FE
373for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
374 $drivedesc_hash->{"unused$i"} = $unuseddesc;
375}
e0fd2b2f
FE
376
377sub 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))),
383 'efidisk0');
384}
385
386sub is_valid_drivename {
387 my $dev = shift;
388
43c4c7b6 389 return defined($drivedesc_hash->{$dev}) && $dev !~ /^unused\d+$/;
e0fd2b2f
FE
390}
391
392PVE::JSONSchema::register_format('pve-qm-bootdisk', \&verify_bootdisk);
393sub verify_bootdisk {
394 my ($value, $noerr) = @_;
395
396 return $value if is_valid_drivename($value);
397
d1c1af4b 398 return if $noerr;
e0fd2b2f
FE
399
400 die "invalid boot disk '$value'\n";
401}
402
403sub drive_is_cloudinit {
404 my ($drive) = @_;
405 return $drive->{file} =~ m@[:/]vm-\d+-cloudinit(?:\.$QEMU_FORMAT_RE)?$@;
406}
407
408sub drive_is_cdrom {
409 my ($drive, $exclude_cloudinit) = @_;
410
411 return 0 if $exclude_cloudinit && drive_is_cloudinit($drive);
412
413 return $drive && $drive->{media} && ($drive->{media} eq 'cdrom');
414}
415
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]
421
422sub parse_drive {
423 my ($key, $data) = @_;
424
425 my ($interface, $index);
426
427 if ($key =~ m/^([^\d]+)(\d+)$/) {
428 $interface = $1;
429 $index = $2;
430 } else {
d1c1af4b 431 return;
e0fd2b2f
FE
432 }
433
43c4c7b6 434 if (!defined($drivedesc_hash->{$key})) {
e0fd2b2f 435 warn "invalid drive key: $key\n";
d1c1af4b 436 return;
e0fd2b2f 437 }
43c4c7b6
FE
438
439 my $desc = $drivedesc_hash->{$key}->{format};
e0fd2b2f 440 my $res = eval { PVE::JSONSchema::parse_property_string($desc, $data) };
d1c1af4b 441 return if !$res;
e0fd2b2f
FE
442 $res->{interface} = $interface;
443 $res->{index} = $index;
444
445 my $error = 0;
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";
450 ++$error;
451 next;
452 }
453 $res->{"m$opt"} = sprintf("%.3f", $bps / (1024*1024.0));
454 }
455 }
456
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";
474 ++$error;
475 }
476 }
477
d1c1af4b 478 return if $error;
e0fd2b2f 479
d1c1af4b
TL
480 return if $res->{mbps_rd} && $res->{mbps};
481 return if $res->{mbps_wr} && $res->{mbps};
482 return if $res->{iops_rd} && $res->{iops};
483 return if $res->{iops_wr} && $res->{iops};
e0fd2b2f
FE
484
485 if ($res->{media} && ($res->{media} eq 'cdrom')) {
d1c1af4b
TL
486 return if $res->{snapshot} || $res->{trans} || $res->{format};
487 return if $res->{heads} || $res->{secs} || $res->{cyls};
488 return if $res->{interface} eq 'virtio';
e0fd2b2f
FE
489 }
490
491 if (my $size = $res->{size}) {
d1c1af4b 492 return if !defined($res->{size} = PVE::JSONSchema::parse_size($size));
e0fd2b2f
FE
493 }
494
495 return $res;
496}
497
498sub print_drive {
499 my ($drive) = @_;
500 my $skip = [ 'index', 'interface' ];
501 return PVE::JSONSchema::print_property_string($drive, $alldrive_fmt, $skip);
502}
503
2141a802
SR
504sub get_bootdisks {
505 my ($conf) = @_;
506
507 my $bootcfg = PVE::JSONSchema::parse_property_string('pve-qm-boot', $conf->{boot})
508 if $conf->{boot};
509
510 if (!defined($bootcfg) || $bootcfg->{legacy}) {
511 return [$conf->{bootdisk}] if $conf->{bootdisk};
512 return [];
513 }
514
515 my @list = PVE::Tools::split_list($bootcfg->{order});
516 @list = grep {is_valid_drivename($_)} @list;
517 return \@list;
518}
519
776c5f50 520sub bootdisk_size {
e0fd2b2f
FE
521 my ($storecfg, $conf) = @_;
522
2141a802 523 my $bootdisks = get_bootdisks($conf);
d1c1af4b 524 return if !@$bootdisks;
2141a802 525 my $bootdisk = $bootdisks->[0];
d1c1af4b 526 return if !is_valid_drivename($bootdisk);
e0fd2b2f 527
d1c1af4b 528 return if !$conf->{$bootdisk};
e0fd2b2f
FE
529
530 my $drive = parse_drive($bootdisk, $conf->{$bootdisk});
d1c1af4b 531 return if !defined($drive);
e0fd2b2f 532
d1c1af4b 533 return if drive_is_cdrom($drive);
e0fd2b2f
FE
534
535 my $volid = $drive->{file};
d1c1af4b 536 return if !$volid;
e0fd2b2f
FE
537
538 return $drive->{size};
539}
540
541sub update_disksize {
9b29cbd0 542 my ($drive, $newsize) = @_;
e0fd2b2f 543
d1c1af4b 544 return if !defined($newsize);
e0fd2b2f 545
63e313f3 546 my $oldsize = $drive->{size} // 0;
e0fd2b2f 547
63e313f3 548 if ($newsize != $oldsize) {
e0fd2b2f
FE
549 $drive->{size} = $newsize;
550
551 my $old_fmt = PVE::JSONSchema::format_size($oldsize);
552 my $new_fmt = PVE::JSONSchema::format_size($newsize);
553
9b29cbd0
FE
554 my $msg = "size of disk '$drive->{file}' updated from $old_fmt to $new_fmt";
555
556 return ($drive, $msg);
e0fd2b2f
FE
557 }
558
d1c1af4b 559 return;
e0fd2b2f
FE
560}
561
562sub is_volume_in_use {
563 my ($storecfg, $conf, $skip_drive, $volid) = @_;
564
565 my $path = PVE::Storage::path($storecfg, $volid);
566
567 my $scan_config = sub {
568 my ($cref, $snapname) = @_;
569
570 foreach my $key (keys %$cref) {
571 my $value = $cref->{$key};
572 if (is_valid_drivename($key)) {
573 next if $skip_drive && $key eq $skip_drive;
574 my $drive = parse_drive($key, $value);
575 next if !$drive || !$drive->{file} || drive_is_cdrom($drive);
576 return 1 if $volid eq $drive->{file};
577 if ($drive->{file} =~ m!^/!) {
578 return 1 if $drive->{file} eq $path;
579 } else {
580 my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file}, 1);
581 next if !$storeid;
582 my $scfg = PVE::Storage::storage_config($storecfg, $storeid, 1);
583 next if !$scfg;
584 return 1 if $path eq PVE::Storage::path($storecfg, $drive->{file}, $snapname);
585 }
586 }
587 }
588
589 return 0;
590 };
591
592 return 1 if &$scan_config($conf);
593
594 undef $skip_drive;
595
596 foreach my $snapname (keys %{$conf->{snapshots}}) {
597 return 1 if &$scan_config($conf->{snapshots}->{$snapname}, $snapname);
598 }
599
600 return 0;
601}
602
603sub resolve_first_disk {
5cfa9f5f 604 my ($conf, $cdrom) = @_;
e0fd2b2f 605 my @disks = valid_drive_names();
5cfa9f5f 606 foreach my $ds (@disks) {
e0fd2b2f
FE
607 next if !$conf->{$ds};
608 my $disk = parse_drive($ds, $conf->{$ds});
5cfa9f5f
SR
609 next if drive_is_cdrom($disk) xor $cdrom;
610 return $ds;
e0fd2b2f 611 }
d1c1af4b 612 return;
e0fd2b2f
FE
613}
614
6151;