@@ -679,6 +679,143 @@ var _ = Describe("lifecycle", func() {
679679 Expect (err ).Should (BeNil ())
680680 })
681681
682+ It ("pod selector - all lets a later replica unblock an earlier primary" , func () {
683+ lifecycleActions .PostProvision .Exec .TargetPodSelector = appsv1 .AllReplicas
684+ pods = []* corev1.Pod {
685+ {ObjectMeta : metav1.ObjectMeta {Namespace : namespace , Name : "primary-0" }},
686+ {ObjectMeta : metav1.ObjectMeta {Namespace : namespace , Name : "replica-0" }},
687+ }
688+
689+ lifecycle , err := New (namespace , clusterName , compName , lifecycleActions , nil , nil , pods )
690+ Expect (err ).Should (BeNil ())
691+ Expect (lifecycle ).ShouldNot (BeNil ())
692+
693+ totalCallCount := 0
694+ roundCallCount := 0
695+ replicaAttached := false
696+ mockKBAgentClient (func (recorder * kbacli.MockClientMockRecorder ) {
697+ recorder .Action (gomock .Any (), gomock .Any ()).DoAndReturn (func (ctx context.Context , req proto.ActionRequest ) (proto.ActionResponse , error ) {
698+ totalCallCount ++
699+ roundCallCount ++
700+ // The stable selection order is primary first, replica second. The
701+ // primary cannot finish until the replica has executed its own action.
702+ if roundCallCount == 1 && ! replicaAttached {
703+ return proto.ActionResponse {Error : proto .Error2Type (proto .ErrFailed )}, nil
704+ }
705+ if roundCallCount == 2 {
706+ replicaAttached = true
707+ }
708+ return proto.ActionResponse {}, nil
709+ }).AnyTimes ()
710+ })
711+
712+ err = lifecycle .PostProvision (ctx , k8sClient , nil )
713+ Expect (errors .Is (err , ErrActionFailed )).Should (BeTrue ())
714+ Expect (roundCallCount ).Should (Equal (2 ))
715+ Expect (replicaAttached ).Should (BeTrue ())
716+
717+ roundCallCount = 0
718+ err = lifecycle .PostProvision (ctx , k8sClient , nil )
719+ Expect (err ).Should (BeNil ())
720+ Expect (roundCallCount ).Should (Equal (2 ))
721+ Expect (totalCallCount ).Should (Equal (4 ))
722+ })
723+
724+ It ("pod selector - all aggregates every pod error with pod identity" , func () {
725+ lifecycleActions .PostProvision .Exec .TargetPodSelector = appsv1 .AllReplicas
726+ pods = []* corev1.Pod {
727+ {ObjectMeta : metav1.ObjectMeta {Namespace : namespace , Name : "pod-0" }},
728+ {ObjectMeta : metav1.ObjectMeta {Namespace : namespace , Name : "pod-1" }},
729+ }
730+
731+ lifecycle , err := New (namespace , clusterName , compName , lifecycleActions , nil , nil , pods )
732+ Expect (err ).Should (BeNil ())
733+ Expect (lifecycle ).ShouldNot (BeNil ())
734+
735+ callCount := 0
736+ mockKBAgentClient (func (recorder * kbacli.MockClientMockRecorder ) {
737+ recorder .Action (gomock .Any (), gomock .Any ()).DoAndReturn (func (ctx context.Context , req proto.ActionRequest ) (proto.ActionResponse , error ) {
738+ callCount ++
739+ if callCount == 1 {
740+ return proto.ActionResponse {Error : proto .Error2Type (proto .ErrInProgress )}, nil
741+ }
742+ return proto.ActionResponse {Error : proto .Error2Type (proto .ErrBusy )}, nil
743+ }).Times (2 )
744+ })
745+
746+ err = lifecycle .PostProvision (ctx , k8sClient , nil )
747+ Expect (err ).ShouldNot (BeNil ())
748+ Expect (errors .Is (err , ErrActionInProgress )).Should (BeTrue ())
749+ Expect (errors .Is (err , ErrActionBusy )).Should (BeTrue ())
750+ Expect (err .Error ()).Should (ContainSubstring ("pod-0" ))
751+ Expect (err .Error ()).Should (ContainSubstring ("pod-1" ))
752+ Expect (callCount ).Should (Equal (2 ))
753+ })
754+
755+ It ("pod selector - all ignores NotDefined only when every pod is NotDefined" , func () {
756+ lifecycleActions .PostProvision .Exec .TargetPodSelector = appsv1 .AllReplicas
757+ pods = []* corev1.Pod {
758+ {ObjectMeta : metav1.ObjectMeta {Namespace : namespace , Name : "pod-0" }},
759+ {ObjectMeta : metav1.ObjectMeta {Namespace : namespace , Name : "pod-1" }},
760+ }
761+
762+ lifecycle , err := New (namespace , clusterName , compName , lifecycleActions , nil , nil , pods )
763+ Expect (err ).Should (BeNil ())
764+ Expect (lifecycle ).ShouldNot (BeNil ())
765+
766+ callCount := 0
767+ mockKBAgentClient (func (recorder * kbacli.MockClientMockRecorder ) {
768+ recorder .Action (gomock .Any (), gomock .Any ()).DoAndReturn (func (ctx context.Context , req proto.ActionRequest ) (proto.ActionResponse , error ) {
769+ callCount ++
770+ if callCount == 1 {
771+ return proto.ActionResponse {Error : proto .Error2Type (proto .ErrNotDefined )}, nil
772+ }
773+ return proto.ActionResponse {Error : proto .Error2Type (proto .ErrFailed )}, nil
774+ }).Times (2 )
775+ })
776+
777+ err = lifecycle .PostProvision (ctx , k8sClient , nil )
778+ Expect (err ).ShouldNot (BeNil ())
779+ Expect (errors .Is (err , ErrActionNotDefined )).Should (BeFalse ())
780+ Expect (errors .Is (err , ErrActionFailed )).Should (BeTrue ())
781+ Expect (IgnoreNotDefined (err )).ShouldNot (BeNil ())
782+ Expect (err .Error ()).Should (ContainSubstring ("pod-0" ))
783+ Expect (err .Error ()).Should (ContainSubstring ("pod-1" ))
784+
785+ mockKBAgentClient (func (recorder * kbacli.MockClientMockRecorder ) {
786+ recorder .Action (gomock .Any (), gomock .Any ()).Return (proto.ActionResponse {Error : proto .Error2Type (proto .ErrNotDefined )}, nil ).Times (2 )
787+ })
788+
789+ err = lifecycle .PostProvision (ctx , k8sClient , nil )
790+ Expect (errors .Is (err , ErrActionNotDefined )).Should (BeTrue ())
791+ Expect (IgnoreNotDefined (err )).Should (BeNil ())
792+ })
793+
794+ It ("pod selector - role keeps first-error behavior" , func () {
795+ lifecycleActions .PostProvision .Exec .TargetPodSelector = appsv1 .RoleSelector
796+ lifecycleActions .PostProvision .Exec .MatchingKey = "leader"
797+ pods = []* corev1.Pod {
798+ {ObjectMeta : metav1.ObjectMeta {Namespace : namespace , Name : "pod-0" , Labels : map [string ]string {constant .RoleLabelKey : "leader" }}},
799+ {ObjectMeta : metav1.ObjectMeta {Namespace : namespace , Name : "pod-1" , Labels : map [string ]string {constant .RoleLabelKey : "leader" }}},
800+ }
801+
802+ lifecycle , err := New (namespace , clusterName , compName , lifecycleActions , nil , nil , pods )
803+ Expect (err ).Should (BeNil ())
804+ Expect (lifecycle ).ShouldNot (BeNil ())
805+
806+ callCount := 0
807+ mockKBAgentClient (func (recorder * kbacli.MockClientMockRecorder ) {
808+ recorder .Action (gomock .Any (), gomock .Any ()).DoAndReturn (func (ctx context.Context , req proto.ActionRequest ) (proto.ActionResponse , error ) {
809+ callCount ++
810+ return proto.ActionResponse {Error : proto .Error2Type (proto .ErrFailed )}, nil
811+ }).Times (1 )
812+ })
813+
814+ err = lifecycle .PostProvision (ctx , k8sClient , nil )
815+ Expect (errors .Is (err , ErrActionFailed )).Should (BeTrue ())
816+ Expect (callCount ).Should (Equal (1 ))
817+ })
818+
682819 It ("pod selector - role" , func () {
683820 lifecycleActions .PostProvision .Exec .TargetPodSelector = appsv1 .RoleSelector
684821 lifecycleActions .PostProvision .Exec .MatchingKey = "leader"
0 commit comments