[pve-devel] [PATCH storage] WIP: upload disk image
Timo Grodzinski
t.grodzinski at profihost.ag
Fri Jan 29 18:42:18 CET 2016
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
More information about the pve-devel
mailing list