1919DEFAULT_CPU = "1000m"
2020DEFAULT_MEMORY = "2048Mi"
2121DEFAULT_MAX_CONCURRENT_ACTIVITIES = 100
22+ MIN_CPU_MILLICORES = 250
23+ MAX_CPU_MILLICORES = 16000
24+ CPU_STEP_MILLICORES = 250
25+ MEMORY_MIB_PER_CORE = 2 * 1024
2226
2327
2428@dataclass
25- class SandboxWorkerProfileOptions :
26- """Options for a decorated sandbox worker profile ."""
29+ class SandboxWorkerProfileImageOptions :
30+ """Options for the sandbox worker image DTS should start ."""
2731
28- worker_profile_id : str
2932 # Full OCI image reference for the sandbox worker container, for example
3033 # "myregistry.azurecr.io/workers/hello:1.0" or
3134 # "myregistry.azurecr.io/workers/hello@sha256:0123456789abcdef...".
32- container_image : Optional [str ] = None
33- image_pull_managed_identity_client_id : Optional [str ] = None
35+ image_ref : Optional [str ] = None
36+ managed_identity_client_id : Optional [str ] = None
37+ entrypoint : list [str ] = field (default_factory = list [str ])
38+ cmd : list [str ] = field (default_factory = list [str ])
39+
40+
41+ @dataclass
42+ class SandboxWorkerProfileOptions :
43+ """Options for a decorated sandbox worker profile."""
44+
45+ @dataclass (frozen = True )
46+ class Activity :
47+ """Activity name and optional version for a sandbox worker profile."""
48+
49+ name : str
50+ version : Optional [str ]
51+
52+ worker_profile_id : str
53+ image : SandboxWorkerProfileImageOptions = field (default_factory = SandboxWorkerProfileImageOptions )
3454 scheduler_managed_identity_client_id : Optional [str ] = None
3555 cpu : str = DEFAULT_CPU
3656 memory : str = DEFAULT_MEMORY
3757 environment_variables : dict [str , str ] = field (default_factory = dict [str , str ])
3858 max_concurrent_activities : int = DEFAULT_MAX_CONCURRENT_ACTIVITIES
39- entrypoint : list [str ] = field (default_factory = list [str ])
40- cmd : list [str ] = field (default_factory = list [str ])
4159 activities : list [SandboxActivity ] = field (default_factory = list [SandboxActivity ])
4260
4361 def add_activity (
@@ -50,6 +68,11 @@ def add_activity(
5068 name = normalize_required (activity_name , "Sandbox activity name is required." ),
5169 version = (version .strip () if version and version .strip () else None )))
5270
71+ def add_activities (self , activities : Iterable [Activity ]) -> None :
72+ """Add activity names and versions to the sandbox worker profile."""
73+ for activity in activities :
74+ self .add_activity (activity .name , activity .version )
75+
5376
5477class SandboxWorkerProfile :
5578 """Base class for configuring a decorated sandbox worker profile."""
@@ -94,21 +117,19 @@ def _build_sandbox_worker_profile(
94117 activities : Iterable [SandboxActivity ],
95118 scheduler_managed_identity_client_id : Optional [str ],
96119 worker_profile_id : str ,
97- container_image : Optional [str ] = None ,
98- image_pull_managed_identity_client_id : Optional [str ] = None ,
120+ image : Optional [SandboxWorkerProfileImageOptions ] = None ,
99121 cpu : str = DEFAULT_CPU ,
100122 memory : str = DEFAULT_MEMORY ,
101123 environment_variables : Optional [dict [str , str ]] = None ,
102- max_concurrent_activities : int = DEFAULT_MAX_CONCURRENT_ACTIVITIES ,
103- entrypoint : Optional [Iterable [str ]] = None ,
104- cmd : Optional [Iterable [str ]] = None ) -> pb .SandboxWorkerProfile :
124+ max_concurrent_activities : int = DEFAULT_MAX_CONCURRENT_ACTIVITIES ) -> pb .SandboxWorkerProfile :
105125 """Build a sandbox activity worker_profile.
106126
107127 Args:
108- container_image: Full OCI image reference for the sandbox worker container ,
128+ image: Sandbox worker image options with the full OCI image reference ,
109129 such as "myregistry.azurecr.io/workers/hello:1.0" or
110130 "myregistry.azurecr.io/workers/hello@sha256:0123456789abcdef...".
111131 """
132+ image_options = image or SandboxWorkerProfileImageOptions ()
112133 resolved_activities = resolve_activities (activities )
113134 if not resolved_activities :
114135 raise ValueError ("Sandbox activity worker_profile requires at least one activity." )
@@ -120,7 +141,7 @@ def _build_sandbox_worker_profile(
120141 raise ValueError ("Sandbox activity max concurrent activities must be greater than zero." )
121142
122143 image_ref = normalize_required (
123- container_image ,
144+ image_options . image_ref ,
124145 "Sandbox activity image metadata requires a container image reference like "
125146 "'myregistry.azurecr.io/workers/hello:1.0' or "
126147 "'myregistry.azurecr.io/workers/hello@sha256:...'." )
@@ -129,11 +150,11 @@ def _build_sandbox_worker_profile(
129150 scheduler_managed_identity_client_id ,
130151 "Sandbox activity worker_profile requires the managed identity client ID workers use to connect to Durable Task Scheduler." )
131152 resolved_image_pull_managed_identity_client_id = normalize_required (
132- image_pull_managed_identity_client_id ,
133- "Sandbox activity worker_profile requires the managed identity client ID ADC uses to pull the worker image." )
153+ image_options . managed_identity_client_id ,
154+ "Sandbox activity worker_profile requires the managed identity client ID used to pull the worker image." )
134155
135- resolved_cpu = _normalize_cpu (cpu )
136- resolved_memory = _normalize_memory (memory )
156+ resolved_cpu , cpu_millicores = _normalize_cpu (cpu )
157+ resolved_memory = _normalize_memory (memory , cpu_millicores )
137158
138159 worker_profile = pb .SandboxWorkerProfile (
139160 worker_profile_id = worker_profile_id .strip (),
@@ -150,8 +171,8 @@ def _build_sandbox_worker_profile(
150171 for activity in resolved_activities
151172 ])
152173 worker_profile .environment_variables .update (environment_variables or {})
153- worker_profile .image .entrypoint .extend (_normalize_optional_strings (entrypoint or [] ))
154- worker_profile .image .cmd .extend (_normalize_optional_strings (cmd or [] ))
174+ worker_profile .image .entrypoint .extend (_normalize_optional_strings (image_options . entrypoint ))
175+ worker_profile .image .cmd .extend (_normalize_optional_strings (image_options . cmd ))
155176 return worker_profile
156177
157178
@@ -175,15 +196,12 @@ def build_sandbox_worker_profiles() -> list[pb.SandboxWorkerProfile]:
175196 worker_profiles .append (_build_sandbox_worker_profile (
176197 activities = activities ,
177198 worker_profile_id = profile .worker_profile_id ,
178- container_image = profile .container_image ,
179- image_pull_managed_identity_client_id = profile .image_pull_managed_identity_client_id ,
199+ image = profile .image ,
180200 scheduler_managed_identity_client_id = profile .scheduler_managed_identity_client_id ,
181201 cpu = profile .cpu ,
182202 memory = profile .memory ,
183203 environment_variables = profile .environment_variables ,
184- max_concurrent_activities = profile .max_concurrent_activities ,
185- entrypoint = profile .entrypoint ,
186- cmd = profile .cmd ))
204+ max_concurrent_activities = profile .max_concurrent_activities ))
187205
188206 return worker_profiles
189207
@@ -236,46 +254,61 @@ def _normalize_optional_strings(values: Iterable[str]) -> list[str]:
236254 return [value .strip () for value in values if value and value .strip ()]
237255
238256
239- def _normalize_cpu (value : str ) -> str :
257+ def _normalize_cpu (value : str ) -> tuple [ str , int ] :
240258 normalized = normalize_required (value , "Sandbox activity worker_profile requires CPU resources." )
241259 milli_cpu = _try_parse_cpu_millicores (normalized )
242- if milli_cpu is None or milli_cpu <= 0 :
260+ if (milli_cpu is None
261+ or milli_cpu < MIN_CPU_MILLICORES
262+ or milli_cpu > MAX_CPU_MILLICORES
263+ or milli_cpu % CPU_STEP_MILLICORES != 0 ):
243264 raise ValueError (
244- "Sandbox activity CPU resources must be a positive Kubernetes-style CPU quantity. "
265+ "Sandbox activity CPU resources must match an ADC sandbox CPU tier: "
266+ "250m through 16000m, in 250m increments. "
245267 "Use formats like '500m', '2', or '0.5'." )
246- return normalized
268+ return normalized , milli_cpu
247269
248270
249- def _normalize_memory (value : str ) -> str :
271+ def _normalize_memory (value : str , cpu_millicores : int ) -> str :
250272 normalized = normalize_required (value , "Sandbox activity worker_profile requires memory resources." )
273+ max_memory_mib = cpu_millicores * MEMORY_MIB_PER_CORE // 1000
251274 memory_mib = _try_parse_memory_mib (normalized )
252275 if memory_mib is None or memory_mib <= 0 :
253276 raise ValueError (
254277 "Sandbox activity memory resources must be a positive Kubernetes-style memory quantity. "
255278 "Use formats like '256Mi', '1Gi', or '2048'." )
279+ if memory_mib > max_memory_mib :
280+ raise ValueError (
281+ "Sandbox activity memory resources exceed the ADC sandbox tier maximum for the configured CPU. "
282+ f"Maximum memory for CPU '{ cpu_millicores } m' is { max_memory_mib } Mi." )
256283 return normalized
257284
258285
259286def _try_parse_cpu_millicores (value : str ) -> Optional [int ]:
260287 try :
261288 if value [- 1 :].lower () == "m" :
262289 return int (value [:- 1 ])
263- return int (Decimal (value ) * 1000 )
290+ millicores = Decimal (value ) * 1000
291+ return int (millicores ) if millicores == millicores .to_integral_value () else None
264292 except (InvalidOperation , ValueError ):
265293 return None
266294
267295
268296def _try_parse_memory_mib (value : str ) -> Optional [int ]:
269297 try :
270298 if value [- 2 :].lower () == "gi" :
271- return int (Decimal (value [:- 2 ]) * 1024 )
299+ return _try_convert_memory_to_mib (Decimal (value [:- 2 ]), 1024 )
272300 if value [- 2 :].lower () == "mi" :
273- return int (Decimal (value [:- 2 ]))
274- return int (Decimal (value ))
301+ return _try_convert_memory_to_mib (Decimal (value [:- 2 ]), 1 )
302+ return _try_convert_memory_to_mib (Decimal (value ), 1 )
275303 except (InvalidOperation , ValueError ):
276304 return None
277305
278306
307+ def _try_convert_memory_to_mib (value : Decimal , multiplier : int ) -> Optional [int ]:
308+ memory_mib = value * multiplier
309+ return int (memory_mib ) if memory_mib == memory_mib .to_integral_value () else None
310+
311+
279312def _parse_sandbox_provider (sandbox_provider : Optional [str ]) -> "pb.SandboxProviderKind" :
280313 if not sandbox_provider :
281314 return pb .SANDBOX_PROVIDER_KIND_UNSPECIFIED
0 commit comments