From 00f474831a0a6b929bbe6421ade93eb8fb07ff4f Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sun, 3 May 2026 15:22:40 -0600 Subject: [PATCH 1/2] protocol: Pass acquiring user from coordinator to exporter The exporter currently cannot tell which user acquired a place - only which client did, via the host/user pair stored as 'acquired'. This blocks features that want to attribute resource use to a specific user, such as per-user serial trace files or audit logs on the exporter host. Extend ExporterSetAcquiredRequest with a 'user' field containing the host/user identity that acquired the place. The field is populated from place.acquired which already holds host/user. The exporter stashes the value on the ResourceExport so subclasses can use it (e.g. SerialPortExport for per-user trace files). Older coordinators that don't send the field continue to work as before; the field defaults to None on the exporter side. Signed-off-by: Simon Glass --- labgrid/remote/coordinator.py | 2 + labgrid/remote/exporter.py | 9 +- .../generated/labgrid_coordinator_pb2.py | 176 +++++++++--------- .../generated/labgrid_coordinator_pb2.pyi | 6 +- .../remote/proto/labgrid-coordinator.proto | 6 + 5 files changed, 108 insertions(+), 91 deletions(-) diff --git a/labgrid/remote/coordinator.py b/labgrid/remote/coordinator.py index a63a21482..113f28daa 100644 --- a/labgrid/remote/coordinator.py +++ b/labgrid/remote/coordinator.py @@ -647,6 +647,8 @@ async def _acquire_resource(self, place, resource): request.group_name = resource.path[1] request.resource_name = resource.path[3] request.place_name = place.name + if place.acquired: + request.user = place.acquired cmd = ExporterCommand(request) self.get_exporter_by_name(resource.path[0]).queue.put_nowait(cmd) await cmd.wait() diff --git a/labgrid/remote/exporter.py b/labgrid/remote/exporter.py index 0e48f1942..ae635fef5 100755 --- a/labgrid/remote/exporter.py +++ b/labgrid/remote/exporter.py @@ -67,6 +67,7 @@ class ResourceExport(ResourceEntry): host = attr.ib(default=gethostname(), validator=attr.validators.instance_of(str)) proxy = attr.ib(default=None) proxy_required = attr.ib(default=False) + user = attr.ib(default=None, init=False) local = attr.ib(init=False) local_params = attr.ib(init=False) start_params = attr.ib(init=False) @@ -912,6 +913,7 @@ async def message_pump(self): out_message.set_acquired_request.group_name, out_message.set_acquired_request.resource_name, out_message.set_acquired_request.place_name, + out_message.set_acquired_request.user or None, ) else: await self.release( @@ -952,7 +954,7 @@ async def message_pump(self): # perhaps with queue join/task_done # this should be a command from the coordinator - async def acquire(self, group_name, resource_name, place_name): + async def acquire(self, group_name, resource_name, place_name, user=None): resource = self.groups.get(group_name, {}).get(resource_name) if resource is None: raise UnknownResourceError( @@ -964,6 +966,11 @@ async def acquire(self, group_name, resource_name, place_name): f"Resource {group_name}/{resource_name} is already acquired by {resource.acquired}" ) + # Stash the acquiring user so ResourceExport subclasses can use it + # (e.g. for per-user trace files). Older coordinators don't send + # this field, in which case it stays None. + if isinstance(resource, ResourceExport): + resource.user = user try: resource.acquire(place_name) finally: diff --git a/labgrid/remote/generated/labgrid_coordinator_pb2.py b/labgrid/remote/generated/labgrid_coordinator_pb2.py index 37652bff7..f0ec652d6 100644 --- a/labgrid/remote/generated/labgrid_coordinator_pb2.py +++ b/labgrid/remote/generated/labgrid_coordinator_pb2.py @@ -14,7 +14,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19labgrid-coordinator.proto\x12\x07labgrid\"\x8a\x01\n\x0f\x43lientInMessage\x12\x1d\n\x04sync\x18\x01 \x01(\x0b\x32\r.labgrid.SyncH\x00\x12\'\n\x07startup\x18\x02 \x01(\x0b\x32\x14.labgrid.StartupDoneH\x00\x12\'\n\tsubscribe\x18\x03 \x01(\x0b\x32\x12.labgrid.SubscribeH\x00\x42\x06\n\x04kind\"\x12\n\x04Sync\x12\n\n\x02id\x18\x01 \x01(\x04\",\n\x0bStartupDone\x12\x0f\n\x07version\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\"r\n\tSubscribe\x12\x1b\n\x0eis_unsubscribe\x18\x01 \x01(\x08H\x01\x88\x01\x01\x12\x14\n\nall_places\x18\x02 \x01(\x08H\x00\x12\x17\n\rall_resources\x18\x03 \x01(\x08H\x00\x42\x06\n\x04kindB\x11\n\x0f_is_unsubscribe\"g\n\x10\x43lientOutMessage\x12 \n\x04sync\x18\x01 \x01(\x0b\x32\r.labgrid.SyncH\x00\x88\x01\x01\x12(\n\x07updates\x18\x02 \x03(\x0b\x32\x17.labgrid.UpdateResponseB\x07\n\x05_sync\"\xa5\x01\n\x0eUpdateResponse\x12%\n\x08resource\x18\x01 \x01(\x0b\x32\x11.labgrid.ResourceH\x00\x12.\n\x0c\x64\x65l_resource\x18\x02 \x01(\x0b\x32\x16.labgrid.Resource.PathH\x00\x12\x1f\n\x05place\x18\x03 \x01(\x0b\x32\x0e.labgrid.PlaceH\x00\x12\x13\n\tdel_place\x18\x04 \x01(\tH\x00\x42\x06\n\x04kind\"\x9a\x01\n\x11\x45xporterInMessage\x12%\n\x08resource\x18\x01 \x01(\x0b\x32\x11.labgrid.ResourceH\x00\x12\'\n\x07startup\x18\x02 \x01(\x0b\x32\x14.labgrid.StartupDoneH\x00\x12-\n\x08response\x18\x03 \x01(\x0b\x32\x19.labgrid.ExporterResponseH\x00\x42\x06\n\x04kind\"\x9e\x03\n\x08Resource\x12$\n\x04path\x18\x01 \x01(\x0b\x32\x16.labgrid.Resource.Path\x12\x0b\n\x03\x63ls\x18\x02 \x01(\t\x12-\n\x06params\x18\x03 \x03(\x0b\x32\x1d.labgrid.Resource.ParamsEntry\x12+\n\x05\x65xtra\x18\x04 \x03(\x0b\x32\x1c.labgrid.Resource.ExtraEntry\x12\x10\n\x08\x61\x63quired\x18\x05 \x01(\t\x12\r\n\x05\x61vail\x18\x06 \x01(\x08\x1a_\n\x04Path\x12\x1a\n\rexporter_name\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x12\n\ngroup_name\x18\x02 \x01(\t\x12\x15\n\rresource_name\x18\x03 \x01(\tB\x10\n\x0e_exporter_name\x1a@\n\x0bParamsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12 \n\x05value\x18\x02 \x01(\x0b\x32\x11.labgrid.MapValue:\x02\x38\x01\x1a?\n\nExtraEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12 \n\x05value\x18\x02 \x01(\x0b\x32\x11.labgrid.MapValue:\x02\x38\x01\"\x82\x01\n\x08MapValue\x12\x14\n\nbool_value\x18\x01 \x01(\x08H\x00\x12\x13\n\tint_value\x18\x02 \x01(\x03H\x00\x12\x14\n\nuint_value\x18\x03 \x01(\x04H\x00\x12\x15\n\x0b\x66loat_value\x18\x04 \x01(\x01H\x00\x12\x16\n\x0cstring_value\x18\x05 \x01(\tH\x00\x42\x06\n\x04kind\"C\n\x10\x45xporterResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x13\n\x06reason\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\t\n\x07_reason\"\x18\n\x05Hello\x12\x0f\n\x07version\x18\x01 \x01(\t\"\x82\x01\n\x12\x45xporterOutMessage\x12\x1f\n\x05hello\x18\x01 \x01(\x0b\x32\x0e.labgrid.HelloH\x00\x12\x43\n\x14set_acquired_request\x18\x02 \x01(\x0b\x32#.labgrid.ExporterSetAcquiredRequestH\x00\x42\x06\n\x04kind\"o\n\x1a\x45xporterSetAcquiredRequest\x12\x12\n\ngroup_name\x18\x01 \x01(\t\x12\x15\n\rresource_name\x18\x02 \x01(\t\x12\x17\n\nplace_name\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\r\n\x0b_place_name\"\x1f\n\x0f\x41\x64\x64PlaceRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x12\n\x10\x41\x64\x64PlaceResponse\"\"\n\x12\x44\x65letePlaceRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x15\n\x13\x44\x65letePlaceResponse\"\x12\n\x10GetPlacesRequest\"3\n\x11GetPlacesResponse\x12\x1e\n\x06places\x18\x01 \x03(\x0b\x32\x0e.labgrid.Place\"\xd2\x02\n\x05Place\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x07\x61liases\x18\x02 \x03(\t\x12\x0f\n\x07\x63omment\x18\x03 \x01(\t\x12&\n\x04tags\x18\x04 \x03(\x0b\x32\x18.labgrid.Place.TagsEntry\x12\'\n\x07matches\x18\x05 \x03(\x0b\x32\x16.labgrid.ResourceMatch\x12\x15\n\x08\x61\x63quired\x18\x06 \x01(\tH\x00\x88\x01\x01\x12\x1a\n\x12\x61\x63quired_resources\x18\x07 \x03(\t\x12\x0f\n\x07\x61llowed\x18\x08 \x03(\t\x12\x0f\n\x07\x63reated\x18\t \x01(\x01\x12\x0f\n\x07\x63hanged\x18\n \x01(\x01\x12\x18\n\x0breservation\x18\x0b \x01(\tH\x01\x88\x01\x01\x1a+\n\tTagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x0b\n\t_acquiredB\x0e\n\x0c_reservation\"y\n\rResourceMatch\x12\x10\n\x08\x65xporter\x18\x01 \x01(\t\x12\r\n\x05group\x18\x02 \x01(\t\x12\x0b\n\x03\x63ls\x18\x03 \x01(\t\x12\x11\n\x04name\x18\x04 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x06rename\x18\x05 \x01(\tH\x01\x88\x01\x01\x42\x07\n\x05_nameB\t\n\x07_rename\"8\n\x14\x41\x64\x64PlaceAliasRequest\x12\x11\n\tplacename\x18\x01 \x01(\t\x12\r\n\x05\x61lias\x18\x02 \x01(\t\"\x17\n\x15\x41\x64\x64PlaceAliasResponse\";\n\x17\x44\x65letePlaceAliasRequest\x12\x11\n\tplacename\x18\x01 \x01(\t\x12\r\n\x05\x61lias\x18\x02 \x01(\t\"\x1a\n\x18\x44\x65letePlaceAliasResponse\"\x8b\x01\n\x13SetPlaceTagsRequest\x12\x11\n\tplacename\x18\x01 \x01(\t\x12\x34\n\x04tags\x18\x02 \x03(\x0b\x32&.labgrid.SetPlaceTagsRequest.TagsEntry\x1a+\n\tTagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x16\n\x14SetPlaceTagsResponse\"<\n\x16SetPlaceCommentRequest\x12\x11\n\tplacename\x18\x01 \x01(\t\x12\x0f\n\x07\x63omment\x18\x02 \x01(\t\"\x19\n\x17SetPlaceCommentResponse\"Z\n\x14\x41\x64\x64PlaceMatchRequest\x12\x11\n\tplacename\x18\x01 \x01(\t\x12\x0f\n\x07pattern\x18\x02 \x01(\t\x12\x13\n\x06rename\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\t\n\x07_rename\"\x17\n\x15\x41\x64\x64PlaceMatchResponse\"]\n\x17\x44\x65letePlaceMatchRequest\x12\x11\n\tplacename\x18\x01 \x01(\t\x12\x0f\n\x07pattern\x18\x02 \x01(\t\x12\x13\n\x06rename\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\t\n\x07_rename\"\x1a\n\x18\x44\x65letePlaceMatchResponse\"(\n\x13\x41\x63quirePlaceRequest\x12\x11\n\tplacename\x18\x01 \x01(\t\"\x16\n\x14\x41\x63quirePlaceResponse\"L\n\x13ReleasePlaceRequest\x12\x11\n\tplacename\x18\x01 \x01(\t\x12\x15\n\x08\x66romuser\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x0b\n\t_fromuser\"\x16\n\x14ReleasePlaceResponse\"4\n\x11\x41llowPlaceRequest\x12\x11\n\tplacename\x18\x01 \x01(\t\x12\x0c\n\x04user\x18\x02 \x01(\t\"\x14\n\x12\x41llowPlaceResponse\"\xb6\x01\n\x18\x43reateReservationRequest\x12?\n\x07\x66ilters\x18\x01 \x03(\x0b\x32..labgrid.CreateReservationRequest.FiltersEntry\x12\x0c\n\x04prio\x18\x02 \x01(\x01\x1aK\n\x0c\x46iltersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12*\n\x05value\x18\x02 \x01(\x0b\x32\x1b.labgrid.Reservation.Filter:\x02\x38\x01\"F\n\x19\x43reateReservationResponse\x12)\n\x0breservation\x18\x01 \x01(\x0b\x32\x14.labgrid.Reservation\"\xcd\x03\n\x0bReservation\x12\r\n\x05owner\x18\x01 \x01(\t\x12\r\n\x05token\x18\x02 \x01(\t\x12\r\n\x05state\x18\x03 \x01(\x05\x12\x0c\n\x04prio\x18\x04 \x01(\x01\x12\x32\n\x07\x66ilters\x18\x05 \x03(\x0b\x32!.labgrid.Reservation.FiltersEntry\x12:\n\x0b\x61llocations\x18\x06 \x03(\x0b\x32%.labgrid.Reservation.AllocationsEntry\x12\x0f\n\x07\x63reated\x18\x07 \x01(\x01\x12\x0f\n\x07timeout\x18\x08 \x01(\x01\x1ap\n\x06\x46ilter\x12\x37\n\x06\x66ilter\x18\x01 \x03(\x0b\x32\'.labgrid.Reservation.Filter.FilterEntry\x1a-\n\x0b\x46ilterEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1aK\n\x0c\x46iltersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12*\n\x05value\x18\x02 \x01(\x0b\x32\x1b.labgrid.Reservation.Filter:\x02\x38\x01\x1a\x32\n\x10\x41llocationsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\")\n\x18\x43\x61ncelReservationRequest\x12\r\n\x05token\x18\x01 \x01(\t\"\x1b\n\x19\x43\x61ncelReservationResponse\"\'\n\x16PollReservationRequest\x12\r\n\x05token\x18\x01 \x01(\t\"D\n\x17PollReservationResponse\x12)\n\x0breservation\x18\x01 \x01(\x0b\x32\x14.labgrid.Reservation\"E\n\x17GetReservationsResponse\x12*\n\x0creservations\x18\x01 \x03(\x0b\x32\x14.labgrid.Reservation\"\x18\n\x16GetReservationsRequest2\xd2\x0b\n\x0b\x43oordinator\x12I\n\x0c\x43lientStream\x12\x18.labgrid.ClientInMessage\x1a\x19.labgrid.ClientOutMessage\"\x00(\x01\x30\x01\x12O\n\x0e\x45xporterStream\x12\x1a.labgrid.ExporterInMessage\x1a\x1b.labgrid.ExporterOutMessage\"\x00(\x01\x30\x01\x12\x41\n\x08\x41\x64\x64Place\x12\x18.labgrid.AddPlaceRequest\x1a\x19.labgrid.AddPlaceResponse\"\x00\x12J\n\x0b\x44\x65letePlace\x12\x1b.labgrid.DeletePlaceRequest\x1a\x1c.labgrid.DeletePlaceResponse\"\x00\x12\x44\n\tGetPlaces\x12\x19.labgrid.GetPlacesRequest\x1a\x1a.labgrid.GetPlacesResponse\"\x00\x12P\n\rAddPlaceAlias\x12\x1d.labgrid.AddPlaceAliasRequest\x1a\x1e.labgrid.AddPlaceAliasResponse\"\x00\x12Y\n\x10\x44\x65letePlaceAlias\x12 .labgrid.DeletePlaceAliasRequest\x1a!.labgrid.DeletePlaceAliasResponse\"\x00\x12M\n\x0cSetPlaceTags\x12\x1c.labgrid.SetPlaceTagsRequest\x1a\x1d.labgrid.SetPlaceTagsResponse\"\x00\x12V\n\x0fSetPlaceComment\x12\x1f.labgrid.SetPlaceCommentRequest\x1a .labgrid.SetPlaceCommentResponse\"\x00\x12P\n\rAddPlaceMatch\x12\x1d.labgrid.AddPlaceMatchRequest\x1a\x1e.labgrid.AddPlaceMatchResponse\"\x00\x12Y\n\x10\x44\x65letePlaceMatch\x12 .labgrid.DeletePlaceMatchRequest\x1a!.labgrid.DeletePlaceMatchResponse\"\x00\x12M\n\x0c\x41\x63quirePlace\x12\x1c.labgrid.AcquirePlaceRequest\x1a\x1d.labgrid.AcquirePlaceResponse\"\x00\x12M\n\x0cReleasePlace\x12\x1c.labgrid.ReleasePlaceRequest\x1a\x1d.labgrid.ReleasePlaceResponse\"\x00\x12G\n\nAllowPlace\x12\x1a.labgrid.AllowPlaceRequest\x1a\x1b.labgrid.AllowPlaceResponse\"\x00\x12\\\n\x11\x43reateReservation\x12!.labgrid.CreateReservationRequest\x1a\".labgrid.CreateReservationResponse\"\x00\x12\\\n\x11\x43\x61ncelReservation\x12!.labgrid.CancelReservationRequest\x1a\".labgrid.CancelReservationResponse\"\x00\x12V\n\x0fPollReservation\x12\x1f.labgrid.PollReservationRequest\x1a .labgrid.PollReservationResponse\"\x00\x12V\n\x0fGetReservations\x12\x1f.labgrid.GetReservationsRequest\x1a .labgrid.GetReservationsResponse\"\x00\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19labgrid-coordinator.proto\x12\x07labgrid\"\x8a\x01\n\x0f\x43lientInMessage\x12\x1d\n\x04sync\x18\x01 \x01(\x0b\x32\r.labgrid.SyncH\x00\x12\'\n\x07startup\x18\x02 \x01(\x0b\x32\x14.labgrid.StartupDoneH\x00\x12\'\n\tsubscribe\x18\x03 \x01(\x0b\x32\x12.labgrid.SubscribeH\x00\x42\x06\n\x04kind\"\x12\n\x04Sync\x12\n\n\x02id\x18\x01 \x01(\x04\",\n\x0bStartupDone\x12\x0f\n\x07version\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\"r\n\tSubscribe\x12\x1b\n\x0eis_unsubscribe\x18\x01 \x01(\x08H\x01\x88\x01\x01\x12\x14\n\nall_places\x18\x02 \x01(\x08H\x00\x12\x17\n\rall_resources\x18\x03 \x01(\x08H\x00\x42\x06\n\x04kindB\x11\n\x0f_is_unsubscribe\"g\n\x10\x43lientOutMessage\x12 \n\x04sync\x18\x01 \x01(\x0b\x32\r.labgrid.SyncH\x00\x88\x01\x01\x12(\n\x07updates\x18\x02 \x03(\x0b\x32\x17.labgrid.UpdateResponseB\x07\n\x05_sync\"\xa5\x01\n\x0eUpdateResponse\x12%\n\x08resource\x18\x01 \x01(\x0b\x32\x11.labgrid.ResourceH\x00\x12.\n\x0c\x64\x65l_resource\x18\x02 \x01(\x0b\x32\x16.labgrid.Resource.PathH\x00\x12\x1f\n\x05place\x18\x03 \x01(\x0b\x32\x0e.labgrid.PlaceH\x00\x12\x13\n\tdel_place\x18\x04 \x01(\tH\x00\x42\x06\n\x04kind\"\x9a\x01\n\x11\x45xporterInMessage\x12%\n\x08resource\x18\x01 \x01(\x0b\x32\x11.labgrid.ResourceH\x00\x12\'\n\x07startup\x18\x02 \x01(\x0b\x32\x14.labgrid.StartupDoneH\x00\x12-\n\x08response\x18\x03 \x01(\x0b\x32\x19.labgrid.ExporterResponseH\x00\x42\x06\n\x04kind\"\x9e\x03\n\x08Resource\x12$\n\x04path\x18\x01 \x01(\x0b\x32\x16.labgrid.Resource.Path\x12\x0b\n\x03\x63ls\x18\x02 \x01(\t\x12-\n\x06params\x18\x03 \x03(\x0b\x32\x1d.labgrid.Resource.ParamsEntry\x12+\n\x05\x65xtra\x18\x04 \x03(\x0b\x32\x1c.labgrid.Resource.ExtraEntry\x12\x10\n\x08\x61\x63quired\x18\x05 \x01(\t\x12\r\n\x05\x61vail\x18\x06 \x01(\x08\x1a_\n\x04Path\x12\x1a\n\rexporter_name\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x12\n\ngroup_name\x18\x02 \x01(\t\x12\x15\n\rresource_name\x18\x03 \x01(\tB\x10\n\x0e_exporter_name\x1a@\n\x0bParamsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12 \n\x05value\x18\x02 \x01(\x0b\x32\x11.labgrid.MapValue:\x02\x38\x01\x1a?\n\nExtraEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12 \n\x05value\x18\x02 \x01(\x0b\x32\x11.labgrid.MapValue:\x02\x38\x01\"\x82\x01\n\x08MapValue\x12\x14\n\nbool_value\x18\x01 \x01(\x08H\x00\x12\x13\n\tint_value\x18\x02 \x01(\x03H\x00\x12\x14\n\nuint_value\x18\x03 \x01(\x04H\x00\x12\x15\n\x0b\x66loat_value\x18\x04 \x01(\x01H\x00\x12\x16\n\x0cstring_value\x18\x05 \x01(\tH\x00\x42\x06\n\x04kind\"C\n\x10\x45xporterResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x13\n\x06reason\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\t\n\x07_reason\"\x18\n\x05Hello\x12\x0f\n\x07version\x18\x01 \x01(\t\"\x82\x01\n\x12\x45xporterOutMessage\x12\x1f\n\x05hello\x18\x01 \x01(\x0b\x32\x0e.labgrid.HelloH\x00\x12\x43\n\x14set_acquired_request\x18\x02 \x01(\x0b\x32#.labgrid.ExporterSetAcquiredRequestH\x00\x42\x06\n\x04kind\"}\n\x1a\x45xporterSetAcquiredRequest\x12\x12\n\ngroup_name\x18\x01 \x01(\t\x12\x15\n\rresource_name\x18\x02 \x01(\t\x12\x17\n\nplace_name\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x0c\n\x04user\x18\x04 \x01(\tB\r\n\x0b_place_name\"\x1f\n\x0f\x41\x64\x64PlaceRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x12\n\x10\x41\x64\x64PlaceResponse\"\"\n\x12\x44\x65letePlaceRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x15\n\x13\x44\x65letePlaceResponse\"\x12\n\x10GetPlacesRequest\"3\n\x11GetPlacesResponse\x12\x1e\n\x06places\x18\x01 \x03(\x0b\x32\x0e.labgrid.Place\"\xd2\x02\n\x05Place\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x07\x61liases\x18\x02 \x03(\t\x12\x0f\n\x07\x63omment\x18\x03 \x01(\t\x12&\n\x04tags\x18\x04 \x03(\x0b\x32\x18.labgrid.Place.TagsEntry\x12\'\n\x07matches\x18\x05 \x03(\x0b\x32\x16.labgrid.ResourceMatch\x12\x15\n\x08\x61\x63quired\x18\x06 \x01(\tH\x00\x88\x01\x01\x12\x1a\n\x12\x61\x63quired_resources\x18\x07 \x03(\t\x12\x0f\n\x07\x61llowed\x18\x08 \x03(\t\x12\x0f\n\x07\x63reated\x18\t \x01(\x01\x12\x0f\n\x07\x63hanged\x18\n \x01(\x01\x12\x18\n\x0breservation\x18\x0b \x01(\tH\x01\x88\x01\x01\x1a+\n\tTagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x0b\n\t_acquiredB\x0e\n\x0c_reservation\"y\n\rResourceMatch\x12\x10\n\x08\x65xporter\x18\x01 \x01(\t\x12\r\n\x05group\x18\x02 \x01(\t\x12\x0b\n\x03\x63ls\x18\x03 \x01(\t\x12\x11\n\x04name\x18\x04 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x06rename\x18\x05 \x01(\tH\x01\x88\x01\x01\x42\x07\n\x05_nameB\t\n\x07_rename\"8\n\x14\x41\x64\x64PlaceAliasRequest\x12\x11\n\tplacename\x18\x01 \x01(\t\x12\r\n\x05\x61lias\x18\x02 \x01(\t\"\x17\n\x15\x41\x64\x64PlaceAliasResponse\";\n\x17\x44\x65letePlaceAliasRequest\x12\x11\n\tplacename\x18\x01 \x01(\t\x12\r\n\x05\x61lias\x18\x02 \x01(\t\"\x1a\n\x18\x44\x65letePlaceAliasResponse\"\x8b\x01\n\x13SetPlaceTagsRequest\x12\x11\n\tplacename\x18\x01 \x01(\t\x12\x34\n\x04tags\x18\x02 \x03(\x0b\x32&.labgrid.SetPlaceTagsRequest.TagsEntry\x1a+\n\tTagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x16\n\x14SetPlaceTagsResponse\"<\n\x16SetPlaceCommentRequest\x12\x11\n\tplacename\x18\x01 \x01(\t\x12\x0f\n\x07\x63omment\x18\x02 \x01(\t\"\x19\n\x17SetPlaceCommentResponse\"Z\n\x14\x41\x64\x64PlaceMatchRequest\x12\x11\n\tplacename\x18\x01 \x01(\t\x12\x0f\n\x07pattern\x18\x02 \x01(\t\x12\x13\n\x06rename\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\t\n\x07_rename\"\x17\n\x15\x41\x64\x64PlaceMatchResponse\"]\n\x17\x44\x65letePlaceMatchRequest\x12\x11\n\tplacename\x18\x01 \x01(\t\x12\x0f\n\x07pattern\x18\x02 \x01(\t\x12\x13\n\x06rename\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\t\n\x07_rename\"\x1a\n\x18\x44\x65letePlaceMatchResponse\"(\n\x13\x41\x63quirePlaceRequest\x12\x11\n\tplacename\x18\x01 \x01(\t\"\x16\n\x14\x41\x63quirePlaceResponse\"L\n\x13ReleasePlaceRequest\x12\x11\n\tplacename\x18\x01 \x01(\t\x12\x15\n\x08\x66romuser\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x0b\n\t_fromuser\"\x16\n\x14ReleasePlaceResponse\"4\n\x11\x41llowPlaceRequest\x12\x11\n\tplacename\x18\x01 \x01(\t\x12\x0c\n\x04user\x18\x02 \x01(\t\"\x14\n\x12\x41llowPlaceResponse\"\xb6\x01\n\x18\x43reateReservationRequest\x12?\n\x07\x66ilters\x18\x01 \x03(\x0b\x32..labgrid.CreateReservationRequest.FiltersEntry\x12\x0c\n\x04prio\x18\x02 \x01(\x01\x1aK\n\x0c\x46iltersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12*\n\x05value\x18\x02 \x01(\x0b\x32\x1b.labgrid.Reservation.Filter:\x02\x38\x01\"F\n\x19\x43reateReservationResponse\x12)\n\x0breservation\x18\x01 \x01(\x0b\x32\x14.labgrid.Reservation\"\xcd\x03\n\x0bReservation\x12\r\n\x05owner\x18\x01 \x01(\t\x12\r\n\x05token\x18\x02 \x01(\t\x12\r\n\x05state\x18\x03 \x01(\x05\x12\x0c\n\x04prio\x18\x04 \x01(\x01\x12\x32\n\x07\x66ilters\x18\x05 \x03(\x0b\x32!.labgrid.Reservation.FiltersEntry\x12:\n\x0b\x61llocations\x18\x06 \x03(\x0b\x32%.labgrid.Reservation.AllocationsEntry\x12\x0f\n\x07\x63reated\x18\x07 \x01(\x01\x12\x0f\n\x07timeout\x18\x08 \x01(\x01\x1ap\n\x06\x46ilter\x12\x37\n\x06\x66ilter\x18\x01 \x03(\x0b\x32\'.labgrid.Reservation.Filter.FilterEntry\x1a-\n\x0b\x46ilterEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1aK\n\x0c\x46iltersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12*\n\x05value\x18\x02 \x01(\x0b\x32\x1b.labgrid.Reservation.Filter:\x02\x38\x01\x1a\x32\n\x10\x41llocationsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\")\n\x18\x43\x61ncelReservationRequest\x12\r\n\x05token\x18\x01 \x01(\t\"\x1b\n\x19\x43\x61ncelReservationResponse\"\'\n\x16PollReservationRequest\x12\r\n\x05token\x18\x01 \x01(\t\"D\n\x17PollReservationResponse\x12)\n\x0breservation\x18\x01 \x01(\x0b\x32\x14.labgrid.Reservation\"E\n\x17GetReservationsResponse\x12*\n\x0creservations\x18\x01 \x03(\x0b\x32\x14.labgrid.Reservation\"\x18\n\x16GetReservationsRequest2\xd2\x0b\n\x0b\x43oordinator\x12I\n\x0c\x43lientStream\x12\x18.labgrid.ClientInMessage\x1a\x19.labgrid.ClientOutMessage\"\x00(\x01\x30\x01\x12O\n\x0e\x45xporterStream\x12\x1a.labgrid.ExporterInMessage\x1a\x1b.labgrid.ExporterOutMessage\"\x00(\x01\x30\x01\x12\x41\n\x08\x41\x64\x64Place\x12\x18.labgrid.AddPlaceRequest\x1a\x19.labgrid.AddPlaceResponse\"\x00\x12J\n\x0b\x44\x65letePlace\x12\x1b.labgrid.DeletePlaceRequest\x1a\x1c.labgrid.DeletePlaceResponse\"\x00\x12\x44\n\tGetPlaces\x12\x19.labgrid.GetPlacesRequest\x1a\x1a.labgrid.GetPlacesResponse\"\x00\x12P\n\rAddPlaceAlias\x12\x1d.labgrid.AddPlaceAliasRequest\x1a\x1e.labgrid.AddPlaceAliasResponse\"\x00\x12Y\n\x10\x44\x65letePlaceAlias\x12 .labgrid.DeletePlaceAliasRequest\x1a!.labgrid.DeletePlaceAliasResponse\"\x00\x12M\n\x0cSetPlaceTags\x12\x1c.labgrid.SetPlaceTagsRequest\x1a\x1d.labgrid.SetPlaceTagsResponse\"\x00\x12V\n\x0fSetPlaceComment\x12\x1f.labgrid.SetPlaceCommentRequest\x1a .labgrid.SetPlaceCommentResponse\"\x00\x12P\n\rAddPlaceMatch\x12\x1d.labgrid.AddPlaceMatchRequest\x1a\x1e.labgrid.AddPlaceMatchResponse\"\x00\x12Y\n\x10\x44\x65letePlaceMatch\x12 .labgrid.DeletePlaceMatchRequest\x1a!.labgrid.DeletePlaceMatchResponse\"\x00\x12M\n\x0c\x41\x63quirePlace\x12\x1c.labgrid.AcquirePlaceRequest\x1a\x1d.labgrid.AcquirePlaceResponse\"\x00\x12M\n\x0cReleasePlace\x12\x1c.labgrid.ReleasePlaceRequest\x1a\x1d.labgrid.ReleasePlaceResponse\"\x00\x12G\n\nAllowPlace\x12\x1a.labgrid.AllowPlaceRequest\x1a\x1b.labgrid.AllowPlaceResponse\"\x00\x12\\\n\x11\x43reateReservation\x12!.labgrid.CreateReservationRequest\x1a\".labgrid.CreateReservationResponse\"\x00\x12\\\n\x11\x43\x61ncelReservation\x12!.labgrid.CancelReservationRequest\x1a\".labgrid.CancelReservationResponse\"\x00\x12V\n\x0fPollReservation\x12\x1f.labgrid.PollReservationRequest\x1a .labgrid.PollReservationResponse\"\x00\x12V\n\x0fGetReservations\x12\x1f.labgrid.GetReservationsRequest\x1a .labgrid.GetReservationsResponse\"\x00\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -68,91 +68,91 @@ _globals['_EXPORTEROUTMESSAGE']._serialized_start=1437 _globals['_EXPORTEROUTMESSAGE']._serialized_end=1567 _globals['_EXPORTERSETACQUIREDREQUEST']._serialized_start=1569 - _globals['_EXPORTERSETACQUIREDREQUEST']._serialized_end=1680 - _globals['_ADDPLACEREQUEST']._serialized_start=1682 - _globals['_ADDPLACEREQUEST']._serialized_end=1713 - _globals['_ADDPLACERESPONSE']._serialized_start=1715 - _globals['_ADDPLACERESPONSE']._serialized_end=1733 - _globals['_DELETEPLACEREQUEST']._serialized_start=1735 - _globals['_DELETEPLACEREQUEST']._serialized_end=1769 - _globals['_DELETEPLACERESPONSE']._serialized_start=1771 - _globals['_DELETEPLACERESPONSE']._serialized_end=1792 - _globals['_GETPLACESREQUEST']._serialized_start=1794 - _globals['_GETPLACESREQUEST']._serialized_end=1812 - _globals['_GETPLACESRESPONSE']._serialized_start=1814 - _globals['_GETPLACESRESPONSE']._serialized_end=1865 - _globals['_PLACE']._serialized_start=1868 - _globals['_PLACE']._serialized_end=2206 - _globals['_PLACE_TAGSENTRY']._serialized_start=2134 - _globals['_PLACE_TAGSENTRY']._serialized_end=2177 - _globals['_RESOURCEMATCH']._serialized_start=2208 - _globals['_RESOURCEMATCH']._serialized_end=2329 - _globals['_ADDPLACEALIASREQUEST']._serialized_start=2331 - _globals['_ADDPLACEALIASREQUEST']._serialized_end=2387 - _globals['_ADDPLACEALIASRESPONSE']._serialized_start=2389 - _globals['_ADDPLACEALIASRESPONSE']._serialized_end=2412 - _globals['_DELETEPLACEALIASREQUEST']._serialized_start=2414 - _globals['_DELETEPLACEALIASREQUEST']._serialized_end=2473 - _globals['_DELETEPLACEALIASRESPONSE']._serialized_start=2475 - _globals['_DELETEPLACEALIASRESPONSE']._serialized_end=2501 - _globals['_SETPLACETAGSREQUEST']._serialized_start=2504 - _globals['_SETPLACETAGSREQUEST']._serialized_end=2643 - _globals['_SETPLACETAGSREQUEST_TAGSENTRY']._serialized_start=2134 - _globals['_SETPLACETAGSREQUEST_TAGSENTRY']._serialized_end=2177 - _globals['_SETPLACETAGSRESPONSE']._serialized_start=2645 - _globals['_SETPLACETAGSRESPONSE']._serialized_end=2667 - _globals['_SETPLACECOMMENTREQUEST']._serialized_start=2669 - _globals['_SETPLACECOMMENTREQUEST']._serialized_end=2729 - _globals['_SETPLACECOMMENTRESPONSE']._serialized_start=2731 - _globals['_SETPLACECOMMENTRESPONSE']._serialized_end=2756 - _globals['_ADDPLACEMATCHREQUEST']._serialized_start=2758 - _globals['_ADDPLACEMATCHREQUEST']._serialized_end=2848 - _globals['_ADDPLACEMATCHRESPONSE']._serialized_start=2850 - _globals['_ADDPLACEMATCHRESPONSE']._serialized_end=2873 - _globals['_DELETEPLACEMATCHREQUEST']._serialized_start=2875 - _globals['_DELETEPLACEMATCHREQUEST']._serialized_end=2968 - _globals['_DELETEPLACEMATCHRESPONSE']._serialized_start=2970 - _globals['_DELETEPLACEMATCHRESPONSE']._serialized_end=2996 - _globals['_ACQUIREPLACEREQUEST']._serialized_start=2998 - _globals['_ACQUIREPLACEREQUEST']._serialized_end=3038 - _globals['_ACQUIREPLACERESPONSE']._serialized_start=3040 - _globals['_ACQUIREPLACERESPONSE']._serialized_end=3062 - _globals['_RELEASEPLACEREQUEST']._serialized_start=3064 - _globals['_RELEASEPLACEREQUEST']._serialized_end=3140 - _globals['_RELEASEPLACERESPONSE']._serialized_start=3142 - _globals['_RELEASEPLACERESPONSE']._serialized_end=3164 - _globals['_ALLOWPLACEREQUEST']._serialized_start=3166 - _globals['_ALLOWPLACEREQUEST']._serialized_end=3218 - _globals['_ALLOWPLACERESPONSE']._serialized_start=3220 - _globals['_ALLOWPLACERESPONSE']._serialized_end=3240 - _globals['_CREATERESERVATIONREQUEST']._serialized_start=3243 - _globals['_CREATERESERVATIONREQUEST']._serialized_end=3425 - _globals['_CREATERESERVATIONREQUEST_FILTERSENTRY']._serialized_start=3350 - _globals['_CREATERESERVATIONREQUEST_FILTERSENTRY']._serialized_end=3425 - _globals['_CREATERESERVATIONRESPONSE']._serialized_start=3427 - _globals['_CREATERESERVATIONRESPONSE']._serialized_end=3497 - _globals['_RESERVATION']._serialized_start=3500 - _globals['_RESERVATION']._serialized_end=3961 - _globals['_RESERVATION_FILTER']._serialized_start=3720 - _globals['_RESERVATION_FILTER']._serialized_end=3832 - _globals['_RESERVATION_FILTER_FILTERENTRY']._serialized_start=3787 - _globals['_RESERVATION_FILTER_FILTERENTRY']._serialized_end=3832 - _globals['_RESERVATION_FILTERSENTRY']._serialized_start=3350 - _globals['_RESERVATION_FILTERSENTRY']._serialized_end=3425 - _globals['_RESERVATION_ALLOCATIONSENTRY']._serialized_start=3911 - _globals['_RESERVATION_ALLOCATIONSENTRY']._serialized_end=3961 - _globals['_CANCELRESERVATIONREQUEST']._serialized_start=3963 - _globals['_CANCELRESERVATIONREQUEST']._serialized_end=4004 - _globals['_CANCELRESERVATIONRESPONSE']._serialized_start=4006 - _globals['_CANCELRESERVATIONRESPONSE']._serialized_end=4033 - _globals['_POLLRESERVATIONREQUEST']._serialized_start=4035 - _globals['_POLLRESERVATIONREQUEST']._serialized_end=4074 - _globals['_POLLRESERVATIONRESPONSE']._serialized_start=4076 - _globals['_POLLRESERVATIONRESPONSE']._serialized_end=4144 - _globals['_GETRESERVATIONSRESPONSE']._serialized_start=4146 - _globals['_GETRESERVATIONSRESPONSE']._serialized_end=4215 - _globals['_GETRESERVATIONSREQUEST']._serialized_start=4217 - _globals['_GETRESERVATIONSREQUEST']._serialized_end=4241 - _globals['_COORDINATOR']._serialized_start=4244 - _globals['_COORDINATOR']._serialized_end=5734 + _globals['_EXPORTERSETACQUIREDREQUEST']._serialized_end=1694 + _globals['_ADDPLACEREQUEST']._serialized_start=1696 + _globals['_ADDPLACEREQUEST']._serialized_end=1727 + _globals['_ADDPLACERESPONSE']._serialized_start=1729 + _globals['_ADDPLACERESPONSE']._serialized_end=1747 + _globals['_DELETEPLACEREQUEST']._serialized_start=1749 + _globals['_DELETEPLACEREQUEST']._serialized_end=1783 + _globals['_DELETEPLACERESPONSE']._serialized_start=1785 + _globals['_DELETEPLACERESPONSE']._serialized_end=1806 + _globals['_GETPLACESREQUEST']._serialized_start=1808 + _globals['_GETPLACESREQUEST']._serialized_end=1826 + _globals['_GETPLACESRESPONSE']._serialized_start=1828 + _globals['_GETPLACESRESPONSE']._serialized_end=1879 + _globals['_PLACE']._serialized_start=1882 + _globals['_PLACE']._serialized_end=2220 + _globals['_PLACE_TAGSENTRY']._serialized_start=2148 + _globals['_PLACE_TAGSENTRY']._serialized_end=2191 + _globals['_RESOURCEMATCH']._serialized_start=2222 + _globals['_RESOURCEMATCH']._serialized_end=2343 + _globals['_ADDPLACEALIASREQUEST']._serialized_start=2345 + _globals['_ADDPLACEALIASREQUEST']._serialized_end=2401 + _globals['_ADDPLACEALIASRESPONSE']._serialized_start=2403 + _globals['_ADDPLACEALIASRESPONSE']._serialized_end=2426 + _globals['_DELETEPLACEALIASREQUEST']._serialized_start=2428 + _globals['_DELETEPLACEALIASREQUEST']._serialized_end=2487 + _globals['_DELETEPLACEALIASRESPONSE']._serialized_start=2489 + _globals['_DELETEPLACEALIASRESPONSE']._serialized_end=2515 + _globals['_SETPLACETAGSREQUEST']._serialized_start=2518 + _globals['_SETPLACETAGSREQUEST']._serialized_end=2657 + _globals['_SETPLACETAGSREQUEST_TAGSENTRY']._serialized_start=2148 + _globals['_SETPLACETAGSREQUEST_TAGSENTRY']._serialized_end=2191 + _globals['_SETPLACETAGSRESPONSE']._serialized_start=2659 + _globals['_SETPLACETAGSRESPONSE']._serialized_end=2681 + _globals['_SETPLACECOMMENTREQUEST']._serialized_start=2683 + _globals['_SETPLACECOMMENTREQUEST']._serialized_end=2743 + _globals['_SETPLACECOMMENTRESPONSE']._serialized_start=2745 + _globals['_SETPLACECOMMENTRESPONSE']._serialized_end=2770 + _globals['_ADDPLACEMATCHREQUEST']._serialized_start=2772 + _globals['_ADDPLACEMATCHREQUEST']._serialized_end=2862 + _globals['_ADDPLACEMATCHRESPONSE']._serialized_start=2864 + _globals['_ADDPLACEMATCHRESPONSE']._serialized_end=2887 + _globals['_DELETEPLACEMATCHREQUEST']._serialized_start=2889 + _globals['_DELETEPLACEMATCHREQUEST']._serialized_end=2982 + _globals['_DELETEPLACEMATCHRESPONSE']._serialized_start=2984 + _globals['_DELETEPLACEMATCHRESPONSE']._serialized_end=3010 + _globals['_ACQUIREPLACEREQUEST']._serialized_start=3012 + _globals['_ACQUIREPLACEREQUEST']._serialized_end=3052 + _globals['_ACQUIREPLACERESPONSE']._serialized_start=3054 + _globals['_ACQUIREPLACERESPONSE']._serialized_end=3076 + _globals['_RELEASEPLACEREQUEST']._serialized_start=3078 + _globals['_RELEASEPLACEREQUEST']._serialized_end=3154 + _globals['_RELEASEPLACERESPONSE']._serialized_start=3156 + _globals['_RELEASEPLACERESPONSE']._serialized_end=3178 + _globals['_ALLOWPLACEREQUEST']._serialized_start=3180 + _globals['_ALLOWPLACEREQUEST']._serialized_end=3232 + _globals['_ALLOWPLACERESPONSE']._serialized_start=3234 + _globals['_ALLOWPLACERESPONSE']._serialized_end=3254 + _globals['_CREATERESERVATIONREQUEST']._serialized_start=3257 + _globals['_CREATERESERVATIONREQUEST']._serialized_end=3439 + _globals['_CREATERESERVATIONREQUEST_FILTERSENTRY']._serialized_start=3364 + _globals['_CREATERESERVATIONREQUEST_FILTERSENTRY']._serialized_end=3439 + _globals['_CREATERESERVATIONRESPONSE']._serialized_start=3441 + _globals['_CREATERESERVATIONRESPONSE']._serialized_end=3511 + _globals['_RESERVATION']._serialized_start=3514 + _globals['_RESERVATION']._serialized_end=3975 + _globals['_RESERVATION_FILTER']._serialized_start=3734 + _globals['_RESERVATION_FILTER']._serialized_end=3846 + _globals['_RESERVATION_FILTER_FILTERENTRY']._serialized_start=3801 + _globals['_RESERVATION_FILTER_FILTERENTRY']._serialized_end=3846 + _globals['_RESERVATION_FILTERSENTRY']._serialized_start=3364 + _globals['_RESERVATION_FILTERSENTRY']._serialized_end=3439 + _globals['_RESERVATION_ALLOCATIONSENTRY']._serialized_start=3925 + _globals['_RESERVATION_ALLOCATIONSENTRY']._serialized_end=3975 + _globals['_CANCELRESERVATIONREQUEST']._serialized_start=3977 + _globals['_CANCELRESERVATIONREQUEST']._serialized_end=4018 + _globals['_CANCELRESERVATIONRESPONSE']._serialized_start=4020 + _globals['_CANCELRESERVATIONRESPONSE']._serialized_end=4047 + _globals['_POLLRESERVATIONREQUEST']._serialized_start=4049 + _globals['_POLLRESERVATIONREQUEST']._serialized_end=4088 + _globals['_POLLRESERVATIONRESPONSE']._serialized_start=4090 + _globals['_POLLRESERVATIONRESPONSE']._serialized_end=4158 + _globals['_GETRESERVATIONSRESPONSE']._serialized_start=4160 + _globals['_GETRESERVATIONSRESPONSE']._serialized_end=4229 + _globals['_GETRESERVATIONSREQUEST']._serialized_start=4231 + _globals['_GETRESERVATIONSREQUEST']._serialized_end=4255 + _globals['_COORDINATOR']._serialized_start=4258 + _globals['_COORDINATOR']._serialized_end=5748 # @@protoc_insertion_point(module_scope) diff --git a/labgrid/remote/generated/labgrid_coordinator_pb2.pyi b/labgrid/remote/generated/labgrid_coordinator_pb2.pyi index 366f4e438..aa4113d94 100644 --- a/labgrid/remote/generated/labgrid_coordinator_pb2.pyi +++ b/labgrid/remote/generated/labgrid_coordinator_pb2.pyi @@ -145,14 +145,16 @@ class ExporterOutMessage(_message.Message): def __init__(self, hello: _Optional[_Union[Hello, _Mapping]] = ..., set_acquired_request: _Optional[_Union[ExporterSetAcquiredRequest, _Mapping]] = ...) -> None: ... class ExporterSetAcquiredRequest(_message.Message): - __slots__ = ("group_name", "resource_name", "place_name") + __slots__ = ("group_name", "resource_name", "place_name", "user") GROUP_NAME_FIELD_NUMBER: _ClassVar[int] RESOURCE_NAME_FIELD_NUMBER: _ClassVar[int] PLACE_NAME_FIELD_NUMBER: _ClassVar[int] + USER_FIELD_NUMBER: _ClassVar[int] group_name: str resource_name: str place_name: str - def __init__(self, group_name: _Optional[str] = ..., resource_name: _Optional[str] = ..., place_name: _Optional[str] = ...) -> None: ... + user: str + def __init__(self, group_name: _Optional[str] = ..., resource_name: _Optional[str] = ..., place_name: _Optional[str] = ..., user: _Optional[str] = ...) -> None: ... class AddPlaceRequest(_message.Message): __slots__ = ("name",) diff --git a/labgrid/remote/proto/labgrid-coordinator.proto b/labgrid/remote/proto/labgrid-coordinator.proto index e0585f7e1..77321575b 100644 --- a/labgrid/remote/proto/labgrid-coordinator.proto +++ b/labgrid/remote/proto/labgrid-coordinator.proto @@ -132,6 +132,12 @@ message ExporterSetAcquiredRequest { string group_name = 1; string resource_name = 2; optional string place_name = 3; + // The user that acquired the place, in "host/user" form. Set + // when place_name is set (acquire); empty on release. Allows the + // exporter to attribute resource use to a specific user, e.g. for + // per-user serial trace files. Plain (non-optional) so the field + // is supported by older protoc. + string user = 4; }; message AddPlaceRequest { From 0d9c88adcf09ae29dfa18cd43b1488765dc7994e Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sun, 3 May 2026 15:22:40 -0600 Subject: [PATCH 2/2] exporter: Allow logging of serial traffic Lab admins running shared boards have no independent record of what is happening on the serial console. The per-client --logfile option captures output for the user who set it, but the lab owner has no way to see traffic across all sessions, which hampers post-mortem when a board ends up in a bad state and the question is which commands got it there. Add support for centralised serial-traffic logging on the exporter host. When LG_SERIAL_TRACE_DIR is set, the exporter passes ser2net's trace-both option for each acquire, capturing every byte that flows in either direction to a per-board, per-user file under that directory. ser2net is started fresh on each resource acquire and stopped on release, so each instance can include both the board (group name) and the acquiring user in the filename: -.log (e.g. bbb-okaro_sjg.log). Repeated acquires by the same user on the same board append to the same file. The user identity comes from the coordinator via the new 'user' field in ExporterSetAcquiredRequest (added in a previous commit). The group name is plumbed from the resource config through add_resource() into the ResourceExport so the trace path can use the human-readable board name instead of the device path. trace-both-timestamp is also enabled, so ser2net writes timestamped OPEN and CLOSE markers around each session. The console data itself is recorded verbatim with both directions interleaved, and the session markers give the lab admin the boundaries needed to correlate a trace with other audit logs. Admins who need per-line timestamps at the cost of readability can additionally set LG_SERIAL_TRACE_HEXDUMP=1 to switch ser2net into hex+ASCII mode, where every line is timestamped. Series-changes: 2 - Mention --lg-log and ConsoleLoggingReporter alongside --logfile, and clarify that the exporter trace is the operator's independent record (the user cannot influence it) - Add real default-mode and hex-mode trace snippets to doc/usage.rst, captured from a running kea exporter - Add LG_SERIAL_TRACE_HEXDUMP=1 to opt into ser2net's hexdump mode (timestamp on every line, at the cost of readability); cover both code paths with tests - Correct the description: in the default mode only the OPEN and CLOSE session markers are timestamped, not every line - Document filename fallbacks: device basename for missing group_name, literal 'unknown' for missing user - Add doc sub-sections for systemd setup, file permissions, privacy implications and disk-usage growth in hexdump mode - Note that log rotation is left to logrotate on the admin host, and that ser2net's append-on-open behaviour makes copytruncate/daily safe Signed-off-by: Simon Glass Co-developed-by: Claude Opus 4.6 --- doc/man/exporter.rst | 27 ++++++- doc/usage.rst | 139 +++++++++++++++++++++++++++++++++++ labgrid/remote/exporter.py | 50 ++++++++++++- man/labgrid-exporter.1 | 25 ++++++- tests/test_exporter_trace.py | 85 +++++++++++++++++++++ 5 files changed, 323 insertions(+), 3 deletions(-) create mode 100644 tests/test_exporter_trace.py diff --git a/doc/man/exporter.rst b/doc/man/exporter.rst index c30ca3180..1a0f581af 100644 --- a/doc/man/exporter.rst +++ b/doc/man/exporter.rst @@ -77,13 +77,38 @@ for more information. ENVIRONMENT VARIABLES --------------------- -The following environment variable can be used to configure labgrid-exporter. +The following environment variables can be used to configure +labgrid-exporter. LG_COORDINATOR ~~~~~~~~~~~~~~ This variable can be used to set the default coordinator in the format ``HOST[:PORT]`` (instead of using the ``-x`` option). +LG_SERIAL_TRACE_DIR +~~~~~~~~~~~~~~~~~~~ +When set, the exporter records all serial-port traffic for each +acquired resource into ``/-.log``, +where ```` is the resource group name and ```` is the +acquiring user identity reported by the coordinator (in +``host/user`` form, with ``/`` rewritten to ``_``). Both directions +are captured verbatim, and ser2net writes timestamped OPEN/CLOSE +markers at the start and end of each session so the lab admin has +an independent record that is not affected by per-client logging +options. + +The directory is created on demand. A fresh ser2net instance is +started for each acquire, so repeated acquires by the same user on +the same board append to the same file. + +LG_SERIAL_TRACE_HEXDUMP +~~~~~~~~~~~~~~~~~~~~~~~ +When set to ``1`` (and ``LG_SERIAL_TRACE_DIR`` is also set), the +exporter additionally enables ser2net's hexdump format for the +trace. This prepends a timestamp to every line at the cost of +producing hex+ASCII output instead of readable text. Use this +when correlating to the millisecond matters more than readability. + EXAMPLES -------- diff --git a/doc/usage.rst b/doc/usage.rst index 37527fb40..25848f95b 100644 --- a/doc/usage.rst +++ b/doc/usage.rst @@ -166,6 +166,145 @@ allocated before returning. A reservation will time out after a short time, if it is neither refreshed nor used by locked places. +Logging Serial Traffic on the Exporter +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When several developers (or CI jobs) share the same boards through a single +coordinator, it is often useful for the lab admin to keep an independent +record of every serial-console session. The per-client ``--logfile`` option +only records output for the user who set it, so it does not help the operator +answer questions like *which commands left this board in a bad state?* after +the fact. + +To enable a centralised log on the exporter host, set the +``LG_SERIAL_TRACE_DIR`` environment variable before starting +``labgrid-exporter``: + +.. code-block:: bash + + $ export LG_SERIAL_TRACE_DIR=/var/log/labgrid/serial + $ labgrid-exporter my-config.yaml + +For each acquire, the exporter asks ``ser2net`` to record both sides of the +serial connection to a file named ``-.log`` under that directory, +where ```` is the resource group name (falling back to the basename of +the device path if the resource has no group) and ```` is the host/user +identity reported by the coordinator (slashes rewritten to underscores so the +result is filesystem safe, or ``unknown`` for older coordinators that do not +send a user). + +Because ``ser2net`` is started fresh on each acquire and stopped on release, +the trace covers exactly one session per acquire. Repeated acquires by the +same user on the same board append to the same file, so a long-running +developer ends up with a single, continuous log per board. + +This feature complements rather than replaces user-side logging (``--logfile`` +on the client, ``--lg-log`` on the pytest plugin, or ``ConsoleLoggingReporter`` +when using labgrid as a library): the user-side logs are what the developer +sees in their terminal and chooses what to capture, while the exporter trace +is the operator's independent record that the user cannot influence. + +A default-mode trace looks like this (user typed ``echo Me again`` at the +U-Boot prompt; each character of the input appears twice because ``tcp`` and +``term`` are written into the same stream, so the typed character and the +board's local echo are interleaved):: + + 2026/05/13 06:58:50 OPEN (ipv6,::ffff:192.168.4.7,60208) + ... + Press SPACE to abort autoboot in 2 seconds + => eecchhoo MMee aaggaaiinn + + Me again + => 2026/05/13 06:59:06 CLOSE netcon (network read close) + 2026/05/13 06:59:06 CLOSE port (All users disconnected) + +Connection events (``OPEN`` when ser2net accepts the client connection, +``CLOSE`` when the acquire is released) are timestamped by ser2net, giving +the session boundary that the lab admin can use to correlate a trace with +other audit logs. The console traffic itself is recorded verbatim with both +directions interleaved as they arrive, so the file reads like a transcript of +what was on the wire. + +If per-line timestamps are needed (for example, when correlating to the +millisecond against another log source), additionally set +``LG_SERIAL_TRACE_HEXDUMP=1`` before starting ``labgrid-exporter``. The +exporter then enables ser2net's hexdump format for the trace, which prepends +a timestamp and a direction tag to every line at the cost of producing +hex+ASCII output instead of readable text. The direction tag is ``term`` for +bytes the board emitted and ``tcp`` for bytes the client sent. A hex trace +of the same ``echo Hello Again`` session looks like this — each typed +character shows up as a ``tcp`` line immediately followed by a ``term`` line +as the board echoes it, then the board's output appears as a single +multi-byte ``term`` line:: + + 2026/05/13 07:21:02 OPEN (ipv6,::ffff:192.168.4.7,44566) + ... + 2026/05/13 07:21:11 tcp 65 |e| + 2026/05/13 07:21:11 term 65 |e| + 2026/05/13 07:21:11 tcp 63 |c| + 2026/05/13 07:21:11 term 63 |c| + 2026/05/13 07:21:11 tcp 68 |h| + 2026/05/13 07:21:11 term 68 |h| + 2026/05/13 07:21:11 tcp 6f |o| + 2026/05/13 07:21:11 term 6f |o| + 2026/05/13 07:21:11 tcp 20 | | + 2026/05/13 07:21:11 term 20 | | + ... + 2026/05/13 07:21:15 tcp 0a |.| + 2026/05/13 07:21:15 term 0d 0a 48 65 6c 6c 6f 20 |..Hello | + 2026/05/13 07:21:15 term 41 67 61 69 6e 0d 0a 3d |Again..=| + 2026/05/13 07:21:15 term 3e 20 |> | + 2026/05/13 07:21:16 CLOSE netcon (network read close) + +Setup with systemd +^^^^^^^^^^^^^^^^^^ + +When the exporter runs from a systemd unit, drop the variables into the +``[Service]`` section so they survive restarts: + +.. code-block:: ini + + [Service] + Environment=LG_SERIAL_TRACE_DIR=/var/log/labgrid/serial + # Optional, for per-line timestamps: + #Environment=LG_SERIAL_TRACE_HEXDUMP=1 + +Apply with ``systemctl daemon-reload && systemctl restart labgrid-exporter``. +Environment variables are read only when the exporter process starts, so a +config change needs a restart, not just a reload. + +Permissions +^^^^^^^^^^^ + +Trace files are created with the exporter user's umask (typically mode +``0600``, owned by whatever user the systemd unit runs as). An admin reading +them needs either ``sudo`` privileges, membership in the exporter user's +group, or the umask widened so a known operator group can read. Keep this in +mind when oncall reaches for the log: ``cat`` as your normal user is not +enough. + +Log rotation is intentionally not handled by the exporter itself: files live +on the lab host where the admin already manages the filesystem, so rotation +is best left to ``logrotate`` or an equivalent tool pointed at +``LG_SERIAL_TRACE_DIR``. Because ser2net opens the trace file in append mode +on each acquire, rotating between sessions (e.g. with ``logrotate``'s +``copytruncate`` or ``daily`` options) is safe. + +Privacy and disk usage +^^^^^^^^^^^^^^^^^^^^^^ + +The trace captures **everything** that flows on the serial console, which can +include passwords typed at a U-Boot or login prompt, ssh keys pasted into a +shell, and anything else the user types or that the board prints. Treat +``LG_SERIAL_TRACE_DIR`` as an audit log: restrict the directory's permissions, +document its existence to users, and check the contents against your +organisation's privacy policy before turning it on in production. + +Disk usage is modest in default mode (the trace is the same byte stream as +the console, plus the OPEN/CLOSE markers) but grows roughly ten-fold in +hexdump mode, where each byte becomes a full timestamped line. Plan rotation +accordingly when enabling ``LG_SERIAL_TRACE_HEXDUMP=1`` on a busy lab. + Library ------- labgrid can be used directly as a Python library, without the infrastructure diff --git a/labgrid/remote/exporter.py b/labgrid/remote/exporter.py index ae635fef5..ec189cfa2 100755 --- a/labgrid/remote/exporter.py +++ b/labgrid/remote/exporter.py @@ -67,6 +67,7 @@ class ResourceExport(ResourceEntry): host = attr.ib(default=gethostname(), validator=attr.validators.instance_of(str)) proxy = attr.ib(default=None) proxy_required = attr.ib(default=False) + group_name = attr.ib(default="") user = attr.ib(default=None, init=False) local = attr.ib(init=False) local_params = attr.ib(init=False) @@ -231,6 +232,43 @@ def _get_params(self): }, } + @staticmethod + def _build_trace_args(group_name, user, path): + """Return ser2net YAML args for trace logging, or [] if disabled + + Reads LG_SERIAL_TRACE_DIR; when set, creates the directory and + builds a per-board, per-user file path under it, then returns + the YAML option pairs needed to enable trace-both for that + file. The board comes from the resource's group name when + available, falling back to the basename of the device path so + the filename is still meaningful. The user is the host/user + identity passed in by the coordinator (slashes are rewritten + to underscores so it is filesystem-safe), or ``unknown`` when + the coordinator did not send one. + + Setting LG_SERIAL_TRACE_HEXDUMP=1 additionally enables ser2net's + hexdump format for the trace, which prepends a timestamp to + every line at the cost of producing hex+ASCII rather than raw + text. Without it, only the OPEN and CLOSE session markers are + timestamped, but the traffic remains readable. + """ + trace_dir = os.environ.get("LG_SERIAL_TRACE_DIR") + if not trace_dir: + return [] + os.makedirs(trace_dir, exist_ok=True) + board = group_name or os.path.basename(path) + user_label = (user or "unknown").replace("/", "_") + trace_path = os.path.join(trace_dir, f"{board}-{user_label}.log") + args = [ + "-Y", + f" trace-both: {trace_path}", + "-Y", + " trace-both-timestamp: true", + ] + if os.environ.get("LG_SERIAL_TRACE_HEXDUMP") == "1": + args += ["-Y", " trace-both-hexdump: true"] + return args + def _start(self, start_params): """Start ``ser2net`` subprocess""" assert self.local.avail @@ -264,6 +302,12 @@ def _start(self, start_params): "-Y", " max-connections: 10", ] + # If LG_SERIAL_TRACE_DIR is set, ask ser2net to log all + # serial traffic for this device. Useful for centralised + # audit on the exporter host. ser2net is started fresh + # on each acquire and stopped on release, so the trace + # file scope is one acquire session. + cmd += self._build_trace_args(self.group_name, self.user, start_params["path"]) else: cmd = [ self.ser2net_bin, @@ -1030,7 +1074,11 @@ async def add_resource(self, group_name, resource_name, cls, params): proxy_req = self.isolated if issubclass(export_cls, ResourceExport): res = group[resource_name] = export_cls( - config, host=self.hostname, proxy=getfqdn(), proxy_required=proxy_req + config, + host=self.hostname, + proxy=getfqdn(), + proxy_required=proxy_req, + group_name=group_name, ) res.poll() else: diff --git a/man/labgrid-exporter.1 b/man/labgrid-exporter.1 index 92619f4d1..214571325 100644 --- a/man/labgrid-exporter.1 +++ b/man/labgrid-exporter.1 @@ -100,11 +100,34 @@ See <\X'tty: link https://labgrid.readthedocs.io/en/latest/configuration.html#ex for more information. .SS ENVIRONMENT VARIABLES .sp -The following environment variable can be used to configure labgrid\-exporter. +The following environment variables can be used to configure +labgrid\-exporter. .SS LG_COORDINATOR .sp This variable can be used to set the default coordinator in the format \fBHOST[:PORT]\fP (instead of using the \fB\-x\fP option). +.SS LG_SERIAL_TRACE_DIR +.sp +When set, the exporter records all serial\-port traffic for each +acquired resource into \fB/\-.log\fP, +where \fB\fP is the resource group name and \fB\fP is the +acquiring user identity reported by the coordinator (in +\fBhost/user\fP form, with \fB/\fP rewritten to \fB_\fP). Both directions +are captured verbatim, and ser2net writes timestamped OPEN/CLOSE +markers at the start and end of each session so the lab admin has +an independent record that is not affected by per\-client logging +options. +.sp +The directory is created on demand. A fresh ser2net instance is +started for each acquire, so repeated acquires by the same user on +the same board append to the same file. +.SS LG_SERIAL_TRACE_HEXDUMP +.sp +When set to \fB1\fP (and \fBLG_SERIAL_TRACE_DIR\fP is also set), the +exporter additionally enables ser2net\(aqs hexdump format for the +trace. This prepends a timestamp to every line at the cost of +producing hex+ASCII output instead of readable text. Use this +when correlating to the millisecond matters more than readability. .SS EXAMPLES .sp Start the exporter with the configuration file \fBmy\-config.yaml\fP: diff --git a/tests/test_exporter_trace.py b/tests/test_exporter_trace.py new file mode 100644 index 000000000..fdb0ed08e --- /dev/null +++ b/tests/test_exporter_trace.py @@ -0,0 +1,85 @@ +"""Tests for the LG_SERIAL_TRACE_DIR feature in labgrid-exporter.""" + +import pytest + +from labgrid.remote.exporter import SerialPortExport + + +@pytest.fixture +def trace_args(monkeypatch): + """Return a builder bound to whatever LG_SERIAL_TRACE_DIR is set.""" + + def _build(group_name="bbb", user="okaro/sjg", path="/dev/ttyUSB0"): + return SerialPortExport._build_trace_args(group_name, user, path) + + return _build + + +def test_returns_empty_when_env_unset(trace_args, monkeypatch): + monkeypatch.delenv("LG_SERIAL_TRACE_DIR", raising=False) + assert trace_args() == [] + + +def test_builds_yaml_args(trace_args, monkeypatch, tmp_path): + monkeypatch.setenv("LG_SERIAL_TRACE_DIR", str(tmp_path)) + args = trace_args(group_name="bbb", user="okaro/sjg") + assert args == [ + "-Y", + f" trace-both: {tmp_path}/bbb-okaro_sjg.log", + "-Y", + " trace-both-timestamp: true", + ] + + +def test_creates_missing_directory(trace_args, monkeypatch, tmp_path): + target = tmp_path / "newdir" + assert not target.exists() + monkeypatch.setenv("LG_SERIAL_TRACE_DIR", str(target)) + trace_args() + assert target.is_dir() + + +def test_user_slashes_rewritten(trace_args, monkeypatch, tmp_path): + monkeypatch.setenv("LG_SERIAL_TRACE_DIR", str(tmp_path)) + args = trace_args(user="myhost/alice") + assert args[1] == f" trace-both: {tmp_path}/bbb-myhost_alice.log" + + +def test_unknown_user_when_none(trace_args, monkeypatch, tmp_path): + monkeypatch.setenv("LG_SERIAL_TRACE_DIR", str(tmp_path)) + args = trace_args(user=None) + assert args[1] == f" trace-both: {tmp_path}/bbb-unknown.log" + + +def test_falls_back_to_path_basename(trace_args, monkeypatch, tmp_path): + monkeypatch.setenv("LG_SERIAL_TRACE_DIR", str(tmp_path)) + args = trace_args(group_name="", path="/dev/ttyUSB7") + assert args[1] == f" trace-both: {tmp_path}/ttyUSB7-okaro_sjg.log" + + +def test_existing_dir_is_reused(trace_args, monkeypatch, tmp_path): + """If the dir already exists, makedirs(exist_ok=True) must not fail.""" + monkeypatch.setenv("LG_SERIAL_TRACE_DIR", str(tmp_path)) + trace_args() + trace_args() # second call would error if exist_ok wasn't honoured + + +def test_hexdump_disabled_by_default(trace_args, monkeypatch, tmp_path): + monkeypatch.setenv("LG_SERIAL_TRACE_DIR", str(tmp_path)) + monkeypatch.delenv("LG_SERIAL_TRACE_HEXDUMP", raising=False) + args = trace_args() + assert " trace-both-hexdump: true" not in args + + +def test_hexdump_enabled_when_set(trace_args, monkeypatch, tmp_path): + monkeypatch.setenv("LG_SERIAL_TRACE_DIR", str(tmp_path)) + monkeypatch.setenv("LG_SERIAL_TRACE_HEXDUMP", "1") + args = trace_args() + assert args[-2:] == ["-Y", " trace-both-hexdump: true"] + + +def test_hexdump_not_enabled_when_other_value(trace_args, monkeypatch, tmp_path): + monkeypatch.setenv("LG_SERIAL_TRACE_DIR", str(tmp_path)) + monkeypatch.setenv("LG_SERIAL_TRACE_HEXDUMP", "true") + args = trace_args() + assert " trace-both-hexdump: true" not in args