qubes: Allow for explicit closing of objects
This commit is contained in:
parent
8547d06cc9
commit
de8ff20976
@ -689,6 +689,21 @@ class PropertyHolder(qubes.events.Emitter):
|
|||||||
# pylint: disable=no-member
|
# pylint: disable=no-member
|
||||||
self.log.fatal(msg)
|
self.log.fatal(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
super().close()
|
||||||
|
|
||||||
|
# Remove all properties -- somewhere in them there are cyclic
|
||||||
|
# references. This just removes all the properties, just in case.
|
||||||
|
# They are removed directly, bypassing write_once.
|
||||||
|
for prop in self.property_list():
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
try:
|
||||||
|
delattr(self, prop._attr_name)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=wrong-import-position
|
# pylint: disable=wrong-import-position
|
||||||
from qubes.vm import VMProperty
|
from qubes.vm import VMProperty
|
||||||
from qubes.app import Qubes
|
from qubes.app import Qubes
|
||||||
|
121
qubes/app.py
121
qubes/app.py
@ -32,6 +32,7 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
|
import traceback
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import lxml.etree
|
import lxml.etree
|
||||||
@ -138,20 +139,22 @@ class VirConnectWrapper(object):
|
|||||||
class VMMConnection(object):
|
class VMMConnection(object):
|
||||||
'''Connection to Virtual Machine Manager (libvirt)'''
|
'''Connection to Virtual Machine Manager (libvirt)'''
|
||||||
|
|
||||||
def __init__(self, offline_mode=None):
|
def __init__(self, app, offline_mode=None):
|
||||||
'''
|
'''
|
||||||
|
|
||||||
:param offline_mode: enable/disable offline mode; default is to
|
:param offline_mode: enable/disable offline mode; default is to
|
||||||
enable when running in chroot as root, otherwise disable
|
enable when running in chroot as root, otherwise disable
|
||||||
'''
|
'''
|
||||||
self._libvirt_conn = None
|
self.app = app
|
||||||
self._xs = None
|
|
||||||
self._xc = None
|
|
||||||
if offline_mode is None:
|
if offline_mode is None:
|
||||||
offline_mode = bool(os.getuid() == 0 and
|
offline_mode = bool(os.getuid() == 0 and
|
||||||
os.stat('/') != os.stat('/proc/1/root/.'))
|
os.stat('/') != os.stat('/proc/1/root/.'))
|
||||||
self._offline_mode = offline_mode
|
self._offline_mode = offline_mode
|
||||||
|
|
||||||
|
self._libvirt_conn = None
|
||||||
|
self._xs = None
|
||||||
|
self._xc = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def offline_mode(self):
|
def offline_mode(self):
|
||||||
'''Check or enable offline mode (do not actually connect to vmm)'''
|
'''Check or enable offline mode (do not actually connect to vmm)'''
|
||||||
@ -218,36 +221,15 @@ class VMMConnection(object):
|
|||||||
self.init_vmm_connection()
|
self.init_vmm_connection()
|
||||||
return self._xc
|
return self._xc
|
||||||
|
|
||||||
def register_event_handlers(self, app):
|
def close(self):
|
||||||
'''Register libvirt event handlers, which will translate libvirt
|
libvirt.registerErrorHandler(None, None)
|
||||||
events into qubes.events. This function should be called only in
|
if self._xs:
|
||||||
'qubesd' process and only when mainloop has been already set.
|
self._xs.close()
|
||||||
'''
|
self._xs = None
|
||||||
self.libvirt_conn.domainEventRegisterAny(
|
|
||||||
None, # any domain
|
|
||||||
libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE,
|
|
||||||
self._domain_event_callback,
|
|
||||||
app
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _domain_event_callback(_conn, domain, event, _detail, opaque):
|
|
||||||
'''Generic libvirt event handler (virConnectDomainEventCallback),
|
|
||||||
translate libvirt event into qubes.events.
|
|
||||||
'''
|
|
||||||
app = opaque
|
|
||||||
try:
|
|
||||||
vm = app.domains[domain.name()]
|
|
||||||
except KeyError:
|
|
||||||
# ignore events for unknown domains
|
|
||||||
return
|
|
||||||
|
|
||||||
if event == libvirt.VIR_DOMAIN_EVENT_STOPPED:
|
|
||||||
vm.fire_event('domain-shutdown')
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
if self._libvirt_conn:
|
if self._libvirt_conn:
|
||||||
self._libvirt_conn.close()
|
self._libvirt_conn.close()
|
||||||
|
self._libvirt_conn = None
|
||||||
|
self._xc = None # and pray it will get garbage-collected
|
||||||
|
|
||||||
|
|
||||||
class QubesHost(object):
|
class QubesHost(object):
|
||||||
@ -398,6 +380,12 @@ class VMCollection(object):
|
|||||||
self._dict = dict()
|
self._dict = dict()
|
||||||
|
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
del self.app
|
||||||
|
self._dict.clear()
|
||||||
|
del self._dict
|
||||||
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<{} {!r}>'.format(
|
return '<{} {!r}>'.format(
|
||||||
self.__class__.__name__, list(sorted(self.keys())))
|
self.__class__.__name__, list(sorted(self.keys())))
|
||||||
@ -729,6 +717,10 @@ class Qubes(qubes.PropertyHolder):
|
|||||||
**kwargs):
|
**kwargs):
|
||||||
#: logger instance for logging global messages
|
#: logger instance for logging global messages
|
||||||
self.log = logging.getLogger('app')
|
self.log = logging.getLogger('app')
|
||||||
|
self.log.debug('init() -> %#x', id(self))
|
||||||
|
self.log.debug('stack:')
|
||||||
|
for frame in traceback.extract_stack():
|
||||||
|
self.log.debug('%s', frame)
|
||||||
|
|
||||||
self._extensions = qubes.ext.get_extensions()
|
self._extensions = qubes.ext.get_extensions()
|
||||||
|
|
||||||
@ -742,7 +734,7 @@ class Qubes(qubes.PropertyHolder):
|
|||||||
self.pools = {}
|
self.pools = {}
|
||||||
|
|
||||||
#: Connection to VMM
|
#: Connection to VMM
|
||||||
self.vmm = VMMConnection(offline_mode=offline_mode)
|
self.vmm = VMMConnection(self, offline_mode=offline_mode)
|
||||||
|
|
||||||
#: Information about host system
|
#: Information about host system
|
||||||
self.host = QubesHost(self)
|
self.host = QubesHost(self)
|
||||||
@ -759,6 +751,7 @@ class Qubes(qubes.PropertyHolder):
|
|||||||
|
|
||||||
self.__load_timestamp = None
|
self.__load_timestamp = None
|
||||||
self.__locked_fh = None
|
self.__locked_fh = None
|
||||||
|
self._domain_event_callback_id = None
|
||||||
|
|
||||||
#: jinja2 environment for libvirt XML templates
|
#: jinja2 environment for libvirt XML templates
|
||||||
self.env = jinja2.Environment(
|
self.env = jinja2.Environment(
|
||||||
@ -910,6 +903,40 @@ class Qubes(qubes.PropertyHolder):
|
|||||||
self._release_lock()
|
self._release_lock()
|
||||||
|
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
'''Deconstruct the object and break circular references
|
||||||
|
|
||||||
|
After calling this the object is unusable, not even for saving.'''
|
||||||
|
|
||||||
|
self.log.debug('close() <- %#x', id(self))
|
||||||
|
for frame in traceback.extract_stack():
|
||||||
|
self.log.debug('%s', frame)
|
||||||
|
|
||||||
|
super().close()
|
||||||
|
|
||||||
|
if self._domain_event_callback_id is not None:
|
||||||
|
self.vmm.libvirt_conn.domainEventDeregisterAny(
|
||||||
|
self._domain_event_callback_id)
|
||||||
|
self._domain_event_callback_id = None
|
||||||
|
|
||||||
|
# Only our Lord, The God Almighty, knows what references
|
||||||
|
# are kept in extensions.
|
||||||
|
del self._extensions
|
||||||
|
|
||||||
|
for vm in self.domains:
|
||||||
|
vm.close()
|
||||||
|
self.domains.close()
|
||||||
|
del self.domains
|
||||||
|
|
||||||
|
self.vmm.close()
|
||||||
|
del self.vmm
|
||||||
|
|
||||||
|
del self.host
|
||||||
|
|
||||||
|
if self.__locked_fh:
|
||||||
|
self._release_lock()
|
||||||
|
|
||||||
|
|
||||||
def _acquire_lock(self, for_save=False):
|
def _acquire_lock(self, for_save=False):
|
||||||
assert self.__locked_fh is None, 'double lock'
|
assert self.__locked_fh is None, 'double lock'
|
||||||
|
|
||||||
@ -1129,6 +1156,34 @@ class Qubes(qubes.PropertyHolder):
|
|||||||
raise qubes.exc.QubesException('No driver %s for pool %s' %
|
raise qubes.exc.QubesException('No driver %s for pool %s' %
|
||||||
(driver, name))
|
(driver, name))
|
||||||
|
|
||||||
|
def register_event_handlers(self):
|
||||||
|
'''Register libvirt event handlers, which will translate libvirt
|
||||||
|
events into qubes.events. This function should be called only in
|
||||||
|
'qubesd' process and only when mainloop has been already set.
|
||||||
|
'''
|
||||||
|
self._domain_event_callback_id = (
|
||||||
|
self.vmm.libvirt_conn.domainEventRegisterAny(
|
||||||
|
None, # any domain
|
||||||
|
libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE,
|
||||||
|
self._domain_event_callback,
|
||||||
|
None))
|
||||||
|
|
||||||
|
def _domain_event_callback(self, _conn, domain, event, _detail, _opaque):
|
||||||
|
'''Generic libvirt event handler (virConnectDomainEventCallback),
|
||||||
|
translate libvirt event into qubes.events.
|
||||||
|
'''
|
||||||
|
if not self.events_enabled:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
vm = self.domains[domain.name()]
|
||||||
|
except KeyError:
|
||||||
|
# ignore events for unknown domains
|
||||||
|
return
|
||||||
|
|
||||||
|
if event == libvirt.VIR_DOMAIN_EVENT_STOPPED:
|
||||||
|
vm.fire_event('domain-shutdown')
|
||||||
|
|
||||||
@qubes.events.handler('domain-pre-delete')
|
@qubes.events.handler('domain-pre-delete')
|
||||||
def on_domain_pre_deleted(self, event, vm):
|
def on_domain_pre_deleted(self, event, vm):
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
|
@ -106,6 +106,8 @@ class Emitter(object, metaclass=EmitterMeta):
|
|||||||
self.events_enabled = False
|
self.events_enabled = False
|
||||||
self.__handlers__ = collections.defaultdict(set)
|
self.__handlers__ = collections.defaultdict(set)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.events_enabled = False
|
||||||
|
|
||||||
def add_handler(self, event, func):
|
def add_handler(self, event, func):
|
||||||
'''Add event handler to subject's class.
|
'''Add event handler to subject's class.
|
||||||
|
@ -35,7 +35,7 @@ def main(args=None):
|
|||||||
loop.close()
|
loop.close()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
args.app.vmm.register_event_handlers(args.app)
|
args.app.register_event_handlers()
|
||||||
|
|
||||||
if args.debug:
|
if args.debug:
|
||||||
qubes.log.enable_debug()
|
qubes.log.enable_debug()
|
||||||
|
@ -297,6 +297,22 @@ class BaseVM(qubes.PropertyHolder):
|
|||||||
if hasattr(self, 'name'):
|
if hasattr(self, 'name'):
|
||||||
self.init_log()
|
self.init_log()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
super().close()
|
||||||
|
|
||||||
|
if self._qdb_connection_watch is not None:
|
||||||
|
asyncio.get_event_loop().remove_reader(
|
||||||
|
self._qdb_connection_watch.watch_fd())
|
||||||
|
self._qdb_connection_watch.close()
|
||||||
|
del self._qdb_connection_watch
|
||||||
|
|
||||||
|
del self.app
|
||||||
|
del self.features
|
||||||
|
del self.storage
|
||||||
|
# TODO storage may have circ references, but it doesn't leak fds
|
||||||
|
del self.devices
|
||||||
|
del self.tags
|
||||||
|
|
||||||
def load_extras(self):
|
def load_extras(self):
|
||||||
# features
|
# features
|
||||||
for node in self.xml.xpath('./features/feature'):
|
for node in self.xml.xpath('./features/feature'):
|
||||||
|
Loading…
Reference in New Issue
Block a user