[Libguestfs] [PATCH] Add SSH transfer method

Matthew Booth mbooth at redhat.com
Fri Apr 9 17:07:04 UTC 2010


Support obtaining guest storage over ssh. The following now works:

virt-v2v -f v2v/virt-v2v.conf -ic 'xen+ssh://xen.example.com/system' \
         -op transfer rhel54pv64
---
 MANIFEST                              |    1 +
 lib/Sys/VirtV2V/Connection.pm         |    1 +
 lib/Sys/VirtV2V/Connection/LibVirt.pm |    4 +
 lib/Sys/VirtV2V/Transfer/SSH.pm       |  208 +++++++++++++++++++++++++++++++++
 v2v/virt-v2v.pl                       |   15 ++-
 5 files changed, 222 insertions(+), 7 deletions(-)
 create mode 100644 lib/Sys/VirtV2V/Transfer/SSH.pm

diff --git a/MANIFEST b/MANIFEST
index 65b5a8e..5fd38c6 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -17,6 +17,7 @@ lib/Sys/VirtV2V/Target/LibVirt.pm
 lib/Sys/VirtV2V/Target/RHEV.pm
 lib/Sys/VirtV2V/Transfer/ESX.pm
 lib/Sys/VirtV2V/Transfer/LocalCopy.pm
+lib/Sys/VirtV2V/Transfer/SSH.pm
 MANIFEST			This list of files
 MANIFEST.SKIP
 META.yml
diff --git a/lib/Sys/VirtV2V/Connection.pm b/lib/Sys/VirtV2V/Connection.pm
index c901b90..da6a44b 100644
--- a/lib/Sys/VirtV2V/Connection.pm
+++ b/lib/Sys/VirtV2V/Connection.pm
@@ -24,6 +24,7 @@ use Sys::Virt;
 
 use Sys::VirtV2V::Transfer::ESX;
 use Sys::VirtV2V::Transfer::LocalCopy;
+use Sys::VirtV2V::Transfer::SSH;
 use Sys::VirtV2V::UserMessage qw(user_message);
 
 use Locale::TextDomain 'virt-v2v';
diff --git a/lib/Sys/VirtV2V/Connection/LibVirt.pm b/lib/Sys/VirtV2V/Connection/LibVirt.pm
index 0a0330e..5e06356 100644
--- a/lib/Sys/VirtV2V/Connection/LibVirt.pm
+++ b/lib/Sys/VirtV2V/Connection/LibVirt.pm
@@ -150,6 +150,10 @@ sub new
         $transfer = "Sys::VirtV2V::Transfer::ESX";
     }
 
+    elsif ($self->{uri}->scheme =~ /\+ssh$/) {
+        $transfer = "Sys::VirtV2V::Transfer::SSH";
+    }
+
     # Default to LocalCopy
     # XXX: Need transfer methods for remote libvirt connections, e.g. scp
     else {
diff --git a/lib/Sys/VirtV2V/Transfer/SSH.pm b/lib/Sys/VirtV2V/Transfer/SSH.pm
new file mode 100644
index 0000000..3b08716
--- /dev/null
+++ b/lib/Sys/VirtV2V/Transfer/SSH.pm
@@ -0,0 +1,208 @@
+# Sys::VirtV2V::Transfer::SSH
+# Copyright (C) 2010 Red Hat Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+package Sys::VirtV2V::Transfer::SSH;
+
+use POSIX;
+use File::Spec;
+use File::stat;
+
+use Sys::VirtV2V::UserMessage qw(user_message);
+
+use Locale::TextDomain 'virt-v2v';
+
+=pod
+
+=head1 NAME
+
+Sys::VirtV2V::Transfer::SSH - Copy a remote guest's storage via ssh
+
+=head1 SYNOPSIS
+
+ use Sys::VirtV2V::Transfer::SSH;
+
+ $vol = Sys::VirtV2V::Transfer::SSH->transfer($conn, $path, $target);
+
+=head1 DESCRIPTION
+
+Sys::VirtV2V::Transfer::SSH retrieves guest storage devices from a remote server
+via SSH.
+
+=head1 METHODS
+
+=over
+
+=item transfer(conn, path, target)
+
+Transfer <path> from the remove server. Storage will be created using <target>.
+
+=cut
+
+sub transfer
+{
+    my $class = shift;
+
+    my ($conn, $path, $target) = @_;
+
+    my (undef, undef, $name) = File::Spec->splitpath($path);
+
+    if ($target->volume_exists($name)) {
+        print STDERR user_message(__x("WARNING: storage volume {name} ".
+                                      "already exists on the target. NOT ".
+                                      "copying it again. Delete the volume ".
+                                      "and retry to copy again.",
+                                      name => $name));
+        return $target->get_volume($name);
+    }
+
+    my $uri      = $conn->{uri};
+    my $username = $conn->{username};
+    my $password = $conn->{password};
+    my $host     = $conn->{hostname};
+
+    die("URI not defined for connection") unless (defined($uri));
+
+    my ($pid, $size, $fh, $error) =
+        _connect($host, $username, $path);
+
+    my $vol = $target->create_volume($name, $size);
+    $vol->open();
+
+    for (;;) {
+        my $buffer;
+        # Transfer in 8k chunks
+        my $in = sysread($fh, $buffer, 8 * 1024);
+        die(user_message(__x("Error reading data from {path}: {error}",
+                             path => $path,
+                             error => $!))) if (!defined($in));
+
+        last if ($in == 0);
+
+        $vol->write($buffer);
+    }
+
+    $vol->close();
+
+    waitpid($pid, 0) == $pid or die("error reaping child: $!");
+    # If the child returned an error, check for anything on its stderr
+    if ($? != 0) {
+        my $msg = "";
+        while (<$error>) {
+            $msg .= $_;
+        }
+        die(user_message(__x("Unexpected error copying {path} from {host}. ".
+                             "Command output: {output}",
+                             path => $path,
+                             host => $uri->host,
+                             output => $msg)));
+    }
+
+    return $vol;
+}
+
+sub _connect
+{
+    my ($host, $username, $path) = @_;
+
+    my ($stdin_read, $stdin_write);
+    my ($stdout_read, $stdout_write);
+    my ($stderr_read, $stderr_write);
+
+    pipe($stdin_read, $stdin_write);
+    pipe($stdout_read, $stdout_write);
+    pipe($stderr_read, $stderr_write);
+
+    my $pid = fork();
+    if ($pid == 0) {
+        my @command;
+        push(@command, 'ssh');
+        push(@command, '-l', $username) if (defined($username));
+        push(@command, $host);
+        push(@command, "stat -c %s $path; cat $path");
+
+        # Close the ends of the pipes we don't need
+        close($stdin_write);
+        close($stdout_read);
+        close($stderr_read);
+
+        # dup2() stdin, stdout and stderr to pipes
+        open(STDIN, "<&".fileno($stdin_read))
+            or die("dup stdin failed: $!");
+        open(STDOUT, "<&".fileno($stdout_write))
+            or die("dup stdout failed: $!");
+        open(STDERR, "<&".fileno($stderr_write))
+            or die("dup stderr failed: $!");
+
+        # We parse stderr, so make sure it's in English
+        $ENV{LANG} = 'C';
+        exec(@command);
+    }
+
+    close($stdin_read);
+    close($stdout_write);
+    close($stderr_write);
+
+    # Check that we don't get output on stderr before we read the file size
+    for(;;) {
+        my ($rin, $rout);
+        $rin = '';
+        vec($rin, fileno($stdout_read), 1) = 1;
+        vec($rin, fileno($stderr_read), 1) = 1;
+
+        my $nfound = select($rout=$rin, undef, undef, undef);
+        die("select failed: $!") if ($nfound < 0);
+
+        if (vec($rout, fileno($stderr_read), 1) == 1) {
+            my $stderr = '';
+            while(<$stderr_read>) {
+                $stderr .= $_;
+            }
+
+            die(user_message(__x("Unexpected error getting {path}: ".
+                                 "{output}",
+                                 path => $path, output => $stderr)));
+        }
+
+        if (vec($rout, fileno($stdout_read), 1) == 1) {
+            last;
+        }
+    }
+
+    # First line returned is the output of stat
+    my $size = <$stdout_read>;
+
+    return ($pid, $size, $stdout_read, $stderr_read);
+}
+
+=back
+
+=head1 COPYRIGHT
+
+Copyright (C) 2010 Red Hat Inc.
+
+=head1 LICENSE
+
+Please see the file COPYING.LIB for the full license.
+
+=head1 SEE ALSO
+
+L<virt-v2v(1)>,
+L<http://libguestfs.org/>.
+
+=cut
+
+1;
diff --git a/v2v/virt-v2v.pl b/v2v/virt-v2v.pl
index 7d30856..3dd1b74 100755
--- a/v2v/virt-v2v.pl
+++ b/v2v/virt-v2v.pl
@@ -96,6 +96,10 @@ my $input_uri = "qemu:///system";
 Specifies the connection to use when using the libvirt input method. If omitted,
 this defaults to qemu:///system.
 
+B<N.B.> virt-v2v can currently automatically obtain guest storage from local
+libvirt connections, ESX connections, and connections over SSH. Other types of
+connection are not supported.
+
 =cut
 
 my $input_transport;
@@ -429,14 +433,11 @@ sub inspect_guest
 
 =head1 PREPARING TO CONVERT A GUEST
 
-=head2 Xen guests
-
-The following steps are required before converting a Xen guest. Note that only
-local Xen guests are currently supported. These steps are not required for
-conversions from ESX, and will not be required for remote Xen guests when we
-support that.
+=head2 Local Xen guests
 
-=head3 Obtain domain XML for the guest domain
+B<N.B.> The following is required when converting guests on a host which used to
+run Xen, but has been updated to run KVM. It is not required when converting a
+Xen guest imported directly from a running libvirt/Xen instance.
 
 virt-v2v uses a libvirt domain description to determine the current
 configuration of the guest, including the location of its storage. This should
-- 
1.6.6.1




More information about the Libguestfs mailing list