[gnome-boxes] Allow user to import system libvirt VMs



commit 680408327854fa9169f6d95dc989840b507d0194
Author: Zeeshan Ali (Khattak) <zeeshanak gnome org>
Date:   Fri Nov 15 17:07:31 2013 +0000

    Allow user to import system libvirt VMs
    
    If there is no boxes defined and there is VMs available on system
    libvirt, allow user to import those VMs to session libvirt (and therefore
    Boxes).
    
    Issues/TODO:
    
    * We gotta elevate privileges to change permissions on disks/images as by
      default they are not even readable to anyone but root/qemu. This
      unfortunately means launching of undesirable polkit dialog.
    
    * The wizard summary in this case could use improvement/more info.
    
    * We don't give any option to user for deleting the source VMs from
      system libvirt after import. If we do that, we'll have to trigger yet
      more polkit dialogs. :( Had a bit of discussion with jimmac about this.
      He suggested that perhaps we should track the imported VMs so that we
      only present option to import if there is something new to import.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=666185

 src/Makefile.am                  |    1 +
 src/libvirt-system-importer.vala |  149 ++++++++++++++++++++++++++++++++++++++
 src/wizard-source.vala           |   24 ++++++
 src/wizard.vala                  |   23 +++++--
 4 files changed, 191 insertions(+), 6 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 78d712a..cbfec97 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -114,6 +114,7 @@ gnome_boxes_SOURCES =                               \
        vm-configurator.vala                    \
        vm-creator.vala                         \
        vm-importer.vala                        \
+       libvirt-system-importer.vala            \
        libvirt-system-vm-importer.vala         \
        vnc-display.vala                        \
        wizard-source.vala                      \
diff --git a/src/libvirt-system-importer.vala b/src/libvirt-system-importer.vala
new file mode 100644
index 0000000..e325ad2
--- /dev/null
+++ b/src/libvirt-system-importer.vala
@@ -0,0 +1,149 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+using GVir;
+
+errordomain Boxes.LibvirtSystemImporterError {
+    NO_IMPORTS,
+    NO_SUITABLE_DISK
+}
+
+private class Boxes.LibvirtSystemImporter: GLib.Object {
+    private GVir.Connection connection;
+    private GLib.List<GVir.Domain> domains;
+
+    public string wizard_menu_label {
+        owned get {
+            var num_domains = domains.length ();
+
+            if (num_domains == 1)
+                return _("Import '%s' from system broker").printf (domains.data.get_name ());
+            else
+                // Translators: %u here is number of boxes availble for import
+                return ngettext ("Import %u box from system broker",
+                                 "Import %u boxes from system broker",
+                                 num_domains).printf (num_domains);
+        }
+    }
+
+    public string wizard_review_label {
+        owned get {
+            var num_domains = domains.length ();
+
+            if (num_domains == 1)
+                return _("Will import '%s' from system broker").printf (domains.data.get_name ());
+            else
+                // Translators: %u here is number of boxes availble for import
+                return ngettext ("Will import %u box from system broker",
+                                 "Will import %u boxes from system broker",
+                                 num_domains).printf (num_domains);
+        }
+    }
+
+    public async LibvirtSystemImporter () throws LibvirtSystemImporterError.NO_IMPORTS {
+        connection = new GVir.Connection ("qemu+unix:///system");
+
+        try {
+            yield connection.open_read_only_async (null);
+            debug ("Connected to system libvirt, now fetching domains..");
+            yield connection.fetch_domains_async (null);
+        } catch (GLib.Error error) {
+            warning ("Failed to connect to system libvirt: %s", error.message);
+
+            return;
+        }
+
+        domains = connection.get_domains ();
+        debug ("Fetched %u domains from system libvirt.", domains.length ());
+        if (domains.length () == 0)
+            throw new LibvirtSystemImporterError.NO_IMPORTS (_("No boxes to import"));
+    }
+
+    public async void import () {
+        GVirConfig.Domain[] configs = {};
+        string[] disk_paths = {};
+
+        foreach (var domain in domains) {
+            GVirConfig.Domain config;
+            string disk_path;
+
+            try {
+                get_domain_info (domain, out config, out disk_path);
+                configs += config;
+                disk_paths += disk_path;
+            } catch (GLib.Error error) {
+                warning ("%s", error.message);
+            }
+        }
+
+        try {
+            yield ensure_disks_readable (disk_paths);
+        } catch (GLib.Error error) {
+            warning ("Failed to make all libvirt system disks readable: %s", error.message);
+
+            return;
+        }
+
+        for (var i = 0; i < configs.length; i++)
+            import_domain.begin (configs[i], disk_paths[i], null, (obj, result) => {
+                try {
+                    import_domain.end (result);
+                } catch (GLib.Error error) {
+                    warning ("Failed to import '%s': %s", configs[i].name, error.message);
+                }
+            });
+    }
+
+    private void get_domain_info (Domain domain, out GVirConfig.Domain config, out string disk_path) throws 
GLib.Error {
+        debug ("Fetching config for '%s' from system libvirt.", domain.get_name ());
+        config = domain.get_config (DomainXMLFlags.INACTIVE);
+        debug ("Finding a suitable disk to import for '%s' from system libvirt.", domain.get_name ());
+        disk_path = get_disk_path (config);
+    }
+
+    private async void import_domain (GVirConfig.Domain config,
+                                      string            disk_path,
+                                      Cancellable?      cancellable = null) throws GLib.Error {
+        debug ("Importing '%s' from system libvirt..", config.name);
+
+        var media = new LibvirtSystemMedia (disk_path, config);
+        var vm_importer = media.get_vm_creator ();
+        var machine = yield vm_importer.create_vm (cancellable);
+        vm_importer.launch_vm (machine);
+    }
+
+    private string get_disk_path (GVirConfig.Domain config) throws 
LibvirtSystemImporterError.NO_SUITABLE_DISK {
+        string disk_path = null;
+
+        var devices = config.get_devices ();
+        foreach (var device in devices) {
+            if (!(device is GVirConfig.DomainDisk))
+                continue;
+
+            var disk = device as GVirConfig.DomainDisk;
+            if (disk.get_guest_device_type () == GVirConfig.DomainDiskGuestDeviceType.DISK) {
+                disk_path = disk.get_source ();
+
+                break;
+            }
+        }
+
+        if (disk_path == null)
+            throw new LibvirtSystemImporterError.NO_SUITABLE_DISK
+                                    (_("Failed to find suitable disk to import for box '%s'"), config.name);
+
+        return disk_path;
+    }
+
+    private async void ensure_disks_readable (string[] disk_paths) throws GLib.Error {
+        string[] argv = {};
+
+        argv += "pkexec";
+        argv += "chmod";
+        argv += "a+r";
+        foreach (var disk_path in disk_paths)
+            argv += disk_path;
+
+        debug ("Making all libvirt system disks readable..");
+        yield exec (argv, null);
+        debug ("Made all libvirt system disks readable.");
+    }
+}
diff --git a/src/wizard-source.vala b/src/wizard-source.vala
index ba89820..62237ee 100644
--- a/src/wizard-source.vala
+++ b/src/wizard-source.vala
@@ -78,6 +78,7 @@ private class Boxes.WizardSource: GLib.Object {
         set { url_entry.set_text (value); }
     }
     public InstallerMedia? install_media { get; private set; }
+    public LibvirtSystemImporter libvirt_sys_importer { get; private set; }
 
     private Gtk.Box main_vbox;
     private Gtk.Box media_vbox;
@@ -173,6 +174,7 @@ private class Boxes.WizardSource: GLib.Object {
 
         notebook.show_all ();
 
+        add_libvirt_sytem_entry.begin ();
         add_media_entries.begin ();
     }
 
@@ -269,6 +271,28 @@ private class Boxes.WizardSource: GLib.Object {
         media_scrolled.show ();
     }
 
+    private async void add_libvirt_sytem_entry () {
+        try {
+            libvirt_sys_importer = yield new LibvirtSystemImporter ();
+        } catch (LibvirtSystemImporterError.NO_IMPORTS error) {
+            debug ("%s", error.message);
+
+            return;
+        }
+
+        var hbox = add_entry (main_vbox, () => {
+            activate ();
+
+            return true;
+        });
+        var label = new Gtk.Label (libvirt_sys_importer.wizard_menu_label);
+        label.xalign = 0.0f;
+        hbox.pack_start (label, true, true);
+        var next = new Gtk.Label ("▶");
+        hbox.pack_start (next, false, false);
+        main_vbox.show_all ();
+    }
+
     private Gtk.Box add_entry (Gtk.Box            box,
                                owned ClickedFunc? clicked = null,
                                int                h_margin = 20,
diff --git a/src/wizard.vala b/src/wizard.vala
index e2a4046..211d97c 100644
--- a/src/wizard.vala
+++ b/src/wizard.vala
@@ -187,9 +187,7 @@ private class Boxes.Wizard: Boxes.UI {
     }
 
     private async bool create () {
-        if (source == null) {
-            return_val_if_fail (vm_creator != null, false); // Shouldn't arrive here with source & 
vm_creator being null
-
+        if (vm_creator != null) {
             if (libvirt_machine == null) {
                 return_val_if_fail (review_cancellable != null, false);
                 // wait until the machine is ready or not
@@ -213,9 +211,13 @@ private class Boxes.Wizard: Boxes.UI {
             vm_creator.install_media.clean_up_preparation_cache ();
             vm_creator = null;
             wizard_source.uri = "";
-        } else {
+        } else if (source != null) {
             source.save ();
             App.app.add_collection_source.begin (source);
+        } else if (wizard_source.libvirt_sys_importer != null) {
+            wizard_source.libvirt_sys_importer.import.begin ();
+        } else {
+            return_val_if_reached (false); // Shouldn't arrive here with no source
         }
 
         machine = null;
@@ -320,6 +322,8 @@ private class Boxes.Wizard: Boxes.UI {
             prep_status_label.label = _("Analyzing...");
             prepare_media.begin (wizard_source.install_media);
             return true;
+        } else if (this.wizard_source.libvirt_sys_importer != null) {
+            return true;
         } else {
             try {
                 prepare_for_location (this.wizard_source.uri);
@@ -334,8 +338,8 @@ private class Boxes.Wizard: Boxes.UI {
     }
 
     private bool setup () {
-        // there is no setup yet for direct source
-        if (source != null)
+        // there is no setup yet for direct source nor libvirt system imports
+        if (source != null || this.wizard_source.libvirt_sys_importer != null)
             return true;
 
         return_if_fail (vm_creator != null);
@@ -461,6 +465,8 @@ private class Boxes.Wizard: Boxes.UI {
             }
 
             nokvm_box.visible = (libvirt_machine.domain_config.get_virt_type () != 
GVirConfig.DomainVirtType.KVM);
+        } else if (this.wizard_source.libvirt_sys_importer != null) {
+            review_label.set_text (this.wizard_source.libvirt_sys_importer.wizard_review_label);
         }
 
         if (machine != null)
@@ -515,6 +521,11 @@ private class Boxes.Wizard: Boxes.UI {
                 && vm_creator.install_media.live
                 && skip_review_for_live)
                     skip_to += 1;
+        } else if (wizard_source.libvirt_sys_importer != null) {
+            if (page == Boxes.WizardPage.PREPARATION)
+                skip_to = forwards ? page + 2 : page - 1;
+            else if (page == Boxes.WizardPage.SETUP)
+                skip_to = forwards ? page + 1 : page - 2;
         }
 
         if (skip_to != page) {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]