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

[et-mgmt-tools] [PATCH] virt-manager: show serial consoles as tabs in 'details' window



The attached patch combines the serial console window
with the VM details window. Opening the serial console
now appends a tab to the details view. In addition,
multiple serial consoles are now supported, not just
the primary/first defined console, though this still
only works for 'pty' devices.

The patch does three things: 

1) Remove all the previous plumbing needed to keep track
   of the separate console window
2) Fix the vmmSerialConsole class to extend gtk.HBox, so
   we can reuse most of the code in a notebook tab.
3) Add all the plumbing in details.py to deal with adding
   and removing tabs on the fly.

Screenshot of a couple tab examples:

http://fedorapeople.org/~crobinso/virt-manager/vmm-serial-tab1.png

Screenshot of selecting which serial console to open
(unsupported console types will have entries, they will
 just be disabled as shown):

http://fedorapeople.org/~crobinso/virt-manager/vmm-serial-tab2.png

Screenshot of terminal right click menu. Tabs can be
closed via this menu, or the 'View' menu shown in the
second screenshot.

http://fedorapeople.org/~crobinso/virt-manager/vmm-serial-tab3.png

Any comments appreciated.

Thanks,
Cole
# HG changeset patch
# User "Cole Robinson <crobinso redhat com>"
# Date 1222789526 14400
# Node ID 597b7d1430152e10b465775c46e86116bf1ef0ab
# Parent  c94fe88e5f64d9273a789b0fa4a4c278ff289777
View serial consoles as tabs in the details window. Allow viewing not just the primary console.

diff -r c94fe88e5f64 -r 597b7d143015 src/virtManager/details.py
--- a/src/virtManager/details.py	Tue Sep 30 11:37:52 2008 -0400
+++ b/src/virtManager/details.py	Tue Sep 30 11:45:26 2008 -0400
@@ -36,6 +36,7 @@
 from virtManager.error import vmmErrorDialog
 from virtManager.addhardware import vmmAddHardware
 from virtManager.choosecd import vmmChooseCD
+from virtManager.serialcon import vmmSerialConsole
 
 import virtinst
 import urlgrabber.progress as progress
@@ -68,14 +69,12 @@
 PAGE_CONSOLE = 0
 PAGE_OVERVIEW = 1
 PAGE_DETAILS = 2
-PAGE_FIRST_CHAR = 3
+PAGE_DYNAMIC_OFFSET = 3
 
 class vmmDetails(gobject.GObject):
     __gsignals__ = {
         "action-show-console": (gobject.SIGNAL_RUN_FIRST,
                                 gobject.TYPE_NONE, (str,str)),
-        "action-show-terminal": (gobject.SIGNAL_RUN_FIRST,
-                                   gobject.TYPE_NONE, (str,str)),
         "action-save-domain": (gobject.SIGNAL_RUN_FIRST,
                                  gobject.TYPE_NONE, (str,str)),
         "action-destroy-domain": (gobject.SIGNAL_RUN_FIRST,
@@ -115,6 +114,7 @@
         topwin.set_title(self.title)
 
         self.engine = engine
+        self.dynamic_tabs = []
 
         # Don't allowing changing network/disks for Dom0
         if self.vm.is_management_domain():
@@ -156,6 +156,25 @@
         destroy.connect("activate", self.control_vm_destroy)
         menu.add(destroy)
 
+        smenu = gtk.Menu()
+        smenu.connect("show", self.populate_serial_menu)
+        self.window.get_widget("details-menu-view-serial-list").set_submenu(smenu)
+
+        self.serial_popup = gtk.Menu()
+
+        self.serial_copy = gtk.ImageMenuItem(gtk.STOCK_COPY)
+        self.serial_popup.add(self.serial_copy)
+
+        self.serial_paste = gtk.ImageMenuItem(gtk.STOCK_PASTE)
+        self.serial_popup.add(self.serial_paste)
+
+        self.serial_popup.add(gtk.SeparatorMenuItem())
+
+        self.serial_close = gtk.ImageMenuItem(_("Close tab"))
+        close_image = gtk.Image()
+        close_image.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
+        self.serial_close.set_image(close_image)
+        self.serial_popup.add(self.serial_close)
 
         self.window.get_widget("hw-panel").set_show_tabs(False)
 
@@ -237,7 +256,6 @@
             "on_details_menu_graphics_activate": self.control_vm_console,
             "on_details_menu_view_toolbar_activate": self.toggle_toolbar,
             "on_details_menu_view_manager_activate": self.view_manager,
-            "on_details_menu_view_serial_activate": self.control_vm_terminal,
 
             "on_details_pages_switch_page": self.switch_page,
 
@@ -431,6 +449,27 @@
         else:
             self.window.get_widget("details-toolbar").hide()
 
+    def populate_serial_menu(self, src):
+        for ent in src:
+            src.remove(ent)
+
+        devs = self.vm.get_serial_devs()
+        if len(devs) == 0:
+            item = gtk.CheckMenuItem(_("No consoles available"))
+            item.set_sensitive(False)
+            src.add(item)
+
+        for dev in devs:
+            item = gtk.CheckMenuItem(dev[0])
+            item.set_sensitive(dev[1])
+            if dev[1] and self.dynamic_tabs.count(dev[0]):
+                # Tab is already open, make sure marked as such
+                item.set_active(True)
+            item.connect("activate", self.control_serial_tab, dev[0], dev[2])
+            src.add(item)
+
+        src.show_all()
+
     def show(self):
         dialog = self.window.get_widget("vmm-details")
         if self.is_visible():
@@ -585,9 +624,6 @@
     def control_vm_reboot(self, src):
         self.emit("action-reboot-domain", self.vm.get_connection().get_uri(), self.vm.get_uuid())     
 
-    def control_vm_terminal(self, src):
-        self.emit("action-show-terminal", self.vm.get_connection().get_uri(), self.vm.get_uuid())
-
     def control_vm_console(self, src):
         self.emit("action-show-console", self.vm.get_connection().get_uri(), self.vm.get_uuid())
 
@@ -608,11 +644,6 @@
     def update_widget_states(self, vm, status):
         self.toggle_toolbar(self.window.get_widget("details-menu-view-toolbar"))
 
-        if vm.is_serial_console_tty_accessible():
-            self.window.get_widget("details-menu-view-serial").set_sensitive(True)
-        else:
-            self.window.get_widget("details-menu-view-serial").set_sensitive(False)
-
         if status in [ libvirt.VIR_DOMAIN_SHUTDOWN,
                        libvirt.VIR_DOMAIN_SHUTOFF ] or vm.is_read_only():
             self.window.get_widget("details-menu-destroy").set_sensitive(False)
@@ -625,7 +656,6 @@
             self.window.get_widget("config-vcpus").set_sensitive(True)
             self.window.get_widget("config-memory").set_sensitive(True)
             self.window.get_widget("config-maxmem").set_sensitive(True)
-            self.window.get_widget("details-menu-view-serial").set_sensitive(False)
         else:
             self.window.get_widget("control-run").set_sensitive(False)
             self.window.get_widget("details-menu-run").set_sensitive(False)
@@ -1221,6 +1251,70 @@
         else:
             fcdialog.hide()
         fcdialog.destroy()
+
+
+    # ------------------------------
+    # Serial Console pieces
+    # ------------------------------
+
+    def control_serial_tab(self, src, name, ttypath):
+        if src.get_active():
+            self._show_serial_tab(name, ttypath)
+        else:
+            self._close_serial_tab(name)
+
+    def show_serial_rcpopup(self, src, event):
+        if event.button != 3:
+            return
+
+        self.serial_popup.show_all()
+        self.serial_copy.connect("activate", self.serial_copy_text, src)
+        self.serial_paste.connect("activate", self.serial_paste_text, src)
+        self.serial_close.connect("activate", self.serial_close_tab,
+                                  self.window.get_widget("details-pages").get_current_page())
+
+        if src.get_has_selection():
+            self.serial_copy.set_sensitive(True)
+        else:
+            self.serial_copy.set_sensitive(False)
+        self.serial_popup.popup(None, None, None, 0, event.time)
+
+    def serial_close_tab(self, src, pagenum):
+        tab_idx = (pagenum - PAGE_DYNAMIC_OFFSET)
+        if (tab_idx < 0) or (tab_idx > len(self.dynamic_tabs)-1):
+            return
+        return self._close_serial_tab(self.dynamic_tabs[tab_idx])
+
+    def serial_copy_text(self, src, terminal):
+        terminal.copy_clipboard()
+
+    def serial_paste_text(self, src, terminal):
+        terminal.paste_clipboard()
+
+    def _show_serial_tab(self, name, ttypath):
+        if not self.dynamic_tabs.count(name):
+            child = vmmSerialConsole(self.vm, ttypath)
+            child.terminal.connect("button-press-event",
+                                   self.show_serial_rcpopup)
+            title = gtk.Label(name)
+            child.show_all()
+            self.window.get_widget("details-pages").append_page(child, title)
+            self.dynamic_tabs.append(name)
+
+        page_idx = self.dynamic_tabs.index(name) + PAGE_DYNAMIC_OFFSET
+        self.window.get_widget("details-pages").set_current_page(page_idx)
+
+    def _close_serial_tab(self, name):
+        if not self.dynamic_tabs.count(name):
+            return
+
+        page_idx = self.dynamic_tabs.index(name) + PAGE_DYNAMIC_OFFSET
+        self.window.get_widget("details-pages").remove_page(page_idx)
+        self.dynamic_tabs.remove(name)
+
+    # -----------------------
+    # Hardware Section Pieces
+    # -----------------------
 
     def config_vcpus_changed(self, src):
         self.window.get_widget("config-vcpus-apply").set_sensitive(True)
diff -r c94fe88e5f64 -r 597b7d143015 src/virtManager/domain.py
--- a/src/virtManager/domain.py	Tue Sep 30 11:37:52 2008 -0400
+++ b/src/virtManager/domain.py	Tue Sep 30 11:45:26 2008 -0400
@@ -424,17 +424,42 @@
     def run_status_icon(self):
         return self.config.get_vm_status_icon(self.status())
 
-    def get_serial_console_tty(self):
-        return util.get_xml_path(self.get_xml(), "/domain/devices/console/@tty")
-
-    def is_serial_console_tty_accessible(self):
+    def _is_serial_console_tty_accessible(self, path):
         # pty serial scheme doesn't work over remote
         if self.connection.is_remote():
             return False
-        tty = self.get_serial_console_tty()
-        if tty == None:
+
+        if path == None:
             return False
-        return os.access(tty, os.R_OK | os.W_OK)
+        return os.access(path, os.R_OK | os.W_OK)
+
+    def get_serial_devs(self):
+        def _parse_serial_consoles(ctx):
+            # [ Name, Can we connect?, tty ]
+            serial_list = []
+            usable_types = ["pty"]
+            devs = ctx.xpathEval("/domain/devices/serial")
+            for node in devs:
+                name = "Serial "
+                usable = False
+                dev_type = node.prop("type")
+                source_path = None
+
+                for child in node.children:
+                    if child.name == "target":
+                        target_port = child.prop("port")
+                        if target_port:
+                            name += str(target_port)
+                    if child.name == "source":
+                        source_path = child.prop("path")
+
+                if dev_type in usable_types:
+                    usable = self._is_serial_console_tty_accessible(source_path)
+
+                serial_list.append([name, usable, source_path])
+
+            return serial_list
+        return self._parse_device_xml(_parse_serial_consoles)
 
     def get_graphics_console(self):
         self.xml = None
diff -r c94fe88e5f64 -r 597b7d143015 src/virtManager/engine.py
--- a/src/virtManager/engine.py	Tue Sep 30 11:37:52 2008 -0400
+++ b/src/virtManager/engine.py	Tue Sep 30 11:45:26 2008 -0400
@@ -36,7 +36,6 @@
 from virtManager.create import vmmCreate
 from virtManager.host import vmmHost
 from virtManager.error import vmmErrorDialog
-from virtManager.serialcon import vmmSerialConsole
 
 class vmmEngine(gobject.GObject):
     __gsignals__ = {
@@ -112,10 +111,6 @@
         if self.connections[hvuri]["windowDetails"].has_key(vmuuid):
             self.connections[hvuri]["windowDetails"][vmuuid].close()
             del self.connections[hvuri]["windowDetails"][vmuuid]
-        if self.connections[hvuri]["windowSerialConsole"].has_key(vmuuid):
-            self.connections[hvuri]["windowSerialConsole"][vmuuid].close()
-            del self.connections[hvuri]["windowSerialConsole"][vmuuid]
-
 
     def _do_connection_changed(self, connection):
         if connection.get_state() == connection.STATE_ACTIVE or \
@@ -126,9 +121,6 @@
         for vmuuid in self.connections[hvuri]["windowDetails"].keys():
             self.connections[hvuri]["windowDetails"][vmuuid].close()
             del self.connections[hvuri]["windowDetails"][vmuuid]
-        for vmuuid in self.connections[hvuri]["windowSerialConsole"].keys():
-            self.connections[hvuri]["windowSerialConsole"][vmuuid].close()
-            del self.connections[hvuri]["windowSerialConsole"][vmuuid]
         if self.connections[hvuri]["windowHost"] is not None:
             self.connections[hvuri]["windowHost"].close()
             self.connections[hvuri]["windowHost"] = None
@@ -192,14 +184,10 @@
         self.show_help(index)
     def _do_show_console(self, src, uri, uuid):
         self.show_console(uri, uuid)
-    def _do_show_terminal(self, src, uri, uuid):
-        self.show_serial_console(uri, uuid)
     def _do_show_manager(self, src):
         self.show_manager()
     def _do_refresh_console(self, src, uri, uuid):
         self.refresh_console(uri, uuid)
-    def _do_refresh_terminal(self, src, uri, uuid):
-        self.refresh_serial_console(uri, uuid)
     def _do_save_domain(self, src, uri, uuid):
         self.save_domain(src, uri, uuid)
     def _do_destroy_domain(self, src, uri, uuid):
@@ -255,16 +243,6 @@
         win = self.show_details(uri, uuid)
         win.activate_console_page()
 
-    def show_serial_console(self, uri, uuid):
-        con = self.get_connection(uri)
-
-        if not(self.connections[uri]["windowSerialConsole"].has_key(uuid)):
-            console = vmmSerialConsole(self.get_config(),
-                                       con.get_vm(uuid))
-            self.connections[uri]["windowSerialConsole"][uuid] = console
-        self.connections[uri]["windowSerialConsole"][uuid].show()
-
-
     def refresh_console(self, uri, uuid):
         con = self.get_connection(uri)
 
@@ -272,18 +250,6 @@
             return
 
         console = self.connections[uri]["windowConsole"][uuid]
-        if not(console.is_visible()):
-            return
-
-        console.show()
-
-    def refresh_serial_console(self, uri, uuid):
-        con = self.get_connection(uri)
-
-        if not(self.connections[uri]["windowSerialConsole"].has_key(uuid)):
-            return
-
-        console = self.connections[uri]["windowSerialConsole"][uuid]
         if not(console.is_visible()):
             return
 
@@ -313,7 +279,6 @@
                 details.connect("action-reboot-domain", self._do_reboot_domain)
                 details.connect("action-exit-app", self._do_exit_app)
                 details.connect("action-view-manager", self._do_show_manager)
-                details.connect("action-show-terminal", self._do_show_terminal)
 
             except Exception, e:
                 self.err.show_err(_("Error bringing up domain details: %s") % str(e),
@@ -341,7 +306,6 @@
             self.windowManager.connect("action-show-connect", self._do_show_connect)
             self.windowManager.connect("action-connect", self._do_connect)
             self.windowManager.connect("action-refresh-console", self._do_refresh_console)
-            self.windowManager.connect("action-refresh-terminal", self._do_refresh_terminal)
             self.windowManager.connect("action-exit-app", self._do_exit_app)
         return self.windowManager
 
@@ -379,7 +343,6 @@
         if self.connections[uri]["windowCreate"] == None:
             create = vmmCreate(self.get_config(), con)
             create.connect("action-show-console", self._do_show_console)
-            create.connect("action-show-terminal", self._do_show_terminal)
             create.connect("action-show-help", self._do_show_help)
             self.connections[uri]["windowCreate"] = create
         self.connections[uri]["windowCreate"].show()
@@ -392,7 +355,6 @@
             "windowCreate": None,
             "windowDetails": {},
             "windowConsole": {},
-            "windowSerialConsole": {},
             }
         self.connections[uri]["connection"].connect("vm-removed", self._do_vm_removed)
         self.connections[uri]["connection"].connect("state-changed", self._do_connection_changed)
diff -r c94fe88e5f64 -r 597b7d143015 src/virtManager/manager.py
--- a/src/virtManager/manager.py	Tue Sep 30 11:37:52 2008 -0400
+++ b/src/virtManager/manager.py	Tue Sep 30 11:45:26 2008 -0400
@@ -434,7 +434,6 @@
                 self.emit("action-show-terminal", uri, vmuuid)
         else:
             self.emit("action-refresh-console", uri, vmuuid)
-            self.emit("action-refresh-terminal", uri, vmuuid)
 
     def _append_vm(self, model, vm, conn):
         logging.debug("About to append vm: %s" % vm.get_name())
diff -r c94fe88e5f64 -r 597b7d143015 src/virtManager/serialcon.py
--- a/src/virtManager/serialcon.py	Tue Sep 30 11:37:52 2008 -0400
+++ b/src/virtManager/serialcon.py	Tue Sep 30 11:45:26 2008 -0400
@@ -25,16 +25,16 @@
 import termios
 import tty
 import pty
+import fcntl
 
-class vmmSerialConsole:
-    def __init__(self, config, vm):
+import libvirt
+
+class vmmSerialConsole(gtk.HBox):
+    def __init__(self, vm, ttypath):
+        gtk.HBox.__init__(self)
 
         self.vm = vm
-        self.config = config
-
-        self.window = gtk.Window()
-        self.window.hide()
-        self.window.set_title(vm.get_name() + " " + _("serial console"))
+        self.ttypath = ttypath
 
         self.terminal = vte.Terminal()
         self.terminal.set_cursor_blinks(True)
@@ -53,42 +53,38 @@
         scrollbar = gtk.VScrollbar()
         scrollbar.set_adjustment(self.terminal.get_adjustment())
 
-        box = gtk.HBox()
-        box.pack_start(self.terminal)
-        box.pack_start(scrollbar)
-
-        self.window.add(box)
+        self.pack_start(self.terminal)
+        self.pack_start(scrollbar, expand=False, fill=False)
 
         self.ptyio = None
         self.ptysrc = None
         self.ptytermios = None
 
-        self.window.connect("delete-event", self.close)
+        self.connect("realize", self.handle_realize)
+        self.connect("unrealize", self.handle_unrealize)
+        self.vm.connect("status-changed", self.vm_status_changed)
 
+    def handle_realize(self, ignore=None):
+        self.opentty()
 
-    def show(self):
-        self.opentty()
-        self.window.show_all()
-        self.window.present()
+    def handle_unrealize(self, src=None, ignore=None):
+        self.closetty()
 
-    def close(self, src=None, ignore=None):
-        self.closetty()
-        self.window.hide()
-        return True
-
-    def is_visible(self):
-        if self.window.flags() & gtk.VISIBLE:
-           return 1
-        return 0
+    def vm_status_changed(self, src, status):
+        if status in [ libvirt.VIR_DOMAIN_RUNNING ]:
+            self.opentty()
+        else:
+            self.closetty()
 
     def opentty(self):
         if self.ptyio != None:
             self.closetty()
-        ipty = self.vm.get_serial_console_tty()
+        ipty = self.ttypath
 
         if ipty == None:
             return
         self.ptyio = pty.slave_open(ipty)
+        fcntl.fcntl(self.ptyio, fcntl.F_SETFL, os.O_NONBLOCK)
         self.ptysrc = gobject.io_add_watch(self.ptyio, gobject.IO_IN | gobject.IO_ERR | gobject.IO_HUP, self.display_data)
 
         # Save term settings & set to raw mode
diff -r c94fe88e5f64 -r 597b7d143015 src/vmm-details.glade
--- a/src/vmm-details.glade	Tue Sep 30 11:37:52 2008 -0400
+++ b/src/vmm-details.glade	Tue Sep 30 11:45:26 2008 -0400
@@ -210,11 +210,11 @@
 		  </child>
 
 		  <child>
-		    <widget class="GtkMenuItem" id="details-menu-view-serial">
-		      <property name="visible">True</property>
-		      <property name="label" translatable="yes">Serial Console...</property>
-		      <property name="use_underline">True</property>
-		      <signal name="activate" handler="on_details_menu_view_serial_activate" last_modification_time="Thu, 24 Jul 2008 19:14:47 GMT"/>
+		    <widget class="GtkMenuItem" id="details-menu-view-serial-list">
+		      <property name="visible">True</property>
+		      <property name="label" translatable="yes">Serial Consoles</property>
+		      <property name="use_underline">True</property>
+		      <signal name="activate" handler="on_details_menu_view_serial_list_activate" last_modification_time="Mon, 29 Sep 2008 16:57:27 GMT"/>
 		    </widget>
 		  </child>
 

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