Skip to content

Commit ad041de

Browse files
committed
Rename serverless to on-demand-sandbox APIs
Rename the Serverless activity RPC surface and messages to OnDemandSandbox equivalents across the proto, generated pb2/pb2_grpc stubs, and client usage. Add CPU and memory normalization/validation helpers to client (kubernetes-style quantities parsed using Decimal) and wire those into activity declaration construction. Remove DefaultAzureCredential usage in the worker (token credential set to None). Update examples, tests, and markdown tooling (enable front-matter in .pymarkdown.json), and add a new .github agent doc. Generated protobuf/grpc files and version/warning text were updated to reflect the renamed package and expected grpc tool/runtime versions.
1 parent ea10305 commit ad041de

12 files changed

Lines changed: 894 additions & 179 deletions

File tree

.github/agents/elementary-pr-teacher.agent.md

Lines changed: 594 additions & 0 deletions
Large diffs are not rendered by default.

.pymarkdown.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
{
2+
"extensions": {
3+
"front-matter": {
4+
"enabled": true
5+
}
6+
},
27
"plugins": {
38
"md013": {
49
"line_length": 100

durabletask-azuremanaged/durabletask/azuremanaged/extensions/serverless/client.py

Lines changed: 64 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# Licensed under the MIT License.
33

44
from dataclasses import dataclass, field
5+
from decimal import Decimal, InvalidOperation
56
from typing import Callable, Iterable, Optional, Sequence
67

78
import grpc
@@ -134,7 +135,7 @@ def build_serverless_activity_declaration(
134135
environment_variables: Optional[dict[str, str]] = None,
135136
max_concurrent_activities: int = DEFAULT_MAX_CONCURRENT_ACTIVITIES,
136137
entrypoint: Optional[Iterable[str]] = None,
137-
cmd: Optional[Iterable[str]] = None) -> pb.ServerlessActivityDeclaration:
138+
cmd: Optional[Iterable[str]] = None) -> pb.OnDemandSandboxActivityDeclaration:
138139
resolved_activity_names = resolve_activity_names(activity_names)
139140
if not resolved_activity_names:
140141
raise ValueError("Serverless activity declaration requires at least one activity name.")
@@ -154,19 +155,16 @@ def build_serverless_activity_declaration(
154155
if not image_ref:
155156
raise ValueError("Serverless activity image metadata requires a container image reference.")
156157

157-
if not cpu or not cpu.strip():
158-
raise ValueError("Serverless activity declaration requires CPU resources.")
158+
resolved_cpu = _normalize_cpu(cpu)
159+
resolved_memory = _normalize_memory(memory)
159160

160-
if not memory or not memory.strip():
161-
raise ValueError("Serverless activity declaration requires memory resources.")
162-
163-
declaration = pb.ServerlessActivityDeclaration(
161+
declaration = pb.OnDemandSandboxActivityDeclaration(
164162
worker_profile_id=worker_profile_id.strip(),
165-
image=pb.ServerlessActivityImage(
163+
image=pb.OnDemandSandboxActivityImage(
166164
image_ref=image_ref),
167-
resources=pb.ServerlessActivityResources(
168-
cpu=cpu.strip(),
169-
memory=memory.strip()),
165+
resources=pb.OnDemandSandboxActivityResources(
166+
cpu=resolved_cpu,
167+
memory=resolved_memory),
170168
max_concurrent_activities=max_concurrent_activities)
171169
declaration.activity_names.extend(resolved_activity_names)
172170
declaration.environment_variables.update(environment_variables or {})
@@ -175,9 +173,9 @@ def build_serverless_activity_declaration(
175173
return declaration
176174

177175

178-
def build_profile_serverless_activity_declarations() -> list[pb.ServerlessActivityDeclaration]:
176+
def build_profile_serverless_activity_declarations() -> list[pb.OnDemandSandboxActivityDeclaration]:
179177
"""Build serverless declarations from worker profile configuration."""
180-
declarations: list[pb.ServerlessActivityDeclaration] = []
178+
declarations: list[pb.OnDemandSandboxActivityDeclaration] = []
181179
activity_owners: dict[str, str] = {}
182180
for profile in _worker_profiles.values():
183181
activity_names = resolve_activity_names(profile.activity_names)
@@ -217,7 +215,7 @@ def build_serverless_worker_start(
217215
max_activities_count: int,
218216
activity_names: Iterable[str],
219217
substrate: Optional[str] = None,
220-
dts_sandbox_identifier: Optional[str] = None) -> pb.ServerlessActivityWorkerMessage:
218+
dts_sandbox_identifier: Optional[str] = None) -> pb.OnDemandSandboxActivityWorkerMessage:
221219
if not taskhub or not taskhub.strip():
222220
raise ValueError("Serverless activity worker registration requires a task hub name.")
223221

@@ -231,8 +229,8 @@ def build_serverless_worker_start(
231229
if not resolved_activity_names:
232230
raise ValueError("Serverless activity worker registration requires at least one registered activity.")
233231

234-
message = pb.ServerlessActivityWorkerMessage(
235-
start=pb.ServerlessActivityWorkerStart(
232+
message = pb.OnDemandSandboxActivityWorkerMessage(
233+
start=pb.OnDemandSandboxActivityWorkerStart(
236234
task_hub=taskhub.strip(),
237235
worker_profile_id=worker_profile_id.strip(),
238236
max_activities_count=max_activities_count,
@@ -242,12 +240,12 @@ def build_serverless_worker_start(
242240
return message
243241

244242

245-
def build_serverless_worker_heartbeat(active_activities_count: int) -> pb.ServerlessActivityWorkerMessage:
243+
def build_serverless_worker_heartbeat(active_activities_count: int) -> pb.OnDemandSandboxActivityWorkerMessage:
246244
if active_activities_count < 0:
247245
raise ValueError("Serverless activity worker active activity count cannot be negative.")
248246

249-
return pb.ServerlessActivityWorkerMessage(
250-
heartbeat=pb.ServerlessActivityWorkerHeartbeat(
247+
return pb.OnDemandSandboxActivityWorkerMessage(
248+
heartbeat=pb.OnDemandSandboxActivityWorkerHeartbeat(
251249
active_activities_count=active_activities_count))
252250

253251

@@ -278,7 +276,7 @@ def __init__(
278276
interceptors=resolved_interceptors,
279277
channel_options=channel_options)
280278
self._channel = channel
281-
self._stub = stubs.ServerlessActivitiesStub(channel)
279+
self._stub = stubs.OnDemandSandboxActivitiesStub(channel)
282280

283281
def close(self) -> None:
284282
if self._owns_channel:
@@ -291,17 +289,18 @@ def enable_serverless_activities(self) -> None:
291289
raise ValueError("No configured serverless activities were found.")
292290

293291
for declaration in declarations:
294-
self._stub.DeclareServerlessActivities(declaration)
292+
self._stub.DeclareOnDemandSandboxActivities(declaration)
295293

296294
def remove_serverless_activity_declaration(self, worker_profile_id: str) -> None:
297295
worker_profile_id = _normalize_required(worker_profile_id, "Worker profile ID is required.")
298-
self._stub.RemoveServerlessActivityDeclaration(
299-
pb.RemoveServerlessActivityDeclarationRequest(worker_profile_id=worker_profile_id))
296+
self._stub.RemoveOnDemandSandboxActivityDeclaration(
297+
pb.RemoveOnDemandSandboxActivityDeclarationRequest(worker_profile_id=worker_profile_id))
300298

301299
def connect_serverless_activity_worker(
302300
self,
303-
messages: Iterable[pb.ServerlessActivityWorkerMessage]) -> pb.ServerlessActivityWorkerSessionResult:
304-
return self._stub.ConnectServerlessActivityWorker(messages)
301+
messages: Iterable[pb.OnDemandSandboxActivityWorkerMessage]
302+
) -> pb.OnDemandSandboxActivityWorkerSessionResult:
303+
return self._stub.ConnectOnDemandSandboxActivityWorker(messages)
305304

306305

307306
def _normalize_optional_strings(values: Iterable[str]) -> list[str]:
@@ -314,6 +313,46 @@ def _normalize_required(value: str, message: str) -> str:
314313
return value.strip()
315314

316315

316+
def _normalize_cpu(value: str) -> str:
317+
normalized = _normalize_required(value, "Serverless activity declaration requires CPU resources.")
318+
milli_cpu = _try_parse_cpu_millicores(normalized)
319+
if milli_cpu is None or milli_cpu <= 0:
320+
raise ValueError(
321+
"Serverless activity CPU resources must be a positive Kubernetes-style CPU quantity. "
322+
"Use formats like '500m', '2', or '0.5'.")
323+
return normalized
324+
325+
326+
def _normalize_memory(value: str) -> str:
327+
normalized = _normalize_required(value, "Serverless activity declaration requires memory resources.")
328+
memory_mib = _try_parse_memory_mib(normalized)
329+
if memory_mib is None or memory_mib <= 0:
330+
raise ValueError(
331+
"Serverless activity memory resources must be a positive Kubernetes-style memory quantity. "
332+
"Use formats like '256Mi', '1Gi', or '2048'.")
333+
return normalized
334+
335+
336+
def _try_parse_cpu_millicores(value: str) -> Optional[int]:
337+
try:
338+
if value[-1:].lower() == "m":
339+
return int(Decimal(value[:-1]))
340+
return int(Decimal(value) * 1000)
341+
except (InvalidOperation, ValueError):
342+
return None
343+
344+
345+
def _try_parse_memory_mib(value: str) -> Optional[int]:
346+
try:
347+
if value[-2:].lower() == "gi":
348+
return int(Decimal(value[:-2]) * 1024)
349+
if value[-2:].lower() == "mi":
350+
return int(Decimal(value[:-2]))
351+
return int(Decimal(value))
352+
except (InvalidOperation, ValueError):
353+
return None
354+
355+
317356
def _parse_substrate(substrate: Optional[str]) -> "pb.SubstrateKind":
318357
if not substrate:
319358
return pb.SUBSTRATE_KIND_UNSPECIFIED

durabletask-azuremanaged/durabletask/azuremanaged/extensions/serverless/worker.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77

88
from typing import Iterator, Optional
99

10-
from azure.identity import DefaultAzureCredential
11-
1210
from durabletask.azuremanaged.extensions.serverless.client import (
1311
DEFAULT_MAX_CONCURRENT_ACTIVITIES,
1412
DEFAULT_WORKER_PROFILE_ID,
@@ -36,10 +34,7 @@ def __init__(self):
3634
resolved_host_address = _resolve_host_address()
3735
resolved_taskhub = _resolve_taskhub()
3836
resolved_secure_channel = _resolve_secure_channel(resolved_host_address)
39-
resolved_token_credential = (
40-
DefaultAzureCredential()
41-
if resolved_secure_channel
42-
else None)
37+
resolved_token_credential = None
4338
resolved_max_concurrent_activities = _resolve_max_concurrent_activities()
4439
concurrency_options = ConcurrencyOptions(
4540
maximum_concurrent_activity_work_items=resolved_max_concurrent_activities)

durabletask-azuremanaged/durabletask/azuremanaged/internal/serverless_activities_service.proto

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,36 @@
33

44
syntax = "proto3";
55

6-
package microsoft.durabletask.serverless;
6+
package microsoft.durabletask.ondemandsandbox;
77

8-
option csharp_namespace = "Microsoft.DurableTask.Protobuf.Serverless";
8+
option csharp_namespace = "Microsoft.DurableTask.Protobuf.OnDemandSandbox";
99

10-
service ServerlessActivities {
11-
// Opens a live serverless activity worker session. The first message must be a
12-
// start message with static worker metadata. Heartbeats carry dynamic state
13-
// only. Closing the stream deregisters the worker.
14-
rpc ConnectServerlessActivityWorker(stream ServerlessActivityWorkerMessage) returns (ServerlessActivityWorkerSessionResult);
10+
service OnDemandSandboxActivities {
11+
// Opens a live on-demand sandbox activity worker session. The first message
12+
// must be a start message with static worker metadata. Heartbeats carry
13+
// dynamic state only. Closing the stream deregisters the worker.
14+
rpc ConnectOnDemandSandboxActivityWorker(stream OnDemandSandboxActivityWorkerMessage) returns (OnDemandSandboxActivityWorkerSessionResult);
1515

16-
// Declares serverless activities before any live worker stream exists. This is a
17-
// configuration contract and does not advertise active worker capacity.
18-
rpc DeclareServerlessActivities(ServerlessActivityDeclaration) returns (ServerlessActivityDeclarationResult);
16+
// Declares on-demand sandbox activities before any live worker stream exists.
17+
// This is a configuration contract and does not advertise active worker
18+
// capacity.
19+
rpc DeclareOnDemandSandboxActivities(OnDemandSandboxActivityDeclaration) returns (OnDemandSandboxActivityDeclarationResult);
1920

20-
// Removes a serverless activity declaration so the backend stops waking new workers
21-
// for the specified worker profile. Existing workers are not terminated by this RPC.
22-
rpc RemoveServerlessActivityDeclaration(RemoveServerlessActivityDeclarationRequest) returns (RemoveServerlessActivityDeclarationResult);
21+
// Removes an on-demand sandbox activity declaration so the backend stops
22+
// waking new sandbox workers for the specified worker profile. Existing
23+
// workers are not terminated by this RPC.
24+
rpc RemoveOnDemandSandboxActivityDeclaration(RemoveOnDemandSandboxActivityDeclarationRequest) returns (RemoveOnDemandSandboxActivityDeclarationResult);
2325

2426
}
2527

26-
message ServerlessActivityWorkerMessage {
28+
message OnDemandSandboxActivityWorkerMessage {
2729
oneof message {
28-
ServerlessActivityWorkerStart start = 1;
29-
ServerlessActivityWorkerHeartbeat heartbeat = 2;
30+
OnDemandSandboxActivityWorkerStart start = 1;
31+
OnDemandSandboxActivityWorkerHeartbeat heartbeat = 2;
3032
}
3133
}
3234

33-
message ServerlessActivityWorkerStart {
35+
message OnDemandSandboxActivityWorkerStart {
3436
reserved 2;
3537
reserved "worker_instance_id";
3638

@@ -47,43 +49,43 @@ message ServerlessActivityWorkerStart {
4749
repeated string activity_names = 7;
4850
}
4951

50-
message ServerlessActivityWorkerHeartbeat {
52+
message OnDemandSandboxActivityWorkerHeartbeat {
5153
int32 active_activities_count = 1;
5254
}
5355

54-
message ServerlessActivityWorkerSessionResult {
56+
message OnDemandSandboxActivityWorkerSessionResult {
5557
bool accepted = 1;
5658
string message = 2;
5759
}
5860

59-
message ServerlessActivityDeclaration {
61+
message OnDemandSandboxActivityDeclaration {
6062
string worker_profile_id = 2;
6163
repeated string activity_names = 3;
62-
ServerlessActivityImage image = 4;
64+
OnDemandSandboxActivityImage image = 4;
6365
map<string, string> environment_variables = 5;
6466
int32 max_concurrent_activities = 6;
65-
ServerlessActivityResources resources = 7;
67+
OnDemandSandboxActivityResources resources = 7;
6668
repeated string entrypoint = 8;
6769
repeated string cmd = 9;
6870
}
6971

70-
message ServerlessActivityImage {
72+
message OnDemandSandboxActivityImage {
7173
string image_ref = 1;
7274
}
7375

74-
message ServerlessActivityResources {
76+
message OnDemandSandboxActivityResources {
7577
string cpu = 1;
7678
string memory = 2;
7779
}
7880

79-
message ServerlessActivityDeclarationResult {
81+
message OnDemandSandboxActivityDeclarationResult {
8082
}
8183

82-
message RemoveServerlessActivityDeclarationRequest {
84+
message RemoveOnDemandSandboxActivityDeclarationRequest {
8385
string worker_profile_id = 1;
8486
}
8587

86-
message RemoveServerlessActivityDeclarationResult {
88+
message RemoveOnDemandSandboxActivityDeclarationResult {
8789
}
8890

8991
// Compute substrate executing the activity worker.

0 commit comments

Comments
 (0)