6565 verify_vfolder_name ,
6666 vfolder_status_map ,
6767)
68- from ai .backend .manager .repositories .base .updater import Updater
6968from ai .backend .manager .repositories .user .repository import UserRepository
7069from ai .backend .manager .repositories .vfolder .repository import VfolderRepository
71- from ai .backend .manager .repositories .vfolder .updaters import (
72- VFolderAttributeUpdaterSpec ,
73- VFolderTrashUpdaterSpec ,
74- )
70+ from ai .backend .manager .repositories .vfolder .updaters import VFolderAttributeUpdaterSpec
7571from ai .backend .manager .services .vfolder .actions .base import (
7672 CloneVFolderAction ,
7773 CloneVFolderActionResult ,
@@ -1636,25 +1632,102 @@ async def get_v2(self, action: GetVFolderV2Action) -> GetVFolderV2ActionResult:
16361632 async def create_in_project (
16371633 self , action : CreateVFolderInProjectAction
16381634 ) -> CreateVFolderInProjectActionResult :
1639- """Create a vfolder scoped to a project.
1635+ """Create a vfolder owned by a project.
16401636
1641- RBAC is enforced by the ScopeActionProcessor wiring; this method
1642- delegates the heavy lifting to ``create_v2`` with ``project_id`` set.
1637+ RBAC is enforced by the ScopeActionProcessor at the processor level.
1638+ Unlike ``create_v2`` this method does NOT check ``UserRole`` — project
1639+ CREATE permission is validated by the scope RBAC validator.
16431640 """
1644- inner_action = CreateVFolderV2Action (
1641+ user_uuid = action .user_id
1642+ domain_name = action .domain_name
1643+ project_id = action .project_id
1644+
1645+ # Resolve host
1646+ folder_host = action .host
1647+ if not folder_host :
1648+ folder_host = self ._config_provider .config .volumes .default_host
1649+ if not folder_host :
1650+ raise VFolderInvalidParameter (
1651+ "You must specify the vfolder host because the default host is not configured."
1652+ )
1653+
1654+ if action .name .startswith ("." ) and action .name != ".local" :
1655+ raise VFolderInvalidParameter ("dot-prefixed vfolders cannot be a group folder." )
1656+
1657+ # Resolve project info
1658+ group_info = await self ._vfolder_repository .get_group_resource_info (project_id , domain_name )
1659+ if not group_info :
1660+ raise ProjectNotFound (f"Project with { project_id } not found." )
1661+ group_uuid , max_vfolder_count , max_quota_scope_size , group_type = group_info
1662+
1663+ quota_scope_id = QuotaScopeID (QuotaScopeType .PROJECT , group_uuid )
1664+
1665+ if group_type == ProjectType .MODEL_STORE :
1666+ if action .usage_mode != VFolderUsageMode .MODEL :
1667+ raise VFolderInvalidParameter (
1668+ "Only Model VFolder can be created under the model store project"
1669+ )
1670+
1671+ # Host permission check
1672+ await self ._vfolder_repository .ensure_host_permission_allowed_by_user (
1673+ folder_host ,
1674+ permission = VFolderHostPermission .CREATE ,
1675+ user_uuid = user_uuid ,
1676+ group_id = group_uuid ,
1677+ )
1678+
1679+ # Quota check
1680+ if max_vfolder_count > 0 :
1681+ current_count = await self ._vfolder_repository .count_vfolders_by_group (group_uuid )
1682+ if current_count >= max_vfolder_count :
1683+ raise VFolderInvalidParameter ("You cannot create more vfolders." )
1684+
1685+ # Create in storage
1686+ folder_id = uuid .uuid4 ()
1687+ mount_permission = action .permission
1688+ if group_type == ProjectType .MODEL_STORE :
1689+ mount_permission = VFolderPermission .READ_ONLY
1690+
1691+ try :
1692+ vfid = VFolderID (quota_scope_id , folder_id )
1693+ proxy_name , volume_name = self ._storage_manager .get_proxy_and_volume (folder_host , False )
1694+ manager_client = self ._storage_manager .get_manager_facing_client (proxy_name )
1695+ await manager_client .create_folder (volume_name , str (vfid ), max_quota_scope_size , None )
1696+ except aiohttp .ClientResponseError as e :
1697+ raise VFolderCreationFailure from e
1698+
1699+ # Create in DB
1700+ params = VFolderCreateParams (
1701+ id = folder_id ,
16451702 name = action .name ,
1646- user_id = action .user_id ,
1647- domain_name = action .domain_name ,
1648- project_id = action .project_id ,
1649- host = action .host ,
1703+ domain_name = domain_name ,
1704+ quota_scope_id = str (quota_scope_id ),
16501705 usage_mode = action .usage_mode ,
1651- permission = action .permission ,
1706+ permission = mount_permission ,
1707+ host = folder_host ,
1708+ creator = str (user_uuid ),
1709+ creator_id = user_uuid ,
1710+ ownership_type = VFolderOwnershipType .GROUP ,
1711+ user = None ,
1712+ group = group_uuid ,
1713+ unmanaged_path = None ,
16521714 cloneable = action .cloneable ,
1715+ status = VFolderOperationStatus .READY ,
16531716 )
1654- inner_result = await self .create_v2 (inner_action )
1717+
1718+ try :
1719+ create_owner_permission = group_type == ProjectType .MODEL_STORE
1720+ await self ._vfolder_repository .create_vfolder_with_permission (
1721+ params , create_owner_permission = create_owner_permission
1722+ )
1723+ except sa_exc .DataError as e :
1724+ raise VFolderInvalidParameter from e
1725+
1726+ # Fetch created vfolder data for response
1727+ vfolder_data = await self ._vfolder_repository .get_by_id (folder_id )
16551728 return CreateVFolderInProjectActionResult (
16561729 project_id = action .project_id ,
1657- vfolder = inner_result . vfolder ,
1730+ vfolder = vfolder_data ,
16581731 )
16591732
16601733 async def delete_v2 (self , action : DeleteVFolderV2Action ) -> DeleteVFolderV2ActionResult :
@@ -1666,8 +1739,7 @@ async def delete_v2(self, action: DeleteVFolderV2Action) -> DeleteVFolderV2Actio
16661739 matching row exists, which the repository translates to
16671740 ``VFolderNotFound``.
16681741 """
1669- updater = Updater (spec = VFolderTrashUpdaterSpec (), pk_value = action .vfolder_id )
1670- await self ._vfolder_repository .trash_vfolder (updater )
1742+ await self ._vfolder_repository .trash_vfolder (action .updater )
16711743 return DeleteVFolderV2ActionResult (vfolder_id = action .vfolder_id )
16721744
16731745 async def purge_v2 (self , action : PurgeVFolderV2Action ) -> PurgeVFolderV2ActionResult :
0 commit comments