mirror of
https://git.proxmox.com/git/qemu-server
synced 2025-05-30 00:30:42 +00:00
schema: drive: use separate schema when disk allocation is possible
via the special syntax <storeid>:<size>. Not worth it by itself, but this is anticipating a new 'import-from' parameter which is only used upon import/allocation, but shouldn't be part of the schema for the config or other API enpoints. Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
This commit is contained in:
parent
39051ac0df
commit
c1accf9db9
@ -63,28 +63,43 @@ my $resolve_cdrom_alias = sub {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Used in import-enabled API endpoints. Parses drives using the extended '_with_alloc' schema.
|
||||||
|
my $foreach_volume_with_alloc = sub {
|
||||||
|
my ($param, $func) = @_;
|
||||||
|
|
||||||
|
for my $opt (sort keys $param->%*) {
|
||||||
|
next if !PVE::QemuServer::is_valid_drivename($opt);
|
||||||
|
|
||||||
|
my $drive = PVE::QemuServer::Drive::parse_drive($opt, $param->{$opt}, 1);
|
||||||
|
next if !$drive;
|
||||||
|
|
||||||
|
$func->($opt, $drive);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
|
||||||
|
|
||||||
my $check_drive_param = sub {
|
my $check_drive_param = sub {
|
||||||
my ($param, $storecfg, $extra_checks) = @_;
|
my ($param, $storecfg, $extra_checks) = @_;
|
||||||
|
|
||||||
for my $opt (sort keys $param->%*) {
|
for my $opt (sort keys $param->%*) {
|
||||||
next if !PVE::QemuServer::is_valid_drivename($opt);
|
next if !PVE::QemuServer::is_valid_drivename($opt);
|
||||||
|
|
||||||
my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
|
my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt}, 1);
|
||||||
raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
|
raise_param_exc({ $opt => "unable to parse drive options" }) if !$drive;
|
||||||
|
|
||||||
PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
|
PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive);
|
||||||
|
|
||||||
$extra_checks->($drive) if $extra_checks;
|
$extra_checks->($drive) if $extra_checks;
|
||||||
|
|
||||||
$param->{$opt} = PVE::QemuServer::print_drive($drive);
|
$param->{$opt} = PVE::QemuServer::print_drive($drive, 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
|
|
||||||
my $check_storage_access = sub {
|
my $check_storage_access = sub {
|
||||||
my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
|
my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
|
||||||
|
|
||||||
PVE::QemuConfig->foreach_volume($settings, sub {
|
$foreach_volume_with_alloc->($settings, sub {
|
||||||
my ($ds, $drive) = @_;
|
my ($ds, $drive) = @_;
|
||||||
|
|
||||||
my $isCDROM = PVE::QemuServer::drive_is_cdrom($drive);
|
my $isCDROM = PVE::QemuServer::drive_is_cdrom($drive);
|
||||||
@ -256,7 +271,7 @@ my $create_disks = sub {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
eval { PVE::QemuConfig->foreach_volume($settings, $code); };
|
eval { $foreach_volume_with_alloc->($settings, $code); };
|
||||||
|
|
||||||
# free allocated images on error
|
# free allocated images on error
|
||||||
if (my $err = $@) {
|
if (my $err = $@) {
|
||||||
@ -583,7 +598,9 @@ __PACKAGE__->register_method({
|
|||||||
default => 0,
|
default => 0,
|
||||||
description => "Start VM after it was created successfully.",
|
description => "Start VM after it was created successfully.",
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
|
1, # with_disk_alloc
|
||||||
|
),
|
||||||
},
|
},
|
||||||
returns => {
|
returns => {
|
||||||
type => 'string',
|
type => 'string',
|
||||||
@ -1304,7 +1321,7 @@ my $update_vm_api = sub {
|
|||||||
|
|
||||||
my $check_drive_perms = sub {
|
my $check_drive_perms = sub {
|
||||||
my ($opt, $val) = @_;
|
my ($opt, $val) = @_;
|
||||||
my $drive = PVE::QemuServer::parse_drive($opt, $val);
|
my $drive = PVE::QemuServer::parse_drive($opt, $val, 1);
|
||||||
# FIXME: cloudinit: CDROM or Disk?
|
# FIXME: cloudinit: CDROM or Disk?
|
||||||
if (PVE::QemuServer::drive_is_cdrom($drive)) { # CDROM
|
if (PVE::QemuServer::drive_is_cdrom($drive)) { # CDROM
|
||||||
$rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
|
$rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
|
||||||
@ -1410,7 +1427,7 @@ my $update_vm_api = sub {
|
|||||||
# default legacy boot order implies all cdroms anyway
|
# default legacy boot order implies all cdroms anyway
|
||||||
if (@bootorder) {
|
if (@bootorder) {
|
||||||
# append new CD drives to bootorder to mark them bootable
|
# append new CD drives to bootorder to mark them bootable
|
||||||
my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
|
my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt}, 1);
|
||||||
if (PVE::QemuServer::drive_is_cdrom($drive, 1) && !grep(/^$opt$/, @bootorder)) {
|
if (PVE::QemuServer::drive_is_cdrom($drive, 1) && !grep(/^$opt$/, @bootorder)) {
|
||||||
push @bootorder, $opt;
|
push @bootorder, $opt;
|
||||||
$conf->{pending}->{boot} = PVE::QemuServer::print_bootorder(\@bootorder);
|
$conf->{pending}->{boot} = PVE::QemuServer::print_bootorder(\@bootorder);
|
||||||
@ -1573,7 +1590,9 @@ __PACKAGE__->register_method({
|
|||||||
maximum => 30,
|
maximum => 30,
|
||||||
optional => 1,
|
optional => 1,
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
|
1, # with_disk_alloc
|
||||||
|
),
|
||||||
},
|
},
|
||||||
returns => {
|
returns => {
|
||||||
type => 'string',
|
type => 'string',
|
||||||
@ -1621,7 +1640,9 @@ __PACKAGE__->register_method({
|
|||||||
maxLength => 40,
|
maxLength => 40,
|
||||||
optional => 1,
|
optional => 1,
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
|
1, # with_disk_alloc
|
||||||
|
),
|
||||||
},
|
},
|
||||||
returns => { type => 'null' },
|
returns => { type => 'null' },
|
||||||
code => sub {
|
code => sub {
|
||||||
|
@ -2184,7 +2184,7 @@ sub verify_usb_device {
|
|||||||
|
|
||||||
# add JSON properties for create and set function
|
# add JSON properties for create and set function
|
||||||
sub json_config_properties {
|
sub json_config_properties {
|
||||||
my $prop = shift;
|
my ($prop, $with_disk_alloc) = @_;
|
||||||
|
|
||||||
my $skip_json_config_opts = {
|
my $skip_json_config_opts = {
|
||||||
parent => 1,
|
parent => 1,
|
||||||
@ -2197,7 +2197,12 @@ sub json_config_properties {
|
|||||||
|
|
||||||
foreach my $opt (keys %$confdesc) {
|
foreach my $opt (keys %$confdesc) {
|
||||||
next if $skip_json_config_opts->{$opt};
|
next if $skip_json_config_opts->{$opt};
|
||||||
$prop->{$opt} = $confdesc->{$opt};
|
|
||||||
|
if ($with_disk_alloc && is_valid_drivename($opt)) {
|
||||||
|
$prop->{$opt} = $PVE::QemuServer::Drive::drivedesc_hash_with_alloc->{$opt};
|
||||||
|
} else {
|
||||||
|
$prop->{$opt} = $confdesc->{$opt};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $prop;
|
return $prop;
|
||||||
|
@ -3,6 +3,8 @@ package PVE::QemuServer::Drive;
|
|||||||
use strict;
|
use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
|
||||||
|
use Storable qw(dclone);
|
||||||
|
|
||||||
use PVE::Storage;
|
use PVE::Storage;
|
||||||
use PVE::JSONSchema qw(get_standard_option);
|
use PVE::JSONSchema qw(get_standard_option);
|
||||||
|
|
||||||
@ -33,6 +35,8 @@ our $MAX_SATA_DISKS = 6;
|
|||||||
our $MAX_UNUSED_DISKS = 256;
|
our $MAX_UNUSED_DISKS = 256;
|
||||||
|
|
||||||
our $drivedesc_hash;
|
our $drivedesc_hash;
|
||||||
|
# Schema when disk allocation is possible.
|
||||||
|
our $drivedesc_hash_with_alloc = {};
|
||||||
|
|
||||||
my %drivedesc_base = (
|
my %drivedesc_base = (
|
||||||
volume => { alias => 'file' },
|
volume => { alias => 'file' },
|
||||||
@ -262,14 +266,10 @@ my $ide_fmt = {
|
|||||||
};
|
};
|
||||||
PVE::JSONSchema::register_format("pve-qm-ide", $ide_fmt);
|
PVE::JSONSchema::register_format("pve-qm-ide", $ide_fmt);
|
||||||
|
|
||||||
my $ALLOCATION_SYNTAX_DESC =
|
|
||||||
"Use the special syntax STORAGE_ID:SIZE_IN_GiB to allocate a new volume.";
|
|
||||||
|
|
||||||
my $idedesc = {
|
my $idedesc = {
|
||||||
optional => 1,
|
optional => 1,
|
||||||
type => 'string', format => $ide_fmt,
|
type => 'string', format => $ide_fmt,
|
||||||
description => "Use volume as IDE hard disk or CD-ROM (n is 0 to " .($MAX_IDE_DISKS -1) . "). " .
|
description => "Use volume as IDE hard disk or CD-ROM (n is 0 to " .($MAX_IDE_DISKS - 1) . ").",
|
||||||
$ALLOCATION_SYNTAX_DESC,
|
|
||||||
};
|
};
|
||||||
PVE::JSONSchema::register_standard_option("pve-qm-ide", $idedesc);
|
PVE::JSONSchema::register_standard_option("pve-qm-ide", $idedesc);
|
||||||
|
|
||||||
@ -285,8 +285,7 @@ my $scsi_fmt = {
|
|||||||
my $scsidesc = {
|
my $scsidesc = {
|
||||||
optional => 1,
|
optional => 1,
|
||||||
type => 'string', format => $scsi_fmt,
|
type => 'string', format => $scsi_fmt,
|
||||||
description => "Use volume as SCSI hard disk or CD-ROM (n is 0 to " . ($MAX_SCSI_DISKS - 1) . "). " .
|
description => "Use volume as SCSI hard disk or CD-ROM (n is 0 to " . ($MAX_SCSI_DISKS - 1) . ").",
|
||||||
$ALLOCATION_SYNTAX_DESC,
|
|
||||||
};
|
};
|
||||||
PVE::JSONSchema::register_standard_option("pve-qm-scsi", $scsidesc);
|
PVE::JSONSchema::register_standard_option("pve-qm-scsi", $scsidesc);
|
||||||
|
|
||||||
@ -298,8 +297,7 @@ my $sata_fmt = {
|
|||||||
my $satadesc = {
|
my $satadesc = {
|
||||||
optional => 1,
|
optional => 1,
|
||||||
type => 'string', format => $sata_fmt,
|
type => 'string', format => $sata_fmt,
|
||||||
description => "Use volume as SATA hard disk or CD-ROM (n is 0 to " . ($MAX_SATA_DISKS - 1). "). " .
|
description => "Use volume as SATA hard disk or CD-ROM (n is 0 to " . ($MAX_SATA_DISKS - 1). ").",
|
||||||
$ALLOCATION_SYNTAX_DESC,
|
|
||||||
};
|
};
|
||||||
PVE::JSONSchema::register_standard_option("pve-qm-sata", $satadesc);
|
PVE::JSONSchema::register_standard_option("pve-qm-sata", $satadesc);
|
||||||
|
|
||||||
@ -311,8 +309,7 @@ my $virtio_fmt = {
|
|||||||
my $virtiodesc = {
|
my $virtiodesc = {
|
||||||
optional => 1,
|
optional => 1,
|
||||||
type => 'string', format => $virtio_fmt,
|
type => 'string', format => $virtio_fmt,
|
||||||
description => "Use volume as VIRTIO hard disk (n is 0 to " . ($MAX_VIRTIO_DISKS - 1) . "). " .
|
description => "Use volume as VIRTIO hard disk (n is 0 to " . ($MAX_VIRTIO_DISKS - 1) . ").",
|
||||||
$ALLOCATION_SYNTAX_DESC,
|
|
||||||
};
|
};
|
||||||
PVE::JSONSchema::register_standard_option("pve-qm-virtio", $virtiodesc);
|
PVE::JSONSchema::register_standard_option("pve-qm-virtio", $virtiodesc);
|
||||||
|
|
||||||
@ -359,9 +356,7 @@ my $efidisk_fmt = {
|
|||||||
my $efidisk_desc = {
|
my $efidisk_desc = {
|
||||||
optional => 1,
|
optional => 1,
|
||||||
type => 'string', format => $efidisk_fmt,
|
type => 'string', format => $efidisk_fmt,
|
||||||
description => "Configure a Disk for storing EFI vars. " .
|
description => "Configure a Disk for storing EFI vars.",
|
||||||
$ALLOCATION_SYNTAX_DESC . " Note that SIZE_IN_GiB is ignored here " .
|
|
||||||
"and that the default EFI vars are copied to the volume instead.",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
PVE::JSONSchema::register_standard_option("pve-qm-efidisk", $efidisk_desc);
|
PVE::JSONSchema::register_standard_option("pve-qm-efidisk", $efidisk_desc);
|
||||||
@ -397,10 +392,7 @@ my $tpmstate_fmt = {
|
|||||||
my $tpmstate_desc = {
|
my $tpmstate_desc = {
|
||||||
optional => 1,
|
optional => 1,
|
||||||
type => 'string', format => $tpmstate_fmt,
|
type => 'string', format => $tpmstate_fmt,
|
||||||
description => "Configure a Disk for storing TPM state. " .
|
description => "Configure a Disk for storing TPM state. The format is fixed to 'raw'.",
|
||||||
$ALLOCATION_SYNTAX_DESC . " Note that SIZE_IN_GiB is ignored here " .
|
|
||||||
"and that the default size of 4 MiB will always be used instead. The " .
|
|
||||||
"format is also fixed to 'raw'.",
|
|
||||||
};
|
};
|
||||||
use constant TPMSTATE_DISK_SIZE => 4 * 1024 * 1024;
|
use constant TPMSTATE_DISK_SIZE => 4 * 1024 * 1024;
|
||||||
|
|
||||||
@ -417,6 +409,10 @@ my $alldrive_fmt = {
|
|||||||
%efitype_fmt,
|
%efitype_fmt,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
my $alldrive_fmt_with_alloc = {
|
||||||
|
%$alldrive_fmt,
|
||||||
|
};
|
||||||
|
|
||||||
my $unused_fmt = {
|
my $unused_fmt = {
|
||||||
volume => { alias => 'file' },
|
volume => { alias => 'file' },
|
||||||
file => {
|
file => {
|
||||||
@ -434,27 +430,61 @@ my $unuseddesc = {
|
|||||||
description => "Reference to unused volumes. This is used internally, and should not be modified manually.",
|
description => "Reference to unused volumes. This is used internally, and should not be modified manually.",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
my $with_alloc_desc_cache = {
|
||||||
|
unused => $unuseddesc, # Allocation for unused is not supported currently.
|
||||||
|
};
|
||||||
|
my $desc_with_alloc = sub {
|
||||||
|
my ($type, $desc) = @_;
|
||||||
|
|
||||||
|
return $with_alloc_desc_cache->{$type} if $with_alloc_desc_cache->{$type};
|
||||||
|
|
||||||
|
my $new_desc = dclone($desc);
|
||||||
|
|
||||||
|
my $extra_note = '';
|
||||||
|
if ($type eq 'efidisk') {
|
||||||
|
$extra_note = " Note that SIZE_IN_GiB is ignored here and that the default EFI vars are ".
|
||||||
|
"copied to the volume instead.";
|
||||||
|
} elsif ($type eq 'tpmstate') {
|
||||||
|
$extra_note = " Note that SIZE_IN_GiB is ignored here and 4 MiB will be used instead.";
|
||||||
|
}
|
||||||
|
|
||||||
|
$new_desc->{description} .= " Use the special syntax STORAGE_ID:SIZE_IN_GiB to allocate a new ".
|
||||||
|
"volume.${extra_note}";
|
||||||
|
|
||||||
|
$with_alloc_desc_cache->{$type} = $new_desc;
|
||||||
|
|
||||||
|
return $new_desc;
|
||||||
|
};
|
||||||
|
|
||||||
for (my $i = 0; $i < $MAX_IDE_DISKS; $i++) {
|
for (my $i = 0; $i < $MAX_IDE_DISKS; $i++) {
|
||||||
$drivedesc_hash->{"ide$i"} = $idedesc;
|
$drivedesc_hash->{"ide$i"} = $idedesc;
|
||||||
|
$drivedesc_hash_with_alloc->{"ide$i"} = $desc_with_alloc->('ide', $idedesc);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (my $i = 0; $i < $MAX_SATA_DISKS; $i++) {
|
for (my $i = 0; $i < $MAX_SATA_DISKS; $i++) {
|
||||||
$drivedesc_hash->{"sata$i"} = $satadesc;
|
$drivedesc_hash->{"sata$i"} = $satadesc;
|
||||||
|
$drivedesc_hash_with_alloc->{"sata$i"} = $desc_with_alloc->('sata', $satadesc);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (my $i = 0; $i < $MAX_SCSI_DISKS; $i++) {
|
for (my $i = 0; $i < $MAX_SCSI_DISKS; $i++) {
|
||||||
$drivedesc_hash->{"scsi$i"} = $scsidesc;
|
$drivedesc_hash->{"scsi$i"} = $scsidesc;
|
||||||
|
$drivedesc_hash_with_alloc->{"scsi$i"} = $desc_with_alloc->('scsi', $scsidesc);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (my $i = 0; $i < $MAX_VIRTIO_DISKS; $i++) {
|
for (my $i = 0; $i < $MAX_VIRTIO_DISKS; $i++) {
|
||||||
$drivedesc_hash->{"virtio$i"} = $virtiodesc;
|
$drivedesc_hash->{"virtio$i"} = $virtiodesc;
|
||||||
|
$drivedesc_hash_with_alloc->{"virtio$i"} = $desc_with_alloc->('virtio', $virtiodesc);
|
||||||
}
|
}
|
||||||
|
|
||||||
$drivedesc_hash->{efidisk0} = $efidisk_desc;
|
$drivedesc_hash->{efidisk0} = $efidisk_desc;
|
||||||
|
$drivedesc_hash_with_alloc->{efidisk0} = $desc_with_alloc->('efidisk', $efidisk_desc);
|
||||||
|
|
||||||
$drivedesc_hash->{tpmstate0} = $tpmstate_desc;
|
$drivedesc_hash->{tpmstate0} = $tpmstate_desc;
|
||||||
|
$drivedesc_hash_with_alloc->{tpmstate0} = $desc_with_alloc->('tpmstate', $tpmstate_desc);
|
||||||
|
|
||||||
for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
|
for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
|
||||||
$drivedesc_hash->{"unused$i"} = $unuseddesc;
|
$drivedesc_hash->{"unused$i"} = $unuseddesc;
|
||||||
|
$drivedesc_hash_with_alloc->{"unused$i"} = $desc_with_alloc->('unused', $unuseddesc);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub valid_drive_names_for_boot {
|
sub valid_drive_names_for_boot {
|
||||||
@ -521,7 +551,7 @@ sub drive_is_read_only {
|
|||||||
# [,iothread=on][,serial=serial][,model=model]
|
# [,iothread=on][,serial=serial][,model=model]
|
||||||
|
|
||||||
sub parse_drive {
|
sub parse_drive {
|
||||||
my ($key, $data) = @_;
|
my ($key, $data, $with_alloc) = @_;
|
||||||
|
|
||||||
my ($interface, $index);
|
my ($interface, $index);
|
||||||
|
|
||||||
@ -532,12 +562,14 @@ sub parse_drive {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!defined($drivedesc_hash->{$key})) {
|
my $desc_hash = $with_alloc ? $drivedesc_hash_with_alloc : $drivedesc_hash;
|
||||||
|
|
||||||
|
if (!defined($desc_hash->{$key})) {
|
||||||
warn "invalid drive key: $key\n";
|
warn "invalid drive key: $key\n";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $desc = $drivedesc_hash->{$key}->{format};
|
my $desc = $desc_hash->{$key}->{format};
|
||||||
my $res = eval { PVE::JSONSchema::parse_property_string($desc, $data) };
|
my $res = eval { PVE::JSONSchema::parse_property_string($desc, $data) };
|
||||||
return if !$res;
|
return if !$res;
|
||||||
$res->{interface} = $interface;
|
$res->{interface} = $interface;
|
||||||
@ -597,9 +629,10 @@ sub parse_drive {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub print_drive {
|
sub print_drive {
|
||||||
my ($drive) = @_;
|
my ($drive, $with_alloc) = @_;
|
||||||
my $skip = [ 'index', 'interface' ];
|
my $skip = [ 'index', 'interface' ];
|
||||||
return PVE::JSONSchema::print_property_string($drive, $alldrive_fmt, $skip);
|
my $fmt = $with_alloc ? $alldrive_fmt_with_alloc : $alldrive_fmt;
|
||||||
|
return PVE::JSONSchema::print_property_string($drive, $fmt, $skip);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub get_bootdisks {
|
sub get_bootdisks {
|
||||||
|
Loading…
Reference in New Issue
Block a user