2020-01-27 06:12:09 +08:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
#
|
|
|
|
# Copyright (C) 2006, 2014 Red Hat, Inc.
|
|
|
|
# Copyright (C) 2006 Daniel P. Berrange <berrange@redhat.com>
|
|
|
|
#
|
|
|
|
# This work is licensed under the GNU GPLv2 or later.
|
|
|
|
# See the COPYING file in the top-level directory.
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
import os
|
|
|
|
import signal
|
|
|
|
import sys
|
|
|
|
import traceback
|
|
|
|
|
|
|
|
import gi
|
|
|
|
gi.require_version('LibvirtGLib', '1.0')
|
|
|
|
from gi.repository import LibvirtGLib
|
|
|
|
|
|
|
|
from virtinst import BuildConfig
|
|
|
|
from virtinst import VirtinstConnection
|
|
|
|
from virtinst import cli
|
|
|
|
from virtinst import log
|
|
|
|
|
|
|
|
# This is massively heavy handed, but I can't figure out any way to shut
|
|
|
|
# up the slew of gtk deprecation warnings that clog up our very useful
|
|
|
|
# stdout --debug output. Of course we could drop use of deprecated APIs,
|
|
|
|
# but it's a serious quantity of churn
|
|
|
|
import warnings # pylint: disable=wrong-import-order
|
|
|
|
warnings.simplefilter("ignore")
|
|
|
|
|
|
|
|
try:
|
|
|
|
gi.check_version("3.22.0")
|
|
|
|
except (ValueError, AttributeError):
|
|
|
|
print("pygobject3 3.22.0 or later is required.")
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
def _show_startup_error(msg, details):
|
|
|
|
log.debug("Error starting virt-manager: %s\n%s", msg, details,
|
|
|
|
exc_info=True)
|
|
|
|
from .error import vmmErrorDialog
|
|
|
|
err = vmmErrorDialog.get_instance()
|
|
|
|
title = _("Error starting Virtual Machine Manager")
|
|
|
|
err.show_err(title + ": " + msg,
|
|
|
|
details=details,
|
|
|
|
title=title,
|
|
|
|
modal=True,
|
|
|
|
debug=False)
|
|
|
|
|
|
|
|
|
|
|
|
def _import_gtk(leftovers):
|
|
|
|
# The never ending fork+gsettings problems now require us to
|
|
|
|
# import Gtk _after_ the fork. This creates a funny race, since we
|
|
|
|
# need to parse the command line arguments to know if we need to
|
|
|
|
# fork, but need to import Gtk before cli processing so it can
|
|
|
|
# handle --g-fatal-args. We strip out our flags first and pass the
|
|
|
|
# left overs to gtk
|
|
|
|
origargv = sys.argv
|
|
|
|
try:
|
|
|
|
sys.argv = origargv[:1] + leftovers[:]
|
|
|
|
gi.require_version('Gtk', '3.0')
|
|
|
|
from gi.repository import Gtk
|
|
|
|
leftovers = sys.argv[1:]
|
|
|
|
|
|
|
|
if Gtk.check_version(3, 22, 0):
|
|
|
|
print("gtk3 3.22.0 or later is required.")
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
# This will error if Gtk wasn't correctly initialized
|
|
|
|
Gtk.init()
|
|
|
|
globals()["Gtk"] = Gtk
|
|
|
|
|
|
|
|
# This ensures we can init gsettings correctly
|
|
|
|
from . import config
|
|
|
|
ignore = config
|
|
|
|
except Exception as e:
|
|
|
|
# Don't just let the exception raise here. abrt reports bugs
|
|
|
|
# when users mess up su/sudo and DISPLAY isn't set. Printing
|
|
|
|
# it avoids the issue
|
|
|
|
display = os.environ.get("DISPLAY", "")
|
|
|
|
msg = str(e)
|
|
|
|
if display:
|
|
|
|
msg += ": Could not open display: %s" % display
|
|
|
|
log.debug("".join(traceback.format_exc()))
|
|
|
|
print(msg)
|
|
|
|
sys.exit(1)
|
|
|
|
finally:
|
|
|
|
sys.argv = origargv
|
|
|
|
|
|
|
|
return leftovers
|
|
|
|
|
|
|
|
|
|
|
|
def _setup_gsettings_path(schemadir):
|
|
|
|
"""
|
|
|
|
If running from the virt-manager.git srcdir, compile our gsettings
|
|
|
|
schema and use it directly
|
|
|
|
"""
|
|
|
|
import subprocess
|
|
|
|
import shutil
|
|
|
|
|
|
|
|
exe = shutil.which("glib-compile-schemas")
|
|
|
|
if not exe: # pragma: no cover
|
|
|
|
raise RuntimeError("You must install glib-compile-schemas to run "
|
|
|
|
"virt-manager from git.")
|
|
|
|
subprocess.check_call([exe, "--strict", schemadir])
|
|
|
|
|
|
|
|
|
|
|
|
def drop_tty():
|
|
|
|
# We fork and setsid so that we drop the controlling
|
|
|
|
# tty. This prevents libvirt's SSH tunnels from prompting
|
|
|
|
# for user input if SSH keys/agent aren't configured.
|
|
|
|
if os.fork() != 0:
|
|
|
|
os._exit(0) # pylint: disable=protected-access
|
|
|
|
|
|
|
|
os.setsid()
|
|
|
|
|
|
|
|
|
|
|
|
def drop_stdio():
|
|
|
|
# This is part of the fork process described in drop_tty()
|
|
|
|
for fd in range(0, 2):
|
|
|
|
try:
|
|
|
|
os.close(fd)
|
|
|
|
except OSError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
os.open(os.devnull, os.O_RDWR)
|
|
|
|
os.dup2(0, 1)
|
|
|
|
os.dup2(0, 2)
|
|
|
|
|
|
|
|
|
|
|
|
def parse_commandline():
|
|
|
|
epilog = ("Also accepts standard GTK arguments like --g-fatal-warnings")
|
|
|
|
parser = argparse.ArgumentParser(usage="virt-manager [options]",
|
|
|
|
epilog=epilog)
|
|
|
|
parser.add_argument('--version', action='version',
|
|
|
|
version=BuildConfig.version)
|
|
|
|
parser.set_defaults(domain=None)
|
|
|
|
|
|
|
|
# Trace every libvirt API call to debug output
|
|
|
|
parser.add_argument("--trace-libvirt", choices=["all", "mainloop"],
|
|
|
|
help=argparse.SUPPRESS)
|
|
|
|
|
|
|
|
# Don't load any connections on startup to test first run
|
|
|
|
# PackageKit integration
|
|
|
|
parser.add_argument("--test-first-run",
|
|
|
|
help=argparse.SUPPRESS, action="store_true")
|
|
|
|
# Force disable use of libvirt object events
|
|
|
|
parser.add_argument("--test-no-events",
|
|
|
|
help=argparse.SUPPRESS, action="store_true")
|
|
|
|
# Enabling this will tell us, at app exit time, which vmmGObjects were not
|
|
|
|
# garbage collected. This is caused by circular references to other objects,
|
|
|
|
# like a signal that wasn't disconnected. It's not a big deal, but if we
|
|
|
|
# have objects that can be created and destroyed a lot over the course of
|
|
|
|
# the app lifecycle, every non-garbage collected class is a memory leak.
|
|
|
|
# So it's nice to poke at this every now and then and try to track down
|
|
|
|
# what we need to add to class _cleanup handling.
|
|
|
|
parser.add_argument("--test-leak-debug",
|
|
|
|
help=argparse.SUPPRESS, action="store_true")
|
|
|
|
|
|
|
|
# comma separated string of options to tweak app behavior,
|
|
|
|
# for manual and automated testing config
|
|
|
|
parser.add_argument("--test-options", help=argparse.SUPPRESS)
|
|
|
|
|
|
|
|
parser.add_argument("-c", "--connect", dest="uri",
|
|
|
|
help="Connect to hypervisor at URI", metavar="URI")
|
|
|
|
parser.add_argument("--debug", action="store_true",
|
|
|
|
help="Print debug output to stdout (implies --no-fork)",
|
|
|
|
default=False)
|
|
|
|
parser.add_argument("--no-fork", action="store_true",
|
|
|
|
help="Don't fork into background on startup")
|
|
|
|
|
|
|
|
parser.add_argument("--show-domain-creator", action="store_true",
|
|
|
|
help="Show 'New VM' wizard")
|
|
|
|
parser.add_argument("--show-domain-editor", metavar="NAME|ID|UUID",
|
|
|
|
help="Show domain details window")
|
|
|
|
parser.add_argument("--show-domain-performance", metavar="NAME|ID|UUID",
|
|
|
|
help="Show domain performance window")
|
|
|
|
parser.add_argument("--show-domain-console", metavar="NAME|ID|UUID",
|
|
|
|
help="Show domain graphical console window")
|
|
|
|
parser.add_argument("--show-domain-delete", metavar="NAME|ID|UUID",
|
|
|
|
help="Show domain delete window")
|
|
|
|
parser.add_argument("--show-host-summary", action="store_true",
|
|
|
|
help="Show connection details window")
|
|
|
|
|
|
|
|
return parser.parse_known_args()
|
|
|
|
|
|
|
|
|
|
|
|
class CLITestOptionsClass:
|
|
|
|
"""
|
|
|
|
Helper class for parsing and tracking --test-* options
|
|
|
|
"""
|
|
|
|
def __init__(self, test_options_str):
|
|
|
|
opts = []
|
|
|
|
if test_options_str:
|
|
|
|
opts = test_options_str.split(",")
|
|
|
|
|
|
|
|
def _get(optname):
|
|
|
|
if optname not in opts:
|
|
|
|
return False
|
|
|
|
opts.remove(optname)
|
|
|
|
return True
|
|
|
|
|
2020-08-22 01:34:10 +08:00
|
|
|
def _get_value(optname):
|
|
|
|
for opt in opts:
|
|
|
|
if opt.startswith(optname + "="):
|
|
|
|
opts.remove(opt)
|
|
|
|
return opt.split("=", 1)[1]
|
|
|
|
|
2020-01-27 06:12:09 +08:00
|
|
|
self.first_run = _get("first-run")
|
|
|
|
self.leak_debug = _get("leak-debug")
|
|
|
|
self.no_events = _get("no-events")
|
|
|
|
self.xmleditor_enabled = _get("xmleditor-enabled")
|
2020-08-22 01:34:10 +08:00
|
|
|
self.gsettings_keyfile = _get_value("gsettings-keyfile")
|
2020-08-24 02:58:01 +08:00
|
|
|
self.break_setfacl = _get("break-setfacl")
|
2020-01-27 06:12:09 +08:00
|
|
|
|
|
|
|
if opts:
|
|
|
|
print("Unknown --test-options keys: %s" % opts)
|
|
|
|
sys.exit(1)
|
|
|
|
|
2020-08-22 01:34:10 +08:00
|
|
|
if self.first_run and not self.gsettings_keyfile:
|
|
|
|
import atexit
|
|
|
|
import tempfile
|
|
|
|
filename = tempfile.mktemp(prefix="virtmanager-firstrun-keyfile")
|
|
|
|
self.gsettings_keyfile = filename
|
|
|
|
atexit.register(lambda: os.unlink(filename))
|
|
|
|
|
2020-01-27 06:12:09 +08:00
|
|
|
|
|
|
|
def main():
|
|
|
|
(options, leftovers) = parse_commandline()
|
|
|
|
|
|
|
|
cli.setupLogging("virt-manager", options.debug, False, False)
|
|
|
|
|
|
|
|
log.debug("virt-manager version: %s", BuildConfig.version)
|
|
|
|
log.debug("virtManager import: %s", os.path.dirname(__file__))
|
|
|
|
|
|
|
|
if BuildConfig.running_from_srcdir:
|
|
|
|
_setup_gsettings_path(BuildConfig.gsettings_dir)
|
|
|
|
|
|
|
|
if options.trace_libvirt:
|
|
|
|
log.debug("Libvirt tracing requested")
|
|
|
|
from .lib import module_trace
|
|
|
|
import libvirt
|
|
|
|
module_trace.wrap_module(libvirt,
|
|
|
|
mainloop=(options.trace_libvirt == "mainloop"),
|
|
|
|
regex=None)
|
|
|
|
|
|
|
|
CLITestOptions = CLITestOptionsClass(options.test_options)
|
|
|
|
if options.test_first_run:
|
|
|
|
CLITestOptions.first_run = True
|
|
|
|
if options.test_leak_debug:
|
|
|
|
CLITestOptions.leak_debug = True
|
|
|
|
if options.test_no_events:
|
|
|
|
CLITestOptions.no_events = True
|
|
|
|
|
2020-08-24 02:58:01 +08:00
|
|
|
if CLITestOptions.break_setfacl:
|
|
|
|
import virtinst.diskbackend
|
|
|
|
def fake_search(*args, **kwargs):
|
|
|
|
raise RuntimeError("Fake search fix fail from test suite")
|
|
|
|
virtinst.diskbackend.SETFACL = "getfacl"
|
|
|
|
# pylint: disable=protected-access
|
|
|
|
virtinst.diskbackend._fix_perms_chmod = fake_search
|
|
|
|
|
2020-01-27 06:12:09 +08:00
|
|
|
# With F27 gnome+wayland we need to set these before GTK import
|
|
|
|
os.environ["GSETTINGS_SCHEMA_DIR"] = BuildConfig.gsettings_dir
|
|
|
|
if CLITestOptions.first_run:
|
|
|
|
os.environ["GSETTINGS_BACKEND"] = "memory"
|
|
|
|
|
|
|
|
# Now we've got basic environment up & running we can fork
|
|
|
|
do_drop_stdio = False
|
|
|
|
if not options.no_fork and not options.debug:
|
|
|
|
drop_tty()
|
|
|
|
do_drop_stdio = True
|
|
|
|
|
|
|
|
# Ignore SIGHUP, otherwise a serial console closing drops the whole app
|
|
|
|
signal.signal(signal.SIGHUP, signal.SIG_IGN)
|
|
|
|
|
|
|
|
leftovers = _import_gtk(leftovers)
|
|
|
|
Gtk = globals()["Gtk"]
|
|
|
|
|
|
|
|
# Do this after the Gtk import so the user has a chance of seeing any error
|
|
|
|
if do_drop_stdio:
|
|
|
|
drop_stdio()
|
|
|
|
|
|
|
|
if leftovers:
|
|
|
|
raise RuntimeError("Unhandled command line options '%s'" % leftovers)
|
|
|
|
|
|
|
|
log.debug("PyGObject version: %d.%d.%d",
|
|
|
|
gi.version_info[0],
|
|
|
|
gi.version_info[1],
|
|
|
|
gi.version_info[2])
|
|
|
|
log.debug("GTK version: %d.%d.%d",
|
|
|
|
Gtk.get_major_version(),
|
|
|
|
Gtk.get_minor_version(),
|
|
|
|
Gtk.get_micro_version())
|
|
|
|
|
|
|
|
if not VirtinstConnection.libvirt_new_enough_for_virtmanager(6000):
|
|
|
|
# We need this version for threaded virConnect access
|
|
|
|
_show_startup_error(
|
|
|
|
_("virt-manager requires libvirt 0.6.0 or later."), "")
|
|
|
|
return
|
|
|
|
|
|
|
|
# Prime the vmmConfig cache
|
|
|
|
from . import config
|
|
|
|
config.vmmConfig.get_instance(BuildConfig, CLITestOptions)
|
|
|
|
|
|
|
|
# Add our icon dir to icon theme
|
|
|
|
icon_theme = Gtk.IconTheme.get_default()
|
|
|
|
icon_theme.prepend_search_path(BuildConfig.icon_dir)
|
|
|
|
|
|
|
|
from .engine import vmmEngine
|
|
|
|
Gtk.Window.set_default_icon_name("virt-manager")
|
|
|
|
|
|
|
|
show_window = None
|
|
|
|
domain = None
|
|
|
|
if options.show_domain_creator:
|
|
|
|
show_window = vmmEngine.CLI_SHOW_DOMAIN_CREATOR
|
|
|
|
elif options.show_host_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
|
|
|
|
elif options.show_domain_delete:
|
|
|
|
show_window = vmmEngine.CLI_SHOW_DOMAIN_DELETE
|
|
|
|
domain = options.show_domain_delete
|
|
|
|
|
|
|
|
if show_window and options.uri is None:
|
|
|
|
raise RuntimeError("can't use --show-* options without --connect")
|
|
|
|
|
|
|
|
skip_autostart = False
|
|
|
|
if show_window:
|
|
|
|
skip_autostart = True
|
|
|
|
|
|
|
|
# Hook libvirt events into glib main loop
|
|
|
|
LibvirtGLib.init(None)
|
|
|
|
LibvirtGLib.event_register()
|
|
|
|
|
|
|
|
engine = vmmEngine.get_instance()
|
|
|
|
|
|
|
|
# Actually exit when we receive ctrl-c
|
|
|
|
from gi.repository import GLib
|
|
|
|
def _sigint_handler(user_data):
|
|
|
|
ignore = user_data
|
|
|
|
log.debug("Received KeyboardInterrupt. Exiting application.")
|
|
|
|
engine.exit_app()
|
|
|
|
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT,
|
|
|
|
_sigint_handler, None)
|
|
|
|
|
|
|
|
engine.start(options.uri, show_window, domain, skip_autostart)
|
|
|
|
|
|
|
|
|
|
|
|
def runcli():
|
|
|
|
try:
|
|
|
|
main()
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
log.debug("Received KeyboardInterrupt. Exiting application.")
|
|
|
|
except Exception as run_e:
|
|
|
|
if "Gtk" not in globals():
|
|
|
|
raise
|
|
|
|
_show_startup_error(str(run_e), "".join(traceback.format_exc()))
|