github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/operators/olm/operatorgroup_test.go (about) 1 package olm 2 3 import ( 4 "fmt" 5 "testing" 6 7 "github.com/google/go-cmp/cmp" 8 "github.com/sirupsen/logrus/hooks/test" 9 "github.com/stretchr/testify/assert" 10 "github.com/stretchr/testify/require" 11 "k8s.io/client-go/metadata/metadatalister" 12 13 "k8s.io/apimachinery/pkg/api/errors" 14 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 "k8s.io/apimachinery/pkg/labels" 16 ktesting "k8s.io/client-go/testing" 17 18 "github.com/operator-framework/api/pkg/operators/v1alpha1" 19 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned/fake" 20 ) 21 22 func TestCopyToNamespace(t *testing.T) { 23 gvr := v1alpha1.SchemeGroupVersion.WithResource("clusterserviceversions") 24 25 for _, tc := range []struct { 26 Name string 27 FromNamespace string 28 ToNamespace string 29 Hash string 30 StatusHash string 31 Prototype v1alpha1.ClusterServiceVersion 32 ExistingCopy *metav1.PartialObjectMetadata 33 ExpectedResult *v1alpha1.ClusterServiceVersion 34 ExpectedError error 35 ExpectedActions []ktesting.Action 36 }{ 37 { 38 Name: "copy to original namespace returns error", 39 FromNamespace: "samesies", 40 ToNamespace: "samesies", 41 ExpectedError: fmt.Errorf("bug: can not copy to active namespace samesies"), 42 }, 43 { 44 Name: "copy created if does not exist", 45 FromNamespace: "from", 46 ToNamespace: "to", 47 Prototype: v1alpha1.ClusterServiceVersion{ 48 ObjectMeta: metav1.ObjectMeta{ 49 Name: "name", 50 }, 51 Spec: v1alpha1.ClusterServiceVersionSpec{ 52 Replaces: "replacee", 53 }, 54 Status: v1alpha1.ClusterServiceVersionStatus{ 55 Phase: "waxing gibbous", 56 }, 57 }, 58 ExpectedActions: []ktesting.Action{ 59 ktesting.NewCreateAction(gvr, "to", &v1alpha1.ClusterServiceVersion{ 60 ObjectMeta: metav1.ObjectMeta{ 61 Name: "name", 62 Namespace: "to", 63 }, 64 Spec: v1alpha1.ClusterServiceVersionSpec{ 65 Replaces: "replacee", 66 }, 67 Status: v1alpha1.ClusterServiceVersionStatus{ 68 Phase: "waxing gibbous", 69 }, 70 }), 71 ktesting.NewUpdateSubresourceAction(gvr, "status", "to", &v1alpha1.ClusterServiceVersion{ 72 ObjectMeta: metav1.ObjectMeta{ 73 Name: "name", 74 Namespace: "to", 75 }, 76 Spec: v1alpha1.ClusterServiceVersionSpec{ 77 Replaces: "replacee", 78 }, 79 Status: v1alpha1.ClusterServiceVersionStatus{ 80 Phase: "waxing gibbous", 81 }, 82 }), 83 }, 84 ExpectedResult: &v1alpha1.ClusterServiceVersion{ 85 ObjectMeta: metav1.ObjectMeta{ 86 Name: "name", 87 Namespace: "to", 88 }, 89 }, 90 }, 91 { 92 Name: "copy updated if hash differs", 93 FromNamespace: "from", 94 ToNamespace: "to", 95 Hash: "hn-1", 96 StatusHash: "hs", 97 Prototype: v1alpha1.ClusterServiceVersion{ 98 ObjectMeta: metav1.ObjectMeta{ 99 Name: "name", 100 }, 101 Spec: v1alpha1.ClusterServiceVersionSpec{ 102 Replaces: "replacee", 103 }, 104 Status: v1alpha1.ClusterServiceVersionStatus{ 105 Phase: "waxing gibbous", 106 }, 107 }, 108 ExistingCopy: &metav1.PartialObjectMetadata{ 109 ObjectMeta: metav1.ObjectMeta{ 110 Name: "name", 111 Namespace: "to", 112 UID: "uid", 113 ResourceVersion: "42", 114 Annotations: map[string]string{ 115 "$copyhash-nonstatus": "hn-2", 116 "$copyhash-status": "hs", 117 }, 118 }, 119 }, 120 ExpectedResult: &v1alpha1.ClusterServiceVersion{ 121 ObjectMeta: metav1.ObjectMeta{ 122 Name: "name", 123 Namespace: "to", 124 UID: "uid", 125 }, 126 }, 127 ExpectedActions: []ktesting.Action{ 128 ktesting.NewUpdateAction(gvr, "to", &v1alpha1.ClusterServiceVersion{ 129 ObjectMeta: metav1.ObjectMeta{ 130 Name: "name", 131 Namespace: "to", 132 UID: "uid", 133 ResourceVersion: "42", 134 }, 135 Spec: v1alpha1.ClusterServiceVersionSpec{ 136 Replaces: "replacee", 137 }, 138 Status: v1alpha1.ClusterServiceVersionStatus{ 139 Phase: "waxing gibbous", 140 }, 141 }), 142 }, 143 }, 144 { 145 Name: "copy status updated if status hash differs", 146 FromNamespace: "from", 147 ToNamespace: "to", 148 Hash: "hn", 149 StatusHash: "hs-1", 150 Prototype: v1alpha1.ClusterServiceVersion{ 151 ObjectMeta: metav1.ObjectMeta{ 152 Name: "name", 153 }, 154 Spec: v1alpha1.ClusterServiceVersionSpec{ 155 Replaces: "replacee", 156 }, 157 Status: v1alpha1.ClusterServiceVersionStatus{ 158 Phase: "waxing gibbous", 159 }, 160 }, 161 ExistingCopy: &metav1.PartialObjectMetadata{ 162 ObjectMeta: metav1.ObjectMeta{ 163 Name: "name", 164 Namespace: "to", 165 UID: "uid", 166 ResourceVersion: "42", 167 Annotations: map[string]string{ 168 "$copyhash-nonstatus": "hn", 169 "$copyhash-status": "hs-2", 170 }, 171 }, 172 }, 173 ExpectedResult: &v1alpha1.ClusterServiceVersion{ 174 ObjectMeta: metav1.ObjectMeta{ 175 Name: "name", 176 Namespace: "to", 177 UID: "uid", 178 }, 179 }, 180 ExpectedActions: []ktesting.Action{ 181 ktesting.NewUpdateSubresourceAction(gvr, "status", "to", &v1alpha1.ClusterServiceVersion{ 182 ObjectMeta: metav1.ObjectMeta{ 183 Name: "name", 184 Namespace: "to", 185 UID: "uid", 186 ResourceVersion: "42", 187 }, 188 Spec: v1alpha1.ClusterServiceVersionSpec{ 189 Replaces: "replacee", 190 }, 191 Status: v1alpha1.ClusterServiceVersionStatus{ 192 Phase: "waxing gibbous", 193 }, 194 }), 195 }, 196 }, 197 { 198 Name: "copy and copy status updated if both hashes differ", 199 FromNamespace: "from", 200 ToNamespace: "to", 201 Hash: "hn-1", 202 StatusHash: "hs-1", 203 Prototype: v1alpha1.ClusterServiceVersion{ 204 ObjectMeta: metav1.ObjectMeta{ 205 Name: "name", 206 }, 207 Spec: v1alpha1.ClusterServiceVersionSpec{ 208 Replaces: "replacee", 209 }, 210 Status: v1alpha1.ClusterServiceVersionStatus{ 211 Phase: "waxing gibbous", 212 }, 213 }, 214 ExistingCopy: &metav1.PartialObjectMetadata{ 215 ObjectMeta: metav1.ObjectMeta{ 216 Name: "name", 217 Namespace: "to", 218 UID: "uid", 219 ResourceVersion: "42", 220 Annotations: map[string]string{ 221 "$copyhash-nonstatus": "hn-2", 222 "$copyhash-status": "hs-2", 223 }, 224 }, 225 }, 226 ExpectedResult: &v1alpha1.ClusterServiceVersion{ 227 ObjectMeta: metav1.ObjectMeta{ 228 Name: "name", 229 Namespace: "to", 230 UID: "uid", 231 }, 232 }, 233 ExpectedActions: []ktesting.Action{ 234 ktesting.NewUpdateAction(gvr, "to", &v1alpha1.ClusterServiceVersion{ 235 ObjectMeta: metav1.ObjectMeta{ 236 Name: "name", 237 Namespace: "to", 238 UID: "uid", 239 ResourceVersion: "42", 240 }, 241 Spec: v1alpha1.ClusterServiceVersionSpec{ 242 Replaces: "replacee", 243 }, 244 Status: v1alpha1.ClusterServiceVersionStatus{ 245 Phase: "waxing gibbous", 246 }, 247 }), 248 ktesting.NewUpdateSubresourceAction(gvr, "status", "to", &v1alpha1.ClusterServiceVersion{ 249 ObjectMeta: metav1.ObjectMeta{ 250 Name: "name", 251 Namespace: "to", 252 UID: "uid", 253 ResourceVersion: "42", 254 }, 255 Spec: v1alpha1.ClusterServiceVersionSpec{ 256 Replaces: "replacee", 257 }, 258 Status: v1alpha1.ClusterServiceVersionStatus{ 259 Phase: "waxing gibbous", 260 }, 261 }), 262 }, 263 }, 264 { 265 Name: "no action taken if neither hash differs", 266 FromNamespace: "from", 267 ToNamespace: "to", 268 Hash: "hn", 269 StatusHash: "hs", 270 Prototype: v1alpha1.ClusterServiceVersion{ 271 ObjectMeta: metav1.ObjectMeta{ 272 Name: "name", 273 }, 274 }, 275 ExistingCopy: &metav1.PartialObjectMetadata{ 276 ObjectMeta: metav1.ObjectMeta{ 277 Name: "name", 278 Namespace: "to", 279 UID: "uid", 280 Annotations: map[string]string{ 281 "$copyhash-nonstatus": "hn", 282 "$copyhash-status": "hs", 283 }, 284 }, 285 }, 286 ExpectedResult: &v1alpha1.ClusterServiceVersion{ 287 ObjectMeta: metav1.ObjectMeta{ 288 Name: "name", 289 Namespace: "to", 290 UID: "uid", 291 }, 292 }, 293 }, 294 } { 295 t.Run(tc.Name, func(t *testing.T) { 296 client := fake.NewSimpleClientset() 297 var lister metadatalister.Lister 298 if tc.ExistingCopy != nil { 299 client = fake.NewSimpleClientset(&v1alpha1.ClusterServiceVersion{ 300 ObjectMeta: tc.ExistingCopy.ObjectMeta, 301 }) 302 lister = FakeClusterServiceVersionLister{tc.ExistingCopy} 303 } else { 304 lister = FakeClusterServiceVersionLister{{}} 305 } 306 307 logger, _ := test.NewNullLogger() 308 o := &Operator{ 309 copiedCSVLister: lister, 310 client: client, 311 logger: logger, 312 } 313 314 result, err := o.copyToNamespace(tc.Prototype.DeepCopy(), tc.FromNamespace, tc.ToNamespace, tc.Hash, tc.StatusHash) 315 316 if tc.ExpectedError == nil { 317 require.NoError(t, err) 318 } else { 319 require.EqualError(t, err, tc.ExpectedError.Error()) 320 } 321 if diff := cmp.Diff(tc.ExpectedResult, result); diff != "" { 322 t.Errorf("incorrect result: %v", diff) 323 } 324 325 actions := client.Actions() 326 if len(actions) == 0 { 327 actions = nil 328 } 329 if diff := cmp.Diff(tc.ExpectedActions, actions); diff != "" { 330 t.Errorf("incorrect actions: %v", diff) 331 } 332 }) 333 } 334 } 335 336 type FakeClusterServiceVersionLister []*metav1.PartialObjectMetadata 337 338 func (l FakeClusterServiceVersionLister) List(selector labels.Selector) ([]*metav1.PartialObjectMetadata, error) { 339 var result []*metav1.PartialObjectMetadata 340 for _, csv := range l { 341 if !selector.Matches(labels.Set(csv.GetLabels())) { 342 continue 343 } 344 result = append(result, csv) 345 } 346 return result, nil 347 } 348 349 func (l FakeClusterServiceVersionLister) Namespace(namespace string) metadatalister.NamespaceLister { 350 var filtered []*metav1.PartialObjectMetadata 351 for _, csv := range l { 352 if csv.GetNamespace() != namespace { 353 continue 354 } 355 filtered = append(filtered, csv) 356 } 357 return FakeClusterServiceVersionLister(filtered) 358 } 359 360 func (l FakeClusterServiceVersionLister) Get(name string) (*metav1.PartialObjectMetadata, error) { 361 for _, csv := range l { 362 if csv.GetName() == name { 363 return csv, nil 364 } 365 } 366 return nil, errors.NewNotFound(v1alpha1.Resource("clusterserviceversion"), name) 367 } 368 369 var ( 370 _ metadatalister.Lister = FakeClusterServiceVersionLister{} 371 _ metadatalister.NamespaceLister = FakeClusterServiceVersionLister{} 372 ) 373 374 func TestCSVCopyPrototype(t *testing.T) { 375 src := v1alpha1.ClusterServiceVersion{ 376 ObjectMeta: metav1.ObjectMeta{ 377 Name: "name", 378 Namespace: "foo", 379 Annotations: map[string]string{ 380 "olm.targetNamespaces": "a,b,c", 381 "kubectl.kubernetes.io/last-applied-configuration": "{}", 382 "preserved": "yes", 383 }, 384 Labels: map[string]string{ 385 "operators.coreos.com/foo": "", 386 "operators.coreos.com/bar": "", 387 "untouched": "fine", 388 }, 389 }, 390 } 391 var dst v1alpha1.ClusterServiceVersion 392 csvCopyPrototype(&src, &dst) 393 assert.Equal(t, v1alpha1.ClusterServiceVersion{ 394 ObjectMeta: metav1.ObjectMeta{ 395 Name: "name", 396 Annotations: map[string]string{ 397 "preserved": "yes", 398 }, 399 Labels: map[string]string{ 400 "untouched": "fine", 401 "olm.copiedFrom": "foo", 402 }, 403 }, 404 Status: v1alpha1.ClusterServiceVersionStatus{ 405 Message: "The operator is running in foo but is managing this namespace", 406 Reason: v1alpha1.CSVReasonCopied, 407 }, 408 }, dst) 409 }