Migrate DeviceCollection to new API

- Use PersistentCollection as _set()
- attach/detach expect DeviceAssignment as parater
- attached(persistent=True) is now persistent()
- attached() returns all attached devices
- assigned() returns all attached device assignments

`#	modified:   templates/libvirt/xen.xml

Signed-off-by: Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
This commit is contained in:
Bahtiar `kalkin-` Gadimov 2017-04-08 04:09:46 +02:00
parent 23c68c5458
commit 990cfd8ab9
No known key found for this signature in database
GPG Key ID: 07799AE179ED4FD4
3 changed files with 79 additions and 48 deletions

View File

@ -55,6 +55,10 @@ class DeviceAlreadyAttached(qubes.exc.QubesException, KeyError):
'''Trying to attach already attached device''' '''Trying to attach already attached device'''
pass pass
class WrongAssignment(qubes.exc.QubesException, KeyError):
'''Trying to attach non permanent assignment to a halted vm'''
pass
class DeviceAssignment(object): # pylint: disable=too-few-public-methods class DeviceAssignment(object): # pylint: disable=too-few-public-methods
''' Maps a device to a frontend_domain. ''' ''' Maps a device to a frontend_domain. '''
@ -138,87 +142,113 @@ class DeviceCollection(object):
def __init__(self, vm, class_): def __init__(self, vm, class_):
self._vm = vm self._vm = vm
self._class = class_ self._class = class_
self._set = set() self._set = PersistentCollection()
self.devclass = qubes.utils.get_entry_point_one( self.devclass = qubes.utils.get_entry_point_one(
'qubes.devices', self._class) 'qubes.devices', self._class)
def attach(self, device, persistent=True): def attach(self, device_assignment: DeviceAssignment):
'''Attach (add) device to domain. '''Attach (add) device to domain.
:param DeviceInfo device: device object :param DeviceInfo device: device object
''' '''
if device in self.attached(): if not device_assignment.frontend_domain:
device_assignment.frontend_domain = self._vm
if device_assignment.frontend_domain != self._vm:
raise WrongAssignment(
"Trying to attach DeviceAssignment belonging to other domain")
if not device_assignment.persistent and self._vm.is_halted():
raise WrongAssignment("Devices can only be attached persistent to "
"a halted vm")
device = self._device(device_assignment)
if device in self.assignments():
raise DeviceAlreadyAttached( raise DeviceAlreadyAttached(
'device {!r} of class {} already attached to {!r}'.format( 'device {!s} of class {} already attached to {!s}'.format(
device, self._class, self._vm)) device, self._class, self._vm))
self._vm.fire_event_pre('device-pre-attach:'+self._class, device=device) self._vm.fire_event_pre('device-pre-attach:'+self._class, device=device)
if persistent: if device_assignment.persistent:
self._set.add(device) self._set.add(device_assignment)
self._vm.fire_event('device-attach:' + self._class, device=device) self._vm.fire_event('device-attach:' + self._class, device=device)
def detach(self, device_assignment: DeviceAssignment):
def detach(self, device, persistent=True):
'''Detach (remove) device from domain. '''Detach (remove) device from domain.
:param DeviceInfo device: device object :param DeviceInfo device: device object
''' '''
if device not in self.attached(): if not device_assignment.frontend_domain:
device_assignment.frontend_domain = self._vm
if device_assignment in self._set and not self._vm.is_halted():
raise WrongAssignment(
"Can not remove a persistent attachment from a non halted vm")
if device_assignment not in self.assignments():
raise DeviceNotAttached( raise DeviceNotAttached(
'device {!s} of class {} not attached to {!s}'.format( 'device {!s} of class {} not attached to {!s}'.format(
device, self._class, self._vm)) device_assignment.ident, self._class, self._vm))
device = self._device(device_assignment)
self._vm.fire_event_pre('device-pre-detach:'+self._class, device=device) self._vm.fire_event_pre('device-pre-detach:'+self._class, device=device)
if persistent: if device in self._set:
self._set.remove(device) device_assignment.persistent = True
self._set.discard(device_assignment)
self._vm.fire_event('device-detach:' + self._class, device=device) self._vm.fire_event('device-detach:' + self._class, device=device)
def attached(self, persistent=None): def attached(self):
'''List devices which are (or may be) attached to this vm '''List devices which are (or may be) attached to this vm '''
return self._vm.fire_event('device-list-attached:' + self._class) or []
def persistent(self):
''' Devices persistently attached and safe to access before libvirt
bootstrap.
'''
return [self._device(a) for a in self._set]
def assignments(self, persistent=None):
'''List assignments for devices which are (or may be) attached to the
vm.
Devices may be attached persistently (so they are included in Devices may be attached persistently (so they are included in
:file:`qubes.xml`) or not. Device can also be in :file:`qubes.xml`, :file:`qubes.xml`) or not. Device can also be in :file:`qubes.xml`,
but be temporarily detached. but be temporarily detached.
:param bool persistent: only include devices which are (or are not) \ :param bool persistent: only include devices which are or are not
attached persistently - None means both attached persistently.
''' '''
seen = self._set.copy()
# ask for really attached devices only when requested not only devices = self._vm.fire_event('device-list-attached:' + self._class,
# persistent ones persistent=persistent)
if persistent is not True: result = []
attached = self._vm.fire_event( for dev in devices:
'device-list-attached:' + self._class, if dev in self._set and persistent is False:
persistent=persistent) continue
for device in attached: elif dev in self._set:
device_persistent = device in self._set result.append(self._set.get(dev))
if persistent is not None and device_persistent != persistent: elif dev not in self._set and persistent is True:
continue continue
assert device.frontend_domain == self._vm, \ else:
'{!r} != {!r}'.format(device.frontend_domain, self._vm) result.append(
DeviceAssignment(backend_domain=dev.backend_domain,
yield device ident=dev.ident, options=dev.options,
frontend_domain=self._vm))
try: if persistent is not False and len(result) == 0:
seen.remove(device) result.extend(self._set)
except KeyError: return result
pass
if persistent is False:
return
for device in seen:
# get fresh object - may contain updated information
device = device.backend_domain.devices[self._class][device.ident]
yield device
def available(self): def available(self):
'''List devices exposed by this vm''' '''List devices exposed by this vm'''
devices = self._vm.fire_event('device-list:' + self._class) devices = self._vm.fire_event('device-list:' + self._class)
return devices return devices
def _device(self, assignment: DeviceAssignment):
''' Helper method for geting a `qubes.devices.DeviceInfo` object from
`qubes.devices.DeviceAssignment`. '''
return assignment.backend_domain.devices[self._class][assignment.ident]
def __iter__(self): def __iter__(self):
return iter(self.available()) return iter(self.available())
@ -259,9 +289,10 @@ class DeviceManager(dict):
class DeviceInfo(object): class DeviceInfo(object):
''' Holds all information about a device '''
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
def __init__(self, backend_domain, ident, description=None, def __init__(self, backend_domain, ident, description=None,
frontend_domain=None, **kwargs): frontend_domain=None, options = None, **kwargs):
#: domain providing this device #: domain providing this device
self.backend_domain = backend_domain self.backend_domain = backend_domain
#: device identifier (unique for given domain and device type) #: device identifier (unique for given domain and device type)

View File

@ -429,7 +429,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
# CORE2: swallowed uses_default_kernelopts # CORE2: swallowed uses_default_kernelopts
kernelopts = qubes.property('kernelopts', type=str, load_stage=4, kernelopts = qubes.property('kernelopts', type=str, load_stage=4,
default=(lambda self: qubes.config.defaults['kernelopts_pcidevs'] default=(lambda self: qubes.config.defaults['kernelopts_pcidevs']
if list(self.devices['pci'].attached(persistent=True)) if list(self.devices['pci'].persistent())
else self.template.kernelopts if hasattr(self, 'template') else self.template.kernelopts if hasattr(self, 'template')
else qubes.config.defaults['kernelopts']), else qubes.config.defaults['kernelopts']),
ls_width=30, ls_width=30,

View File

@ -32,7 +32,7 @@
<viridian/> <viridian/>
{% endif %} {% endif %}
{% if vm.devices['pci'].attached(persistent=True) | list {% if vm.devices['pci'].persistent() | list
and vm.features.get('pci-e820-host', True) %} and vm.features.get('pci-e820-host', True) %}
<xen> <xen>
<e820_host state="on"/> <e820_host state="on"/>
@ -106,7 +106,7 @@
{% include 'libvirt/devices/net.xml' with context %} {% include 'libvirt/devices/net.xml' with context %}
{% endif %} {% endif %}
{% for device in vm.devices.pci.attached(persistent=True) %} {% for device in vm.devices.pci.persistent() %}
{% include 'libvirt/devices/pci.xml' %} {% include 'libvirt/devices/pci.xml' %}
{% endfor %} {% endfor %}