@@ -133,6 +133,102 @@ func DeploymentBecomesAvailableWithin(d time.Duration, namespace, name string) f
133133 }
134134}
135135
136+ // DeploymentPodIsRunningMustNotChangeWithin fails a test if the supplied Deployment does
137+ // not have a running Pod that stays running for the supplied duration.
138+ func DeploymentPodIsRunningMustNotChangeWithin (d time.Duration , namespace , name string ) features.Func {
139+ return func (ctx context.Context , t * testing.T , c * envconf.Config ) context.Context {
140+ t .Helper ()
141+
142+ dp := & appsv1.Deployment {ObjectMeta : metav1.ObjectMeta {Namespace : namespace , Name : name }}
143+ t .Logf ("Ensuring deployment %s/%s has running pod that does not change within %s" , dp .GetNamespace (), dp .GetName (), d .String ())
144+ start := time .Now ()
145+
146+ pod , err := podForDeployment (ctx , t , c , dp )
147+ if err != nil {
148+ t .Errorf ("Failed to get pod for deployment %s/%s: %s" , dp .GetNamespace (), dp .GetName (), err )
149+ return ctx
150+ }
151+
152+ // first wait for pod to be running
153+ if err := wait .For (conditions .New (c .Client ().Resources ()).PodConditionMatch (pod , corev1 .PodReady , corev1 .ConditionTrue ), wait .WithTimeout (d ), wait .WithInterval (DefaultPollInterval )); err != nil {
154+ t .Errorf ("Deployment %s/%s never got a running pod after %s" , dp .GetNamespace (), dp .GetName (), since (start ))
155+ return ctx
156+ }
157+
158+ // now wait to make sure the pod stays running (does not change)
159+ start = time .Now ()
160+ if err := wait .For (conditions .New (c .Client ().Resources ()).PodConditionMatch (pod , corev1 .PodReady , corev1 .ConditionFalse ), wait .WithTimeout (d ), wait .WithInterval (DefaultPollInterval )); err != nil {
161+ if deadlineExceed (err ) {
162+ t .Logf ("Deployment %s/%s had running pod that did not change after %s" , dp .GetNamespace (), dp .GetName (), since (start ))
163+ return ctx
164+ }
165+
166+ t .Errorf ("Error while observing pod for deployment %s/%s" , dp .GetNamespace (), dp .GetName ())
167+ return ctx
168+ }
169+ t .Errorf ("Deployment %s/%s had pod that changed within %s, but it should not have" , dp .GetNamespace (), dp .GetName (), d .String ())
170+ return ctx
171+ }
172+ }
173+
174+ // ArgSetWithin fails a test if the supplied Deployment does not have a Pod with
175+ // the given argument set within the supplied duration.
176+ func ArgSetWithin (d time.Duration , arg , namespace , name string ) features.Func {
177+ return checkArgSetWithin (d , arg , true , namespace , name )
178+ }
179+
180+ // ArgUnsetWithin fails a test if the supplied Deployment does not have a Pod with
181+ // the given argument unset within the supplied duration.
182+ func ArgUnsetWithin (d time.Duration , arg , namespace , name string ) features.Func {
183+ return checkArgSetWithin (d , arg , false , namespace , name )
184+ }
185+
186+ // checkArgSetWithin implements a check for the supplied Deployment having a Pod
187+ // with the given argument being either set or unset within the supplied
188+ // duration.
189+ func checkArgSetWithin (d time.Duration , arg string , wantSet bool , namespace , name string ) features.Func {
190+ return func (ctx context.Context , t * testing.T , c * envconf.Config ) context.Context {
191+ t .Helper ()
192+
193+ dp := & appsv1.Deployment {ObjectMeta : metav1.ObjectMeta {Namespace : namespace , Name : name }}
194+ t .Logf ("Waiting %s for pod in deployment %s/%s to have arg %s set=%t..." , d , dp .GetNamespace (), dp .GetName (), arg , wantSet )
195+ start := time .Now ()
196+
197+ if err := wait .For (func (ctx context.Context ) (done bool , err error ) {
198+ pod , err := podForDeployment (ctx , t , c , dp )
199+ if err != nil {
200+ t .Logf ("failed to get pod for deployment %s/%s: %s" , dp .GetNamespace (), dp .GetName (), err )
201+ return false , nil
202+ }
203+
204+ found := false
205+ c := pod .Spec .Containers [0 ]
206+ for _ , a := range c .Args {
207+ if a == arg {
208+ found = true
209+ }
210+ }
211+
212+ switch {
213+ case wantSet && ! found :
214+ t .Logf ("did not find arg %s within %s" , arg , c .Args )
215+ return false , nil
216+ case ! wantSet && found :
217+ t .Logf ("unexpectedly found arg %s within %s" , arg , c .Args )
218+ return false , nil
219+ default :
220+ return true , nil
221+ }
222+ }, wait .WithTimeout (d ), wait .WithInterval (DefaultPollInterval )); err != nil {
223+ t .Fatal (err )
224+ return ctx
225+ }
226+
227+ t .Logf ("Deployment %s/%s has pod with arg %s set=%t after %s" , dp .GetNamespace (), dp .GetName (), arg , wantSet , since (start ))
228+ return ctx
229+ }
230+ }
231+
136232// ResourcesCreatedWithin fails a test if the supplied resources are not found
137233// to exist within the supplied duration.
138234func ResourcesCreatedWithin (d time.Duration , dir , pattern string , options ... decoder.DecodeOption ) features.Func {
@@ -597,9 +693,10 @@ func ClaimUnderTestMustNotChangeWithin(d time.Duration) features.Func {
597693 if err := wait .For (conditions .New (c .Client ().Resources ()).ResourcesMatch (list , m ), wait .WithTimeout (d )); err != nil {
598694 if deadlineExceed (err ) {
599695 t .Logf ("Claim %s did not change within %s" , identifier (cm ), d .String ())
600- } else {
601- t .Errorf ("Error while observing claim %s: %v" , identifier (cm ), err )
696+ return ctx
602697 }
698+
699+ t .Errorf ("Error while observing claim %s: %v" , identifier (cm ), err )
603700 return ctx
604701 }
605702 t .Errorf ("Claim %s changed within %s, but it should not have" , identifier (cm ), d .String ())
@@ -644,9 +741,10 @@ func CompositeUnderTestMustNotChangeWithin(d time.Duration) features.Func {
644741 if err := wait .For (conditions .New (c .Client ().Resources ()).ResourcesMatch (list , m ), wait .WithTimeout (d )); err != nil {
645742 if deadlineExceed (err ) {
646743 t .Logf ("Composite resource %s did not change within %s" , identifier (cp ), d .String ())
647- } else {
648- t .Errorf ("Error while observing composite resource %s: %v" , identifier (cp ), err )
744+ return ctx
649745 }
746+
747+ t .Errorf ("Error while observing composite resource %s: %v" , identifier (cp ), err )
650748 return ctx
651749 }
652750 t .Errorf ("Composite resource %s changed within %s, but it should not have" , identifier (cp ), d .String ())
@@ -727,7 +825,7 @@ func CompositeResourceHasFieldValueWithin(d time.Duration, dir, claimFile, path
727825 return got != ""
728826 }
729827
730- if err := wait .For (conditions .New (c .Client ().Resources ()).ResourceMatch (cm , hasResourceRef ), wait .WithTimeout (d ), wait .WithInterval (DefaultPollInterval )); err != nil {
828+ if err := wait .For (conditions .New (c .Client ().Resources ()).ResourceMatch (cm , hasResourceRef ), wait .WithTimeout (d ), wait .WithInterval (DefaultPollInterval ), wait . WithImmediate () ); err != nil {
731829 t .Errorf ("Claim %q does not have a resourceRef to an XR: %v" , cm .GetName (), err )
732830 return ctx
733831 }
@@ -1026,7 +1124,7 @@ func DeletionBlockedByUsageWebhook(dir, pattern string, options ...decoder.Decod
10261124 return ctx
10271125 }
10281126
1029- if ! strings .HasPrefix (err .Error (), "admission webhook \" nousages.apiextensions.crossplane.io\" denied the request" ) {
1127+ if ! strings .Contains (err .Error (), "admission webhook \" nousages.apiextensions.crossplane.io\" denied the request" ) {
10301128 t .Fatalf ("expected the usage webhook to deny the request but it failed with err: %s" , err .Error ())
10311129 return ctx
10321130 }
@@ -1168,3 +1266,33 @@ func since(t time.Time) string {
11681266func deadlineExceed (err error ) bool {
11691267 return errors .Is (err , context .DeadlineExceeded ) || strings .Contains (err .Error (), "would exceed context deadline" )
11701268}
1269+
1270+ // podForDeployment returns the pod for a given Deployment. If the number of
1271+ // pods found is not exactly one, or that one pod does not have exactly one
1272+ // container, then this function returns an error.
1273+ func podForDeployment (ctx context.Context , t * testing.T , c * envconf.Config , dp * appsv1.Deployment ) (* corev1.Pod , error ) {
1274+ t .Helper ()
1275+ if err := c .Client ().Resources ().Get (ctx , dp .GetName (), dp .GetNamespace (), dp ); err != nil {
1276+ t .Logf ("failed to get deployment %s/%s: %s" , dp .GetNamespace (), dp .GetName (), err )
1277+ return nil , err
1278+ }
1279+
1280+ // use the deployment's selector to list all pods belonging to the deployment
1281+ selector := metav1 .FormatLabelSelector (dp .Spec .Selector )
1282+ pods := & corev1.PodList {}
1283+ if err := c .Client ().Resources ().List (ctx , pods , resources .WithLabelSelector (selector )); err != nil {
1284+ t .Logf ("failed to list pods for deployment %s/%s: %s" , dp .GetNamespace (), dp .GetName (), err )
1285+ return nil , err
1286+ }
1287+
1288+ if len (pods .Items ) != 1 {
1289+ return nil , errors .Errorf ("expected 1 pod, found %d" , len (pods .Items ))
1290+ }
1291+
1292+ pod := pods .Items [0 ]
1293+ if len (pod .Spec .Containers ) != 1 {
1294+ return nil , errors .Errorf ("expected 1 container, found %d" , len (pod .Spec .Containers ))
1295+ }
1296+
1297+ return & pod , nil
1298+ }
0 commit comments