|
19 | 19 | from dstack._internal.core.models.services import AnyModel, OpenAIChatModel |
20 | 20 | from dstack._internal.core.models.unix import UnixUser |
21 | 21 | from dstack._internal.core.models.volumes import MountPoint, VolumeConfiguration, parse_mount_point |
| 22 | +from dstack._internal.utils.common import has_duplicates |
22 | 23 | from dstack._internal.utils.json_utils import ( |
23 | 24 | pydantic_orjson_dumps_with_indent, |
24 | 25 | ) |
|
38 | 39 | DEFAULT_PROBE_TIMEOUT = 10 |
39 | 40 | DEFAULT_PROBE_INTERVAL = 15 |
40 | 41 | DEFAULT_PROBE_READY_AFTER = 1 |
| 42 | +DEFAULT_PROBE_METHOD = "get" |
41 | 43 | MAX_PROBE_URL_LEN = 2048 |
42 | 44 |
|
43 | 45 |
|
@@ -169,11 +171,54 @@ class RateLimit(CoreModel): |
169 | 171 | ] = 0 |
170 | 172 |
|
171 | 173 |
|
| 174 | +HTTPMethod = Literal["get", "post", "put", "delete", "patch", "head"] |
| 175 | + |
| 176 | + |
| 177 | +class HTTPHeaderSpec(CoreModel): |
| 178 | + name: Annotated[ |
| 179 | + str, |
| 180 | + Field( |
| 181 | + description="The name of the HTTP header", |
| 182 | + min_length=1, |
| 183 | + max_length=256, |
| 184 | + ), |
| 185 | + ] |
| 186 | + value: Annotated[ |
| 187 | + str, |
| 188 | + Field( |
| 189 | + description="The value of the HTTP header", |
| 190 | + min_length=1, |
| 191 | + max_length=2048, |
| 192 | + ), |
| 193 | + ] |
| 194 | + |
| 195 | + |
172 | 196 | class ProbeConfig(CoreModel): |
173 | 197 | type: Literal["http"] # expect other probe types in the future, namely `exec` |
174 | 198 | url: Annotated[ |
175 | 199 | Optional[str], Field(description=f"The URL to request. Defaults to `{DEFAULT_PROBE_URL}`") |
176 | 200 | ] = None |
| 201 | + method: Annotated[ |
| 202 | + Optional[HTTPMethod], |
| 203 | + Field( |
| 204 | + description=( |
| 205 | + "The HTTP method to use for the probe (e.g., `get`, `post`, etc.)." |
| 206 | + f" Defaults to `{DEFAULT_PROBE_METHOD}`" |
| 207 | + ) |
| 208 | + ), |
| 209 | + ] = None |
| 210 | + headers: Annotated[ |
| 211 | + list[HTTPHeaderSpec], |
| 212 | + Field(description="A list of HTTP headers to include in the request", max_items=16), |
| 213 | + ] = [] |
| 214 | + body: Annotated[ |
| 215 | + Optional[str], |
| 216 | + Field( |
| 217 | + description="The HTTP request body to send with the probe", |
| 218 | + min_length=1, |
| 219 | + max_length=2048, |
| 220 | + ), |
| 221 | + ] = None |
177 | 222 | timeout: Annotated[ |
178 | 223 | Optional[Union[int, str]], |
179 | 224 | Field( |
@@ -203,9 +248,6 @@ class ProbeConfig(CoreModel): |
203 | 248 | ), |
204 | 249 | ] = None |
205 | 250 |
|
206 | | - class Config: |
207 | | - frozen = True |
208 | | - |
209 | 251 | @validator("timeout") |
210 | 252 | def parse_timeout(cls, v: Optional[Union[int, str]]) -> Optional[int]: |
211 | 253 | if v is None: |
@@ -236,6 +278,13 @@ def validate_url(cls, v: Optional[str]) -> Optional[str]: |
236 | 278 | raise ValueError("Cannot contain non-printable characters") |
237 | 279 | return v |
238 | 280 |
|
| 281 | + @root_validator |
| 282 | + def validate_body_matches_method(cls, values): |
| 283 | + method: HTTPMethod = values["method"] |
| 284 | + if values["body"] is not None and method in ["get", "head"]: |
| 285 | + raise ValueError(f"Cannot set request body for the `{method}` method") |
| 286 | + return values |
| 287 | + |
239 | 288 |
|
240 | 289 | class BaseRunConfiguration(CoreModel): |
241 | 290 | type: Literal["none"] |
@@ -592,7 +641,7 @@ def validate_rate_limits(cls, v: list[RateLimit]) -> list[RateLimit]: |
592 | 641 |
|
593 | 642 | @validator("probes") |
594 | 643 | def validate_probes(cls, v: list[ProbeConfig]) -> list[ProbeConfig]: |
595 | | - if len(v) != len(set(v)): |
| 644 | + if has_duplicates(v): |
596 | 645 | # Using a custom validator instead of Field(unique_items=True) to avoid Pydantic bug: |
597 | 646 | # https://github.com/pydantic/pydantic/issues/3765 |
598 | 647 | # Because of the bug, our gen_schema_reference.py fails to determine the type of |
|
0 commit comments