21
21
Sequence ,
22
22
TypeAlias ,
23
23
cast ,
24
+ overload ,
24
25
override ,
25
26
)
26
27
@@ -838,6 +839,29 @@ async def get_allowed_vfolder_hosts_by_user(
838
839
return allowed_hosts
839
840
840
841
842
+ @overload
843
+ def check_overlapping_mounts (mounts : Iterable [str ]) -> None :
844
+ pass
845
+
846
+
847
+ @overload
848
+ def check_overlapping_mounts (mounts : Iterable [PurePosixPath ]) -> None :
849
+ pass
850
+
851
+
852
+ def check_overlapping_mounts (mounts : Iterable [str ] | Iterable [PurePosixPath ]) -> None :
853
+ for p1 in mounts :
854
+ for p2 in mounts :
855
+ _p1 = PurePosixPath (p1 )
856
+ _p2 = PurePosixPath (p2 )
857
+ if _p1 == _p2 :
858
+ continue
859
+ if _p1 .is_relative_to (_p2 ):
860
+ raise InvalidAPIParameters (
861
+ f"VFolder path '{ _p1 } ' overlaps with '{ _p2 } '" ,
862
+ )
863
+
864
+
841
865
async def prepare_vfolder_mounts (
842
866
conn : SAConnection ,
843
867
storage_manager : StorageSessionManager ,
@@ -852,6 +876,9 @@ async def prepare_vfolder_mounts(
852
876
Determine the actual mount information from the requested vfolder lists,
853
877
vfolder configurations, and the given user scope.
854
878
"""
879
+ # TODO: Refactor the whole function:
880
+ # - Replace 'requested_mount_references', 'requested_mount_reference_map' and 'requested_mount_reference_options' with one mapping parameter.
881
+ # - DO NOT validate value of subdirectories here.
855
882
requested_mounts : list [str ] = [
856
883
name for name in requested_mount_references if isinstance (name , str )
857
884
]
@@ -867,24 +894,12 @@ async def prepare_vfolder_mounts(
867
894
vfolder_ids_to_resolve = [
868
895
vfid for vfid in requested_mount_references if isinstance (vfid , uuid .UUID )
869
896
]
870
- query = (
871
- sa .select ([vfolders .c .id , vfolders .c .name ])
872
- .select_from (vfolders )
873
- .where (vfolders .c .id .in_ (vfolder_ids_to_resolve ))
874
- )
875
- result = await conn .execute (query )
876
-
877
- for vfid , name in result .fetchall ():
878
- requested_mounts .append (name )
879
- if path := requested_mount_reference_map .get (vfid ):
880
- requested_mount_map [name ] = path
881
- if options := requested_mount_reference_options .get (vfid ):
882
- requested_mount_options [name ] = options
883
897
884
898
requested_vfolder_names : dict [str , str ] = {}
885
899
requested_vfolder_subpaths : dict [str , str ] = {}
886
900
requested_vfolder_dstpaths : dict [str , str ] = {}
887
901
matched_vfolder_mounts : list [VFolderMount ] = []
902
+ _already_resolved : set [str ] = set ()
888
903
889
904
# Split the vfolder name and subpaths
890
905
for key in requested_mounts :
@@ -895,6 +910,7 @@ async def prepare_vfolder_mounts(
895
910
)
896
911
requested_vfolder_names [key ] = name
897
912
requested_vfolder_subpaths [key ] = os .path .normpath (subpath )
913
+ _already_resolved .add (name )
898
914
for key , value in requested_mount_map .items ():
899
915
requested_vfolder_dstpaths [key ] = value
900
916
@@ -911,14 +927,17 @@ async def prepare_vfolder_mounts(
911
927
# Query the accessible vfolders that satisfy either:
912
928
# - the name matches with the requested vfolder name, or
913
929
# - the name starts with a dot (dot-prefixed vfolder) for automatic mounting.
914
- extra_vf_conds = vfolders .c .name .startswith ("." ) & vfolders .c .status .not_in (
915
- DEAD_VFOLDER_STATUSES
916
- )
930
+ extra_vf_conds = vfolders .c .name .startswith ("." )
917
931
if requested_vfolder_names :
918
- extra_vf_conds = extra_vf_conds | (
919
- vfolders .c .name .in_ (requested_vfolder_names .values ())
920
- & vfolders .c .status .not_in (DEAD_VFOLDER_STATUSES )
932
+ extra_vf_conds = sa .or_ (
933
+ extra_vf_conds , vfolders .c .name .in_ (requested_vfolder_names .values ())
934
+ )
935
+ if vfolder_ids_to_resolve :
936
+ extra_vf_conds = sa .or_ (
937
+ extra_vf_conds ,
938
+ VFolderRow .id .in_ (vfolder_ids_to_resolve ),
921
939
)
940
+ extra_vf_conds = sa .and_ (extra_vf_conds , VFolderRow .status .not_in (DEAD_VFOLDER_STATUSES ))
922
941
accessible_vfolders = await query_accessible_vfolders (
923
942
conn ,
924
943
user_scope .user_uuid ,
@@ -934,7 +953,19 @@ async def prepare_vfolder_mounts(
934
953
raise VFolderNotFound ("There is no accessible vfolders at all." )
935
954
else :
936
955
return []
937
- accessible_vfolders_map = {vfolder ["name" ]: vfolder for vfolder in accessible_vfolders }
956
+ for row in accessible_vfolders :
957
+ vfid = row ["id" ]
958
+ name = row ["name" ]
959
+ if name in _already_resolved :
960
+ continue
961
+ requested_mounts .append (name )
962
+ if path := requested_mount_reference_map .get (vfid ):
963
+ requested_mount_map [name ] = path
964
+ if options := requested_mount_reference_options .get (vfid ):
965
+ requested_mount_options [name ] = options
966
+
967
+ # Check if there are overlapping mount sources
968
+ check_overlapping_mounts (requested_mounts )
938
969
939
970
# add automount folder list into requested_vfolder_names
940
971
# and requested_vfolder_subpath
@@ -944,6 +975,7 @@ async def prepare_vfolder_mounts(
944
975
requested_vfolder_subpaths .setdefault (_vfolder ["name" ], "." )
945
976
946
977
# for vfolder in accessible_vfolders:
978
+ accessible_vfolders_map = {vfolder ["name" ]: vfolder for vfolder in accessible_vfolders }
947
979
for key , vfolder_name in requested_vfolder_names .items ():
948
980
if not (vfolder := accessible_vfolders_map .get (vfolder_name )):
949
981
raise VFolderNotFound (f"VFolder { vfolder_name } is not found or accessible." )
@@ -957,6 +989,24 @@ async def prepare_vfolder_mounts(
957
989
group_id = user_scope .group_id ,
958
990
permission = VFolderHostPermission .MOUNT_IN_SESSION ,
959
991
)
992
+ if unmanaged_path := cast (Optional [str ], vfolder ["unmanaged_path" ]):
993
+ kernel_path_raw = requested_vfolder_dstpaths .get (key )
994
+ if kernel_path_raw is None :
995
+ kernel_path = PurePosixPath (f"/home/work/{ vfolder ['name' ]} " )
996
+ else :
997
+ kernel_path = PurePosixPath (kernel_path_raw )
998
+ matched_vfolder_mounts .append (
999
+ VFolderMount (
1000
+ name = vfolder ["name" ],
1001
+ vfid = VFolderID (vfolder ["quota_scope_id" ], vfolder ["id" ]),
1002
+ vfsubpath = PurePosixPath ("." ),
1003
+ host_path = PurePosixPath (unmanaged_path ),
1004
+ kernel_path = kernel_path ,
1005
+ mount_perm = vfolder ["permission" ],
1006
+ usage_mode = vfolder ["usage_mode" ],
1007
+ )
1008
+ )
1009
+ continue
960
1010
if vfolder ["group" ] is not None and vfolder ["group" ] != str (user_scope .group_id ):
961
1011
# User's accessible group vfolders should not be mounted
962
1012
# if they do not belong to the execution kernel.
@@ -1033,14 +1083,7 @@ async def prepare_vfolder_mounts(
1033
1083
)
1034
1084
1035
1085
# Check if there are overlapping mount targets
1036
- for vf1 in matched_vfolder_mounts :
1037
- for vf2 in matched_vfolder_mounts :
1038
- if vf1 .name == vf2 .name :
1039
- continue
1040
- if vf1 .kernel_path .is_relative_to (vf2 .kernel_path ):
1041
- raise InvalidAPIParameters (
1042
- f"VFolder mount path { vf1 .kernel_path } overlaps with { vf2 .kernel_path } " ,
1043
- )
1086
+ check_overlapping_mounts ([trgt .kernel_path for trgt in matched_vfolder_mounts ])
1044
1087
1045
1088
return matched_vfolder_mounts
1046
1089
@@ -1123,6 +1166,10 @@ async def ensure_host_permission_allowed(
1123
1166
domain_name : str ,
1124
1167
group_id : Optional [uuid .UUID ] = None ,
1125
1168
) -> None :
1169
+ from .storage import StorageSessionManager
1170
+
1171
+ if StorageSessionManager .is_noop_host (folder_host ):
1172
+ return
1126
1173
allowed_hosts = await filter_host_allowed_permission (
1127
1174
db_conn ,
1128
1175
allowed_vfolder_types = allowed_vfolder_types ,
@@ -1203,7 +1250,7 @@ async def _insert_vfolder() -> None:
1203
1250
"ownership_type" : VFolderOwnershipType ("user" ),
1204
1251
"user" : vfolder_info .user_id ,
1205
1252
"group" : None ,
1206
- "unmanaged_path" : "" ,
1253
+ "unmanaged_path" : None ,
1207
1254
"cloneable" : vfolder_info .cloneable ,
1208
1255
"quota_scope_id" : vfolder_info .source_vfolder_id .quota_scope_id ,
1209
1256
}
0 commit comments