Skip to content
Browse files

Merge pull request #481 from jluebbe/resource-locks

Acquire resources on exporter
  • Loading branch information...
jluebbe committed Sep 9, 2019
2 parents b6312f8 + eae7e58 commit 9413f3523028d4d2ecabd18f7428421d2f873fef
Showing with 287 additions and 82 deletions.
  1. +3 −0 CHANGES.rst
  2. +21 −3 labgrid/remote/
  3. +44 −5 labgrid/remote/
  4. +158 −35 labgrid/remote/
  5. +40 −39 labgrid/remote/
  6. +21 −0 tests/
@@ -26,6 +26,9 @@ New Features in 0.3.0
on the DUT can be written directly. The flashrom driver implements the
bootstrap protocol.
- AndroidFastbootDriver now supports 'getvar' and 'oem getenv' subcommands.
- The coordinator now updates the resource acquired state at the exporter.
Accordingly, the exporter now starts ser2net only when a resources is
aquired. Furthermore, resource conflicts between places are now detected.

Breaking changes in 0.3.0
@@ -526,10 +526,28 @@ def get_acquired_place(self, place=None):
res = await
if not res:
raise ServerError("failed to acquire place {}".format(
if res:
print("acquired place {}".format(

# check potential failure causes
for exporter, groups in sorted(self.resources.items()):
for group_name, group in sorted(groups.items()):
for resource_name, resource in sorted(group.items()):
resource_path = (exporter, group_name, resource.cls, resource_name)
if resource.acquired is None:
match = place.getmatch(resource_path)
if match is None:
name = resource_name
if match.rename:
name = match.rename
print("Matching resource '{}' ({}/{}/{}/{}) already acquired by place '{}'".format(
name, exporter, group_name, resource.cls, resource_name, resource.acquired))

raise ServerError("failed to acquire place {}".format(

async def release(self):
"""Release a previously acquired place"""
@@ -10,11 +10,15 @@
class ResourceEntry:
data = attr.ib() # cls, params
acquired = attr.ib(default=None)

def __attrs_post_init__(self):'acquired', None)'avail', False)

def acquired(self):

def avail(self):
@@ -47,6 +51,21 @@ def asdict(self):
'avail': self.avail,

def update(self, data):
"""apply updated information from the exporter on the coordinator"""
data = data.copy()
data.setdefault('acquired', None)
data.setdefault('avail', False) = data

def acquire(self, place_name):
assert['acquired'] is None['acquired'] = place_name

def release(self):
# ignore repeated releases['acquired'] = None

@attr.s(cmp=True, repr=False, str=False)
# This class requires cmp=True, since we put the matches into a list and require
@@ -108,9 +127,24 @@ class Place:
changed = attr.ib(default=attr.Factory(time.time))

def asdict(self):
result = attr.asdict(self)
del result['name'] # the name is the key in the places dict
return result
# in the coordinator, we have resource objects, otherwise just a path
acquired_resources = []
for resource in self.acquired_resources: # pylint: disable=not-an-iterable
if isinstance(resource, (tuple, list)):

return {
'aliases': list(self.aliases),
'comment': self.comment,
'matches': [attr.asdict(x) for x in self.matches],
'acquired': self.acquired,
'acquired_resources': acquired_resources,
'allowed': list(self.allowed),
'created': self.created,
'changed': self.changed,

def update(self, config):
fields = attr.fields_dict(type(self))
@@ -131,7 +165,12 @@ def show(self, level=0):
print(indent + " {}".format(match))
print(indent + "acquired: {}".format(self.acquired))
print(indent + "acquired resources:")
for resource_path in self.acquired_resources: # pylint: disable=not-an-iterable
# in the coordinator, we have resource objects, otherwise just a path
for resource in self.acquired_resources: # pylint: disable=not-an-iterable
if isinstance(resource, (tuple, list)):
resource_path = resource
resource_path = resource.path
match = self.getmatch(resource_path)
if match.rename:
print(indent + " {}{}".format(

0 comments on commit 9413f35

Please sign in to comment.
You can’t perform that action at this time.