connection: Unify initial object refresh handling

Have libvirtobjects advertise a routine specifically for initial setup,
and emit a signal when it's complete. Then dispatch the associated conn
signal on demand as the objects are initialized. This should avoid a
whole class of ordering issues, and is easier to follow IMO.
This commit is contained in:
Cole Robinson
2015-04-10 15:00:42 -04:00
parent 629627b663
commit d0ffd954cd
7 changed files with 147 additions and 105 deletions
+83 -71
View File
@@ -171,6 +171,9 @@ class vmmConnection(vmmGObject):
self._backend = virtinst.VirtualConnection(self._uri) self._backend = virtinst.VirtualConnection(self._uri)
self._closing = False self._closing = False
self._init_object_count = None
self._init_object_event = None
self._network_capable = None self._network_capable = None
self._storage_capable = None self._storage_capable = None
self._interface_capable = None self._interface_capable = None
@@ -877,6 +880,9 @@ class vmmConnection(vmmGObject):
self._backend.close() self._backend.close()
self.record = [] self.record = []
if self._init_object_event:
self._init_object_event.clear()
self._objects.cleanup() self._objects.cleanup()
self._objects = _ObjectList() self._objects = _ObjectList()
@@ -962,20 +968,31 @@ class vmmConnection(vmmGObject):
logging.debug("Connection doesn't support KeepAlive, " logging.debug("Connection doesn't support KeepAlive, "
"skipping") "skipping")
# The initial tick will set up a threading event that will only
# trigger after all the polled libvirt objects are fully initialized.
# That way we only report the connection is open when everything is
# nicely setup for the rest of the app.
self._init_object_event = threading.Event()
self._init_object_count = 0
self.schedule_priority_tick(stats_update=True,
pollvm=True, pollnet=True,
pollpool=True, polliface=True,
pollnodedev=True, force=True, initial_poll=True)
self._init_object_event.wait()
self._init_object_event = None
self._init_object_count = None
def _open_thread(self): def _open_thread(self):
try: try:
is_active, connectError = self._do_open() is_active, connectError = self._do_open()
if is_active: if is_active:
self._populate_initial_state() self._populate_initial_state()
else:
self.idle_add(self._change_state, self._STATE_DISCONNECTED)
if is_active: self.idle_add(self._change_state, is_active and
self.schedule_priority_tick(stats_update=True, self._STATE_ACTIVE or self._STATE_DISCONNECTED)
pollvm=True, pollnet=True,
pollpool=True, polliface=True,
pollnodedev=True,
force=True)
except Exception, e: except Exception, e:
is_active = False is_active = False
self._schedule_close() self._schedule_close()
@@ -990,22 +1007,23 @@ class vmmConnection(vmmGObject):
# Tick/Update methods # # Tick/Update methods #
####################### #######################
def _send_object_signals(self, new_objects, gone_objects, def _gone_object_signals(self, gone_objects):
finish_connecting):
""" """
Responsible for signaling the UI for any updates. All possible UI Responsible for signaling the UI for any updates. All possible UI
updates need to go here to enable threading that doesn't block the updates need to go here to enable threading that doesn't block the
app with long tick operations. app with long tick operations.
""" """
# Connection closed out from under us
if not self._backend.is_open(): if not self._backend.is_open():
return return
for obj in gone_objects: for obj in gone_objects:
class_name = obj.class_name()
if not self._objects.remove(obj): if not self._objects.remove(obj):
logging.debug("Requested removal of %s=%s, but it's "
"not in our object list.", class_name, obj.get_name())
continue continue
class_name = obj.class_name()
logging.debug("%s=%s removed", class_name, obj.get_name()) logging.debug("%s=%s removed", class_name, obj.get_name())
if class_name == "domain": if class_name == "domain":
self.emit("vm-removed", obj.get_connkey()) self.emit("vm-removed", obj.get_connkey())
@@ -1019,11 +1037,18 @@ class vmmConnection(vmmGObject):
self.emit("nodedev-removed", obj.get_connkey()) self.emit("nodedev-removed", obj.get_connkey())
obj.cleanup() obj.cleanup()
for obj in new_objects: def _new_object_cb(self, obj):
if not self._objects.add(obj): if not self._backend.is_open():
continue return
try:
class_name = obj.class_name() class_name = obj.class_name()
if not self._objects.add(obj):
logging.debug("New %s=%s requested, but it's already tracked.",
class_name, obj.get_name())
return
if class_name != "nodedev": if class_name != "nodedev":
# Skip nodedev logging since it's noisy and not interesting # Skip nodedev logging since it's noisy and not interesting
logging.debug("%s=%s status=%s added", class_name, logging.debug("%s=%s status=%s added", class_name,
@@ -1038,34 +1063,11 @@ class vmmConnection(vmmGObject):
self.emit("interface-added", obj.get_connkey()) self.emit("interface-added", obj.get_connkey())
elif class_name == "nodedev": elif class_name == "nodedev":
self.emit("nodedev-added", obj.get_connkey()) self.emit("nodedev-added", obj.get_connkey())
finally:
if finish_connecting: if self._init_object_event:
self._change_state(self._STATE_ACTIVE) self._init_object_count -= 1
if self._init_object_count <= 0:
def _refresh_new_objects(self, newlist): self._init_object_event.set()
if not newlist:
return
def _refresh_generic():
for obj in newlist:
obj.refresh_xml()
def _refresh_pool():
for pool in newlist:
pool.refresh()
def _refresh_volumes(p):
for vol in p.get_volumes():
vol.refresh_xml()
self._start_thread(_refresh_volumes,
"pool=%s refreshing xml for volumes" % pool.get_name(),
(pool,))
cb = _refresh_generic
if hasattr(newlist[0], "get_volumes"):
cb = _refresh_pool
self._start_thread(cb,
"refreshing xml for new %s" % newlist[0].__class__)
def _update_nets(self, dopoll): def _update_nets(self, dopoll):
keymap = dict((o.get_connkey(), o) for o in self.list_nets()) keymap = dict((o.get_connkey(), o) for o in self.list_nets())
@@ -1102,60 +1104,71 @@ class vmmConnection(vmmGObject):
return pollhelpers.fetch_vms(self._backend, keymap, return pollhelpers.fetch_vms(self._backend, keymap,
(lambda obj, key: vmmDomain(self, obj, key))) (lambda obj, key: vmmDomain(self, obj, key)))
def _poll(self, pollvm, pollnet, pollpool, polliface, pollnodedev): def _poll(self, initial_poll,
pollvm, pollnet, pollpool, polliface, pollnodedev):
""" """
Helper called from tick() to do necessary polling and return Helper called from tick() to do necessary polling and return
the relevant object lists the relevant object lists
""" """
gone_objects = [] gone_objects = []
new_objects = []
preexisting_objects = [] preexisting_objects = []
def _process_objects(polloutput): def _process_objects(polloutput):
gone, new, master = polloutput gone, new, master = polloutput
if initial_poll:
self._init_object_count += len(new)
gone_objects.extend(gone) gone_objects.extend(gone)
new_objects.extend(new)
preexisting_objects.extend([o for o in master if o not in new]) preexisting_objects.extend([o for o in master if o not in new])
return new return new
self._refresh_new_objects(_process_objects( new_vms = _process_objects(self._update_vms(pollvm))
self._update_nets(pollnet))) new_nets = _process_objects(self._update_nets(pollnet))
self._refresh_new_objects(_process_objects( new_pools = _process_objects(self._update_pools(pollpool))
self._update_pools(pollpool))) new_ifaces = _process_objects(self._update_interfaces(polliface))
self._refresh_new_objects(_process_objects( new_nodedevs = _process_objects(self._update_nodedevs(pollnodedev))
self._update_interfaces(polliface)))
self._refresh_new_objects(_process_objects(
self._update_nodedevs(pollnodedev)))
# These are refreshing in their __init__ method, because the # Kick off one thread per object type to handle the initial
# data is wanted immediately # XML fetching. Going any more fine grained then this probably
_process_objects(self._update_vms(pollvm)) # won't be that useful due to libvirt's locking structure.
#
# Would prefer to start refreshing some objects before all polling
# is complete, but we need init_object_count to be fully accurate
# before we start initializing objects
for newlist in [new_vms, new_nets, new_pools,
new_ifaces, new_nodedevs]:
if not newlist:
continue
return gone_objects, new_objects, preexisting_objects def cb(lst):
for obj in lst:
obj.connect_once("initialized", self._new_object_cb)
obj.init_libvirt_state()
self._start_thread(cb,
"refreshing xml for new %s" % newlist[0].class_name(),
args=(newlist,))
return gone_objects, preexisting_objects
def _tick(self, stats_update, def _tick(self, stats_update,
pollvm=False, pollnet=False, pollvm=False, pollnet=False,
pollpool=False, polliface=False, pollpool=False, polliface=False,
pollnodedev=False, pollnodedev=False,
force=False): force=False, initial_poll=False):
""" """
main update function: polls for new objects, updates stats, ... main update function: polls for new objects, updates stats, ...
:param force: Perform the requested polling even if async events :param force: Perform the requested polling even if async events
are in use are in use.
""" """
finish_connecting = False
if self._closing: if self._closing:
return return
if self.is_disconnected(): if self.is_disconnected():
return return
if self.is_connecting(): if self.is_connecting() and not force:
# If in 'connecting' state, and force requested, this means
# we are performing the initial poll.
if not force:
return return
finish_connecting = True
# We need to set this before the event check, since stats polling # We need to set this before the event check, since stats polling
# is independent of events # is independent of events
@@ -1169,10 +1182,9 @@ class vmmConnection(vmmGObject):
self.hostinfo = self._backend.getInfo() self.hostinfo = self._backend.getInfo()
gone_objects, new_objects, preexisting_objects = self._poll( gone_objects, preexisting_objects = self._poll(
pollvm, pollnet, pollpool, polliface, pollnodedev) initial_poll, pollvm, pollnet, pollpool, polliface, pollnodedev)
self.idle_add(self._send_object_signals, self.idle_add(self._gone_object_signals, gone_objects)
new_objects, gone_objects, finish_connecting)
# Only tick() pre-existing objects, since new objects will be # Only tick() pre-existing objects, since new objects will be
# initialized asynchronously and tick() would be redundant # initialized asynchronously and tick() would be redundant
+11 -18
View File
@@ -170,8 +170,6 @@ class vmmDomainSnapshot(vmmLibvirtObject):
vmmLibvirtObject.__init__(self, conn, backend, backend.getName(), vmmLibvirtObject.__init__(self, conn, backend, backend.getName(),
DomainSnapshot) DomainSnapshot)
self.refresh_xml()
########################## ##########################
# Required class methods # # Required class methods #
@@ -192,6 +190,8 @@ class vmmDomainSnapshot(vmmLibvirtObject):
def tick(self, stats_update=True): def tick(self, stats_update=True):
ignore = stats_update ignore = stats_update
def _init_libvirt_state(self):
self.refresh_xml()
########### ###########
@@ -344,20 +344,12 @@ class vmmDomain(vmmLibvirtObject):
self.inspection = vmmInspectionData() self.inspection = vmmInspectionData()
if isinstance(self._backend, Guest):
return
self._libvirt_init()
def _cleanup(self): def _cleanup(self):
for snap in self._snapshot_list or []: for snap in self._snapshot_list or []:
snap.cleanup() snap.cleanup()
self._snapshot_list = None self._snapshot_list = None
def _libvirt_init(self): def _init_libvirt_state(self):
"""
Initialization to do if backed by a libvirt virDomain
"""
self.managedsave_supported = self.conn.check_support( self.managedsave_supported = self.conn.check_support(
self.conn.SUPPORT_DOMAIN_MANAGED_SAVE, self._backend) self.conn.SUPPORT_DOMAIN_MANAGED_SAVE, self._backend)
self.remote_console_supported = self.conn.check_support( self.remote_console_supported = self.conn.check_support(
@@ -374,16 +366,16 @@ class vmmDomain(vmmLibvirtObject):
(self._inactive_xml_flags, (self._inactive_xml_flags,
self._active_xml_flags) = self.conn.get_dom_flags(self._backend) self._active_xml_flags) = self.conn.get_dom_flags(self._backend)
# Prime caches
self.tick()
self.has_managed_save()
self.snapshots_supported()
self.toggle_sample_network_traffic() self.toggle_sample_network_traffic()
self.toggle_sample_disk_io() self.toggle_sample_disk_io()
self.toggle_sample_mem_stats() self.toggle_sample_mem_stats()
self.toggle_sample_cpu_stats() self.toggle_sample_cpu_stats()
# Prime caches
self.refresh_xml()
self.has_managed_save()
self.snapshots_supported()
# Hook up listeners that need to be cleaned up # Hook up listeners that need to be cleaned up
self.add_gsettings_handle( self.add_gsettings_handle(
self.config.on_stats_enable_cpu_poll_changed( self.config.on_stats_enable_cpu_poll_changed(
@@ -541,7 +533,6 @@ class vmmDomain(vmmLibvirtObject):
def _invalidate_xml(self): def _invalidate_xml(self):
vmmLibvirtObject._invalidate_xml(self) vmmLibvirtObject._invalidate_xml(self)
self._id = None self._id = None
self._has_managed_save = None
self._status_reason = None self._status_reason = None
self._has_managed_save = None self._has_managed_save = None
@@ -1092,7 +1083,9 @@ class vmmDomain(vmmLibvirtObject):
if self._snapshot_list is None: if self._snapshot_list is None:
newlist = [] newlist = []
for rawsnap in self._backend.listAllSnapshots(): for rawsnap in self._backend.listAllSnapshots():
newlist.append(vmmDomainSnapshot(self.conn, rawsnap)) obj = vmmDomainSnapshot(self.conn, rawsnap)
obj.init_libvirt_state()
newlist.append(obj)
self._snapshot_list = newlist self._snapshot_list = newlist
return self._snapshot_list[:] return self._snapshot_list[:]
+6 -3
View File
@@ -27,9 +27,6 @@ class vmmInterface(vmmLibvirtObject):
def __init__(self, conn, backend, key): def __init__(self, conn, backend, key):
vmmLibvirtObject.__init__(self, conn, backend, key, Interface) vmmLibvirtObject.__init__(self, conn, backend, key, Interface)
(self._inactive_xml_flags,
self._active_xml_flags) = self.conn.get_interface_flags(self._backend)
########################## ##########################
# Required class methods # # Required class methods #
@@ -55,6 +52,12 @@ class vmmInterface(vmmLibvirtObject):
ignore = stats_update ignore = stats_update
self._refresh_status() self._refresh_status()
def _init_libvirt_state(self):
(self._inactive_xml_flags,
self._active_xml_flags) = self.conn.get_interface_flags(self._backend)
self.tick()
##################### #####################
# Object operations # # Object operations #
+22 -9
View File
@@ -28,8 +28,7 @@ from .baseclass import vmmGObject
class vmmLibvirtObject(vmmGObject): class vmmLibvirtObject(vmmGObject):
__gsignals__ = { __gsignals__ = {
"state-changed": (GObject.SignalFlags.RUN_FIRST, None, []), "state-changed": (GObject.SignalFlags.RUN_FIRST, None, []),
"started": (GObject.SignalFlags.RUN_FIRST, None, []), "initialized": (GObject.SignalFlags.RUN_FIRST, None, []),
"stopped": (GObject.SignalFlags.RUN_FIRST, None, []),
} }
_STATUS_ACTIVE = 1 _STATUS_ACTIVE = 1
@@ -42,7 +41,7 @@ class vmmLibvirtObject(vmmGObject):
self._key = key self._key = key
self._parseclass = parseclass self._parseclass = parseclass
self._initial_status_update = False self._initialized = False
self.__status = self._STATUS_ACTIVE self.__status = self._STATUS_ACTIVE
self._support_isactive = None self._support_isactive = None
@@ -54,12 +53,11 @@ class vmmLibvirtObject(vmmGObject):
self._inactive_xml_flags = 0 self._inactive_xml_flags = 0
self._active_xml_flags = 0 self._active_xml_flags = 0
# Cache object name # Cache object name. We may need to do this even
# before init_libvirt_state since it might be needed ahead of time.
self._name = None self._name = None
self.get_name() self.get_name()
self._refresh_status()
@staticmethod @staticmethod
def log_redefine_xml_diff(obj, origxml, newxml): def log_redefine_xml_diff(obj, origxml, newxml):
objname = "<%s name=%s>" % (obj.__class__.__name__, obj.get_name()) objname = "<%s name=%s>" % (obj.__class__.__name__, obj.get_name())
@@ -177,6 +175,23 @@ class vmmLibvirtObject(vmmGObject):
def tick(self, stats_update=True): def tick(self, stats_update=True):
raise NotImplementedError() raise NotImplementedError()
def _init_libvirt_state(self):
raise NotImplementedError()
def init_libvirt_state(self):
"""
Function called by vmmConnection to populate initial state when
a new object appears.
"""
if self._initialized:
return
try:
self._init_libvirt_state()
finally:
self._initialized = True
self.idle_emit("initialized")
################### ###################
# Status handling # # Status handling #
@@ -215,7 +230,7 @@ class vmmLibvirtObject(vmmGObject):
""" """
if (self._using_events() and if (self._using_events() and
skip_if_have_events and skip_if_have_events and
self._initial_status_update): self._initialized):
return return
try: try:
@@ -237,8 +252,6 @@ class vmmLibvirtObject(vmmGObject):
kwargs = {"force": True, poll_param: True} kwargs = {"force": True, poll_param: True}
logging.debug("Scheduling priority tick with: %s", kwargs) logging.debug("Scheduling priority tick with: %s", kwargs)
self.conn.schedule_priority_tick(**kwargs) self.conn.schedule_priority_tick(**kwargs)
finally:
self._initial_status_update = True
def _backend_get_active(self): def _backend_get_active(self):
if self._support_isactive is None: if self._support_isactive is None:
+3
View File
@@ -68,6 +68,9 @@ class vmmNetwork(vmmLibvirtObject):
ignore = stats_update ignore = stats_update
self._refresh_status() self._refresh_status()
def _init_libvirt_state(self):
self.tick()
########### ###########
# Actions # # Actions #
+5 -1
View File
@@ -30,7 +30,6 @@ def _parse_convert(conn, parsexml=None):
class vmmNodeDevice(vmmLibvirtObject): class vmmNodeDevice(vmmLibvirtObject):
def __init__(self, conn, backend, key): def __init__(self, conn, backend, key):
vmmLibvirtObject.__init__(self, conn, backend, key, _parse_convert) vmmLibvirtObject.__init__(self, conn, backend, key, _parse_convert)
self._name = key
def _conn_tick_poll_param(self): def _conn_tick_poll_param(self):
return "pollnodedev" return "pollnodedev"
@@ -41,8 +40,13 @@ class vmmNodeDevice(vmmLibvirtObject):
return self._backend.XMLDesc(flags) return self._backend.XMLDesc(flags)
def _get_backend_status(self): def _get_backend_status(self):
return self._STATUS_ACTIVE return self._STATUS_ACTIVE
def _backend_get_name(self):
return self.get_connkey()
def is_active(self): def is_active(self):
return True return True
def tick(self, stats_update=True): def tick(self, stats_update=True):
# Deliberately empty
ignore = stats_update ignore = stats_update
def _init_libvirt_state(self):
self.tick()
+15 -1
View File
@@ -56,7 +56,10 @@ class vmmStorageVolume(vmmLibvirtObject):
return self._STATUS_ACTIVE return self._STATUS_ACTIVE
def tick(self, stats_update=True): def tick(self, stats_update=True):
# Deliberately empty
ignore = stats_update ignore = stats_update
def _init_libvirt_state(self):
self.refresh_xml()
########### ###########
@@ -141,6 +144,12 @@ class vmmStoragePool(vmmLibvirtObject):
ignore = stats_update ignore = stats_update
self._refresh_status() self._refresh_status()
def _init_libvirt_state(self):
self.tick()
self.refresh(skip_xml_refresh=True)
for vol in self.get_volumes():
vol.init_libvirt_state()
########### ###########
# Actions # # Actions #
@@ -160,11 +169,16 @@ class vmmStoragePool(vmmLibvirtObject):
self._backend.undefine() self._backend.undefine()
self._backend = None self._backend = None
def refresh(self): def refresh(self, skip_xml_refresh=False):
"""
:param skip_xml_refresh: Only used by init_libvirt_state to avoid
double XML updating
"""
if not self.is_active(): if not self.is_active():
return return
self._backend.refresh(0) self._backend.refresh(0)
if skip_xml_refresh:
self.refresh_xml() self.refresh_xml()
self._update_volumes() self._update_volumes()
self.idle_emit("refreshed") self.idle_emit("refreshed")