Skip to content

Commit ce38437

Browse files
authored
RSDK-7698: Add Shutdown command to Python SDK (#633)
1 parent 13bf1b6 commit ce38437

File tree

3 files changed

+65
-0
lines changed

3 files changed

+65
-0
lines changed

src/viam/robot/client.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from threading import RLock
55
from typing import Any, Dict, List, Optional, Union
66

7+
from grpclib import GRPCError, Status
78
from grpclib.client import Channel
89
from typing_extensions import Self
910

@@ -33,6 +34,7 @@
3334
ResourceNamesRequest,
3435
ResourceNamesResponse,
3536
RobotServiceStub,
37+
ShutdownRequest,
3638
StopAllRequest,
3739
StopExtraParameters,
3840
TransformPoseRequest,
@@ -787,3 +789,33 @@ async def get_cloud_metadata(self) -> GetCloudMetadataResponse:
787789

788790
request = GetCloudMetadataRequest()
789791
return await self._client.GetCloudMetadata(request)
792+
793+
############
794+
# Shutdown #
795+
############
796+
797+
async def shutdown(self):
798+
"""
799+
Shutdown shuts down the robot.
800+
801+
Raises:
802+
GRPCError: Raised with DeadlineExceeded status if shutdown request times out, or if
803+
robot server shuts down before having a chance to send a response. Raised with
804+
status Unavailable if server is unavailable, or if robot server is in the process of
805+
shutting down when response is ready.
806+
"""
807+
request = ShutdownRequest()
808+
try:
809+
await self._client.Shutdown(request)
810+
LOGGER.info("robot shutdown successful")
811+
except GRPCError as e:
812+
if e.status == Status.INTERNAL or e.status == Status.UNKNOWN:
813+
LOGGER.info("robot shutdown successful")
814+
elif e.status == Status.UNAVAILABLE:
815+
LOGGER.warn("server unavailable, likely due to successful robot shutdown")
816+
raise e
817+
elif e.status == Status.DEADLINE_EXCEEDED:
818+
LOGGER.warn("request timeout, robot shutdown may still be successful")
819+
raise e
820+
else:
821+
raise e

tests/mocks/robot.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
RestartModuleResponse,
3232
SendSessionHeartbeatRequest,
3333
SendSessionHeartbeatResponse,
34+
ShutdownRequest,
35+
ShutdownResponse,
3436
StartSessionRequest,
3537
StartSessionResponse,
3638
StopAllRequest,
@@ -115,3 +117,6 @@ async def Log(self, stream: Stream[LogRequest, LogResponse]) -> None:
115117

116118
async def GetCloudMetadata(self, stream: Stream[GetCloudMetadataRequest, GetCloudMetadataResponse]) -> None:
117119
raise MethodNotImplementedError("GetCloudMetadata").grpc_error
120+
121+
async def Shutdown(self, stream: Stream[ShutdownRequest, ShutdownResponse]) -> None:
122+
raise MethodNotImplementedError("Shutdown").grpc_error

tests/test_robot.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
22
from typing import Any, Dict, List, Optional, Tuple
3+
from unittest import mock
34

45
import pytest
56
from google.protobuf.struct_pb2 import Struct, Value
@@ -39,6 +40,8 @@
3940
ResourceNamesRequest,
4041
ResourceNamesResponse,
4142
RobotServiceStub,
43+
ShutdownRequest,
44+
ShutdownResponse,
4245
Status,
4346
StopAllRequest,
4447
StopExtraParameters,
@@ -209,13 +212,20 @@ async def GetCloudMetadata(stream: Stream[GetCloudMetadataRequest, GetCloudMetad
209212
assert request is not None
210213
await stream.send_message(GET_CLOUD_METADATA_RESPONSE)
211214

215+
async def Shutdown(stream: Stream[ShutdownRequest, ShutdownResponse]) -> None:
216+
request = await stream.recv_message()
217+
assert request is not None
218+
response = ShutdownResponse()
219+
await stream.send_message(response)
220+
212221
manager = ResourceManager(resources)
213222
service = RobotService(manager)
214223
service.FrameSystemConfig = Config
215224
service.TransformPose = TransformPose
216225
service.DiscoverComponents = DiscoverComponents
217226
service.GetOperations = GetOperations
218227
service.GetCloudMetadata = GetCloudMetadata
228+
service.Shutdown = Shutdown
219229

220230
return service
221231

@@ -599,3 +609,21 @@ async def get_geometries(
599609

600610
await client.close()
601611
Registry._SUBTYPES[Arm.SUBTYPE].create_rpc_client = old_create_client
612+
613+
@pytest.mark.asyncio
614+
async def test_shutdown(self, service: RobotService):
615+
async with ChannelFor([service]) as channel:
616+
617+
async def shutdown_client_mock(self):
618+
return await self._client.Shutdown(ShutdownRequest())
619+
620+
client = await RobotClient.with_channel(channel, RobotClient.Options())
621+
622+
with mock.patch("viam.robot.client.RobotClient.shutdown") as shutdown_mock:
623+
shutdown_mock.return_value = await shutdown_client_mock(client)
624+
shutdown_response = await client.shutdown()
625+
626+
assert shutdown_response == ShutdownResponse()
627+
shutdown_mock.assert_called_once()
628+
629+
await client.close()

0 commit comments

Comments
 (0)