virt-manager: revive cli dbus API (bz 1162815)

This allows virt-manager command line that launch windows to work for an
existing virt-manager instance. This is handy for scripting, and giving
other apps a reasonable way to launch a virt-manager VM window for
example.
This commit is contained in:
Cole Robinson 2015-11-24 15:21:26 -05:00
parent f801a7e4ec
commit a9bc56add3
3 changed files with 253 additions and 94 deletions

View File

@ -91,7 +91,7 @@ def parse_commandline():
epilog=epilog)
parser.add_argument('--version', action='version',
version=CLIConfig.version)
parser.set_defaults(uuid=None)
parser.set_defaults(domain=None)
# Trace every libvirt API call to debug output
parser.add_argument("--trace-libvirt", dest="tracelibvirt",
@ -116,7 +116,7 @@ def parse_commandline():
parser.add_argument("--no-fork", action="store_true", dest="nofork",
help="Don't fork into background on startup")
parser.add_argument("--no-conn-autostart", action="store_true",
dest="no_conn_auto", help="Do not autostart connections")
dest="skip_autostart", help="Do not autostart connections")
parser.add_argument("--spice-disable-auto-usbredir", action="store_true",
dest="usbredir", help="Disable Auto USB redirection support")
@ -134,23 +134,6 @@ def parse_commandline():
return parser.parse_known_args()
def launch_specific_window(engine, show, uri, clistr):
if not show:
return
logging.debug("Launching requested window '%s'", show)
if show == 'creator':
engine.show_domain_creator(uri)
elif show == 'editor':
engine.show_domain_editor(uri, clistr)
elif show == 'performance':
engine.show_domain_performance(uri, clistr)
elif show == 'console':
engine.show_domain_console(uri, clistr)
elif show == 'summary':
engine.show_host_summary(uri)
def main():
(options, leftovers) = parse_commandline()
@ -246,52 +229,34 @@ def main():
import virtinst.pollhelpers
virtinst.pollhelpers.FORCE_OLD_POLL = bool(options.testoldpoll)
show = None
show_window = None
domain = None
if options.show_domain_creator:
show = "creator"
elif options.show_domain_editor:
show = "editor"
elif options.show_domain_performance:
show = "performance"
elif options.show_domain_console:
show = "console"
show_window = vmmEngine.CLI_SHOW_DOMAIN_CREATOR
elif options.show_host_summary:
show = "summary"
show_window = vmmEngine.CLI_SHOW_HOST_SUMMARY
elif options.show_domain_editor:
show_window = vmmEngine.CLI_SHOW_DOMAIN_EDITOR
domain = options.show_domain_editor
elif options.show_domain_performance:
show_window = vmmEngine.CLI_SHOW_DOMAIN_PERFORMANCE
domain = options.show_domain_performance
elif options.show_domain_console:
show_window = vmmEngine.CLI_SHOW_DOMAIN_CONSOLE
domain = options.show_domain_console
if show and options.uri is None:
if show_window and options.uri is None:
raise RuntimeError("can't use --show-* options without --connect")
if show:
options.uuid = (options.uuid or options.show_domain_creator or
options.show_domain_editor or
options.show_domain_performance or
options.show_domain_console or
options.show_host_summary)
# Hook libvirt events into glib main loop
LibvirtGLib.init(None)
LibvirtGLib.event_register()
engine = vmmEngine()
engine.skip_autostart = options.no_conn_auto
engine.uri_at_startup = options.uri
from virtManager.dbusapi import StartupAPI
api = StartupAPI()
if show:
def cb(conn):
if conn.is_disconnected():
# Connection error
return True
if conn.is_active():
launch_specific_window(engine, show, options.uri, options.uuid)
return True
return False
engine.uri_cb = cb
engine.show_manager_window = False
engine.skip_autostart = True
# Finally start the app for real
engine.application.run(None)
api.run_cli_command(options.uri, show_window, domain)
api.start(options.skip_autostart)
if __name__ == "__main__":

147
virtManager/dbusapi.py Normal file
View File

@ -0,0 +1,147 @@
#
# 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

@ -63,6 +63,12 @@ DETAILS_CONSOLE = 3
class vmmEngine(vmmGObject):
CLI_SHOW_DOMAIN_CREATOR = "creator"
CLI_SHOW_DOMAIN_EDITOR = "editor"
CLI_SHOW_DOMAIN_PERFORMANCE = "performance"
CLI_SHOW_DOMAIN_CONSOLE = "console"
CLI_SHOW_HOST_SUMMARY = "summary"
__gsignals__ = {
"conn-added": (GObject.SignalFlags.RUN_FIRST, None, [object]),
"conn-removed": (GObject.SignalFlags.RUN_FIRST, None, [str]),
@ -87,10 +93,10 @@ class vmmEngine(vmmGObject):
self.systray = None
self.delete_dialog = None
self.application = Gtk.Application(
application_id="com.redhat.virt-manager",
flags=0)
self.application.connect("activate", self._activate)
self._application = Gtk.Application(
application_id="org.virt-manager.gtkapplication",
flags=0)
self._application.connect("activate", self._activate)
self._appwindow = Gtk.Window()
self._tick_counter = 0
@ -109,11 +115,9 @@ class vmmEngine(vmmGObject):
# keep running in system tray if enabled
self.windows = 0
# Public bits set by virt-manager cli
self.skip_autostart = False
self.uri_at_startup = None
self.uri_cb = None
self.show_manager_window = True
self._cli_uri = None
self._show_manager_at_startup = True
self._skip_autostart = False
self.init_systray()
@ -130,23 +134,18 @@ class vmmEngine(vmmGObject):
def _activate(self, ignore):
if self.show_manager_window:
self.show_manager()
if self._show_manager_at_startup:
self._show_manager()
else:
self.get_manager()
self.application.add_window(self._appwindow)
self._application.add_window(self._appwindow)
if self.uri_at_startup:
conn = self.make_conn(self.uri_at_startup)
self.register_conn(conn, skip_config=True)
if conn and self.uri_cb:
conn.connect_opt_out("state-changed", self.uri_cb)
self.connect_to_uri(self.uri_at_startup)
if not self.skip_autostart:
if not self._skip_autostart and not self._cli_uri:
self.autostart_conns()
def start(self, skip_autostart):
self._skip_autostart = skip_autostart
self._application.run(None)
def init_systray(self):
if self.systray:
@ -176,11 +175,11 @@ class vmmEngine(vmmGObject):
systray_enabled = self.config.get_view_system_tray()
if self.windows == 0 and not systray_enabled:
# Show the manager so that the user can control the application
self.show_manager()
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.uri_at_startup:
if self.config.get_conn_uris() or self._cli_uri:
return
self.timeout_add(1000, self._add_default_conn, manager)
@ -468,7 +467,7 @@ class vmmEngine(vmmGObject):
for ignore in range(Gtk.main_level()):
Gtk.main_quit()
self.application.remove_window(self._appwindow)
self._application.remove_window(self._appwindow)
def _create_inspection_thread(self):
logging.debug("libguestfs inspection support: %s",
@ -851,7 +850,7 @@ class vmmEngine(vmmGObject):
self.connect("conn-added", obj.add_conn)
self.connect("conn-removed", obj.remove_conn)
obj.set_initial_selection(self.uri_at_startup)
obj.set_initial_selection(self._cli_uri)
self.windowManager = obj
return self.windowManager
@ -919,17 +918,6 @@ class vmmEngine(vmmGObject):
# Window launchers from virt-manager cli #
##########################################
def show_manager(self):
self._do_show_manager(None)
def show_host_summary(self, uri):
self._do_show_host(self.get_manager(), uri)
def show_domain_creator(self, uri):
self.show_manager()
self._do_show_create(self.get_manager(), uri)
def _find_vm_by_cli_str(self, uri, clistr):
"""
Lookup a VM by a string passed in on the CLI. Can be either
@ -958,15 +946,74 @@ class vmmEngine(vmmGObject):
self._show_vm_helper(src, uri, vm, page, True)
def show_domain_console(self, uri, clistr):
def _show_manager(self):
self._do_show_manager(None)
def _show_host_summary(self, uri):
self._do_show_host(self.get_manager(), uri)
def _show_domain_creator(self, uri):
self._show_manager()
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)
def show_domain_editor(self, uri, clistr):
def _show_domain_editor(self, uri, clistr):
self.idle_add(self._cli_show_vm_helper, uri, clistr, DETAILS_CONFIG)
def show_domain_performance(self, uri, clistr):
def _show_domain_performance(self, uri, clistr):
self.idle_add(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)
def _cli_conn_connected_cb(self, conn, uri, show_window, domain):
ignore = conn
if conn.is_disconnected():
# Connection error
return True
if conn.is_active():
self._launch_cli_window(uri, show_window, domain)
return True
return False
def run_cli_command(self, uri, show_window, domain):
if not uri:
return
self._cli_uri = uri
conn = self.make_conn(uri)
self.register_conn(conn, skip_config=True)
if show_window:
if conn.is_active():
self.idle_add(self._launch_cli_window,
uri, show_window, domain)
else:
conn.connect_opt_out("state-changed",
self._cli_conn_connected_cb, uri, show_window, domain)
self._show_manager_at_startup = False
if conn.is_disconnected():
def connect():
self.connect_to_uri(uri)
self.idle_add(connect)
#######################################
# Domain actions run/destroy/save ... #