github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/operators/olm/operator_test.go (about) 1 package olm 2 3 import ( 4 "context" 5 "crypto/ecdsa" 6 "crypto/elliptic" 7 "crypto/rand" 8 "crypto/x509" 9 "crypto/x509/pkix" 10 "encoding/pem" 11 "errors" 12 "fmt" 13 "math" 14 "math/big" 15 "reflect" 16 "sort" 17 "strings" 18 "testing" 19 "time" 20 21 "github.com/google/go-cmp/cmp" 22 configfake "github.com/openshift/client-go/config/clientset/versioned/fake" 23 hashutil "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/kubernetes/pkg/util/hash" 24 "github.com/sirupsen/logrus" 25 "github.com/stretchr/testify/assert" 26 "github.com/stretchr/testify/require" 27 admissionregistrationv1 "k8s.io/api/admissionregistration/v1" 28 appsv1 "k8s.io/api/apps/v1" 29 corev1 "k8s.io/api/core/v1" 30 rbacv1 "k8s.io/api/rbac/v1" 31 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 32 apiextensionsfake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake" 33 apierrors "k8s.io/apimachinery/pkg/api/errors" 34 "k8s.io/apimachinery/pkg/api/meta" 35 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 36 "k8s.io/apimachinery/pkg/labels" 37 "k8s.io/apimachinery/pkg/runtime" 38 "k8s.io/apimachinery/pkg/types" 39 utilerrors "k8s.io/apimachinery/pkg/util/errors" 40 "k8s.io/apimachinery/pkg/util/intstr" 41 "k8s.io/apimachinery/pkg/util/wait" 42 k8sfake "k8s.io/client-go/kubernetes/fake" 43 k8sscheme "k8s.io/client-go/kubernetes/scheme" 44 metadatafake "k8s.io/client-go/metadata/fake" 45 "k8s.io/client-go/pkg/version" 46 "k8s.io/client-go/rest" 47 "k8s.io/client-go/tools/cache" 48 "k8s.io/client-go/tools/record" 49 apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" 50 apiregistrationfake "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake" 51 utilclock "k8s.io/utils/clock" 52 utilclocktesting "k8s.io/utils/clock/testing" 53 54 operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" 55 "github.com/operator-framework/api/pkg/operators/v1alpha1" 56 opregistry "github.com/operator-framework/operator-registry/pkg/registry" 57 clienttesting "k8s.io/client-go/testing" 58 59 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" 60 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned/fake" 61 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/certs" 62 olmerrors "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/errors" 63 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install" 64 resolvercache "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" 65 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/clientfake" 66 csvutility "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/csv" 67 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/labeler" 68 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient" 69 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister" 70 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil" 71 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/queueinformer" 72 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/scoped" 73 ) 74 75 type TestStrategy struct{} 76 77 func (t *TestStrategy) GetStrategyName() string { 78 return "teststrategy" 79 } 80 81 type TestInstaller struct { 82 installErr error 83 checkInstallErr error 84 } 85 86 func NewTestInstaller(installErr error, checkInstallErr error) install.StrategyInstaller { 87 return &TestInstaller{ 88 installErr: installErr, 89 checkInstallErr: checkInstallErr, 90 } 91 } 92 93 func (i *TestInstaller) Install(s install.Strategy) error { 94 return i.installErr 95 } 96 97 func (i *TestInstaller) CheckInstalled(s install.Strategy) (bool, error) { 98 if i.checkInstallErr != nil { 99 return false, i.checkInstallErr 100 } 101 return true, nil 102 } 103 104 func (i *TestInstaller) ShouldRotateCerts(s install.Strategy) (bool, error) { 105 return false, nil 106 } 107 108 func (i *TestInstaller) CertsRotateAt() time.Time { 109 return time.Time{} 110 } 111 112 func (i *TestInstaller) CertsRotated() bool { 113 return false 114 } 115 116 func ownerLabelFromCSV(name, namespace string) map[string]string { 117 return map[string]string{ 118 ownerutil.OwnerKey: name, 119 ownerutil.OwnerNamespaceKey: namespace, 120 ownerutil.OwnerKind: v1alpha1.ClusterServiceVersionKind, 121 } 122 } 123 124 func addDepSpecHashLabel(t *testing.T, labels map[string]string, strategy v1alpha1.NamedInstallStrategy) map[string]string { 125 hash, err := hashutil.DeepHashObject(&strategy.StrategySpec.DeploymentSpecs[0].Spec) 126 if err != nil { 127 t.Fatal(err) 128 } 129 labels[install.DeploymentSpecHashLabelKey] = hash 130 return labels 131 } 132 133 func apiResourcesForObjects(objs []runtime.Object) []*metav1.APIResourceList { 134 apis := []*metav1.APIResourceList{} 135 for _, o := range objs { 136 switch o := o.(type) { 137 case *apiextensionsv1.CustomResourceDefinition: 138 crd := o 139 apis = append(apis, &metav1.APIResourceList{ 140 GroupVersion: metav1.GroupVersion{Group: crd.Spec.Group, Version: crd.Spec.Versions[0].Name}.String(), 141 APIResources: []metav1.APIResource{ 142 { 143 Name: crd.GetName(), 144 SingularName: crd.Spec.Names.Singular, 145 Namespaced: crd.Spec.Scope == apiextensionsv1.NamespaceScoped, 146 Group: crd.Spec.Group, 147 Version: crd.Spec.Versions[0].Name, 148 Kind: crd.Spec.Names.Kind, 149 }, 150 }, 151 }) 152 case *apiregistrationv1.APIService: 153 a := o 154 names := strings.Split(a.Name, ".") 155 apis = append(apis, &metav1.APIResourceList{ 156 GroupVersion: metav1.GroupVersion{Group: names[1], Version: a.Spec.Version}.String(), 157 APIResources: []metav1.APIResource{ 158 { 159 Name: names[1], 160 Group: names[1], 161 Version: a.Spec.Version, 162 Kind: names[1] + "Kind", 163 }, 164 }, 165 }) 166 } 167 } 168 return apis 169 } 170 171 // fakeOperatorConfig is the configuration for a fake operator. 172 type fakeOperatorConfig struct { 173 *operatorConfig 174 175 recorder record.EventRecorder 176 namespaces []string 177 fakeClientOptions []clientfake.Option 178 clientObjs []runtime.Object 179 k8sObjs []runtime.Object 180 extObjs []runtime.Object 181 regObjs []runtime.Object 182 partialMetadata []runtime.Object 183 actionLog *[]clienttesting.Action 184 } 185 186 // fakeOperatorOption applies an option to the given fake operator configuration. 187 type fakeOperatorOption func(*fakeOperatorConfig) 188 189 func withOperatorNamespace(namespace string) fakeOperatorOption { 190 return func(config *fakeOperatorConfig) { 191 config.operatorNamespace = namespace 192 } 193 } 194 195 func withClock(clock utilclock.Clock) fakeOperatorOption { 196 return func(config *fakeOperatorConfig) { 197 config.clock = clock 198 } 199 } 200 201 func withAPIReconciler(apiReconciler APIIntersectionReconciler) fakeOperatorOption { 202 return func(config *fakeOperatorConfig) { 203 if apiReconciler != nil { 204 config.apiReconciler = apiReconciler 205 } 206 } 207 } 208 209 func withAPILabeler(apiLabeler labeler.Labeler) fakeOperatorOption { 210 return func(config *fakeOperatorConfig) { 211 if apiLabeler != nil { 212 config.apiLabeler = apiLabeler 213 } 214 } 215 } 216 217 func withNamespaces(namespaces ...string) fakeOperatorOption { 218 return func(config *fakeOperatorConfig) { 219 config.namespaces = namespaces 220 } 221 } 222 223 func withClientObjs(clientObjs ...runtime.Object) fakeOperatorOption { 224 return func(config *fakeOperatorConfig) { 225 config.clientObjs = clientObjs 226 } 227 } 228 229 func withK8sObjs(k8sObjs ...runtime.Object) fakeOperatorOption { 230 return func(config *fakeOperatorConfig) { 231 config.k8sObjs = k8sObjs 232 } 233 } 234 235 func withExtObjs(extObjs ...runtime.Object) fakeOperatorOption { 236 return func(config *fakeOperatorConfig) { 237 config.extObjs = extObjs 238 } 239 } 240 241 func withRegObjs(regObjs ...runtime.Object) fakeOperatorOption { 242 return func(config *fakeOperatorConfig) { 243 config.regObjs = regObjs 244 } 245 } 246 247 func withPartialMetadata(objects ...runtime.Object) fakeOperatorOption { 248 return func(config *fakeOperatorConfig) { 249 config.partialMetadata = objects 250 } 251 } 252 253 func withActionLog(log *[]clienttesting.Action) fakeOperatorOption { 254 return func(config *fakeOperatorConfig) { 255 config.actionLog = log 256 } 257 } 258 259 func withLogger(logger *logrus.Logger) fakeOperatorOption { 260 return func(config *fakeOperatorConfig) { 261 config.logger = logger 262 } 263 } 264 265 // NewFakeOperator creates and starts a new operator using fake clients. 266 func NewFakeOperator(ctx context.Context, options ...fakeOperatorOption) (*Operator, error) { 267 logrus.SetLevel(logrus.DebugLevel) 268 // Apply options to default config 269 config := &fakeOperatorConfig{ 270 operatorConfig: &operatorConfig{ 271 resyncPeriod: queueinformer.ResyncWithJitter(5*time.Minute, 0.1), 272 operatorNamespace: "default", 273 watchedNamespaces: []string{metav1.NamespaceAll}, 274 clock: &utilclock.RealClock{}, 275 logger: logrus.New(), 276 strategyResolver: &install.StrategyResolver{}, 277 apiReconciler: APIIntersectionReconcileFunc(ReconcileAPIIntersection), 278 apiLabeler: labeler.Func(LabelSetsFor), 279 restConfig: &rest.Config{}, 280 }, 281 recorder: &record.FakeRecorder{}, 282 // default expected namespaces 283 namespaces: []string{"default", "kube-system", "kube-public"}, 284 actionLog: &[]clienttesting.Action{}, 285 } 286 for _, option := range options { 287 option(config) 288 } 289 290 scheme := runtime.NewScheme() 291 if err := k8sscheme.AddToScheme(scheme); err != nil { 292 return nil, err 293 } 294 if err := metav1.AddMetaToScheme(scheme); err != nil { 295 return nil, err 296 } 297 if err := fake.AddToScheme(scheme); err != nil { 298 return nil, err 299 } 300 301 // Create client fakes 302 externalFake := fake.NewReactionForwardingClientsetDecorator(config.clientObjs, config.fakeClientOptions...) 303 config.externalClient = externalFake 304 // TODO: Using the ReactionForwardingClientsetDecorator for k8s objects causes issues with adding Resources for discovery. 305 // For now, directly use a SimpleClientset instead. 306 k8sClientFake := k8sfake.NewSimpleClientset(config.k8sObjs...) 307 k8sClientFake.Resources = apiResourcesForObjects(append(config.extObjs, config.regObjs...)) 308 k8sClientFake.PrependReactor("*", "*", clienttesting.ReactionFunc(func(action clienttesting.Action) (bool, runtime.Object, error) { 309 *config.actionLog = append(*config.actionLog, action) 310 return false, nil, nil 311 })) 312 apiextensionsFake := apiextensionsfake.NewSimpleClientset(config.extObjs...) 313 config.operatorClient = operatorclient.NewClient(k8sClientFake, apiextensionsFake, apiregistrationfake.NewSimpleClientset(config.regObjs...)) 314 config.configClient = configfake.NewSimpleClientset() 315 metadataFake := metadatafake.NewSimpleMetadataClient(scheme, config.partialMetadata...) 316 config.metadataClient = metadataFake 317 // It's a travesty that we need to do this, but the fakes leave us no other option. In the API server, of course 318 // changes to objects are transparently exposed in the metadata client. In fake-land, we need to enforce that ourselves. 319 propagate := func(action clienttesting.Action) (bool, runtime.Object, error) { 320 var err error 321 switch action.GetVerb() { 322 case "create": 323 a := action.(clienttesting.CreateAction) 324 m := a.GetObject().(metav1.ObjectMetaAccessor).GetObjectMeta().(*metav1.ObjectMeta) 325 _, err = metadataFake.Resource(action.GetResource()).Namespace(action.GetNamespace()).(metadatafake.MetadataClient).CreateFake(&metav1.PartialObjectMetadata{ObjectMeta: *m}, metav1.CreateOptions{}) 326 case "update": 327 a := action.(clienttesting.UpdateAction) 328 m := a.GetObject().(metav1.ObjectMetaAccessor).GetObjectMeta().(*metav1.ObjectMeta) 329 _, err = metadataFake.Resource(action.GetResource()).Namespace(action.GetNamespace()).(metadatafake.MetadataClient).UpdateFake(&metav1.PartialObjectMetadata{ObjectMeta: *m}, metav1.UpdateOptions{}) 330 case "delete": 331 a := action.(clienttesting.DeleteAction) 332 err = metadataFake.Resource(action.GetResource()).Delete(context.TODO(), a.GetName(), metav1.DeleteOptions{}) 333 } 334 return false, nil, err 335 } 336 externalFake.PrependReactor("*", "*", propagate) 337 apiextensionsFake.PrependReactor("*", "*", propagate) 338 339 for _, ns := range config.namespaces { 340 _, err := config.operatorClient.KubernetesInterface().CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}}, metav1.CreateOptions{}) 341 // Ignore already-exists errors 342 if err != nil && !apierrors.IsAlreadyExists(err) { 343 return nil, err 344 } 345 } 346 347 op, err := newOperatorWithConfig(ctx, config.operatorConfig) 348 if err != nil { 349 return nil, err 350 } 351 op.recorder = config.recorder 352 353 op.csvSetGenerator = csvutility.NewSetGenerator(config.logger, op.lister) 354 op.csvReplaceFinder = csvutility.NewReplaceFinder(config.logger, config.externalClient) 355 op.serviceAccountSyncer = scoped.NewUserDefinedServiceAccountSyncer(config.logger, scheme, config.operatorClient, op.client) 356 357 // Only start the operator's informers (no reconciliation) 358 op.RunInformers(ctx) 359 360 if ok := cache.WaitForCacheSync(ctx.Done(), op.HasSynced); !ok { 361 return nil, fmt.Errorf("failed to wait for caches to sync") 362 } 363 364 op.clientFactory = &stubClientFactory{ 365 operatorClient: config.operatorClient, 366 kubernetesClient: config.externalClient, 367 } 368 369 return op, nil 370 } 371 372 type fakeAPIIntersectionReconciler struct { 373 Result APIReconciliationResult 374 } 375 376 func (f fakeAPIIntersectionReconciler) Reconcile(resolvercache.APISet, OperatorGroupSurface, ...OperatorGroupSurface) APIReconciliationResult { 377 return f.Result 378 } 379 380 func buildFakeAPIIntersectionReconcilerThatReturns(result APIReconciliationResult) APIIntersectionReconciler { 381 return fakeAPIIntersectionReconciler{ 382 Result: result, 383 } 384 } 385 386 func deployment(deploymentName, namespace, serviceAccountName string, templateAnnotations map[string]string) *appsv1.Deployment { 387 var ( 388 singleInstance = int32(1) 389 revisionHistoryLimit = int32(1) 390 ) 391 return &appsv1.Deployment{ 392 ObjectMeta: metav1.ObjectMeta{ 393 Name: deploymentName, 394 Namespace: namespace, 395 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 396 }, 397 Spec: appsv1.DeploymentSpec{ 398 Selector: &metav1.LabelSelector{ 399 MatchLabels: map[string]string{ 400 "app": deploymentName, 401 }, 402 }, 403 RevisionHistoryLimit: &revisionHistoryLimit, 404 Replicas: &singleInstance, 405 Template: corev1.PodTemplateSpec{ 406 ObjectMeta: metav1.ObjectMeta{ 407 Labels: map[string]string{ 408 "app": deploymentName, 409 }, 410 Annotations: templateAnnotations, 411 }, 412 Spec: corev1.PodSpec{ 413 ServiceAccountName: serviceAccountName, 414 Containers: []corev1.Container{ 415 { 416 Name: deploymentName + "-c1", 417 Image: "nginx:1.7.9", 418 Ports: []corev1.ContainerPort{ 419 { 420 ContainerPort: 80, 421 }, 422 }, 423 }, 424 }, 425 }, 426 }, 427 }, 428 Status: appsv1.DeploymentStatus{ 429 Replicas: singleInstance, 430 AvailableReplicas: singleInstance, 431 UpdatedReplicas: singleInstance, 432 Conditions: []appsv1.DeploymentCondition{{ 433 Type: appsv1.DeploymentAvailable, 434 Status: corev1.ConditionTrue, 435 }}, 436 }, 437 } 438 } 439 440 func serviceAccount(name, namespace string) *corev1.ServiceAccount { 441 serviceAccount := &corev1.ServiceAccount{ 442 ObjectMeta: metav1.ObjectMeta{ 443 Name: name, 444 Namespace: namespace, 445 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 446 }, 447 } 448 449 return serviceAccount 450 } 451 452 func service(name, namespace, deploymentName string, targetPort int, ownerReferences ...metav1.OwnerReference) *corev1.Service { 453 service := &corev1.Service{ 454 ObjectMeta: metav1.ObjectMeta{ 455 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 456 }, 457 Spec: corev1.ServiceSpec{ 458 Ports: []corev1.ServicePort{ 459 { 460 Port: int32(443), 461 TargetPort: intstr.FromInt(targetPort), 462 }, 463 }, 464 Selector: map[string]string{ 465 "app": deploymentName, 466 }, 467 }, 468 } 469 service.SetName(name) 470 service.SetNamespace(namespace) 471 service.SetOwnerReferences(ownerReferences) 472 473 return service 474 } 475 476 func clusterRoleBinding(name, clusterRoleName, serviceAccountName, serviceAccountNamespace string) *rbacv1.ClusterRoleBinding { 477 clusterRoleBinding := &rbacv1.ClusterRoleBinding{ 478 ObjectMeta: metav1.ObjectMeta{ 479 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 480 }, 481 Subjects: []rbacv1.Subject{ 482 { 483 Kind: "ServiceAccount", 484 APIGroup: "", 485 Name: serviceAccountName, 486 Namespace: serviceAccountNamespace, 487 }, 488 }, 489 RoleRef: rbacv1.RoleRef{ 490 APIGroup: "rbac.authorization.k8s.io", 491 Kind: "ClusterRole", 492 Name: clusterRoleName, 493 }, 494 } 495 clusterRoleBinding.SetName(name) 496 497 return clusterRoleBinding 498 } 499 500 func clusterRole(name string, rules []rbacv1.PolicyRule) *rbacv1.ClusterRole { 501 clusterRole := &rbacv1.ClusterRole{ 502 ObjectMeta: metav1.ObjectMeta{ 503 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 504 }, 505 Rules: rules, 506 } 507 clusterRole.SetName(name) 508 509 return clusterRole 510 } 511 512 func role(name, namespace string, rules []rbacv1.PolicyRule) *rbacv1.Role { 513 role := &rbacv1.Role{ 514 ObjectMeta: metav1.ObjectMeta{ 515 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 516 }, 517 Rules: rules, 518 } 519 role.SetName(name) 520 role.SetNamespace(namespace) 521 522 return role 523 } 524 525 func roleBinding(name, namespace, roleName, serviceAccountName, serviceAccountNamespace string) *rbacv1.RoleBinding { 526 roleBinding := &rbacv1.RoleBinding{ 527 ObjectMeta: metav1.ObjectMeta{ 528 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 529 }, 530 Subjects: []rbacv1.Subject{ 531 { 532 Kind: "ServiceAccount", 533 APIGroup: "", 534 Name: serviceAccountName, 535 Namespace: serviceAccountNamespace, 536 }, 537 }, 538 RoleRef: rbacv1.RoleRef{ 539 APIGroup: "rbac.authorization.k8s.io", 540 Kind: "Role", 541 Name: roleName, 542 }, 543 } 544 roleBinding.SetName(name) 545 roleBinding.SetNamespace(namespace) 546 547 return roleBinding 548 } 549 550 func tlsSecret(name, namespace string, certPEM, privPEM []byte) *corev1.Secret { 551 secret := &corev1.Secret{ 552 ObjectMeta: metav1.ObjectMeta{ 553 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 554 }, 555 Data: map[string][]byte{ 556 "tls.crt": certPEM, 557 "tls.key": privPEM, 558 }, 559 Type: corev1.SecretTypeTLS, 560 } 561 secret.SetName(name) 562 secret.SetNamespace(namespace) 563 564 return secret 565 } 566 567 func withCA(secret *corev1.Secret, caPEM []byte) *corev1.Secret { 568 secret.Data[install.OLMCAPEMKey] = caPEM 569 return secret 570 } 571 572 func keyPairToTLSSecret(name, namespace string, kp *certs.KeyPair) *corev1.Secret { 573 var privPEM []byte 574 var certPEM []byte 575 576 if kp != nil { 577 var err error 578 certPEM, privPEM, err = kp.ToPEM() 579 if err != nil { 580 panic(err) 581 } 582 } 583 584 return tlsSecret(name, namespace, certPEM, privPEM) 585 } 586 587 func signedServingPair(notAfter time.Time, ca *certs.KeyPair, hosts []string) *certs.KeyPair { 588 servingPair, err := certs.CreateSignedServingPair(notAfter, install.Organization, ca, hosts) 589 if err != nil { 590 panic(err) 591 } 592 593 return servingPair 594 } 595 596 func withAnnotations(obj runtime.Object, annotations map[string]string) runtime.Object { 597 meta, ok := obj.(metav1.Object) 598 if !ok { 599 panic("could not find metadata on object") 600 } 601 meta.SetAnnotations(annotations) 602 return meta.(runtime.Object) 603 } 604 605 func csvWithAnnotations(csv *v1alpha1.ClusterServiceVersion, annotations map[string]string) *v1alpha1.ClusterServiceVersion { 606 return withAnnotations(csv, annotations).(*v1alpha1.ClusterServiceVersion) 607 } 608 609 func withUID(obj runtime.Object, uid types.UID) runtime.Object { 610 meta, ok := obj.(metav1.Object) 611 if !ok { 612 panic("could not find metadata on object") 613 } 614 meta.SetUID(uid) 615 return meta.(runtime.Object) 616 } 617 618 func csvWithUID(csv *v1alpha1.ClusterServiceVersion, uid types.UID) *v1alpha1.ClusterServiceVersion { 619 return withUID(csv, uid).(*v1alpha1.ClusterServiceVersion) 620 } 621 622 func withLabels(obj runtime.Object, labels map[string]string) runtime.Object { 623 meta, ok := obj.(metav1.Object) 624 if !ok { 625 panic("could not find metadata on object") 626 } 627 meta.SetLabels(labels) 628 return meta.(runtime.Object) 629 } 630 631 func csvWithLabels(csv *v1alpha1.ClusterServiceVersion, labels map[string]string) *v1alpha1.ClusterServiceVersion { 632 return withLabels(csv, labels).(*v1alpha1.ClusterServiceVersion) 633 } 634 635 func addAnnotations(annotations map[string]string, add map[string]string) map[string]string { 636 out := map[string]string{} 637 for k, v := range annotations { 638 out[k] = v 639 } 640 for k, v := range add { 641 out[k] = v 642 } 643 return out 644 } 645 646 func addAnnotation(obj runtime.Object, key string, value string) runtime.Object { 647 meta, ok := obj.(metav1.Object) 648 if !ok { 649 panic("could not find metadata on object") 650 } 651 return withAnnotations(obj, addAnnotations(meta.GetAnnotations(), map[string]string{key: value})) 652 } 653 654 func csvWithStatusReason(csv *v1alpha1.ClusterServiceVersion, reason v1alpha1.ConditionReason) *v1alpha1.ClusterServiceVersion { 655 out := csv.DeepCopy() 656 out.Status.Reason = reason 657 return csv 658 } 659 660 func installStrategy(deploymentName string, permissions []v1alpha1.StrategyDeploymentPermissions, clusterPermissions []v1alpha1.StrategyDeploymentPermissions) v1alpha1.NamedInstallStrategy { 661 var singleInstance = int32(1) 662 strategy := v1alpha1.StrategyDetailsDeployment{ 663 DeploymentSpecs: []v1alpha1.StrategyDeploymentSpec{ 664 { 665 Name: deploymentName, 666 Spec: appsv1.DeploymentSpec{ 667 Selector: &metav1.LabelSelector{ 668 MatchLabels: map[string]string{ 669 "app": deploymentName, 670 }, 671 }, 672 Replicas: &singleInstance, 673 Template: corev1.PodTemplateSpec{ 674 ObjectMeta: metav1.ObjectMeta{ 675 Labels: map[string]string{ 676 "app": deploymentName, 677 }, 678 }, 679 Spec: corev1.PodSpec{ 680 ServiceAccountName: "sa", 681 Containers: []corev1.Container{ 682 { 683 Name: deploymentName + "-c1", 684 Image: "nginx:1.7.9", 685 Ports: []corev1.ContainerPort{ 686 { 687 ContainerPort: 80, 688 }, 689 }, 690 }, 691 }, 692 }, 693 }, 694 }, 695 }, 696 }, 697 Permissions: permissions, 698 ClusterPermissions: clusterPermissions, 699 } 700 701 return v1alpha1.NamedInstallStrategy{ 702 StrategyName: v1alpha1.InstallStrategyNameDeployment, 703 StrategySpec: strategy, 704 } 705 } 706 707 func apiServiceInstallStrategy(deploymentName string, cahash string, permissions []v1alpha1.StrategyDeploymentPermissions, clusterPermissions []v1alpha1.StrategyDeploymentPermissions) v1alpha1.NamedInstallStrategy { 708 strategy := installStrategy(deploymentName, permissions, clusterPermissions) 709 710 strategy.StrategySpec.DeploymentSpecs[0].Spec.Template.Annotations = map[string]string{install.OLMCAHashAnnotationKey: cahash} 711 712 strategy.StrategySpec.DeploymentSpecs[0].Spec.Template.Spec.Volumes = []corev1.Volume{{ 713 Name: "apiservice-cert", 714 VolumeSource: corev1.VolumeSource{ 715 Secret: &corev1.SecretVolumeSource{ 716 SecretName: "v1.a1-cert", 717 Items: []corev1.KeyToPath{ 718 { 719 Key: "tls.crt", 720 Path: "apiserver.crt", 721 }, 722 { 723 Key: "tls.key", 724 Path: "apiserver.key", 725 }, 726 }, 727 }, 728 }, 729 }} 730 strategy.StrategySpec.DeploymentSpecs[0].Spec.Template.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{{ 731 Name: "apiservice-cert", 732 MountPath: "/apiserver.local.config/certificates", 733 }} 734 return strategy 735 } 736 737 func withTemplateAnnotations(strategy v1alpha1.NamedInstallStrategy, annotations map[string]string) v1alpha1.NamedInstallStrategy { 738 strategy.StrategySpec.DeploymentSpecs[0].Spec.Template.Annotations = annotations 739 return strategy 740 } 741 742 func csv( 743 name, namespace, minKubeVersion, replaces string, 744 installStrategy v1alpha1.NamedInstallStrategy, 745 owned, required []*apiextensionsv1.CustomResourceDefinition, 746 phase v1alpha1.ClusterServiceVersionPhase, 747 ) *v1alpha1.ClusterServiceVersion { 748 requiredCRDDescs := make([]v1alpha1.CRDDescription, 0) 749 for _, crd := range required { 750 requiredCRDDescs = append(requiredCRDDescs, v1alpha1.CRDDescription{Name: crd.GetName(), Version: crd.Spec.Versions[0].Name, Kind: crd.Spec.Names.Kind}) 751 } 752 753 ownedCRDDescs := make([]v1alpha1.CRDDescription, 0) 754 for _, crd := range owned { 755 ownedCRDDescs = append(ownedCRDDescs, v1alpha1.CRDDescription{Name: crd.GetName(), Version: crd.Spec.Versions[0].Name, Kind: crd.Spec.Names.Kind}) 756 } 757 758 return &v1alpha1.ClusterServiceVersion{ 759 TypeMeta: metav1.TypeMeta{ 760 Kind: v1alpha1.ClusterServiceVersionKind, 761 APIVersion: v1alpha1.SchemeGroupVersion.String(), 762 }, 763 ObjectMeta: metav1.ObjectMeta{ 764 Name: name, 765 Namespace: namespace, 766 }, 767 Spec: v1alpha1.ClusterServiceVersionSpec{ 768 MinKubeVersion: minKubeVersion, 769 Replaces: replaces, 770 InstallStrategy: installStrategy, 771 InstallModes: []v1alpha1.InstallMode{ 772 { 773 Type: v1alpha1.InstallModeTypeOwnNamespace, 774 Supported: true, 775 }, 776 { 777 Type: v1alpha1.InstallModeTypeSingleNamespace, 778 Supported: true, 779 }, 780 { 781 Type: v1alpha1.InstallModeTypeMultiNamespace, 782 Supported: true, 783 }, 784 { 785 Type: v1alpha1.InstallModeTypeAllNamespaces, 786 Supported: true, 787 }, 788 }, 789 CustomResourceDefinitions: v1alpha1.CustomResourceDefinitions{ 790 Owned: ownedCRDDescs, 791 Required: requiredCRDDescs, 792 }, 793 }, 794 Status: v1alpha1.ClusterServiceVersionStatus{ 795 Phase: phase, 796 }, 797 } 798 } 799 800 func withConditionReason(csv *v1alpha1.ClusterServiceVersion, reason v1alpha1.ConditionReason) *v1alpha1.ClusterServiceVersion { 801 csv.Status.Reason = reason 802 return csv 803 } 804 805 func withPhase(csv *v1alpha1.ClusterServiceVersion, phase v1alpha1.ClusterServiceVersionPhase, reason v1alpha1.ConditionReason, message string, now metav1.Time) *v1alpha1.ClusterServiceVersion { 806 csv.SetPhase(phase, reason, message, &now) 807 return csv 808 } 809 810 func withCertInfo(csv *v1alpha1.ClusterServiceVersion, rotateAt metav1.Time, lastUpdated metav1.Time) *v1alpha1.ClusterServiceVersion { 811 csv.Status.CertsRotateAt = &rotateAt 812 csv.Status.CertsLastUpdated = &lastUpdated 813 return csv 814 } 815 816 func withAPIServices(csv *v1alpha1.ClusterServiceVersion, owned, required []v1alpha1.APIServiceDescription) *v1alpha1.ClusterServiceVersion { 817 csv.Spec.APIServiceDefinitions = v1alpha1.APIServiceDefinitions{ 818 Owned: owned, 819 Required: required, 820 } 821 return csv 822 } 823 824 func withInstallModes(csv *v1alpha1.ClusterServiceVersion, installModes []v1alpha1.InstallMode) *v1alpha1.ClusterServiceVersion { 825 csv.Spec.InstallModes = installModes 826 return csv 827 } 828 829 func apis(apis ...string) []v1alpha1.APIServiceDescription { 830 descs := []v1alpha1.APIServiceDescription{} 831 for _, av := range apis { 832 split := strings.Split(av, ".") 833 descs = append(descs, v1alpha1.APIServiceDescription{ 834 Group: split[0], 835 Version: split[1], 836 Kind: split[2], 837 DeploymentName: split[0], 838 }) 839 } 840 return descs 841 } 842 843 func apiService(group, version, serviceName, serviceNamespace, deploymentName string, caBundle []byte, availableStatus apiregistrationv1.ConditionStatus, ownerLabel map[string]string) *apiregistrationv1.APIService { 844 apiService := &apiregistrationv1.APIService{ 845 ObjectMeta: metav1.ObjectMeta{ 846 Labels: ownerLabel, 847 OwnerReferences: []metav1.OwnerReference{}, 848 }, 849 Spec: apiregistrationv1.APIServiceSpec{ 850 Group: group, 851 Version: version, 852 GroupPriorityMinimum: int32(2000), 853 VersionPriority: int32(15), 854 CABundle: caBundle, 855 Service: &apiregistrationv1.ServiceReference{ 856 Name: serviceName, 857 Namespace: serviceNamespace, 858 }, 859 }, 860 Status: apiregistrationv1.APIServiceStatus{ 861 Conditions: []apiregistrationv1.APIServiceCondition{ 862 { 863 Type: apiregistrationv1.Available, 864 Status: availableStatus, 865 }, 866 }, 867 }, 868 } 869 apiServiceName := fmt.Sprintf("%s.%s", version, group) 870 apiService.SetName(apiServiceName) 871 872 return apiService 873 } 874 875 func crd(name, version, group string) *apiextensionsv1.CustomResourceDefinition { 876 return &apiextensionsv1.CustomResourceDefinition{ 877 ObjectMeta: metav1.ObjectMeta{ 878 Name: name + "." + group, 879 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 880 }, 881 Spec: apiextensionsv1.CustomResourceDefinitionSpec{ 882 Group: group, 883 Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ 884 { 885 Name: version, 886 Storage: true, 887 Served: true, 888 }, 889 }, 890 Names: apiextensionsv1.CustomResourceDefinitionNames{ 891 Kind: name, 892 }, 893 }, 894 Status: apiextensionsv1.CustomResourceDefinitionStatus{ 895 Conditions: []apiextensionsv1.CustomResourceDefinitionCondition{ 896 { 897 Type: apiextensionsv1.Established, 898 Status: apiextensionsv1.ConditionTrue, 899 }, 900 { 901 Type: apiextensionsv1.NamesAccepted, 902 Status: apiextensionsv1.ConditionTrue, 903 }, 904 }, 905 }, 906 } 907 } 908 909 func generateCA(notAfter time.Time, organization string) (*certs.KeyPair, error) { 910 notBefore := time.Now() 911 912 serial, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64)) 913 if err != nil { 914 return nil, err 915 } 916 917 caDetails := &x509.Certificate{ 918 SerialNumber: serial, 919 Subject: pkix.Name{ 920 Organization: []string{install.Organization}, 921 }, 922 NotBefore: notBefore, 923 NotAfter: notAfter, 924 IsCA: true, 925 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, 926 KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, 927 BasicConstraintsValid: true, 928 } 929 930 privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 931 if err != nil { 932 return nil, err 933 } 934 935 publicKey := &privateKey.PublicKey 936 certRaw, err := x509.CreateCertificate(rand.Reader, caDetails, caDetails, publicKey, privateKey) 937 if err != nil { 938 return nil, err 939 } 940 941 cert, err := x509.ParseCertificate(certRaw) 942 if err != nil { 943 return nil, err 944 } 945 946 ca := &certs.KeyPair{ 947 Cert: cert, 948 Priv: privateKey, 949 } 950 951 return ca, nil 952 } 953 954 func TestTransitionCSV(t *testing.T) { 955 logrus.SetLevel(logrus.DebugLevel) 956 namespace := "ns" 957 958 apiHash, err := resolvercache.APIKeyToGVKHash(opregistry.APIKey{Group: "g1", Version: "v1", Kind: "c1"}) 959 require.NoError(t, err) 960 961 defaultOperatorGroup := &operatorsv1.OperatorGroup{ 962 TypeMeta: metav1.TypeMeta{ 963 Kind: "OperatorGroup", 964 APIVersion: operatorsv1.SchemeGroupVersion.String(), 965 }, 966 ObjectMeta: metav1.ObjectMeta{ 967 Name: "default", 968 Namespace: namespace, 969 }, 970 Spec: operatorsv1.OperatorGroupSpec{}, 971 Status: operatorsv1.OperatorGroupStatus{ 972 Namespaces: []string{namespace}, 973 }, 974 } 975 976 defaultTemplateAnnotations := map[string]string{ 977 operatorsv1.OperatorGroupTargetsAnnotationKey: namespace, 978 operatorsv1.OperatorGroupNamespaceAnnotationKey: namespace, 979 operatorsv1.OperatorGroupAnnotationKey: defaultOperatorGroup.GetName(), 980 } 981 982 // Generate valid and expired CA fixtures 983 validCA, err := generateCA(time.Now().Add(10*365*24*time.Hour), install.Organization) 984 require.NoError(t, err) 985 validCAPEM, _, err := validCA.ToPEM() 986 require.NoError(t, err) 987 validCAHash := certs.PEMSHA256(validCAPEM) 988 989 expiredCA, err := generateCA(time.Now(), install.Organization) 990 require.NoError(t, err) 991 expiredCAPEM, _, err := expiredCA.ToPEM() 992 require.NoError(t, err) 993 expiredCAHash := certs.PEMSHA256(expiredCAPEM) 994 995 type csvState struct { 996 exists bool 997 phase v1alpha1.ClusterServiceVersionPhase //nolint:structcheck 998 reason v1alpha1.ConditionReason 999 } 1000 type operatorConfig struct { 1001 apiReconciler APIIntersectionReconciler 1002 apiLabeler labeler.Labeler 1003 } 1004 type initial struct { 1005 csvs []*v1alpha1.ClusterServiceVersion 1006 clientObjs []runtime.Object 1007 crds []runtime.Object 1008 objs []runtime.Object 1009 apis []runtime.Object 1010 } 1011 type expected struct { 1012 csvStates map[string]csvState 1013 objs []runtime.Object 1014 err map[string]error 1015 } 1016 tests := []struct { 1017 name string 1018 config operatorConfig 1019 initial initial 1020 expected expected 1021 }{ 1022 { 1023 name: "SingleCSVNoneToPending/CRD", 1024 initial: initial{ 1025 csvs: []*v1alpha1.ClusterServiceVersion{ 1026 csvWithAnnotations(csv("csv1", 1027 namespace, 1028 "0.0.0", 1029 "", 1030 installStrategy("csv1-dep1", nil, nil), 1031 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 1032 []*apiextensionsv1.CustomResourceDefinition{}, 1033 v1alpha1.CSVPhaseNone, 1034 ), defaultTemplateAnnotations), 1035 }, 1036 clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")}, 1037 }, 1038 expected: expected{ 1039 csvStates: map[string]csvState{ 1040 "csv1": {exists: true, phase: v1alpha1.CSVPhasePending}, 1041 }, 1042 }, 1043 }, 1044 { 1045 name: "SingleCSVNoneToPending/APIService/Required", 1046 initial: initial{ 1047 csvs: []*v1alpha1.ClusterServiceVersion{ 1048 withAPIServices(csvWithAnnotations(csv("csv1", 1049 namespace, 1050 "0.0.0", 1051 "", 1052 installStrategy("csv1-dep1", nil, nil), 1053 []*apiextensionsv1.CustomResourceDefinition{}, 1054 []*apiextensionsv1.CustomResourceDefinition{}, 1055 v1alpha1.CSVPhaseNone, 1056 ), defaultTemplateAnnotations), nil, apis("a1.corev1.a1Kind")), 1057 }, 1058 clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "a1Kind.corev1.a1")}, 1059 }, 1060 expected: expected{ 1061 csvStates: map[string]csvState{ 1062 "csv1": {exists: true, phase: v1alpha1.CSVPhasePending}, 1063 }, 1064 }, 1065 }, 1066 { 1067 name: "SingleCSVPendingToFailed/BadStrategyPermissions", 1068 initial: initial{ 1069 csvs: []*v1alpha1.ClusterServiceVersion{ 1070 csvWithUID(csvWithAnnotations(csv("csv1", 1071 namespace, 1072 "0.0.0", 1073 "", 1074 installStrategy("csv1-dep1", 1075 nil, 1076 []v1alpha1.StrategyDeploymentPermissions{ 1077 { 1078 ServiceAccountName: "sa", 1079 Rules: []rbacv1.PolicyRule{ 1080 { 1081 Verbs: []string{"*"}, 1082 Resources: []string{"*"}, 1083 NonResourceURLs: []string{"/osb"}, 1084 }, 1085 }, 1086 }, 1087 }), 1088 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 1089 []*apiextensionsv1.CustomResourceDefinition{}, 1090 v1alpha1.CSVPhasePending, 1091 ), defaultTemplateAnnotations), types.UID("csv-uid")), 1092 }, 1093 clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")}, 1094 crds: []runtime.Object{ 1095 crd("c1", "v1", "g1"), 1096 }, 1097 objs: []runtime.Object{ 1098 &corev1.ServiceAccount{ 1099 ObjectMeta: metav1.ObjectMeta{ 1100 Name: "sa", 1101 Namespace: namespace, 1102 OwnerReferences: []metav1.OwnerReference{ 1103 { 1104 Kind: v1alpha1.ClusterServiceVersionKind, 1105 UID: "csv-uid", 1106 }, 1107 }, 1108 }, 1109 }, 1110 }, 1111 }, 1112 expected: expected{ 1113 csvStates: map[string]csvState{ 1114 "csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed}, 1115 }, 1116 }, 1117 }, 1118 { 1119 name: "SingleCSVPendingToPending/CRD", 1120 initial: initial{ 1121 csvs: []*v1alpha1.ClusterServiceVersion{ 1122 csvWithAnnotations(csv("csv1", 1123 namespace, 1124 "0.0.0", 1125 "", 1126 installStrategy("csv1-dep1", nil, nil), 1127 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 1128 []*apiextensionsv1.CustomResourceDefinition{}, 1129 v1alpha1.CSVPhasePending, 1130 ), defaultTemplateAnnotations), 1131 }, 1132 clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")}, 1133 crds: []runtime.Object{}, 1134 }, 1135 expected: expected{ 1136 csvStates: map[string]csvState{ 1137 "csv1": {exists: true, phase: v1alpha1.CSVPhasePending}, 1138 }, 1139 err: map[string]error{ 1140 "csv1": ErrRequirementsNotMet, 1141 }, 1142 }, 1143 }, 1144 { 1145 name: "SingleCSVPendingToPending/APIService/Required/Missing", 1146 initial: initial{ 1147 csvs: []*v1alpha1.ClusterServiceVersion{ 1148 withAPIServices(csvWithAnnotations(csv("csv1", 1149 namespace, 1150 "0.0.0", 1151 "", 1152 installStrategy("csv1-dep1", nil, nil), 1153 []*apiextensionsv1.CustomResourceDefinition{}, 1154 []*apiextensionsv1.CustomResourceDefinition{}, 1155 v1alpha1.CSVPhasePending, 1156 ), defaultTemplateAnnotations), nil, apis("a1.v1.a1Kind")), 1157 }, 1158 clientObjs: []runtime.Object{defaultOperatorGroup}, 1159 }, 1160 expected: expected{ 1161 csvStates: map[string]csvState{ 1162 "csv1": {exists: true, phase: v1alpha1.CSVPhasePending}, 1163 }, 1164 err: map[string]error{ 1165 "csv1": ErrRequirementsNotMet, 1166 }, 1167 }, 1168 }, 1169 { 1170 name: "SingleCSVPendingToPending/APIService/Required/Unavailable", 1171 initial: initial{ 1172 csvs: []*v1alpha1.ClusterServiceVersion{ 1173 withAPIServices(csvWithAnnotations(csv("csv1", 1174 namespace, 1175 "0.0.0", 1176 "", 1177 installStrategy("csv1-dep1", nil, nil), 1178 []*apiextensionsv1.CustomResourceDefinition{}, 1179 []*apiextensionsv1.CustomResourceDefinition{}, 1180 v1alpha1.CSVPhasePending, 1181 ), defaultTemplateAnnotations), nil, apis("a1.v1.a1Kind")), 1182 }, 1183 clientObjs: []runtime.Object{defaultOperatorGroup}, 1184 apis: []runtime.Object{apiService("a1", "v1", "", "", "", validCAPEM, apiregistrationv1.ConditionFalse, ownerLabelFromCSV("csv1", namespace))}, 1185 }, 1186 expected: expected{ 1187 csvStates: map[string]csvState{ 1188 "csv1": {exists: true, phase: v1alpha1.CSVPhasePending}, 1189 }, 1190 err: map[string]error{ 1191 "csv1": ErrRequirementsNotMet, 1192 }, 1193 }, 1194 }, 1195 { 1196 name: "SingleCSVPendingToPending/APIService/Required/Unknown", 1197 initial: initial{ 1198 csvs: []*v1alpha1.ClusterServiceVersion{ 1199 withAPIServices(csvWithAnnotations(csv("csv1", 1200 namespace, 1201 "0.0.0", 1202 "", 1203 installStrategy("csv1-dep1", nil, nil), 1204 []*apiextensionsv1.CustomResourceDefinition{}, 1205 []*apiextensionsv1.CustomResourceDefinition{}, 1206 v1alpha1.CSVPhasePending, 1207 ), defaultTemplateAnnotations), nil, apis("a1.v1.a1Kind")), 1208 }, 1209 clientObjs: []runtime.Object{defaultOperatorGroup}, 1210 apis: []runtime.Object{apiService("a1", "v1", "", "", "", validCAPEM, apiregistrationv1.ConditionUnknown, ownerLabelFromCSV("csv1", namespace))}, 1211 }, 1212 expected: expected{ 1213 csvStates: map[string]csvState{ 1214 "csv1": {exists: true, phase: v1alpha1.CSVPhasePending}, 1215 }, 1216 err: map[string]error{ 1217 "csv1": ErrRequirementsNotMet, 1218 }, 1219 }, 1220 }, 1221 { 1222 name: "SingleCSVPendingToPending/APIService/Owned/DeploymentNotFound", 1223 initial: initial{ 1224 csvs: []*v1alpha1.ClusterServiceVersion{ 1225 withAPIServices(csvWithAnnotations(csv("csv1", 1226 namespace, 1227 "0.0.0", 1228 "", 1229 installStrategy("b1", nil, nil), 1230 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 1231 []*apiextensionsv1.CustomResourceDefinition{}, 1232 v1alpha1.CSVPhasePending, 1233 ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), 1234 }, 1235 clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1,a1Kind.v1.a1")}, 1236 crds: []runtime.Object{ 1237 crd("c1", "v1", "g1"), 1238 }, 1239 }, 1240 expected: expected{ 1241 csvStates: map[string]csvState{ 1242 "csv1": {exists: true, phase: v1alpha1.CSVPhasePending}, 1243 }, 1244 err: map[string]error{ 1245 "csv1": ErrRequirementsNotMet, 1246 }, 1247 }, 1248 }, 1249 { 1250 name: "CSVPendingToFailed/CRDOwnerConflict", 1251 initial: initial{ 1252 csvs: []*v1alpha1.ClusterServiceVersion{ 1253 csvWithAnnotations(csv("csv1", 1254 namespace, 1255 "0.0.0", 1256 "", 1257 installStrategy("csv1-dep1", nil, nil), 1258 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 1259 []*apiextensionsv1.CustomResourceDefinition{}, 1260 v1alpha1.CSVPhaseSucceeded, 1261 ), defaultTemplateAnnotations), 1262 csvWithAnnotations(csv("csv2", 1263 namespace, 1264 "0.0.0", 1265 "", 1266 installStrategy("csv2-dep1", nil, nil), 1267 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 1268 []*apiextensionsv1.CustomResourceDefinition{}, 1269 v1alpha1.CSVPhasePending, 1270 ), defaultTemplateAnnotations), 1271 }, 1272 clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")}, 1273 crds: []runtime.Object{ 1274 crd("c1", "v1", "g1"), 1275 }, 1276 objs: []runtime.Object{ 1277 withLabels( 1278 deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations), 1279 addDepSpecHashLabel(t, ownerLabelFromCSV("csv1", namespace), withTemplateAnnotations(installStrategy("csv1-dep1", nil, nil), defaultTemplateAnnotations)), 1280 ), 1281 }, 1282 }, 1283 expected: expected{ 1284 csvStates: map[string]csvState{ 1285 "csv1": {exists: true, phase: v1alpha1.CSVPhaseSucceeded}, 1286 "csv2": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonOwnerConflict}, 1287 }, 1288 err: map[string]error{ 1289 "csv2": ErrCRDOwnerConflict, 1290 }, 1291 }, 1292 }, 1293 { 1294 name: "CSVPendingToFailed/APIServiceOwnerConflict", 1295 initial: initial{ 1296 csvs: []*v1alpha1.ClusterServiceVersion{ 1297 withCertInfo(withAPIServices(csvWithAnnotations(csv("csv1", 1298 namespace, 1299 "0.0.0", 1300 "", 1301 installStrategy("a1", nil, nil), 1302 []*apiextensionsv1.CustomResourceDefinition{}, 1303 []*apiextensionsv1.CustomResourceDefinition{}, 1304 v1alpha1.CSVPhaseSucceeded, 1305 ), defaultTemplateAnnotations), 1306 apis("a1.v1.a1Kind"), nil), metav1.NewTime(time.Now().Add(24*time.Hour)), metav1.NewTime(time.Now())), 1307 withAPIServices(csvWithAnnotations(csv("csv2", 1308 namespace, 1309 "0.0.0", 1310 "", 1311 installStrategy("a1", nil, nil), 1312 []*apiextensionsv1.CustomResourceDefinition{}, 1313 []*apiextensionsv1.CustomResourceDefinition{}, 1314 v1alpha1.CSVPhasePending, 1315 ), defaultTemplateAnnotations), 1316 apis("a1.v1.a1Kind"), nil), 1317 }, 1318 clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "a1Kind.v1.a1")}, 1319 apis: []runtime.Object{apiService("a1", "v1", "a1-service", namespace, "", validCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace))}, 1320 objs: []runtime.Object{ 1321 withLabels( 1322 deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{ 1323 install.OLMCAHashAnnotationKey: validCAHash, 1324 })), 1325 addDepSpecHashLabel(t, ownerLabelFromCSV("csv1", namespace), withTemplateAnnotations(apiServiceInstallStrategy("a1", validCAHash, nil, nil), addAnnotations(defaultTemplateAnnotations, map[string]string{ 1326 install.OLMCAHashAnnotationKey: validCAHash, 1327 }))), 1328 ), 1329 withAnnotations(keyPairToTLSSecret("a1-service-cert", namespace, signedServingPair(time.Now().Add(24*time.Hour), validCA, []string{"a1-service.ns", "a1-service.ns.svc"})), map[string]string{ 1330 install.OLMCAHashAnnotationKey: validCAHash, 1331 }), 1332 service("a1", namespace, "a1", 80), 1333 serviceAccount("sa", namespace), 1334 role("a1-cert", namespace, []rbacv1.PolicyRule{ 1335 { 1336 Verbs: []string{"get"}, 1337 APIGroups: []string{""}, 1338 Resources: []string{"secrets"}, 1339 ResourceNames: []string{"a1-service-cert"}, 1340 }, 1341 }), 1342 roleBinding("a1-service-cert", namespace, "a1-cert", "sa", namespace), 1343 role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{ 1344 { 1345 Verbs: []string{"get"}, 1346 APIGroups: []string{""}, 1347 Resources: []string{"configmaps"}, 1348 ResourceNames: []string{"extension-apiserver-authentication"}, 1349 }, 1350 }), 1351 roleBinding("a1-service-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace), 1352 clusterRole("system:auth-delegator", []rbacv1.PolicyRule{ 1353 { 1354 Verbs: []string{"create"}, 1355 APIGroups: []string{"authentication.k8s.io"}, 1356 Resources: []string{"tokenreviews"}, 1357 }, 1358 { 1359 Verbs: []string{"create"}, 1360 APIGroups: []string{"authentication.k8s.io"}, 1361 Resources: []string{"subjectaccessreviews"}, 1362 }, 1363 }), 1364 clusterRoleBinding("a1-service-system:auth-delegator", "system:auth-delegator", "sa", namespace), 1365 }, 1366 }, 1367 expected: expected{ 1368 csvStates: map[string]csvState{ 1369 "csv1": {exists: true, phase: v1alpha1.CSVPhaseSucceeded}, 1370 "csv2": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonOwnerConflict}, 1371 }, 1372 err: map[string]error{ 1373 "csv2": ErrAPIServiceOwnerConflict, 1374 }, 1375 }, 1376 }, 1377 { 1378 name: "SingleCSVFailedToPending/Deployment", 1379 initial: initial{ 1380 csvs: []*v1alpha1.ClusterServiceVersion{ 1381 csvWithAnnotations(csv("csv1", 1382 namespace, 1383 "0.0.0", 1384 "", 1385 installStrategy("a1", nil, nil), 1386 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 1387 []*apiextensionsv1.CustomResourceDefinition{}, 1388 v1alpha1.CSVPhaseFailed, 1389 ), defaultTemplateAnnotations), 1390 }, 1391 clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")}, 1392 crds: []runtime.Object{ 1393 crd("c1", "v1", "g1"), 1394 }, 1395 }, 1396 expected: expected{ 1397 csvStates: map[string]csvState{ 1398 "csv1": {exists: true, phase: v1alpha1.CSVPhasePending, reason: v1alpha1.CSVReasonNeedsReinstall}, 1399 }, 1400 }, 1401 }, 1402 { 1403 name: "SingleCSVFailedToPending/CRD", 1404 initial: initial{ 1405 csvs: []*v1alpha1.ClusterServiceVersion{ 1406 csvWithAnnotations(csv("csv1", 1407 namespace, 1408 "0.0.0", 1409 "", 1410 installStrategy("a1", nil, nil), 1411 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 1412 []*apiextensionsv1.CustomResourceDefinition{}, 1413 v1alpha1.CSVPhaseFailed, 1414 ), defaultTemplateAnnotations), 1415 }, 1416 clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")}, 1417 objs: []runtime.Object{ 1418 deployment("a1", namespace, "sa", defaultTemplateAnnotations), 1419 }, 1420 }, 1421 expected: expected{ 1422 csvStates: map[string]csvState{ 1423 "csv1": {exists: true, phase: v1alpha1.CSVPhasePending, reason: v1alpha1.CSVReasonRequirementsNotMet}, 1424 }, 1425 }, 1426 }, 1427 { 1428 name: "SingleCSVPendingToInstallReady/CRD", 1429 initial: initial{ 1430 csvs: []*v1alpha1.ClusterServiceVersion{ 1431 csvWithAnnotations(csv("csv1", 1432 namespace, 1433 "0.0.0", 1434 "", 1435 installStrategy("csv1-dep1", nil, nil), 1436 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 1437 []*apiextensionsv1.CustomResourceDefinition{}, 1438 v1alpha1.CSVPhasePending, 1439 ), defaultTemplateAnnotations), 1440 }, 1441 clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")}, 1442 crds: []runtime.Object{ 1443 crd("c1", "v1", "g1"), 1444 }, 1445 }, 1446 expected: expected{ 1447 csvStates: map[string]csvState{ 1448 "csv1": {exists: true, phase: v1alpha1.CSVPhaseInstallReady}, 1449 }, 1450 }, 1451 }, 1452 { 1453 name: "SingleCSVPendingToInstallReady/APIService/Required", 1454 initial: initial{ 1455 csvs: []*v1alpha1.ClusterServiceVersion{ 1456 withAPIServices(csvWithAnnotations(csv("csv1", 1457 namespace, 1458 "0.0.0", 1459 "", 1460 installStrategy("csv1-dep1", nil, nil), 1461 []*apiextensionsv1.CustomResourceDefinition{}, 1462 []*apiextensionsv1.CustomResourceDefinition{}, 1463 v1alpha1.CSVPhasePending, 1464 ), defaultTemplateAnnotations), nil, apis("a1.v1.a1Kind")), 1465 }, 1466 clientObjs: []runtime.Object{defaultOperatorGroup}, 1467 apis: []runtime.Object{apiService("a1", "v1", "", "", "", validCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace))}, 1468 }, 1469 expected: expected{ 1470 csvStates: map[string]csvState{ 1471 "csv1": {exists: true, phase: v1alpha1.CSVPhaseInstallReady}, 1472 }, 1473 }, 1474 }, 1475 { 1476 name: "SingleCSVInstallReadyToInstalling", 1477 initial: initial{ 1478 csvs: []*v1alpha1.ClusterServiceVersion{ 1479 csvWithAnnotations(csv("csv1", 1480 namespace, 1481 "0.0.0", 1482 "", 1483 installStrategy("csv1-dep1", nil, nil), 1484 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 1485 []*apiextensionsv1.CustomResourceDefinition{}, 1486 v1alpha1.CSVPhaseInstallReady, 1487 ), defaultTemplateAnnotations), 1488 }, 1489 clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1")}, 1490 crds: []runtime.Object{ 1491 crd("c1", "v1", "g1"), 1492 }, 1493 }, 1494 expected: expected{ 1495 csvStates: map[string]csvState{ 1496 "csv1": {exists: true, phase: v1alpha1.CSVPhaseInstalling}, 1497 }, 1498 }, 1499 }, 1500 { 1501 name: "SingleCSVInstallReadyToInstalling/APIService/Owned", 1502 initial: initial{ 1503 csvs: []*v1alpha1.ClusterServiceVersion{ 1504 withAPIServices(csvWithAnnotations(csv("csv1", 1505 namespace, 1506 "0.0.0", 1507 "", 1508 installStrategy("a1", nil, nil), 1509 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 1510 []*apiextensionsv1.CustomResourceDefinition{}, 1511 v1alpha1.CSVPhaseInstallReady, 1512 ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), 1513 }, 1514 objs: []runtime.Object{ 1515 // Note: Ideally we would not pre-create these objects, but fake client does not support 1516 // creation through SSA, see issue here: https://github.com/kubernetes/kubernetes/issues/115598 1517 // Once resolved, these objects and others in this file may be removed. 1518 roleBinding("a1-service-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace), 1519 service("a1-service", namespace, "a1", 80), 1520 clusterRoleBinding("a1-service-system:auth-delegator", "system:auth-delegator", "sa", namespace), 1521 }, 1522 clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1,a1Kind.v1.a1")}, 1523 crds: []runtime.Object{ 1524 crd("c1", "v1", "g1"), 1525 }, 1526 }, 1527 expected: expected{ 1528 csvStates: map[string]csvState{ 1529 "csv1": {exists: true, phase: v1alpha1.CSVPhaseInstalling}, 1530 }, 1531 }, 1532 }, 1533 { 1534 name: "SingleCSVSucceededToPending/APIService/Owned/CertRotation", 1535 initial: initial{ 1536 csvs: []*v1alpha1.ClusterServiceVersion{ 1537 withCertInfo(withAPIServices(csvWithAnnotations(csv("csv1", 1538 namespace, 1539 "0.0.0", 1540 "", 1541 installStrategy("a1", nil, nil), 1542 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 1543 []*apiextensionsv1.CustomResourceDefinition{}, 1544 v1alpha1.CSVPhaseSucceeded, 1545 ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()), 1546 }, 1547 clientObjs: []runtime.Object{defaultOperatorGroup}, 1548 apis: []runtime.Object{ 1549 apiService("a1", "v1", "a1-service", namespace, "a1", validCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)), 1550 }, 1551 objs: []runtime.Object{ 1552 deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{ 1553 install.OLMCAHashAnnotationKey: validCAHash, 1554 })), 1555 withLabels(withAnnotations(keyPairToTLSSecret("a1-service-cert", namespace, signedServingPair(time.Now().Add(24*time.Hour), validCA, []string{"a1-service.ns", "a1-service.ns.svc"})), map[string]string{ 1556 install.OLMCAHashAnnotationKey: validCAHash, 1557 }), map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}), 1558 service("a1-service", namespace, "a1", 80), 1559 serviceAccount("sa", namespace), 1560 role("a1-service-cert", namespace, []rbacv1.PolicyRule{ 1561 { 1562 Verbs: []string{"get"}, 1563 APIGroups: []string{""}, 1564 Resources: []string{"secrets"}, 1565 ResourceNames: []string{"a1-service-cert"}, 1566 }, 1567 }), 1568 roleBinding("a1-service-cert", namespace, "a1-service-cert", "sa", namespace), 1569 role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{ 1570 { 1571 Verbs: []string{"get"}, 1572 APIGroups: []string{""}, 1573 Resources: []string{"configmaps"}, 1574 ResourceNames: []string{"extension-apiserver-authentication"}, 1575 }, 1576 }), 1577 roleBinding("a1-service-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace), 1578 clusterRole("system:auth-delegator", []rbacv1.PolicyRule{ 1579 { 1580 Verbs: []string{"create"}, 1581 APIGroups: []string{"authentication.k8s.io"}, 1582 Resources: []string{"tokenreviews"}, 1583 }, 1584 { 1585 Verbs: []string{"create"}, 1586 APIGroups: []string{"authentication.k8s.io"}, 1587 Resources: []string{"subjectaccessreviews"}, 1588 }, 1589 }), 1590 clusterRoleBinding("a1-service-system:auth-delegator", "system:auth-delegator", "sa", namespace), 1591 }, 1592 crds: []runtime.Object{ 1593 crd("c1", "v1", "g1"), 1594 }, 1595 }, 1596 expected: expected{ 1597 csvStates: map[string]csvState{ 1598 "csv1": {exists: true, phase: v1alpha1.CSVPhasePending, reason: v1alpha1.CSVReasonNeedsCertRotation}, 1599 }, 1600 }, 1601 }, 1602 { 1603 name: "SingleCSVSucceededToFailed/APIService/Owned/BadCAHash/Deployment", 1604 initial: initial{ 1605 csvs: []*v1alpha1.ClusterServiceVersion{ 1606 withCertInfo(withAPIServices(csvWithAnnotations(csv("csv1", 1607 namespace, 1608 "0.0.0", 1609 "", 1610 installStrategy("a1", nil, nil), 1611 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 1612 []*apiextensionsv1.CustomResourceDefinition{}, 1613 v1alpha1.CSVPhaseSucceeded, 1614 ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()), 1615 }, 1616 clientObjs: []runtime.Object{defaultOperatorGroup}, 1617 apis: []runtime.Object{ 1618 apiService("a1", "v1", "v1-a1", namespace, "a1", validCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)), 1619 }, 1620 objs: []runtime.Object{ 1621 deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{ 1622 install.OLMCAHashAnnotationKey: "a-pretty-bad-hash", 1623 })), 1624 withAnnotations(keyPairToTLSSecret("v1.a1-cert", namespace, signedServingPair(time.Now().Add(24*time.Hour), validCA, []string{"v1-a1.ns", "v1-a1.ns.svc"})), map[string]string{ 1625 install.OLMCAHashAnnotationKey: validCAHash, 1626 }), 1627 service("v1-a1", namespace, "a1", 80), 1628 serviceAccount("sa", namespace), 1629 role("v1.a1-cert", namespace, []rbacv1.PolicyRule{ 1630 { 1631 Verbs: []string{"get"}, 1632 APIGroups: []string{""}, 1633 Resources: []string{"secrets"}, 1634 ResourceNames: []string{"v1.a1-cert"}, 1635 }, 1636 }), 1637 roleBinding("v1.a1-cert", namespace, "v1.a1-cert", "sa", namespace), 1638 role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{ 1639 { 1640 Verbs: []string{"get"}, 1641 APIGroups: []string{""}, 1642 Resources: []string{"configmaps"}, 1643 ResourceNames: []string{"extension-apiserver-authentication"}, 1644 }, 1645 }), 1646 roleBinding("v1.a1-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace), 1647 clusterRole("system:auth-delegator", []rbacv1.PolicyRule{ 1648 { 1649 Verbs: []string{"create"}, 1650 APIGroups: []string{"authentication.k8s.io"}, 1651 Resources: []string{"tokenreviews"}, 1652 }, 1653 { 1654 Verbs: []string{"create"}, 1655 APIGroups: []string{"authentication.k8s.io"}, 1656 Resources: []string{"subjectaccessreviews"}, 1657 }, 1658 }), 1659 clusterRoleBinding("v1.a1-system:auth-delegator", "system:auth-delegator", "sa", namespace), 1660 }, 1661 crds: []runtime.Object{ 1662 crd("c1", "v1", "g1"), 1663 }, 1664 }, 1665 expected: expected{ 1666 csvStates: map[string]csvState{ 1667 "csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonAPIServiceResourceIssue}, 1668 }, 1669 }, 1670 }, 1671 { 1672 name: "SingleCSVSucceededToFailed/APIService/Owned/BadCAHash/Secret", 1673 initial: initial{ 1674 csvs: []*v1alpha1.ClusterServiceVersion{ 1675 withCertInfo(withAPIServices(csvWithAnnotations(csv("csv1", 1676 namespace, 1677 "0.0.0", 1678 "", 1679 installStrategy("a1", nil, nil), 1680 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 1681 []*apiextensionsv1.CustomResourceDefinition{}, 1682 v1alpha1.CSVPhaseSucceeded, 1683 ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()), 1684 }, 1685 clientObjs: []runtime.Object{defaultOperatorGroup}, 1686 apis: []runtime.Object{ 1687 apiService("a1", "v1", "v1-a1", namespace, "a1", validCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)), 1688 }, 1689 objs: []runtime.Object{ 1690 deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{ 1691 install.OLMCAHashAnnotationKey: validCAHash, 1692 })), 1693 withAnnotations(keyPairToTLSSecret("v1.a1-cert", namespace, signedServingPair(time.Now().Add(24*time.Hour), validCA, []string{"v1-a1.ns", "v1-a1.ns.svc"})), map[string]string{ 1694 install.OLMCAHashAnnotationKey: "also-a-pretty-bad-hash", 1695 }), 1696 service("v1-a1", namespace, "a1", 80), 1697 serviceAccount("sa", namespace), 1698 role("v1.a1-cert", namespace, []rbacv1.PolicyRule{ 1699 { 1700 Verbs: []string{"get"}, 1701 APIGroups: []string{""}, 1702 Resources: []string{"secrets"}, 1703 ResourceNames: []string{"v1.a1-cert"}, 1704 }, 1705 }), 1706 roleBinding("v1.a1-cert", namespace, "v1.a1-cert", "sa", namespace), 1707 role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{ 1708 { 1709 Verbs: []string{"get"}, 1710 APIGroups: []string{""}, 1711 Resources: []string{"configmaps"}, 1712 ResourceNames: []string{"extension-apiserver-authentication"}, 1713 }, 1714 }), 1715 roleBinding("v1.a1-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace), 1716 clusterRole("system:auth-delegator", []rbacv1.PolicyRule{ 1717 { 1718 Verbs: []string{"create"}, 1719 APIGroups: []string{"authentication.k8s.io"}, 1720 Resources: []string{"tokenreviews"}, 1721 }, 1722 { 1723 Verbs: []string{"create"}, 1724 APIGroups: []string{"authentication.k8s.io"}, 1725 Resources: []string{"subjectaccessreviews"}, 1726 }, 1727 }), 1728 clusterRoleBinding("v1.a1-system:auth-delegator", "system:auth-delegator", "sa", namespace), 1729 }, 1730 crds: []runtime.Object{ 1731 crd("c1", "v1", "g1"), 1732 }, 1733 }, 1734 expected: expected{ 1735 csvStates: map[string]csvState{ 1736 "csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonAPIServiceResourceIssue}, 1737 }, 1738 }, 1739 }, 1740 { 1741 name: "SingleCSVSucceededToFailed/APIService/Owned/BadCAHash/DeploymentAndSecret", 1742 initial: initial{ 1743 csvs: []*v1alpha1.ClusterServiceVersion{ 1744 withCertInfo(withAPIServices(csvWithAnnotations(csv("csv1", 1745 namespace, 1746 "0.0.0", 1747 "", 1748 installStrategy("a1", nil, nil), 1749 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 1750 []*apiextensionsv1.CustomResourceDefinition{}, 1751 v1alpha1.CSVPhaseSucceeded, 1752 ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()), 1753 }, 1754 clientObjs: []runtime.Object{defaultOperatorGroup}, 1755 apis: []runtime.Object{ 1756 apiService("a1", "v1", "v1-a1", namespace, "a1", validCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)), 1757 }, 1758 objs: []runtime.Object{ 1759 deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{ 1760 install.OLMCAHashAnnotationKey: "a-pretty-bad-hash", 1761 })), 1762 withAnnotations(keyPairToTLSSecret("v1.a1-cert", namespace, signedServingPair(time.Now().Add(24*time.Hour), validCA, []string{"v1-a1.ns", "v1-a1.ns.svc"})), map[string]string{ 1763 install.OLMCAHashAnnotationKey: "also-a-pretty-bad-hash", 1764 }), 1765 service("v1-a1", namespace, "a1", 80), 1766 serviceAccount("sa", namespace), 1767 role("v1.a1-cert", namespace, []rbacv1.PolicyRule{ 1768 { 1769 Verbs: []string{"get"}, 1770 APIGroups: []string{""}, 1771 Resources: []string{"secrets"}, 1772 ResourceNames: []string{"v1.a1-cert"}, 1773 }, 1774 }), 1775 roleBinding("v1.a1-cert", namespace, "v1.a1-cert", "sa", namespace), 1776 role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{ 1777 { 1778 Verbs: []string{"get"}, 1779 APIGroups: []string{""}, 1780 Resources: []string{"configmaps"}, 1781 ResourceNames: []string{"extension-apiserver-authentication"}, 1782 }, 1783 }), 1784 roleBinding("v1.a1-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace), 1785 clusterRole("system:auth-delegator", []rbacv1.PolicyRule{ 1786 { 1787 Verbs: []string{"create"}, 1788 APIGroups: []string{"authentication.k8s.io"}, 1789 Resources: []string{"tokenreviews"}, 1790 }, 1791 { 1792 Verbs: []string{"create"}, 1793 APIGroups: []string{"authentication.k8s.io"}, 1794 Resources: []string{"subjectaccessreviews"}, 1795 }, 1796 }), 1797 clusterRoleBinding("v1.a1-system:auth-delegator", "system:auth-delegator", "sa", namespace), 1798 }, 1799 crds: []runtime.Object{ 1800 crd("c1", "v1", "g1"), 1801 }, 1802 }, 1803 expected: expected{ 1804 csvStates: map[string]csvState{ 1805 "csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonAPIServiceResourceIssue}, 1806 }, 1807 }, 1808 }, 1809 { 1810 name: "SingleCSVSucceededToFailed/APIService/Owned/BadCA", 1811 initial: initial{ 1812 csvs: []*v1alpha1.ClusterServiceVersion{ 1813 withCertInfo(withAPIServices(csvWithAnnotations(csv("csv1", 1814 namespace, 1815 "0.0.0", 1816 "", 1817 installStrategy("a1", nil, nil), 1818 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 1819 []*apiextensionsv1.CustomResourceDefinition{}, 1820 v1alpha1.CSVPhaseSucceeded, 1821 ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()), 1822 }, 1823 clientObjs: []runtime.Object{defaultOperatorGroup}, 1824 apis: []runtime.Object{ 1825 apiService("a1", "v1", "v1-a1", namespace, "a1", []byte("a-bad-ca"), apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)), 1826 }, 1827 objs: []runtime.Object{ 1828 deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{ 1829 install.OLMCAHashAnnotationKey: validCAHash, 1830 })), 1831 withAnnotations(keyPairToTLSSecret("v1.a1-cert", namespace, signedServingPair(time.Now().Add(24*time.Hour), validCA, []string{"v1-a1.ns", "v1-a1.ns.svc"})), map[string]string{ 1832 install.OLMCAHashAnnotationKey: validCAHash, 1833 }), 1834 service("v1-a1", namespace, "a1", 80), 1835 serviceAccount("sa", namespace), 1836 role("v1.a1-cert", namespace, []rbacv1.PolicyRule{ 1837 { 1838 Verbs: []string{"get"}, 1839 APIGroups: []string{""}, 1840 Resources: []string{"secrets"}, 1841 ResourceNames: []string{"v1.a1-cert"}, 1842 }, 1843 }), 1844 roleBinding("v1.a1-cert", namespace, "v1.a1-cert", "sa", namespace), 1845 role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{ 1846 { 1847 Verbs: []string{"get"}, 1848 APIGroups: []string{""}, 1849 Resources: []string{"configmaps"}, 1850 ResourceNames: []string{"extension-apiserver-authentication"}, 1851 }, 1852 }), 1853 roleBinding("v1.a1-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace), 1854 clusterRole("system:auth-delegator", []rbacv1.PolicyRule{ 1855 { 1856 Verbs: []string{"create"}, 1857 APIGroups: []string{"authentication.k8s.io"}, 1858 Resources: []string{"tokenreviews"}, 1859 }, 1860 { 1861 Verbs: []string{"create"}, 1862 APIGroups: []string{"authentication.k8s.io"}, 1863 Resources: []string{"subjectaccessreviews"}, 1864 }, 1865 }), 1866 clusterRoleBinding("v1.a1-system:auth-delegator", "system:auth-delegator", "sa", namespace), 1867 }, 1868 crds: []runtime.Object{ 1869 crd("c1", "v1", "g1"), 1870 }, 1871 }, 1872 expected: expected{ 1873 csvStates: map[string]csvState{ 1874 "csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonAPIServiceResourceIssue}, 1875 }, 1876 }, 1877 }, 1878 { 1879 name: "SingleCSVSucceededToFailed/APIService/Owned/BadServingCert", 1880 initial: initial{ 1881 csvs: []*v1alpha1.ClusterServiceVersion{ 1882 withCertInfo(withAPIServices(csvWithAnnotations(csv("csv1", 1883 namespace, 1884 "0.0.0", 1885 "", 1886 installStrategy("a1", nil, nil), 1887 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 1888 []*apiextensionsv1.CustomResourceDefinition{}, 1889 v1alpha1.CSVPhaseSucceeded, 1890 ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()), 1891 }, 1892 clientObjs: []runtime.Object{defaultOperatorGroup}, 1893 apis: []runtime.Object{ 1894 apiService("a1", "v1", "v1-a1", namespace, "a1", validCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)), 1895 }, 1896 objs: []runtime.Object{ 1897 deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{ 1898 install.OLMCAHashAnnotationKey: validCAHash, 1899 })), 1900 withAnnotations(tlsSecret("v1.a1-cert", namespace, []byte("bad-cert"), []byte("bad-key")), map[string]string{ 1901 install.OLMCAHashAnnotationKey: validCAHash, 1902 }), 1903 service("v1-a1", namespace, "a1", 80), 1904 serviceAccount("sa", namespace), 1905 role("v1.a1-cert", namespace, []rbacv1.PolicyRule{ 1906 { 1907 Verbs: []string{"get"}, 1908 APIGroups: []string{""}, 1909 Resources: []string{"secrets"}, 1910 ResourceNames: []string{"v1.a1-cert"}, 1911 }, 1912 }), 1913 roleBinding("v1.a1-cert", namespace, "v1.a1-cert", "sa", namespace), 1914 role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{ 1915 { 1916 Verbs: []string{"get"}, 1917 APIGroups: []string{""}, 1918 Resources: []string{"configmaps"}, 1919 ResourceNames: []string{"extension-apiserver-authentication"}, 1920 }, 1921 }), 1922 roleBinding("v1.a1-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace), 1923 clusterRole("system:auth-delegator", []rbacv1.PolicyRule{ 1924 { 1925 Verbs: []string{"create"}, 1926 APIGroups: []string{"authentication.k8s.io"}, 1927 Resources: []string{"tokenreviews"}, 1928 }, 1929 { 1930 Verbs: []string{"create"}, 1931 APIGroups: []string{"authentication.k8s.io"}, 1932 Resources: []string{"subjectaccessreviews"}, 1933 }, 1934 }), 1935 clusterRoleBinding("v1.a1-system:auth-delegator", "system:auth-delegator", "sa", namespace), 1936 }, 1937 crds: []runtime.Object{ 1938 crd("c1", "v1", "g1"), 1939 }, 1940 }, 1941 expected: expected{ 1942 csvStates: map[string]csvState{ 1943 "csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonAPIServiceResourceIssue}, 1944 }, 1945 }, 1946 }, 1947 { 1948 name: "SingleCSVSucceededToFailed/APIService/Owned/ExpiredCA", 1949 initial: initial{ 1950 csvs: []*v1alpha1.ClusterServiceVersion{ 1951 withCertInfo(withAPIServices(csvWithAnnotations(csv("csv1", 1952 namespace, 1953 "0.0.0", 1954 "", 1955 installStrategy("a1", nil, nil), 1956 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 1957 []*apiextensionsv1.CustomResourceDefinition{}, 1958 v1alpha1.CSVPhaseSucceeded, 1959 ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()), 1960 }, 1961 clientObjs: []runtime.Object{defaultOperatorGroup}, 1962 apis: []runtime.Object{ 1963 apiService("a1", "v1", install.ServiceName("a1"), namespace, "a1", expiredCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)), 1964 }, 1965 objs: []runtime.Object{ 1966 deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{ 1967 install.OLMCAHashAnnotationKey: expiredCAHash, 1968 })), 1969 withAnnotations(keyPairToTLSSecret(install.SecretName(install.ServiceName("a1")), namespace, signedServingPair(time.Now().Add(24*time.Hour), expiredCA, install.HostnamesForService(install.ServiceName("a1"), "ns"))), map[string]string{ 1970 install.OLMCAHashAnnotationKey: expiredCAHash, 1971 }), 1972 service(install.ServiceName("a1"), namespace, "a1", 80), 1973 serviceAccount("sa", namespace), 1974 role(install.SecretName(install.ServiceName("a1")), namespace, []rbacv1.PolicyRule{ 1975 { 1976 Verbs: []string{"get"}, 1977 APIGroups: []string{""}, 1978 Resources: []string{"secrets"}, 1979 ResourceNames: []string{install.SecretName(install.ServiceName("a1"))}, 1980 }, 1981 }), 1982 roleBinding(install.SecretName(install.ServiceName("a1")), namespace, install.SecretName(install.ServiceName("a1")), "sa", namespace), 1983 role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{ 1984 { 1985 Verbs: []string{"get"}, 1986 APIGroups: []string{""}, 1987 Resources: []string{"configmaps"}, 1988 ResourceNames: []string{"extension-apiserver-authentication"}, 1989 }, 1990 }), 1991 roleBinding(install.AuthReaderRoleBindingName(install.ServiceName("a1")), "kube-system", "extension-apiserver-authentication-reader", "sa", namespace), 1992 clusterRole("system:auth-delegator", []rbacv1.PolicyRule{ 1993 { 1994 Verbs: []string{"create"}, 1995 APIGroups: []string{"authentication.k8s.io"}, 1996 Resources: []string{"tokenreviews"}, 1997 }, 1998 { 1999 Verbs: []string{"create"}, 2000 APIGroups: []string{"authentication.k8s.io"}, 2001 Resources: []string{"subjectaccessreviews"}, 2002 }, 2003 }), 2004 clusterRoleBinding(install.AuthDelegatorClusterRoleBindingName(install.ServiceName("a1")), "system:auth-delegator", "sa", namespace), 2005 }, 2006 crds: []runtime.Object{ 2007 crd("c1", "v1", "g1"), 2008 }, 2009 }, 2010 expected: expected{ 2011 csvStates: map[string]csvState{ 2012 "csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonNeedsCertRotation}, 2013 }, 2014 }, 2015 }, 2016 { 2017 name: "SingleCSVFailedToPending/APIService/Owned/ExpiredCA", 2018 initial: initial{ 2019 csvs: []*v1alpha1.ClusterServiceVersion{ 2020 withCertInfo(withAPIServices(csvWithAnnotations(csv("csv1", 2021 namespace, 2022 "0.0.0", 2023 "", 2024 installStrategy("a1", nil, nil), 2025 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2026 []*apiextensionsv1.CustomResourceDefinition{}, 2027 v1alpha1.CSVPhaseFailed, 2028 ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), metav1.Now(), metav1.Now()), 2029 }, 2030 clientObjs: []runtime.Object{defaultOperatorGroup}, 2031 apis: []runtime.Object{ 2032 apiService("a1", "v1", install.ServiceName("a1"), namespace, "a1", expiredCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)), 2033 }, 2034 objs: []runtime.Object{ 2035 deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{ 2036 install.OLMCAHashAnnotationKey: expiredCAHash, 2037 })), 2038 withAnnotations(keyPairToTLSSecret(install.SecretName(install.ServiceName("a1")), namespace, signedServingPair(time.Now().Add(24*time.Hour), expiredCA, install.HostnamesForService(install.ServiceName("a1"), "ns"))), map[string]string{ 2039 install.OLMCAHashAnnotationKey: expiredCAHash, 2040 }), 2041 service(install.ServiceName("a1"), namespace, "a1", 80), 2042 serviceAccount("sa", namespace), 2043 role(install.SecretName(install.ServiceName("a1")), namespace, []rbacv1.PolicyRule{ 2044 { 2045 Verbs: []string{"get"}, 2046 APIGroups: []string{""}, 2047 Resources: []string{"secrets"}, 2048 ResourceNames: []string{install.SecretName(install.ServiceName("a1"))}, 2049 }, 2050 }), 2051 roleBinding(install.SecretName(install.ServiceName("a1")), namespace, install.SecretName(install.ServiceName("a1")), "sa", namespace), 2052 role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{ 2053 { 2054 Verbs: []string{"get"}, 2055 APIGroups: []string{""}, 2056 Resources: []string{"configmaps"}, 2057 ResourceNames: []string{"extension-apiserver-authentication"}, 2058 }, 2059 }), 2060 roleBinding(install.AuthReaderRoleBindingName(install.ServiceName("a1")), "kube-system", "extension-apiserver-authentication-reader", "sa", namespace), 2061 clusterRole("system:auth-delegator", []rbacv1.PolicyRule{ 2062 { 2063 Verbs: []string{"create"}, 2064 APIGroups: []string{"authentication.k8s.io"}, 2065 Resources: []string{"tokenreviews"}, 2066 }, 2067 { 2068 Verbs: []string{"create"}, 2069 APIGroups: []string{"authentication.k8s.io"}, 2070 Resources: []string{"subjectaccessreviews"}, 2071 }, 2072 }), 2073 clusterRoleBinding(install.AuthDelegatorClusterRoleBindingName(install.ServiceName("a1")), "system:auth-delegator", "sa", namespace), 2074 }, 2075 crds: []runtime.Object{ 2076 crd("c1", "v1", "g1"), 2077 }, 2078 }, 2079 expected: expected{ 2080 csvStates: map[string]csvState{ 2081 "csv1": {exists: true, phase: v1alpha1.CSVPhasePending, reason: v1alpha1.CSVReasonNeedsCertRotation}, 2082 }, 2083 }, 2084 }, 2085 { 2086 name: "SingleCSVFailedToPending/InstallModes/Owned/PreviouslyUnsupported", 2087 initial: initial{ 2088 csvs: []*v1alpha1.ClusterServiceVersion{ 2089 withConditionReason(csvWithAnnotations(csv("csv1", 2090 namespace, 2091 "0.0.0", 2092 "", 2093 installStrategy("a1", nil, nil), 2094 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2095 []*apiextensionsv1.CustomResourceDefinition{}, 2096 v1alpha1.CSVPhaseFailed, 2097 ), defaultTemplateAnnotations), v1alpha1.CSVReasonUnsupportedOperatorGroup), 2098 }, 2099 clientObjs: []runtime.Object{defaultOperatorGroup}, 2100 apis: []runtime.Object{}, 2101 objs: []runtime.Object{ 2102 deployment("a1", namespace, "sa", defaultTemplateAnnotations), 2103 serviceAccount("sa", namespace), 2104 }, 2105 crds: []runtime.Object{ 2106 crd("c1", "v1", "g1"), 2107 }, 2108 }, 2109 expected: expected{ 2110 csvStates: map[string]csvState{ 2111 "csv1": {exists: true, phase: v1alpha1.CSVPhasePending, reason: v1alpha1.CSVReasonRequirementsUnknown}, 2112 }, 2113 }, 2114 }, 2115 { 2116 name: "SingleCSVFailedToPending/InstallModes/Owned/PreviouslyNoOperatorGroups", 2117 initial: initial{ 2118 csvs: []*v1alpha1.ClusterServiceVersion{ 2119 withConditionReason(csvWithAnnotations(csv("csv1", 2120 namespace, 2121 "0.0.0", 2122 "", 2123 installStrategy("a1", nil, nil), 2124 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2125 []*apiextensionsv1.CustomResourceDefinition{}, 2126 v1alpha1.CSVPhaseFailed, 2127 ), defaultTemplateAnnotations), v1alpha1.CSVReasonNoOperatorGroup), 2128 }, 2129 clientObjs: []runtime.Object{defaultOperatorGroup}, 2130 apis: []runtime.Object{}, 2131 objs: []runtime.Object{ 2132 deployment("a1", namespace, "sa", defaultTemplateAnnotations), 2133 serviceAccount("sa", namespace), 2134 }, 2135 crds: []runtime.Object{ 2136 crd("c1", "v1", "g1"), 2137 }, 2138 }, 2139 expected: expected{ 2140 csvStates: map[string]csvState{ 2141 "csv1": {exists: true, phase: v1alpha1.CSVPhasePending, reason: v1alpha1.CSVReasonRequirementsUnknown}, 2142 }, 2143 }, 2144 }, 2145 { 2146 name: "SingleCSVFailedToPending/InstallModes/Owned/PreviouslyTooManyOperatorGroups", 2147 initial: initial{ 2148 csvs: []*v1alpha1.ClusterServiceVersion{ 2149 withConditionReason(csvWithAnnotations(csv("csv1", 2150 namespace, 2151 "0.0.0", 2152 "", 2153 installStrategy("a1", nil, nil), 2154 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2155 []*apiextensionsv1.CustomResourceDefinition{}, 2156 v1alpha1.CSVPhaseFailed, 2157 ), defaultTemplateAnnotations), v1alpha1.CSVReasonTooManyOperatorGroups), 2158 }, 2159 clientObjs: []runtime.Object{defaultOperatorGroup}, 2160 apis: []runtime.Object{}, 2161 objs: []runtime.Object{ 2162 deployment("a1", namespace, "sa", defaultTemplateAnnotations), 2163 serviceAccount("sa", namespace), 2164 }, 2165 crds: []runtime.Object{ 2166 crd("c1", "v1", "g1"), 2167 }, 2168 }, 2169 expected: expected{ 2170 csvStates: map[string]csvState{ 2171 "csv1": {exists: true, phase: v1alpha1.CSVPhasePending, reason: v1alpha1.CSVReasonRequirementsUnknown}, 2172 }, 2173 }, 2174 }, 2175 { 2176 name: "SingleCSVSucceededToFailed/InstallModes/Owned/Unsupported", 2177 initial: initial{ 2178 csvs: []*v1alpha1.ClusterServiceVersion{ 2179 withInstallModes(withConditionReason(csvWithAnnotations(csv("csv1", 2180 namespace, 2181 "0.0.0", 2182 "", 2183 installStrategy("a1", nil, nil), 2184 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2185 []*apiextensionsv1.CustomResourceDefinition{}, 2186 v1alpha1.CSVPhaseSucceeded, 2187 ), defaultTemplateAnnotations), v1alpha1.CSVReasonInstallSuccessful), 2188 []v1alpha1.InstallMode{ 2189 { 2190 Type: v1alpha1.InstallModeTypeSingleNamespace, 2191 Supported: false, 2192 }, 2193 }, 2194 ), 2195 }, 2196 clientObjs: []runtime.Object{defaultOperatorGroup}, 2197 apis: []runtime.Object{}, 2198 objs: []runtime.Object{ 2199 deployment("a1", namespace, "sa", defaultTemplateAnnotations), 2200 serviceAccount("sa", namespace), 2201 }, 2202 crds: []runtime.Object{ 2203 crd("c1", "v1", "g1"), 2204 }, 2205 }, 2206 expected: expected{ 2207 csvStates: map[string]csvState{ 2208 "csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonUnsupportedOperatorGroup}, 2209 }, 2210 }, 2211 }, 2212 { 2213 name: "SingleCSVSucceededToFailed/InstallModes/Owned/NoOperatorGroups", 2214 initial: initial{ 2215 csvs: []*v1alpha1.ClusterServiceVersion{ 2216 withConditionReason(csvWithAnnotations(csv("csv1", 2217 namespace, 2218 "0.0.0", 2219 "", 2220 installStrategy("a1", nil, nil), 2221 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2222 []*apiextensionsv1.CustomResourceDefinition{}, 2223 v1alpha1.CSVPhaseSucceeded, 2224 ), defaultTemplateAnnotations), v1alpha1.CSVReasonInstallSuccessful), 2225 }, 2226 apis: []runtime.Object{}, 2227 objs: []runtime.Object{ 2228 deployment("a1", namespace, "sa", defaultTemplateAnnotations), 2229 serviceAccount("sa", namespace), 2230 }, 2231 crds: []runtime.Object{ 2232 crd("c1", "v1", "g1"), 2233 }, 2234 }, 2235 expected: expected{ 2236 csvStates: map[string]csvState{ 2237 "csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonNoOperatorGroup}, 2238 }, 2239 err: map[string]error{ 2240 "csv1": fmt.Errorf("csv in namespace with no operatorgroups"), 2241 }, 2242 }, 2243 }, 2244 { 2245 name: "SingleCSVSucceededToFailed/InstallModes/Owned/TooManyOperatorGroups", 2246 initial: initial{ 2247 csvs: []*v1alpha1.ClusterServiceVersion{ 2248 withConditionReason(csvWithAnnotations(csv("csv1", 2249 namespace, 2250 "0.0.0", 2251 "", 2252 installStrategy("a1", nil, nil), 2253 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2254 []*apiextensionsv1.CustomResourceDefinition{}, 2255 v1alpha1.CSVPhaseSucceeded, 2256 ), defaultTemplateAnnotations), v1alpha1.CSVReasonInstallSuccessful), 2257 }, 2258 clientObjs: []runtime.Object{ 2259 defaultOperatorGroup, 2260 &operatorsv1.OperatorGroup{ 2261 TypeMeta: metav1.TypeMeta{ 2262 Kind: "OperatorGroup", 2263 APIVersion: operatorsv1.SchemeGroupVersion.String(), 2264 }, 2265 ObjectMeta: metav1.ObjectMeta{ 2266 Name: "default-2", 2267 Namespace: namespace, 2268 }, 2269 Spec: operatorsv1.OperatorGroupSpec{}, 2270 Status: operatorsv1.OperatorGroupStatus{ 2271 Namespaces: []string{namespace}, 2272 }, 2273 }, 2274 }, 2275 apis: []runtime.Object{}, 2276 objs: []runtime.Object{ 2277 deployment("a1", namespace, "sa", defaultTemplateAnnotations), 2278 serviceAccount("sa", namespace), 2279 }, 2280 crds: []runtime.Object{ 2281 crd("c1", "v1", "g1"), 2282 }, 2283 }, 2284 expected: expected{ 2285 csvStates: map[string]csvState{ 2286 "csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonTooManyOperatorGroups}, 2287 }, 2288 err: map[string]error{ 2289 "csv1": fmt.Errorf("csv created in namespace with multiple operatorgroups, can't pick one automatically"), 2290 }, 2291 }, 2292 }, 2293 { 2294 name: "SingleCSVSucceededToSucceeded/OperatorGroupChanged", 2295 initial: initial{ 2296 csvs: []*v1alpha1.ClusterServiceVersion{ 2297 withConditionReason(csvWithAnnotations(csv("csv1", 2298 namespace, 2299 "0.0.0", 2300 "", 2301 installStrategy("a1", nil, nil), 2302 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2303 []*apiextensionsv1.CustomResourceDefinition{}, 2304 v1alpha1.CSVPhaseSucceeded, 2305 ), defaultTemplateAnnotations), v1alpha1.CSVReasonInstallSuccessful), 2306 }, 2307 clientObjs: []runtime.Object{ 2308 &operatorsv1.OperatorGroup{ 2309 TypeMeta: metav1.TypeMeta{ 2310 Kind: "OperatorGroup", 2311 APIVersion: operatorsv1.SchemeGroupVersion.String(), 2312 }, 2313 ObjectMeta: metav1.ObjectMeta{ 2314 Name: "default", 2315 Namespace: namespace, 2316 }, 2317 Spec: operatorsv1.OperatorGroupSpec{}, 2318 Status: operatorsv1.OperatorGroupStatus{ 2319 Namespaces: []string{namespace, "new-namespace"}, 2320 }, 2321 }, 2322 }, 2323 apis: []runtime.Object{}, 2324 objs: []runtime.Object{ 2325 deployment("a1", namespace, "sa", defaultTemplateAnnotations), 2326 serviceAccount("sa", namespace), 2327 }, 2328 crds: []runtime.Object{ 2329 crd("c1", "v1", "g1"), 2330 }, 2331 }, 2332 expected: expected{ 2333 csvStates: map[string]csvState{ 2334 "csv1": {exists: true, phase: v1alpha1.CSVPhaseSucceeded, reason: v1alpha1.CSVReasonInstallSuccessful}, 2335 }, 2336 }, 2337 }, 2338 { 2339 name: "SingleCSVInstallingToSucceeded/UnmanagedDeploymentNotAffected", 2340 initial: initial{ 2341 csvs: []*v1alpha1.ClusterServiceVersion{ 2342 csvWithAnnotations(csv("csv1", 2343 namespace, 2344 "0.0.0", 2345 "", 2346 installStrategy("csv1-dep1", nil, nil), 2347 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2348 []*apiextensionsv1.CustomResourceDefinition{}, 2349 v1alpha1.CSVPhaseInstalling, 2350 ), defaultTemplateAnnotations), 2351 }, 2352 clientObjs: []runtime.Object{defaultOperatorGroup}, 2353 crds: []runtime.Object{ 2354 crd("c1", "v1", "g1"), 2355 }, 2356 objs: []runtime.Object{ 2357 withLabels( 2358 deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations), 2359 addDepSpecHashLabel(t, ownerLabelFromCSV("csv1", namespace), withTemplateAnnotations(installStrategy("csv1-dep1", nil, nil), defaultTemplateAnnotations)), 2360 ), 2361 deployment("extra-dep", namespace, "sa", nil), 2362 }, 2363 }, 2364 expected: expected{ 2365 csvStates: map[string]csvState{ 2366 "csv1": {exists: true, phase: v1alpha1.CSVPhaseSucceeded}, 2367 }, 2368 objs: []runtime.Object{ 2369 deployment("extra-dep", namespace, "sa", nil), 2370 }, 2371 }, 2372 }, 2373 { 2374 name: "SingleCSVInstallingToInstallReady", 2375 initial: initial{ 2376 csvs: []*v1alpha1.ClusterServiceVersion{ 2377 csvWithAnnotations(csv("csv1", 2378 namespace, 2379 "0.0.0", 2380 "", 2381 installStrategy("csv1-dep1", nil, nil), 2382 []*apiextensionsv1.CustomResourceDefinition{}, 2383 []*apiextensionsv1.CustomResourceDefinition{}, 2384 v1alpha1.CSVPhaseInstalling, 2385 ), defaultTemplateAnnotations), 2386 }, 2387 clientObjs: []runtime.Object{defaultOperatorGroup}, 2388 crds: []runtime.Object{}, 2389 objs: []runtime.Object{ 2390 withLabels( 2391 deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations), 2392 map[string]string{install.DeploymentSpecHashLabelKey: "BadHash"}, 2393 ), 2394 }, 2395 }, 2396 expected: expected{ 2397 csvStates: map[string]csvState{ 2398 "csv1": {exists: true, phase: v1alpha1.CSVPhaseInstallReady, reason: "InstallWaiting"}, 2399 }, 2400 }, 2401 }, 2402 { 2403 name: "SingleCSVInstallingToInstallReadyDueToAnnotations", 2404 initial: initial{ 2405 csvs: []*v1alpha1.ClusterServiceVersion{ 2406 csvWithAnnotations(csv("csv1", 2407 namespace, 2408 "0.0.0", 2409 "", 2410 installStrategy("csv1-dep1", nil, nil), 2411 []*apiextensionsv1.CustomResourceDefinition{}, 2412 []*apiextensionsv1.CustomResourceDefinition{}, 2413 v1alpha1.CSVPhaseInstalling, 2414 ), defaultTemplateAnnotations), 2415 }, 2416 clientObjs: []runtime.Object{defaultOperatorGroup}, 2417 crds: []runtime.Object{}, 2418 objs: []runtime.Object{ 2419 deployment("csv1-dep1", namespace, "sa", map[string]string{}), 2420 }, 2421 }, 2422 expected: expected{ 2423 csvStates: map[string]csvState{ 2424 "csv1": {exists: true, phase: v1alpha1.CSVPhaseInstallReady, reason: ""}, 2425 }, 2426 }, 2427 }, 2428 { 2429 name: "SingleCSVSucceededToSucceeded/UnmanagedDeploymentInNamespace", 2430 initial: initial{ 2431 csvs: []*v1alpha1.ClusterServiceVersion{ 2432 withConditionReason(csvWithAnnotations(csv("csv1", 2433 namespace, 2434 "0.0.0", 2435 "", 2436 installStrategy("csv1-dep1", nil, nil), 2437 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2438 []*apiextensionsv1.CustomResourceDefinition{}, 2439 v1alpha1.CSVPhaseSucceeded, 2440 ), defaultTemplateAnnotations), v1alpha1.CSVReasonInstallSuccessful), 2441 }, 2442 clientObjs: []runtime.Object{defaultOperatorGroup}, 2443 crds: []runtime.Object{ 2444 crd("c1", "v1", "g1"), 2445 }, 2446 objs: []runtime.Object{ 2447 withLabels( 2448 deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations), 2449 addDepSpecHashLabel(t, map[string]string{ 2450 ownerutil.OwnerKey: "csv1", 2451 ownerutil.OwnerNamespaceKey: namespace, 2452 ownerutil.OwnerKind: "ClusterServiceVersion", 2453 }, withTemplateAnnotations(installStrategy("csv1-dep1", nil, nil), defaultTemplateAnnotations)), 2454 ), 2455 deployment("extra-dep", namespace, "sa", nil), 2456 }, 2457 }, 2458 expected: expected{ 2459 csvStates: map[string]csvState{ 2460 "csv1": {exists: true, phase: v1alpha1.CSVPhaseSucceeded}, 2461 }, 2462 objs: []runtime.Object{ 2463 deployment("extra-dep", namespace, "sa", nil), 2464 }, 2465 }, 2466 }, 2467 { 2468 name: "SingleCSVSucceededToFailed/CRD", 2469 initial: initial{ 2470 csvs: []*v1alpha1.ClusterServiceVersion{ 2471 withAPIServices(csvWithAnnotations(csv("csv1", 2472 namespace, 2473 "0.0.0", 2474 "", 2475 installStrategy("a1", nil, nil), 2476 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2477 []*apiextensionsv1.CustomResourceDefinition{}, 2478 v1alpha1.CSVPhaseSucceeded, 2479 ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), 2480 }, 2481 clientObjs: []runtime.Object{defaultOperatorGroup}, 2482 }, 2483 expected: expected{ 2484 csvStates: map[string]csvState{ 2485 "csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed}, 2486 }, 2487 }, 2488 }, 2489 { 2490 name: "SingleCSVSucceededToPending/DeploymentSpecChanged", 2491 initial: initial{ 2492 csvs: []*v1alpha1.ClusterServiceVersion{ 2493 withConditionReason(csvWithAnnotations(csv("csv1", 2494 namespace, 2495 "0.0.0", 2496 "", 2497 installStrategy("csv1-dep1", nil, nil), 2498 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2499 []*apiextensionsv1.CustomResourceDefinition{}, 2500 v1alpha1.CSVPhaseSucceeded, 2501 ), addAnnotations(defaultTemplateAnnotations, map[string]string{"new": "annotation"})), v1alpha1.CSVReasonInstallSuccessful), 2502 }, 2503 clientObjs: []runtime.Object{defaultOperatorGroup}, 2504 crds: []runtime.Object{ 2505 crd("c1", "v1", "g1"), 2506 }, 2507 objs: []runtime.Object{ 2508 withLabels( 2509 deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations), 2510 addDepSpecHashLabel(t, map[string]string{ 2511 ownerutil.OwnerKey: "csv1", 2512 ownerutil.OwnerNamespaceKey: namespace, 2513 ownerutil.OwnerKind: "ClusterServiceVersion", 2514 }, withTemplateAnnotations(installStrategy("csv1-dep1", nil, nil), defaultTemplateAnnotations)), 2515 ), 2516 }, 2517 }, 2518 expected: expected{ 2519 csvStates: map[string]csvState{ 2520 "csv1": {exists: true, phase: v1alpha1.CSVPhasePending}, 2521 }, 2522 }, 2523 }, 2524 { 2525 name: "CSVSucceededToReplacing", 2526 initial: initial{ 2527 csvs: []*v1alpha1.ClusterServiceVersion{ 2528 withAnnotations(csv("csv1", 2529 namespace, 2530 "0.0.0", 2531 "", 2532 installStrategy("csv1-dep1", nil, nil), 2533 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2534 []*apiextensionsv1.CustomResourceDefinition{}, 2535 v1alpha1.CSVPhaseSucceeded, 2536 ), defaultTemplateAnnotations).(*v1alpha1.ClusterServiceVersion), 2537 csvWithAnnotations(csv("csv2", 2538 namespace, 2539 "0.0.0", 2540 "csv1", 2541 installStrategy("csv2-dep1", nil, nil), 2542 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2543 []*apiextensionsv1.CustomResourceDefinition{}, 2544 v1alpha1.CSVPhaseNone, 2545 ), defaultTemplateAnnotations), 2546 }, 2547 clientObjs: []runtime.Object{defaultOperatorGroup}, 2548 crds: []runtime.Object{ 2549 crd("c1", "v1", "g1"), 2550 }, 2551 objs: []runtime.Object{ 2552 deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations), 2553 }, 2554 }, 2555 expected: expected{ 2556 csvStates: map[string]csvState{ 2557 "csv1": {exists: true, phase: v1alpha1.CSVPhaseReplacing}, 2558 "csv2": {exists: true, phase: v1alpha1.CSVPhasePending}, 2559 }, 2560 }, 2561 }, 2562 { 2563 name: "CSVReplacingToDeleted", 2564 initial: initial{ 2565 csvs: []*v1alpha1.ClusterServiceVersion{ 2566 csvWithAnnotations(csv("csv1", 2567 namespace, 2568 "0.0.0", 2569 "", 2570 installStrategy("csv1-dep1", nil, nil), 2571 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2572 []*apiextensionsv1.CustomResourceDefinition{}, 2573 v1alpha1.CSVPhaseReplacing, 2574 ), defaultTemplateAnnotations), 2575 csvWithAnnotations(csv("csv2", 2576 namespace, 2577 "0.0.0", 2578 "csv1", 2579 installStrategy("csv2-dep1", nil, nil), 2580 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2581 []*apiextensionsv1.CustomResourceDefinition{}, 2582 v1alpha1.CSVPhaseSucceeded, 2583 ), defaultTemplateAnnotations), 2584 }, 2585 clientObjs: []runtime.Object{defaultOperatorGroup}, 2586 crds: []runtime.Object{ 2587 crd("c1", "v1", "g1"), 2588 }, 2589 objs: []runtime.Object{ 2590 withLabels( 2591 deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations), 2592 addDepSpecHashLabel(t, ownerLabelFromCSV("csv1", namespace), withTemplateAnnotations(installStrategy("csv1-dep1", nil, nil), defaultTemplateAnnotations)), 2593 ), 2594 withLabels( 2595 deployment("csv2-dep1", namespace, "sa", defaultTemplateAnnotations), 2596 addDepSpecHashLabel(t, ownerLabelFromCSV("csv2", namespace), withTemplateAnnotations(installStrategy("csv2-dep1", nil, nil), defaultTemplateAnnotations)), 2597 ), 2598 }, 2599 }, 2600 expected: expected{ 2601 csvStates: map[string]csvState{ 2602 "csv1": {exists: true, phase: v1alpha1.CSVPhaseDeleting}, 2603 "csv2": {exists: true, phase: v1alpha1.CSVPhaseSucceeded}, 2604 }, 2605 }, 2606 }, 2607 { 2608 name: "CSVDeletedToGone", 2609 initial: initial{ 2610 csvs: []*v1alpha1.ClusterServiceVersion{ 2611 csvWithAnnotations(csv("csv1", 2612 namespace, 2613 "0.0.0", 2614 "", 2615 installStrategy("csv1-dep1", nil, nil), 2616 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2617 []*apiextensionsv1.CustomResourceDefinition{}, 2618 v1alpha1.CSVPhaseDeleting, 2619 ), defaultTemplateAnnotations), 2620 csvWithAnnotations(csv("csv2", 2621 namespace, 2622 "0.0.0", 2623 "csv1", 2624 installStrategy("csv2-dep1", nil, nil), 2625 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2626 []*apiextensionsv1.CustomResourceDefinition{}, 2627 v1alpha1.CSVPhaseSucceeded, 2628 ), defaultTemplateAnnotations), 2629 }, 2630 clientObjs: []runtime.Object{defaultOperatorGroup}, 2631 crds: []runtime.Object{ 2632 crd("c1", "v1", "g1"), 2633 }, 2634 objs: []runtime.Object{ 2635 withLabels( 2636 deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations), 2637 addDepSpecHashLabel(t, ownerLabelFromCSV("csv1", namespace), withTemplateAnnotations(installStrategy("csv1-dep1", nil, nil), defaultTemplateAnnotations)), 2638 ), 2639 withLabels( 2640 deployment("csv2-dep1", namespace, "sa", defaultTemplateAnnotations), 2641 addDepSpecHashLabel(t, ownerLabelFromCSV("csv2", namespace), withTemplateAnnotations(installStrategy("csv2-dep1", nil, nil), defaultTemplateAnnotations)), 2642 ), 2643 }, 2644 }, 2645 expected: expected{ 2646 csvStates: map[string]csvState{ 2647 "csv1": {exists: false, phase: v1alpha1.CSVPhaseNone}, 2648 "csv2": {exists: true, phase: v1alpha1.CSVPhaseSucceeded}, 2649 }, 2650 }, 2651 }, 2652 { 2653 name: "CSVMultipleReplacingToDeleted", 2654 initial: initial{ 2655 // order matters in this test case - we want to apply the latest CSV first to test the GC marking 2656 csvs: []*v1alpha1.ClusterServiceVersion{ 2657 csvWithLabels(csvWithAnnotations(csv("csv3", 2658 namespace, 2659 "0.0.0", 2660 "csv2", 2661 installStrategy("csv3-dep1", nil, nil), 2662 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2663 []*apiextensionsv1.CustomResourceDefinition{}, 2664 v1alpha1.CSVPhaseSucceeded, 2665 ), defaultTemplateAnnotations), labels.Set{ 2666 APILabelKeyPrefix + apiHash: "provided", 2667 }), 2668 csvWithLabels(csvWithAnnotations(csv("csv1", 2669 namespace, 2670 "0.0.0", 2671 "", 2672 installStrategy("csv1-dep1", nil, nil), 2673 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2674 []*apiextensionsv1.CustomResourceDefinition{}, 2675 v1alpha1.CSVPhaseReplacing, 2676 ), defaultTemplateAnnotations), labels.Set{ 2677 APILabelKeyPrefix + apiHash: "provided", 2678 }), 2679 csvWithLabels(csvWithAnnotations(csv("csv2", 2680 namespace, 2681 "0.0.0", 2682 "csv1", 2683 installStrategy("csv2-dep1", nil, nil), 2684 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2685 []*apiextensionsv1.CustomResourceDefinition{}, 2686 v1alpha1.CSVPhaseReplacing, 2687 ), defaultTemplateAnnotations), labels.Set{ 2688 APILabelKeyPrefix + apiHash: "provided", 2689 }), 2690 }, 2691 clientObjs: []runtime.Object{defaultOperatorGroup}, 2692 crds: []runtime.Object{ 2693 crd("c1", "v1", "g1"), 2694 }, 2695 objs: []runtime.Object{ 2696 withLabels( 2697 deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations), 2698 addDepSpecHashLabel(t, ownerLabelFromCSV("csv1", namespace), withTemplateAnnotations(installStrategy("csv1-dep1", nil, nil), defaultTemplateAnnotations)), 2699 ), 2700 withLabels( 2701 deployment("csv2-dep1", namespace, "sa", defaultTemplateAnnotations), 2702 addDepSpecHashLabel(t, ownerLabelFromCSV("csv2", namespace), withTemplateAnnotations(installStrategy("csv2-dep1", nil, nil), defaultTemplateAnnotations)), 2703 ), 2704 withLabels( 2705 deployment("csv3-dep1", namespace, "sa", defaultTemplateAnnotations), 2706 addDepSpecHashLabel(t, ownerLabelFromCSV("csv3", namespace), withTemplateAnnotations(installStrategy("csv3-dep1", nil, nil), defaultTemplateAnnotations)), 2707 ), 2708 }, 2709 }, 2710 expected: expected{ 2711 csvStates: map[string]csvState{ 2712 "csv1": {exists: true, phase: v1alpha1.CSVPhaseReplacing}, 2713 "csv2": {exists: true, phase: v1alpha1.CSVPhaseReplacing}, 2714 "csv3": {exists: true, phase: v1alpha1.CSVPhaseSucceeded}, 2715 }, 2716 }, 2717 }, 2718 { 2719 name: "CSVMultipleDeletedToGone", 2720 initial: initial{ 2721 csvs: []*v1alpha1.ClusterServiceVersion{ 2722 csvWithAnnotations(csv("csv3", 2723 namespace, 2724 "0.0.0", 2725 "csv2", 2726 installStrategy("csv3-dep1", nil, nil), 2727 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2728 []*apiextensionsv1.CustomResourceDefinition{}, 2729 v1alpha1.CSVPhaseSucceeded, 2730 ), defaultTemplateAnnotations), 2731 csvWithAnnotations(csv("csv1", 2732 namespace, 2733 "0.0.0", 2734 "", 2735 installStrategy("csv1-dep1", nil, nil), 2736 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2737 []*apiextensionsv1.CustomResourceDefinition{}, 2738 v1alpha1.CSVPhaseDeleting, 2739 ), defaultTemplateAnnotations), 2740 csvWithAnnotations(csv("csv2", 2741 namespace, 2742 "0.0.0", 2743 "csv1", 2744 installStrategy("csv2-dep1", nil, nil), 2745 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2746 []*apiextensionsv1.CustomResourceDefinition{}, 2747 v1alpha1.CSVPhaseReplacing, 2748 ), defaultTemplateAnnotations), 2749 }, 2750 clientObjs: []runtime.Object{defaultOperatorGroup}, 2751 crds: []runtime.Object{ 2752 crd("c1", "v1", "g1"), 2753 }, 2754 objs: []runtime.Object{ 2755 withLabels( 2756 deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations), 2757 addDepSpecHashLabel(t, ownerLabelFromCSV("csv1", namespace), withTemplateAnnotations(installStrategy("csv1-dep1", nil, nil), defaultTemplateAnnotations)), 2758 ), 2759 withLabels( 2760 deployment("csv2-dep1", namespace, "sa", defaultTemplateAnnotations), 2761 addDepSpecHashLabel(t, ownerLabelFromCSV("csv2", namespace), withTemplateAnnotations(installStrategy("csv2-dep1", nil, nil), defaultTemplateAnnotations)), 2762 ), 2763 withLabels( 2764 deployment("csv3-dep1", namespace, "sa", defaultTemplateAnnotations), 2765 addDepSpecHashLabel(t, ownerLabelFromCSV("csv3", namespace), withTemplateAnnotations(installStrategy("csv3-dep1", nil, nil), defaultTemplateAnnotations)), 2766 ), 2767 }, 2768 }, 2769 expected: expected{ 2770 csvStates: map[string]csvState{ 2771 "csv1": {exists: false, phase: v1alpha1.CSVPhaseNone}, 2772 "csv2": {exists: true, phase: v1alpha1.CSVPhaseDeleting}, 2773 "csv3": {exists: true, phase: v1alpha1.CSVPhaseSucceeded}, 2774 }, 2775 }, 2776 }, 2777 { 2778 name: "CSVMultipleDeletedToGone/AfterOneDeleted", 2779 initial: initial{ 2780 csvs: []*v1alpha1.ClusterServiceVersion{ 2781 csvWithAnnotations(csv("csv2", 2782 namespace, 2783 "0.0.0", 2784 "csv1", 2785 installStrategy("csv2-dep1", nil, nil), 2786 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2787 []*apiextensionsv1.CustomResourceDefinition{}, 2788 v1alpha1.CSVPhaseReplacing, 2789 ), defaultTemplateAnnotations), 2790 csvWithAnnotations(csv("csv3", 2791 namespace, 2792 "0.0.0", 2793 "csv2", 2794 installStrategy("csv3-dep1", nil, nil), 2795 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2796 []*apiextensionsv1.CustomResourceDefinition{}, 2797 v1alpha1.CSVPhaseSucceeded, 2798 ), defaultTemplateAnnotations), 2799 }, 2800 clientObjs: []runtime.Object{defaultOperatorGroup}, 2801 crds: []runtime.Object{ 2802 crd("c1", "v1", "g1"), 2803 }, 2804 objs: []runtime.Object{ 2805 withLabels( 2806 deployment("csv2-dep1", namespace, "sa", defaultTemplateAnnotations), 2807 addDepSpecHashLabel(t, ownerLabelFromCSV("csv2", namespace), withTemplateAnnotations(installStrategy("csv2-dep1", nil, nil), defaultTemplateAnnotations)), 2808 ), 2809 withLabels( 2810 deployment("csv3-dep1", namespace, "sa", defaultTemplateAnnotations), 2811 addDepSpecHashLabel(t, ownerLabelFromCSV("csv3", namespace), withTemplateAnnotations(installStrategy("csv3-dep1", nil, nil), defaultTemplateAnnotations)), 2812 ), 2813 }, 2814 }, 2815 expected: expected{ 2816 csvStates: map[string]csvState{ 2817 "csv1": {exists: false, phase: v1alpha1.CSVPhaseNone}, 2818 "csv2": {exists: true, phase: v1alpha1.CSVPhaseDeleting}, 2819 "csv3": {exists: true, phase: v1alpha1.CSVPhaseSucceeded}, 2820 }, 2821 }, 2822 }, 2823 { 2824 name: "CSVMultipleDeletedToGone/AfterTwoDeleted", 2825 initial: initial{ 2826 csvs: []*v1alpha1.ClusterServiceVersion{ 2827 csvWithAnnotations(csv("csv2", 2828 namespace, 2829 "0.0.0", 2830 "csv1", 2831 installStrategy("csv2-dep1", nil, nil), 2832 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2833 []*apiextensionsv1.CustomResourceDefinition{}, 2834 v1alpha1.CSVPhaseDeleting, 2835 ), defaultTemplateAnnotations), 2836 csvWithAnnotations(csv("csv3", 2837 namespace, 2838 "0.0.0", 2839 "csv2", 2840 installStrategy("csv3-dep1", nil, nil), 2841 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2842 []*apiextensionsv1.CustomResourceDefinition{}, 2843 v1alpha1.CSVPhaseSucceeded, 2844 ), defaultTemplateAnnotations), 2845 }, 2846 clientObjs: []runtime.Object{defaultOperatorGroup}, 2847 crds: []runtime.Object{ 2848 crd("c1", "v1", "g1"), 2849 }, 2850 objs: []runtime.Object{ 2851 withLabels( 2852 deployment("csv2-dep1", namespace, "sa", defaultTemplateAnnotations), 2853 addDepSpecHashLabel(t, ownerLabelFromCSV("csv2", namespace), withTemplateAnnotations(installStrategy("csv2-dep1", nil, nil), defaultTemplateAnnotations)), 2854 ), 2855 withLabels( 2856 deployment("csv3-dep1", namespace, "sa", defaultTemplateAnnotations), 2857 addDepSpecHashLabel(t, ownerLabelFromCSV("csv3", namespace), withTemplateAnnotations(installStrategy("csv3-dep1", nil, nil), defaultTemplateAnnotations)), 2858 ), 2859 }, 2860 }, 2861 expected: expected{ 2862 csvStates: map[string]csvState{ 2863 "csv2": {exists: false, phase: v1alpha1.CSVPhaseNone}, 2864 "csv3": {exists: true, phase: v1alpha1.CSVPhaseSucceeded}, 2865 }, 2866 }, 2867 }, 2868 { 2869 name: "SingleCSVNoneToFailed/InterOperatorGroupOwnerConflict", 2870 config: operatorConfig{apiReconciler: buildFakeAPIIntersectionReconcilerThatReturns(APIConflict)}, 2871 initial: initial{ 2872 csvs: []*v1alpha1.ClusterServiceVersion{ 2873 csvWithAnnotations(csv("csv1", 2874 namespace, 2875 "0.0.0", 2876 "", 2877 installStrategy("csv1-dep1", nil, nil), 2878 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2879 []*apiextensionsv1.CustomResourceDefinition{}, 2880 v1alpha1.CSVPhaseNone, 2881 ), defaultTemplateAnnotations), 2882 }, 2883 clientObjs: []runtime.Object{defaultOperatorGroup}, 2884 }, 2885 expected: expected{ 2886 csvStates: map[string]csvState{ 2887 "csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonInterOperatorGroupOwnerConflict}, 2888 }, 2889 }, 2890 }, 2891 { 2892 name: "SingleCSVNoneToNone/AddAPIs", 2893 config: operatorConfig{apiReconciler: buildFakeAPIIntersectionReconcilerThatReturns(AddAPIs)}, 2894 initial: initial{ 2895 csvs: []*v1alpha1.ClusterServiceVersion{ 2896 csvWithAnnotations(csv("csv1", 2897 namespace, 2898 "0.0.0", 2899 "", 2900 installStrategy("csv1-dep1", nil, nil), 2901 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2902 []*apiextensionsv1.CustomResourceDefinition{}, 2903 v1alpha1.CSVPhaseNone, 2904 ), defaultTemplateAnnotations), 2905 }, 2906 clientObjs: []runtime.Object{defaultOperatorGroup}, 2907 }, 2908 expected: expected{ 2909 csvStates: map[string]csvState{ 2910 "csv1": {exists: true, phase: v1alpha1.CSVPhaseNone}, 2911 }, 2912 }, 2913 }, 2914 { 2915 name: "SingleCSVNoneToNone/RemoveAPIs", 2916 config: operatorConfig{apiReconciler: buildFakeAPIIntersectionReconcilerThatReturns(RemoveAPIs)}, 2917 initial: initial{ 2918 csvs: []*v1alpha1.ClusterServiceVersion{ 2919 csvWithAnnotations(csv("csv1", 2920 namespace, 2921 "0.0.0", 2922 "", 2923 installStrategy("csv1-dep1", nil, nil), 2924 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2925 []*apiextensionsv1.CustomResourceDefinition{}, 2926 v1alpha1.CSVPhaseNone, 2927 ), defaultTemplateAnnotations), 2928 }, 2929 clientObjs: []runtime.Object{defaultOperatorGroup}, 2930 }, 2931 expected: expected{ 2932 csvStates: map[string]csvState{ 2933 "csv1": {exists: true, phase: v1alpha1.CSVPhaseNone}, 2934 }, 2935 }, 2936 }, 2937 { 2938 name: "SingleCSVNoneToFailed/StaticOperatorGroup/AddAPIs", 2939 config: operatorConfig{apiReconciler: buildFakeAPIIntersectionReconcilerThatReturns(AddAPIs)}, 2940 initial: initial{ 2941 csvs: []*v1alpha1.ClusterServiceVersion{ 2942 csvWithAnnotations(csv("csv1", 2943 namespace, 2944 "0.0.0", 2945 "", 2946 installStrategy("csv1-dep1", nil, nil), 2947 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2948 []*apiextensionsv1.CustomResourceDefinition{}, 2949 v1alpha1.CSVPhaseNone, 2950 ), defaultTemplateAnnotations), 2951 }, 2952 clientObjs: []runtime.Object{ 2953 func() *operatorsv1.OperatorGroup { 2954 // Make the default OperatorGroup static 2955 static := defaultOperatorGroup.DeepCopy() 2956 static.Spec.StaticProvidedAPIs = true 2957 return static 2958 }(), 2959 }, 2960 }, 2961 expected: expected{ 2962 csvStates: map[string]csvState{ 2963 "csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs}, 2964 }, 2965 }, 2966 }, 2967 { 2968 name: "SingleCSVNoneToFailed/StaticOperatorGroup/RemoveAPIs", 2969 config: operatorConfig{apiReconciler: buildFakeAPIIntersectionReconcilerThatReturns(RemoveAPIs)}, 2970 initial: initial{ 2971 csvs: []*v1alpha1.ClusterServiceVersion{ 2972 csvWithAnnotations(csv("csv1", 2973 namespace, 2974 "0.0.0", 2975 "", 2976 installStrategy("csv1-dep1", nil, nil), 2977 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 2978 []*apiextensionsv1.CustomResourceDefinition{}, 2979 v1alpha1.CSVPhaseNone, 2980 ), defaultTemplateAnnotations), 2981 }, 2982 clientObjs: []runtime.Object{ 2983 func() *operatorsv1.OperatorGroup { 2984 // Make the default OperatorGroup static 2985 static := defaultOperatorGroup.DeepCopy() 2986 static.Spec.StaticProvidedAPIs = true 2987 return static 2988 }(), 2989 }, 2990 }, 2991 expected: expected{ 2992 csvStates: map[string]csvState{ 2993 "csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs}, 2994 }, 2995 }, 2996 }, 2997 { 2998 name: "SingleCSVNoneToPending/StaticOperatorGroup/NoAPIConflict", 2999 config: operatorConfig{apiReconciler: buildFakeAPIIntersectionReconcilerThatReturns(NoAPIConflict)}, 3000 initial: initial{ 3001 csvs: []*v1alpha1.ClusterServiceVersion{ 3002 csvWithAnnotations(csv("csv1", 3003 namespace, 3004 "0.0.0", 3005 "", 3006 installStrategy("csv1-dep1", nil, nil), 3007 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 3008 []*apiextensionsv1.CustomResourceDefinition{}, 3009 v1alpha1.CSVPhaseNone, 3010 ), defaultTemplateAnnotations), 3011 }, 3012 clientObjs: []runtime.Object{ 3013 func() *operatorsv1.OperatorGroup { 3014 // Make the default OperatorGroup static 3015 static := defaultOperatorGroup.DeepCopy() 3016 static.Spec.StaticProvidedAPIs = true 3017 return static 3018 }(), 3019 }, 3020 }, 3021 expected: expected{ 3022 csvStates: map[string]csvState{ 3023 "csv1": {exists: true, phase: v1alpha1.CSVPhasePending}, 3024 }, 3025 }, 3026 }, 3027 { 3028 name: "SingleCSVFailedToPending/InterOperatorGroupOwnerConflict/NoAPIConflict", 3029 config: operatorConfig{apiReconciler: buildFakeAPIIntersectionReconcilerThatReturns(NoAPIConflict)}, 3030 initial: initial{ 3031 csvs: []*v1alpha1.ClusterServiceVersion{ 3032 csvWithAnnotations(csvWithStatusReason(csv("csv1", 3033 namespace, 3034 "0.0.0", 3035 "", 3036 installStrategy("csv1-dep1", nil, nil), 3037 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 3038 []*apiextensionsv1.CustomResourceDefinition{}, 3039 v1alpha1.CSVPhaseFailed, 3040 ), v1alpha1.CSVReasonInterOperatorGroupOwnerConflict), defaultTemplateAnnotations), 3041 }, 3042 clientObjs: []runtime.Object{defaultOperatorGroup}, 3043 }, 3044 expected: expected{ 3045 csvStates: map[string]csvState{ 3046 "csv1": {exists: true, phase: v1alpha1.CSVPhasePending}, 3047 }, 3048 }, 3049 }, 3050 { 3051 name: "SingleCSVFailedToPending/StaticOperatorGroup/CannotModifyStaticOperatorGroupProvidedAPIs/NoAPIConflict", 3052 config: operatorConfig{apiReconciler: buildFakeAPIIntersectionReconcilerThatReturns(NoAPIConflict)}, 3053 initial: initial{ 3054 csvs: []*v1alpha1.ClusterServiceVersion{ 3055 csvWithAnnotations(csvWithStatusReason(csv("csv1", 3056 namespace, 3057 "0.0.0", 3058 "", 3059 installStrategy("csv1-dep1", nil, nil), 3060 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 3061 []*apiextensionsv1.CustomResourceDefinition{}, 3062 v1alpha1.CSVPhaseFailed, 3063 ), v1alpha1.CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs), defaultTemplateAnnotations), 3064 }, 3065 clientObjs: []runtime.Object{ 3066 func() *operatorsv1.OperatorGroup { 3067 // Make the default OperatorGroup static 3068 static := defaultOperatorGroup.DeepCopy() 3069 static.Spec.StaticProvidedAPIs = true 3070 return static 3071 }(), 3072 }, 3073 }, 3074 expected: expected{ 3075 csvStates: map[string]csvState{ 3076 "csv1": {exists: true, phase: v1alpha1.CSVPhasePending}, 3077 }, 3078 }, 3079 }, 3080 { 3081 name: "SingleCSVFailedToFailed/InterOperatorGroupOwnerConflict/APIConflict", 3082 config: operatorConfig{apiReconciler: buildFakeAPIIntersectionReconcilerThatReturns(APIConflict)}, 3083 initial: initial{ 3084 csvs: []*v1alpha1.ClusterServiceVersion{ 3085 csvWithAnnotations(csvWithStatusReason(csv("csv1", 3086 namespace, 3087 "0.0.0", 3088 "", 3089 installStrategy("csv1-dep1", nil, nil), 3090 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 3091 []*apiextensionsv1.CustomResourceDefinition{}, 3092 v1alpha1.CSVPhaseFailed, 3093 ), v1alpha1.CSVReasonInterOperatorGroupOwnerConflict), defaultTemplateAnnotations), 3094 }, 3095 clientObjs: []runtime.Object{defaultOperatorGroup}, 3096 }, 3097 expected: expected{ 3098 csvStates: map[string]csvState{ 3099 "csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonInterOperatorGroupOwnerConflict}, 3100 }, 3101 }, 3102 }, 3103 { 3104 name: "SingleCSVFailedToFailed/StaticOperatorGroup/CannotModifyStaticOperatorGroupProvidedAPIs/AddAPIs", 3105 config: operatorConfig{apiReconciler: buildFakeAPIIntersectionReconcilerThatReturns(AddAPIs)}, 3106 initial: initial{ 3107 csvs: []*v1alpha1.ClusterServiceVersion{ 3108 csvWithAnnotations(csvWithStatusReason(csv("csv1", 3109 namespace, 3110 "0.0.0", 3111 "", 3112 installStrategy("csv1-dep1", nil, nil), 3113 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 3114 []*apiextensionsv1.CustomResourceDefinition{}, 3115 v1alpha1.CSVPhaseFailed, 3116 ), v1alpha1.CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs), defaultTemplateAnnotations), 3117 }, 3118 clientObjs: []runtime.Object{ 3119 func() *operatorsv1.OperatorGroup { 3120 // Make the default OperatorGroup static 3121 static := defaultOperatorGroup.DeepCopy() 3122 static.Spec.StaticProvidedAPIs = true 3123 return static 3124 }(), 3125 }, 3126 }, 3127 expected: expected{ 3128 csvStates: map[string]csvState{ 3129 "csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs}, 3130 }, 3131 }, 3132 }, 3133 { 3134 name: "SingleCSVFailedToFailed/StaticOperatorGroup/CannotModifyStaticOperatorGroupProvidedAPIs/RemoveAPIs", 3135 config: operatorConfig{apiReconciler: buildFakeAPIIntersectionReconcilerThatReturns(RemoveAPIs)}, 3136 initial: initial{ 3137 csvs: []*v1alpha1.ClusterServiceVersion{ 3138 csvWithAnnotations(csvWithStatusReason(csv("csv1", 3139 namespace, 3140 "0.0.0", 3141 "", 3142 installStrategy("csv1-dep1", nil, nil), 3143 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 3144 []*apiextensionsv1.CustomResourceDefinition{}, 3145 v1alpha1.CSVPhaseFailed, 3146 ), v1alpha1.CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs), defaultTemplateAnnotations), 3147 }, 3148 clientObjs: []runtime.Object{ 3149 func() *operatorsv1.OperatorGroup { 3150 // Make the default OperatorGroup static 3151 static := defaultOperatorGroup.DeepCopy() 3152 static.Spec.StaticProvidedAPIs = true 3153 return static 3154 }(), 3155 }, 3156 }, 3157 expected: expected{ 3158 csvStates: map[string]csvState{ 3159 "csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonCannotModifyStaticOperatorGroupProvidedAPIs}, 3160 }, 3161 }, 3162 }, 3163 } 3164 for _, tt := range tests { 3165 t.Run(tt.name, func(t *testing.T) { 3166 // Create test operator 3167 ctx, cancel := context.WithCancel(context.TODO()) 3168 defer cancel() 3169 clientObjects := tt.initial.clientObjs 3170 var partials []runtime.Object 3171 for _, csv := range tt.initial.csvs { 3172 clientObjects = append(clientObjects, csv) 3173 partials = append(partials, &metav1.PartialObjectMetadata{ 3174 ObjectMeta: csv.ObjectMeta, 3175 }) 3176 } 3177 op, err := NewFakeOperator( 3178 ctx, 3179 withNamespaces(namespace, "kube-system"), 3180 withClientObjs(clientObjects...), 3181 withK8sObjs(tt.initial.objs...), 3182 withExtObjs(tt.initial.crds...), 3183 withRegObjs(tt.initial.apis...), 3184 withPartialMetadata(partials...), 3185 withOperatorNamespace(namespace), 3186 withAPIReconciler(tt.config.apiReconciler), 3187 withAPILabeler(tt.config.apiLabeler), 3188 ) 3189 require.NoError(t, err) 3190 3191 // run csv sync for each CSV 3192 for _, csv := range tt.initial.csvs { 3193 err := op.syncClusterServiceVersion(csv) 3194 expectedErr := tt.expected.err[csv.Name] 3195 require.Equal(t, expectedErr, err) 3196 } 3197 3198 // get csvs in the cluster 3199 outCSVMap := map[string]*v1alpha1.ClusterServiceVersion{} 3200 outCSVs, err := op.client.OperatorsV1alpha1().ClusterServiceVersions(namespace).List(context.TODO(), metav1.ListOptions{}) 3201 require.NoError(t, err) 3202 for _, csv := range outCSVs.Items { 3203 outCSVMap[csv.GetName()] = csv.DeepCopy() 3204 } 3205 3206 // verify expectations of csvs in cluster 3207 for csvName, csvState := range tt.expected.csvStates { 3208 csv, ok := outCSVMap[csvName] 3209 require.Equal(t, ok, csvState.exists, "%s existence should be %t", csvName, csvState.exists) 3210 if csvState.exists { 3211 if csvState.reason != "" { 3212 require.EqualValues(t, string(csvState.reason), string(csv.Status.Reason), "%s had incorrect condition reason - %v", csvName, csv) 3213 } 3214 } 3215 } 3216 3217 // Verify other objects 3218 if tt.expected.objs != nil { 3219 RequireObjectsInNamespace(t, op.opClient, op.client, namespace, tt.expected.objs) 3220 } 3221 }) 3222 } 3223 } 3224 3225 // TODO: Merge the following set of tests with those defined in TestTransitionCSV 3226 // once those tests are updated to include validation against CSV phases. 3227 func TestTransitionCSVFailForward(t *testing.T) { 3228 logrus.SetLevel(logrus.DebugLevel) 3229 namespace := "ns" 3230 3231 defaultOperatorGroup := &operatorsv1.OperatorGroup{ 3232 TypeMeta: metav1.TypeMeta{ 3233 Kind: "OperatorGroup", 3234 APIVersion: operatorsv1.SchemeGroupVersion.String(), 3235 }, 3236 ObjectMeta: metav1.ObjectMeta{ 3237 Name: "default", 3238 Namespace: namespace, 3239 Annotations: map[string]string{ 3240 "olm.providedAPIs": "c1.v1.g1", 3241 }, 3242 }, 3243 Spec: operatorsv1.OperatorGroupSpec{}, 3244 Status: operatorsv1.OperatorGroupStatus{ 3245 Namespaces: []string{namespace}, 3246 }, 3247 } 3248 3249 defaultTemplateAnnotations := map[string]string{ 3250 operatorsv1.OperatorGroupTargetsAnnotationKey: namespace, 3251 operatorsv1.OperatorGroupNamespaceAnnotationKey: namespace, 3252 operatorsv1.OperatorGroupAnnotationKey: defaultOperatorGroup.GetName(), 3253 } 3254 3255 type csvState struct { 3256 exists bool 3257 phase v1alpha1.ClusterServiceVersionPhase 3258 reason v1alpha1.ConditionReason 3259 } 3260 type operatorConfig struct { 3261 apiReconciler APIIntersectionReconciler 3262 apiLabeler labeler.Labeler 3263 } 3264 type initial struct { 3265 csvs []*v1alpha1.ClusterServiceVersion 3266 clientObjs []runtime.Object 3267 crds []runtime.Object 3268 objs []runtime.Object 3269 apis []runtime.Object 3270 } 3271 type expected struct { 3272 csvStates map[string]csvState 3273 objs []runtime.Object 3274 err map[string]error 3275 } 3276 tests := []struct { 3277 name string 3278 config operatorConfig 3279 initial initial 3280 expected expected 3281 }{ 3282 { 3283 name: "FailForwardEnabled/CSV1/FailedToReplacing", 3284 initial: initial{ 3285 csvs: []*v1alpha1.ClusterServiceVersion{ 3286 csvWithAnnotations(csv("csv1", 3287 namespace, 3288 "1.0.0", 3289 "", 3290 installStrategy("csv1-dep1", nil, nil), 3291 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 3292 []*apiextensionsv1.CustomResourceDefinition{}, 3293 v1alpha1.CSVPhaseFailed, 3294 ), addAnnotations(defaultTemplateAnnotations, map[string]string{})), 3295 csv("csv2", 3296 namespace, 3297 "2.0.0", 3298 "csv1", 3299 installStrategy("csv2-dep1", nil, nil), 3300 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 3301 []*apiextensionsv1.CustomResourceDefinition{}, 3302 v1alpha1.CSVPhaseNone, 3303 ), 3304 }, 3305 clientObjs: []runtime.Object{ 3306 func() *operatorsv1.OperatorGroup { 3307 og := defaultOperatorGroup.DeepCopy() 3308 og.Spec.UpgradeStrategy = operatorsv1.UpgradeStrategyUnsafeFailForward 3309 return og 3310 }(), 3311 }, 3312 }, 3313 expected: expected{ 3314 csvStates: map[string]csvState{ 3315 "csv1": {exists: true, phase: v1alpha1.CSVPhaseReplacing}, 3316 "csv2": {exists: true, phase: v1alpha1.CSVPhaseNone}, 3317 }, 3318 }, 3319 }, 3320 { 3321 name: "FailForwardDisabled/CSV1/FailedToPending", 3322 initial: initial{ 3323 csvs: []*v1alpha1.ClusterServiceVersion{ 3324 csvWithAnnotations(csv("csv1", 3325 namespace, 3326 "1.0.0", 3327 "", 3328 installStrategy("csv1-dep1", nil, nil), 3329 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 3330 []*apiextensionsv1.CustomResourceDefinition{}, 3331 v1alpha1.CSVPhaseFailed, 3332 ), addAnnotations(defaultTemplateAnnotations, map[string]string{})), 3333 csv("csv2", 3334 namespace, 3335 "2.0.0", 3336 "csv1", 3337 installStrategy("csv2-dep1", nil, nil), 3338 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 3339 []*apiextensionsv1.CustomResourceDefinition{}, 3340 v1alpha1.CSVPhaseNone, 3341 ), 3342 }, 3343 clientObjs: []runtime.Object{ 3344 func() *operatorsv1.OperatorGroup { 3345 og := defaultOperatorGroup.DeepCopy() 3346 og.Spec.UpgradeStrategy = operatorsv1.UpgradeStrategyDefault 3347 return og 3348 }(), 3349 }, 3350 }, 3351 expected: expected{ 3352 csvStates: map[string]csvState{ 3353 "csv1": {exists: true, phase: v1alpha1.CSVPhasePending}, 3354 "csv2": {exists: true, phase: v1alpha1.CSVPhaseNone}, 3355 }, 3356 }, 3357 }, 3358 { 3359 name: "FailForwardEnabled/ReplacementChain/CSV2/FailedToReplacing", 3360 initial: initial{ 3361 csvs: []*v1alpha1.ClusterServiceVersion{ 3362 csvWithAnnotations(csv("csv1", 3363 namespace, 3364 "1.0.0", 3365 "", 3366 installStrategy("csv1-dep1", nil, nil), 3367 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 3368 []*apiextensionsv1.CustomResourceDefinition{}, 3369 v1alpha1.CSVPhaseReplacing, 3370 ), addAnnotations(defaultTemplateAnnotations, map[string]string{})), 3371 csvWithAnnotations(csv("csv2", 3372 namespace, 3373 "2.0.0", 3374 "csv1", 3375 installStrategy("csv2-dep1", nil, nil), 3376 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 3377 []*apiextensionsv1.CustomResourceDefinition{}, 3378 v1alpha1.CSVPhaseFailed, 3379 ), addAnnotations(defaultTemplateAnnotations, map[string]string{})), 3380 csv("csv3", 3381 namespace, 3382 "3.0.0", 3383 "csv2", 3384 installStrategy("csv3-dep1", nil, nil), 3385 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 3386 []*apiextensionsv1.CustomResourceDefinition{}, 3387 v1alpha1.CSVPhaseNone, 3388 ), 3389 }, 3390 clientObjs: []runtime.Object{ 3391 func() *operatorsv1.OperatorGroup { 3392 og := defaultOperatorGroup.DeepCopy() 3393 og.Spec.UpgradeStrategy = operatorsv1.UpgradeStrategyUnsafeFailForward 3394 return og 3395 }(), 3396 }, 3397 }, 3398 expected: expected{ 3399 csvStates: map[string]csvState{ 3400 "csv1": {exists: true, phase: v1alpha1.CSVPhaseReplacing}, 3401 "csv2": {exists: true, phase: v1alpha1.CSVPhaseReplacing}, 3402 "csv3": {exists: true, phase: v1alpha1.CSVPhaseNone}, 3403 }, 3404 }, 3405 }, 3406 { 3407 name: "FailForwardDisabled/ReplacementChain/CSV2/FailedToPending", 3408 initial: initial{ 3409 csvs: []*v1alpha1.ClusterServiceVersion{ 3410 csvWithAnnotations(csv("csv1", 3411 namespace, 3412 "1.0.0", 3413 "", 3414 installStrategy("csv1-dep1", nil, nil), 3415 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 3416 []*apiextensionsv1.CustomResourceDefinition{}, 3417 v1alpha1.CSVPhaseReplacing, 3418 ), addAnnotations(defaultTemplateAnnotations, map[string]string{})), 3419 csvWithAnnotations(csv("csv2", 3420 namespace, 3421 "2.0.0", 3422 "csv1", 3423 installStrategy("csv2-dep1", nil, nil), 3424 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 3425 []*apiextensionsv1.CustomResourceDefinition{}, 3426 v1alpha1.CSVPhaseFailed, 3427 ), addAnnotations(defaultTemplateAnnotations, map[string]string{})), 3428 csv("csv3", 3429 namespace, 3430 "3.0.0", 3431 "csv2", 3432 installStrategy("csv3-dep1", nil, nil), 3433 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 3434 []*apiextensionsv1.CustomResourceDefinition{}, 3435 v1alpha1.CSVPhaseNone, 3436 ), 3437 }, 3438 clientObjs: []runtime.Object{ 3439 func() *operatorsv1.OperatorGroup { 3440 og := defaultOperatorGroup.DeepCopy() 3441 og.Spec.UpgradeStrategy = operatorsv1.UpgradeStrategyDefault 3442 return og 3443 }(), 3444 }, 3445 }, 3446 expected: expected{ 3447 csvStates: map[string]csvState{ 3448 "csv1": {exists: true, phase: v1alpha1.CSVPhaseReplacing}, 3449 "csv2": {exists: true, phase: v1alpha1.CSVPhasePending}, 3450 "csv3": {exists: true, phase: v1alpha1.CSVPhaseNone}, 3451 }, 3452 }, 3453 }, 3454 } 3455 for _, tt := range tests { 3456 t.Run(tt.name, func(t *testing.T) { 3457 // Create test operator 3458 ctx, cancel := context.WithCancel(context.TODO()) 3459 defer cancel() 3460 clientObjects := tt.initial.clientObjs 3461 var partials []runtime.Object 3462 for _, csv := range tt.initial.csvs { 3463 clientObjects = append(clientObjects, csv) 3464 partials = append(partials, &metav1.PartialObjectMetadata{ 3465 ObjectMeta: csv.ObjectMeta, 3466 }) 3467 } 3468 op, err := NewFakeOperator( 3469 ctx, 3470 withNamespaces(namespace, "kube-system"), 3471 withClientObjs(clientObjects...), 3472 withK8sObjs(tt.initial.objs...), 3473 withExtObjs(tt.initial.crds...), 3474 withRegObjs(tt.initial.apis...), 3475 withPartialMetadata(partials...), 3476 withOperatorNamespace(namespace), 3477 withAPIReconciler(tt.config.apiReconciler), 3478 withAPILabeler(tt.config.apiLabeler), 3479 ) 3480 require.NoError(t, err) 3481 3482 // run csv sync for each CSV 3483 for _, csv := range tt.initial.csvs { 3484 err := op.syncClusterServiceVersion(csv) 3485 expectedErr := tt.expected.err[csv.Name] 3486 require.Equal(t, expectedErr, err) 3487 } 3488 3489 // get csvs in the cluster 3490 outCSVMap := map[string]*v1alpha1.ClusterServiceVersion{} 3491 outCSVs, err := op.client.OperatorsV1alpha1().ClusterServiceVersions(namespace).List(context.TODO(), metav1.ListOptions{}) 3492 require.NoError(t, err) 3493 for _, csv := range outCSVs.Items { 3494 outCSVMap[csv.GetName()] = csv.DeepCopy() 3495 } 3496 3497 // verify expectations of csvs in cluster 3498 for csvName, csvState := range tt.expected.csvStates { 3499 csv, ok := outCSVMap[csvName] 3500 require.Equal(t, ok, csvState.exists, "%s existence should be %t", csvName, csvState.exists) 3501 if csvState.exists { 3502 if csvState.reason != "" { 3503 require.EqualValues(t, string(csvState.reason), string(csv.Status.Reason), "%s had incorrect condition reason - %v", csvName, csv) 3504 } 3505 require.Equal(t, csvState.phase, csv.Status.Phase) 3506 } 3507 } 3508 3509 // Verify other objects 3510 if tt.expected.objs != nil { 3511 RequireObjectsInNamespace(t, op.opClient, op.client, namespace, tt.expected.objs) 3512 } 3513 }) 3514 } 3515 } 3516 3517 func TestWebhookCABundleRetrieval(t *testing.T) { 3518 logrus.SetLevel(logrus.DebugLevel) 3519 namespace := "ns" 3520 missingCAError := fmt.Errorf("unable to find CA") 3521 caBundle := []byte("Foo") 3522 3523 type initial struct { 3524 csvs []*v1alpha1.ClusterServiceVersion 3525 crds []runtime.Object 3526 objs []runtime.Object 3527 desc v1alpha1.WebhookDescription 3528 } 3529 type expected struct { 3530 caBundle []byte 3531 err error 3532 } 3533 tests := []struct { 3534 name string 3535 initial initial 3536 expected expected 3537 }{ 3538 { 3539 name: "MissingCAResource", 3540 initial: initial{ 3541 csvs: []*v1alpha1.ClusterServiceVersion{ 3542 csv("csv1", 3543 namespace, 3544 "0.0.0", 3545 "", 3546 installStrategy("csv1-dep1", 3547 nil, 3548 []v1alpha1.StrategyDeploymentPermissions{}, 3549 ), 3550 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 3551 []*apiextensionsv1.CustomResourceDefinition{}, 3552 v1alpha1.CSVPhaseInstalling, 3553 ), 3554 }, 3555 desc: v1alpha1.WebhookDescription{ 3556 GenerateName: "webhook", 3557 Type: v1alpha1.ValidatingAdmissionWebhook, 3558 }, 3559 }, 3560 expected: expected{ 3561 caBundle: nil, 3562 err: missingCAError, 3563 }, 3564 }, 3565 { 3566 name: "RetrieveCAFromConversionWebhook", 3567 initial: initial{ 3568 csvs: []*v1alpha1.ClusterServiceVersion{ 3569 csvWithConversionWebhook(csv("csv1", 3570 namespace, 3571 "0.0.0", 3572 "", 3573 installStrategy("csv1-dep1", 3574 nil, 3575 []v1alpha1.StrategyDeploymentPermissions{}, 3576 ), 3577 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 3578 []*apiextensionsv1.CustomResourceDefinition{}, 3579 v1alpha1.CSVPhaseInstalling, 3580 ), "csv1-dep1", []string{"c1.g1"}), 3581 }, 3582 crds: []runtime.Object{ 3583 crdWithConversionWebhook(crd("c1", "v1", "g1"), caBundle), 3584 }, 3585 desc: v1alpha1.WebhookDescription{ 3586 GenerateName: "webhook", 3587 Type: v1alpha1.ConversionWebhook, 3588 ConversionCRDs: []string{"c1.g1"}, 3589 }, 3590 }, 3591 expected: expected{ 3592 caBundle: caBundle, 3593 err: nil, 3594 }, 3595 }, 3596 { 3597 name: "FailToRetrieveCAFromConversionWebhook", 3598 initial: initial{ 3599 csvs: []*v1alpha1.ClusterServiceVersion{ 3600 csvWithConversionWebhook(csv("csv1", 3601 namespace, 3602 "0.0.0", 3603 "", 3604 installStrategy("csv1-dep1", 3605 nil, 3606 []v1alpha1.StrategyDeploymentPermissions{}, 3607 ), 3608 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 3609 []*apiextensionsv1.CustomResourceDefinition{}, 3610 v1alpha1.CSVPhaseInstalling, 3611 ), "csv1-dep1", []string{"c1.g1"}), 3612 }, 3613 crds: []runtime.Object{ 3614 crdWithConversionWebhook(crd("c1", "v1", "g1"), nil), 3615 }, 3616 desc: v1alpha1.WebhookDescription{ 3617 GenerateName: "webhook", 3618 Type: v1alpha1.ConversionWebhook, 3619 ConversionCRDs: []string{"c1.g1"}, 3620 }, 3621 }, 3622 expected: expected{ 3623 caBundle: nil, 3624 err: missingCAError, 3625 }, 3626 }, 3627 { 3628 name: "RetrieveFromValidatingAdmissionWebhook", 3629 initial: initial{ 3630 csvs: []*v1alpha1.ClusterServiceVersion{ 3631 csvWithValidatingAdmissionWebhook(csv("csv1", 3632 namespace, 3633 "0.0.0", 3634 "", 3635 installStrategy("csv1-dep1", 3636 nil, 3637 []v1alpha1.StrategyDeploymentPermissions{}, 3638 ), 3639 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 3640 []*apiextensionsv1.CustomResourceDefinition{}, 3641 v1alpha1.CSVPhaseInstalling, 3642 ), "csv1-dep1", []string{"c1.g1"}), 3643 }, 3644 objs: []runtime.Object{ 3645 &admissionregistrationv1.ValidatingWebhookConfiguration{ 3646 ObjectMeta: metav1.ObjectMeta{ 3647 Name: "webhook", 3648 Namespace: namespace, 3649 Labels: map[string]string{ 3650 "olm.owner": "csv1", 3651 "olm.owner.namespace": namespace, 3652 "olm.owner.kind": v1alpha1.ClusterServiceVersionKind, 3653 "olm.webhook-description-generate-name": "webhook", 3654 }, 3655 }, 3656 Webhooks: []admissionregistrationv1.ValidatingWebhook{ 3657 { 3658 Name: "Webhook", 3659 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 3660 CABundle: caBundle, 3661 }, 3662 }, 3663 }, 3664 }, 3665 }, 3666 desc: v1alpha1.WebhookDescription{ 3667 GenerateName: "webhook", 3668 Type: v1alpha1.ValidatingAdmissionWebhook, 3669 }, 3670 }, 3671 expected: expected{ 3672 caBundle: caBundle, 3673 err: nil, 3674 }, 3675 }, 3676 { 3677 name: "RetrieveFromMutatingAdmissionWebhook", 3678 initial: initial{ 3679 csvs: []*v1alpha1.ClusterServiceVersion{ 3680 csvWithMutatingAdmissionWebhook(csv("csv1", 3681 namespace, 3682 "0.0.0", 3683 "", 3684 installStrategy("csv1-dep1", 3685 nil, 3686 []v1alpha1.StrategyDeploymentPermissions{}, 3687 ), 3688 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 3689 []*apiextensionsv1.CustomResourceDefinition{}, 3690 v1alpha1.CSVPhaseInstalling, 3691 ), "csv1-dep1", []string{"c1.g1"}), 3692 }, 3693 objs: []runtime.Object{ 3694 &admissionregistrationv1.MutatingWebhookConfiguration{ 3695 ObjectMeta: metav1.ObjectMeta{ 3696 Name: "webhook", 3697 Namespace: namespace, 3698 Labels: map[string]string{ 3699 "olm.owner": "csv1", 3700 "olm.owner.namespace": namespace, 3701 "olm.owner.kind": v1alpha1.ClusterServiceVersionKind, 3702 "olm.webhook-description-generate-name": "webhook", 3703 }, 3704 }, 3705 Webhooks: []admissionregistrationv1.MutatingWebhook{ 3706 { 3707 Name: "Webhook", 3708 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 3709 CABundle: caBundle, 3710 }, 3711 }, 3712 }, 3713 }, 3714 }, 3715 desc: v1alpha1.WebhookDescription{ 3716 GenerateName: "webhook", 3717 Type: v1alpha1.MutatingAdmissionWebhook, 3718 }, 3719 }, 3720 expected: expected{ 3721 caBundle: caBundle, 3722 err: nil, 3723 }, 3724 }, 3725 } 3726 for _, tt := range tests { 3727 t.Run(tt.name, func(t *testing.T) { 3728 // Create test operator 3729 ctx, cancel := context.WithCancel(context.TODO()) 3730 defer cancel() 3731 var csvs []runtime.Object 3732 var partials []runtime.Object 3733 for _, csv := range tt.initial.csvs { 3734 csvs = append(csvs, csv) 3735 partials = append(partials, &metav1.PartialObjectMetadata{ 3736 ObjectMeta: csv.ObjectMeta, 3737 }) 3738 } 3739 op, err := NewFakeOperator( 3740 ctx, 3741 withNamespaces(namespace, "kube-system"), 3742 withClientObjs(csvs...), 3743 withK8sObjs(tt.initial.objs...), 3744 withExtObjs(tt.initial.crds...), 3745 withPartialMetadata(partials...), 3746 withOperatorNamespace(namespace), 3747 ) 3748 require.NoError(t, err) 3749 3750 // run csv sync for each CSV 3751 for _, csv := range tt.initial.csvs { 3752 caBundle, err := op.getWebhookCABundle(csv, &tt.initial.desc) 3753 require.Equal(t, tt.expected.err, err) 3754 require.Equal(t, tt.expected.caBundle, caBundle) 3755 } 3756 }) 3757 } 3758 } 3759 3760 // TestUpdates verifies that a set of expected phase transitions occur when multiple CSVs are present 3761 // and that they do not depend on sync order or event order 3762 func TestUpdates(t *testing.T) { 3763 t.Parallel() 3764 3765 // A - replacedby -> B - replacedby -> C 3766 namespace := "ns" 3767 defaultOperatorGroup := &operatorsv1.OperatorGroup{ 3768 TypeMeta: metav1.TypeMeta{ 3769 Kind: "OperatorGroup", 3770 APIVersion: operatorsv1.SchemeGroupVersion.String(), 3771 }, 3772 ObjectMeta: metav1.ObjectMeta{ 3773 Name: "default", 3774 Namespace: namespace, 3775 }, 3776 Spec: operatorsv1.OperatorGroupSpec{ 3777 TargetNamespaces: []string{namespace}, 3778 }, 3779 Status: operatorsv1.OperatorGroupStatus{ 3780 Namespaces: []string{namespace}, 3781 }, 3782 } 3783 defaultTemplateAnnotations := map[string]string{ 3784 operatorsv1.OperatorGroupTargetsAnnotationKey: namespace, 3785 operatorsv1.OperatorGroupNamespaceAnnotationKey: namespace, 3786 operatorsv1.OperatorGroupAnnotationKey: defaultOperatorGroup.GetName(), 3787 } 3788 runningOperator := []runtime.Object{ 3789 withLabels( 3790 deployment("csv1-dep1", namespace, "sa", defaultTemplateAnnotations), 3791 map[string]string{ 3792 ownerutil.OwnerKey: "csv1", 3793 ownerutil.OwnerNamespaceKey: namespace, 3794 ownerutil.OwnerKind: "ClusterServiceVersion", 3795 }, 3796 ), 3797 } 3798 3799 deleted := v1alpha1.ClusterServiceVersionPhase("deleted") 3800 deploymentName := "csv1-dep1" 3801 crd := crd("c1", "v1", "g1") 3802 a := csv("csvA", 3803 namespace, 3804 "0.0.0", 3805 "", 3806 installStrategy(deploymentName, nil, nil), 3807 []*apiextensionsv1.CustomResourceDefinition{crd}, 3808 []*apiextensionsv1.CustomResourceDefinition{}, 3809 v1alpha1.CSVPhaseNone) 3810 b := csv("csvB", 3811 namespace, 3812 "0.0.0", 3813 "csvA", 3814 installStrategy(deploymentName, nil, nil), 3815 []*apiextensionsv1.CustomResourceDefinition{crd}, 3816 []*apiextensionsv1.CustomResourceDefinition{}, 3817 v1alpha1.CSVPhaseNone) 3818 c := csv("csvC", 3819 namespace, 3820 "0.0.0", 3821 "csvB", 3822 installStrategy(deploymentName, nil, nil), 3823 []*apiextensionsv1.CustomResourceDefinition{crd}, 3824 []*apiextensionsv1.CustomResourceDefinition{}, 3825 v1alpha1.CSVPhaseNone) 3826 3827 simulateSuccessfulRollout := func(csv *v1alpha1.ClusterServiceVersion, client operatorclient.ClientInterface) { 3828 // get the deployment, which should exist 3829 dep, err := client.GetDeployment(namespace, deploymentName) 3830 require.NoError(t, err) 3831 3832 // force it healthy 3833 dep.Status.Replicas = 1 3834 dep.Status.UpdatedReplicas = 1 3835 dep.Status.AvailableReplicas = 1 3836 dep.Status.Conditions = []appsv1.DeploymentCondition{{ 3837 Type: appsv1.DeploymentAvailable, 3838 Status: corev1.ConditionTrue, 3839 }} 3840 _, err = client.KubernetesInterface().AppsV1().Deployments(namespace).UpdateStatus(context.TODO(), dep, metav1.UpdateOptions{}) 3841 require.NoError(t, err) 3842 } 3843 3844 // when csv A is in phase, X, expect B and C to be in state Y 3845 type csvPhaseKey struct { 3846 name string 3847 phase v1alpha1.ClusterServiceVersionPhase 3848 } 3849 type expectation struct { 3850 whenIn csvPhaseKey 3851 shouldBe map[string]v1alpha1.ClusterServiceVersionPhase 3852 } 3853 // for a given CSV and phase, set the expected phases of the other CSVs 3854 expected := []expectation{ 3855 { 3856 whenIn: csvPhaseKey{name: a.GetName(), phase: v1alpha1.CSVPhaseNone}, 3857 shouldBe: map[string]v1alpha1.ClusterServiceVersionPhase{ 3858 b.GetName(): v1alpha1.CSVPhaseNone, 3859 c.GetName(): v1alpha1.CSVPhaseNone, 3860 }, 3861 }, 3862 { 3863 whenIn: csvPhaseKey{name: a.GetName(), phase: v1alpha1.CSVPhasePending}, 3864 shouldBe: map[string]v1alpha1.ClusterServiceVersionPhase{ 3865 b.GetName(): v1alpha1.CSVPhasePending, 3866 c.GetName(): v1alpha1.CSVPhasePending, 3867 }, 3868 }, 3869 { 3870 whenIn: csvPhaseKey{name: a.GetName(), phase: v1alpha1.CSVPhaseInstallReady}, 3871 shouldBe: map[string]v1alpha1.ClusterServiceVersionPhase{ 3872 b.GetName(): v1alpha1.CSVPhasePending, 3873 c.GetName(): v1alpha1.CSVPhasePending, 3874 }, 3875 }, 3876 { 3877 whenIn: csvPhaseKey{name: a.GetName(), phase: v1alpha1.CSVPhaseInstalling}, 3878 shouldBe: map[string]v1alpha1.ClusterServiceVersionPhase{ 3879 b.GetName(): v1alpha1.CSVPhasePending, 3880 c.GetName(): v1alpha1.CSVPhasePending, 3881 }, 3882 }, 3883 { 3884 whenIn: csvPhaseKey{name: a.GetName(), phase: v1alpha1.CSVPhaseSucceeded}, 3885 shouldBe: map[string]v1alpha1.ClusterServiceVersionPhase{ 3886 b.GetName(): v1alpha1.CSVPhasePending, 3887 c.GetName(): v1alpha1.CSVPhasePending, 3888 }, 3889 }, 3890 { 3891 whenIn: csvPhaseKey{name: b.GetName(), phase: v1alpha1.CSVPhaseInstallReady}, 3892 shouldBe: map[string]v1alpha1.ClusterServiceVersionPhase{ 3893 a.GetName(): v1alpha1.CSVPhaseReplacing, 3894 c.GetName(): v1alpha1.CSVPhasePending, 3895 }, 3896 }, 3897 { 3898 whenIn: csvPhaseKey{name: b.GetName(), phase: v1alpha1.CSVPhaseInstalling}, 3899 shouldBe: map[string]v1alpha1.ClusterServiceVersionPhase{ 3900 a.GetName(): v1alpha1.CSVPhaseReplacing, 3901 c.GetName(): v1alpha1.CSVPhasePending, 3902 }, 3903 }, 3904 { 3905 whenIn: csvPhaseKey{name: b.GetName(), phase: v1alpha1.CSVPhaseSucceeded}, 3906 shouldBe: map[string]v1alpha1.ClusterServiceVersionPhase{ 3907 a.GetName(): v1alpha1.CSVPhaseDeleting, 3908 c.GetName(): v1alpha1.CSVPhasePending, 3909 }, 3910 }, 3911 { 3912 whenIn: csvPhaseKey{name: c.GetName(), phase: v1alpha1.CSVPhaseInstallReady}, 3913 shouldBe: map[string]v1alpha1.ClusterServiceVersionPhase{ 3914 a.GetName(): deleted, 3915 b.GetName(): v1alpha1.CSVPhaseReplacing, 3916 }, 3917 }, 3918 { 3919 whenIn: csvPhaseKey{name: c.GetName(), phase: v1alpha1.CSVPhaseInstalling}, 3920 shouldBe: map[string]v1alpha1.ClusterServiceVersionPhase{ 3921 a.GetName(): deleted, 3922 b.GetName(): v1alpha1.CSVPhaseReplacing, 3923 }, 3924 }, 3925 { 3926 whenIn: csvPhaseKey{name: c.GetName(), phase: v1alpha1.CSVPhaseSucceeded}, 3927 shouldBe: map[string]v1alpha1.ClusterServiceVersionPhase{ 3928 a.GetName(): deleted, 3929 b.GetName(): deleted, 3930 }, 3931 }, 3932 } 3933 tests := []struct { 3934 name string 3935 in []*v1alpha1.ClusterServiceVersion 3936 }{ 3937 { 3938 name: "abc", 3939 in: []*v1alpha1.ClusterServiceVersion{a, b, c}, 3940 }, 3941 { 3942 name: "acb", 3943 in: []*v1alpha1.ClusterServiceVersion{a, c, b}, 3944 }, 3945 { 3946 name: "bac", 3947 in: []*v1alpha1.ClusterServiceVersion{b, a, c}, 3948 }, 3949 { 3950 name: "bca", 3951 in: []*v1alpha1.ClusterServiceVersion{b, c, a}, 3952 }, 3953 { 3954 name: "cba", 3955 in: []*v1alpha1.ClusterServiceVersion{c, b, a}, 3956 }, 3957 { 3958 name: "cab", 3959 in: []*v1alpha1.ClusterServiceVersion{c, a, b}, 3960 }, 3961 } 3962 for _, xt := range tests { 3963 tt := xt 3964 t.Run(tt.name, func(t *testing.T) { 3965 t.Parallel() 3966 3967 // Setup fake operator 3968 ctx, cancel := context.WithCancel(context.TODO()) 3969 defer cancel() 3970 op, err := NewFakeOperator( 3971 ctx, 3972 withExtObjs(crd), 3973 withClientObjs(defaultOperatorGroup), 3974 withK8sObjs(runningOperator...), 3975 withNamespaces(namespace), 3976 ) 3977 require.NoError(t, err) 3978 3979 // helper to get the latest view of a set of CSVs from the set - we only expect no errors if not deleted 3980 fetchLatestCSVs := func(csvsToSync map[string]*v1alpha1.ClusterServiceVersion, deleted map[string]struct{}) (out map[string]*v1alpha1.ClusterServiceVersion) { 3981 out = map[string]*v1alpha1.ClusterServiceVersion{} 3982 for name := range csvsToSync { 3983 fetched, err := op.client.OperatorsV1alpha1().ClusterServiceVersions(namespace).Get(context.TODO(), name, metav1.GetOptions{}) 3984 if _, ok := deleted[name]; !ok { 3985 require.NoError(t, err) 3986 out[name] = fetched 3987 } 3988 } 3989 return out 3990 } 3991 3992 // helper to sync a set of csvs, in order, and return the latest view from the cluster 3993 syncCSVs := func(csvsToSync map[string]*v1alpha1.ClusterServiceVersion, deleted map[string]struct{}) (out map[string]*v1alpha1.ClusterServiceVersion) { 3994 for name, csv := range csvsToSync { 3995 _ = op.syncClusterServiceVersion(csv) 3996 if _, ok := deleted[name]; !ok { 3997 require.NoError(t, err) 3998 } 3999 } 4000 return fetchLatestCSVs(csvsToSync, deleted) 4001 } 4002 4003 // helper, given a set of expectations, pull out which entries we expect to have been deleted from the cluster 4004 deletedCSVs := func(shouldBe map[string]v1alpha1.ClusterServiceVersionPhase) map[string]struct{} { 4005 out := map[string]struct{}{} 4006 for name, phase := range shouldBe { 4007 if phase != deleted { 4008 continue 4009 } 4010 out[name] = struct{}{} 4011 } 4012 return out 4013 } 4014 4015 // Create input CSV set 4016 csvsToSync := map[string]*v1alpha1.ClusterServiceVersion{} 4017 for _, csv := range tt.in { 4018 _, err := op.client.OperatorsV1alpha1().ClusterServiceVersions(namespace).Create(context.TODO(), csv, metav1.CreateOptions{}) 4019 require.NoError(t, err) 4020 csvsToSync[csv.GetName()] = csv 4021 } 4022 4023 for _, e := range expected { 4024 // get the latest view from the cluster 4025 csvsToSync = fetchLatestCSVs(csvsToSync, deletedCSVs(e.shouldBe)) 4026 4027 // sync the current csv until it's reached the expected status 4028 current := csvsToSync[e.whenIn.name] 4029 4030 if current.Status.Phase == v1alpha1.CSVPhaseInstalling { 4031 simulateSuccessfulRollout(current, op.opClient) 4032 } 4033 for current.Status.Phase != e.whenIn.phase { 4034 csvsToSync = syncCSVs(csvsToSync, deletedCSVs(e.shouldBe)) 4035 current = csvsToSync[e.whenIn.name] 4036 fmt.Printf("waiting for (when) %s to be %s\n", e.whenIn.name, e.whenIn.phase) 4037 time.Sleep(1 * time.Second) 4038 } 4039 4040 // sync the other csvs until they're in the expected status 4041 for name, phase := range e.shouldBe { 4042 if phase == deleted { 4043 // todo verify deleted 4044 continue 4045 } 4046 other := csvsToSync[name] 4047 for other.Status.Phase != phase { 4048 fmt.Printf("waiting for %s to be %s\n", name, phase) 4049 _ = op.syncClusterServiceVersion(other) 4050 other, err = op.client.OperatorsV1alpha1().ClusterServiceVersions(namespace).Get(context.TODO(), name, metav1.GetOptions{}) 4051 require.NoError(t, err) 4052 } 4053 csvsToSync[name] = other 4054 } 4055 4056 for name, phase := range e.shouldBe { 4057 if phase == deleted { 4058 continue 4059 } 4060 require.Equal(t, phase, csvsToSync[name].Status.Phase) 4061 } 4062 } 4063 }) 4064 } 4065 } 4066 4067 type tDotLogWriter struct { 4068 *testing.T 4069 } 4070 4071 func (w tDotLogWriter) Write(p []byte) (int, error) { 4072 w.T.Logf("%s", string(p)) 4073 return len(p), nil 4074 } 4075 4076 func testLogrusLogger(t *testing.T) *logrus.Logger { 4077 l := logrus.New() 4078 l.SetOutput(tDotLogWriter{t}) 4079 return l 4080 } 4081 4082 func TestSyncNamespace(t *testing.T) { 4083 namespace := func(name string, labels map[string]string) corev1.Namespace { 4084 return corev1.Namespace{ 4085 ObjectMeta: metav1.ObjectMeta{ 4086 Name: name, 4087 Labels: labels, 4088 }, 4089 } 4090 } 4091 4092 operatorgroup := func(name string, targets []string) operatorsv1.OperatorGroup { 4093 return operatorsv1.OperatorGroup{ 4094 ObjectMeta: metav1.ObjectMeta{ 4095 Name: name, 4096 UID: types.UID(fmt.Sprintf("%s-uid", name)), 4097 }, 4098 Status: operatorsv1.OperatorGroupStatus{ 4099 Namespaces: targets, 4100 }, 4101 } 4102 } 4103 4104 for _, tc := range []struct { 4105 name string 4106 before corev1.Namespace 4107 operatorgroups []operatorsv1.OperatorGroup 4108 noop bool 4109 expected []string 4110 }{ 4111 { 4112 name: "adds missing labels", 4113 before: namespace("test-namespace", map[string]string{"unrelated": ""}), 4114 operatorgroups: []operatorsv1.OperatorGroup{ 4115 operatorgroup("test-group-1", []string{"test-namespace"}), 4116 operatorgroup("test-group-2", []string{"test-namespace"}), 4117 }, 4118 expected: []string{ 4119 "olm.operatorgroup.uid/test-group-1-uid", 4120 "olm.operatorgroup.uid/test-group-2-uid", 4121 "unrelated", 4122 }, 4123 }, 4124 { 4125 name: "removes stale labels", 4126 before: namespace("test-namespace", map[string]string{ 4127 "olm.operatorgroup.uid/test-group-1-uid": "", 4128 "olm.operatorgroup.uid/test-group-2-uid": "", 4129 }), 4130 operatorgroups: []operatorsv1.OperatorGroup{ 4131 operatorgroup("test-group-2", []string{"test-namespace"}), 4132 }, 4133 expected: []string{ 4134 "olm.operatorgroup.uid/test-group-2-uid", 4135 }, 4136 }, 4137 { 4138 name: "does not add label if namespace is not a target namespace", 4139 before: namespace("test-namespace", nil), 4140 operatorgroups: []operatorsv1.OperatorGroup{ 4141 operatorgroup("test-group-1", []string{"test-namespace"}), 4142 operatorgroup("test-group-2", []string{"not-test-namespace"}), 4143 }, 4144 expected: []string{ 4145 "olm.operatorgroup.uid/test-group-1-uid", 4146 }, 4147 }, 4148 { 4149 name: "no update if labels are in sync", 4150 before: namespace("test-namespace", map[string]string{ 4151 "olm.operatorgroup.uid/test-group-1-uid": "", 4152 "olm.operatorgroup.uid/test-group-2-uid": "", 4153 }), 4154 operatorgroups: []operatorsv1.OperatorGroup{ 4155 operatorgroup("test-group-1", []string{"test-namespace"}), 4156 operatorgroup("test-group-2", []string{"test-namespace"}), 4157 }, 4158 noop: true, 4159 expected: []string{ 4160 "olm.operatorgroup.uid/test-group-1-uid", 4161 "olm.operatorgroup.uid/test-group-2-uid", 4162 }, 4163 }, 4164 } { 4165 t.Run(tc.name, func(t *testing.T) { 4166 ctx, cancel := context.WithCancel(context.Background()) 4167 defer cancel() 4168 4169 var ogs []runtime.Object 4170 for i := range tc.operatorgroups { 4171 ogs = append(ogs, &tc.operatorgroups[i]) 4172 } 4173 4174 var actions []clienttesting.Action 4175 4176 o, err := NewFakeOperator( 4177 ctx, 4178 withClientObjs(ogs...), 4179 withK8sObjs(&tc.before), 4180 withActionLog(&actions), 4181 withLogger(testLogrusLogger(t)), 4182 ) 4183 if err != nil { 4184 t.Fatalf("setup failed: %v", err) 4185 } 4186 4187 actions = actions[:0] 4188 4189 err = o.syncNamespace(&tc.before) 4190 if err != nil { 4191 t.Fatalf("unexpected error: %v", err) 4192 } 4193 4194 if tc.noop { 4195 for _, action := range actions { 4196 if action.GetResource().Resource != "namespaces" { 4197 continue 4198 } 4199 if namer, ok := action.(interface{ GetName() string }); ok { 4200 if namer.GetName() != tc.before.Name { 4201 continue 4202 } 4203 } else if objer, ok := action.(interface{ GetObject() runtime.Object }); ok { 4204 if namer, ok := objer.GetObject().(interface{ GetName() string }); ok { 4205 if namer.GetName() != tc.before.Name { 4206 continue 4207 } 4208 } 4209 } 4210 t.Errorf("unexpected client operation: %v", action) 4211 } 4212 } 4213 4214 after, err := o.opClient.KubernetesInterface().CoreV1().Namespaces().Get(ctx, tc.before.Name, metav1.GetOptions{}) 4215 if err != nil { 4216 t.Fatalf("unexpected error: %v", err) 4217 } 4218 4219 if len(after.Labels) != len(tc.expected) { 4220 t.Errorf("expected %d labels, got %d", len(tc.expected), len(after.Labels)) 4221 } 4222 4223 for _, l := range tc.expected { 4224 if _, ok := after.Labels[l]; !ok { 4225 t.Errorf("missing expected label %q", l) 4226 } 4227 } 4228 }) 4229 } 4230 } 4231 4232 func TestSyncOperatorGroups(t *testing.T) { 4233 logrus.SetLevel(logrus.WarnLevel) 4234 clockFake := utilclocktesting.NewFakeClock(time.Date(2006, time.January, 2, 15, 4, 5, 0, time.FixedZone("MST", -7*3600))) 4235 now := metav1.NewTime(clockFake.Now().UTC()) 4236 const ( 4237 timeout = 5 * time.Second 4238 tick = 50 * time.Millisecond 4239 ) 4240 4241 operatorNamespace := "operator-ns" 4242 targetNamespace := "target-ns" 4243 4244 serviceAccount := serviceAccount("sa", operatorNamespace) 4245 4246 permissions := []v1alpha1.StrategyDeploymentPermissions{ 4247 { 4248 ServiceAccountName: serviceAccount.GetName(), 4249 Rules: []rbacv1.PolicyRule{ 4250 { 4251 Verbs: []string{"get"}, 4252 APIGroups: []string{"my.api.group"}, 4253 Resources: []string{"apis"}, 4254 }, 4255 }, 4256 }, 4257 } 4258 deploymentName := "csv1-dep1" 4259 crd := crd("c1", "v1", "fake.api.group") 4260 operatorCSV := csvWithLabels(csv("csv1", 4261 operatorNamespace, 4262 "0.0.0", 4263 "", 4264 installStrategy(deploymentName, permissions, nil), 4265 []*apiextensionsv1.CustomResourceDefinition{crd}, 4266 []*apiextensionsv1.CustomResourceDefinition{}, 4267 v1alpha1.CSVPhaseNone, 4268 ), labels.Set{APILabelKeyPrefix + "9f4c46c37bdff8d0": "provided"}) 4269 4270 operatorCSV.Spec.InstallStrategy.StrategySpec.DeploymentSpecs[0].Spec.Template.Spec.Containers[0].Env = []corev1.EnvVar{{ 4271 Name: "OPERATOR_CONDITION_NAME", 4272 Value: operatorCSV.GetName(), 4273 }} 4274 4275 serverVersion := version.Get().String() 4276 // after state transitions from operatorgroups, this is the operator csv we expect 4277 operatorCSVFinal := operatorCSV.DeepCopy() 4278 operatorCSVFinal.Status.Phase = v1alpha1.CSVPhaseSucceeded 4279 operatorCSVFinal.Status.Message = "install strategy completed with no errors" 4280 operatorCSVFinal.Status.Reason = v1alpha1.CSVReasonInstallSuccessful 4281 operatorCSVFinal.Status.LastUpdateTime = &now 4282 operatorCSVFinal.Status.LastTransitionTime = &now 4283 operatorCSVFinal.Status.RequirementStatus = []v1alpha1.RequirementStatus{ 4284 { 4285 Group: "operators.coreos.com", 4286 Version: "v1alpha1", 4287 Kind: "ClusterServiceVersion", 4288 Name: "csv1", 4289 Status: v1alpha1.RequirementStatusReasonPresent, 4290 Message: "CSV minKubeVersion (0.0.0) less than server version (" + serverVersion + ")", 4291 }, 4292 { 4293 Group: "apiextensions.k8s.io", 4294 Version: "v1", 4295 Kind: "CustomResourceDefinition", 4296 Name: crd.GetName(), 4297 Status: v1alpha1.RequirementStatusReasonPresent, 4298 Message: "CRD is present and Established condition is true", 4299 }, 4300 { 4301 Group: "", 4302 Version: "v1", 4303 Kind: "ServiceAccount", 4304 Name: serviceAccount.GetName(), 4305 Status: v1alpha1.RequirementStatusReasonPresent, 4306 Dependents: []v1alpha1.DependentStatus{ 4307 { 4308 Group: "rbac.authorization.k8s.io", 4309 Version: "v1", 4310 Kind: "PolicyRule", 4311 Status: "Satisfied", 4312 Message: "namespaced rule:{\"verbs\":[\"get\"],\"apiGroups\":[\"my.api.group\"],\"resources\":[\"apis\"]}", 4313 }, 4314 }, 4315 }, 4316 } 4317 operatorCSVFinal.Status.Conditions = []v1alpha1.ClusterServiceVersionCondition{ 4318 { 4319 Phase: v1alpha1.CSVPhasePending, 4320 Reason: v1alpha1.CSVReasonRequirementsUnknown, 4321 Message: "requirements not yet checked", 4322 LastUpdateTime: &now, 4323 LastTransitionTime: &now, 4324 }, 4325 { 4326 Phase: v1alpha1.CSVPhaseInstallReady, 4327 Reason: v1alpha1.CSVReasonRequirementsMet, 4328 Message: "all requirements found, attempting install", 4329 LastUpdateTime: &now, 4330 LastTransitionTime: &now, 4331 }, 4332 { 4333 Phase: v1alpha1.CSVPhaseInstalling, 4334 Reason: v1alpha1.CSVReasonInstallSuccessful, 4335 Message: "waiting for install components to report healthy", 4336 LastUpdateTime: &now, 4337 LastTransitionTime: &now, 4338 }, 4339 { 4340 Phase: v1alpha1.CSVPhaseSucceeded, 4341 Reason: v1alpha1.CSVReasonInstallSuccessful, 4342 Message: "install strategy completed with no errors", 4343 LastUpdateTime: &now, 4344 LastTransitionTime: &now, 4345 }, 4346 } 4347 4348 // Failed CSV due to operatorgroup namespace selector doesn't any existing namespaces 4349 operatorCSVFailedNoTargetNS := operatorCSV.DeepCopy() 4350 operatorCSVFailedNoTargetNS.Status.Phase = v1alpha1.CSVPhaseFailed 4351 operatorCSVFailedNoTargetNS.Status.Message = "no targetNamespaces are matched operatorgroups namespace selection" 4352 operatorCSVFailedNoTargetNS.Status.Reason = v1alpha1.CSVReasonNoTargetNamespaces 4353 operatorCSVFailedNoTargetNS.Status.LastUpdateTime = &now 4354 operatorCSVFailedNoTargetNS.Status.LastTransitionTime = &now 4355 operatorCSVFailedNoTargetNS.Status.Conditions = []v1alpha1.ClusterServiceVersionCondition{ 4356 { 4357 Phase: v1alpha1.CSVPhaseFailed, 4358 Reason: v1alpha1.CSVReasonNoTargetNamespaces, 4359 Message: "no targetNamespaces are matched operatorgroups namespace selection", 4360 LastUpdateTime: &now, 4361 LastTransitionTime: &now, 4362 }, 4363 } 4364 4365 targetCSV := operatorCSVFinal.DeepCopy() 4366 targetCSV.SetNamespace(targetNamespace) 4367 targetCSV.Status.Reason = v1alpha1.CSVReasonCopied 4368 targetCSV.Status.Message = "The operator is running in operator-ns but is managing this namespace" 4369 targetCSV.Status.LastUpdateTime = &now 4370 4371 ownerutil.AddNonBlockingOwner(serviceAccount, operatorCSV) 4372 4373 ownedDeployment := deployment(deploymentName, operatorNamespace, serviceAccount.GetName(), nil) 4374 ownedDeployment.Spec.Template.Spec.Containers[0].Env = []corev1.EnvVar{{Name: "OPERATOR_CONDITION_NAME", Value: "csv1"}} 4375 ownerutil.AddNonBlockingOwner(ownedDeployment, operatorCSV) 4376 deploymentSpec := installStrategy(deploymentName, permissions, nil).StrategySpec.DeploymentSpecs[0].Spec 4377 hash, err := hashutil.DeepHashObject(&deploymentSpec) 4378 if err != nil { 4379 t.Fatal(err) 4380 } 4381 ownedDeployment.SetLabels(map[string]string{ 4382 install.DeploymentSpecHashLabelKey: hash, 4383 }) 4384 4385 annotatedDeployment := ownedDeployment.DeepCopy() 4386 annotatedDeployment.Spec.Template.SetAnnotations(map[string]string{operatorsv1.OperatorGroupTargetsAnnotationKey: operatorNamespace + "," + targetNamespace, operatorsv1.OperatorGroupAnnotationKey: "operator-group-1", operatorsv1.OperatorGroupNamespaceAnnotationKey: operatorNamespace}) 4387 hash, err = hashutil.DeepHashObject(&annotatedDeployment.Spec) 4388 if err != nil { 4389 t.Fatal(err) 4390 } 4391 annotatedDeployment.SetLabels(map[string]string{ 4392 "olm.managed": "true", 4393 "olm.owner": "csv1", 4394 "olm.owner.namespace": "operator-ns", 4395 "olm.owner.kind": "ClusterServiceVersion", 4396 install.DeploymentSpecHashLabelKey: hash, 4397 }) 4398 4399 annotatedGlobalDeployment := ownedDeployment.DeepCopy() 4400 annotatedGlobalDeployment.Spec.Template.SetAnnotations(map[string]string{operatorsv1.OperatorGroupTargetsAnnotationKey: "", operatorsv1.OperatorGroupAnnotationKey: "operator-group-1", operatorsv1.OperatorGroupNamespaceAnnotationKey: operatorNamespace}) 4401 hash, err = hashutil.DeepHashObject(&annotatedGlobalDeployment.Spec) 4402 if err != nil { 4403 t.Fatal(err) 4404 } 4405 annotatedGlobalDeployment.SetLabels(map[string]string{ 4406 "olm.managed": "true", 4407 "olm.owner": "csv1", 4408 "olm.owner.namespace": "operator-ns", 4409 "olm.owner.kind": "ClusterServiceVersion", 4410 install.DeploymentSpecHashLabelKey: hash, 4411 }) 4412 4413 role := &rbacv1.Role{ 4414 TypeMeta: metav1.TypeMeta{ 4415 Kind: "Role", 4416 APIVersion: rbacv1.GroupName, 4417 }, 4418 ObjectMeta: metav1.ObjectMeta{ 4419 Name: "csv-role", 4420 Namespace: operatorNamespace, 4421 Labels: ownerutil.OwnerLabel(operatorCSV, v1alpha1.ClusterServiceVersionKind), 4422 OwnerReferences: []metav1.OwnerReference{ownerutil.NonBlockingOwner(operatorCSV)}, 4423 }, 4424 Rules: permissions[0].Rules, 4425 } 4426 role.Labels[install.OLMManagedLabelKey] = install.OLMManagedLabelValue 4427 4428 roleBinding := &rbacv1.RoleBinding{ 4429 TypeMeta: metav1.TypeMeta{ 4430 Kind: "RoleBinding", 4431 APIVersion: rbacv1.GroupName, 4432 }, 4433 ObjectMeta: metav1.ObjectMeta{ 4434 Name: "csv-rolebinding", 4435 Namespace: operatorNamespace, 4436 Labels: ownerutil.OwnerLabel(operatorCSV, v1alpha1.ClusterServiceVersionKind), 4437 OwnerReferences: []metav1.OwnerReference{ownerutil.NonBlockingOwner(operatorCSV)}, 4438 }, 4439 Subjects: []rbacv1.Subject{ 4440 { 4441 Kind: "ServiceAccount", 4442 APIGroup: serviceAccount.GetObjectKind().GroupVersionKind().Group, 4443 Name: serviceAccount.GetName(), 4444 Namespace: serviceAccount.GetNamespace(), 4445 }, 4446 }, 4447 RoleRef: rbacv1.RoleRef{ 4448 APIGroup: rbacv1.GroupName, 4449 Kind: role.GetObjectKind().GroupVersionKind().Kind, 4450 Name: role.GetName(), 4451 }, 4452 } 4453 roleBinding.Labels[install.OLMManagedLabelKey] = install.OLMManagedLabelValue 4454 4455 type initial struct { 4456 operatorGroup *operatorsv1.OperatorGroup 4457 csvs []*v1alpha1.ClusterServiceVersion 4458 clientObjs []runtime.Object 4459 crds []*apiextensionsv1.CustomResourceDefinition 4460 k8sObjs []runtime.Object 4461 apis []runtime.Object 4462 } 4463 type final struct { 4464 objects map[string][]runtime.Object 4465 } 4466 tests := []struct { 4467 initial initial 4468 name string 4469 expectedEqual bool 4470 expectedStatus operatorsv1.OperatorGroupStatus 4471 final final 4472 ignoreCopyError bool 4473 }{ 4474 { 4475 name: "NoMatchingNamespace/NoCSVs", 4476 expectedEqual: true, 4477 initial: initial{ 4478 operatorGroup: &operatorsv1.OperatorGroup{ 4479 ObjectMeta: metav1.ObjectMeta{ 4480 Name: "operator-group-1", 4481 Namespace: operatorNamespace, 4482 }, 4483 Spec: operatorsv1.OperatorGroupSpec{ 4484 Selector: &metav1.LabelSelector{ 4485 MatchLabels: map[string]string{"a": "app-a"}, 4486 }, 4487 }, 4488 }, 4489 k8sObjs: []runtime.Object{ 4490 &corev1.Namespace{ 4491 ObjectMeta: metav1.ObjectMeta{ 4492 Name: operatorNamespace, 4493 }, 4494 }, 4495 &corev1.Namespace{ 4496 ObjectMeta: metav1.ObjectMeta{ 4497 Name: targetNamespace, 4498 }, 4499 }, 4500 }, 4501 }, 4502 expectedStatus: operatorsv1.OperatorGroupStatus{}, 4503 }, 4504 { 4505 name: "NoMatchingNamespace/CSVPresent", 4506 expectedEqual: true, 4507 initial: initial{ 4508 operatorGroup: &operatorsv1.OperatorGroup{ 4509 ObjectMeta: metav1.ObjectMeta{ 4510 Name: "operator-group-1", 4511 Namespace: operatorNamespace, 4512 }, 4513 Spec: operatorsv1.OperatorGroupSpec{ 4514 Selector: &metav1.LabelSelector{ 4515 MatchLabels: map[string]string{"a": "app-a"}, 4516 }, 4517 }, 4518 }, 4519 clientObjs: []runtime.Object{operatorCSV}, 4520 k8sObjs: []runtime.Object{ 4521 &corev1.Namespace{ 4522 ObjectMeta: metav1.ObjectMeta{ 4523 Name: operatorNamespace, 4524 }, 4525 }, 4526 &corev1.Namespace{ 4527 ObjectMeta: metav1.ObjectMeta{ 4528 Name: targetNamespace, 4529 }, 4530 }, 4531 ownedDeployment, 4532 serviceAccount, 4533 role, 4534 roleBinding, 4535 }, 4536 crds: []*apiextensionsv1.CustomResourceDefinition{crd}, 4537 }, 4538 expectedStatus: operatorsv1.OperatorGroupStatus{}, 4539 final: final{objects: map[string][]runtime.Object{ 4540 operatorNamespace: { 4541 withAnnotations(operatorCSVFailedNoTargetNS.DeepCopy(), map[string]string{operatorsv1.OperatorGroupAnnotationKey: "operator-group-1", operatorsv1.OperatorGroupNamespaceAnnotationKey: operatorNamespace}), 4542 }, 4543 }}, 4544 ignoreCopyError: true, 4545 }, 4546 { 4547 name: "MatchingNamespace/NoCSVs", 4548 expectedEqual: true, 4549 initial: initial{ 4550 operatorGroup: &operatorsv1.OperatorGroup{ 4551 ObjectMeta: metav1.ObjectMeta{ 4552 Name: "operator-group-1", 4553 Namespace: operatorNamespace, 4554 }, 4555 Spec: operatorsv1.OperatorGroupSpec{ 4556 Selector: &metav1.LabelSelector{ 4557 MatchLabels: map[string]string{"app": "app-a"}, 4558 }, 4559 }, 4560 }, 4561 k8sObjs: []runtime.Object{ 4562 &corev1.Namespace{ 4563 ObjectMeta: metav1.ObjectMeta{ 4564 Name: operatorNamespace, 4565 }, 4566 }, 4567 &corev1.Namespace{ 4568 ObjectMeta: metav1.ObjectMeta{ 4569 Name: targetNamespace, 4570 Labels: map[string]string{"app": "app-a"}, 4571 }, 4572 }, 4573 }, 4574 }, 4575 expectedStatus: operatorsv1.OperatorGroupStatus{ 4576 Namespaces: []string{targetNamespace}, 4577 LastUpdated: &now, 4578 }, 4579 }, 4580 { 4581 name: "MatchingNamespace/NoCSVs/CreatesClusterRoles", 4582 expectedEqual: true, 4583 initial: initial{ 4584 operatorGroup: &operatorsv1.OperatorGroup{ 4585 ObjectMeta: metav1.ObjectMeta{ 4586 Name: "operator-group-1", 4587 Namespace: operatorNamespace, Labels: map[string]string{"app": "app-a"}, 4588 }, 4589 Spec: operatorsv1.OperatorGroupSpec{ 4590 Selector: &metav1.LabelSelector{ 4591 MatchLabels: map[string]string{"app": "app-a"}, 4592 }, 4593 }, 4594 }, 4595 k8sObjs: []runtime.Object{ 4596 &corev1.Namespace{ 4597 ObjectMeta: metav1.ObjectMeta{ 4598 Name: operatorNamespace, 4599 }, 4600 }, 4601 &corev1.Namespace{ 4602 ObjectMeta: metav1.ObjectMeta{ 4603 Name: targetNamespace, 4604 Labels: map[string]string{"app": "app-a"}, 4605 }, 4606 }, 4607 }, 4608 }, 4609 expectedStatus: operatorsv1.OperatorGroupStatus{ 4610 Namespaces: []string{targetNamespace}, 4611 LastUpdated: &now, 4612 }, 4613 final: final{objects: map[string][]runtime.Object{ 4614 "": { 4615 &rbacv1.ClusterRole{ 4616 ObjectMeta: metav1.ObjectMeta{ 4617 Name: "olm.og.operator-group-1.admin-8rdAjL0E35JMMAkOqYmoorzjpIIihfnj3DcgDU", 4618 Labels: map[string]string{ 4619 "olm.managed": "true", 4620 "olm.owner": "operator-group-1", 4621 "olm.owner.namespace": "operator-ns", 4622 "olm.owner.kind": "OperatorGroup", 4623 }, 4624 }, 4625 }, 4626 &rbacv1.ClusterRole{ 4627 ObjectMeta: metav1.ObjectMeta{ 4628 Name: "olm.og.operator-group-1.edit-9lBEUxqAYE7CX7wZfFEPYutTfQTo43WarB08od", 4629 Labels: map[string]string{ 4630 "olm.managed": "true", 4631 "olm.owner": "operator-group-1", 4632 "olm.owner.namespace": "operator-ns", 4633 "olm.owner.kind": "OperatorGroup", 4634 }, 4635 }, 4636 }, 4637 &rbacv1.ClusterRole{ 4638 ObjectMeta: metav1.ObjectMeta{ 4639 Name: "olm.og.operator-group-1.view-1l6ymczPK5SceF4d0DCtAnWZuvmKn6s8oBUxHr", 4640 Labels: map[string]string{ 4641 "olm.managed": "true", 4642 "olm.owner": "operator-group-1", 4643 "olm.owner.namespace": "operator-ns", 4644 "olm.owner.kind": "OperatorGroup", 4645 }, 4646 }, 4647 }, 4648 }, 4649 }}, 4650 }, 4651 { 4652 // check that even if cluster roles exist without the naming convention, we create the new ones and leave the old ones unchanged 4653 name: "MatchingNamespace/NoCSVs/KeepOldClusterRoles", 4654 expectedEqual: true, 4655 initial: initial{ 4656 operatorGroup: &operatorsv1.OperatorGroup{ 4657 ObjectMeta: metav1.ObjectMeta{ 4658 Name: "operator-group-1", 4659 Namespace: operatorNamespace, 4660 }, 4661 Spec: operatorsv1.OperatorGroupSpec{ 4662 Selector: &metav1.LabelSelector{ 4663 MatchLabels: map[string]string{"app": "app-a"}, 4664 }, 4665 }, 4666 }, 4667 k8sObjs: []runtime.Object{ 4668 &corev1.Namespace{ 4669 ObjectMeta: metav1.ObjectMeta{ 4670 Name: operatorNamespace, 4671 }, 4672 }, 4673 &corev1.Namespace{ 4674 ObjectMeta: metav1.ObjectMeta{ 4675 Name: targetNamespace, 4676 Labels: map[string]string{"app": "app-a"}, 4677 }, 4678 }, 4679 &rbacv1.ClusterRole{ 4680 ObjectMeta: metav1.ObjectMeta{ 4681 Name: "operator-group-1-admin", 4682 Labels: map[string]string{ 4683 "olm.managed": "true", 4684 "olm.owner": "operator-group-1", 4685 "olm.owner.namespace": "operator-ns", 4686 "olm.owner.kind": "OperatorGroup", 4687 }, 4688 }, 4689 }, 4690 &rbacv1.ClusterRole{ 4691 ObjectMeta: metav1.ObjectMeta{ 4692 Name: "operator-group-1-view", 4693 Labels: map[string]string{ 4694 "olm.managed": "true", 4695 "olm.owner": "operator-group-1", 4696 "olm.owner.namespace": "operator-ns", 4697 "olm.owner.kind": "OperatorGroup", 4698 }, 4699 }, 4700 }, 4701 &rbacv1.ClusterRole{ 4702 ObjectMeta: metav1.ObjectMeta{ 4703 Name: "operator-group-1-edit", 4704 Labels: map[string]string{ 4705 "olm.managed": "true", 4706 "olm.owner": "operator-group-1", 4707 "olm.owner.namespace": "operator-ns", 4708 "olm.owner.kind": "OperatorGroup", 4709 }, 4710 }, 4711 }, 4712 }, 4713 }, 4714 expectedStatus: operatorsv1.OperatorGroupStatus{ 4715 Namespaces: []string{targetNamespace}, 4716 LastUpdated: &now, 4717 }, 4718 final: final{objects: map[string][]runtime.Object{ 4719 "": { 4720 &rbacv1.ClusterRole{ 4721 ObjectMeta: metav1.ObjectMeta{ 4722 Name: "olm.og.operator-group-1.admin-8rdAjL0E35JMMAkOqYmoorzjpIIihfnj3DcgDU", 4723 Labels: map[string]string{ 4724 "olm.managed": "true", 4725 "olm.owner": "operator-group-1", 4726 "olm.owner.namespace": "operator-ns", 4727 "olm.owner.kind": "OperatorGroup", 4728 }, 4729 }, 4730 }, 4731 &rbacv1.ClusterRole{ 4732 ObjectMeta: metav1.ObjectMeta{ 4733 Name: "olm.og.operator-group-1.edit-9lBEUxqAYE7CX7wZfFEPYutTfQTo43WarB08od", 4734 Labels: map[string]string{ 4735 "olm.managed": "true", 4736 "olm.owner": "operator-group-1", 4737 "olm.owner.namespace": "operator-ns", 4738 "olm.owner.kind": "OperatorGroup", 4739 }, 4740 }, 4741 }, 4742 &rbacv1.ClusterRole{ 4743 ObjectMeta: metav1.ObjectMeta{ 4744 Name: "olm.og.operator-group-1.view-1l6ymczPK5SceF4d0DCtAnWZuvmKn6s8oBUxHr", 4745 Labels: map[string]string{ 4746 "olm.managed": "true", 4747 "olm.owner": "operator-group-1", 4748 "olm.owner.namespace": "operator-ns", 4749 "olm.owner.kind": "OperatorGroup", 4750 }, 4751 }, 4752 }, 4753 &rbacv1.ClusterRole{ 4754 ObjectMeta: metav1.ObjectMeta{ 4755 Name: "operator-group-1-admin", 4756 Labels: map[string]string{ 4757 "olm.managed": "true", 4758 "olm.owner": "operator-group-1", 4759 "olm.owner.namespace": "operator-ns", 4760 "olm.owner.kind": "OperatorGroup", 4761 }, 4762 }, 4763 }, 4764 &rbacv1.ClusterRole{ 4765 ObjectMeta: metav1.ObjectMeta{ 4766 Name: "operator-group-1-view", 4767 Labels: map[string]string{ 4768 "olm.managed": "true", 4769 "olm.owner": "operator-group-1", 4770 "olm.owner.namespace": "operator-ns", 4771 "olm.owner.kind": "OperatorGroup", 4772 }, 4773 }, 4774 }, 4775 &rbacv1.ClusterRole{ 4776 ObjectMeta: metav1.ObjectMeta{ 4777 Name: "operator-group-1-edit", 4778 Labels: map[string]string{ 4779 "olm.managed": "true", 4780 "olm.owner": "operator-group-1", 4781 "olm.owner.namespace": "operator-ns", 4782 "olm.owner.kind": "OperatorGroup", 4783 }, 4784 }, 4785 }, 4786 }, 4787 }}, 4788 }, 4789 { 4790 // ensure that ownership labels are fixed but user labels are preserved 4791 name: "MatchingNamespace/NoCSVs/ClusterRoleOwnershipLabels", 4792 expectedEqual: true, 4793 initial: initial{ 4794 operatorGroup: &operatorsv1.OperatorGroup{ 4795 ObjectMeta: metav1.ObjectMeta{ 4796 Name: "operator-group-1", 4797 Namespace: operatorNamespace, 4798 }, 4799 Spec: operatorsv1.OperatorGroupSpec{ 4800 Selector: &metav1.LabelSelector{ 4801 MatchLabels: map[string]string{"app": "app-a"}, 4802 }, 4803 }, 4804 }, 4805 k8sObjs: []runtime.Object{ 4806 &corev1.Namespace{ 4807 ObjectMeta: metav1.ObjectMeta{ 4808 Name: operatorNamespace, 4809 }, 4810 }, 4811 &corev1.Namespace{ 4812 ObjectMeta: metav1.ObjectMeta{ 4813 Name: targetNamespace, 4814 Labels: map[string]string{"app": "app-a"}, 4815 }, 4816 }, 4817 &rbacv1.ClusterRole{ 4818 ObjectMeta: metav1.ObjectMeta{ 4819 Name: "olm.og.operator-group-1.admin-8rdAjL0E35JMMAkOqYmoorzjpIIihfnj3DcgDU", 4820 Labels: map[string]string{ 4821 "olm.managed": "true", 4822 "olm.owner": "operator-group-1", 4823 "olm.owner.namespace": "operator-ns-bob", 4824 "olm.owner.kind": "OperatorGroup", 4825 "not.an.olm.label": "true", 4826 }, 4827 }, 4828 }, 4829 &rbacv1.ClusterRole{ 4830 ObjectMeta: metav1.ObjectMeta{ 4831 Name: "olm.og.operator-group-1.view-1l6ymczPK5SceF4d0DCtAnWZuvmKn6s8oBUxHr", 4832 Labels: map[string]string{ 4833 "olm.managed": "true", 4834 "olm.owner": "operator-group-5", 4835 "olm.owner.namespace": "operator-ns", 4836 "olm.owner.kind": "OperatorGroup", 4837 "not.an.olm.label": "false", 4838 "another.olm.label": "or maybe not", 4839 }, 4840 }, 4841 }, 4842 &rbacv1.ClusterRole{ 4843 ObjectMeta: metav1.ObjectMeta{ 4844 Name: "olm.og.operator-group-1.edit-9lBEUxqAYE7CX7wZfFEPYutTfQTo43WarB08od", 4845 Labels: map[string]string{ 4846 "olm.managed": "true", 4847 "olm.owner": "operator-group-1", 4848 "olm.owner.namespace": "operator-ns", 4849 "olm.owner.kind": "OperatorGroupKind", 4850 }, 4851 }, 4852 }, 4853 }, 4854 }, 4855 expectedStatus: operatorsv1.OperatorGroupStatus{ 4856 Namespaces: []string{targetNamespace}, 4857 LastUpdated: &now, 4858 }, 4859 final: final{objects: map[string][]runtime.Object{ 4860 "": { 4861 &rbacv1.ClusterRole{ 4862 ObjectMeta: metav1.ObjectMeta{ 4863 Name: "olm.og.operator-group-1.admin-8rdAjL0E35JMMAkOqYmoorzjpIIihfnj3DcgDU", 4864 Labels: map[string]string{ 4865 "olm.managed": "true", 4866 "olm.owner": "operator-group-1", 4867 "olm.owner.namespace": "operator-ns", 4868 "olm.owner.kind": "OperatorGroup", 4869 "not.an.olm.label": "true", 4870 }, 4871 }, 4872 }, 4873 &rbacv1.ClusterRole{ 4874 ObjectMeta: metav1.ObjectMeta{ 4875 Name: "olm.og.operator-group-1.edit-9lBEUxqAYE7CX7wZfFEPYutTfQTo43WarB08od", 4876 Labels: map[string]string{ 4877 "olm.managed": "true", 4878 "olm.owner": "operator-group-1", 4879 "olm.owner.namespace": "operator-ns", 4880 "olm.owner.kind": "OperatorGroup", 4881 }, 4882 }, 4883 }, 4884 &rbacv1.ClusterRole{ 4885 ObjectMeta: metav1.ObjectMeta{ 4886 Name: "olm.og.operator-group-1.view-1l6ymczPK5SceF4d0DCtAnWZuvmKn6s8oBUxHr", 4887 Labels: map[string]string{ 4888 "olm.managed": "true", 4889 "olm.owner": "operator-group-1", 4890 "olm.owner.namespace": "operator-ns", 4891 "olm.owner.kind": "OperatorGroup", 4892 "not.an.olm.label": "false", 4893 "another.olm.label": "or maybe not", 4894 }, 4895 }, 4896 }, 4897 }, 4898 }}, 4899 }, 4900 { 4901 // if a cluster role exists with the correct name, use that 4902 name: "MatchingNamespace/NoCSVs/DoesNotUpdateClusterRoles", 4903 expectedEqual: true, 4904 initial: initial{ 4905 operatorGroup: &operatorsv1.OperatorGroup{ 4906 ObjectMeta: metav1.ObjectMeta{ 4907 Name: "operator-group-1", 4908 Namespace: operatorNamespace, 4909 }, 4910 Spec: operatorsv1.OperatorGroupSpec{ 4911 Selector: &metav1.LabelSelector{ 4912 MatchLabels: map[string]string{"app": "app-a"}, 4913 }, 4914 }, 4915 }, 4916 k8sObjs: []runtime.Object{ 4917 &corev1.Namespace{ 4918 ObjectMeta: metav1.ObjectMeta{ 4919 Name: operatorNamespace, 4920 }, 4921 }, 4922 &corev1.Namespace{ 4923 ObjectMeta: metav1.ObjectMeta{ 4924 Name: targetNamespace, 4925 Labels: map[string]string{"app": "app-a"}, 4926 }, 4927 }, 4928 &rbacv1.ClusterRole{ 4929 ObjectMeta: metav1.ObjectMeta{ 4930 Name: "olm.og.operator-group-1.admin-8rdAjL0E35JMMAkOqYmoorzjpIIihfnj3DcgDU", 4931 Labels: map[string]string{ 4932 "olm.managed": "true", 4933 "olm.owner": "operator-group-1", 4934 "olm.owner.namespace": "operator-ns", 4935 "olm.owner.kind": "OperatorGroup", 4936 }, 4937 }, 4938 }, 4939 &rbacv1.ClusterRole{ 4940 ObjectMeta: metav1.ObjectMeta{ 4941 Name: "olm.og.operator-group-1.edit-9lBEUxqAYE7CX7wZfFEPYutTfQTo43WarB08od", 4942 Labels: map[string]string{ 4943 "olm.managed": "true", 4944 "olm.owner": "operator-group-1", 4945 "olm.owner.namespace": "operator-ns", 4946 "olm.owner.kind": "OperatorGroup", 4947 }, 4948 }, 4949 }, 4950 &rbacv1.ClusterRole{ 4951 ObjectMeta: metav1.ObjectMeta{ 4952 Name: "olm.og.operator-group-1.view-1l6ymczPK5SceF4d0DCtAnWZuvmKn6s8oBUxHr", 4953 Labels: map[string]string{ 4954 "olm.managed": "true", 4955 "olm.owner": "operator-group-1", 4956 "olm.owner.namespace": "operator-ns", 4957 "olm.owner.kind": "OperatorGroup", 4958 }, 4959 }, 4960 }}, 4961 }, 4962 expectedStatus: operatorsv1.OperatorGroupStatus{ 4963 Namespaces: []string{targetNamespace}, 4964 LastUpdated: &now, 4965 }, 4966 final: final{objects: map[string][]runtime.Object{ 4967 "": { 4968 &rbacv1.ClusterRole{ 4969 ObjectMeta: metav1.ObjectMeta{ 4970 Name: "olm.og.operator-group-1.admin-8rdAjL0E35JMMAkOqYmoorzjpIIihfnj3DcgDU", 4971 Labels: map[string]string{ 4972 "olm.managed": "true", 4973 "olm.owner": "operator-group-1", 4974 "olm.owner.namespace": "operator-ns", 4975 "olm.owner.kind": "OperatorGroup", 4976 }, 4977 }, 4978 }, 4979 &rbacv1.ClusterRole{ 4980 ObjectMeta: metav1.ObjectMeta{ 4981 Name: "olm.og.operator-group-1.edit-9lBEUxqAYE7CX7wZfFEPYutTfQTo43WarB08od", 4982 Labels: map[string]string{ 4983 "olm.managed": "true", 4984 "olm.owner": "operator-group-1", 4985 "olm.owner.namespace": "operator-ns", 4986 "olm.owner.kind": "OperatorGroup", 4987 }, 4988 }, 4989 }, 4990 &rbacv1.ClusterRole{ 4991 ObjectMeta: metav1.ObjectMeta{ 4992 Name: "olm.og.operator-group-1.view-1l6ymczPK5SceF4d0DCtAnWZuvmKn6s8oBUxHr", 4993 Labels: map[string]string{ 4994 "olm.managed": "true", 4995 "olm.owner": "operator-group-1", 4996 "olm.owner.namespace": "operator-ns", 4997 "olm.owner.kind": "OperatorGroup", 4998 }, 4999 }, 5000 }, 5001 }, 5002 }, 5003 }, 5004 }, 5005 { 5006 name: "MatchingNamespace/CSVPresent/Found", 5007 expectedEqual: true, 5008 initial: initial{ 5009 operatorGroup: &operatorsv1.OperatorGroup{ 5010 ObjectMeta: metav1.ObjectMeta{ 5011 Name: "operator-group-1", 5012 Namespace: operatorNamespace, 5013 }, 5014 Spec: operatorsv1.OperatorGroupSpec{ 5015 Selector: &metav1.LabelSelector{ 5016 MatchLabels: map[string]string{"app": "app-a"}, 5017 }, 5018 }, 5019 }, 5020 clientObjs: []runtime.Object{}, 5021 csvs: []*v1alpha1.ClusterServiceVersion{operatorCSV}, 5022 k8sObjs: []runtime.Object{ 5023 &corev1.Namespace{ 5024 ObjectMeta: metav1.ObjectMeta{ 5025 Name: operatorNamespace, 5026 Labels: map[string]string{"app": "app-a"}, 5027 }, 5028 }, 5029 &corev1.Namespace{ 5030 ObjectMeta: metav1.ObjectMeta{ 5031 Name: targetNamespace, 5032 Labels: map[string]string{"app": "app-a"}, 5033 }, 5034 }, 5035 ownedDeployment, 5036 serviceAccount, 5037 role, 5038 roleBinding, 5039 }, 5040 crds: []*apiextensionsv1.CustomResourceDefinition{crd}, 5041 }, 5042 expectedStatus: operatorsv1.OperatorGroupStatus{ 5043 Namespaces: []string{operatorNamespace, targetNamespace}, 5044 LastUpdated: &now, 5045 }, 5046 final: final{objects: map[string][]runtime.Object{ 5047 operatorNamespace: { 5048 withAnnotations(operatorCSVFinal.DeepCopy(), map[string]string{operatorsv1.OperatorGroupTargetsAnnotationKey: operatorNamespace + "," + targetNamespace, operatorsv1.OperatorGroupAnnotationKey: "operator-group-1", operatorsv1.OperatorGroupNamespaceAnnotationKey: operatorNamespace}), 5049 annotatedDeployment, 5050 }, 5051 targetNamespace: { 5052 withLabels( 5053 withAnnotations(targetCSV.DeepCopy(), map[string]string{operatorsv1.OperatorGroupAnnotationKey: "operator-group-1", operatorsv1.OperatorGroupNamespaceAnnotationKey: operatorNamespace}), 5054 labels.Merge(targetCSV.GetLabels(), map[string]string{v1alpha1.CopiedLabelKey: operatorNamespace}), 5055 ), 5056 &rbacv1.Role{ 5057 TypeMeta: metav1.TypeMeta{ 5058 Kind: "Role", 5059 APIVersion: rbacv1.GroupName, 5060 }, 5061 ObjectMeta: metav1.ObjectMeta{ 5062 ResourceVersion: "0", 5063 Name: "csv-role", 5064 Namespace: targetNamespace, 5065 Labels: map[string]string{ 5066 "olm.managed": "true", 5067 "olm.copiedFrom": "operator-ns", 5068 "olm.owner": "csv1", 5069 "olm.owner.namespace": "target-ns", 5070 "olm.owner.kind": "ClusterServiceVersion", 5071 }, 5072 OwnerReferences: []metav1.OwnerReference{ 5073 ownerutil.NonBlockingOwner(targetCSV), 5074 }, 5075 }, 5076 Rules: permissions[0].Rules, 5077 }, 5078 &rbacv1.RoleBinding{ 5079 TypeMeta: metav1.TypeMeta{ 5080 Kind: "RoleBinding", 5081 APIVersion: rbacv1.GroupName, 5082 }, 5083 ObjectMeta: metav1.ObjectMeta{ 5084 ResourceVersion: "0", 5085 Name: "csv-rolebinding", 5086 Namespace: targetNamespace, 5087 Labels: map[string]string{ 5088 "olm.managed": "true", 5089 "olm.copiedFrom": "operator-ns", 5090 "olm.owner": "csv1", 5091 "olm.owner.namespace": "target-ns", 5092 "olm.owner.kind": "ClusterServiceVersion", 5093 }, 5094 OwnerReferences: []metav1.OwnerReference{ 5095 ownerutil.NonBlockingOwner(targetCSV), 5096 }, 5097 }, 5098 Subjects: []rbacv1.Subject{ 5099 { 5100 Kind: rbacv1.ServiceAccountKind, 5101 Name: serviceAccount.GetName(), 5102 Namespace: operatorNamespace, 5103 }, 5104 }, 5105 RoleRef: rbacv1.RoleRef{ 5106 APIGroup: rbacv1.GroupName, 5107 Kind: role.GroupVersionKind().Kind, 5108 Name: "csv-role", 5109 }, 5110 }, 5111 }, 5112 }}, 5113 }, 5114 { 5115 name: "MatchingNamespace/CSVPresent/Found/ExplicitTargetNamespaces", 5116 expectedEqual: true, 5117 initial: initial{ 5118 operatorGroup: &operatorsv1.OperatorGroup{ 5119 ObjectMeta: metav1.ObjectMeta{ 5120 Name: "operator-group-1", 5121 Namespace: operatorNamespace, 5122 }, 5123 Spec: operatorsv1.OperatorGroupSpec{ 5124 TargetNamespaces: []string{operatorNamespace, targetNamespace}, 5125 }, 5126 }, 5127 clientObjs: []runtime.Object{}, 5128 csvs: []*v1alpha1.ClusterServiceVersion{operatorCSV}, 5129 k8sObjs: []runtime.Object{ 5130 &corev1.Namespace{ 5131 ObjectMeta: metav1.ObjectMeta{ 5132 Name: operatorNamespace, 5133 }, 5134 }, 5135 &corev1.Namespace{ 5136 ObjectMeta: metav1.ObjectMeta{ 5137 Name: targetNamespace, 5138 }, 5139 }, 5140 ownedDeployment, 5141 serviceAccount, 5142 role, 5143 roleBinding, 5144 }, 5145 crds: []*apiextensionsv1.CustomResourceDefinition{crd}, 5146 }, 5147 expectedStatus: operatorsv1.OperatorGroupStatus{ 5148 Namespaces: []string{operatorNamespace, targetNamespace}, 5149 LastUpdated: &now, 5150 }, 5151 final: final{objects: map[string][]runtime.Object{ 5152 operatorNamespace: { 5153 withAnnotations(operatorCSVFinal.DeepCopy(), map[string]string{operatorsv1.OperatorGroupTargetsAnnotationKey: operatorNamespace + "," + targetNamespace, operatorsv1.OperatorGroupAnnotationKey: "operator-group-1", operatorsv1.OperatorGroupNamespaceAnnotationKey: operatorNamespace}), 5154 annotatedDeployment, 5155 }, 5156 targetNamespace: { 5157 withLabels( 5158 withAnnotations(targetCSV.DeepCopy(), map[string]string{operatorsv1.OperatorGroupAnnotationKey: "operator-group-1", operatorsv1.OperatorGroupNamespaceAnnotationKey: operatorNamespace}), 5159 labels.Merge(targetCSV.GetLabels(), map[string]string{v1alpha1.CopiedLabelKey: operatorNamespace}), 5160 ), 5161 &rbacv1.Role{ 5162 TypeMeta: metav1.TypeMeta{ 5163 Kind: "Role", 5164 APIVersion: rbacv1.GroupName, 5165 }, 5166 ObjectMeta: metav1.ObjectMeta{ 5167 ResourceVersion: "0", 5168 Name: "csv-role", 5169 Namespace: targetNamespace, 5170 Labels: map[string]string{ 5171 "olm.managed": "true", 5172 "olm.copiedFrom": "operator-ns", 5173 "olm.owner": "csv1", 5174 "olm.owner.namespace": "target-ns", 5175 "olm.owner.kind": "ClusterServiceVersion", 5176 }, 5177 OwnerReferences: []metav1.OwnerReference{ 5178 ownerutil.NonBlockingOwner(targetCSV), 5179 }, 5180 }, 5181 Rules: permissions[0].Rules, 5182 }, 5183 &rbacv1.RoleBinding{ 5184 TypeMeta: metav1.TypeMeta{ 5185 Kind: "RoleBinding", 5186 APIVersion: rbacv1.GroupName, 5187 }, 5188 ObjectMeta: metav1.ObjectMeta{ 5189 ResourceVersion: "0", 5190 Name: "csv-rolebinding", 5191 Namespace: targetNamespace, 5192 Labels: map[string]string{ 5193 "olm.managed": "true", 5194 "olm.copiedFrom": "operator-ns", 5195 "olm.owner": "csv1", 5196 "olm.owner.namespace": "target-ns", 5197 "olm.owner.kind": "ClusterServiceVersion", 5198 }, 5199 OwnerReferences: []metav1.OwnerReference{ 5200 ownerutil.NonBlockingOwner(targetCSV), 5201 }, 5202 }, 5203 Subjects: []rbacv1.Subject{ 5204 { 5205 Kind: rbacv1.ServiceAccountKind, 5206 Name: serviceAccount.GetName(), 5207 Namespace: operatorNamespace, 5208 }, 5209 }, 5210 RoleRef: rbacv1.RoleRef{ 5211 APIGroup: rbacv1.GroupName, 5212 Kind: role.GroupVersionKind().Kind, 5213 Name: "csv-role", 5214 }, 5215 }, 5216 }, 5217 }}, 5218 }, 5219 { 5220 name: "AllNamespaces/CSVPresent/Found", 5221 expectedEqual: true, 5222 initial: initial{ 5223 operatorGroup: &operatorsv1.OperatorGroup{ 5224 ObjectMeta: metav1.ObjectMeta{ 5225 Name: "operator-group-1", 5226 Namespace: operatorNamespace, 5227 Labels: map[string]string{"app": "app-a"}, 5228 }, 5229 Spec: operatorsv1.OperatorGroupSpec{}, 5230 }, 5231 clientObjs: []runtime.Object{}, 5232 csvs: []*v1alpha1.ClusterServiceVersion{operatorCSV}, 5233 k8sObjs: []runtime.Object{ 5234 &corev1.Namespace{ 5235 ObjectMeta: metav1.ObjectMeta{ 5236 Name: operatorNamespace, 5237 Labels: map[string]string{"app": "app-a"}, 5238 Annotations: map[string]string{"test": "annotation"}, 5239 }, 5240 }, 5241 &corev1.Namespace{ 5242 ObjectMeta: metav1.ObjectMeta{ 5243 Name: targetNamespace, 5244 Labels: map[string]string{"app": "app-a"}, 5245 Annotations: map[string]string{"test": "annotation"}, 5246 }, 5247 }, 5248 ownedDeployment, 5249 serviceAccount, 5250 role, 5251 roleBinding, 5252 }, 5253 crds: []*apiextensionsv1.CustomResourceDefinition{crd}, 5254 }, 5255 expectedStatus: operatorsv1.OperatorGroupStatus{ 5256 Namespaces: []string{corev1.NamespaceAll}, 5257 LastUpdated: &now, 5258 }, 5259 final: final{objects: map[string][]runtime.Object{ 5260 operatorNamespace: { 5261 withAnnotations(operatorCSVFinal.DeepCopy(), map[string]string{operatorsv1.OperatorGroupTargetsAnnotationKey: "", operatorsv1.OperatorGroupAnnotationKey: "operator-group-1", operatorsv1.OperatorGroupNamespaceAnnotationKey: operatorNamespace}), 5262 annotatedGlobalDeployment, 5263 }, 5264 "": { 5265 &rbacv1.ClusterRole{ 5266 TypeMeta: metav1.TypeMeta{ 5267 Kind: "ClusterRole", 5268 APIVersion: rbacv1.GroupName, 5269 }, 5270 ObjectMeta: metav1.ObjectMeta{ 5271 Name: "csv-role", 5272 Labels: map[string]string{ 5273 "olm.managed": "true", 5274 "olm.owner": "csv1", 5275 "olm.owner.namespace": "operator-ns", 5276 "olm.owner.kind": "ClusterServiceVersion", 5277 }, 5278 }, 5279 Rules: append(permissions[0].Rules, rbacv1.PolicyRule{ 5280 Verbs: ViewVerbs, 5281 APIGroups: []string{corev1.GroupName}, 5282 Resources: []string{"namespaces"}, 5283 }), 5284 }, 5285 &rbacv1.ClusterRoleBinding{ 5286 TypeMeta: metav1.TypeMeta{ 5287 Kind: "ClusterRoleBinding", 5288 APIVersion: rbacv1.GroupName, 5289 }, 5290 ObjectMeta: metav1.ObjectMeta{ 5291 Name: "csv-rolebinding", 5292 Labels: map[string]string{ 5293 "olm.managed": "true", 5294 "olm.owner": "csv1", 5295 "olm.owner.namespace": "operator-ns", 5296 "olm.owner.kind": "ClusterServiceVersion", 5297 }, 5298 }, 5299 Subjects: []rbacv1.Subject{ 5300 { 5301 Kind: rbacv1.ServiceAccountKind, 5302 Name: serviceAccount.GetName(), 5303 Namespace: operatorNamespace, 5304 }, 5305 }, 5306 RoleRef: rbacv1.RoleRef{ 5307 APIGroup: rbacv1.GroupName, 5308 Kind: "ClusterRole", 5309 Name: "csv-role", 5310 }, 5311 }, 5312 }, 5313 targetNamespace: { 5314 withLabels( 5315 withAnnotations(targetCSV.DeepCopy(), map[string]string{operatorsv1.OperatorGroupAnnotationKey: "operator-group-1", operatorsv1.OperatorGroupNamespaceAnnotationKey: operatorNamespace}), 5316 labels.Merge(targetCSV.GetLabels(), map[string]string{v1alpha1.CopiedLabelKey: operatorNamespace}), 5317 ), 5318 }, 5319 }}, 5320 }, 5321 { 5322 name: "AllNamespaces/CSVPresent/Found/PruneMissingProvidedAPI/StaticProvidedAPIs", 5323 expectedEqual: true, 5324 initial: initial{ 5325 operatorGroup: &operatorsv1.OperatorGroup{ 5326 TypeMeta: metav1.TypeMeta{ 5327 Kind: operatorsv1.OperatorGroupKind, 5328 APIVersion: operatorsv1.GroupVersion.String(), 5329 }, 5330 ObjectMeta: metav1.ObjectMeta{ 5331 Name: "operator-group-1", 5332 Namespace: operatorNamespace, 5333 Labels: map[string]string{"app": "app-a"}, 5334 Annotations: map[string]string{ 5335 operatorsv1.OperatorGroupProvidedAPIsAnnotationKey: "missing.fake.api.group", 5336 }, 5337 }, 5338 Spec: operatorsv1.OperatorGroupSpec{ 5339 StaticProvidedAPIs: true, 5340 }, 5341 }, 5342 k8sObjs: []runtime.Object{ 5343 &corev1.Namespace{ 5344 ObjectMeta: metav1.ObjectMeta{ 5345 Name: operatorNamespace, 5346 Labels: map[string]string{"app": "app-a"}, 5347 Annotations: map[string]string{"test": "annotation"}, 5348 }, 5349 }, 5350 }, 5351 }, 5352 expectedStatus: operatorsv1.OperatorGroupStatus{ 5353 Namespaces: []string{corev1.NamespaceAll}, 5354 LastUpdated: &now, 5355 }, 5356 final: final{objects: map[string][]runtime.Object{ 5357 operatorNamespace: { 5358 &operatorsv1.OperatorGroup{ 5359 TypeMeta: metav1.TypeMeta{ 5360 Kind: operatorsv1.OperatorGroupKind, 5361 APIVersion: operatorsv1.GroupVersion.String(), 5362 }, 5363 ObjectMeta: metav1.ObjectMeta{ 5364 Name: "operator-group-1", 5365 Namespace: operatorNamespace, 5366 Labels: map[string]string{"app": "app-a"}, 5367 Annotations: map[string]string{ 5368 operatorsv1.OperatorGroupProvidedAPIsAnnotationKey: "missing.fake.api.group", 5369 }, 5370 }, 5371 Spec: operatorsv1.OperatorGroupSpec{ 5372 StaticProvidedAPIs: true, 5373 }, 5374 Status: operatorsv1.OperatorGroupStatus{ 5375 Namespaces: []string{corev1.NamespaceAll}, 5376 LastUpdated: &now, 5377 }, 5378 }, 5379 }, 5380 }}, 5381 }, 5382 { 5383 name: "AllNamespaces/CSVPresent/InstallModeNotSupported", 5384 expectedEqual: true, 5385 initial: initial{ 5386 operatorGroup: &operatorsv1.OperatorGroup{ 5387 ObjectMeta: metav1.ObjectMeta{ 5388 Name: "operator-group-1", 5389 Namespace: operatorNamespace, 5390 }, 5391 Spec: operatorsv1.OperatorGroupSpec{}, 5392 }, 5393 clientObjs: []runtime.Object{}, 5394 csvs: []*v1alpha1.ClusterServiceVersion{withInstallModes(operatorCSV.DeepCopy(), []v1alpha1.InstallMode{ 5395 { 5396 Type: v1alpha1.InstallModeTypeAllNamespaces, 5397 Supported: false, 5398 }, 5399 })}, 5400 k8sObjs: []runtime.Object{ 5401 &corev1.Namespace{ 5402 ObjectMeta: metav1.ObjectMeta{ 5403 Name: operatorNamespace, 5404 Annotations: map[string]string{"test": "annotation"}, 5405 }, 5406 }, 5407 &corev1.Namespace{ 5408 ObjectMeta: metav1.ObjectMeta{ 5409 Name: targetNamespace, 5410 Annotations: map[string]string{"test": "annotation"}, 5411 }, 5412 }, 5413 ownedDeployment, 5414 serviceAccount, 5415 role, 5416 roleBinding, 5417 }, 5418 crds: []*apiextensionsv1.CustomResourceDefinition{crd}, 5419 }, 5420 expectedStatus: operatorsv1.OperatorGroupStatus{ 5421 Namespaces: []string{corev1.NamespaceAll}, 5422 LastUpdated: &now, 5423 }, 5424 final: final{objects: map[string][]runtime.Object{ 5425 operatorNamespace: { 5426 withPhase( 5427 withInstallModes( 5428 withAnnotations(operatorCSV.DeepCopy(), map[string]string{ 5429 operatorsv1.OperatorGroupTargetsAnnotationKey: "", 5430 operatorsv1.OperatorGroupAnnotationKey: "operator-group-1", 5431 operatorsv1.OperatorGroupNamespaceAnnotationKey: operatorNamespace, 5432 }).(*v1alpha1.ClusterServiceVersion), 5433 []v1alpha1.InstallMode{ 5434 { 5435 Type: v1alpha1.InstallModeTypeAllNamespaces, 5436 Supported: false, 5437 }, 5438 }), v1alpha1.CSVPhaseFailed, 5439 v1alpha1.CSVReasonUnsupportedOperatorGroup, 5440 "AllNamespaces InstallModeType not supported, cannot configure to watch all namespaces", 5441 now), 5442 }, 5443 }}, 5444 }, 5445 } 5446 5447 copyObjs := func(objs []runtime.Object) []runtime.Object { 5448 if len(objs) < 1 { 5449 return nil 5450 } 5451 5452 copied := make([]runtime.Object, len(objs)) 5453 for i, obj := range objs { 5454 copied[i] = obj.DeepCopyObject() 5455 } 5456 5457 return copied 5458 } 5459 5460 for _, tt := range tests { 5461 t.Run(tt.name, func(t *testing.T) { 5462 // Pick out Namespaces 5463 var namespaces []string 5464 for _, obj := range tt.initial.k8sObjs { 5465 if ns, ok := obj.(*corev1.Namespace); ok { 5466 namespaces = append(namespaces, ns.GetName()) 5467 } 5468 } 5469 5470 // DeepCopy test fixtures to prevent test case pollution 5471 var ( 5472 operatorGroup = tt.initial.operatorGroup.DeepCopy() 5473 clientObjs = copyObjs(append(tt.initial.clientObjs, operatorGroup)) 5474 k8sObjs = copyObjs(tt.initial.k8sObjs) 5475 extObjs []runtime.Object 5476 regObjs = copyObjs(tt.initial.apis) 5477 ) 5478 5479 // Create test operator 5480 ctx, cancel := context.WithCancel(context.Background()) 5481 defer cancel() 5482 5483 var partials []runtime.Object 5484 for _, csv := range tt.initial.csvs { 5485 clientObjs = append(clientObjs, csv.DeepCopy()) 5486 partials = append(partials, &metav1.PartialObjectMetadata{ 5487 TypeMeta: metav1.TypeMeta{ 5488 Kind: "ClusterServiceVersion", 5489 APIVersion: v1alpha1.SchemeGroupVersion.String(), 5490 }, 5491 ObjectMeta: csv.ObjectMeta, 5492 }) 5493 } 5494 for _, crd := range tt.initial.crds { 5495 extObjs = append(extObjs, crd.DeepCopy()) 5496 partials = append(partials, &metav1.PartialObjectMetadata{ 5497 TypeMeta: metav1.TypeMeta{ 5498 Kind: "CustomResourceDefinition", 5499 APIVersion: apiextensionsv1.SchemeGroupVersion.String(), 5500 }, 5501 ObjectMeta: crd.ObjectMeta, 5502 }) 5503 } 5504 l := logrus.New() 5505 l.SetLevel(logrus.DebugLevel) 5506 l = l.WithField("test", tt.name).Logger 5507 op, err := NewFakeOperator( 5508 ctx, 5509 withClock(clockFake), 5510 withNamespaces(namespaces...), 5511 withOperatorNamespace(operatorNamespace), 5512 withClientObjs(clientObjs...), 5513 withK8sObjs(k8sObjs...), 5514 withExtObjs(extObjs...), 5515 withRegObjs(regObjs...), 5516 withPartialMetadata(partials...), 5517 withLogger(l), 5518 ) 5519 require.NoError(t, err) 5520 5521 simulateSuccessfulRollout := func(csv *v1alpha1.ClusterServiceVersion) { 5522 // Get the deployment, which should exist 5523 namespace := operatorGroup.GetNamespace() 5524 dep, err := op.opClient.GetDeployment(namespace, deploymentName) 5525 require.NoError(t, err) 5526 5527 // Force it healthy 5528 dep.Status.Replicas = 1 5529 dep.Status.UpdatedReplicas = 1 5530 dep.Status.AvailableReplicas = 1 5531 dep.Status.Conditions = []appsv1.DeploymentCondition{{ 5532 Type: appsv1.DeploymentAvailable, 5533 Status: corev1.ConditionTrue, 5534 }} 5535 _, err = op.opClient.KubernetesInterface().AppsV1().Deployments(namespace).UpdateStatus(ctx, dep, metav1.UpdateOptions{}) 5536 require.NoError(t, err) 5537 5538 // Wait for the lister cache to catch up 5539 err = wait.PollUntilContextTimeout(ctx, tick, timeout, true, func(ctx context.Context) (bool, error) { 5540 deployment, err := op.lister.AppsV1().DeploymentLister().Deployments(namespace).Get(dep.GetName()) 5541 if err != nil || deployment == nil { 5542 return false, err 5543 } 5544 5545 for _, condition := range deployment.Status.Conditions { 5546 if condition.Type == appsv1.DeploymentAvailable { 5547 return condition.Status == corev1.ConditionTrue, nil 5548 } 5549 } 5550 5551 return false, nil 5552 }) 5553 require.NoError(t, err) 5554 } 5555 5556 err = op.syncOperatorGroups(operatorGroup) 5557 require.NoError(t, err) 5558 5559 // Wait on operator group updated status to be in the cache as it is required for later CSV operations 5560 err = wait.PollUntilContextTimeout(ctx, tick, timeout, true, func(ctx context.Context) (bool, error) { 5561 og, err := op.lister.OperatorsV1().OperatorGroupLister().OperatorGroups(operatorGroup.GetNamespace()).Get(operatorGroup.GetName()) 5562 if err != nil { 5563 return false, err 5564 } 5565 sort.Strings(tt.expectedStatus.Namespaces) 5566 sort.Strings(og.Status.Namespaces) 5567 if !reflect.DeepEqual(tt.expectedStatus, og.Status) { 5568 return false, err 5569 } 5570 5571 operatorGroup = og 5572 5573 return true, nil 5574 }) 5575 require.NoError(t, err) 5576 5577 // This must be done (at least) twice to have annotateCSVs run in syncOperatorGroups and to catch provided API changes 5578 // syncOperatorGroups is eventually consistent and may return errors until the cache has caught up with the cluster (fake client here) 5579 err = wait.PollUntilContextTimeout(ctx, tick, timeout, true, func(ctx context.Context) (bool, error) { // Throw away timeout errors since any timeout will coincide with err != nil anyway 5580 err = op.syncOperatorGroups(operatorGroup) 5581 return err == nil, nil 5582 }) 5583 require.NoError(t, err) 5584 5585 var foundErr error 5586 // Sync csvs enough to get them back to a succeeded state 5587 err = wait.PollUntilContextTimeout(ctx, tick, timeout, true, func(ctx context.Context) (bool, error) { 5588 csvs, err := op.client.OperatorsV1alpha1().ClusterServiceVersions(operatorNamespace).List(ctx, metav1.ListOptions{}) 5589 if err != nil { 5590 return false, err 5591 } 5592 5593 for _, csv := range csvs.Items { 5594 if csv.Status.Phase == v1alpha1.CSVPhaseInstalling { 5595 simulateSuccessfulRollout(&csv) 5596 } 5597 5598 if err := op.syncClusterServiceVersion(&csv); err != nil { 5599 return false, fmt.Errorf("failed to syncClusterServiceVersion: %w", err) 5600 } 5601 5602 if err := op.syncCopyCSV(&csv); err != nil && !tt.ignoreCopyError { 5603 return false, fmt.Errorf("failed to syncCopyCSV: %w", err) 5604 } 5605 } 5606 5607 for namespace, objects := range tt.final.objects { 5608 if err := RequireObjectsInCache(t, op.lister, namespace, objects, true); err != nil { 5609 foundErr = err 5610 return false, nil 5611 } 5612 } 5613 5614 return true, nil 5615 }) 5616 t.Log(foundErr) 5617 require.NoError(t, err) 5618 5619 operatorGroup, err = op.client.OperatorsV1().OperatorGroups(operatorGroup.GetNamespace()).Get(ctx, operatorGroup.GetName(), metav1.GetOptions{}) 5620 require.NoError(t, err) 5621 sort.Strings(tt.expectedStatus.Namespaces) 5622 sort.Strings(operatorGroup.Status.Namespaces) 5623 assert.Equal(t, tt.expectedStatus, operatorGroup.Status) 5624 5625 for namespace, objects := range tt.final.objects { 5626 var foundErr error 5627 err = wait.PollUntilContextTimeout(ctx, tick, timeout, true, func(ctx context.Context) (bool, error) { 5628 foundErr = CheckObjectsInNamespace(t, op.opClient, op.client, namespace, objects) 5629 return foundErr == nil, nil 5630 }) 5631 t.Log(foundErr) 5632 require.NoError(t, err) 5633 } 5634 }) 5635 } 5636 } 5637 5638 func TestOperatorGroupConditions(t *testing.T) { 5639 logrus.SetLevel(logrus.DebugLevel) 5640 clockFake := utilclocktesting.NewFakeClock(time.Date(2006, time.January, 2, 15, 4, 5, 0, time.FixedZone("MST", -7*3600))) 5641 5642 operatorNamespace := "operator-ns" 5643 serviceAccount := serviceAccount("sa", operatorNamespace) 5644 5645 type initial struct { 5646 operatorGroup *operatorsv1.OperatorGroup 5647 clientObjs []runtime.Object 5648 k8sObjs []runtime.Object 5649 } 5650 5651 tests := []struct { 5652 initial initial 5653 name string 5654 expectedConditions []metav1.Condition 5655 expectError bool 5656 }{ 5657 { 5658 name: "ValidOperatorGroup/NoServiceAccount", 5659 initial: initial{ 5660 operatorGroup: &operatorsv1.OperatorGroup{ 5661 ObjectMeta: metav1.ObjectMeta{ 5662 Name: "operator-group-1", 5663 Namespace: operatorNamespace, 5664 UID: "135e02a5-a7e2-44e7-abaa-88c63838993c", 5665 }, 5666 Spec: operatorsv1.OperatorGroupSpec{ 5667 TargetNamespaces: []string{operatorNamespace}, 5668 }, 5669 }, 5670 k8sObjs: []runtime.Object{ 5671 &corev1.Namespace{ 5672 ObjectMeta: metav1.ObjectMeta{ 5673 Name: operatorNamespace, 5674 }, 5675 }, 5676 }, 5677 }, 5678 expectError: false, 5679 expectedConditions: []metav1.Condition{}, 5680 }, 5681 { 5682 name: "ValidOperatorGroup/ValidServiceAccount", 5683 initial: initial{ 5684 operatorGroup: &operatorsv1.OperatorGroup{ 5685 ObjectMeta: metav1.ObjectMeta{ 5686 Name: "operator-group-1", 5687 Namespace: operatorNamespace, 5688 UID: "135e02a5-a7e2-44e7-abaa-88c63838993c", 5689 }, 5690 Spec: operatorsv1.OperatorGroupSpec{ 5691 ServiceAccountName: "sa", 5692 TargetNamespaces: []string{operatorNamespace}, 5693 }, 5694 }, 5695 k8sObjs: []runtime.Object{ 5696 &corev1.Namespace{ 5697 ObjectMeta: metav1.ObjectMeta{ 5698 Name: operatorNamespace, 5699 }, 5700 }, 5701 serviceAccount, 5702 }, 5703 }, 5704 expectError: false, 5705 expectedConditions: []metav1.Condition{}, 5706 }, 5707 { 5708 name: "BadOperatorGroup/MissingServiceAccount", 5709 initial: initial{ 5710 operatorGroup: &operatorsv1.OperatorGroup{ 5711 ObjectMeta: metav1.ObjectMeta{ 5712 Name: "operator-group-1", 5713 Namespace: operatorNamespace, 5714 UID: "135e02a5-a7e2-44e7-abaa-88c63838993c", 5715 }, 5716 Spec: operatorsv1.OperatorGroupSpec{ 5717 ServiceAccountName: "nonexistingSA", 5718 TargetNamespaces: []string{operatorNamespace}, 5719 }, 5720 }, 5721 k8sObjs: []runtime.Object{ 5722 &corev1.Namespace{ 5723 ObjectMeta: metav1.ObjectMeta{ 5724 Name: operatorNamespace, 5725 }, 5726 }, 5727 }, 5728 }, 5729 expectError: true, 5730 expectedConditions: []metav1.Condition{ 5731 { 5732 Type: operatorsv1.OperatorGroupServiceAccountCondition, 5733 Status: metav1.ConditionTrue, 5734 Reason: operatorsv1.OperatorGroupServiceAccountReason, 5735 Message: "ServiceAccount nonexistingSA not found", 5736 }, 5737 }, 5738 }, 5739 { 5740 name: "BadOperatorGroup/MultipleOperatorGroups", 5741 initial: initial{ 5742 operatorGroup: &operatorsv1.OperatorGroup{ 5743 ObjectMeta: metav1.ObjectMeta{ 5744 Name: "operator-group-1", 5745 Namespace: operatorNamespace, 5746 UID: "135e02a5-a7e2-44e7-abaa-88c63838993c", 5747 }, 5748 Spec: operatorsv1.OperatorGroupSpec{ 5749 TargetNamespaces: []string{operatorNamespace}, 5750 }, 5751 }, 5752 clientObjs: []runtime.Object{ 5753 &operatorsv1.OperatorGroup{ 5754 ObjectMeta: metav1.ObjectMeta{ 5755 Name: "operator-group-2", 5756 Namespace: operatorNamespace, 5757 UID: "cdc9643e-7c52-4f7c-ae75-28ccb6aec97d", 5758 }, 5759 Spec: operatorsv1.OperatorGroupSpec{ 5760 TargetNamespaces: []string{operatorNamespace, "some-namespace"}, 5761 }, 5762 }, 5763 }, 5764 k8sObjs: []runtime.Object{ 5765 &corev1.Namespace{ 5766 ObjectMeta: metav1.ObjectMeta{ 5767 Name: operatorNamespace, 5768 }, 5769 }, 5770 }, 5771 }, 5772 expectError: true, 5773 expectedConditions: []metav1.Condition{ 5774 { 5775 Type: operatorsv1.MutlipleOperatorGroupCondition, 5776 Status: metav1.ConditionTrue, 5777 Reason: operatorsv1.MultipleOperatorGroupsReason, 5778 Message: "Multiple OperatorGroup found in the same namespace", 5779 }, 5780 }, 5781 }, 5782 } 5783 5784 for _, tt := range tests { 5785 t.Run(tt.name, func(t *testing.T) { 5786 namespaces := []string{} 5787 // Pick out Namespaces 5788 for _, obj := range tt.initial.k8sObjs { 5789 if ns, ok := obj.(*corev1.Namespace); ok { 5790 namespaces = append(namespaces, ns.GetName()) 5791 } 5792 } 5793 5794 // Append operatorGroup to initialObjs 5795 tt.initial.clientObjs = append(tt.initial.clientObjs, tt.initial.operatorGroup) 5796 5797 // Create test operator 5798 ctx, cancel := context.WithCancel(context.TODO()) 5799 defer cancel() 5800 op, err := NewFakeOperator( 5801 ctx, 5802 withClock(clockFake), 5803 withNamespaces(namespaces...), 5804 withOperatorNamespace(operatorNamespace), 5805 withClientObjs(tt.initial.clientObjs...), 5806 withK8sObjs(tt.initial.k8sObjs...), 5807 ) 5808 require.NoError(t, err) 5809 5810 err = op.syncOperatorGroups(tt.initial.operatorGroup) 5811 if !tt.expectError { 5812 require.NoError(t, err) 5813 } 5814 5815 operatorGroup, err := op.client.OperatorsV1().OperatorGroups(tt.initial.operatorGroup.GetNamespace()).Get(context.TODO(), tt.initial.operatorGroup.GetName(), metav1.GetOptions{}) 5816 require.NoError(t, err) 5817 assert.Equal(t, len(tt.expectedConditions), len(operatorGroup.Status.Conditions)) 5818 if len(tt.expectedConditions) > 0 { 5819 for _, cond := range tt.expectedConditions { 5820 c := meta.FindStatusCondition(operatorGroup.Status.Conditions, cond.Type) 5821 assert.Equal(t, cond.Status, c.Status) 5822 assert.Equal(t, cond.Reason, c.Reason) 5823 assert.Equal(t, cond.Message, c.Message) 5824 } 5825 } 5826 }) 5827 } 5828 } 5829 5830 func RequireObjectsInCache(t *testing.T, lister operatorlister.OperatorLister, namespace string, objects []runtime.Object, doCompare bool) error { 5831 for _, object := range objects { 5832 var err error 5833 var fetched runtime.Object 5834 switch o := object.(type) { 5835 case *appsv1.Deployment: 5836 fetched, err = lister.AppsV1().DeploymentLister().Deployments(namespace).Get(o.GetName()) 5837 case *rbacv1.ClusterRole: 5838 fetched, err = lister.RbacV1().ClusterRoleLister().Get(o.GetName()) 5839 case *rbacv1.Role: 5840 fetched, err = lister.RbacV1().RoleLister().Roles(namespace).Get(o.GetName()) 5841 case *rbacv1.ClusterRoleBinding: 5842 fetched, err = lister.RbacV1().ClusterRoleBindingLister().Get(o.GetName()) 5843 case *rbacv1.RoleBinding: 5844 fetched, err = lister.RbacV1().RoleBindingLister().RoleBindings(namespace).Get(o.GetName()) 5845 case *v1alpha1.ClusterServiceVersion: 5846 fetched, err = lister.OperatorsV1alpha1().ClusterServiceVersionLister().ClusterServiceVersions(namespace).Get(o.GetName()) 5847 // We don't care about finalizers 5848 object.(*v1alpha1.ClusterServiceVersion).Finalizers = nil 5849 fetched.(*v1alpha1.ClusterServiceVersion).Finalizers = nil 5850 case *operatorsv1.OperatorGroup: 5851 fetched, err = lister.OperatorsV1().OperatorGroupLister().OperatorGroups(namespace).Get(o.GetName()) 5852 default: 5853 require.Failf(t, "couldn't find expected object", "%#v", object) 5854 } 5855 if err != nil { 5856 if apierrors.IsNotFound(err) { 5857 return err 5858 } 5859 return errors.Join(err, fmt.Errorf("namespace: %v, error: %v", namespace, err)) 5860 } 5861 if doCompare { 5862 if !reflect.DeepEqual(object, fetched) { 5863 return fmt.Errorf("expected object didn't match: %s", cmp.Diff(object, fetched)) 5864 } 5865 } 5866 } 5867 return nil 5868 } 5869 5870 func RequireObjectsInNamespace(t *testing.T, opClient operatorclient.ClientInterface, client versioned.Interface, namespace string, objects []runtime.Object) { 5871 require.NoError(t, CheckObjectsInNamespace(t, opClient, client, namespace, objects)) 5872 } 5873 5874 func CheckObjectsInNamespace(t *testing.T, opClient operatorclient.ClientInterface, client versioned.Interface, namespace string, objects []runtime.Object) error { 5875 for _, object := range objects { 5876 var err error 5877 var fetched runtime.Object 5878 var name string 5879 switch o := object.(type) { 5880 case *appsv1.Deployment: 5881 name = o.GetName() 5882 fetched, err = opClient.GetDeployment(namespace, o.GetName()) 5883 case *rbacv1.ClusterRole: 5884 name = o.GetName() 5885 fetched, err = opClient.GetClusterRole(o.GetName()) 5886 case *rbacv1.Role: 5887 name = o.GetName() 5888 fetched, err = opClient.GetRole(namespace, o.GetName()) 5889 case *rbacv1.ClusterRoleBinding: 5890 name = o.GetName() 5891 fetched, err = opClient.GetClusterRoleBinding(o.GetName()) 5892 case *rbacv1.RoleBinding: 5893 name = o.GetName() 5894 fetched, err = opClient.GetRoleBinding(namespace, o.GetName()) 5895 case *v1alpha1.ClusterServiceVersion: 5896 name = o.GetName() 5897 fetched, err = client.OperatorsV1alpha1().ClusterServiceVersions(namespace).Get(context.TODO(), o.GetName(), metav1.GetOptions{}) 5898 // This protects against small timing issues in sync tests 5899 // We generally don't care about the conditions (state history in this case, unlike many kube resources) 5900 // and this will still check that the final state is correct 5901 object.(*v1alpha1.ClusterServiceVersion).Status.Conditions = nil 5902 fetched.(*v1alpha1.ClusterServiceVersion).Status.Conditions = nil 5903 object.(*v1alpha1.ClusterServiceVersion).Finalizers = nil 5904 fetched.(*v1alpha1.ClusterServiceVersion).Finalizers = nil 5905 case *operatorsv1.OperatorGroup: 5906 name = o.GetName() 5907 fetched, err = client.OperatorsV1().OperatorGroups(namespace).Get(context.TODO(), o.GetName(), metav1.GetOptions{}) 5908 case *corev1.Secret: 5909 name = o.GetName() 5910 fetched, err = opClient.GetSecret(namespace, o.GetName()) 5911 default: 5912 require.Failf(t, "couldn't find expected object", "%#v", object) 5913 } 5914 if err != nil { 5915 return fmt.Errorf("couldn't fetch %s/%s: %w", namespace, name, err) 5916 } 5917 if diff := cmp.Diff(object, fetched); diff != "" { 5918 return fmt.Errorf("incorrect object %s/%s: %v", namespace, name, diff) 5919 } 5920 } 5921 return nil 5922 } 5923 5924 func TestCARotation(t *testing.T) { 5925 logrus.SetLevel(logrus.DebugLevel) 5926 namespace := "ns" 5927 5928 defaultOperatorGroup := &operatorsv1.OperatorGroup{ 5929 TypeMeta: metav1.TypeMeta{ 5930 Kind: "OperatorGroup", 5931 APIVersion: operatorsv1.SchemeGroupVersion.String(), 5932 }, 5933 ObjectMeta: metav1.ObjectMeta{ 5934 Name: "default", 5935 Namespace: namespace, 5936 }, 5937 Spec: operatorsv1.OperatorGroupSpec{}, 5938 Status: operatorsv1.OperatorGroupStatus{ 5939 Namespaces: []string{namespace}, 5940 }, 5941 } 5942 5943 defaultTemplateAnnotations := map[string]string{ 5944 operatorsv1.OperatorGroupTargetsAnnotationKey: namespace, 5945 operatorsv1.OperatorGroupNamespaceAnnotationKey: namespace, 5946 operatorsv1.OperatorGroupAnnotationKey: defaultOperatorGroup.GetName(), 5947 } 5948 5949 // Generate valid and expired CA fixtures 5950 expiresAt := metav1.NewTime(install.CalculateCertExpiration(time.Now())) 5951 rotateAt := metav1.NewTime(install.CalculateCertRotatesAt(expiresAt.Time)) 5952 5953 lastUpdate := metav1.Time{Time: time.Now().UTC()} 5954 5955 validCA, err := generateCA(expiresAt.Time, install.Organization) 5956 require.NoError(t, err) 5957 validCAPEM, _, err := validCA.ToPEM() 5958 require.NoError(t, err) 5959 validCAHash := certs.PEMSHA256(validCAPEM) 5960 5961 ownerReference := metav1.OwnerReference{ 5962 Kind: v1alpha1.ClusterServiceVersionKind, 5963 UID: "csv-uid", 5964 } 5965 5966 type operatorConfig struct { 5967 apiReconciler APIIntersectionReconciler 5968 apiLabeler labeler.Labeler 5969 } 5970 type initial struct { 5971 csvs []*v1alpha1.ClusterServiceVersion 5972 clientObjs []runtime.Object 5973 crds []runtime.Object 5974 objs []runtime.Object 5975 apis []runtime.Object 5976 } 5977 tests := []struct { 5978 name string 5979 config operatorConfig 5980 initial initial 5981 }{ 5982 { 5983 // Happy path: cert is created and csv status contains the right cert dates 5984 name: "NoCertificate/CertificateCreated", 5985 initial: initial{ 5986 csvs: []*v1alpha1.ClusterServiceVersion{ 5987 withAPIServices(csvWithAnnotations(csv("csv1", 5988 namespace, 5989 "0.0.0", 5990 "", 5991 installStrategy("a1", nil, nil), 5992 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 5993 []*apiextensionsv1.CustomResourceDefinition{}, 5994 v1alpha1.CSVPhaseInstallReady, 5995 ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), 5996 }, 5997 clientObjs: []runtime.Object{addAnnotation(defaultOperatorGroup, operatorsv1.OperatorGroupProvidedAPIsAnnotationKey, "c1.v1.g1,a1Kind.v1.a1")}, 5998 // The rolebinding, service, and clusterRoleBinding have been added here as a workaround to fake client not supporting SSA 5999 objs: []runtime.Object{ 6000 roleBinding("a1-service-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace), 6001 service("a1-service", namespace, "a1", 80, ownerReference), 6002 clusterRoleBinding("a1-service-system:auth-delegator", "system:auth-delegator", "sa", namespace), 6003 }, 6004 crds: []runtime.Object{ 6005 crd("c1", "v1", "g1"), 6006 }, 6007 }, 6008 }, { 6009 // If a CSV finds itself in the InstallReady phase with a valid certificate 6010 // it's likely that a deployment pod or other resource is gone and the installer will re-apply the 6011 // resources. If the certs exist and are valid, no need to rotate or update the csv status. 6012 name: "HasValidCertificate/ManagedPodDeleted/NoRotation", 6013 initial: initial{ 6014 csvs: []*v1alpha1.ClusterServiceVersion{ 6015 withUID(withCertInfo(withAPIServices(csvWithAnnotations(csv("csv1", 6016 namespace, 6017 "0.0.0", 6018 "", 6019 installStrategy("a1", nil, nil), 6020 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 6021 []*apiextensionsv1.CustomResourceDefinition{}, 6022 v1alpha1.CSVPhaseInstallReady, 6023 ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), rotateAt, lastUpdate), types.UID("csv-uid")).(*v1alpha1.ClusterServiceVersion), 6024 }, 6025 clientObjs: []runtime.Object{defaultOperatorGroup}, 6026 crds: []runtime.Object{ 6027 crd("c1", "v1", "g1"), 6028 }, 6029 apis: []runtime.Object{ 6030 apiService("a1", "v1", "a1-service", namespace, "a1", validCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)), 6031 }, 6032 objs: []runtime.Object{ 6033 deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{ 6034 install.OLMCAHashAnnotationKey: validCAHash, 6035 })), 6036 withLabels(withAnnotations(withCA(keyPairToTLSSecret("a1-service-cert", namespace, signedServingPair(expiresAt.Time, validCA, []string{"a1-service.ns", "a1-service.ns.svc"})), validCAPEM), map[string]string{ 6037 install.OLMCAHashAnnotationKey: validCAHash, 6038 }), map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}), 6039 service("a1-service", namespace, "a1", 80, ownerReference), 6040 serviceAccount("sa", namespace), 6041 role("a1-service-cert", namespace, []rbacv1.PolicyRule{ 6042 { 6043 Verbs: []string{"get"}, 6044 APIGroups: []string{""}, 6045 Resources: []string{"secrets"}, 6046 ResourceNames: []string{"a1-service-cert"}, 6047 }, 6048 }), 6049 roleBinding("a1-service-cert", namespace, "a1-service-cert", "sa", namespace), 6050 role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{ 6051 { 6052 Verbs: []string{"get"}, 6053 APIGroups: []string{""}, 6054 Resources: []string{"configmaps"}, 6055 ResourceNames: []string{"extension-apiserver-authentication"}, 6056 }, 6057 }), 6058 roleBinding("a1-service-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace), 6059 clusterRole("system:auth-delegator", []rbacv1.PolicyRule{ 6060 { 6061 Verbs: []string{"create"}, 6062 APIGroups: []string{"authentication.k8s.io"}, 6063 Resources: []string{"tokenreviews"}, 6064 }, 6065 { 6066 Verbs: []string{"create"}, 6067 APIGroups: []string{"authentication.k8s.io"}, 6068 Resources: []string{"subjectaccessreviews"}, 6069 }, 6070 }), 6071 // The clusterRoleBinding has been added here as a workaround to fake client not supporting SSA 6072 clusterRoleBinding("a1-service-system:auth-delegator", "system:auth-delegator", "sa", namespace), 6073 }, 6074 }, 6075 }, { 6076 // If the cert secret is deleted, a new one is created 6077 name: "ValidCert/SecretMissing/NewCertCreated", 6078 initial: initial{ 6079 csvs: []*v1alpha1.ClusterServiceVersion{ 6080 withUID(withCertInfo(withAPIServices(csvWithAnnotations(csv("csv1", 6081 namespace, 6082 "0.0.0", 6083 "", 6084 installStrategy("a1", nil, nil), 6085 []*apiextensionsv1.CustomResourceDefinition{crd("c1", "v1", "g1")}, 6086 []*apiextensionsv1.CustomResourceDefinition{}, 6087 v1alpha1.CSVPhaseInstallReady, 6088 ), defaultTemplateAnnotations), apis("a1.v1.a1Kind"), nil), rotateAt, lastUpdate), types.UID("csv-uid")).(*v1alpha1.ClusterServiceVersion), 6089 }, 6090 clientObjs: []runtime.Object{defaultOperatorGroup}, 6091 crds: []runtime.Object{ 6092 crd("c1", "v1", "g1"), 6093 }, 6094 apis: []runtime.Object{ 6095 apiService("a1", "v1", "a1-service", namespace, "a1", validCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)), 6096 }, 6097 objs: []runtime.Object{ 6098 deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{ 6099 install.OLMCAHashAnnotationKey: validCAHash, 6100 })), 6101 service("a1-service", namespace, "a1", 80, ownerReference), 6102 serviceAccount("sa", namespace), 6103 role("a1-service-cert", namespace, []rbacv1.PolicyRule{ 6104 { 6105 Verbs: []string{"get"}, 6106 APIGroups: []string{""}, 6107 Resources: []string{"secrets"}, 6108 ResourceNames: []string{"a1-service-cert"}, 6109 }, 6110 }), 6111 roleBinding("a1-service-cert", namespace, "a1-service-cert", "sa", namespace), 6112 role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{ 6113 { 6114 Verbs: []string{"get"}, 6115 APIGroups: []string{""}, 6116 Resources: []string{"configmaps"}, 6117 ResourceNames: []string{"extension-apiserver-authentication"}, 6118 }, 6119 }), 6120 roleBinding("a1-service-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace), 6121 clusterRole("system:auth-delegator", []rbacv1.PolicyRule{ 6122 { 6123 Verbs: []string{"create"}, 6124 APIGroups: []string{"authentication.k8s.io"}, 6125 Resources: []string{"tokenreviews"}, 6126 }, 6127 { 6128 Verbs: []string{"create"}, 6129 APIGroups: []string{"authentication.k8s.io"}, 6130 Resources: []string{"subjectaccessreviews"}, 6131 }, 6132 }), 6133 // The clusterRoleBinding has been added here as a workaround to fake client not supporting SSA 6134 clusterRoleBinding("a1-service-system:auth-delegator", "system:auth-delegator", "sa", namespace), 6135 }, 6136 }, 6137 }, 6138 } 6139 6140 for _, tt := range tests { 6141 t.Run(tt.name, func(t *testing.T) { 6142 // Create test operator 6143 ctx, cancel := context.WithCancel(context.TODO()) 6144 defer cancel() 6145 clientObjects := tt.initial.clientObjs 6146 var partials []runtime.Object 6147 for _, csv := range tt.initial.csvs { 6148 clientObjects = append(clientObjects, csv) 6149 partials = append(partials, &metav1.PartialObjectMetadata{ 6150 ObjectMeta: csv.ObjectMeta, 6151 }) 6152 } 6153 op, err := NewFakeOperator( 6154 ctx, 6155 withNamespaces(namespace, "kube-system"), 6156 withClientObjs(clientObjects...), 6157 withK8sObjs(tt.initial.objs...), 6158 withExtObjs(tt.initial.crds...), 6159 withRegObjs(tt.initial.apis...), 6160 withPartialMetadata(partials...), 6161 withOperatorNamespace(namespace), 6162 withAPIReconciler(tt.config.apiReconciler), 6163 withAPILabeler(tt.config.apiLabeler), 6164 ) 6165 require.NoError(t, err) 6166 6167 // run csv sync for each CSV 6168 for _, csv := range tt.initial.csvs { 6169 // sync works 6170 err := op.syncClusterServiceVersion(csv) 6171 require.NoError(t, err) 6172 6173 outCSV, err := op.client.OperatorsV1alpha1().ClusterServiceVersions(namespace).Get(context.Background(), csv.GetName(), metav1.GetOptions{}) 6174 require.NoError(t, err) 6175 6176 require.Equal(t, outCSV.Status.Phase, v1alpha1.CSVPhaseInstalling) 6177 6178 for _, apiServiceDescriptor := range outCSV.GetAllAPIServiceDescriptions() { 6179 // Get secret with the certificate 6180 secretName := fmt.Sprintf("%s-service-cert", apiServiceDescriptor.DeploymentName) 6181 serviceSecret, err := op.opClient.GetSecret(csv.GetNamespace(), secretName) 6182 require.NoError(t, err) 6183 require.NotNil(t, serviceSecret) 6184 6185 // Extract certificate validity period 6186 start, end, err := GetServiceCertificaValidityPeriod(serviceSecret) 6187 require.NoError(t, err) 6188 require.NotNil(t, start) 6189 require.NotNil(t, end) 6190 6191 rotationTime := end.Add(-1 * install.DefaultCertMinFresh) 6192 // The csv status is updated after the certificate is created/rotated 6193 require.LessOrEqual(t, start.Unix(), outCSV.Status.CertsLastUpdated.Unix()) 6194 6195 // Rotation time should always be the same between the certificate and the status 6196 require.Equal(t, rotationTime.Unix(), outCSV.Status.CertsRotateAt.Unix()) 6197 } 6198 } 6199 6200 // get csvs in the cluster 6201 outCSVMap := map[string]*v1alpha1.ClusterServiceVersion{} 6202 outCSVs, err := op.client.OperatorsV1alpha1().ClusterServiceVersions(namespace).List(context.TODO(), metav1.ListOptions{}) 6203 require.NoError(t, err) 6204 for _, csv := range outCSVs.Items { 6205 outCSVMap[csv.GetName()] = csv.DeepCopy() 6206 } 6207 }) 6208 } 6209 } 6210 6211 func GetServiceCertificaValidityPeriod(serviceSecret *corev1.Secret) (start *time.Time, end *time.Time, err error) { 6212 // Extract certificate 6213 root := x509.NewCertPool() 6214 rootPEM, ok := serviceSecret.Data[install.OLMCAPEMKey] 6215 if !ok { 6216 return nil, nil, fmt.Errorf("could not find the service root certificate") 6217 } 6218 6219 ok = root.AppendCertsFromPEM(rootPEM) 6220 if !ok { 6221 return nil, nil, fmt.Errorf("could not append the service root certificate") 6222 } 6223 6224 certPEM, ok := serviceSecret.Data["tls.crt"] 6225 if !ok { 6226 return nil, nil, fmt.Errorf("could not find the service certificate") 6227 } 6228 block, _ := pem.Decode(certPEM) 6229 6230 cert, err := x509.ParseCertificate(block.Bytes) 6231 if err != nil { 6232 return nil, nil, err 6233 } 6234 6235 return &cert.NotBefore, &cert.NotAfter, nil 6236 } 6237 6238 func TestIsReplacing(t *testing.T) { 6239 logrus.SetLevel(logrus.DebugLevel) 6240 namespace := "ns" 6241 6242 type initial struct { 6243 csvs []runtime.Object 6244 } 6245 tests := []struct { 6246 name string 6247 initial initial 6248 in *v1alpha1.ClusterServiceVersion 6249 expected *v1alpha1.ClusterServiceVersion 6250 }{ 6251 { 6252 name: "QueryErr", 6253 in: csv("name", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded), 6254 initial: initial{ 6255 csvs: []runtime.Object{}, 6256 }, 6257 expected: nil, 6258 }, 6259 { 6260 name: "CSVInCluster/NotReplacing", 6261 in: csv("csv1", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded), 6262 initial: initial{ 6263 csvs: []runtime.Object{ 6264 csv("csv1", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded), 6265 }, 6266 }, 6267 expected: nil, 6268 }, 6269 { 6270 name: "CSVInCluster/Replacing", 6271 in: csv("csv2", namespace, "0.0.0", "csv1", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded), 6272 initial: initial{ 6273 csvs: []runtime.Object{ 6274 csv("csv1", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded), 6275 }, 6276 }, 6277 expected: csv("csv1", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded), 6278 }, 6279 { 6280 name: "CSVInCluster/ReplacingNotFound", 6281 in: csv("csv2", namespace, "0.0.0", "csv1", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded), 6282 initial: initial{ 6283 csvs: []runtime.Object{ 6284 csv("csv3", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded), 6285 }, 6286 }, 6287 expected: nil, 6288 }, 6289 } 6290 for _, tt := range tests { 6291 t.Run(tt.name, func(t *testing.T) { 6292 // Create test operator 6293 ctx, cancel := context.WithCancel(context.TODO()) 6294 defer cancel() 6295 op, err := NewFakeOperator(ctx, withNamespaces(namespace), withClientObjs(tt.initial.csvs...)) 6296 require.NoError(t, err) 6297 6298 require.Equal(t, tt.expected, op.isReplacing(tt.in)) 6299 }) 6300 } 6301 } 6302 6303 func TestIsBeingReplaced(t *testing.T) { 6304 namespace := "ns" 6305 6306 type initial struct { 6307 csvs map[string]*v1alpha1.ClusterServiceVersion 6308 } 6309 tests := []struct { 6310 name string 6311 initial initial 6312 in *v1alpha1.ClusterServiceVersion 6313 expected *v1alpha1.ClusterServiceVersion 6314 }{ 6315 { 6316 name: "QueryErr", 6317 in: csv("name", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded), 6318 expected: nil, 6319 }, 6320 { 6321 name: "CSVInCluster/NotReplacing", 6322 in: csv("csv1", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded), 6323 initial: initial{ 6324 csvs: map[string]*v1alpha1.ClusterServiceVersion{ 6325 "csv2": csv("csv2", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded), 6326 }, 6327 }, 6328 expected: nil, 6329 }, 6330 { 6331 name: "CSVInCluster/Replacing", 6332 in: csv("csv1", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded), 6333 initial: initial{ 6334 csvs: map[string]*v1alpha1.ClusterServiceVersion{ 6335 "csv2": csv("csv2", namespace, "0.0.0", "csv1", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded), 6336 }, 6337 }, 6338 expected: csv("csv2", namespace, "0.0.0", "csv1", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded), 6339 }, 6340 } 6341 for _, tt := range tests { 6342 t.Run(tt.name, func(t *testing.T) { 6343 ctx, cancel := context.WithCancel(context.TODO()) 6344 defer cancel() 6345 op, err := NewFakeOperator(ctx, withNamespaces(namespace)) 6346 require.NoError(t, err) 6347 6348 require.Equal(t, tt.expected, op.isBeingReplaced(tt.in, tt.initial.csvs)) 6349 }) 6350 } 6351 } 6352 6353 func TestCheckReplacement(t *testing.T) { 6354 namespace := "ns" 6355 6356 type initial struct { 6357 csvs map[string]*v1alpha1.ClusterServiceVersion 6358 } 6359 tests := []struct { 6360 name string 6361 initial initial 6362 in *v1alpha1.ClusterServiceVersion 6363 expected *v1alpha1.ClusterServiceVersion 6364 }{ 6365 { 6366 name: "QueryErr", 6367 in: csv("name", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded), 6368 expected: nil, 6369 }, 6370 { 6371 name: "CSVInCluster/NotReplacing", 6372 in: csv("csv1", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded), 6373 initial: initial{ 6374 csvs: map[string]*v1alpha1.ClusterServiceVersion{ 6375 "csv2": csv("csv2", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded), 6376 }, 6377 }, 6378 expected: nil, 6379 }, 6380 { 6381 name: "CSVInCluster/Replacing", 6382 in: csv("csv1", namespace, "0.0.0", "", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded), 6383 initial: initial{ 6384 csvs: map[string]*v1alpha1.ClusterServiceVersion{ 6385 "csv2": csv("csv2", namespace, "0.0.0", "csv1", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded), 6386 }, 6387 }, 6388 expected: csv("csv2", namespace, "0.0.0", "csv1", installStrategy("dep", nil, nil), nil, nil, v1alpha1.CSVPhaseSucceeded), 6389 }, 6390 } 6391 for _, tt := range tests { 6392 t.Run(tt.name, func(t *testing.T) { 6393 ctx, cancel := context.WithCancel(context.TODO()) 6394 defer cancel() 6395 op, err := NewFakeOperator(ctx, withNamespaces(namespace)) 6396 require.NoError(t, err) 6397 require.Equal(t, tt.expected, op.isBeingReplaced(tt.in, tt.initial.csvs)) 6398 }) 6399 } 6400 } 6401 6402 func TestAPIServiceResourceErrorActionable(t *testing.T) { 6403 tests := []struct { 6404 name string 6405 errs []error 6406 actionable bool 6407 }{ 6408 { 6409 name: "Nil/Actionable", 6410 errs: nil, 6411 actionable: true, 6412 }, 6413 { 6414 name: "Empty/Actionable", 6415 errs: nil, 6416 actionable: true, 6417 }, 6418 { 6419 name: "Error/Actionable", 6420 errs: []error{fmt.Errorf("err-a")}, 6421 actionable: true, 6422 }, 6423 { 6424 name: "Errors/Actionable", 6425 errs: []error{fmt.Errorf("err-a"), fmt.Errorf("err-b")}, 6426 actionable: true, 6427 }, 6428 { 6429 name: "ContainsUnadoptable/NotActionable", 6430 errs: []error{fmt.Errorf("err-a"), olmerrors.UnadoptableError{}}, 6431 actionable: false, 6432 }, 6433 } 6434 6435 for _, tt := range tests { 6436 t.Run(tt.name, func(t *testing.T) { 6437 op := &Operator{} 6438 aggregate := utilerrors.NewAggregate(tt.errs) 6439 require.Equal(t, tt.actionable, op.apiServiceResourceErrorActionable(aggregate)) 6440 }) 6441 } 6442 } 6443 6444 func crdWithConversionWebhook(crd *apiextensionsv1.CustomResourceDefinition, caBundle []byte) *apiextensionsv1.CustomResourceDefinition { 6445 crd.Spec.Conversion = &apiextensionsv1.CustomResourceConversion{ 6446 Strategy: "Webhook", 6447 Webhook: &apiextensionsv1.WebhookConversion{ 6448 ConversionReviewVersions: []string{"v1beta1"}, 6449 ClientConfig: &apiextensionsv1.WebhookClientConfig{ 6450 CABundle: caBundle, 6451 }, 6452 }, 6453 } 6454 return crd 6455 } 6456 6457 func csvWithConversionWebhook(csv *v1alpha1.ClusterServiceVersion, deploymentName string, conversionCRDs []string) *v1alpha1.ClusterServiceVersion { 6458 return csvWithWebhook(csv, deploymentName, conversionCRDs, v1alpha1.ConversionWebhook) 6459 } 6460 6461 func csvWithValidatingAdmissionWebhook(csv *v1alpha1.ClusterServiceVersion, deploymentName string, conversionCRDs []string) *v1alpha1.ClusterServiceVersion { 6462 return csvWithWebhook(csv, deploymentName, conversionCRDs, v1alpha1.ValidatingAdmissionWebhook) 6463 } 6464 6465 func csvWithMutatingAdmissionWebhook(csv *v1alpha1.ClusterServiceVersion, deploymentName string, conversionCRDs []string) *v1alpha1.ClusterServiceVersion { 6466 return csvWithWebhook(csv, deploymentName, conversionCRDs, v1alpha1.MutatingAdmissionWebhook) 6467 } 6468 6469 func csvWithWebhook(csv *v1alpha1.ClusterServiceVersion, deploymentName string, conversionCRDs []string, webhookType v1alpha1.WebhookAdmissionType) *v1alpha1.ClusterServiceVersion { 6470 sideEffectNone := admissionregistrationv1.SideEffectClassNone 6471 targetPort := intstr.FromInt(443) 6472 csv.Spec.WebhookDefinitions = []v1alpha1.WebhookDescription{ 6473 { 6474 Type: webhookType, 6475 DeploymentName: deploymentName, 6476 ContainerPort: 443, 6477 TargetPort: &targetPort, 6478 SideEffects: &sideEffectNone, 6479 ConversionCRDs: conversionCRDs, 6480 AdmissionReviewVersions: []string{"v1beta1"}, 6481 }, 6482 } 6483 return csv 6484 } 6485 6486 func TestGetReplacementChain(t *testing.T) { 6487 CSV := func(name, replaces string) *v1alpha1.ClusterServiceVersion { 6488 return &v1alpha1.ClusterServiceVersion{ 6489 ObjectMeta: metav1.ObjectMeta{ 6490 Name: name, 6491 }, 6492 Spec: v1alpha1.ClusterServiceVersionSpec{ 6493 Replaces: replaces, 6494 }, 6495 } 6496 } 6497 6498 for _, tc := range []struct { 6499 Name string 6500 From *v1alpha1.ClusterServiceVersion 6501 All map[string]*v1alpha1.ClusterServiceVersion 6502 Expected []string 6503 }{ 6504 { 6505 Name: "csv replaces itself", 6506 From: CSV("itself", "itself"), 6507 All: map[string]*v1alpha1.ClusterServiceVersion{ 6508 "itself": CSV("itself", "itself"), 6509 }, 6510 Expected: []string{"itself"}, 6511 }, 6512 { 6513 Name: "two csvs replace each other", 6514 From: CSV("a", "b"), 6515 All: map[string]*v1alpha1.ClusterServiceVersion{ 6516 "a": CSV("a", "b"), 6517 "b": CSV("b", "a"), 6518 }, 6519 Expected: []string{"a", "b"}, 6520 }, 6521 { 6522 Name: "starting from head of chain without cycles", 6523 From: CSV("a", "b"), 6524 All: map[string]*v1alpha1.ClusterServiceVersion{ 6525 "a": CSV("a", "b"), 6526 "b": CSV("b", "c"), 6527 "c": CSV("c", ""), 6528 }, 6529 Expected: []string{"a", "b", "c"}, 6530 }, 6531 } { 6532 t.Run(tc.Name, func(t *testing.T) { 6533 assert := assert.New(t) 6534 var actual []string 6535 for name := range (&Operator{}).getReplacementChain(tc.From, tc.All) { 6536 actual = append(actual, name) 6537 } 6538 assert.ElementsMatch(tc.Expected, actual) 6539 }) 6540 } 6541 }