Skip to content

Commit 9c3636c

Browse files
jopemachineclaude
andcommitted
refactor(BA-6041): split MountOption into SessionMountOption and ModelServingMountOption
The unified ``MountOption`` introduced in #11608 conflated two distinct wire schemas. ``mount_destination`` was honored only by inference service creation (where ``extra_mounts`` is a single ``dict[UUID, MountOption]`` without a separate destination map). Session creation has always carried destinations via ``mount_map`` / ``mount_id_map`` and never read ``mount_destination`` from ``mount_options[uuid]``. Replace the unified type with two structurally independent classes (no inheritance) so callers cannot mix them up: - ``ai.backend.common.dto.manager.session.types.SessionMountOption`` — used by ``CreationConfigV*.mount_options``; carries ``type``, ``permission``, ``subpath`` only. - ``ai.backend.common.dto.manager.model_serving.request.ModelServingMountOption`` — used by ``ServiceConfigModel.extra_mounts``; adds ``mount_destination`` on top of the same three fields. All SDK / manager / test callers are updated accordingly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f1a770c commit 9c3636c

12 files changed

Lines changed: 113 additions & 37 deletions

File tree

changes/11610.enhance.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Split the unified `MountOption` into two distinct types — `SessionMountOption` (in `common/dto/manager/session/types`) and `ModelServingMountOption` (in `common/dto/manager/model_serving/request`) — so the wire schemas cannot accidentally cross paths. Session creation does not accept inline `mount_destination` (destinations come from `mount_map` / `mount_id_map`); inference service creation embeds `mount_destination` per-vfolder. The two types are kept structurally independent rather than inheriting, so callers must explicitly pick the right one.

src/ai/backend/client/cli/service.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from ai.backend.client.session import AsyncSession, Session
2020
from ai.backend.common.arch import DEFAULT_IMAGE_ARCH
2121
from ai.backend.common.bgtask.types import BgtaskStatus
22-
from ai.backend.common.dto.manager.session.types import MountOption
22+
from ai.backend.common.dto.manager.model_serving.request import ModelServingMountOption
2323
from ai.backend.common.types import ClusterMode, RuntimeVariant
2424

2525
from .extensions import pass_ctx_obj
@@ -326,7 +326,7 @@ def create(
326326
parsed_resources = prepare_resource_arg(resources)
327327
parsed_resource_opts = prepare_resource_arg(resource_opts)
328328
extra_mount_options = {
329-
key: MountOption.model_validate(opts) for key, opts in mount_options.items()
329+
key: ModelServingMountOption.model_validate(opts) for key, opts in mount_options.items()
330330
}
331331
body = {
332332
"service_name": name,

src/ai/backend/client/cli/session/execute.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
if TYPE_CHECKING:
4242
from ai.backend.client.func.session import ComputeSession
4343
from ai.backend.common.arch import DEFAULT_IMAGE_ARCH
44-
from ai.backend.common.dto.manager.session.types import MountOption
44+
from ai.backend.common.dto.manager.session.types import SessionMountOption
4545
from ai.backend.common.types import ClusterMode, MountExpression
4646

4747
from .args import click_start_option
@@ -477,7 +477,7 @@ def run(
477477
resource_opts = prepare_resource_arg(resource_opts)
478478
mount, mount_map, raw_mount_options = prepare_mount_arg(mount, escape=True)
479479
mount_options = {
480-
key: MountOption.model_validate(opts) for key, opts in raw_mount_options.items()
480+
key: SessionMountOption.model_validate(opts) for key, opts in raw_mount_options.items()
481481
}
482482

483483
env_ranges: dict[str, Any] = {v: r for v, r in env_range} # type: ignore[has-type]

src/ai/backend/client/cli/session/lifecycle.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
from ai.backend.client.session import AsyncSession, Session
4444
from ai.backend.common.arch import DEFAULT_IMAGE_ARCH
4545
from ai.backend.common.bgtask.types import BgtaskStatus
46-
from ai.backend.common.dto.manager.session.types import MountOption
46+
from ai.backend.common.dto.manager.session.types import SessionMountOption
4747
from ai.backend.common.types import ClusterMode
4848

4949
from .args import click_start_option
@@ -199,7 +199,7 @@ def create(
199199
parsed_resource_opts = prepare_resource_arg(resource_opts)
200200
mount, mount_map, raw_mount_options = prepare_mount_arg(mount, escape=True)
201201
mount_options = {
202-
key: MountOption.model_validate(opts) for key, opts in raw_mount_options.items()
202+
key: SessionMountOption.model_validate(opts) for key, opts in raw_mount_options.items()
203203
}
204204

205205
preopen_ports = preopen

src/ai/backend/client/func/service.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from ai.backend.client.session import api_session
1414
from ai.backend.client.utils import dedent as _d
1515
from ai.backend.common.arch import DEFAULT_IMAGE_ARCH
16-
from ai.backend.common.dto.manager.session.types import MountOption
16+
from ai.backend.common.dto.manager.model_serving.request import ModelServingMountOption
1717
from ai.backend.common.typed_validators import SESSION_NAME_MAX_LENGTH
1818
from ai.backend.common.types import RuntimeVariant
1919

@@ -104,7 +104,7 @@ async def create(
104104
scaling_group: str,
105105
extra_mounts: Sequence[str] | None = None,
106106
extra_mount_map: Mapping[str, str] | None = None,
107-
extra_mount_options: Mapping[str, MountOption] | None = None,
107+
extra_mount_options: Mapping[str, ModelServingMountOption] | None = None,
108108
service_name: str | None = None,
109109
model_version: str | None = None,
110110
_dependencies: Sequence[str] | None = None,
@@ -183,7 +183,7 @@ async def create(
183183
if mount not in vfolder_name_to_id:
184184
raise BackendClientError(f"VFolder (name: {mount}) not found") from e
185185
vfolder_id = vfolder_name_to_id[mount]
186-
options = extra_mount_options.get(mount) or MountOption()
186+
options = extra_mount_options.get(mount) or ModelServingMountOption()
187187
body = options.model_dump(exclude_none=True)
188188
if (dest := extra_mount_map.get(mount)) is not None:
189189
body["mount_destination"] = dest

src/ai/backend/client/func/session.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
from ai.backend.client.utils import dedent as _d
4545
from ai.backend.client.versioning import get_id_or_name, get_naming
4646
from ai.backend.common.arch import DEFAULT_IMAGE_ARCH
47-
from ai.backend.common.dto.manager.session.types import MountOption
47+
from ai.backend.common.dto.manager.session.types import SessionMountOption
4848
from ai.backend.common.types import ClusterMode, SessionTypes
4949

5050
from .base import BaseFunction, api_function
@@ -211,7 +211,7 @@ async def get_or_create(
211211
mount_map: Mapping[str, str] | None = None,
212212
mount_ids: list[UUID] | None = None,
213213
mount_id_map: Mapping[UUID, str] | None = None,
214-
mount_options: Mapping[str, MountOption] | None = None,
214+
mount_options: Mapping[str, SessionMountOption] | None = None,
215215
envs: Mapping[str, str] | None = None,
216216
startup_command: str | None = None,
217217
batch_timeout: str | int | None = None,
@@ -1424,7 +1424,7 @@ async def get_or_create(
14241424
callback_url: str | None = None,
14251425
mounts: list[str] | None = None,
14261426
mount_map: Mapping[str, str] | None = None,
1427-
mount_options: Mapping[str, MountOption] | None = None,
1427+
mount_options: Mapping[str, SessionMountOption] | None = None,
14281428
mount_ids: list[UUID] | None = None,
14291429
mount_id_map: Mapping[UUID, str] | None = None,
14301430
envs: Mapping[str, str] | None = None,

src/ai/backend/common/dto/manager/model_serving/request.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,9 @@
1919
)
2020

2121
from ai.backend.common import typed_validators as tv
22-
from ai.backend.common.api_handlers import BaseRequestModel
22+
from ai.backend.common.api_handlers import BaseFieldModel, BaseRequestModel
2323
from ai.backend.common.dto.manager.query import StringFilter
24-
from ai.backend.common.dto.manager.session.types import MountOption
25-
from ai.backend.common.types import RuntimeVariant
24+
from ai.backend.common.types import MountPermission, MountTypes, RuntimeVariant
2625

2726
__all__ = (
2827
# Path param models
@@ -32,6 +31,7 @@
3231
"ListServeRequestModel",
3332
"ServiceFilterModel",
3433
"SearchServicesRequestModel",
34+
"ModelServingMountOption",
3535
"ServiceConfigModel",
3636
"NewServiceRequestModel",
3737
"ScaleRequestModel",
@@ -40,6 +40,40 @@
4040
)
4141

4242

43+
class ModelServingMountOption(BaseFieldModel):
44+
"""Per-mount option used inside ``ServiceConfigModel.extra_mounts``.
45+
46+
Inference service creation embeds the per-vfolder mount destination inline
47+
because the wire schema has no separate destination map for ``extra_mounts``.
48+
Session creation uses
49+
:class:`ai.backend.common.dto.manager.session.types.SessionMountOption`
50+
instead and supplies destinations via ``mount_map`` / ``mount_id_map``;
51+
the two types are kept structurally distinct so callers cannot accidentally
52+
use one where the other is expected.
53+
"""
54+
55+
mount_destination: str | None = Field(
56+
default=None,
57+
description=(
58+
"Mount destination inside the container. Defaults to ``/home/work/{folder_name}``."
59+
),
60+
)
61+
type: MountTypes = MountTypes.BIND
62+
permission: MountPermission | None = Field(
63+
default=None,
64+
validation_alias=AliasChoices("permission", "perm"),
65+
)
66+
subpath: str | None = Field(
67+
default=None,
68+
min_length=1,
69+
description=(
70+
"Subpath within the vfolder to mount. ``null`` (default) mounts the vfolder root."
71+
" Empty string is rejected; omit the field to mount the root."
72+
),
73+
)
74+
model_config = ConfigDict(extra="allow")
75+
76+
4377
class ServiceIdPathParam(BaseRequestModel):
4478
service_id: uuid.UUID
4579

@@ -95,7 +129,7 @@ class ServiceConfigModel(BaseRequestModel):
95129
alias="vfolderSubpath",
96130
)
97131

98-
extra_mounts: dict[uuid.UUID, MountOption] = Field(
132+
extra_mounts: dict[uuid.UUID, ModelServingMountOption] = Field(
99133
description=(
100134
"Specifications about extra VFolders mounted to model service session. "
101135
"MODEL type VFolders are not allowed to be attached to model service session"

src/ai/backend/common/dto/manager/session/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,14 @@
5656
CreationConfigV6,
5757
CreationConfigV6Template,
5858
CreationConfigV7,
59-
MountOption,
6059
ResourceOpts,
60+
SessionMountOption,
6161
)
6262

6363
__all__ = (
6464
# Types
6565
"ResourceOpts",
66-
"MountOption",
66+
"SessionMountOption",
6767
"CreationConfigV1",
6868
"CreationConfigV2",
6969
"CreationConfigV3",

src/ai/backend/common/dto/manager/session/types.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"TimeoutSeconds",
2323
# Shared field models
2424
"ResourceOpts",
25-
"MountOption",
25+
"SessionMountOption",
2626
# Creation configs
2727
"CreationConfigV1",
2828
"CreationConfigV2",
@@ -144,18 +144,15 @@ class ResourceOpts(BaseFieldModel):
144144
model_config = ConfigDict(extra="allow")
145145

146146

147-
class MountOption(BaseFieldModel):
148-
"""Per-mount option used inside ``mount_options`` mapping.
147+
class SessionMountOption(BaseFieldModel):
148+
"""Per-mount option used inside ``CreationConfigV*.mount_options``.
149149
150-
Single source of truth for per-vfolder mount overrides — used by both
151-
session creation (``CreationConfigV*.mount_options``) and inference
152-
service creation (``ServiceConfigModel.extra_mounts``).
150+
Session creation does NOT accept inline ``mount_destination`` — destinations
151+
are supplied separately via ``mount_map`` / ``mount_id_map``. For inference
152+
service creation (which embeds the destination per-vfolder), see
153+
:class:`ai.backend.common.dto.manager.model_serving.request.ModelServingMountOption`.
153154
"""
154155

155-
mount_destination: str | None = Field(
156-
default=None,
157-
description="Mount destination inside the container. Defaults to ``/home/work/{folder_name}``.",
158-
)
159156
type: MountTypes = MountTypes.BIND
160157
permission: MountPermission | None = Field(
161158
default=None,
@@ -331,7 +328,7 @@ class CreationConfigV5(BaseFieldModel):
331328
default=None,
332329
validation_alias=AliasChoices("mount_map", "mountMap"),
333330
)
334-
mount_options: dict[str, MountOption] | None = Field(
331+
mount_options: dict[str, SessionMountOption] | None = Field(
335332
default=None,
336333
validation_alias=AliasChoices("mount_options", "mountOptions"),
337334
)
@@ -383,7 +380,7 @@ class CreationConfigV6(BaseFieldModel):
383380
default=None,
384381
validation_alias=AliasChoices("mount_map", "mountMap"),
385382
)
386-
mount_options: dict[str, MountOption] | None = Field(
383+
mount_options: dict[str, SessionMountOption] | None = Field(
387384
default=None,
388385
validation_alias=AliasChoices("mount_options", "mountOptions"),
389386
)
@@ -448,7 +445,7 @@ class CreationConfigV7(BaseFieldModel):
448445
default=None,
449446
validation_alias=AliasChoices("mount_id_map", "mountIdMap"),
450447
)
451-
mount_options: dict[str, MountOption] | None = Field(
448+
mount_options: dict[str, SessionMountOption] | None = Field(
452449
default=None,
453450
validation_alias=AliasChoices("mount_options", "mountOptions"),
454451
)

src/ai/backend/manager/data/model_serving/types.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
from ai.backend.common.config import ModelDefinition
1414
from ai.backend.common.data.endpoint.types import EndpointLifecycle, ScalingState
1515
from ai.backend.common.data.user.types import UserRole
16-
from ai.backend.common.dto.manager.session.types import MountOption as MountOptionDTO
16+
from ai.backend.common.dto.manager.model_serving.request import (
17+
ModelServingMountOption as MountOptionDTO,
18+
)
1719
from ai.backend.common.identifier.deployment import DeploymentID
1820
from ai.backend.common.identifier.runtime_variant import RuntimeVariantID
1921
from ai.backend.common.identifier.vfolder import VFolderUUID

0 commit comments

Comments
 (0)