@@ -31,8 +31,9 @@ fn statefulset_name(tenant: &Tenant, pool: &Pool) -> String {
3131
3232impl Tenant {
3333 /// Constructs the RUSTFS_VOLUMES environment variable value
34- /// Format: http://{tenant}-{pool}-{0...servers-1}.{service}.{namespace}.svc.cluster.local:9000{path}/{0...volumes-1}
34+ /// Format: http://{tenant}-{pool}-{0...servers-1}.{service}.{namespace}.svc.cluster.local:9000{path}/rustfs {0...volumes-1}
3535 /// All pools are combined into a space-separated string for a unified cluster
36+ /// Follows RustFS convention: /data/rustfs0, /data/rustfs1, etc.
3637 fn rustfs_volumes_env_value ( & self ) -> Result < String , types:: error:: Error > {
3738 let namespace = self . namespace ( ) ?;
3839 let tenant_name = self . name ( ) ;
@@ -47,8 +48,9 @@ impl Tenant {
4748 let pool_name = & pool. name ;
4849
4950 // Construct volume specification with range notation
51+ // Follows RustFS convention: /data/rustfs{0...N}
5052 format ! (
51- "http://{}-{}-{{0...{}}}.{}.{}.svc.cluster.local:9000{}/{{0...{}}}" ,
53+ "http://{}-{}-{{0...{}}}.{}.{}.svc.cluster.local:9000{}/rustfs {{0...{}}}" ,
5254 tenant_name,
5355 pool_name,
5456 pool. servers - 1 ,
@@ -129,11 +131,12 @@ impl Tenant {
129131 // Generate volume mounts for each volume
130132 // Default path is /data if not specified
131133 // Volume mount names must match the volume claim template names (vol-0, vol-1, etc.)
134+ // Mount paths follow RustFS convention: /data/rustfs0, /data/rustfs1, etc.
132135 let base_path = pool. persistence . path . as_deref ( ) . unwrap_or ( "/data" ) ;
133136 let volume_mounts: Vec < corev1:: VolumeMount > = ( 0 ..pool. persistence . volumes_per_server )
134137 . map ( |i| corev1:: VolumeMount {
135138 name : volume_claim_template_name ( i) ,
136- mount_path : format ! ( "{}/{}" , base_path. trim_end_matches( '/' ) , i) ,
139+ mount_path : format ! ( "{}/rustfs {}" , base_path. trim_end_matches( '/' ) , i) ,
137140 ..Default :: default ( )
138141 } )
139142 . collect ( ) ;
@@ -149,6 +152,25 @@ impl Tenant {
149152 ..Default :: default ( )
150153 } ) ;
151154
155+ // Add required RustFS environment variables
156+ env_vars. push ( corev1:: EnvVar {
157+ name : "RUSTFS_ADDRESS" . to_owned ( ) ,
158+ value : Some ( "0.0.0.0:9000" . to_owned ( ) ) ,
159+ ..Default :: default ( )
160+ } ) ;
161+
162+ env_vars. push ( corev1:: EnvVar {
163+ name : "RUSTFS_CONSOLE_ADDRESS" . to_owned ( ) ,
164+ value : Some ( "0.0.0.0:9001" . to_owned ( ) ) ,
165+ ..Default :: default ( )
166+ } ) ;
167+
168+ env_vars. push ( corev1:: EnvVar {
169+ name : "RUSTFS_CONSOLE_ENABLE" . to_owned ( ) ,
170+ value : Some ( "true" . to_owned ( ) ) ,
171+ ..Default :: default ( )
172+ } ) ;
173+
152174 // Merge with user-provided environment variables
153175 // User-provided vars can override operator-managed ones
154176 for user_env in & self . spec . env {
@@ -173,14 +195,16 @@ impl Tenant {
173195 ..Default :: default ( )
174196 } ,
175197 corev1:: ContainerPort {
176- container_port: 9090 ,
198+ container_port: 9001 ,
177199 name: Some ( "console" . to_owned( ) ) ,
178200 protocol: Some ( "TCP" . to_owned( ) ) ,
179201 ..Default :: default ( )
180202 } ,
181203 ] ) ,
182204 volume_mounts : Some ( volume_mounts) ,
183205 lifecycle : self . spec . lifecycle . clone ( ) ,
206+ // Apply pool-level resource requirements to container
207+ resources : pool. scheduling . resources . clone ( ) ,
184208 ..Default :: default ( )
185209 } ;
186210
@@ -215,7 +239,17 @@ impl Tenant {
215239 service_account_name : Some ( self . service_account_name ( ) ) ,
216240 containers : vec ! [ container] ,
217241 scheduler_name : self . spec . scheduler . clone ( ) ,
218- priority_class_name : self . spec . priority_class_name . clone ( ) ,
242+ // Pool-level priority class overrides tenant-level
243+ priority_class_name : pool
244+ . scheduling
245+ . priority_class_name
246+ . clone ( )
247+ . or_else ( || self . spec . priority_class_name . clone ( ) ) ,
248+ // Pool-level scheduling controls
249+ node_selector : pool. scheduling . node_selector . clone ( ) ,
250+ affinity : pool. scheduling . affinity . clone ( ) ,
251+ tolerations : pool. scheduling . tolerations . clone ( ) ,
252+ topology_spread_constraints : pool. scheduling . topology_spread_constraints . clone ( ) ,
219253 ..Default :: default ( )
220254 } ) ,
221255 } ,
@@ -229,6 +263,8 @@ impl Tenant {
229263
230264#[ cfg( test) ]
231265mod tests {
266+ use k8s_openapi:: api:: core:: v1 as corev1;
267+
232268 // Test: StatefulSet uses correct service account
233269 #[ test]
234270 fn test_statefulset_uses_default_sa ( ) {
@@ -277,4 +313,153 @@ mod tests {
277313 "Pod should use custom service account"
278314 ) ;
279315 }
316+
317+ // Test: StatefulSet applies pool-level node selector
318+ #[ test]
319+ fn test_statefulset_applies_node_selector ( ) {
320+ let mut tenant = super :: super :: tests:: create_test_tenant ( None , None ) ;
321+ let mut node_selector = std:: collections:: BTreeMap :: new ( ) ;
322+ node_selector. insert ( "storage-type" . to_string ( ) , "nvme" . to_string ( ) ) ;
323+ tenant. spec . pools [ 0 ] . scheduling . node_selector = Some ( node_selector. clone ( ) ) ;
324+
325+ let pool = & tenant. spec . pools [ 0 ] ;
326+ let statefulset = tenant
327+ . new_statefulset ( pool)
328+ . expect ( "Should create StatefulSet" ) ;
329+
330+ let pod_spec = statefulset
331+ . spec
332+ . expect ( "StatefulSet should have spec" )
333+ . template
334+ . spec
335+ . expect ( "Pod template should have spec" ) ;
336+
337+ assert_eq ! (
338+ pod_spec. node_selector,
339+ Some ( node_selector) ,
340+ "Pod should use pool-level node selector"
341+ ) ;
342+ }
343+
344+ // Test: StatefulSet applies pool-level tolerations
345+ #[ test]
346+ fn test_statefulset_applies_tolerations ( ) {
347+ let mut tenant = super :: super :: tests:: create_test_tenant ( None , None ) ;
348+ let tolerations = vec ! [ corev1:: Toleration {
349+ key: Some ( "spot-instance" . to_string( ) ) ,
350+ operator: Some ( "Equal" . to_string( ) ) ,
351+ value: Some ( "true" . to_string( ) ) ,
352+ effect: Some ( "NoSchedule" . to_string( ) ) ,
353+ ..Default :: default ( )
354+ } ] ;
355+ tenant. spec . pools [ 0 ] . scheduling . tolerations = Some ( tolerations. clone ( ) ) ;
356+
357+ let pool = & tenant. spec . pools [ 0 ] ;
358+ let statefulset = tenant
359+ . new_statefulset ( pool)
360+ . expect ( "Should create StatefulSet" ) ;
361+
362+ let pod_spec = statefulset
363+ . spec
364+ . expect ( "StatefulSet should have spec" )
365+ . template
366+ . spec
367+ . expect ( "Pod template should have spec" ) ;
368+
369+ assert_eq ! (
370+ pod_spec. tolerations,
371+ Some ( tolerations) ,
372+ "Pod should use pool-level tolerations"
373+ ) ;
374+ }
375+
376+ // Test: Pool-level priority class overrides tenant-level
377+ #[ test]
378+ fn test_pool_priority_class_overrides_tenant ( ) {
379+ let mut tenant = super :: super :: tests:: create_test_tenant ( None , None ) ;
380+ tenant. spec . priority_class_name = Some ( "tenant-priority" . to_string ( ) ) ;
381+ tenant. spec . pools [ 0 ] . scheduling . priority_class_name = Some ( "pool-priority" . to_string ( ) ) ;
382+
383+ let pool = & tenant. spec . pools [ 0 ] ;
384+ let statefulset = tenant
385+ . new_statefulset ( pool)
386+ . expect ( "Should create StatefulSet" ) ;
387+
388+ let pod_spec = statefulset
389+ . spec
390+ . expect ( "StatefulSet should have spec" )
391+ . template
392+ . spec
393+ . expect ( "Pod template should have spec" ) ;
394+
395+ assert_eq ! (
396+ pod_spec. priority_class_name,
397+ Some ( "pool-priority" . to_string( ) ) ,
398+ "Pool-level priority class should override tenant-level"
399+ ) ;
400+ }
401+
402+ // Test: Tenant-level priority class used when pool-level not set
403+ #[ test]
404+ fn test_tenant_priority_class_fallback ( ) {
405+ let mut tenant = super :: super :: tests:: create_test_tenant ( None , None ) ;
406+ tenant. spec . priority_class_name = Some ( "tenant-priority" . to_string ( ) ) ;
407+ // pool.priority_class_name remains None
408+
409+ let pool = & tenant. spec . pools [ 0 ] ;
410+ let statefulset = tenant
411+ . new_statefulset ( pool)
412+ . expect ( "Should create StatefulSet" ) ;
413+
414+ let pod_spec = statefulset
415+ . spec
416+ . expect ( "StatefulSet should have spec" )
417+ . template
418+ . spec
419+ . expect ( "Pod template should have spec" ) ;
420+
421+ assert_eq ! (
422+ pod_spec. priority_class_name,
423+ Some ( "tenant-priority" . to_string( ) ) ,
424+ "Should fall back to tenant-level priority class when pool-level not set"
425+ ) ;
426+ }
427+
428+ // Test: Pool-level resources applied to container
429+ #[ test]
430+ fn test_pool_resources_applied_to_container ( ) {
431+ let mut tenant = super :: super :: tests:: create_test_tenant ( None , None ) ;
432+ let mut requests = std:: collections:: BTreeMap :: new ( ) ;
433+ requests. insert ( "cpu" . to_string ( ) , k8s_openapi:: apimachinery:: pkg:: api:: resource:: Quantity ( "4" . to_string ( ) ) ) ;
434+ requests. insert ( "memory" . to_string ( ) , k8s_openapi:: apimachinery:: pkg:: api:: resource:: Quantity ( "16Gi" . to_string ( ) ) ) ;
435+
436+ tenant. spec . pools [ 0 ] . scheduling . resources = Some ( corev1:: ResourceRequirements {
437+ requests : Some ( requests. clone ( ) ) ,
438+ limits : None ,
439+ claims : None ,
440+ } ) ;
441+
442+ let pool = & tenant. spec . pools [ 0 ] ;
443+ let statefulset = tenant
444+ . new_statefulset ( pool)
445+ . expect ( "Should create StatefulSet" ) ;
446+
447+ let container = & statefulset
448+ . spec
449+ . expect ( "StatefulSet should have spec" )
450+ . template
451+ . spec
452+ . expect ( "Pod template should have spec" )
453+ . containers [ 0 ] ;
454+
455+ assert ! (
456+ container. resources. is_some( ) ,
457+ "Container should have resources"
458+ ) ;
459+ assert_eq ! (
460+ container. resources. as_ref( ) . unwrap( ) . requests,
461+ Some ( requests) ,
462+ "Container should use pool-level resource requests"
463+ ) ;
464+ }
280465}
0 commit comments