@@ -10,6 +10,7 @@ import (
1010 "github.com/google/go-containerregistry/pkg/crane"
1111 "github.com/stretchr/testify/assert"
1212 "github.com/stretchr/testify/require"
13+ appsv1 "k8s.io/api/apps/v1"
1314 corev1 "k8s.io/api/core/v1"
1415 networkingv1 "k8s.io/api/networking/v1"
1516 rbacv1 "k8s.io/api/rbac/v1"
@@ -20,6 +21,7 @@ import (
2021 "k8s.io/apimachinery/pkg/labels"
2122 "k8s.io/apimachinery/pkg/types"
2223 "k8s.io/apimachinery/pkg/util/rand"
24+ "k8s.io/utils/ptr"
2325 "sigs.k8s.io/controller-runtime/pkg/client"
2426
2527 ocv1 "github.com/operator-framework/operator-controller/api/v1"
@@ -204,20 +206,32 @@ func createClusterRoleAndBindingForSA(ctx context.Context, name string, sa *core
204206}
205207
206208func testInit (t * testing.T ) (* ocv1.ClusterExtension , * ocv1.ClusterCatalog , * corev1.ServiceAccount , * corev1.Namespace ) {
207- var err error
209+ ce , cc := testInitClusterExtensionClusterCatalog (t )
210+ sa , ns := testInitServiceAccountNamespace (t , ce .Name )
211+ return ce , cc , sa , ns
212+ }
208213
209- clusterExtensionName := fmt .Sprintf ("clusterextension-%s" , rand .String (8 ))
214+ func testInitClusterExtensionClusterCatalog (t * testing.T ) (* ocv1.ClusterExtension , * ocv1.ClusterCatalog ) {
215+ ceName := fmt .Sprintf ("clusterextension-%s" , rand .String (8 ))
210216
211- ns , err := createNamespace (context .Background (), clusterExtensionName )
212- require .NoError (t , err )
213-
214- clusterExtension := & ocv1.ClusterExtension {
217+ ce := & ocv1.ClusterExtension {
215218 ObjectMeta : metav1.ObjectMeta {
216- Name : clusterExtensionName ,
219+ Name : ceName ,
217220 },
218221 }
219222
220- extensionCatalog , err := createTestCatalog (context .Background (), testCatalogName , os .Getenv (testCatalogRefEnvVar ))
223+ cc , err := createTestCatalog (context .Background (), testCatalogName , os .Getenv (testCatalogRefEnvVar ))
224+ require .NoError (t , err )
225+
226+ validateCatalogUnpack (t )
227+
228+ return ce , cc
229+ }
230+
231+ func testInitServiceAccountNamespace (t * testing.T , clusterExtensionName string ) (* corev1.ServiceAccount , * corev1.Namespace ) {
232+ var err error
233+
234+ ns , err := createNamespace (context .Background (), clusterExtensionName )
221235 require .NoError (t , err )
222236
223237 name := types.NamespacedName {
@@ -228,9 +242,7 @@ func testInit(t *testing.T) (*ocv1.ClusterExtension, *ocv1.ClusterCatalog, *core
228242 sa , err := createServiceAccount (context .Background (), name , clusterExtensionName )
229243 require .NoError (t , err )
230244
231- validateCatalogUnpack (t )
232-
233- return clusterExtension , extensionCatalog , sa , ns
245+ return sa , ns
234246}
235247
236248func validateCatalogUnpack (t * testing.T ) {
@@ -292,35 +304,42 @@ func ensureNoExtensionResources(t *testing.T, clusterExtensionName string) {
292304}
293305
294306func testCleanup (t * testing.T , cat * ocv1.ClusterCatalog , clusterExtension * ocv1.ClusterExtension , sa * corev1.ServiceAccount , ns * corev1.Namespace ) {
295- t .Logf ("By deleting ClusterCatalog %q" , cat .Name )
296- require .NoError (t , c .Delete (context .Background (), cat ))
297- require .Eventually (t , func () bool {
298- err := c .Get (context .Background (), types.NamespacedName {Name : cat .Name }, & ocv1.ClusterCatalog {})
299- return errors .IsNotFound (err )
300- }, pollDuration , pollInterval )
301-
302- t .Logf ("By deleting ClusterExtension %q" , clusterExtension .Name )
303- require .NoError (t , c .Delete (context .Background (), clusterExtension ))
304- require .Eventually (t , func () bool {
305- err := c .Get (context .Background (), types.NamespacedName {Name : clusterExtension .Name }, & ocv1.ClusterExtension {})
306- return errors .IsNotFound (err )
307- }, pollDuration , pollInterval )
307+ if cat != nil {
308+ t .Logf ("By deleting ClusterCatalog %q" , cat .Name )
309+ require .NoError (t , c .Delete (context .Background (), cat ))
310+ require .Eventually (t , func () bool {
311+ err := c .Get (context .Background (), types.NamespacedName {Name : cat .Name }, & ocv1.ClusterCatalog {})
312+ return errors .IsNotFound (err )
313+ }, pollDuration , pollInterval )
314+ }
308315
309- t .Logf ("By deleting ServiceAccount %q" , sa .Name )
310- require .NoError (t , c .Delete (context .Background (), sa ))
311- require .Eventually (t , func () bool {
312- err := c .Get (context .Background (), types.NamespacedName {Name : sa .Name , Namespace : sa .Namespace }, & corev1.ServiceAccount {})
313- return errors .IsNotFound (err )
314- }, pollDuration , pollInterval )
316+ if clusterExtension != nil {
317+ t .Logf ("By deleting ClusterExtension %q" , clusterExtension .Name )
318+ require .NoError (t , c .Delete (context .Background (), clusterExtension ))
319+ require .Eventually (t , func () bool {
320+ err := c .Get (context .Background (), types.NamespacedName {Name : clusterExtension .Name }, & ocv1.ClusterExtension {})
321+ return errors .IsNotFound (err )
322+ }, pollDuration , pollInterval )
323+ ensureNoExtensionResources (t , clusterExtension .Name )
324+ }
315325
316- ensureNoExtensionResources (t , clusterExtension .Name )
326+ if sa != nil {
327+ t .Logf ("By deleting ServiceAccount %q" , sa .Name )
328+ require .NoError (t , c .Delete (context .Background (), sa ))
329+ require .Eventually (t , func () bool {
330+ err := c .Get (context .Background (), types.NamespacedName {Name : sa .Name , Namespace : sa .Namespace }, & corev1.ServiceAccount {})
331+ return errors .IsNotFound (err )
332+ }, pollDuration , pollInterval )
333+ }
317334
318- t .Logf ("By deleting Namespace %q" , ns .Name )
319- require .NoError (t , c .Delete (context .Background (), ns ))
320- require .Eventually (t , func () bool {
321- err := c .Get (context .Background (), types.NamespacedName {Name : ns .Name }, & corev1.Namespace {})
322- return errors .IsNotFound (err )
323- }, pollDuration , pollInterval )
335+ if ns != nil {
336+ t .Logf ("By deleting Namespace %q" , ns .Name )
337+ require .NoError (t , c .Delete (context .Background (), ns ))
338+ require .Eventually (t , func () bool {
339+ err := c .Get (context .Background (), types.NamespacedName {Name : ns .Name }, & corev1.Namespace {})
340+ return errors .IsNotFound (err )
341+ }, pollDuration , pollInterval )
342+ }
324343}
325344
326345func TestClusterExtensionInstallRegistry (t * testing.T ) {
@@ -882,21 +901,95 @@ func TestClusterExtensionInstallReResolvesWhenManagedContentChanged(t *testing.T
882901 }, pollDuration , pollInterval )
883902}
884903
885- func TestClusterExtensionRecoversFromInitialInstallFailedWhenFailureFixed (t * testing.T ) {
904+ func TestClusterExtensionRecoversFromNoNamespaceWhenFailureFixed (t * testing.T ) {
886905 t .Log ("When a cluster extension is installed from a catalog" )
887906 t .Log ("When the extension bundle format is registry+v1" )
888907
889- clusterExtension , extensionCatalog , _ , ns := testInit (t )
908+ t .Log ("By not creating the Namespace and ServiceAccount" )
909+ clusterExtension , extensionCatalog := testInitClusterExtensionClusterCatalog (t )
890910
891- name := rand .String (10 )
892- sa := & corev1.ServiceAccount {
893- ObjectMeta : metav1.ObjectMeta {
894- Name : name ,
895- Namespace : ns .Name ,
911+ defer testCleanup (t , extensionCatalog , clusterExtension , nil , nil )
912+ defer utils .CollectTestArtifacts (t , artifactName , c , cfg )
913+
914+ clusterExtension .Spec = ocv1.ClusterExtensionSpec {
915+ Source : ocv1.SourceConfig {
916+ SourceType : "Catalog" ,
917+ Catalog : & ocv1.CatalogFilter {
918+ PackageName : "test" ,
919+ Selector : & metav1.LabelSelector {
920+ MatchLabels : map [string ]string {"olm.operatorframework.io/metadata.name" : extensionCatalog .Name },
921+ },
922+ },
923+ },
924+ Namespace : clusterExtension .Name ,
925+ ServiceAccount : ocv1.ServiceAccountReference {
926+ Name : clusterExtension .Name ,
896927 },
897928 }
898- err := c .Create (context .Background (), sa )
899- require .NoError (t , err )
929+
930+ t .Log ("It resolves the specified package with correct bundle path" )
931+ t .Log ("By creating the ClusterExtension resource" )
932+ require .NoError (t , c .Create (context .Background (), clusterExtension ))
933+
934+ t .Log ("By eventually reporting a successful resolution and bundle path" )
935+ require .EventuallyWithT (t , func (ct * assert.CollectT ) {
936+ require .NoError (ct , c .Get (context .Background (), types.NamespacedName {Name : clusterExtension .Name }, clusterExtension ))
937+ }, pollDuration , pollInterval )
938+
939+ t .Log ("By eventually reporting Progressing == True with Reason Retrying" )
940+ require .EventuallyWithT (t , func (ct * assert.CollectT ) {
941+ require .NoError (ct , c .Get (context .Background (), types.NamespacedName {Name : clusterExtension .Name }, clusterExtension ))
942+ cond := apimeta .FindStatusCondition (clusterExtension .Status .Conditions , ocv1 .TypeProgressing )
943+ require .NotNil (ct , cond )
944+ require .Equal (ct , metav1 .ConditionTrue , cond .Status )
945+ require .Equal (ct , ocv1 .ReasonRetrying , cond .Reason )
946+ }, pollDuration , pollInterval )
947+
948+ t .Log ("By eventually failing to install the package successfully due to no namespace" )
949+ require .EventuallyWithT (t , func (ct * assert.CollectT ) {
950+ require .NoError (ct , c .Get (context .Background (), types.NamespacedName {Name : clusterExtension .Name }, clusterExtension ))
951+ cond := apimeta .FindStatusCondition (clusterExtension .Status .Conditions , ocv1 .TypeInstalled )
952+ require .NotNil (ct , cond )
953+ require .Equal (ct , metav1 .ConditionUnknown , cond .Status )
954+ require .Equal (ct , ocv1 .ReasonFailed , cond .Reason )
955+ require .Contains (ct , cond .Message , fmt .Sprintf ("service account %q not found in namespace %q: unable to authenticate with the Kubernetes cluster." , clusterExtension .Name , clusterExtension .Name ))
956+ }, pollDuration , pollInterval )
957+
958+ t .Log ("By creating the Namespace and ServiceAccount" )
959+ sa , ns := testInitServiceAccountNamespace (t , clusterExtension .Name )
960+ defer testCleanup (t , nil , nil , sa , ns )
961+
962+ // NOTE: In order to ensure predictable results we need to ensure we have a single
963+ // known failure with a singular fix operation. Additionally, due to the exponential
964+ // backoff of this eventually check we MUST ensure we do not touch the ClusterExtension
965+ // after creating and binding the needed permissions to the ServiceAccount.
966+ t .Log ("By eventually installing the package successfully" )
967+ require .EventuallyWithT (t , func (ct * assert.CollectT ) {
968+ require .NoError (ct , c .Get (context .Background (), types.NamespacedName {Name : clusterExtension .Name }, clusterExtension ))
969+ cond := apimeta .FindStatusCondition (clusterExtension .Status .Conditions , ocv1 .TypeInstalled )
970+ require .NotNil (ct , cond )
971+ require .Equal (ct , metav1 .ConditionTrue , cond .Status )
972+ require .Equal (ct , ocv1 .ReasonSucceeded , cond .Reason )
973+ require .Contains (ct , cond .Message , "Installed bundle" )
974+ require .NotEmpty (ct , clusterExtension .Status .Install )
975+ }, pollDuration , pollInterval )
976+
977+ t .Log ("By eventually reporting Progressing == True with Reason Success" )
978+ require .EventuallyWithT (t , func (ct * assert.CollectT ) {
979+ require .NoError (ct , c .Get (context .Background (), types.NamespacedName {Name : clusterExtension .Name }, clusterExtension ))
980+ cond := apimeta .FindStatusCondition (clusterExtension .Status .Conditions , ocv1 .TypeProgressing )
981+ require .NotNil (ct , cond )
982+ require .Equal (ct , metav1 .ConditionTrue , cond .Status )
983+ require .Equal (ct , ocv1 .ReasonSucceeded , cond .Reason )
984+ }, pollDuration , pollInterval )
985+ }
986+
987+ func TestClusterExtensionRecoversFromExistingDeploymentWhenFailureFixed (t * testing.T ) {
988+ t .Log ("When a cluster extension is installed from a catalog" )
989+ t .Log ("When the extension bundle format is registry+v1" )
990+
991+ clusterExtension , extensionCatalog , sa , ns := testInit (t )
992+
900993 defer testCleanup (t , extensionCatalog , clusterExtension , sa , ns )
901994 defer utils .CollectTestArtifacts (t , artifactName , c , cfg )
902995
@@ -910,11 +1003,46 @@ func TestClusterExtensionRecoversFromInitialInstallFailedWhenFailureFixed(t *tes
9101003 },
9111004 },
9121005 },
913- Namespace : ns .Name ,
1006+ Namespace : clusterExtension .Name ,
9141007 ServiceAccount : ocv1.ServiceAccountReference {
915- Name : sa .Name ,
1008+ Name : clusterExtension .Name ,
1009+ },
1010+ }
1011+
1012+ t .Log ("By creating a new Deployment that can not be adopted" )
1013+ newDeployment := & appsv1.Deployment {
1014+ ObjectMeta : metav1.ObjectMeta {
1015+ Name : "test-operator" ,
1016+ Namespace : clusterExtension .Name ,
1017+ },
1018+ Spec : appsv1.DeploymentSpec {
1019+ Replicas : ptr .To (int32 (1 )),
1020+ Selector : & metav1.LabelSelector {
1021+ MatchLabels : map [string ]string {"app" : "test-operator" },
1022+ },
1023+ Template : corev1.PodTemplateSpec {
1024+ ObjectMeta : metav1.ObjectMeta {
1025+ Labels : map [string ]string {"app" : "test-operator" },
1026+ },
1027+ Spec : corev1.PodSpec {
1028+ Containers : []corev1.Container {
1029+ {
1030+ Command : []string {"sleep" , "1000" },
1031+ Image : "busybox" ,
1032+ ImagePullPolicy : corev1 .PullAlways ,
1033+ Name : "busybox" ,
1034+ SecurityContext : & corev1.SecurityContext {
1035+ RunAsNonRoot : ptr .To (true ),
1036+ RunAsUser : ptr .To (int64 (1000 )),
1037+ },
1038+ },
1039+ },
1040+ },
1041+ },
9161042 },
9171043 }
1044+ require .NoError (t , c .Create (context .Background (), newDeployment ))
1045+
9181046 t .Log ("It resolves the specified package with correct bundle path" )
9191047 t .Log ("By creating the ClusterExtension resource" )
9201048 require .NoError (t , c .Create (context .Background (), clusterExtension ))
@@ -933,18 +1061,18 @@ func TestClusterExtensionRecoversFromInitialInstallFailedWhenFailureFixed(t *tes
9331061 require .Equal (ct , ocv1 .ReasonRetrying , cond .Reason )
9341062 }, pollDuration , pollInterval )
9351063
936- t .Log ("By eventually failing to install the package successfully due to insufficient ServiceAccount permissions " )
1064+ t .Log ("By eventually failing to install the package successfully due to no adoption support " )
9371065 require .EventuallyWithT (t , func (ct * assert.CollectT ) {
9381066 require .NoError (ct , c .Get (context .Background (), types.NamespacedName {Name : clusterExtension .Name }, clusterExtension ))
9391067 cond := apimeta .FindStatusCondition (clusterExtension .Status .Conditions , ocv1 .TypeInstalled )
9401068 require .NotNil (ct , cond )
9411069 require .Equal (ct , metav1 .ConditionFalse , cond .Status )
9421070 require .Equal (ct , ocv1 .ReasonFailed , cond .Reason )
943- require .Equal (ct , "No bundle installed" , cond . Message )
1071+ require .Contains (ct , cond . Message , "No bundle installed" )
9441072 }, pollDuration , pollInterval )
9451073
946- t .Log ("By fixing the ServiceAccount permissions " )
947- require .NoError (t , createClusterRoleAndBindingForSA (context .Background (), name , sa , clusterExtension . Name ))
1074+ t .Log ("By deleting the new Deployment " )
1075+ require .NoError (t , c . Delete (context .Background (), newDeployment ))
9481076
9491077 // NOTE: In order to ensure predictable results we need to ensure we have a single
9501078 // known failure with a singular fix operation. Additionally, due to the exponential
0 commit comments