Skip to content

Commit 188ee1a

Browse files
timflannagannjhale
andauthored
pkg/controller: Fix panic when creating cluster-scoped RBAC in OG controller (#2349)
* pkg/controller: Fix panic when creating cluster-scoped RBAC in OG controller Fixes [#2091](#2091). This is a follow-up to [#2309](#2309) that attempted to fix the original issue. When checking whether the ClusterRole/ClusterRoleBinding resources already exist, we're also checking whether the existing labels are owned by the CSV we're currently handling. When accessing the "cr" or "crb" variables that the Create calls output, a panic is produced as we're attempting to access the meta.Labels key from those resources, except those resources themselves are nil. Update the check to verify that the cr/crb variables are not nil before attempting to access those object's labels. The testing fake client may need to be updated in the future to handle returning these resources properly. Signed-off-by: timflannagan <[email protected]> * test(og): de-flake sync unit tests Co-authored-by: Nick Hale <[email protected]>
1 parent 8b0ac13 commit 188ee1a

File tree

2 files changed

+108
-69
lines changed

2 files changed

+108
-69
lines changed

pkg/controller/operators/olm/operator_test.go

+102-61
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import (
3434
"k8s.io/apimachinery/pkg/runtime"
3535
"k8s.io/apimachinery/pkg/types"
3636
utilclock "k8s.io/apimachinery/pkg/util/clock"
37-
"k8s.io/apimachinery/pkg/util/diff"
3837
utilerrors "k8s.io/apimachinery/pkg/util/errors"
3938
"k8s.io/apimachinery/pkg/util/intstr"
4039
"k8s.io/apimachinery/pkg/util/wait"
@@ -3667,9 +3666,13 @@ func TestUpdates(t *testing.T) {
36673666
}
36683667

36693668
func TestSyncOperatorGroups(t *testing.T) {
3670-
logrus.SetLevel(logrus.DebugLevel)
3669+
logrus.SetLevel(logrus.WarnLevel)
36713670
clockFake := utilclock.NewFakeClock(time.Date(2006, time.January, 2, 15, 4, 5, 0, time.FixedZone("MST", -7*3600)))
36723671
now := metav1.NewTime(clockFake.Now().UTC())
3672+
const (
3673+
timeout = 5 * time.Second
3674+
tick = 50 * time.Millisecond
3675+
)
36733676

36743677
operatorNamespace := "operator-ns"
36753678
targetNamespace := "target-ns"
@@ -4305,6 +4308,10 @@ func TestSyncOperatorGroups(t *testing.T) {
43054308
expectedEqual: true,
43064309
initial: initial{
43074310
operatorGroup: &v1.OperatorGroup{
4311+
TypeMeta: metav1.TypeMeta{
4312+
Kind: v1.OperatorGroupKind,
4313+
APIVersion: v1.GroupVersion.String(),
4314+
},
43084315
ObjectMeta: metav1.ObjectMeta{
43094316
Name: "operator-group-1",
43104317
Namespace: operatorNamespace,
@@ -4334,6 +4341,10 @@ func TestSyncOperatorGroups(t *testing.T) {
43344341
final: final{objects: map[string][]runtime.Object{
43354342
operatorNamespace: {
43364343
&v1.OperatorGroup{
4344+
TypeMeta: metav1.TypeMeta{
4345+
Kind: v1.OperatorGroupKind,
4346+
APIVersion: v1.GroupVersion.String(),
4347+
},
43374348
ObjectMeta: metav1.ObjectMeta{
43384349
Name: "operator-group-1",
43394350
Namespace: operatorNamespace,
@@ -4415,124 +4426,154 @@ func TestSyncOperatorGroups(t *testing.T) {
44154426
"AllNamespaces InstallModeType not supported, cannot configure to watch all namespaces",
44164427
now),
44174428
},
4418-
"": {},
4419-
targetNamespace: {},
44204429
}},
44214430
},
44224431
}
44234432

4433+
copyObjs := func(objs []runtime.Object) []runtime.Object {
4434+
if len(objs) < 1 {
4435+
return nil
4436+
}
4437+
4438+
copied := make([]runtime.Object, len(objs))
4439+
for i, obj := range objs {
4440+
copied[i] = obj.DeepCopyObject()
4441+
}
4442+
4443+
return copied
4444+
}
4445+
44244446
for _, tt := range tests {
44254447
t.Run(tt.name, func(t *testing.T) {
4426-
namespaces := []string{}
44274448
// Pick out Namespaces
4449+
var namespaces []string
44284450
for _, obj := range tt.initial.k8sObjs {
44294451
if ns, ok := obj.(*corev1.Namespace); ok {
44304452
namespaces = append(namespaces, ns.GetName())
44314453
}
44324454
}
44334455

4434-
// Append operatorGroup to initialObjs
4435-
tt.initial.clientObjs = append(tt.initial.clientObjs, tt.initial.operatorGroup)
4456+
// DeepCopy test fixtures to prevent test case pollution
4457+
var (
4458+
operatorGroup = tt.initial.operatorGroup.DeepCopy()
4459+
clientObjs = copyObjs(append(tt.initial.clientObjs, operatorGroup))
4460+
k8sObjs = copyObjs(tt.initial.k8sObjs)
4461+
extObjs = copyObjs(tt.initial.crds)
4462+
regObjs = copyObjs(tt.initial.apis)
4463+
)
44364464

44374465
// Create test operator
4438-
ctx, cancel := context.WithCancel(context.TODO())
4466+
ctx, cancel := context.WithCancel(context.Background())
44394467
defer cancel()
4468+
44404469
op, err := NewFakeOperator(
44414470
ctx,
44424471
withClock(clockFake),
44434472
withNamespaces(namespaces...),
44444473
withOperatorNamespace(operatorNamespace),
4445-
withClientObjs(tt.initial.clientObjs...),
4446-
withK8sObjs(tt.initial.k8sObjs...),
4447-
withExtObjs(tt.initial.crds...),
4448-
withRegObjs(tt.initial.apis...),
4474+
withClientObjs(clientObjs...),
4475+
withK8sObjs(k8sObjs...),
4476+
withExtObjs(extObjs...),
4477+
withRegObjs(regObjs...),
44494478
)
44504479
require.NoError(t, err)
44514480

4452-
simulateSuccessfulRollout := func(csv *v1alpha1.ClusterServiceVersion, client operatorclient.ClientInterface) {
4453-
// get the deployment, which should exist
4454-
dep, err := client.GetDeployment(tt.initial.operatorGroup.GetNamespace(), deploymentName)
4481+
simulateSuccessfulRollout := func(csv *v1alpha1.ClusterServiceVersion) {
4482+
// Get the deployment, which should exist
4483+
namespace := operatorGroup.GetNamespace()
4484+
dep, err := op.opClient.GetDeployment(namespace, deploymentName)
44554485
require.NoError(t, err)
44564486

4457-
// force it healthy
4487+
// Force it healthy
44584488
dep.Status.Replicas = 1
44594489
dep.Status.UpdatedReplicas = 1
44604490
dep.Status.AvailableReplicas = 1
44614491
dep.Status.Conditions = []appsv1.DeploymentCondition{{
44624492
Type: appsv1.DeploymentAvailable,
44634493
Status: corev1.ConditionTrue,
44644494
}}
4465-
_, err = client.KubernetesInterface().AppsV1().Deployments(tt.initial.operatorGroup.GetNamespace()).UpdateStatus(context.TODO(), dep, metav1.UpdateOptions{})
4495+
_, err = op.opClient.KubernetesInterface().AppsV1().Deployments(namespace).UpdateStatus(ctx, dep, metav1.UpdateOptions{})
4496+
require.NoError(t, err)
4497+
4498+
// Wait for the lister cache to catch up
4499+
err = wait.PollImmediateWithContext(ctx, tick, timeout, func(ctx context.Context) (bool, error) {
4500+
deployment, err := op.lister.AppsV1().DeploymentLister().Deployments(namespace).Get(dep.GetName())
4501+
if err != nil || deployment == nil {
4502+
return false, err
4503+
}
4504+
4505+
for _, condition := range deployment.Status.Conditions {
4506+
if condition.Type == appsv1.DeploymentAvailable {
4507+
return condition.Status == corev1.ConditionTrue, nil
4508+
}
4509+
}
4510+
4511+
return false, nil
4512+
})
44664513
require.NoError(t, err)
44674514
}
44684515

4469-
err = op.syncOperatorGroups(tt.initial.operatorGroup)
4516+
err = op.syncOperatorGroups(operatorGroup)
44704517
require.NoError(t, err)
44714518

4472-
// wait on operator group updated status to be in the cache as it is required for later CSV operations
4473-
err = wait.PollImmediate(1*time.Millisecond, 5*time.Second, func() (bool, error) {
4474-
operatorGroup, err := op.lister.OperatorsV1().OperatorGroupLister().OperatorGroups(tt.initial.operatorGroup.GetNamespace()).Get(tt.initial.operatorGroup.GetName())
4519+
// Wait on operator group updated status to be in the cache as it is required for later CSV operations
4520+
err = wait.PollImmediateWithContext(ctx, tick, timeout, func(ctx context.Context) (bool, error) {
4521+
og, err := op.lister.OperatorsV1().OperatorGroupLister().OperatorGroups(operatorGroup.GetNamespace()).Get(operatorGroup.GetName())
44754522
if err != nil {
44764523
return false, err
44774524
}
44784525
sort.Strings(tt.expectedStatus.Namespaces)
4479-
sort.Strings(operatorGroup.Status.Namespaces)
4480-
if !reflect.DeepEqual(tt.expectedStatus, operatorGroup.Status) {
4526+
sort.Strings(og.Status.Namespaces)
4527+
if !reflect.DeepEqual(tt.expectedStatus, og.Status) {
44814528
return false, err
44824529
}
4530+
4531+
operatorGroup = og
4532+
44834533
return true, nil
44844534
})
44854535
require.NoError(t, err)
44864536

4487-
// this must be done twice to have annotateCSVs run in syncOperatorGroups
4488-
// and to catch provided API changes
4489-
err = op.syncOperatorGroups(tt.initial.operatorGroup)
4537+
// This must be done (at least) twice to have annotateCSVs run in syncOperatorGroups and to catch provided API changes
4538+
// syncOperatorGroups is eventually consistent and may return errors until the cache has caught up with the cluster (fake client here)
4539+
wait.PollImmediateWithContext(ctx, tick, timeout, func(ctx context.Context) (bool, error) { // Throw away timeout errors since any timeout will coincide with err != nil anyway
4540+
err = op.syncOperatorGroups(operatorGroup)
4541+
return err == nil, nil
4542+
})
44904543
require.NoError(t, err)
44914544

4492-
// Sync csvs enough to get them back to succeeded state
4493-
for i := 0; i < 16; i++ {
4494-
opGroupCSVs, err := op.client.OperatorsV1alpha1().ClusterServiceVersions(operatorNamespace).List(context.TODO(), metav1.ListOptions{})
4495-
require.NoError(t, err)
4545+
// Sync csvs enough to get them back to a succeeded state
4546+
err = wait.PollImmediateWithContext(ctx, tick, timeout, func(ctx context.Context) (bool, error) {
4547+
csvs, err := op.client.OperatorsV1alpha1().ClusterServiceVersions(operatorNamespace).List(ctx, metav1.ListOptions{})
4548+
if err != nil {
4549+
return false, err
4550+
}
44964551

4497-
for i, obj := range opGroupCSVs.Items {
4498-
if obj.Status.Phase == v1alpha1.CSVPhaseInstalling {
4499-
simulateSuccessfulRollout(&obj, op.opClient)
4552+
for _, csv := range csvs.Items {
4553+
if csv.Status.Phase == v1alpha1.CSVPhaseInstalling {
4554+
simulateSuccessfulRollout(&csv)
45004555
}
4501-
err = op.syncClusterServiceVersion(&obj)
4502-
require.NoError(t, err, "%#v", obj)
45034556

4504-
err = op.syncCopyCSV(&obj)
4505-
if !tt.ignoreCopyError {
4506-
require.NoError(t, err, "%#v", obj)
4557+
if err := op.syncClusterServiceVersion(&csv); err != nil {
4558+
return false, err
45074559
}
45084560

4509-
if i == 0 {
4510-
err = wait.PollImmediate(1*time.Millisecond, 10*time.Second, func() (bool, error) {
4511-
for namespace, objects := range tt.final.objects {
4512-
if err := RequireObjectsInCache(t, op.lister, namespace, objects, false); err != nil {
4513-
return false, nil
4514-
}
4515-
}
4516-
return true, nil
4517-
})
4518-
require.NoError(t, err)
4561+
if err := op.syncCopyCSV(&csv); err != nil && !tt.ignoreCopyError {
4562+
return false, err
45194563
}
4564+
}
45204565

4521-
if i == 16 {
4522-
err = wait.PollImmediate(1*time.Millisecond, 10*time.Second, func() (bool, error) {
4523-
for namespace, objects := range tt.final.objects {
4524-
if err := RequireObjectsInCache(t, op.lister, namespace, objects, true); err != nil {
4525-
return false, nil
4526-
}
4527-
}
4528-
return true, nil
4529-
})
4530-
require.NoError(t, err)
4566+
for namespace, objects := range tt.final.objects {
4567+
if err := RequireObjectsInCache(t, op.lister, namespace, objects, true); err != nil {
4568+
return false, nil
45314569
}
45324570
}
4533-
}
45344571

4535-
operatorGroup, err := op.client.OperatorsV1().OperatorGroups(tt.initial.operatorGroup.GetNamespace()).Get(context.TODO(), tt.initial.operatorGroup.GetName(), metav1.GetOptions{})
4572+
return true, nil
4573+
})
4574+
require.NoError(t, err)
4575+
4576+
operatorGroup, err = op.client.OperatorsV1().OperatorGroups(operatorGroup.GetNamespace()).Get(ctx, operatorGroup.GetName(), metav1.GetOptions{})
45364577
require.NoError(t, err)
45374578
sort.Strings(tt.expectedStatus.Namespaces)
45384579
sort.Strings(operatorGroup.Status.Namespaces)
@@ -4799,7 +4840,7 @@ func RequireObjectsInNamespace(t *testing.T, opClient operatorclient.ClientInter
47994840
require.Failf(t, "couldn't find expected object", "%#v", object)
48004841
}
48014842
require.NoError(t, err, "couldn't fetch %s %v", namespace, object)
4802-
require.True(t, reflect.DeepEqual(object, fetched), diff.ObjectDiff(object, fetched))
4843+
require.True(t, reflect.DeepEqual(object, fetched), cmp.Diff(object, fetched))
48034844
}
48044845
}
48054846

pkg/controller/operators/olm/operatorgroup.go

+6-8
Original file line numberDiff line numberDiff line change
@@ -532,12 +532,11 @@ func (a *Operator) ensureSingletonRBAC(operatorNamespace string, csv *v1alpha1.C
532532
}
533533
// TODO: this should do something smarter if the cluster role already exists
534534
if cr, err := a.opClient.CreateClusterRole(clusterRole); err != nil {
535-
// if the CR already exists, but the label is correct, the cache is just behind
536-
if k8serrors.IsAlreadyExists(err) && ownerutil.IsOwnedByLabel(cr, csv) {
535+
// If the CR already exists, but the label is correct, the cache is just behind
536+
if k8serrors.IsAlreadyExists(err) && cr != nil && ownerutil.IsOwnedByLabel(cr, csv) {
537537
continue
538-
} else {
539-
return err
540538
}
539+
return err
541540
}
542541
a.logger.Debug("created cluster role")
543542
}
@@ -572,12 +571,11 @@ func (a *Operator) ensureSingletonRBAC(operatorNamespace string, csv *v1alpha1.C
572571
}
573572
// TODO: this should do something smarter if the cluster role binding already exists
574573
if crb, err := a.opClient.CreateClusterRoleBinding(clusterRoleBinding); err != nil {
575-
// if the CR already exists, but the label is correct, the cache is just behind
576-
if k8serrors.IsAlreadyExists(err) && ownerutil.IsOwnedByLabel(crb, csv) {
574+
// If the CRB already exists, but the label is correct, the cache is just behind
575+
if k8serrors.IsAlreadyExists(err) && crb != nil && ownerutil.IsOwnedByLabel(crb, csv) {
577576
continue
578-
} else {
579-
return err
580577
}
578+
return err
581579
}
582580
}
583581
}

0 commit comments

Comments
 (0)