@@ -6,20 +6,18 @@ use crate::builder::meta::ObjectMetaBuilder;
66use crate :: commons:: product_image_selection:: ResolvedProductImage ;
77use crate :: error:: { Error , OperatorResult } ;
88
9+ use super :: { ListenerOperatorVolumeSourceBuilder , ListenerReference } ;
910use k8s_openapi:: {
1011 api:: core:: v1:: {
1112 Affinity , Container , LocalObjectReference , NodeAffinity , NodeSelector ,
12- NodeSelectorRequirement , NodeSelectorTerm , Pod , PodAffinity , PodCondition ,
13+ NodeSelectorRequirement , NodeSelectorTerm , Pod , PodAffinity , PodAntiAffinity , PodCondition ,
1314 PodSecurityContext , PodSpec , PodStatus , PodTemplateSpec , Toleration , Volume ,
1415 } ,
1516 apimachinery:: pkg:: apis:: meta:: v1:: { LabelSelector , LabelSelectorRequirement , ObjectMeta } ,
1617} ;
1718use std:: collections:: BTreeMap ;
1819
19- use super :: { ListenerOperatorVolumeSourceBuilder , ListenerReference } ;
20-
21- /// A builder to build [`Pod`] objects.
22- ///
20+ /// A builder to build [`Pod`] or [`PodTemplateSpec`] objects.
2321#[ derive( Clone , Default ) ]
2422pub struct PodBuilder {
2523 containers : Vec < Container > ,
@@ -29,6 +27,7 @@ pub struct PodBuilder {
2927 node_name : Option < String > ,
3028 node_selector : Option < LabelSelector > ,
3129 pod_affinity : Option < PodAffinity > ,
30+ pod_anti_affinity : Option < PodAntiAffinity > ,
3231 status : Option < PodStatus > ,
3332 security_context : Option < PodSecurityContext > ,
3433 tolerations : Option < Vec < Toleration > > ,
@@ -87,11 +86,31 @@ impl PodBuilder {
8786 self
8887 }
8988
89+ pub fn pod_affinity_opt ( & mut self , affinity : Option < PodAffinity > ) -> & mut Self {
90+ self . pod_affinity = affinity;
91+ self
92+ }
93+
94+ pub fn pod_anti_affinity ( & mut self , anti_affinity : PodAntiAffinity ) -> & mut Self {
95+ self . pod_anti_affinity = Some ( anti_affinity) ;
96+ self
97+ }
98+
99+ pub fn pod_anti_affinity_opt ( & mut self , anti_affinity : Option < PodAntiAffinity > ) -> & mut Self {
100+ self . pod_anti_affinity = anti_affinity;
101+ self
102+ }
103+
90104 pub fn node_selector ( & mut self , node_selector : LabelSelector ) -> & mut Self {
91105 self . node_selector = Some ( node_selector) ;
92106 self
93107 }
94108
109+ pub fn node_selector_opt ( & mut self , node_selector : Option < LabelSelector > ) -> & mut Self {
110+ self . node_selector = node_selector;
111+ self
112+ }
113+
95114 pub fn phase ( & mut self , phase : & str ) -> & mut Self {
96115 let mut status = self . status . get_or_insert_with ( PodStatus :: default) ;
97116 status. phase = Some ( phase. to_string ( ) ) ;
@@ -354,18 +373,11 @@ impl PodBuilder {
354373 init_containers : self . init_containers . clone ( ) ,
355374 node_name : self . node_name . clone ( ) ,
356375 node_selector : node_selector_labels,
357- affinity : node_affinity
358- . map ( |node_affinity| Affinity {
359- node_affinity : Some ( node_affinity) ,
360- pod_affinity : self . pod_affinity . clone ( ) ,
361- ..Affinity :: default ( )
362- } )
363- . or_else ( || {
364- Some ( Affinity {
365- pod_affinity : self . pod_affinity . clone ( ) ,
366- ..Affinity :: default ( )
367- } )
368- } ) ,
376+ affinity : Some ( Affinity {
377+ node_affinity,
378+ pod_affinity : self . pod_affinity . clone ( ) ,
379+ pod_anti_affinity : self . pod_anti_affinity . clone ( ) ,
380+ } ) ,
369381 security_context : self . security_context . clone ( ) ,
370382 tolerations : self . tolerations . clone ( ) ,
371383 volumes : self . volumes . clone ( ) ,
@@ -406,33 +418,71 @@ impl PodBuilder {
406418
407419#[ cfg( test) ]
408420mod tests {
421+ use super :: * ;
409422 use crate :: builder:: {
410423 meta:: ObjectMetaBuilder ,
411- pod:: { container:: ContainerBuilder , volume:: VolumeBuilder , PodBuilder } ,
424+ pod:: { container:: ContainerBuilder , volume:: VolumeBuilder } ,
412425 } ;
413426 use k8s_openapi:: {
414427 api:: core:: v1:: { LocalObjectReference , PodAffinity , PodAffinityTerm } ,
415428 apimachinery:: pkg:: apis:: meta:: v1:: { LabelSelector , LabelSelectorRequirement } ,
416429 } ;
430+ use rstest:: * ;
417431
418- #[ test]
419- fn test_pod_builder ( ) {
420- let container = ContainerBuilder :: new ( "containername" )
432+ // A simple [`Container`] with a name and image.
433+ #[ fixture]
434+ fn dummy_container ( ) -> Container {
435+ ContainerBuilder :: new ( "container" )
421436 . expect ( "ContainerBuilder not created" )
422- . image ( "stackable/zookeeper:2.4.14" )
423- . command ( vec ! [ "zk-server-start.sh" . to_string( ) ] )
424- . args ( vec ! [ "stackable/conf/zk.properties" . to_string( ) ] )
425- . add_volume_mount ( "zk-worker-1" , "conf/" )
426- . build ( ) ;
437+ . image ( "private-company/product:2.4.14" )
438+ . build ( )
439+ }
427440
428- let init_container = ContainerBuilder :: new ( "init-containername" )
429- . expect ( "ContainerBuilder not created" )
430- . image ( "stackable/zookeeper:2.4.14" )
431- . command ( vec ! [ "wrapper.sh" . to_string( ) ] )
432- . args ( vec ! [ "12345" . to_string( ) ] )
433- . build ( ) ;
441+ /// A [`PodBuilder`] that already contains the minum setup to build a Pod (name and container).
442+ #[ fixture]
443+ fn pod_builder_with_name_and_container ( dummy_container : Container ) -> PodBuilder {
444+ let mut builder = PodBuilder :: new ( ) ;
445+ builder
446+ . metadata ( ObjectMetaBuilder :: new ( ) . name ( "testpod" ) . build ( ) )
447+ . add_container ( dummy_container) ;
448+ builder
449+ }
450+
451+ // A fixture for a node selector to use on a Pod, and the resulting node selector labels and node affinity.
452+ #[ fixture]
453+ fn node_selector1 ( ) -> (
454+ LabelSelector ,
455+ Option < BTreeMap < String , String > > ,
456+ Option < NodeAffinity > ,
457+ ) {
458+ let labels = BTreeMap :: from ( [ ( "key" . to_owned ( ) , "value" . to_owned ( ) ) ] ) ;
459+ let label_selector = LabelSelector {
460+ match_expressions : Some ( vec ! [ LabelSelectorRequirement {
461+ key: "security" . to_owned( ) ,
462+ operator: "In" . to_owned( ) ,
463+ values: Some ( vec![ "S1" . to_owned( ) , "S2" . to_owned( ) ] ) ,
464+ } ] ) ,
465+ match_labels : Some ( labels. clone ( ) ) ,
466+ } ;
467+ let affinity = Some ( NodeAffinity {
468+ required_during_scheduling_ignored_during_execution : Some ( NodeSelector {
469+ node_selector_terms : vec ! [ NodeSelectorTerm {
470+ match_expressions: Some ( vec![ NodeSelectorRequirement {
471+ key: "security" . to_owned( ) ,
472+ operator: "In" . to_owned( ) ,
473+ values: Some ( vec![ "S1" . to_owned( ) , "S2" . to_owned( ) ] ) ,
474+ } ] ) ,
475+ ..Default :: default ( )
476+ } ] ,
477+ } ) ,
478+ ..Default :: default ( )
479+ } ) ;
480+ ( label_selector, Some ( labels) , affinity)
481+ }
434482
435- let pod_affinity = PodAffinity {
483+ #[ fixture]
484+ fn pod_affinity ( ) -> PodAffinity {
485+ PodAffinity {
436486 preferred_during_scheduling_ignored_during_execution : None ,
437487 required_during_scheduling_ignored_during_execution : Some ( vec ! [ PodAffinityTerm {
438488 label_selector: Some ( LabelSelector {
@@ -446,12 +496,39 @@ mod tests {
446496 topology_key: "topology.kubernetes.io/zone" . to_string( ) ,
447497 ..Default :: default ( )
448498 } ] ) ,
449- } ;
499+ }
500+ }
501+
502+ #[ fixture]
503+ fn pod_anti_affinity ( pod_affinity : PodAffinity ) -> PodAntiAffinity {
504+ PodAntiAffinity {
505+ preferred_during_scheduling_ignored_during_execution : None ,
506+ required_during_scheduling_ignored_during_execution : pod_affinity
507+ . required_during_scheduling_ignored_during_execution ,
508+ }
509+ }
510+
511+ #[ rstest]
512+ fn test_pod_builder_pod_name ( ) {
513+ let pod = PodBuilder :: new ( )
514+ . metadata_builder ( |builder| builder. name ( "foo" ) )
515+ . build ( )
516+ . unwrap ( ) ;
517+
518+ assert_eq ! ( pod. metadata. name. unwrap( ) , "foo" ) ;
519+ }
520+
521+ #[ rstest]
522+ fn test_pod_builder ( pod_affinity : PodAffinity , dummy_container : Container ) {
523+ let init_container = ContainerBuilder :: new ( "init-containername" )
524+ . expect ( "ContainerBuilder not created" )
525+ . image ( "stackable/zookeeper:2.4.14" )
526+ . build ( ) ;
450527
451528 let pod = PodBuilder :: new ( )
452529 . pod_affinity ( pod_affinity. clone ( ) )
453530 . metadata ( ObjectMetaBuilder :: new ( ) . name ( "testpod" ) . build ( ) )
454- . add_container ( container )
531+ . add_container ( dummy_container )
455532 . add_init_container ( init_container)
456533 . node_name ( "worker-1.stackable.demo" )
457534 . add_volume (
@@ -486,25 +563,11 @@ mod tests {
486563 . and_then( |volume| volume. config_map. as_ref( ) ?. name. clone( ) ) ) ,
487564 Some ( "configmap" . to_string( ) )
488565 ) ;
489-
490- let pod = PodBuilder :: new ( )
491- . metadata_builder ( |builder| builder. name ( "foo" ) )
492- . build ( )
493- . unwrap ( ) ;
494-
495- assert_eq ! ( pod. metadata. name. unwrap( ) , "foo" ) ;
496566 }
497567
498- #[ test]
499- fn test_pod_builder_image_pull_secrets ( ) {
500- let container = ContainerBuilder :: new ( "container" )
501- . expect ( "ContainerBuilder not created" )
502- . image ( "private-comapany/product:2.4.14" )
503- . build ( ) ;
504-
505- let pod = PodBuilder :: new ( )
506- . metadata ( ObjectMetaBuilder :: new ( ) . name ( "testpod" ) . build ( ) )
507- . add_container ( container)
568+ #[ rstest]
569+ fn test_pod_builder_image_pull_secrets ( mut pod_builder_with_name_and_container : PodBuilder ) {
570+ let pod = pod_builder_with_name_and_container
508571 . image_pull_secrets ( vec ! [ "company-registry-secret" . to_string( ) ] . into_iter ( ) )
509572 . build ( )
510573 . unwrap ( ) ;
@@ -516,4 +579,88 @@ mod tests {
516579 } ]
517580 ) ;
518581 }
582+
583+ /// Test if setting a node selector generates the correct node selector labels and node affinity on the Pod.
584+ #[ rstest]
585+ fn test_pod_builder_node_selector (
586+ mut pod_builder_with_name_and_container : PodBuilder ,
587+ node_selector1 : (
588+ LabelSelector ,
589+ Option < BTreeMap < String , String > > ,
590+ Option < NodeAffinity > ,
591+ ) ,
592+ ) {
593+ // destruct fixture
594+ let ( node_selector, expected_labels, expected_affinity) = node_selector1;
595+ // first test with the normal node_selector function
596+ let pod = pod_builder_with_name_and_container
597+ . clone ( )
598+ . node_selector ( node_selector. clone ( ) )
599+ . build ( )
600+ . unwrap ( ) ;
601+
602+ let spec = pod. spec . unwrap ( ) ;
603+ assert_eq ! ( spec. node_selector, expected_labels) ;
604+ assert_eq ! ( spec. affinity. unwrap( ) . node_affinity, expected_affinity) ;
605+
606+ // test the node_selector_opt function
607+ let pod = pod_builder_with_name_and_container
608+ . node_selector_opt ( Some ( node_selector) )
609+ . build ( )
610+ . unwrap ( ) ;
611+
612+ // asserts
613+ let spec = pod. spec . unwrap ( ) ;
614+ assert_eq ! ( spec. node_selector, expected_labels) ;
615+ assert_eq ! ( spec. affinity. unwrap( ) . node_affinity, expected_affinity) ;
616+ }
617+
618+ /// Test if setting a node selector generates the correct node selector labels and node affinity on the Pod,
619+ /// while keeping the manually set Pod affinities. Since they are mangled together, it makes sense to make sure that
620+ /// one is not replacing the other
621+ #[ rstest]
622+ fn test_pod_builder_node_selector_and_affinity (
623+ mut pod_builder_with_name_and_container : PodBuilder ,
624+ node_selector1 : (
625+ LabelSelector ,
626+ Option < BTreeMap < String , String > > ,
627+ Option < NodeAffinity > ,
628+ ) ,
629+ pod_affinity : PodAffinity ,
630+ pod_anti_affinity : PodAntiAffinity ,
631+ ) {
632+ // destruct fixture
633+ let ( node_selector, expected_labels, expected_affinity) = node_selector1;
634+ // first test with the normal functions
635+ let pod = pod_builder_with_name_and_container
636+ . clone ( )
637+ . node_selector ( node_selector. clone ( ) )
638+ . pod_affinity ( pod_affinity. clone ( ) )
639+ . pod_anti_affinity ( pod_anti_affinity. clone ( ) )
640+ . build ( )
641+ . unwrap ( ) ;
642+
643+ let spec = pod. spec . unwrap ( ) ;
644+ assert_eq ! ( spec. node_selector, expected_labels) ;
645+ let affinity = spec. affinity . unwrap ( ) ;
646+ assert_eq ! ( affinity. node_affinity, expected_affinity) ;
647+ assert_eq ! ( affinity. pod_affinity, Some ( pod_affinity. clone( ) ) ) ;
648+ assert_eq ! ( affinity. pod_anti_affinity, Some ( pod_anti_affinity. clone( ) ) ) ;
649+
650+ // test the *_opt functions
651+ let pod = pod_builder_with_name_and_container
652+ . node_selector_opt ( Some ( node_selector) )
653+ . pod_affinity_opt ( Some ( pod_affinity. clone ( ) ) )
654+ . pod_anti_affinity_opt ( Some ( pod_anti_affinity. clone ( ) ) )
655+ . build ( )
656+ . unwrap ( ) ;
657+
658+ // asserts
659+ let spec = pod. spec . unwrap ( ) ;
660+ assert_eq ! ( spec. node_selector, expected_labels) ;
661+ let affinity = spec. affinity . unwrap ( ) ;
662+ assert_eq ! ( affinity. node_affinity, expected_affinity) ;
663+ assert_eq ! ( affinity. pod_affinity, Some ( pod_affinity) ) ;
664+ assert_eq ! ( affinity. pod_anti_affinity, Some ( pod_anti_affinity) ) ;
665+ }
519666}
0 commit comments