[Libguestfs] [PATCH] support remotely mounting disk images in p2v

Mo Morsi mmorsi at redhat.com
Thu May 16 20:27:59 UTC 2013


---
 p2v/client/ext/rblibssh2/rblibssh2_channel.c | 131 +++++++++++++++++++++++++++
 p2v/client/lib/virt-p2v/blockdevice.rb       |  13 ++-
 p2v/client/lib/virt-p2v/connection.rb        |  16 ++++
 p2v/client/lib/virt-p2v/converter.rb         |  32 ++++++-
 p2v/client/lib/virt-p2v/ui/convert.rb        |  44 ++++++---
 p2v/client/lib/virt-p2v/ui/p2v.ui            |  22 ++++-
 p2v/server/virt-p2v-server.pl                |  31 +++++++
 7 files changed, 268 insertions(+), 21 deletions(-)

diff --git a/p2v/client/ext/rblibssh2/rblibssh2_channel.c b/p2v/client/ext/rblibssh2/rblibssh2_channel.c
index 285da17..f648d20 100644
--- a/p2v/client/ext/rblibssh2/rblibssh2_channel.c
+++ b/p2v/client/ext/rblibssh2/rblibssh2_channel.c
@@ -39,20 +39,24 @@ static VALUE eApplicationError;
 static VALUE eShortReadError;
 
 static VALUE session_exec(VALUE self_rv, VALUE cmd_rv);
+static VALUE session_forward(VALUE self_rv, VALUE port_rv);
 
 static VALUE channel_read(VALUE self_rv, VALUE bytes_rv);
 static VALUE channel_write(VALUE self_rv, VALUE data_rv);
 static VALUE channel_send_data(VALUE self_rv, VALUE io_rv);
+static VALUE channel_forward(VALUE self_rv, VALUE io_rv);
 static VALUE channel_close(VALUE self_rv);
 
 void rblibssh2_channel_init()
 {
     rb_define_method(cSession, "exec", session_exec, 1);
+    rb_define_method(cSession, "forward_port", session_forward, 1);
 
     cChannel = rb_define_class_under(cLibssh2, "Channel", rb_cObject);
     rb_define_method(cChannel, "read", channel_read, 1);
     rb_define_method(cChannel, "write", channel_write, 1);
     rb_define_method(cChannel, "send_data", channel_send_data, 1);
+    rb_define_method(cChannel, "forward", channel_forward, 1);
     rb_define_method(cChannel, "close", channel_close, 0);
 
     eApplicationError = rb_define_class_under(cChannel, "ApplicationError",
@@ -138,6 +142,60 @@ static VALUE session_exec(VALUE self_rv, VALUE cmd_rv)
     return channel_rv;
 }
 
+struct session_forward {
+    struct channel *c;
+    int port;
+};
+
+static void *session_forward_w(void *params)
+{
+    struct session_forward *sf = (struct session_forward *)params;
+    struct channel *c = sf->c;
+
+    LIBSSH2_SESSION *session    = rblibssh2_session_get(c->s);
+    LIBSSH2_LISTENER *listener =
+       libssh2_channel_forward_listen_ex(session,
+                                         "127.0.0.1",
+                                         sf->port, &sf->port, 1);
+    c->channel = libssh2_channel_forward_accept(listener);
+    if (c->channel == NULL) {
+        char *err;
+        int ssh_errno = libssh2_session_last_error(session, &err, NULL, 0);
+        rblibssh2_session_set_error(rb_eIOError,
+                "Failed to open channel: %s(%i)", err, ssh_errno);
+        return NULL;
+    }
+
+    return c;
+}
+
+static VALUE session_forward(VALUE self_rv, VALUE port_rv)
+{
+    struct session *s;
+    Data_Get_Struct(self_rv, struct session, s);
+
+    if (rblibssh2_session_get(s) == NULL)
+        rb_raise(eInternalError, "Session is closed");
+
+    struct channel *c;
+    VALUE channel_rv = Data_Make_Struct(cChannel, struct channel,
+                                        NULL, channel_free, c);
+    memset(c, 0, sizeof(*c));
+    c->s = s;
+    c->rv = channel_rv;
+
+    rblibssh2_session_channel_add(s, channel_rv);
+
+    struct session_forward p = {
+        .c = c,
+        .port = NUM2INT(port_rv) // TODO allow port to be nil for autoassignment
+    };
+
+    rblibssh2_session_runthread(s, session_forward_w, &p, NULL, NULL, NULL, NULL);
+
+    return channel_rv;
+}
+
 struct channel_data {
     struct channel *c;
     char *data;
@@ -427,6 +485,79 @@ static VALUE channel_send_data(VALUE self_rv, VALUE io_rv)
     return Qnil;
 }
 
+struct channel_forward_data {
+    struct channel *c;
+    int fd;
+};
+
+static void *channel_forward_w(void *params)
+{
+    ssize_t l;
+    struct channel_forward_data *cfd = (struct channel_forward_data *) params;
+    struct channel *c = cfd->c;
+
+    LIBSSH2_SESSION *session = rblibssh2_session_get(c->s);
+
+    char *data = xmalloc(1024);
+    struct channel_data *cd = xmalloc(sizeof *cd);
+    cd->c = c;
+    cd->len  = 1024;
+    cd->data = data;
+
+
+    while((l = libssh2_channel_read(c->channel, cd->data, cd->len)) > 0){
+        if (l < 0) {
+            char *err;
+            libssh2_session_last_error(session, &err, NULL, 0);
+            rblibssh2_session_set_error(rb_eIOError,
+                "Error reading from channel in channel_read: %s(%i)", err, l);
+            break;
+        } else if (l == 0) {
+            if (libssh2_channel_eof(c->channel)) {
+                rblibssh2_session_set_error(rb_eIOError,
+                        "Unexpected EOF on channel in channel_read");
+                break;
+            }
+            /* Can't think of any other reason we'd get a zero-length return,
+             * but go round again anyway. */
+        } else {
+            // send to remote socket
+            // TODO analyze return value
+            write(cfd->fd, cd->data, l);
+            cd->len = l;
+        }
+    }
+
+    channel_data_free(cd);
+    return cfd;
+}
+
+static VALUE channel_forward(VALUE self_rv, VALUE io_rv)
+{
+    struct channel *c;
+    Data_Get_Struct(self_rv, typeof(*c), c);
+
+    if (c->channel == NULL)
+        rb_raise(eInternalError, "Channel is closed");
+
+    rb_io_t *io;
+    GetOpenFile(io_rv, io);
+    rb_io_check_writable(io);
+
+    struct channel_forward_data *cfd = xmalloc(sizeof(*cfd));
+    cfd->c = c;
+#ifdef HAVE_STRUCT_RB_IO_T_F
+    cfd->fd = fileno(io->f);
+#elif HAVE_STRUCT_RB_IO_T_FD
+    cfd->fd = io->fd;
+#endif
+
+    rblibssh2_session_runthread(c->s, channel_forward_w,
+                                cfd, xfree,
+                                NULL, NULL, NULL);
+    return Qnil;
+}
+
 static void *channel_close_w(void *arg)
 {
     struct channel *c = (struct channel *) arg;
diff --git a/p2v/client/lib/virt-p2v/blockdevice.rb b/p2v/client/lib/virt-p2v/blockdevice.rb
index 644d390..d0b56db 100644
--- a/p2v/client/lib/virt-p2v/blockdevice.rb
+++ b/p2v/client/lib/virt-p2v/blockdevice.rb
@@ -20,7 +20,8 @@ class NoSuchDeviceError < StandardError; end
 class InvalidDevice < StandardError; end
 
 class FixedBlockDevice
-    @@devices = {}
+    @@current_port = 1024
+    @@devices      = {}
 
     def self.all_devices
         @@devices.values
@@ -32,7 +33,7 @@ class FixedBlockDevice
         @@devices[device]
     end
 
-    attr_reader :device, :size
+    attr_reader :device, :size, :handle
 
     def initialize(device)
         size = 0
@@ -55,6 +56,14 @@ class FixedBlockDevice
         @size = size
         @@devices[@device] = self
     end
+
+    # export the disk using qemu-nbd
+    def export
+      @@current_port += 1
+      @port = @@current_port
+      `/usr/bin/qemu-nbd --read-only --partition=1  --snapshot  #{@device} --port #{@port}`
+      @handle = TCPSocket.open('localhost', @port)
+    end
 end
 
 class RemovableBlockDevice
diff --git a/p2v/client/lib/virt-p2v/connection.rb b/p2v/client/lib/virt-p2v/connection.rb
index c3e5537..6f7dcb6 100644
--- a/p2v/client/lib/virt-p2v/connection.rb
+++ b/p2v/client/lib/virt-p2v/connection.rb
@@ -243,6 +243,22 @@ class Connection
         }
     end
 
+    def import(dev, &cb)
+        raise NotConnectedError if @channel.nil?
+
+        # instruct ssh to setup forwarding
+        channel = @session.forward_port dev.port
+        channel.forward dev.handle
+
+        run(completion) {
+            channel.write("IMPORT #{dev.path} #{dev.port}\n")
+            result = parse_return
+
+            Gtk.queue { cb.call(result) }
+        }
+    end
+
+
     private
 
     def run(cb)
diff --git a/p2v/client/lib/virt-p2v/converter.rb b/p2v/client/lib/virt-p2v/converter.rb
index 7cb4e33..4748d37 100644
--- a/p2v/client/lib/virt-p2v/converter.rb
+++ b/p2v/client/lib/virt-p2v/converter.rb
@@ -37,6 +37,10 @@ module VirtP2V
 #   path          Detected
 #   is_block      1
 #   format        raw
+# exports        Editable, default to none
+#   device        Detected
+#   uri           Detected
+#   format        raw
 # removables    Editable, default to all
 #   device        Detected
 #   type          Detected
@@ -47,7 +51,7 @@ module VirtP2V
 
 class Converter
     attr_accessor :profile, :name, :cpus, :memory, :arch, :debug
-    attr_reader :features, :disks, :removables, :nics
+    attr_reader :features, :disks, :exports, :removables, :nics
 
     attr_reader :connection
 
@@ -71,7 +75,14 @@ class Converter
                     lambda { |cb2|
                         disk(dev, status, progress, cb2)
                     }
-                }, cb)
+                }, cb) unless @disks.empty?
+            },
+            lambda { |cb|
+                iterate(@exports.map { |dev|
+                    lambda { |cb2|
+                      export(dev, status, cb2)
+                    }
+                }, cb) unless @exports.empty?
             },
             lambda { |cb|
                 if @debug
@@ -140,6 +151,7 @@ class Converter
         # Initialise empty lists for optional devices. These will be added
         # according to the user's selection
         @disks = []
+        @exports = []
         @removables = []
         @nics = []
 
@@ -174,6 +186,16 @@ class Converter
         ], completion)
     end
 
+    def export(dev, status, completion)
+        path  = "/dev/#{dev}"
+        status.call("Exporting #{dev}")
+        dev.export
+        iterate([
+            # instruct server to import disk
+            lambda { |cb| @connection.import(dev, &cb) }
+          ], completion)
+    end
+
     def iterate(stages, completion)
         i = 0
         cb = lambda { |result|
@@ -204,6 +226,12 @@ class Converter
                     'path'      => "/dev/#{device}"
                 }
             },
+            'exports'    => @exports.map { |device|
+                {
+                    'device'     => device,
+                    'path'       => "/dev/#{device}",
+                }
+            },
             'removables' => @removables.map { |device|
                 removable = RemovableBlockDevice[device]
                 {
diff --git a/p2v/client/lib/virt-p2v/ui/convert.rb b/p2v/client/lib/virt-p2v/ui/convert.rb
index 244125e..acfbca2 100644
--- a/p2v/client/lib/virt-p2v/ui/convert.rb
+++ b/p2v/client/lib/virt-p2v/ui/convert.rb
@@ -26,9 +26,10 @@ module VirtP2V::UI::Convert
     CONVERT_NETWORK_DEVICE  = 1
 
     CONVERT_FIXED_CONVERT   = 0
-    CONVERT_FIXED_DEVICE    = 1
-    CONVERT_FIXED_PROGRESS  = 2
-    CONVERT_FIXED_SIZE_GB   = 3
+    CONVERT_FIXED_EXPORT    = 1
+    CONVERT_FIXED_DEVICE    = 2
+    CONVERT_FIXED_PROGRESS  = 3
+    CONVERT_FIXED_SIZE_GB   = 4
 
     CONVERT_REMOVABLE_CONVERT   = 0
     CONVERT_REMOVABLE_DEVICE    = 1
@@ -99,6 +100,7 @@ module VirtP2V::UI::Convert
         VirtP2V::FixedBlockDevice.all_devices.each { |dev|
             fixed = @fixeds.append
             fixed[CONVERT_FIXED_CONVERT]    = true
+            fixed[CONVERT_FIXED_EXPORT]     = false
             fixed[CONVERT_FIXED_DEVICE]     = dev.device
             fixed[CONVERT_FIXED_PROGRESS]   = 0
             fixed[CONVERT_FIXED_SIZE_GB]    = dev.size / 1024 / 1024 / 1024
@@ -134,6 +136,8 @@ module VirtP2V::UI::Convert
                             method(:update_values))
         ui.register_handler('convert_fixed_select_toggled',
                             method(:convert_fixed_select_toggled))
+        ui.register_handler('export_fixed_select_toggled',
+                            method(:export_fixed_select_toggled))
         ui.register_handler('convert_removable_select_toggled',
                             method(:convert_removable_select_toggled))
         ui.register_handler('convert_network_select_toggled',
@@ -319,16 +323,22 @@ module VirtP2V::UI::Convert
             raise InvalidUIState if memory.nil?
             @converter.memory = memory * 1024 * 1024
 
-            # Check that at least 1 fixed storage device is selected
-            fixed = false
+            # TODO add option to toggle exporting disks using nbd or iscsi
+
+            # Check that at least 1 fixed or exported storage device is selected
+            has_disk = false
+            @converter.exports.clear
             @converter.disks.clear
             @fixeds.each { |model, path, iter|
                 if iter[CONVERT_FIXED_CONVERT] then
-                    fixed = true
+                    has_disk = true
                     @converter.disks << iter[CONVERT_FIXED_DEVICE]
+                elsif iter[CONVERT_FIXED_EXPORT] then
+                    has_disk = true
+                    @converter.exports << iter[CONVERT_FIXED_EXPORT]
                 end
             }
-            raise InvalidUIState unless fixed
+            raise InvalidUIState unless has_disk
 
             # Populate removables and nics, although these aren't required to be
             # selected for the ui state to be valid
@@ -373,17 +383,16 @@ module VirtP2V::UI::Convert
         memory = Integer(memory) rescue nil
         return false if memory.nil?
 
-        # Check that at least 1 fixed storage device is selected
-        fixed = false
+        # Check that at least 1 fixed or exported storage device is selected
+        has_disk = false
         @fixeds.each { |model, path, iter|
-            if iter[CONVERT_FIXED_CONVERT] then
-                fixed = true
+            if iter[CONVERT_FIXED_CONVERT] ||
+               iter[CONVERT_FIXED_EXPORT]   then
+                has_disk = true
                 break
             end
         }
-        return false unless fixed
-
-        return true
+        return has_disk
     end
 
     def self.convert_cpus_changed
@@ -416,6 +425,13 @@ module VirtP2V::UI::Convert
     def self.convert_fixed_select_toggled(widget, path)
         iter = @fixeds.get_iter(path)
         iter[CONVERT_FIXED_CONVERT] = !iter[CONVERT_FIXED_CONVERT]
+        iter[CONVERT_FIXED_EXPORT]   = false if iter[CONVERT_FIXED_CONVERT]
+    end
+
+    def self.export_fixed_select_toggled(widget, path)
+        iter = @fixeds.get_iter(path)
+        iter[CONVERT_FIXED_EXPORT]   = !iter[CONVERT_FIXED_EXPORT]
+        iter[CONVERT_FIXED_CONVERT] = false if iter[CONVERT_FIXED_EXPORT]
     end
 
     def self.convert_removable_select_toggled(widget, path)
diff --git a/p2v/client/lib/virt-p2v/ui/p2v.ui b/p2v/client/lib/virt-p2v/ui/p2v.ui
index a27bd20..b5d74a2 100644
--- a/p2v/client/lib/virt-p2v/ui/p2v.ui
+++ b/p2v/client/lib/virt-p2v/ui/p2v.ui
@@ -6,6 +6,8 @@
     <columns>
       <!-- column-name Convert -->
       <column type="gboolean"/>
+      <!-- column-name Export -->
+      <column type="gboolean"/>
       <!-- column-name Device -->
       <column type="gchararray"/>
       <!-- column-name Progress -->
@@ -276,12 +278,26 @@
                                   </object>
                                 </child>
                                 <child>
+                                  <object class="GtkTreeViewColumn" id="treeviewcolumn13">
+                                    <property name="title">Export</property>
+                                    <property name="clickable">True</property>
+                                    <child>
+                                      <object class="GtkCellRendererToggle" id="export_fixed_select">
+                                        <signal name="toggled" handler="export_fixed_select_toggled" swapped="no"/>
+                                      </object>
+                                      <attributes>
+                                        <attribute name="active">1</attribute>
+                                      </attributes>
+                                    </child>
+                                  </object>
+                                </child>
+                                <child>
                                   <object class="GtkTreeViewColumn" id="treeviewcolumn5">
                                     <property name="title">Device</property>
                                     <child>
                                       <object class="GtkCellRendererText" id="cellrenderertext4"/>
                                       <attributes>
-                                        <attribute name="text">1</attribute>
+                                        <attribute name="text">2</attribute>
                                       </attributes>
                                     </child>
                                   </object>
@@ -292,7 +308,7 @@
                                     <child>
                                       <object class="GtkCellRendererText" id="cellrenderertext9"/>
                                       <attributes>
-                                        <attribute name="text">3</attribute>
+                                        <attribute name="text">4</attribute>
                                       </attributes>
                                     </child>
                                   </object>
@@ -304,7 +320,7 @@
                                     <child>
                                       <object class="GtkCellRendererProgress" id="cellrendererprogress1"/>
                                       <attributes>
-                                        <attribute name="value">2</attribute>
+                                        <attribute name="value">3</attribute>
                                       </attributes>
                                     </child>
                                   </object>
diff --git a/p2v/server/virt-p2v-server.pl b/p2v/server/virt-p2v-server.pl
index c15efb2..5c5b415 100755
--- a/p2v/server/virt-p2v-server.pl
+++ b/p2v/server/virt-p2v-server.pl
@@ -58,6 +58,7 @@ $SIG{'PIPE'} = 'IGNORE';
 use constant MSG_OPTIONS        => 'OPTIONS';
 use constant MSG_METADATA       => 'METADATA';
 use constant MSG_PATH           => 'PATH';
+use constant MSG_IMPORT         => 'IMPORT';
 use constant MSG_CONVERT        => 'CONVERT';
 use constant MSG_LIST_PROFILES  => 'LIST_PROFILES';
 use constant MSG_SET_PROFILE    => 'SET_PROFILE';
@@ -68,6 +69,7 @@ my @MSGS = (
     MSG_METADATA,
     MSG_OPTIONS,
     MSG_PATH,
+    MSG_IMPORT,
     MSG_CONVERT,
     MSG_LIST_PROFILES,
     MSG_SET_PROFILE,
@@ -181,6 +183,13 @@ eval {
             receive_path($path, $length);
         }
 
+        # IMPORT path port
+        elsif ($type eq MSG_IMPORT) {
+            my $path = $msg->{args}[1];
+            my $port = $msg->{args}[1];
+            import_disk($path, $port);
+        }
+
         # CONVERT
         elsif ($type eq MSG_CONVERT) {
             convert();
@@ -358,6 +367,12 @@ sub convert
                             $_->{dst}->get_path(),
                             $_->{dst}->get_format() ] } @{$meta->{disks}};
 
+        foreach my $export ($meta->{exports}){
+          push(@disks, map { [ $_->{device},
+                               "nbd://localhost:$_->{port}",
+                               $_->{format} ] } @{$export})
+        }
+
         $g = new Sys::VirtConvert::GuestfsHandle(
             \@disks,
             $transferiso,
@@ -411,6 +426,22 @@ sub convert
     p2v_return_ok();
 }
 
+sub import_disk
+{
+    my ($path,$port) = @_;
+
+    die("PATH without prior SET_PROFILE command\n")
+        unless defined($target);
+    die("IMPORT without prior METADATA command\n")
+        unless defined($meta);
+
+    my ($disk) = grep { $_->{path} eq $path } @{$meta->{exports}};
+    $disk->{port}   =  $port;
+    $disk->{format} = 'raw';
+
+    p2v_return_ok();
+}
+
 sub unexpected_msg
 {
     my $msg = shift;
-- 
1.7.11.7




More information about the Libguestfs mailing list