events: add qubesd reconnection support

If connection is interrupted (for example qubesd restart), attempt to
reconnect.
This commit is contained in:
Marek Marczykowski-Górecki 2017-04-15 20:12:42 +02:00
parent 6dd7c69b3f
commit ef683485e2
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
4 changed files with 64 additions and 32 deletions

View File

@ -24,3 +24,4 @@
QUBESD_SOCKET = '/var/run/qubesd.sock' QUBESD_SOCKET = '/var/run/qubesd.sock'
QREXEC_CLIENT = '/usr/lib/qubes/qrexec-client' QREXEC_CLIENT = '/usr/lib/qubes/qrexec-client'
QREXEC_CLIENT_VM = '/usr/bin/qrexec-client-vm' QREXEC_CLIENT_VM = '/usr/bin/qrexec-client-vm'
QUBESD_RECONNECT_DELAY = 1.0

View File

@ -102,7 +102,7 @@ class EventsDispatcher(object):
return reader, cleanup_func return reader, cleanup_func
@asyncio.coroutine @asyncio.coroutine
def listen_for_events(self, vm=None): def listen_for_events(self, vm=None, reconnect=True):
''' '''
Listen for events and call appropriate handlers. Listen for events and call appropriate handlers.
This function do not exit until manually terminated. This function do not exit until manually terminated.
@ -111,14 +111,40 @@ class EventsDispatcher(object):
:param vm: Listen for events only for this VM, use None to listen for :param vm: Listen for events only for this VM, use None to listen for
events about all VMs and not related to any particular VM. events about all VMs and not related to any particular VM.
:return: None :param reconnect: should reconnect to qubesd if connection is
interrupted?
:rtype: None
'''
while True:
try:
yield from self._listen_for_events(vm)
except ConnectionRefusedError:
pass
if not reconnect:
break
self.app.log.warning(
'Connection to qubesd terminated, reconnecting in {} '
'seconds'.format(qubesmgmt.config.QUBESD_RECONNECT_DELAY))
# avoid busy-loop if qubesd is dead
yield from asyncio.sleep(qubesmgmt.config.QUBESD_RECONNECT_DELAY)
@asyncio.coroutine
def _listen_for_events(self, vm=None):
'''
Listen for events and call appropriate handlers.
This function do not exit until manually terminated.
This is coroutine.
:param vm: Listen for events only for this VM, use None to listen for
events about all VMs and not related to any particular VM.
:return: True if any event was received, otherwise False
:rtype: bool
''' '''
try:
reader, cleanup_func = yield from self._get_events_reader(vm) reader, cleanup_func = yield from self._get_events_reader(vm)
except asyncio.CancelledError: try:
return some_event_received = False
while not reader.at_eof(): while not reader.at_eof():
try: try:
event_data = yield from reader.readuntil(b'\0\0') event_data = yield from reader.readuntil(b'\0\0')
@ -126,8 +152,6 @@ class EventsDispatcher(object):
# event with non-VM subject contains \0\0 inside of # event with non-VM subject contains \0\0 inside of
# event, need to receive rest of the data # event, need to receive rest of the data
event_data += yield from reader.readuntil(b'\0\0') event_data += yield from reader.readuntil(b'\0\0')
except asyncio.CancelledError:
break
except asyncio.IncompleteReadError as err: except asyncio.IncompleteReadError as err:
if err.partial == b'': if err.partial == b'':
break break
@ -144,7 +168,10 @@ class EventsDispatcher(object):
kwargs = dict(zip(kwargs[:-2:2], kwargs[1:-2:2])) kwargs = dict(zip(kwargs[:-2:2], kwargs[1:-2:2]))
self.handle(subject, event, **kwargs) self.handle(subject, event, **kwargs)
some_event_received = True
finally:
cleanup_func() cleanup_func()
return some_event_received
def handle(self, subject, event, **kwargs): def handle(self, subject, event, **kwargs):
'''Call handlers for given event''' '''Call handlers for given event'''

View File

@ -111,7 +111,8 @@ class TC_00_Events(qubesmgmt.tests.QubesTestCase):
b'1\0some-vm\0other-event\0\0', b'1\0some-vm\0other-event\0\0',
] ]
asyncio.ensure_future(self.send_events(stream, events)) asyncio.ensure_future(self.send_events(stream, events))
loop.run_until_complete(self.dispatcher.listen_for_events()) loop.run_until_complete(self.dispatcher.listen_for_events(
reconnect=False))
self.assertEqual(handler.mock_calls, [ self.assertEqual(handler.mock_calls, [
unittest.mock.call(None, 'some-event', arg1='value1'), unittest.mock.call(None, 'some-event', arg1='value1'),
unittest.mock.call( unittest.mock.call(

View File

@ -209,7 +209,10 @@ def main(args=None):
loop.add_signal_handler(getattr(signal, signame), loop.add_signal_handler(getattr(signal, signame),
events_listener.cancel) # pylint: disable=no-member events_listener.cancel) # pylint: disable=no-member
try:
loop.run_until_complete(events_listener) loop.run_until_complete(events_listener)
except asyncio.CancelledError:
pass
loop.stop() loop.stop()
loop.run_forever() loop.run_forever()
loop.close() loop.close()