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))
vm.fire_event('monitor-layout-change')
vm.wait_for_session()
@staticmethod

View File

@ -23,6 +23,7 @@
from __future__ import absolute_import
import asyncio
import copy
import base64
import datetime
@ -642,6 +643,10 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
self._libvirt_domain = 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:
# we are creating new VM and attributes came through kwargs
assert hasattr(self, 'qid')
@ -788,7 +793,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
# 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):
'''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,
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:
# pylint: disable = no-member
if self.netvm.qid != 0:
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)
self.storage.start()
await asyncio.get_event_loop().run_in_executor(None,
self.storage.start)
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:
self.libvirt_domain.createWithFlags(libvirt.VIR_DOMAIN_START_PAUSED)
except:
@ -833,7 +841,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
preparing_dvm=preparing_dvm, start_guid=start_guid)
self.log.info('Setting Qubes DB info for the VM')
self.start_qubesdb()
await self.start_qubesdb()
self.create_qdb_entries()
if preparing_dvm:
@ -855,7 +863,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
# self.start_guid()
if not preparing_dvm:
self.start_qrexec_daemon()
await self.start_qrexec_daemon()
self.fire_event('domain-start',
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():
# 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()
await self.kill()
raise
asyncio.ensure_future(self.wait_for_session())
return self
def shutdown(self, force=False, wait=False):
async def shutdown(self, force=False, wait=False):
'''Shutdown domain.
: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.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():
time.sleep(0.25)
await asyncio.sleep(0.25)
return self
def kill(self):
async def kill(self):
'''Forcefuly shutdown (destroy) domain.
:raises qubes.exc.QubesVMNotStartedError: \
@ -900,7 +912,8 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
raise qubes.exc.QubesVMNotStartedError(self)
self.libvirt_domain.destroy()
self.storage.stop()
await asyncio.get_event_loop().run_in_executor(None,
self.storage.stop)
return self
@ -909,11 +922,9 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
warnings.warn(
'Call to deprecated function force_shutdown(), use kill() instead',
DeprecationWarning, stacklevel=2)
self.kill(*args, **kwargs)
return self.kill(*args, **kwargs)
return self
def suspend(self):
async def suspend(self):
'''Suspend (pause) domain.
:raises qubes.exc.QubesVMNotRunnignError: \
@ -934,7 +945,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
return self
def pause(self):
async def pause(self):
'''Pause (suspend) domain. This currently delegates to \
:py:meth:`suspend`.'''
@ -945,7 +956,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
return self
def resume(self):
async def resume(self):
'''Resume suspended domain.
: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
def unpause(self):
async def unpause(self):
'''Resume (unpause) a domain'''
if not self.is_paused():
raise qubes.exc.QubesVMNotPausedError(self)
@ -969,6 +980,8 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
return self
# TODO async def
# TODO def run_for_retcode, factor out passio
def run(self, command, user=None, autostart=False, notify_function=None,
passio=False, passio_popen=False, passio_stderr=False,
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)
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'],
'-d', str(self.name),
'{}:{}'.format(user, command)]
@ -1127,7 +1143,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
return qmemman_client
@staticmethod
def start_daemon(command, **kwargs):
async def start_daemon(*command, input=None, **kwargs):
'''Start a daemon for the VM
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
'''
prefix_cmd = []
if os.getuid() == 0:
# try to always have VM daemons running as normal user, otherwise
# some files (like clipboard) may be created as root and cause
# permission problems
qubes_group = grp.getgrnam('qubes')
prefix_cmd = ['runuser', '-u', qubes_group.gr_mem[0], '--']
subprocess.check_call(prefix_cmd + command, **kwargs)
command = ['runuser', '-u', qubes_group.gr_mem[0], '--'] + command
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.
: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)
try:
self.start_daemon(
[qubes.config.system_path["qrexec_daemon_path"]] + qrexec_args,
await self.start_daemon(
qubes.config.system_path['qrexec_daemon_path'], *qrexec_args,
env=qrexec_env)
except subprocess.CalledProcessError:
raise qubes.exc.QubesVMError(self, 'Cannot execute qrexec-daemon!')
def start_qubesdb(self):
async def start_qubesdb(self):
'''Start QubesDB daemon.
: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')
try:
self.start_daemon([
qubes.config.system_path["qubesdb_daemon_path"],
await self.start_daemon(
qubes.config.system_path['qubesdb_daemon_path'],
str(self.xid),
self.name])
self.name)
except subprocess.CalledProcessError:
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.
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')
# Note : User root is redefined to SYSTEM in the Windows agent code
p = self.run('QUBESRPC qubes.WaitForSession none',
user="root", passio_popen=True, gui=False, wait=True)
p.communicate(input=self.default_user.encode())
p = await asyncio.get_event_loop().run_in_executor(
functools.partial(self.run, 'QUBESRPC qubes.WaitForSession none',
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):
'''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')
# TODO async; update this in constructor
def _update_libvirt_domain(self):
'''Re-initialise :py:attr:`libvirt_domain`.'''
domain_config = self.create_config_file()