Skip to content

Commit 84b76cc

Browse files
authored
Add Restart Api Support at DurableOrchestrationClient (#597)
* initial commit * udpate with comments
1 parent d9622c6 commit 84b76cc

3 files changed

Lines changed: 94 additions & 1 deletion

File tree

azure/durable_functions/models/DurableOrchestrationClient.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,50 @@ async def suspend(self, instance_id: str, reason: str) -> None:
791791
if error_message:
792792
raise Exception(error_message)
793793

794+
async def restart(self, instance_id: str,
795+
restart_with_new_instance_id: bool = True) -> str:
796+
"""Restart an orchestration instance with its original input.
797+
798+
Parameters
799+
----------
800+
instance_id : str
801+
The ID of the orchestration instance to restart.
802+
restart_with_new_instance_id : bool
803+
If True, the restarted instance will use a new instance ID.
804+
If False, the restarted instance will reuse the original instance ID.
805+
806+
Raises
807+
------
808+
Exception:
809+
When the instance with the given ID is not found.
810+
811+
Returns
812+
-------
813+
str
814+
The instance ID of the restarted orchestration.
815+
"""
816+
restart_with_new_instance_id_str = str(restart_with_new_instance_id).lower()
817+
request_url = f"{self._orchestration_bindings.rpc_base_url}instances/{instance_id}/" \
818+
f"restart?restartWithNewInstanceId={restart_with_new_instance_id_str}"
819+
response = await self._post_async_request(
820+
request_url,
821+
None,
822+
function_invocation_id=self._function_invocation_id)
823+
switch_statement = {
824+
202: lambda: None, # instance is restarted
825+
410: lambda: None, # instance completed
826+
404: lambda: f"No instance with ID '{instance_id}' found.",
827+
}
828+
829+
has_error_message = switch_statement.get(
830+
response[0],
831+
lambda: f"The operation failed with an unexpected status code {response[0]}")
832+
error_message = has_error_message()
833+
if error_message:
834+
raise Exception(error_message)
835+
836+
return response[1] if response[1] else instance_id
837+
794838
async def resume(self, instance_id: str, reason: str) -> None:
795839
"""Resume the specified orchestration instance.
796840

tests/conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ def get_binding_string():
4646
"resumePostUri": f"{BASE_URL}/instances/INSTANCEID/resume?reason="
4747
"{text}&taskHub="
4848
f"{TASK_HUB_NAME}&connection=Storage&code={AUTH_CODE}",
49+
"restartPostUri": f"{BASE_URL}/instances/INSTANCEID/restart?taskHub="
50+
f"{TASK_HUB_NAME}&connection=Storage&code={AUTH_CODE}",
4951
},
5052
"rpcBaseUrl": RPC_BASE_URL
5153
}

tests/models/test_DurableOrchestrationClient.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,11 @@ def test_create_check_status_response(binding_string):
157157
"resumePostUri":
158158
r"http://test_azure.net/runtime/webhooks/durabletask/instances/"
159159
r"2e2568e7-a906-43bd-8364-c81733c5891e/resume"
160-
r"?reason={text}&taskHub=TASK_HUB_NAME&connection=Storage&code=AUTH_CODE"
160+
r"?reason={text}&taskHub=TASK_HUB_NAME&connection=Storage&code=AUTH_CODE",
161+
"restartPostUri":
162+
r"http://test_azure.net/runtime/webhooks/durabletask/instances/"
163+
r"2e2568e7-a906-43bd-8364-c81733c5891e/restart"
164+
r"?taskHub=TASK_HUB_NAME&connection=Storage&code=AUTH_CODE"
161165
}
162166
for key, _ in http_management_payload.items():
163167
http_management_payload[key] = replace_stand_in_bits(http_management_payload[key])
@@ -742,6 +746,49 @@ async def test_post_500_resume(binding_string):
742746
await client.resume(TEST_INSTANCE_ID, raw_reason)
743747

744748

749+
@pytest.mark.asyncio
750+
async def test_restart_with_new_instance_id(binding_string):
751+
"""Test restart calls the HTTP restart endpoint with restartWithNewInstanceId=true."""
752+
new_instance_id = "new-instance-id-1234"
753+
754+
post_mock = MockRequest(
755+
expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/restart?restartWithNewInstanceId=true",
756+
response=[202, new_instance_id])
757+
758+
client = DurableOrchestrationClient(binding_string)
759+
client._post_async_request = post_mock.post
760+
761+
result = await client.restart(TEST_INSTANCE_ID)
762+
assert result == new_instance_id
763+
764+
765+
@pytest.mark.asyncio
766+
async def test_restart_with_same_instance_id(binding_string):
767+
"""Test restart calls the HTTP restart endpoint with restartWithNewInstanceId=false."""
768+
post_mock = MockRequest(
769+
expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/restart?restartWithNewInstanceId=false",
770+
response=[202, TEST_INSTANCE_ID])
771+
772+
client = DurableOrchestrationClient(binding_string)
773+
client._post_async_request = post_mock.post
774+
775+
result = await client.restart(TEST_INSTANCE_ID, restart_with_new_instance_id=False)
776+
assert result == TEST_INSTANCE_ID
777+
778+
779+
@pytest.mark.asyncio
780+
async def test_restart_instance_not_found(binding_string):
781+
"""Test restart raises exception when instance is not found."""
782+
post_mock = MockRequest(
783+
expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/restart?restartWithNewInstanceId=true",
784+
response=[404, None])
785+
786+
client = DurableOrchestrationClient(binding_string)
787+
client._post_async_request = post_mock.post
788+
789+
with pytest.raises(Exception) as ex:
790+
await client.restart(TEST_INSTANCE_ID)
791+
assert f"No instance with ID '{TEST_INSTANCE_ID}' found." in str(ex.value)
745792
# Tests for function_invocation_id parameter
746793
def test_client_stores_function_invocation_id(binding_string):
747794
"""Test that the client stores the function_invocation_id parameter."""

0 commit comments

Comments
 (0)