Skip to content

Commit 1337d98

Browse files
remote/coordinator: propagate exporter failures
Report exporter acquire and release failures back through the coordinator RPCs instead of collapsing them to generic precondition errors. Preserve exporter command timeout and exception details so clients can see the underlying failure reason. Signed-off-by: Asher Pemberton <asher.pemberton@arm.com> Reviewed-by: Asher Pemberton <asher.pemberton@arm.com> # gatekeeper Co-authored-by: Idan Saadon <idan.saadon@arm.com>
1 parent 5818762 commit 1337d98

2 files changed

Lines changed: 56 additions & 17 deletions

File tree

labgrid/remote/coordinator.py

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,11 @@ async def wait(self):
205205
self.expired = True
206206

207207

208-
class ExporterError(Exception):
208+
class CoordinatorError(Exception):
209+
pass
210+
211+
212+
class ExporterError(CoordinatorError):
209213
pass
210214

211215

@@ -650,9 +654,12 @@ async def _acquire_resource(self, place, resource):
650654
request.place_name = place.name
651655
cmd = ExporterCommand(request)
652656
self.get_exporter_by_name(resource.path[0]).queue.put_nowait(cmd)
653-
await cmd.wait()
657+
try:
658+
await cmd.wait()
659+
except asyncio.TimeoutError as e:
660+
raise ExporterError("timed out waiting for exporter while acquiring resource") from e
654661
if not cmd.response.success:
655-
raise ExporterError(f"failed to acquire {resource} ({cmd.response.reason})")
662+
raise ExporterError(cmd.response.reason or "exporter returned failure without a reason")
656663
if resource.acquired != place.name:
657664
logging.warning("resource %s not acquired by this place after acquire request", resource)
658665

@@ -663,33 +670,38 @@ async def _acquire_resources(self, place, resources):
663670
# all resources need to be free
664671
for resource in resources:
665672
if resource.acquired:
666-
return False
673+
raise CoordinatorError(f"{resource} is already acquired by {resource.acquired}")
667674

668675
for otherplace in self.places.values():
669676
for oldres in otherplace.acquired_resources:
670677
if resource.path == oldres.path:
671678
logging.info(
672679
"Conflicting orphaned resource %s for acquire request for place %s", oldres, place.name
673680
)
674-
return False
681+
raise CoordinatorError(
682+
f"conflicting orphaned resource {oldres} for acquire request for place {place.name}"
683+
)
675684

676685
# acquire resources
677686
acquired = []
678687
try:
679688
for resource in resources:
680689
await self._acquire_resource(place, resource)
681690
acquired.append(resource)
682-
except Exception:
691+
except Exception as e:
683692
logging.exception("failed to acquire %s", resource)
684693
# cleanup
685-
await self._release_resources(place, acquired)
686-
return False
694+
try:
695+
await self._release_resources(place, acquired)
696+
except CoordinatorError:
697+
logging.exception("failed to release acquired resources during acquire cleanup")
698+
if isinstance(e, CoordinatorError):
699+
raise
700+
raise CoordinatorError(f"failed to acquire {resource}") from e
687701

688702
for resource in resources:
689703
place.acquired_resources.append(resource)
690704

691-
return True
692-
693705
async def _release_resources(self, place, resources, callback=True):
694706
assert self.lock.locked()
695707

@@ -701,6 +713,7 @@ async def _release_resources(self, place, resources, callback=True):
701713
except ValueError:
702714
pass
703715

716+
failure = None
704717
for resource in resources:
705718
if resource.orphaned:
706719
continue
@@ -714,18 +727,27 @@ async def _release_resources(self, place, resources, callback=True):
714727
# request.place_name is left unset to indicate release
715728
cmd = ExporterCommand(request)
716729
self.get_exporter_by_name(resource.path[0]).queue.put_nowait(cmd)
717-
await cmd.wait()
730+
try:
731+
await cmd.wait()
732+
except asyncio.TimeoutError as e:
733+
raise ExporterError("timed out waiting for exporter while releasing resource") from e
718734
if not cmd.response.success:
719-
raise ExporterError(f"failed to release {resource} ({cmd.response.reason})")
735+
raise ExporterError(cmd.response.reason or "exporter returned failure without a reason")
720736
if resource.acquired:
721737
logging.warning("resource %s still acquired after release request", resource)
722-
except (ExporterError, TimeoutError):
738+
except ExporterError as e:
739+
if failure is None:
740+
failure = e
723741
logging.exception("failed to release %s", resource)
724742
# at leaset try to notify the clients
725743
try:
726744
self._publish_resource(resource)
727745
except:
728746
logging.exception("failed to publish released resource %s", resource)
747+
if failure:
748+
if isinstance(failure, ExporterError):
749+
raise failure
750+
raise CoordinatorError(f"failed to release resources for place {place.name}") from failure
729751

730752
async def _synchronize_resources(self):
731753
assert self.lock.locked()
@@ -877,10 +899,18 @@ async def AcquirePlace(self, request, context):
877899
if not place.hasmatch(resource.path):
878900
continue
879901
resources.append(resource)
880-
if not await self._acquire_resources(place, resources):
902+
try:
903+
await self._acquire_resources(place, resources)
904+
except CoordinatorError as e:
881905
# revert earlier change
882906
place.acquired = None
883-
await context.abort(grpc.StatusCode.FAILED_PRECONDITION, f"Failed to acquire resources for place {name}")
907+
message = f"Failed to acquire resources for place {name}"
908+
if str(e):
909+
message += f": {e}"
910+
await context.abort(
911+
grpc.StatusCode.FAILED_PRECONDITION,
912+
message,
913+
)
884914
place.touch()
885915
self._publish_place(place)
886916
self.save_later()
@@ -904,7 +934,13 @@ async def ReleasePlace(self, request, context):
904934
if fromuser and place.acquired != fromuser:
905935
return labgrid_coordinator_pb2.ReleasePlaceResponse()
906936

907-
await self._release_resources(place, place.acquired_resources)
937+
try:
938+
await self._release_resources(place, place.acquired_resources)
939+
except CoordinatorError as e:
940+
message = f"Failed to release resources for place {name}"
941+
if str(e):
942+
message += f": {e}"
943+
await context.abort(grpc.StatusCode.FAILED_PRECONDITION, message)
908944

909945
place.acquired = None
910946
place.allowed = set()

labgrid/remote/exporter.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -926,8 +926,11 @@ async def message_pump(self):
926926
)
927927
success = True
928928
except (BrokenResourceError, InvalidResourceRequestError, UnknownResourceError) as e:
929-
reason = e.args[0]
929+
reason = str(e)
930930
logging.warning("set_acquired_request failed: %s", reason)
931+
except Exception as e:
932+
reason = str(e) or repr(e)
933+
logging.exception("failed to handle set_acquired_request: %s", reason)
931934
finally:
932935
in_message = labgrid_coordinator_pb2.ExporterInMessage()
933936
in_message.response.success = success

0 commit comments

Comments
 (0)