[pve-devel] [PATCH v4 qemu-server] Add API for disk & VM import

Dominic Jäger d.jaeger at proxmox.com
Thu Feb 11 11:32:23 CET 2021


Thank you for looking so carefully!

On Wed, Feb 10, 2021 at 10:40:56AM +0100, Fabian Grünbichler wrote:
> 
> On February 5, 2021 11:04 am, Dominic Jäger wrote:
> > Extend qm importdisk/importovf functionality to the API.
> >  
> > @@ -4325,4 +4324,378 @@ __PACKAGE__->register_method({
> >  	return PVE::QemuServer::Cloudinit::dump_cloudinit_config($conf, $param->{vmid}, $param->{type});
> >      }});
> >  
> > +# Raise exception if $format is not supported by $storageid
> > +my $check_format_is_supported = sub {
> > +    my ($format, $storageid) = @_;
> > +
> > +    return if !$format;
> > +
> > +    my $store_conf = PVE::Storage::config();
> 
> it probably makes sense to pass this in as parameter, all call sites 
> probably have it already ;)

I just noticed that I have it in importdisk, but unused.  Does it make a
relevant difference in terms of performance to call ::config() multiple times
instead of passing it as parameter?

> 
> > +    }
> > +};
> > +
> > +# paths are returned as is
> > +# volids are returned as paths
> > +#
> > +# Also checks if $original actually exists
> > +my $convert_to_path = sub {
> > +	my ($original) = @_;
> > +	my $volid_as_path = eval { # Nonempty iff $original_source is a volid
> > +	    PVE::Storage::path(PVE::Storage::config(), $original);
> > +	};
> > +	my $result = $volid_as_path || $original ;
> > +	if (!-e $result) {
> > +	    die "Could not import because source '$original' does not exist!";
> > +	}
> > +	return $result;
> > +};
> 
> what does this do that PVE::Storage::abs_filesystem_path doesn't already 
> do (except having 'import' in the error message ;))?

Haven't thought about that function in the last time... 
I'll have a look at it.

> ah, further down below I see that -b might be missing for raw block 
> devices.. maybe it makes sense to allow those in that helper as well? 
> optionally? call-sites would need to be checked..
 Would it make sense to not change helper for the moment, certainly not break
other call-sites and refactor later in a separate patch?
> 
> > +
> > +# vmid ... target VM ID
> > +# source ... absolute path of the source image (volid must be converted before)
> 
> that restriction is not actually needed, see below
> 
> > +# storage ... target storage for the disk image
> > +# format ... target format for the disk image (optional)
> > +#
> > +# returns ... volid of the allocated disk image (e.g. local-lvm:vm-100-disk-2)
> > +my $import_disk_image = sub {
> > +    my ($param) = @_;
> > +    my $vmid = $param->{vmid};
> > +    my $requested_format = $param->{format};
> > +    my $storage = $param->{storage};
> > +    my $source = $param->{source};
> > +
> > +    my $vm_conf = PVE::QemuConfig->load_config($vmid);
> 
> could be passed in, the call sites already have it.. and it might allow 
> to pull in some of the surrounding codes at the call sites that is 
> similar (config updating, property string building) into this helper

It's actually unused. I'll remove it.
> 
> > +    my $store_conf = PVE::Storage::config();
> 
> could also be passed in here
* see below
> 
> > +    if (!$source) {
> > +	die "It is necessary to pass the source parameter";
> > +    }
> 
> just a check for definedness
Hmm but if someone does
$import_disk_image->({vmid => 102, source => "", storage => "local-lvm"});
then
> 
> > +    if ($source !~ m!^/!) {
> > +	die "source must be an absolute path but is $source";
will only say "... path but is  at /usr/share/..."?

> 
> > +    if (!$storage) {
> > +	die "It is necessary to pass the storage parameter";
> > +    }
> 
> call PVE::Storage::storage_config() here to get both that and the check 
> that the storage exists..
Do you mean $store_conf->{ids}->{$storage} ?

> 
> > +
> > +__PACKAGE__->register_method ({
> > +    name => 'importdisk',
> > +    path => '{vmid}/importdisk',
> > +    method => 'POST',
> > +    protected => 1, # for worker upid file
> 
> huh? no - because we need elevated privileges to allocate disks on 
> storages..
Might be left over, I'll check.
> 
> > +    proxyto => 'node',
> > +    description => "Import an external disk image into a VM. The image format ".
> > +	"has to be supported by qemu-img.",
> > +    parameters => {
> > +	additionalProperties => 0,
> > +	properties => {
> > +	    node => get_standard_option('pve-node'),
> > +	    vmid => get_standard_option('pve-vmid',
> > +		{completion => \&PVE::QemuServer::complete_vmid}),
> > +	    source => {
> > +		description => "Disk image to import. Can be a volid ".
> > +		    "(local-lvm:vm-104-disk-0), an image on a PVE storage ".
> > +		    "(local:104/toImport.raw) or (for root only) an absolute ".
> 
> how does the second work here? and the whole thing is root-only, so that 
> qualifier at the end is redundant ;)

The description is bad... I guess local:104/toImport.raw is a volid, too?
I could just image it to be useful here to allow special volids like
--source local:99/somedisk.qcow2

> 
> > +	    format => {
> > +		type => 'string',
> > +		description => 'Target format.',
> > +		enum => [ 'raw', 'qcow2', 'vmdk' ],
> > +		optional => 1,
> > +	    },
> 
> not directly related to this series, but this enum is copied all over 
> the place and might be defined in one place as standard option 
> (qm-new-disk-format ?)
Makes sense to me. Only
> (qm-new-disk-format ?)
raw, qcow2 and vmdk are not only for new disks? Those are allowed formats for every directory storage, so maybe something like dir-disk-format?
> 
> > +	    digest => get_standard_option('pve-config-digest'),
> > +	},
> > +    },
> > +    returns => { type => 'string'},
> > +    code => sub {
> > +	my ($param) = @_;
> > +	my $vmid = extract_param($param, 'vmid');
> > +	my $node = extract_param($param, 'node');
> > +	my $original_source = extract_param($param, 'source');
> 
> not sure why this gets this name, it's just passed once and not used 
> afterwards..
It was useful previously... Changed it now.
> 
> > +	my $digest = extract_param($param, 'digest');
> > +	my $device_options = extract_param($param, 'device_options');
> 
> IMHO this one needs to be parsed - e.g., by adding a fake volume with 
> the syntax used in importvm at the front and parsing it according to the 
> disk schema..
> 
> both to catch bogus stuff early, and to make the handling below more 
> robust w.r.t. future changes..

OK. The API currently allows any string. Would it be worth to change that then?
> > +	    if ($format_explicit && $format_device_option) {
> > +		raise_param_exc({format => "Disk format may be specified only once!"});
> 
> format already specified in device_options?
Also good for me.

> 
> > +	    }
> > +	}
> > +	my $format = $format_explicit || $format_device_option;
> 
> since both of those are not needed after this point, this could just be 
> returned / set by the above if, dropping the extra variables in the 
> outer scope..
Do you mean with a small function?
my $get_format = sub {
	...
	return $explicit || $device_options
}
my $format = $get_format($device_options, extract_param($param, 'format'));
> 
> > +	$check_format_is_supported->($format, $storeid);
> > +
> > +	my $locked = sub {
> > +	    my $conf = PVE::QemuConfig->load_config($vmid);
> > +	    PVE::Tools::assert_if_modified($conf->{digest}, $digest);
> > +
> > +	    if ($device && $conf->{$device}) {
> > +		die "Could not import because device $device is already in ".
> > +		"use in VM $vmid. Choose a different device!";
> 
> both of these checks might make sense outside of the worker already to 
> give immediate feedback in the API response..
I wanted to do this but didn't know what to do with the lock.
If I check first and lock later then the config could already have changed?
Or check twice?
1. outside the worker, because most times it is OK and gives a nice error and
2. inside the worker, to be really sure?

Or lock outside the worker? I'll have to try what is actually possible...

> > +	    }
> 
> > +
> > +	my $msg = "There must be exactly as many devices specified as there " .
> > +	    " are devices in the diskimage parameter.\n For example for " .
> > +	    "--scsi0 local-lvm:0,discard=on --scsi1 local:0,cache=unsafe " .
> > +	    "there must be --diskimages scsi0=/source/path,scsi1=/other/path";
> > +	my $device_count = grep { $use_import->($_) } keys %$param;
> 
> why though? isn't it sufficient to say there must be a matching device for 
> each diskimages entry and vice-versa?
That's what I meant, but your formulation is better.

> 
> > +	}
> > +
> > +	my $worker = sub {
> > +	    eval { PVE::QemuConfig->create_and_lock_config($vmid, 0, 'import') };
> > +	    die "Unable to create config for VM import: $@" if $@;
> 
> should happen outside the worker (so that a VMID clash is detected 
> early). inside the worker you just lock and load, then check that the 
> 'import' lock is still there..
> 
> we could also filter out disk parameters, and call update_vm_api with 
> the rest here (those should be fast, but potentially in the future could 
> run into permission issues that would be nice to return early before 
> doing a massive disk conversion that has to be undone afterwards..
> 
> > +
> > +	    my @volids_of_imported = ();
> > +	    eval { foreach my $opt (keys %$param) {
> 
> this could then just iterate over the disk parameters
I thought about that, too. But I didn't take future permission issues into account. And then having a single loop and no filters seemed like the easier-to-read option to me.

I'll change it.
> 
> > +		next if ($opt eq 'start');
> > +
> > +		my $updated_value;
> > +		if ($use_import->($opt)) {
> > +		    # $opt is bus/device like ide0, scsi5
> > +
> > +		    my $device = PVE::QemuServer::parse_drive($opt, $param->{$opt});
> 
> $drive? $device is used in this very patch for something else ;)
Right.


"OK, will fix" to everything that I haven't mentioned. I certainly read it.





More information about the pve-devel mailing list