[pve-devel] [PATCH storage] WIP: upload disk image

Wolfgang Bumiller w.bumiller at proxmox.com
Mon Feb 1 09:45:51 CET 2016


I wonder if it makes more sense to just handle the
($content eq 'images') case in the existing upload call since this
duplicates quite a lot of code and you might just have to adapt the
$content if/else cases and add the '# convert image with qemu-img'
portion conditionally to the worker for the image case? The 'vmid'
option would have to be made optional and checked for the non-images
case.


On Fri, Jan 29, 2016 at 06:42:18PM +0100, Timo Grodzinski wrote:
> Signed-off-by: Timo Grodzinski <t.grodzinski at profihost.ag>
> ---
>  PVE/API2/Storage/Status.pm | 211 +++++++++++++++++++++++++++++++++++++++++++++
>  PVE/Storage.pm             |   2 +-
>  PVE/Storage/DirPlugin.pm   |   2 +-
>  PVE/Storage/Plugin.pm      |   2 +-
>  4 files changed, 214 insertions(+), 3 deletions(-)
> 
> diff --git a/PVE/API2/Storage/Status.pm b/PVE/API2/Storage/Status.pm
> index 49bb58c..f887932 100644
> --- a/PVE/API2/Storage/Status.pm
> +++ b/PVE/API2/Storage/Status.pm
> @@ -421,4 +421,215 @@ __PACKAGE__->register_method ({
>  	return $upid;
>     }});
>      
> +
> +__PACKAGE__->register_method(
> +    {
> +        name        => 'upload_image',
> +        path        => '{storage}/upload_image',
> +        method      => 'POST',
> +        description => "Upload templates and ISO images.",
> +        permissions => {
> +            check => [ 'perm', '/storage/{storage}', ['Datastore.AllocateTemplate'] ]
> +            ,    # XXX [ 'Datastore.AllocateSpace' ]
> +        },
> +        protected  => 1,
> +        parameters => {
> +            additionalProperties => 0,
> +            properties           => {
> +                node => get_standard_option( 'pve-node' ),
> +                vmid => get_standard_option( 'pve-vmid', { completion => \&PVE::QemuServer::complete_vmid } ),
> +                storage => get_standard_option( 'pve-storage-id' ),    # temporary storage before qemu-img
> +                format  => {
> +                    description => "Target format for file storage.",
> +                    type        => 'string',
> +                    optional    => 1,
> +                    enum        => [ 'raw', 'qcow2', 'vmdk' ],
> +                },
> +                content => {
> +                    description => "Content type.",
> +                    type        => 'string',
> +                    format      => 'pve-storage-content',
> +                },
> +                filename => {
> +                    description => "The name of the file to create.",
> +                    type        => 'string',
> +                },
> +                tmpfilename => {
> +                    description =>
> +"The source file name. This parameter is usually set by the REST handler. You can only overwrite it when connecting to the trustet port on localhost.",
> +                    type     => 'string',
> +                    optional => 1,
> +                },
> +            },
> +        },
> +        returns => { type => "string" },
> +        code    => sub {
> +            my ( $param ) = @_;
> +
> +            my $rpcenv = PVE::RPCEnvironment::get();
> +
> +            my $user = $rpcenv->get_user();
> +
> +            my $cfg = cfs_read_file( "storage.cfg" );
> +
> +            # XXX use PVE::Tools::extract_param ?
> +            my $node   = $param->{node};
> +            my $vmid   = $param->{vmid};
> +            my $format = $param->{format};
> +
> +            my $scfg = PVE::Storage::storage_check_enabled( $cfg, $param->{storage}, $node );
> +
> +            die "cant upload to storage type '$scfg->{type}'\n"
> +              if !( $scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs' || $scfg->{type} eq 'glusterfs' );
> +
> +            my $content = $param->{content};
> +
> +            my $tmpfilename = $param->{tmpfilename};
> +            die "missing temporary file name\n" if !$tmpfilename;
> +
> +            my $size = -s $tmpfilename;
> +            die "temporary file '$tmpfilename' does not exists\n" if !defined( $size );
> +
> +            my $filename = $param->{filename};
> +
> +            chomp $filename;
> +            $filename =~ s/^.*[\/\\]//;
> +            $filename =~ s/[;:,=\s\x80-\xff]/_/g;
> +
> +            my $path;
> +
> +            if ( $content eq 'images' ) {    # XXX see also pve-storage/PVE/Storage/DirPlugin.pm and other
> +
> +                my @allowed_extensions = qw(raw qcow qcow2 cow vdi vmdk vpc cloop);
> +                raise_param_exc(
> +                    { filename => "extension must be one of " . join ', ', map "'$_'", @allowed_extensions } )
> +                  if !grep { $filename =~ m![^/]+\.$_$! } @allowed_extensions;
> +
> +                # XXX use another location!
> +                $path = PVE::Storage::get_vztmpl_dir( $cfg, $param->{storage} );
> +            }
> +            else {
> +                raise_param_exc( { content => "upload content type '$content' not allowed" } );
> +            }
> +
> +            die "storage '$param->{storage}' does not support '$content' content\n"
> +              if !$scfg->{content}->{$content};
> +
> +            my $dest    = "$path/$filename";
> +            my $dirname = dirname( $dest );
> +
> +            # we simply overwrite when destination when file already exists
> +
> +            # -- copy --
> +
> +            my $cmd;
> +            if ( $node ne 'localhost' && $node ne PVE::INotify::nodename() ) {
> +                my $remip = PVE::Cluster::remote_node_ip( $node );
> +
> +                my @ssh_options = ( '-o', 'BatchMode=yes' );
> +
> +                my @remcmd = ( '/usr/bin/ssh', @ssh_options, $remip, '--' );
> +
> +                eval {
> +                    # activate remote storage
> +                    PVE::Tools::run_command( [ @remcmd, '/usr/sbin/pvesm', 'status', '--storage', $param->{storage} ] );
> +                };
> +                die "can't activate storage '$param->{storage}' on node '$node'\n" if $@;
> +
> +                PVE::Tools::run_command( [ @remcmd, '/bin/mkdir', '-p', '--', PVE::Tools::shell_quote( $dirname ) ],
> +                    errmsg => "mkdir failed" );
> +
> +                $cmd =
> +                  [ '/usr/bin/scp', @ssh_options, '--', $tmpfilename, "[$remip]:" . PVE::Tools::shell_quote( $dest ) ];
> +            }
> +            else {
> +                PVE::Storage::activate_storage( $cfg, $param->{storage} );
> +                File::Path::make_path( $dirname );
> +                $cmd = [ 'cp', '--', $tmpfilename, $dest ];
> +            }
> +
> +            # -- import --
> +
> +            die "vm $vmid is running\n" if PVE::QemuServer::check_running( $vmid );    # XXX
> +
> +            my $conf     = PVE::QemuServer::load_config( $vmid );
> +            my $storecfg = PVE::Storage::config();
> +            my $full     = 1;
> +
> +            my ( $drivename, $drive );
> +            for my $opt ( keys %{$conf} ) {
> +                if ( PVE::QemuServer::valid_drivename( $opt ) ) {
> +                    $drivename = $opt;
> +                    $drive = PVE::QemuServer::parse_drive( $opt, $conf->{$opt} );
> +                    die "unable to parse drive options for '$opt'\n" if !$drive;
> +                    next if PVE::QemuServer::drive_is_cdrom( $drive );
> +                    die "Full clone feature is not available"
> +                      if $full && !PVE::Storage::volume_has_feature( $storecfg, 'copy', $drive->{file}, undef, undef );
> +                    last;
> +                }
> +            }
> +
> +            my $storage = PVE::Storage::parse_volume_id( $drive->{file} );
> +
> +            my $worker = sub {
> +                my $upid = shift;
> +
> +                # copy file to storage
> +                print "starting file import from: $tmpfilename\n";
> +                print "target node: $node\n";
> +                print "target file: $dest\n";
> +                print "file size is: $size\n";
> +                print "command: " . join( ' ', @$cmd ) . "\n";
> +
> +                eval { PVE::Tools::run_command( $cmd, errmsg => 'import failed' ); };
> +                if ( my $err = $@ ) {
> +                    unlink $dest;
> +                    die $err;
> +                }
> +                print "finished file import successfully\n";
> +
> +                # convert image with qemu-img
> +                print "\n";
> +                print "starting import of image: $dest\n";
> +
> +                # my $source_file = "$param->{storage}:$dest";
> +                my $source = PVE::Storage::path_to_volume_id( $storecfg, $dest );
> +
> +                # XXX remove
> +                use Data::Dumper;
> +                warn 'PVE::API2::Storage::Status: ' . Dumper(
> +                    {
> +                        filename => $filename,
> +                        dest     => $dest,
> +                        dirname  => dirname( $dest ),
> +                        node     => $node,
> +                        vmid     => $vmid,
> +                        conf     => $conf,
> +                        storecfg => $storecfg,
> +                        drive    => $drive,
> +                        storage  => $storage,
> +                        #source_file => $source_file,
> +                        source => $source,
> +                    }
> +                );
> +
> +                my $newvollist = [];
> +                my $newdrive = PVE::QemuServer::import_disk( $source, $storecfg, $vmid, $drive, $newvollist );
> +
> +                print Dumper $newdrive;
> +
> +            };
> +
> +            my $upid = $rpcenv->fork_worker( 'imgcopy', undef, $user, $worker ); # XXX other name than imgcopy
> +
> +            # apache removes the temporary file on return, so we need
> +            # to wait here to make sure the worker process starts and
> +            # opens the file before it gets removed.
> +            sleep( 1 );
> +
> +            return $upid;
> +          }
> +    }
> +);
> +
>  1;
> diff --git a/PVE/Storage.pm b/PVE/Storage.pm
> index 140f8ae..e112d4e 100755
> --- a/PVE/Storage.pm
> +++ b/PVE/Storage.pm
> @@ -372,7 +372,7 @@ sub path_to_volume_id {
>  	} elsif ($path =~ m!^$isodir/([^/]+\.[Ii][Ss][Oo])$!) {
>  	    my $name = $1;
>  	    return ('iso', "$sid:iso/$name");
> -	} elsif ($path =~ m!^$tmpldir/([^/]+\.tar\.gz)$!) {
> +	} elsif ($path =~ m!^$tmpldir/([^/]+(:?\.tar\.gz|\.vmdk))$!) {
>  	    my $name = $1;
>  	    return ('vztmpl', "$sid:vztmpl/$name");
>  	} elsif ($path =~ m!^$privatedir/(\d+)$!) {
> diff --git a/PVE/Storage/DirPlugin.pm b/PVE/Storage/DirPlugin.pm
> index bc3c61f..87e9cd7 100644
> --- a/PVE/Storage/DirPlugin.pm
> +++ b/PVE/Storage/DirPlugin.pm
> @@ -16,7 +16,7 @@ sub type {
>  
>  sub plugindata {
>      return {
> -	content => [ { images => 1, rootdir => 1, vztmpl => 1, iso => 1, backup => 1, none => 1 },
> +	content => [ { images => 1, rootdir => 1, vztmpl => 1, iso => 1, backup => 1, none => 1 }, # XXX maybe add new content type?
>  		     { images => 1,  rootdir => 1 }],
>  	format => [ { raw => 1, qcow2 => 1, vmdk => 1, subvol => 1 } , 'raw' ],
>      };
> diff --git a/PVE/Storage/Plugin.pm b/PVE/Storage/Plugin.pm
> index 6aa71e0..a8bb385 100644
> --- a/PVE/Storage/Plugin.pm
> +++ b/PVE/Storage/Plugin.pm
> @@ -374,7 +374,7 @@ sub parse_volname {
>  	return ('images', $name, $vmid, undef, undef, $isBase, $format);
>      } elsif ($volname =~ m!^iso/([^/]+\.[Ii][Ss][Oo])$!) {
>  	return ('iso', $1);
> -    } elsif ($volname =~ m!^vztmpl/([^/]+\.tar\.[gx]z)$!) {
> +    } elsif ($volname =~ m!^vztmpl/([^/]+(:?\.tar\.[gx]z|\.vmdk|\.qcow2|\.raw))$!) {
>  	return ('vztmpl', $1);
>      } elsif ($volname =~ m!^rootdir/(\d+)$!) {
>  	return ('rootdir', $1, $1);
> -- 
> 2.1.4
> 
> _______________________________________________
> pve-devel mailing list
> pve-devel at pve.proxmox.com
> http://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
> 




More information about the pve-devel mailing list