|
34 | 34 | DEFAULT_REPO_DIR = "/workflow" |
35 | 35 | MIN_PROBE_TIMEOUT = 1 |
36 | 36 | MIN_PROBE_INTERVAL = 1 |
| 37 | +DEFAULT_PROBE_URL = "/" |
| 38 | +DEFAULT_PROBE_TIMEOUT = 10 |
| 39 | +DEFAULT_PROBE_INTERVAL = 15 |
| 40 | +DEFAULT_PROBE_READY_AFTER = 1 |
| 41 | +MAX_PROBE_URL_LEN = 2048 |
37 | 42 |
|
38 | 43 |
|
39 | 44 | class RunConfigurationType(str, Enum): |
@@ -166,53 +171,69 @@ class RateLimit(CoreModel): |
166 | 171 |
|
167 | 172 | class ProbeConfig(CoreModel): |
168 | 173 | type: Literal["http"] # expect other probe types in the future, namely `exec` |
169 | | - url: Annotated[str, Field(description="The URL to request")] = "/" |
| 174 | + url: Annotated[ |
| 175 | + Optional[str], Field(description=f"The URL to request. Defaults to `{DEFAULT_PROBE_URL}`") |
| 176 | + ] = None |
170 | 177 | timeout: Annotated[ |
171 | | - Union[int, str], |
172 | | - Field(description=("Maximum amount of time the HTTP request is allowed to take")), |
173 | | - ] = "10s" |
| 178 | + Optional[Union[int, str]], |
| 179 | + Field( |
| 180 | + description=( |
| 181 | + f"Maximum amount of time the HTTP request is allowed to take. Defaults to `{DEFAULT_PROBE_TIMEOUT}s`" |
| 182 | + ) |
| 183 | + ), |
| 184 | + ] = None |
174 | 185 | interval: Annotated[ |
175 | | - Union[int, str], |
| 186 | + Optional[Union[int, str]], |
176 | 187 | Field( |
177 | 188 | description=( |
178 | 189 | "Minimum amount of time between the end of one probe execution" |
179 | | - " and the start of the next" |
| 190 | + f" and the start of the next. Defaults to `{DEFAULT_PROBE_INTERVAL}s`" |
180 | 191 | ) |
181 | 192 | ), |
182 | | - ] = "15s" |
| 193 | + ] = None |
183 | 194 | ready_after: Annotated[ |
184 | | - int, |
| 195 | + Optional[int], |
185 | 196 | Field( |
186 | 197 | ge=1, |
187 | 198 | description=( |
188 | | - "The number of consecutive successful probe executions required for the job" |
189 | | - " to be considered ready. Used during rolling deployments" |
| 199 | + "The number of consecutive successful probe executions required for the replica" |
| 200 | + " to be considered ready. Used during rolling deployments." |
| 201 | + f" Defaults to `{DEFAULT_PROBE_READY_AFTER}`" |
190 | 202 | ), |
191 | 203 | ), |
192 | | - ] = 1 |
| 204 | + ] = None |
193 | 205 |
|
194 | 206 | class Config: |
195 | 207 | frozen = True |
196 | 208 |
|
197 | 209 | @validator("timeout") |
198 | | - def parse_timeout(cls, v: Union[int, str]) -> int: |
| 210 | + def parse_timeout(cls, v: Optional[Union[int, str]]) -> Optional[int]: |
| 211 | + if v is None: |
| 212 | + return v |
199 | 213 | parsed = parse_duration(v) |
200 | 214 | if parsed < MIN_PROBE_TIMEOUT: |
201 | 215 | raise ValueError(f"Probe timeout cannot be shorter than {MIN_PROBE_TIMEOUT}s") |
202 | 216 | return parsed |
203 | 217 |
|
204 | 218 | @validator("interval") |
205 | | - def parse_interval(cls, v: Union[int, str]) -> int: |
| 219 | + def parse_interval(cls, v: Optional[Union[int, str]]) -> Optional[int]: |
| 220 | + if v is None: |
| 221 | + return v |
206 | 222 | parsed = parse_duration(v) |
207 | 223 | if parsed < MIN_PROBE_INTERVAL: |
208 | 224 | raise ValueError(f"Probe interval cannot be shorter than {MIN_PROBE_INTERVAL}s") |
209 | 225 | return parsed |
210 | 226 |
|
211 | 227 | @validator("url") |
212 | | - def validate_url(cls, v: str) -> str: |
213 | | - # TODO: stricter constraints to avoid HTTPX URL parsing errors |
| 228 | + def validate_url(cls, v: Optional[str]) -> Optional[str]: |
| 229 | + if v is None: |
| 230 | + return v |
214 | 231 | if not v.startswith("/"): |
215 | 232 | raise ValueError("Must start with `/`") |
| 233 | + if len(v) > MAX_PROBE_URL_LEN: |
| 234 | + raise ValueError(f"Cannot be longer than {MAX_PROBE_URL_LEN} characters") |
| 235 | + if not v.isprintable(): |
| 236 | + raise ValueError("Cannot contain non-printable characters") |
216 | 237 | return v |
217 | 238 |
|
218 | 239 |
|
@@ -504,7 +525,7 @@ class ServiceConfigurationParams(CoreModel): |
504 | 525 | rate_limits: Annotated[list[RateLimit], Field(description="Rate limiting rules")] = [] |
505 | 526 | probes: Annotated[ |
506 | 527 | list[ProbeConfig], |
507 | | - Field(unique_items=True, description="List of probes used to determine job health"), |
| 528 | + Field(description="List of probes used to determine job health"), |
508 | 529 | ] = [] |
509 | 530 |
|
510 | 531 | @validator("port") |
@@ -569,6 +590,16 @@ def validate_rate_limits(cls, v: list[RateLimit]) -> list[RateLimit]: |
569 | 590 | ) |
570 | 591 | return v |
571 | 592 |
|
| 593 | + @validator("probes") |
| 594 | + def validate_probes(cls, v: list[ProbeConfig]) -> list[ProbeConfig]: |
| 595 | + if len(v) != len(set(v)): |
| 596 | + # Using a custom validator instead of Field(unique_items=True) to avoid Pydantic bug: |
| 597 | + # https://github.com/pydantic/pydantic/issues/3765 |
| 598 | + # Because of the bug, our gen_schema_reference.py fails to determine the type of |
| 599 | + # ServiceConfiguration.probes and insert the correct hyperlink. |
| 600 | + raise ValueError("Probes must be unique") |
| 601 | + return v |
| 602 | + |
572 | 603 |
|
573 | 604 | class ServiceConfiguration( |
574 | 605 | ProfileParams, BaseRunConfigurationWithCommands, ServiceConfigurationParams |
|
0 commit comments