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

Dominic Jäger d.jaeger at proxmox.com
Fri Feb 12 12:13:00 CET 2021


On Fri, Feb 12, 2021 at 10:03:50AM +0100, Fabian Grünbichler wrote:
> >> > +    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?
> 
> well, it should be the same and fairly cheap since it comes from a cache 
> after the first request, but if you trigger a cfs_update() in-between 
> you might get two different versions.
Makes sense. Fixed it.

> >> > +# 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?
> 
> optionally supporting block devices should not be able to break any 
> existing call sites ;) but yeah, you can also replace your helper here 
> with a fork of abs_filesystem_path that allows -b, that would allow 
> simply switching it out if we want to handle that in pve-storage..
> 
> >> 
Makes sense, too. I added another parameter to abs_filesystem_path.

> > then
> >> 
> >> > +    if ($source !~ m!^/!) {
> >> > +	die "source must be an absolute path but is $source";
> > will only say "... path but is  at /usr/share/..."?
> 
> no, because that check gets removed as well.. $source could be a volid 
> at this point
Thank you. It became clear when I changed all the signatures...
> 
> > 
> >> 
> >> > +    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} ?
> 
> no, I mean storage_config() ;)
> 
> it checks whether $storage is defined & non-empty, is an actually 
> defined-in-storage.cfg storage, and returns that storage's config 
> section.
Sorry, now I see it. Reading is hard...

> >> > +	    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
> 
> it's a volid, but not a valid VM-owned volume that we can do ACL checks 
> on.. so it's important to think about where to use `parse_volid` vs 
> `parse_volname` (the latter gives you all the checks per storage type, 
> and returns type of volume, associated VMID, etc.).
> 
> for this series/patch, we can accept a proper vdisk volid if the user 
> has access, or any volid/path for root. we can't allow arbitrary volids 
> for unprivileged users, as that would allow reading ANY file on the 
> storage.
OK, will have to give that a closer look.

> >> > +	    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?
> 
> as separate parameter they are only used when creating/importing/.. NEW 
> disks. of course, they are also part of the schema as part of property 
> strings. also dir-disk-format would be wrong - just because 
> non-dir-storages only support raw(/subvol) doesn't make the parameter 
> invalid for them ;)
Right :D

> 
> >> 
> >> > +	    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?
> 
> not sure whether that's trivially possible - the options are not uniform 
> across bus / device types? you could have a 'everything optional 
> superset of all disk device types' schema, that would be less strict 
> than the final check but better than nothing? looking at 
> PVE::QemuServer::Drive it almost exists in $alldrive_fmt, so cloning 
> that and removing the file property could work?
I'll give it a try. Having some checks sounds better than none at all to me, too.

> >> > +	}
> >> > +	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'));
> 
> I think a function is overkill for a single use, just
> 
> my $format;
> if (...) {
>   // decide and set $format
> }
OK. My if-solution so far looks ugly to me, but it avoids those extra variables
in outer scope.
> >> > +	$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...
> 
> check before, compare digest after fork + lock + reload should handle any 
> changes in-between?
> 
> then you only need to duplicate the digest check.
I like. Done.

> 
> > 
> >> > +	    }
> >> 
> >> > +
> >> > +	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.
> 
> it also allows you to give concrete error messages if you do one-to-one 
> mapping:
> 
> "scsi0 configured for disk import, but no matching import source given"
> "virtio0 not configured for disk import, but import source '...' given"
That might work together well with the filter-out-disk-parameters idea

> >> 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





More information about the pve-devel mailing list