View serial consoles as tabs in the details window. Allow viewing not just the primary console.

This commit is contained in:
Cole Robinson 2008-10-02 16:17:25 -04:00
parent b9ed921a7c
commit 4ac507f181
6 changed files with 179 additions and 87 deletions

View File

@ -36,6 +36,7 @@ import cairo
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_VNCVIEWER = 3
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 @@ class vmmDetails(gobject.GObject):
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 @@ class vmmDetails(gobject.GObject):
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 @@ class vmmDetails(gobject.GObject):
"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,47 @@ class vmmDetails(gobject.GObject):
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 serial devices found"))
item.set_sensitive(False)
src.add(item)
usable_types = [ "pty" ]
for dev in devs:
sensitive = False
msg = ""
item = gtk.CheckMenuItem(dev[0])
if self.vm.get_connection().is_remote():
msg = _("Serial console not yet supported over remote "
"connection.")
elif not self.vm.is_active():
msg = _("Serial console not available for inactive guest.")
elif not dev[1] in usable_types:
msg = _("Console for device type '%s' not yet supported.") % \
dev[1]
elif dev[2] and not os.access(dev[2], os.R_OK | os.W_OK):
msg = _("Can not access console path '%s'.") % str(dev[2])
else:
sensitive = True
if not sensitive:
item.set_tooltip_text(msg)
item.set_sensitive(sensitive)
if sensitive 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 +644,6 @@ class vmmDetails(gobject.GObject):
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 +664,6 @@ class vmmDetails(gobject.GObject):
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 +676,6 @@ class vmmDetails(gobject.GObject):
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)
@ -1222,6 +1272,70 @@ class vmmDetails(gobject.GObject):
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)

View File

@ -424,17 +424,38 @@ class vmmDomain(gobject.GObject):
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, device type, source path
serial_list = []
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")
serial_list.append([name, dev_type, source_path])
return serial_list
return self._parse_device_xml(_parse_serial_consoles)
def get_graphics_console(self):
self.xml = None

View File

@ -36,7 +36,6 @@ from virtManager.asyncjob import vmmAsyncJob
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 @@ class vmmEngine(gobject.GObject):
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 @@ class vmmEngine(gobject.GObject):
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 @@ class vmmEngine(gobject.GObject):
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 @@ class vmmEngine(gobject.GObject):
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)
@ -277,18 +255,6 @@ class vmmEngine(gobject.GObject):
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
console.show()
def show_details_performance(self, uri, uuid):
win = self.show_details(uri, uuid)
win.activate_performance_page()
@ -313,7 +279,6 @@ class vmmEngine(gobject.GObject):
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 @@ class vmmEngine(gobject.GObject):
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 @@ class vmmEngine(gobject.GObject):
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 @@ class vmmEngine(gobject.GObject):
"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)

View File

@ -434,7 +434,6 @@ class vmmManager(gobject.GObject):
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())

View File

@ -25,16 +25,16 @@ import gobject
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 @@ class vmmSerialConsole:
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 show(self):
def handle_realize(self, ignore=None):
self.opentty()
self.window.show_all()
self.window.present()
def close(self, src=None, ignore=None):
def handle_unrealize(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

View File

@ -210,11 +210,11 @@
</child>
<child>
<widget class="GtkMenuItem" id="details-menu-view-serial">
<widget class="GtkMenuItem" id="details-menu-view-serial-list">
<property name="visible">True</property>
<property name="label" translatable="yes">Serial Console...</property>
<property name="label" translatable="yes">Serial Consoles</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"/>
<signal name="activate" handler="on_details_menu_view_serial_list_activate" last_modification_time="Mon, 29 Sep 2008 16:57:27 GMT"/>
</widget>
</child>