virt-manager: Drop manual dbusapi, use GtkApplication

So I was confused here and completely missed that GtkApplication is
meant to handle the common case of invoking actions in an existing
app instance. So drop the manual dbus server and use the simpler
Gtk support.

Fix a bunch of other errors in this area too
This commit is contained in:
Cole Robinson 2015-12-03 15:54:52 -05:00
parent 7ef17138da
commit febe0abdd2
4 changed files with 130 additions and 227 deletions

View File

@ -252,11 +252,9 @@ def main():
LibvirtGLib.init(None)
LibvirtGLib.event_register()
from virtManager.dbusapi import StartupAPI
api = StartupAPI()
engine = vmmEngine()
api.run_cli_command(options.uri, show_window, domain)
api.start(options.skip_autostart)
engine.start(options.uri, show_window, domain, options.skip_autostart)
if __name__ == "__main__":

View File

@ -1,147 +0,0 @@
#
# Copyright (C) 2015 Red Hat, Inc.
# Copyright (C) 2015 Cole Robinson <crobinso@redhat.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA.
#
import logging
from gi.repository import Gio
from virtManager.engine import vmmEngine
class _DBusServer(object):
"""
Initializing this object starts the dbus service. It's job is just
to proxy run_cli_command calls from a virt-manager command line
invocation to the already running virt-manager instance
"""
SERVICE_NAME = "org.virt-manager.cli"
OBJECT_PATH = "/org/virtmanager/cli"
INTERFACE_NAME = "org.virtmanager.cli"
API_XML = """
<node>
<interface name='%s'>
<method name='run_cli_command'>
<arg type='s' name='uri' direction='in'/>
<arg type='s' name='show_window' direction='in'/>
<arg type='s' name='domain' direction='in'/>
</method>
</interface>
</node>
""" % INTERFACE_NAME
def __init__(self, engine):
self.engine = engine
logging.debug("Starting dbus cli server")
Gio.bus_own_name(
Gio.BusType.SESSION,
self.SERVICE_NAME,
Gio.BusNameOwnerFlags.NONE,
self._on_bus_acquired,
self._on_name_acquired,
self._on_name_lost)
def _handle_method_call(self,
connection, sender, object_path, interface_name,
method_name, parameters, invocation):
ignore = connection
ignore = sender
ignore = object_path
ignore = interface_name
try:
if method_name == "run_cli_command":
logging.debug("dbus run_cli_command invoked with args=%s",
parameters)
self.engine.run_cli_command(*parameters)
invocation.return_value(None)
else:
raise RuntimeError("Unhandled method=%s" % method_name)
except Exception, e:
logging.debug("Error processing dbus method=%s",
method_name, exc_info=True)
Gio.DBusMethodInvocation.return_error_literal(
invocation, Gio.DBusError.quark(), Gio.DBusError.FAILED, str(e))
def _on_bus_acquired(self, connection, name):
ignore = name
introspection_data = Gio.DBusNodeInfo.new_for_xml(self.API_XML)
connection.register_object(
self.OBJECT_PATH,
introspection_data.interfaces[0],
self._handle_method_call, None, None)
def _on_name_acquired(self, *args, **kwargs):
pass
def _on_name_lost(self, *args, **kwargs):
logging.debug("Failed to acquire dbus service args=%s kwargs=%s",
args, kwargs)
class StartupAPI(object):
def __init__(self):
self._engine = vmmEngine()
self._proxy = self._init_dbus()
#################
# Dbus handling #
#################
def _init_dbus(self):
bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
proxy = Gio.DBusProxy.new_sync(
bus, 0, None,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus")
if not proxy.NameHasOwner("(s)", _DBusServer.SERVICE_NAME):
_DBusServer(self._engine)
return
logging.debug("Detected app is already running, connecting "
"to existing instance.")
return Gio.DBusProxy.new_sync(
bus, 0, None,
_DBusServer.SERVICE_NAME,
_DBusServer.OBJECT_PATH,
_DBusServer.INTERFACE_NAME)
##############
# Public API #
##############
def start(self, skip_autostart):
# Unconditionally use the engine here, since GtkApplication already
# provides us with app uniqueness checking.
self._engine.start(skip_autostart)
def run_cli_command(self, uri, show_window, domain):
if self._proxy:
self._proxy.run_cli_command("(sss)",
uri or "", show_window or "", domain or "")
else:
self._engine.run_cli_command(uri, show_window, domain)

View File

@ -18,6 +18,7 @@
# MA 02110-1301 USA.
#
from gi.repository import Gio
from gi.repository import GLib
from gi.repository import GObject
from gi.repository import Gtk
@ -93,11 +94,9 @@ class vmmEngine(vmmGObject):
self.systray = None
self.delete_dialog = None
self._application = Gtk.Application(
application_id="org.virt-manager.gtkapplication",
flags=0)
self._application.connect("activate", self._activate)
self._appwindow = Gtk.Window()
self._gtkapplication = None
self._init_gtk_application()
self._tick_counter = 0
self._tick_thread_slow = False
@ -115,10 +114,6 @@ class vmmEngine(vmmGObject):
# keep running in system tray if enabled
self.windows = 0
self._cli_uri = None
self._show_manager_at_startup = True
self._skip_autostart = False
self.init_systray()
self.add_gsettings_handle(
@ -133,20 +128,59 @@ class vmmEngine(vmmGObject):
self.tick()
def _activate(self, ignore):
if self._show_manager_at_startup:
self._show_manager()
############################
# Gtk Application handling #
############################
def _on_gtk_application_activated(self, ignore):
"""
Invoked after application.run()
"""
if not self._application.get_windows():
logging.debug("Initial gtkapplication activated")
self._application.add_window(Gtk.Window())
def _init_gtk_application(self):
self._application = Gtk.Application(
application_id="org.virt-manager.virt-manager", flags=0)
self._application.register(None)
self._application.connect("activate",
self._on_gtk_application_activated)
action = Gio.SimpleAction.new("cli_command",
GLib.VariantType.new("(sss)"))
action.connect("activate", self._handle_cli_command)
self._application.add_action(action)
def _default_startup(self, skip_autostart):
uris = self.conns.keys()
if not uris:
logging.debug("No stored URIs found.")
else:
self.get_manager()
self._application.add_window(self._appwindow)
logging.debug("Loading stored URIs:\n%s",
" \n".join(sorted(uris)))
if not self._skip_autostart and not self._cli_uri:
self.autostart_conns()
if not skip_autostart:
self.idle_add(self.autostart_conns)
def start(self, skip_autostart):
self._skip_autostart = skip_autostart
if not self.config.get_conn_uris():
# Only add default if no connections are currently known
self.timeout_add(1000, self._add_default_conn)
def start(self, uri, show_window, domain, skip_autostart):
# Dispatch dbus CLI command
data = GLib.Variant("(sss)",
(uri or "", show_window or "", domain or ""))
self._application.activate_action("cli_command", data)
if self._application.get_is_remote():
logging.debug("Connected to remote app instance.")
return
self._default_startup(skip_autostart)
self._application.run(None)
def init_systray(self):
if self.systray:
return
@ -177,14 +211,9 @@ class vmmEngine(vmmGObject):
# Show the manager so that the user can control the application
self._show_manager()
def add_default_conn(self, manager):
# Only add default if no connections are currently known
if self.config.get_conn_uris() or self._cli_uri:
return
def _add_default_conn(self):
manager = self.get_manager()
self.timeout_add(1000, self._add_default_conn, manager)
def _add_default_conn(self, manager):
# Manager fail message
msg = _("Could not detect a default hypervisor. Make\n"
"sure the appropriate virtualization packages\n"
@ -230,10 +259,7 @@ class vmmEngine(vmmGObject):
def load_stored_uris(self):
uris = self.config.get_conn_uris()
if not uris:
return
logging.debug("About to connect to uris %s", uris)
uris = self.config.get_conn_uris() or []
for uri in uris:
conn = self.make_conn(uri)
self.register_conn(conn, skip_config=True)
@ -378,10 +404,7 @@ class vmmEngine(vmmGObject):
self.windows -= 1
logging.debug("window counter decremented to %s", self.windows)
if self._can_exit():
# Defer this to an idle callback, since we can race with
# a vmmDetails window being deleted.
self.idle_add(self.exit_app, src)
self._exit_app_if_no_windows(src)
def _can_exit(self):
# Don't exit if system tray is enabled
@ -438,6 +461,13 @@ class vmmEngine(vmmGObject):
self.cleanup_conn(uri)
self.conns = {}
def _exit_app_if_no_windows(self, src=None):
def cb():
if self._can_exit():
logging.debug("No windows found, requesting app exit")
self.exit_app(src or self)
self.idle_add(cb)
def exit_app(self, src):
if self.err is None:
# Already in cleanup
@ -467,7 +497,7 @@ class vmmEngine(vmmGObject):
for ignore in range(Gtk.main_level()):
Gtk.main_quit()
self._application.remove_window(self._appwindow)
self._application.quit()
def _create_inspection_thread(self):
logging.debug("libguestfs inspection support: %s",
@ -682,7 +712,7 @@ class vmmEngine(vmmGObject):
else:
if self._can_exit():
self.err.show_err(msg, details, title, modal=True)
self.idle_add(self.exit_app, conn)
self._exit_app_if_no_windows(conn)
else:
self.err.show_err(msg, details, title)
@ -810,9 +840,6 @@ class vmmEngine(vmmGObject):
details.show()
except Exception, e:
src.err.show_err(_("Error launching details: %s") % str(e))
finally:
if self._can_exit():
self.idle_add(self.exit_app, src)
def _do_show_vm(self, src, uri, connkey):
conn = self._lookup_conn(uri)
@ -845,13 +872,10 @@ class vmmEngine(vmmGObject):
obj.connect("manager-opened", self.increment_window_counter)
obj.connect("manager-closed", self.decrement_window_counter)
obj.connect("remove-conn", self.remove_conn)
obj.connect("add-default-conn", self.add_default_conn)
self.connect("conn-added", obj.add_conn)
self.connect("conn-removed", obj.remove_conn)
obj.set_initial_selection(self._cli_uri)
self.windowManager = obj
return self.windowManager
@ -941,7 +965,6 @@ class vmmEngine(vmmGObject):
if not vm:
src.err.show_err("%s does not have VM '%s'" %
(uri, clistr), modal=True)
self.exit_app(src.err)
return
self._show_vm_helper(src, uri, vm, page, True)
@ -957,49 +980,76 @@ class vmmEngine(vmmGObject):
self._do_show_create(self.get_manager(), uri)
def _show_domain_console(self, uri, clistr):
self.idle_add(self._cli_show_vm_helper, uri, clistr, DETAILS_CONSOLE)
self._cli_show_vm_helper(uri, clistr, DETAILS_CONSOLE)
def _show_domain_editor(self, uri, clistr):
self.idle_add(self._cli_show_vm_helper, uri, clistr, DETAILS_CONFIG)
self._cli_show_vm_helper(uri, clistr, DETAILS_CONFIG)
def _show_domain_performance(self, uri, clistr):
self.idle_add(self._cli_show_vm_helper, uri, clistr, DETAILS_PERF)
self._cli_show_vm_helper(uri, clistr, DETAILS_PERF)
def _launch_cli_window(self, uri, show_window, clistr):
logging.debug("Launching requested window '%s'", show_window)
if show_window == self.CLI_SHOW_DOMAIN_CREATOR:
self._show_domain_creator(uri)
elif show_window == self.CLI_SHOW_DOMAIN_EDITOR:
self._show_domain_editor(uri, clistr)
elif show_window == self.CLI_SHOW_DOMAIN_PERFORMANCE:
self._show_domain_performance(uri, clistr)
elif show_window == self.CLI_SHOW_DOMAIN_CONSOLE:
self._show_domain_console(uri, clistr)
elif show_window == self.CLI_SHOW_HOST_SUMMARY:
self._show_host_summary(uri)
else:
logging.debug("Unknown cli window command '%s'", show_window)
try:
logging.debug("Launching requested window '%s'", show_window)
if show_window == self.CLI_SHOW_DOMAIN_CREATOR:
self._show_domain_creator(uri)
elif show_window == self.CLI_SHOW_DOMAIN_EDITOR:
self._show_domain_editor(uri, clistr)
elif show_window == self.CLI_SHOW_DOMAIN_PERFORMANCE:
self._show_domain_performance(uri, clistr)
elif show_window == self.CLI_SHOW_DOMAIN_CONSOLE:
self._show_domain_console(uri, clistr)
elif show_window == self.CLI_SHOW_HOST_SUMMARY:
self._show_host_summary(uri)
else:
raise RuntimeError("Unknown cli window command '%s'" %
show_window)
finally:
# In case of cli error, we may need to exit the app
self._exit_app_if_no_windows()
def _cli_conn_connected_cb(self, conn, uri, show_window, domain):
ignore = conn
try:
ignore = conn
if conn.is_disconnected():
# Connection error
if conn.is_disconnected():
raise RuntimeError("failed to connect to cli uri=%s" % uri)
if conn.is_active():
self._launch_cli_window(uri, show_window, domain)
return True
return False
except:
# In case of cli error, we may need to exit the app
logging.debug("Error in cli connection callback", exc_info=True)
self._exit_app_if_no_windows()
return True
if conn.is_active():
self._launch_cli_window(uri, show_window, domain)
return True
def _do_handle_cli_command(self, actionobj, variant):
ignore = actionobj
uri = variant[0]
show_window = variant[1]
domain = variant[2]
return False
def run_cli_command(self, uri, show_window, domain):
logging.debug("processing cli command uri=%s show_window=%s domain=%s",
uri, show_window, domain)
if not uri:
logging.debug("No cli action requested, launching default window")
self._show_manager()
return
self._cli_uri = uri
conn = self.make_conn(uri)
self.register_conn(conn, skip_config=True)
if conn.is_disconnected():
# Schedule connection open
def connect():
# We need to wrap this to ignore the return value, otherwise
# the callback will be rescheduled
self.connect_to_uri(uri)
self.idle_add(connect)
if show_window:
if conn.is_active():
self.idle_add(self._launch_cli_window,
@ -1007,12 +1057,17 @@ class vmmEngine(vmmGObject):
else:
conn.connect_opt_out("state-changed",
self._cli_conn_connected_cb, uri, show_window, domain)
self._show_manager_at_startup = False
else:
self.get_manager().set_initial_selection(uri)
self._show_manager()
if conn.is_disconnected():
def connect():
self.connect_to_uri(uri)
self.idle_add(connect)
def _handle_cli_command(self, actionobj, variant):
try:
return self._do_handle_cli_command(actionobj, variant)
except:
# In case of cli error, we may need to exit the app
logging.debug("Error handling cli command", exc_info=True)
self._exit_app_if_no_windows()
#######################################

View File

@ -107,7 +107,6 @@ class vmmManager(vmmGObjectUI):
"manager-closed": (GObject.SignalFlags.RUN_FIRST, None, []),
"manager-opened": (GObject.SignalFlags.RUN_FIRST, None, []),
"remove-conn": (GObject.SignalFlags.RUN_FIRST, None, [str]),
"add-default-conn": (GObject.SignalFlags.RUN_FIRST, None, []),
}
def __init__(self):
@ -190,8 +189,6 @@ class vmmManager(vmmGObjectUI):
self.enable_polling(COL_NETWORK)
self.enable_polling(COL_MEM)
# Queue up the default connection detector
self.idle_emit("add-default-conn")
##################
# Common methods #