github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/registry/reconciler/grpc_test.go (about)

     1  package reconciler
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/operator-framework/api/pkg/operators/v1alpha1"
     9  	"github.com/sirupsen/logrus"
    10  	"github.com/stretchr/testify/require"
    11  	corev1 "k8s.io/api/core/v1"
    12  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    13  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  	"k8s.io/apimachinery/pkg/labels"
    15  	"k8s.io/apimachinery/pkg/runtime"
    16  	"k8s.io/apimachinery/pkg/types"
    17  
    18  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
    19  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/clientfake"
    20  )
    21  
    22  func validGrpcCatalogSource(image, address string) *v1alpha1.CatalogSource {
    23  	return &v1alpha1.CatalogSource{
    24  		ObjectMeta: metav1.ObjectMeta{
    25  			Name:      "img-catalog",
    26  			Namespace: testNamespace,
    27  			UID:       "catalog-uid",
    28  			Labels:    map[string]string{"olm.catalogSource": "img-catalog"},
    29  		},
    30  		Spec: v1alpha1.CatalogSourceSpec{
    31  			Image:      image,
    32  			Address:    address,
    33  			SourceType: v1alpha1.SourceTypeGrpc,
    34  		},
    35  	}
    36  }
    37  
    38  func grpcCatalogSourceWithSecret(secretNames []string) *v1alpha1.CatalogSource {
    39  	return &v1alpha1.CatalogSource{
    40  		ObjectMeta: metav1.ObjectMeta{
    41  			Name:      "private-catalog",
    42  			Namespace: testNamespace,
    43  			UID:       types.UID("catalog-uid"),
    44  			Labels:    map[string]string{"olm.catalogSource": "img-catalog"},
    45  		},
    46  		Spec: v1alpha1.CatalogSourceSpec{
    47  			Image:      "private-image",
    48  			Address:    "",
    49  			SourceType: v1alpha1.SourceTypeGrpc,
    50  			Secrets:    secretNames,
    51  		},
    52  	}
    53  }
    54  func grpcCatalogSourceWithStatus(status v1alpha1.CatalogSourceStatus) *v1alpha1.CatalogSource {
    55  	catsrc := validGrpcCatalogSource("image", "")
    56  	catsrc.Status = status
    57  	return catsrc
    58  }
    59  
    60  func grpcCatalogSourceWithAnnotations(annotations map[string]string) *v1alpha1.CatalogSource {
    61  	catsrc := validGrpcCatalogSource("image", "")
    62  	catsrc.ObjectMeta.Annotations = annotations
    63  	return catsrc
    64  }
    65  
    66  func grpcCatalogSourceWithName(name string) *v1alpha1.CatalogSource {
    67  	catsrc := validGrpcCatalogSource("image", "")
    68  	catsrc.SetName(name)
    69  	catsrc.ObjectMeta.Labels["olm.catalogSource"] = name
    70  	return catsrc
    71  }
    72  
    73  func withPodDeletedButNotRemoved(objs []runtime.Object) []runtime.Object {
    74  	var out []runtime.Object
    75  	for _, obj := range objs {
    76  		o := obj.DeepCopyObject()
    77  		if pod, ok := obj.(*corev1.Pod); ok {
    78  			pod.DeletionTimestamp = &metav1.Time{Time: time.Now()}
    79  			pod.Status.Conditions = append(pod.Status.Conditions, corev1.PodCondition{
    80  				Type:   corev1.DisruptionTarget,
    81  				Reason: "DeletionByTaintManager",
    82  				Status: corev1.ConditionTrue,
    83  			})
    84  			o = pod
    85  		}
    86  		out = append(out, o)
    87  	}
    88  	return out
    89  }
    90  func TestGrpcRegistryReconciler(t *testing.T) {
    91  	now := func() metav1.Time { return metav1.Date(2018, time.January, 26, 20, 40, 0, 0, time.UTC) }
    92  	blockOwnerDeletion := true
    93  	isController := true
    94  
    95  	// We expect the empty string secret name should not be set
    96  	// on the service account
    97  	testSecrets := []string{"test-secret", ""}
    98  
    99  	type cluster struct {
   100  		k8sObjs []runtime.Object
   101  	}
   102  	type in struct {
   103  		cluster cluster
   104  		catsrc  *v1alpha1.CatalogSource
   105  	}
   106  	type out struct {
   107  		status  *v1alpha1.RegistryServiceStatus
   108  		cluster cluster
   109  		err     error
   110  	}
   111  	tests := []struct {
   112  		testName string
   113  		in       in
   114  		out      out
   115  	}{
   116  		{
   117  			testName: "Grpc/NoExistingRegistry/CreateSuccessful",
   118  			in: in{
   119  				cluster: cluster{
   120  					k8sObjs: baseClusterState(),
   121  				},
   122  				catsrc: validGrpcCatalogSource("test-img", ""),
   123  			},
   124  			out: out{
   125  				status: &v1alpha1.RegistryServiceStatus{
   126  					CreatedAt:        now(),
   127  					Protocol:         "grpc",
   128  					ServiceName:      "img-catalog",
   129  					ServiceNamespace: testNamespace,
   130  					Port:             "50051",
   131  				},
   132  			},
   133  		},
   134  		{
   135  			testName: "Grpc/NoExistingRegistry/CreateSuccessful/CatalogSourceWithPeriodInNameCreatesValidServiceName",
   136  			in: in{
   137  				cluster: cluster{
   138  					k8sObjs: baseClusterState(),
   139  				},
   140  				catsrc: grpcCatalogSourceWithName("img.catalog"),
   141  			},
   142  			out: out{
   143  				status: &v1alpha1.RegistryServiceStatus{
   144  					CreatedAt:        now(),
   145  					Protocol:         "grpc",
   146  					ServiceName:      "img-catalog",
   147  					ServiceNamespace: testNamespace,
   148  					Port:             "50051",
   149  				},
   150  			},
   151  		},
   152  		{
   153  			testName: "Grpc/ExistingRegistry/CreateSuccessful",
   154  			in: in{
   155  				cluster: cluster{
   156  					k8sObjs: objectsForCatalogSource(t, validGrpcCatalogSource("test-img", "")),
   157  				},
   158  				catsrc: validGrpcCatalogSource("test-img", ""),
   159  			},
   160  			out: out{
   161  				status: &v1alpha1.RegistryServiceStatus{
   162  					CreatedAt:        now(),
   163  					Protocol:         "grpc",
   164  					ServiceName:      "img-catalog",
   165  					ServiceNamespace: testNamespace,
   166  					Port:             "50051",
   167  				},
   168  			},
   169  		},
   170  		{
   171  			testName: "Grpc/Address/CreateSuccessful",
   172  			in: in{
   173  				cluster: cluster{
   174  					k8sObjs: baseClusterState(),
   175  				},
   176  				catsrc: validGrpcCatalogSource("", "catalog.svc.cluster.local:50001"),
   177  			},
   178  			out: out{
   179  				status: &v1alpha1.RegistryServiceStatus{
   180  					CreatedAt: now(),
   181  					Protocol:  "grpc",
   182  				},
   183  			},
   184  		},
   185  		{
   186  			testName: "Grpc/AddressAndImage/CreateSuccessful",
   187  			in: in{
   188  				cluster: cluster{
   189  					k8sObjs: baseClusterState(),
   190  				},
   191  				catsrc: validGrpcCatalogSource("img-catalog", "catalog.svc.cluster.local:50001"),
   192  			},
   193  			out: out{
   194  				status: &v1alpha1.RegistryServiceStatus{
   195  					CreatedAt:        now(),
   196  					Protocol:         "grpc",
   197  					ServiceName:      "img-catalog",
   198  					ServiceNamespace: testNamespace,
   199  					Port:             "50051",
   200  				},
   201  			},
   202  		},
   203  		{
   204  			testName: "Grpc/ExistingRegistry/BadServiceWithWrongHash",
   205  			in: in{
   206  				cluster: cluster{
   207  					k8sObjs: setLabel(objectsForCatalogSource(t, validGrpcCatalogSource("test-img", "")), &corev1.Service{}, ServiceHashLabelKey, "wrongHash"),
   208  				},
   209  				catsrc: validGrpcCatalogSource("test-img", ""),
   210  			},
   211  			out: out{
   212  				status: &v1alpha1.RegistryServiceStatus{
   213  					CreatedAt:        now(),
   214  					Protocol:         "grpc",
   215  					ServiceName:      "img-catalog",
   216  					ServiceNamespace: testNamespace,
   217  					Port:             "50051",
   218  				},
   219  			},
   220  		},
   221  		{
   222  			testName: "Grpc/ExistingRegistry/BadService",
   223  			in: in{
   224  				cluster: cluster{
   225  					k8sObjs: modifyObjName(objectsForCatalogSource(t, validGrpcCatalogSource("test-img", "")), &corev1.Service{}, "badName"),
   226  				},
   227  				catsrc: validGrpcCatalogSource("test-img", ""),
   228  			},
   229  			out: out{
   230  				status: &v1alpha1.RegistryServiceStatus{
   231  					CreatedAt:        now(),
   232  					Protocol:         "grpc",
   233  					ServiceName:      "img-catalog",
   234  					ServiceNamespace: testNamespace,
   235  					Port:             "50051",
   236  				},
   237  			},
   238  		},
   239  		{
   240  			testName: "Grpc/ExistingRegistry/BadPod",
   241  			in: in{
   242  				cluster: cluster{
   243  					k8sObjs: setLabel(objectsForCatalogSource(t, validGrpcCatalogSource("test-img", "")), &corev1.Pod{}, CatalogSourceLabelKey, ""),
   244  				},
   245  				catsrc: validGrpcCatalogSource("test-img", ""),
   246  			},
   247  			out: out{
   248  				status: &v1alpha1.RegistryServiceStatus{
   249  					CreatedAt:        now(),
   250  					Protocol:         "grpc",
   251  					ServiceName:      "img-catalog",
   252  					ServiceNamespace: testNamespace,
   253  					Port:             "50051",
   254  				},
   255  			},
   256  		},
   257  		{
   258  			testName: "Grpc/ExistingRegistry/OldPod",
   259  			in: in{
   260  				cluster: cluster{
   261  					k8sObjs: objectsForCatalogSource(t, validGrpcCatalogSource("old-img", "")),
   262  				},
   263  				catsrc: validGrpcCatalogSource("new-img", ""),
   264  			},
   265  			out: out{
   266  				status: &v1alpha1.RegistryServiceStatus{
   267  					CreatedAt:        now(),
   268  					Protocol:         "grpc",
   269  					ServiceName:      "img-catalog",
   270  					ServiceNamespace: testNamespace,
   271  					Port:             "50051",
   272  				},
   273  			},
   274  		},
   275  		{
   276  			testName: "Grpc/PrivateRegistry/SAHasSecrets",
   277  			in: in{
   278  				cluster: cluster{
   279  					k8sObjs: []runtime.Object{
   280  						defaultNamespace(),
   281  						&corev1.Secret{
   282  							ObjectMeta: metav1.ObjectMeta{
   283  								Name:      "test-secret",
   284  								Namespace: testNamespace,
   285  							},
   286  						},
   287  					},
   288  				},
   289  				catsrc: grpcCatalogSourceWithSecret(testSecrets),
   290  			},
   291  			out: out{
   292  				status: &v1alpha1.RegistryServiceStatus{
   293  					CreatedAt:        now(),
   294  					Protocol:         "grpc",
   295  					ServiceName:      "private-catalog",
   296  					ServiceNamespace: testNamespace,
   297  					Port:             "50051",
   298  				},
   299  				cluster: cluster{
   300  					k8sObjs: []runtime.Object{
   301  						&corev1.ServiceAccount{
   302  							ObjectMeta: metav1.ObjectMeta{
   303  								Name:      "private-catalog",
   304  								Namespace: testNamespace,
   305  								Labels:    map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue},
   306  								OwnerReferences: []metav1.OwnerReference{
   307  									{
   308  										Name:               "private-catalog",
   309  										UID:                types.UID("catalog-uid"),
   310  										Kind:               v1alpha1.CatalogSourceKind,
   311  										APIVersion:         v1alpha1.CatalogSourceCRDAPIVersion,
   312  										BlockOwnerDeletion: &blockOwnerDeletion,
   313  										Controller:         &isController,
   314  									},
   315  								},
   316  							},
   317  							ImagePullSecrets: []corev1.LocalObjectReference{
   318  								{
   319  									Name: "test-secret",
   320  								},
   321  							},
   322  						},
   323  					},
   324  				},
   325  			},
   326  		},
   327  		{
   328  			testName: "Grpc/NoExistingRegistry/CreateWithAnnotations",
   329  			in: in{
   330  				cluster: cluster{
   331  					k8sObjs: baseClusterState(),
   332  				},
   333  				catsrc: grpcCatalogSourceWithAnnotations(map[string]string{
   334  					"annotation1": "value1",
   335  					"annotation2": "value2",
   336  				}),
   337  			},
   338  			out: out{
   339  				status: &v1alpha1.RegistryServiceStatus{
   340  					CreatedAt:        now(),
   341  					Protocol:         "grpc",
   342  					ServiceName:      "img-catalog",
   343  					ServiceNamespace: testNamespace,
   344  					Port:             "50051",
   345  				},
   346  			},
   347  		},
   348  		{
   349  			testName: "Grpc/ExistingRegistry/UpdateInvalidRegistryServiceStatus",
   350  			in: in{
   351  				cluster: cluster{
   352  					k8sObjs: objectsForCatalogSource(t, validGrpcCatalogSource("image", "")),
   353  				},
   354  				catsrc: grpcCatalogSourceWithStatus(v1alpha1.CatalogSourceStatus{
   355  					RegistryServiceStatus: &v1alpha1.RegistryServiceStatus{
   356  						CreatedAt: now(),
   357  						Protocol:  "grpc",
   358  					},
   359  				}),
   360  			},
   361  			out: out{
   362  				status: &v1alpha1.RegistryServiceStatus{
   363  					CreatedAt:        now(),
   364  					Protocol:         "grpc",
   365  					ServiceName:      "img-catalog",
   366  					ServiceNamespace: testNamespace,
   367  					Port:             "50051",
   368  				},
   369  			},
   370  		},
   371  	}
   372  	for _, tt := range tests {
   373  		t.Run(tt.testName, func(t *testing.T) {
   374  			stopc := make(chan struct{})
   375  			defer close(stopc)
   376  
   377  			factory, client := fakeReconcilerFactory(t, stopc, withNow(now), withK8sObjs(tt.in.cluster.k8sObjs...), withK8sClientOptions(clientfake.WithNameGeneration(t)))
   378  			rec := factory.ReconcilerForSource(tt.in.catsrc)
   379  
   380  			err := rec.EnsureRegistryServer(logrus.NewEntry(logrus.New()), tt.in.catsrc)
   381  
   382  			require.Equal(t, tt.out.err, err)
   383  			require.Equal(t, tt.out.status, tt.in.catsrc.Status.RegistryServiceStatus)
   384  
   385  			if tt.out.err != nil {
   386  				return
   387  			}
   388  
   389  			// Check for resource existence
   390  			decorated := grpcCatalogSourceDecorator{CatalogSource: tt.in.catsrc, createPodAsUser: runAsUser}
   391  			sa := decorated.ServiceAccount()
   392  			pod, err := decorated.Pod(sa, defaultPodSecurityConfig)
   393  			if err != nil {
   394  				t.Fatal(err)
   395  			}
   396  			service, err := decorated.Service()
   397  			if err != nil {
   398  				t.Fatal(err)
   399  			}
   400  			listOptions := metav1.ListOptions{LabelSelector: labels.SelectorFromSet(labels.Set{CatalogSourceLabelKey: tt.in.catsrc.GetName()}).String()}
   401  			outPods, podErr := client.KubernetesInterface().CoreV1().Pods(pod.GetNamespace()).List(context.TODO(), listOptions)
   402  			outService, serviceErr := client.KubernetesInterface().CoreV1().Services(service.GetNamespace()).Get(context.TODO(), service.GetName(), metav1.GetOptions{})
   403  			outsa, saerr := client.KubernetesInterface().CoreV1().ServiceAccounts(sa.GetNamespace()).Get(context.TODO(), sa.GetName(), metav1.GetOptions{})
   404  			switch rec.(type) {
   405  			case *GrpcRegistryReconciler:
   406  				// Should be created by a GrpcRegistryReconciler
   407  				require.NoError(t, podErr)
   408  				require.Len(t, outPods.Items, 1)
   409  				outPod := outPods.Items[0]
   410  				require.Equal(t, pod.GetGenerateName(), outPod.GetGenerateName())
   411  				require.Equal(t, pod.GetLabels(), outPod.GetLabels())
   412  				require.Equal(t, pod.GetAnnotations(), outPod.GetAnnotations())
   413  				require.Equal(t, pod.Spec, outPod.Spec)
   414  				require.NoError(t, serviceErr)
   415  				require.Equal(t, service, outService)
   416  				require.NoError(t, saerr)
   417  				if len(tt.in.catsrc.Spec.Secrets) > 0 {
   418  					require.Equal(t, tt.out.cluster.k8sObjs[0], outsa)
   419  				}
   420  			case *GrpcAddressRegistryReconciler:
   421  				// Should not be created by a GrpcAddressRegistryReconciler
   422  				require.NoError(t, podErr)
   423  				require.Len(t, outPods.Items, 0)
   424  				require.NoError(t, err)
   425  				require.True(t, apierrors.IsNotFound(serviceErr))
   426  			}
   427  		})
   428  	}
   429  }
   430  
   431  func TestRegistryPodPriorityClass(t *testing.T) {
   432  	now := func() metav1.Time { return metav1.Date(2018, time.January, 26, 20, 40, 0, 0, time.UTC) }
   433  
   434  	type cluster struct {
   435  		k8sObjs []runtime.Object
   436  	}
   437  	type in struct {
   438  		cluster cluster
   439  		catsrc  *v1alpha1.CatalogSource
   440  	}
   441  	tests := []struct {
   442  		testName      string
   443  		in            in
   444  		priorityclass string
   445  	}{
   446  		{
   447  			testName: "Grpc/WithValidPriorityClassAnnotation",
   448  			in: in{
   449  				catsrc: grpcCatalogSourceWithAnnotations(map[string]string{
   450  					"operatorframework.io/priorityclass": "system-cluster-critical",
   451  				}),
   452  			},
   453  			priorityclass: "system-cluster-critical",
   454  		},
   455  		{
   456  			testName: "Grpc/WithInvalidPriorityClassAnnotation",
   457  			in: in{
   458  				catsrc: grpcCatalogSourceWithAnnotations(map[string]string{
   459  					"operatorframework.io/priorityclass": "",
   460  				}),
   461  			},
   462  			priorityclass: "",
   463  		},
   464  		{
   465  			testName: "Grpc/WithNoPriorityClassAnnotation",
   466  			in: in{
   467  				catsrc: grpcCatalogSourceWithAnnotations(map[string]string{
   468  					"annotationkey": "annotationvalue",
   469  				}),
   470  			},
   471  			priorityclass: "",
   472  		},
   473  	}
   474  	for _, tt := range tests {
   475  		t.Run(tt.testName, func(t *testing.T) {
   476  			stopc := make(chan struct{})
   477  			defer close(stopc)
   478  
   479  			// a defaultNamespace resource must be present so that the reconciler can determine the
   480  			// security context configuration for the underlying pod
   481  			clusterState := append(tt.in.cluster.k8sObjs, defaultNamespace())
   482  
   483  			factory, client := fakeReconcilerFactory(t, stopc, withNow(now), withK8sObjs(clusterState...), withK8sClientOptions(clientfake.WithNameGeneration(t)))
   484  			rec := factory.ReconcilerForSource(tt.in.catsrc)
   485  
   486  			err := rec.EnsureRegistryServer(logrus.NewEntry(logrus.New()), tt.in.catsrc)
   487  			require.NoError(t, err)
   488  
   489  			// Check for resource existence
   490  			decorated := grpcCatalogSourceDecorator{CatalogSource: tt.in.catsrc, createPodAsUser: runAsUser}
   491  			pod, err := decorated.Pod(serviceAccount(tt.in.catsrc.Namespace, tt.in.catsrc.Name), defaultPodSecurityConfig)
   492  			require.NoError(t, err)
   493  			listOptions := metav1.ListOptions{LabelSelector: labels.SelectorFromSet(labels.Set{CatalogSourceLabelKey: tt.in.catsrc.GetName()}).String()}
   494  			outPods, podErr := client.KubernetesInterface().CoreV1().Pods(pod.GetNamespace()).List(context.TODO(), listOptions)
   495  			require.NoError(t, podErr)
   496  			require.Len(t, outPods.Items, 1)
   497  			outPod := outPods.Items[0]
   498  			require.Equal(t, tt.priorityclass, outPod.Spec.PriorityClassName)
   499  			require.Equal(t, pod.GetLabels()[PodHashLabelKey], outPod.GetLabels()[PodHashLabelKey])
   500  		})
   501  	}
   502  }
   503  
   504  func TestGrpcRegistryChecker(t *testing.T) {
   505  	type cluster struct {
   506  		k8sObjs []runtime.Object
   507  	}
   508  	type in struct {
   509  		cluster cluster
   510  		catsrc  *v1alpha1.CatalogSource
   511  	}
   512  	type out struct {
   513  		healthy bool
   514  		err     error
   515  	}
   516  	tests := []struct {
   517  		testName string
   518  		in       in
   519  		out      out
   520  	}{
   521  		{
   522  			testName: "Grpc/ExistingRegistry/Image/Healthy",
   523  			in: in{
   524  				cluster: cluster{
   525  					k8sObjs: objectsForCatalogSource(t, validGrpcCatalogSource("test-img", "")),
   526  				},
   527  				catsrc: validGrpcCatalogSource("test-img", ""),
   528  			},
   529  			out: out{
   530  				healthy: true,
   531  			},
   532  		},
   533  		{
   534  			testName: "Grpc/NoExistingRegistry/Image/NotHealthy",
   535  			in: in{
   536  				catsrc: validGrpcCatalogSource("test-img", ""),
   537  			},
   538  			out: out{
   539  				healthy: false,
   540  			},
   541  		},
   542  		{
   543  			testName: "Grpc/ExistingRegistry/Image/BadService",
   544  			in: in{
   545  				cluster: cluster{
   546  					k8sObjs: modifyObjName(objectsForCatalogSource(t, validGrpcCatalogSource("test-img", "")), &corev1.Service{}, "badName"),
   547  				},
   548  				catsrc: validGrpcCatalogSource("test-img", ""),
   549  			},
   550  			out: out{
   551  				healthy: false,
   552  			},
   553  		},
   554  		{
   555  			testName: "Grpc/ExistingRegistry/Image/BadServiceAccount",
   556  			in: in{
   557  				cluster: cluster{
   558  					k8sObjs: modifyObjName(objectsForCatalogSource(t, validGrpcCatalogSource("test-img", "")), &corev1.ServiceAccount{}, "badName"),
   559  				},
   560  				catsrc: validGrpcCatalogSource("test-img", ""),
   561  			},
   562  			out: out{
   563  				healthy: false,
   564  			},
   565  		},
   566  		{
   567  			testName: "Grpc/ExistingRegistry/Image/BadPod",
   568  			in: in{
   569  				cluster: cluster{
   570  					k8sObjs: setLabel(objectsForCatalogSource(t, validGrpcCatalogSource("test-img", "")), &corev1.Pod{}, CatalogSourceLabelKey, ""),
   571  				},
   572  				catsrc: validGrpcCatalogSource("test-img", ""),
   573  			},
   574  			out: out{
   575  				healthy: false,
   576  			},
   577  		},
   578  		{
   579  			testName: "Grpc/ExistingRegistry/Image/DeadPod",
   580  			in: in{
   581  				cluster: cluster{
   582  					k8sObjs: withPodDeletedButNotRemoved(objectsForCatalogSource(t, validGrpcCatalogSource("test-img", ""))),
   583  				},
   584  				catsrc: validGrpcCatalogSource("test-img", ""),
   585  			},
   586  			out: out{
   587  				healthy: false,
   588  			},
   589  		},
   590  		{
   591  			testName: "Grpc/ExistingRegistry/Image/OldPod/NotHealthy",
   592  			in: in{
   593  				cluster: cluster{
   594  					k8sObjs: objectsForCatalogSource(t, validGrpcCatalogSource("old-img", "")),
   595  				},
   596  				catsrc: validGrpcCatalogSource("new-img", ""),
   597  			},
   598  			out: out{
   599  				healthy: false,
   600  			},
   601  		},
   602  		{
   603  			testName: "Grpc/NoExistingRegistry/Address/Healthy",
   604  			in: in{
   605  				catsrc: validGrpcCatalogSource("", "catalog.svc.cluster.local:50001"),
   606  			},
   607  			out: out{
   608  				healthy: true,
   609  			},
   610  		},
   611  		{
   612  			testName: "Grpc/ExistingRegistry/AddressAndImage/Healthy",
   613  			in: in{
   614  				cluster: cluster{
   615  					k8sObjs: objectsForCatalogSource(t, validGrpcCatalogSource("img-catalog", "catalog.svc.cluster.local:50001")),
   616  				},
   617  				catsrc: validGrpcCatalogSource("img-catalog", "catalog.svc.cluster.local:50001"),
   618  			},
   619  			out: out{
   620  				healthy: true,
   621  			},
   622  		},
   623  		{
   624  			testName: "Grpc/NoExistingRegistry/AddressAndImage/NotHealthy",
   625  			in: in{
   626  				catsrc: validGrpcCatalogSource("img-catalog", "catalog.svc.cluster.local:50001"),
   627  			},
   628  			out: out{
   629  				healthy: false,
   630  			},
   631  		},
   632  		{
   633  			testName: "Grpc/ExistingRegistry/AddressAndImage/BadService/NotHealthy",
   634  			in: in{
   635  				cluster: cluster{
   636  					k8sObjs: modifyObjName(objectsForCatalogSource(t, validGrpcCatalogSource("test-img", "catalog.svc.cluster.local:50001")), &corev1.Service{}, "badName"),
   637  				},
   638  				catsrc: validGrpcCatalogSource("test-img", "catalog.svc.cluster.local:50001"),
   639  			},
   640  			out: out{
   641  				healthy: false,
   642  			},
   643  		},
   644  		{
   645  			testName: "Grpc/ExistingRegistry/AddressAndImage/OldPod/NotHealthy",
   646  			in: in{
   647  				cluster: cluster{
   648  					k8sObjs: objectsForCatalogSource(t, validGrpcCatalogSource("old-img", "catalog.svc.cluster.local:50001")),
   649  				},
   650  				catsrc: validGrpcCatalogSource("new-img", "catalog.svc.cluster.local:50001"),
   651  			},
   652  			out: out{
   653  				healthy: false,
   654  			},
   655  		},
   656  	}
   657  	for _, tt := range tests {
   658  		t.Run(tt.testName, func(t *testing.T) {
   659  			stopc := make(chan struct{})
   660  			defer close(stopc)
   661  
   662  			factory, _ := fakeReconcilerFactory(t, stopc, withK8sObjs(tt.in.cluster.k8sObjs...))
   663  			rec := factory.ReconcilerForSource(tt.in.catsrc)
   664  
   665  			healthy, err := rec.CheckRegistryServer(logrus.NewEntry(logrus.New()), tt.in.catsrc)
   666  
   667  			require.Equal(t, tt.out.err, err)
   668  			if tt.out.err != nil {
   669  				return
   670  			}
   671  
   672  			require.Equal(t, tt.out.healthy, healthy)
   673  		})
   674  	}
   675  }
   676  
   677  func TestGetPodImageID(t *testing.T) {
   678  	var table = []struct {
   679  		description string
   680  		pod         *corev1.Pod
   681  		result      string
   682  	}{
   683  		{
   684  			description: "default pod has status: return status",
   685  			pod:         &corev1.Pod{Status: corev1.PodStatus{ContainerStatuses: []corev1.ContainerStatus{{ImageID: "xyz123"}}}},
   686  			result:      "xyz123",
   687  		},
   688  		{
   689  			description: "extractConfig pod has status: return status",
   690  			pod: &corev1.Pod{Status: corev1.PodStatus{
   691  				InitContainerStatuses: []corev1.ContainerStatus{
   692  					{ImageID: "xyz123"},
   693  					{ImageID: "abc456"},
   694  				},
   695  				ContainerStatuses: []corev1.ContainerStatus{
   696  					{ImageID: "xyz123"},
   697  				},
   698  			}},
   699  			result: "abc456",
   700  		},
   701  		{
   702  			description: "pod has unexpected container config",
   703  			pod: &corev1.Pod{Status: corev1.PodStatus{ContainerStatuses: []corev1.ContainerStatus{
   704  				{ImageID: "xyz123"},
   705  				{ImageID: "abc456"},
   706  			}}},
   707  			result: "",
   708  		},
   709  		{
   710  			description: "pod has no status",
   711  			pod:         &corev1.Pod{Status: corev1.PodStatus{}},
   712  			result:      "",
   713  		},
   714  	}
   715  
   716  	for i, tt := range table {
   717  		require.Equal(t, tt.result, imageID(tt.pod), table[i].description)
   718  	}
   719  }
   720  
   721  func TestUpdatePodByDigest(t *testing.T) {
   722  	var table = []struct {
   723  		description string
   724  		updatePod   *corev1.Pod
   725  		servingPods []*corev1.Pod
   726  		result      bool
   727  	}{
   728  		{
   729  			description: "pod image ids match: not update from the registry: return false",
   730  			updatePod:   &corev1.Pod{Status: corev1.PodStatus{ContainerStatuses: []corev1.ContainerStatus{{ImageID: "xyz123"}}}},
   731  			servingPods: []*corev1.Pod{{Status: corev1.PodStatus{ContainerStatuses: []corev1.ContainerStatus{{ImageID: "xyz123"}}}}},
   732  			result:      false,
   733  		},
   734  		{
   735  			description: "pod image ids do not match: update on the registry: return true",
   736  			updatePod:   &corev1.Pod{Status: corev1.PodStatus{ContainerStatuses: []corev1.ContainerStatus{{ImageID: "abc456"}}}},
   737  			servingPods: []*corev1.Pod{{Status: corev1.PodStatus{ContainerStatuses: []corev1.ContainerStatus{{ImageID: "xyz123"}}}}},
   738  			result:      true,
   739  		},
   740  	}
   741  
   742  	for i, tt := range table {
   743  		require.Equal(t, tt.result, imageChanged(logrus.NewEntry(logrus.New()), tt.updatePod, tt.servingPods), table[i].description)
   744  	}
   745  }