qubes/vm/qubesvm: async def start

QubesOS/qubes-issues#2622
This commit is contained in:
Wojtek Porczyk 2017-03-29 17:11:24 +02:00
parent 8e3621c4e5
commit cce809c2cb
2 changed files with 58 additions and 36 deletions

View File

@ -201,7 +201,6 @@ class GUI(qubes.ext.Extension):
'Cannot start qubes-guid for domain {!r}'.format(vm.name)) 'Cannot start qubes-guid for domain {!r}'.format(vm.name))
vm.fire_event('monitor-layout-change') vm.fire_event('monitor-layout-change')
vm.wait_for_session()
@staticmethod @staticmethod

View File

@ -23,6 +23,7 @@
from __future__ import absolute_import from __future__ import absolute_import
import asyncio
import copy import copy
import base64 import base64
import datetime import datetime
@ -642,6 +643,10 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
self._libvirt_domain = None self._libvirt_domain = None
self._qdb_connection = None self._qdb_connection = None
#: this :py:class:`asyncio.Event` will fire when session is obtained
self.have_session = asyncio.Event()
if xml is None: if xml is None:
# we are creating new VM and attributes came through kwargs # we are creating new VM and attributes came through kwargs
assert hasattr(self, 'qid') assert hasattr(self, 'qid')
@ -788,7 +793,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
# methods for changing domain state # methods for changing domain state
# #
def start(self, preparing_dvm=False, start_guid=True, async def start(self, preparing_dvm=False, start_guid=True,
notify_function=None, mem_required=None): notify_function=None, mem_required=None):
'''Start domain '''Start domain
@ -808,19 +813,22 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
self.fire_event_pre('domain-pre-start', preparing_dvm=preparing_dvm, self.fire_event_pre('domain-pre-start', preparing_dvm=preparing_dvm,
start_guid=start_guid, mem_required=mem_required) start_guid=start_guid, mem_required=mem_required)
self.storage.verify() await asyncio.get_event_loop().run_in_executor(None,
self.storage.verify)
if self.netvm is not None: if self.netvm is not None:
# pylint: disable = no-member # pylint: disable = no-member
if self.netvm.qid != 0: if self.netvm.qid != 0:
if not self.netvm.is_running(): if not self.netvm.is_running():
self.netvm.start(start_guid=start_guid, await self.netvm.start(start_guid=start_guid,
notify_function=notify_function) notify_function=notify_function)
self.storage.start() await asyncio.get_event_loop().run_in_executor(None,
self.storage.start)
self._update_libvirt_domain() self._update_libvirt_domain()
qmemman_client = self.request_memory(mem_required) qmemman_client = await asyncio.get_event_loop().run_in_executor(None,
self.request_memory, mem_required)
try: try:
self.libvirt_domain.createWithFlags(libvirt.VIR_DOMAIN_START_PAUSED) self.libvirt_domain.createWithFlags(libvirt.VIR_DOMAIN_START_PAUSED)
except: except:
@ -833,7 +841,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
preparing_dvm=preparing_dvm, start_guid=start_guid) preparing_dvm=preparing_dvm, start_guid=start_guid)
self.log.info('Setting Qubes DB info for the VM') self.log.info('Setting Qubes DB info for the VM')
self.start_qubesdb() await self.start_qubesdb()
self.create_qdb_entries() self.create_qdb_entries()
if preparing_dvm: if preparing_dvm:
@ -855,7 +863,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
# self.start_guid() # self.start_guid()
if not preparing_dvm: if not preparing_dvm:
self.start_qrexec_daemon() await self.start_qrexec_daemon()
self.fire_event('domain-start', self.fire_event('domain-start',
preparing_dvm=preparing_dvm, start_guid=start_guid) preparing_dvm=preparing_dvm, start_guid=start_guid)
@ -864,12 +872,14 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
if self.is_running() or self.is_paused(): if self.is_running() or self.is_paused():
# This avoids losing the exception if an exception is raised in # This avoids losing the exception if an exception is raised in
# self.force_shutdown(), because the vm is not running or paused # self.force_shutdown(), because the vm is not running or paused
self.force_shutdown() await self.kill()
raise raise
asyncio.ensure_future(self.wait_for_session())
return self return self
def shutdown(self, force=False, wait=False): async def shutdown(self, force=False, wait=False):
'''Shutdown domain. '''Shutdown domain.
:raises qubes.exc.QubesVMNotStartedError: \ :raises qubes.exc.QubesVMNotStartedError: \
@ -882,14 +892,16 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
self.fire_event_pre('domain-pre-shutdown', force=force) self.fire_event_pre('domain-pre-shutdown', force=force)
self.libvirt_domain.shutdown() self.libvirt_domain.shutdown()
self.storage.stop()
await asyncio.get_event_loop().run_in_executor(None,
self.storage.stop)
while wait and not self.is_halted(): while wait and not self.is_halted():
time.sleep(0.25) await asyncio.sleep(0.25)
return self return self
def kill(self): async def kill(self):
'''Forcefuly shutdown (destroy) domain. '''Forcefuly shutdown (destroy) domain.
:raises qubes.exc.QubesVMNotStartedError: \ :raises qubes.exc.QubesVMNotStartedError: \
@ -900,7 +912,8 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
raise qubes.exc.QubesVMNotStartedError(self) raise qubes.exc.QubesVMNotStartedError(self)
self.libvirt_domain.destroy() self.libvirt_domain.destroy()
self.storage.stop() await asyncio.get_event_loop().run_in_executor(None,
self.storage.stop)
return self return self
@ -909,11 +922,9 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
warnings.warn( warnings.warn(
'Call to deprecated function force_shutdown(), use kill() instead', 'Call to deprecated function force_shutdown(), use kill() instead',
DeprecationWarning, stacklevel=2) DeprecationWarning, stacklevel=2)
self.kill(*args, **kwargs) return self.kill(*args, **kwargs)
return self async def suspend(self):
def suspend(self):
'''Suspend (pause) domain. '''Suspend (pause) domain.
:raises qubes.exc.QubesVMNotRunnignError: \ :raises qubes.exc.QubesVMNotRunnignError: \
@ -934,7 +945,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
return self return self
def pause(self): async def pause(self):
'''Pause (suspend) domain. This currently delegates to \ '''Pause (suspend) domain. This currently delegates to \
:py:meth:`suspend`.''' :py:meth:`suspend`.'''
@ -945,7 +956,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
return self return self
def resume(self): async def resume(self):
'''Resume suspended domain. '''Resume suspended domain.
:raises qubes.exc.QubesVMNotSuspendedError: when machine is not paused :raises qubes.exc.QubesVMNotSuspendedError: when machine is not paused
@ -960,7 +971,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
return self return self
def unpause(self): async def unpause(self):
'''Resume (unpause) a domain''' '''Resume (unpause) a domain'''
if not self.is_paused(): if not self.is_paused():
raise qubes.exc.QubesVMNotPausedError(self) raise qubes.exc.QubesVMNotPausedError(self)
@ -969,6 +980,8 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
return self return self
# TODO async def
# TODO def run_for_retcode, factor out passio
def run(self, command, user=None, autostart=False, notify_function=None, def run(self, command, user=None, autostart=False, notify_function=None,
passio=False, passio_popen=False, passio_stderr=False, passio=False, passio_popen=False, passio_stderr=False,
ignore_stderr=False, localcmd=None, wait=False, gui=True, ignore_stderr=False, localcmd=None, wait=False, gui=True,
@ -1018,6 +1031,9 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
self.fire_event_pre('domain-cmd-pre-run', start_guid=gui) self.fire_event_pre('domain-cmd-pre-run', start_guid=gui)
if not self.have_session.is_set():
raise qubes.exc.QubesVMError(self, 'don\'t have session yet')
args = [qubes.config.system_path['qrexec_client_path'], args = [qubes.config.system_path['qrexec_client_path'],
'-d', str(self.name), '-d', str(self.name),
'{}:{}'.format(user, command)] '{}:{}'.format(user, command)]
@ -1127,7 +1143,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
return qmemman_client return qmemman_client
@staticmethod @staticmethod
def start_daemon(command, **kwargs): async def start_daemon(*command, input=None, **kwargs):
'''Start a daemon for the VM '''Start a daemon for the VM
This function take care to run it as appropriate user. This function take care to run it as appropriate user.
@ -1138,16 +1154,19 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
:return: None :return: None
''' '''
prefix_cmd = []
if os.getuid() == 0: if os.getuid() == 0:
# try to always have VM daemons running as normal user, otherwise # try to always have VM daemons running as normal user, otherwise
# some files (like clipboard) may be created as root and cause # some files (like clipboard) may be created as root and cause
# permission problems # permission problems
qubes_group = grp.getgrnam('qubes') qubes_group = grp.getgrnam('qubes')
prefix_cmd = ['runuser', '-u', qubes_group.gr_mem[0], '--'] command = ['runuser', '-u', qubes_group.gr_mem[0], '--'] + command
subprocess.check_call(prefix_cmd + command, **kwargs) p = await asyncio.create_subprocess_exec(*command, **kwargs)
stdout, stderr = await p.communicate(input=input)
if p.returncode:
raise subprocess.CalledProcessError(p.returncode, command,
output=stdout, stderr=stderr)
def start_qrexec_daemon(self): async def start_qrexec_daemon(self):
'''Start qrexec daemon. '''Start qrexec daemon.
:raises OSError: when starting fails. :raises OSError: when starting fails.
@ -1167,13 +1186,13 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
qrexec_env['QREXEC_STARTUP_TIMEOUT'] = str(self.qrexec_timeout) qrexec_env['QREXEC_STARTUP_TIMEOUT'] = str(self.qrexec_timeout)
try: try:
self.start_daemon( await self.start_daemon(
[qubes.config.system_path["qrexec_daemon_path"]] + qrexec_args, qubes.config.system_path['qrexec_daemon_path'], *qrexec_args,
env=qrexec_env) env=qrexec_env)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
raise qubes.exc.QubesVMError(self, 'Cannot execute qrexec-daemon!') raise qubes.exc.QubesVMError(self, 'Cannot execute qrexec-daemon!')
def start_qubesdb(self): async def start_qubesdb(self):
'''Start QubesDB daemon. '''Start QubesDB daemon.
:raises OSError: when starting fails. :raises OSError: when starting fails.
@ -1184,14 +1203,14 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
self.log.info('Starting Qubes DB') self.log.info('Starting Qubes DB')
try: try:
self.start_daemon([ await self.start_daemon(
qubes.config.system_path["qubesdb_daemon_path"], qubes.config.system_path['qubesdb_daemon_path'],
str(self.xid), str(self.xid),
self.name]) self.name)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
raise qubes.exc.QubesException('Cannot execute qubesdb-daemon') raise qubes.exc.QubesException('Cannot execute qubesdb-daemon')
def wait_for_session(self): async def wait_for_session(self):
'''Wait until machine finished boot sequence. '''Wait until machine finished boot sequence.
This is done by executing qubes RPC call that checks if dummy system This is done by executing qubes RPC call that checks if dummy system
@ -1201,9 +1220,12 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
self.log.info('Waiting for qubes-session') self.log.info('Waiting for qubes-session')
# Note : User root is redefined to SYSTEM in the Windows agent code # Note : User root is redefined to SYSTEM in the Windows agent code
p = self.run('QUBESRPC qubes.WaitForSession none', p = await asyncio.get_event_loop().run_in_executor(
user="root", passio_popen=True, gui=False, wait=True) functools.partial(self.run, 'QUBESRPC qubes.WaitForSession none',
p.communicate(input=self.default_user.encode()) user='root', passio_popen=True, gui=False, wait=True))
await asyncio.get_event_loop().run_in_executor(functools.partial(
p.communicate, input=self.default_user.encode()))
self.have_session.set()
def create_on_disk(self, pool=None, pools=None): def create_on_disk(self, pool=None, pools=None):
'''Create files needed for VM. '''Create files needed for VM.
@ -1714,6 +1736,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
self.fire_event('domain-qdb-create') self.fire_event('domain-qdb-create')
# TODO async; update this in constructor
def _update_libvirt_domain(self): def _update_libvirt_domain(self):
'''Re-initialise :py:attr:`libvirt_domain`.''' '''Re-initialise :py:attr:`libvirt_domain`.'''
domain_config = self.create_config_file() domain_config = self.create_config_file()