Source code for labgrid.target

from time import monotonic

import attr

from .binding import BindingError, BindingState
from .driver import Driver
from .exceptions import NoSupplierFoundError, NoDriverFoundError, NoResourceFoundError
from .resource import Resource, ManagedResource
from .strategy import Strategy
from .util import Timeout


@attr.s
[docs]class Target: name = attr.ib(validator=attr.validators.instance_of(str)) env = attr.ib(default=None) def __attrs_post_init__(self): self.resources = [] self.drivers = [] self.last_update = 0.0
[docs] def interact(self, msg): if self.env: self.env.interact("{}: {}".format(self.name, msg)) else: input(msg)
[docs] def update_resources(self): """ Iterate over all relevant managers and deactivate any active but unavailable resources. """ if (monotonic() - self.last_update) < 0.1: return self.last_update = monotonic() resources = (r for r in self.resources if isinstance(r, ManagedResource)) managers = set(r.manager for r in resources) for manager in managers: manager.poll() for resource in resources: if not resource.avail and resource.state is BindingState.active: print("deactivating unavailable resource {}".format(resource)) self.deactivate(resource)
[docs] def await_resources(self, resources, timeout=None): """ Poll the given resources and wait until they are available. """ self.update_resources() waiting = set(resource for resource in resources if isinstance(resource, ManagedResource)) if not waiting: return if timeout is None: timeout = Timeout(max(resource.timeout for resource in waiting)) else: timeout = Timeout(timeout) while waiting and not timeout.expired: waiting = set(r for r in waiting if not r.avail) managers = set(r.manager for r in waiting) for m in managers: m.poll() # TODO: sleep if no progress if waiting: raise NoResourceFoundError("Not all resources are available: {}".format(waiting))
[docs] def get_resource(self, cls, *, await=True): """ Helper function to get a resource of the target. Returns the first valid resource found, otherwise None. Arguments: cls -- resource-class to return as a resource await -- wait for the resource to become available (default True) """ for res in self.resources: if isinstance(res, cls): if await: self.await_resources([res]) return res raise NoResourceFoundError( "no resource matching {} found in target {}".format(cls, self) )
[docs] def get_driver(self, cls, *, activate=True): """ Helper function to get a driver of the target. Returns the first valid driver found, otherwise None. Arguments: cls -- driver-class to return as a resource activate -- activate the driver (default True) """ found = [] for drv in self.drivers: if isinstance(drv, cls): if isinstance(drv, Strategy): found.append(drv) # don't activate strategies, they have conflicting bindings continue if activate: self.activate(drv) found.append(drv) if len(found) == 0: raise NoDriverFoundError( "no driver matching {} found in target {}".format(cls, self) ) elif len(found) > 1: raise NoDriverFoundError( "multiple drivers matching {} found in target {}".format(cls, self) ) return found[0]
[docs] def get_active_driver(self, cls): """ Helper function to get the active driver of the target. Returns the active driver found, otherwise None. Arguments: cls -- driver-class to return as a resource """ for drv in self.drivers: if isinstance(drv, cls): if drv.state == BindingState.active: return drv raise NoDriverFoundError( "no driver matching {} found in target {}".format(cls, self) )
[docs] def bind_resource(self, resource): """ Bind the resource to this target. """ if resource.state is not BindingState.idle: raise BindingError( "{} is not in state {}".format(resource, BindingState.idle) ) # consistency check assert isinstance(resource, Resource) assert not resource.bindings assert resource not in self.resources assert resource.target is None # update state self.resources.append(resource) resource.target = self resource.state = BindingState.bound
[docs] def bind_driver(self, client): """ Bind the driver to all suppliers (resources and other drivers). Currently, we only support binding all suppliers at once. """ if client.state is not BindingState.idle: raise BindingError( "{} is not in state {}".format(client, BindingState.idle) ) # consistency check assert isinstance(client, Driver) assert client not in self.drivers assert client.target is None # locate suppliers suppliers = [] for name, requirements in client.bindings.items(): # use sets even for a single requirement if not isinstance(requirements, set): requirements = {requirements} errors = [] suppliers = [] for requirement in requirements: try: if issubclass(requirement, Resource): suppliers.append( self.get_resource(requirement, await=False), ) else: suppliers.append( self.get_driver(requirement, activate=False), ) except NoSupplierFoundError as e: errors.append(e) if not suppliers: if len(errors) == 1: raise errors[0] else: raise NoSupplierFoundError( "no supplier matching {} found in target {}".format(requirements, self) ) elif len(suppliers) > 1: raise NoSupplierFoundError( "conflicting suppliers matching {} found in target {}".format(requirements, self) ) setattr(client, name, suppliers[0]) suppliers.append(suppliers[0]) # consistency checks for supplier in suppliers: assert supplier.target is self assert client not in supplier.clients assert supplier not in client.suppliers # update relationship in both directions self.drivers.append(client) client.target = self for supplier in suppliers: supplier.clients.add(client) client.suppliers.add(supplier) client.on_supplier_bound(supplier, name) supplier.on_client_bound(client) client.state = BindingState.bound
[docs] def bind(self, bindable): if isinstance(bindable, Resource): return self.bind_resource(bindable) elif isinstance(bindable, Driver): return self.bind_driver(bindable) else: raise BindingError("object {} is not bindable".format(bindable))
[docs] def activate(self, client): """ Activate the client by activating all bound suppliers. This may require deactivating other clients. """ if client.state is BindingState.active: return # nothing to do if client.state is not BindingState.bound: raise BindingError( "{} is not in state {}".format(client, BindingState.bound) ) # consistency check assert client in self.resources or client in self.drivers # wait until resources are available resources = [resource for resource in client.suppliers if isinstance(resource, Resource)] self.await_resources(resources) # activate recursively and resolve conflicts for supplier in client.suppliers: if supplier.state is not BindingState.active: self.activate(supplier) supplier.resolve_conflicts(client) # update state client.on_activate() client.state = BindingState.active
[docs] def deactivate(self, client): """ Recursively deactivate the client's clients and itself. This is needed to ensure that no client has an inactive supplier. """ if client.state is BindingState.bound: return # nothing to do if client.state is not BindingState.active: raise BindingError( "{} is not in state {}".format(client, BindingState.active) ) # consistency check assert client in self.resources or client in self.drivers # update state client.on_deactivate() client.state = BindingState.bound
[docs] def cleanup(self): """Clean up conntected drivers and resources in reversed order""" for drv in reversed(self.drivers): self.deactivate(drv) for res in reversed(self.resources): self.deactivate(res)