---
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
@(a)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((a)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