Skip to content

Commit 6d39f65

Browse files
committed
feat(og): Project OperatorGroup errors to status conditions
Currently, errors associated with ServiceAccount not found or multiple OperatorGroup in the same namespace are not recorded in OperatorGroup's status. This PR will project those errors to OG's status conditions to improve the UX. Signed-off-by: Vu Dinh <[email protected]>
1 parent d9ec51a commit 6d39f65

File tree

3 files changed

+269
-0
lines changed

3 files changed

+269
-0
lines changed

pkg/controller/operators/olm/operator.go

+32
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
rbacv1 "k8s.io/api/rbac/v1"
1616
extinf "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions"
1717
k8serrors "k8s.io/apimachinery/pkg/api/errors"
18+
meta "k8s.io/apimachinery/pkg/api/meta"
1819
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1920
"k8s.io/apimachinery/pkg/labels"
2021
"k8s.io/apimachinery/pkg/runtime"
@@ -827,6 +828,37 @@ func (a *Operator) syncNamespace(obj interface{}) error {
827828
return err
828829
}
829830

831+
// Query OG in this namespace
832+
groups, err := a.lister.OperatorsV1().OperatorGroupLister().OperatorGroups(namespace.GetName()).List(labels.Everything())
833+
834+
// Check if there is a stale multiple OG condition and clear it if existed.
835+
if len(groups) == 1 {
836+
og := groups[0]
837+
if c := meta.FindStatusCondition(og.Status.Conditions, v1.MutlipleOperatorGroupCondition); c != nil {
838+
meta.RemoveStatusCondition(&og.Status.Conditions, v1.MutlipleOperatorGroupCondition)
839+
_, err = a.client.OperatorsV1().OperatorGroups(namespace.GetName()).UpdateStatus(context.TODO(), og, metav1.UpdateOptions{})
840+
if err != nil {
841+
logger.Warnf("fail to upgrade operator group status og=%s with condition %+v: %s", og.GetName(), c, err.Error())
842+
}
843+
}
844+
} else if len(groups) > 1 {
845+
// Add to all OG's status conditions to indicate they're multiple OGs in the
846+
// same namespace which is not allowed.
847+
cond := metav1.Condition{
848+
Type: v1.MutlipleOperatorGroupCondition,
849+
Status: metav1.ConditionTrue,
850+
Reason: v1.MultipleOperatorGroupsReason,
851+
Message: "Multiple OperatorGroup found in the same namespace",
852+
}
853+
for _, og := range groups {
854+
meta.SetStatusCondition(&og.Status.Conditions, cond)
855+
_, err = a.client.OperatorsV1().OperatorGroups(namespace.GetName()).UpdateStatus(context.TODO(), og, metav1.UpdateOptions{})
856+
if err != nil {
857+
logger.Warnf("fail to upgrade operator group status og=%s with condition %+v: %s", og.GetName(), cond, err.Error())
858+
}
859+
}
860+
}
861+
830862
for _, group := range operatorGroupList {
831863
namespaceSet := resolver.NewNamespaceSet(group.Status.Namespaces)
832864

pkg/controller/operators/olm/operator_test.go

+212
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2929
apiextensionsfake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
3030
k8serrors "k8s.io/apimachinery/pkg/api/errors"
31+
meta "k8s.io/apimachinery/pkg/api/meta"
3132
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3233
"k8s.io/apimachinery/pkg/labels"
3334
"k8s.io/apimachinery/pkg/runtime"
@@ -4537,6 +4538,217 @@ func TestSyncOperatorGroups(t *testing.T) {
45374538
}
45384539
}
45394540

4541+
func TestOperatorGroupConditions(t *testing.T) {
4542+
logrus.SetLevel(logrus.DebugLevel)
4543+
clockFake := utilclock.NewFakeClock(time.Date(2006, time.January, 2, 15, 4, 5, 0, time.FixedZone("MST", -7*3600)))
4544+
4545+
operatorNamespace := "operator-ns"
4546+
opNamespace := &corev1.Namespace{
4547+
ObjectMeta: metav1.ObjectMeta{
4548+
Name: operatorNamespace,
4549+
},
4550+
}
4551+
serviceAccount := serviceAccount("sa", operatorNamespace)
4552+
4553+
type initial struct {
4554+
operatorGroup *v1.OperatorGroup
4555+
clientObjs []runtime.Object
4556+
k8sObjs []runtime.Object
4557+
}
4558+
4559+
tests := []struct {
4560+
initial initial
4561+
name string
4562+
expectedConditions []metav1.Condition
4563+
expectError bool
4564+
}{
4565+
{
4566+
name: "ValidOperatorGroup/NoServiceAccount",
4567+
initial: initial{
4568+
operatorGroup: &v1.OperatorGroup{
4569+
ObjectMeta: metav1.ObjectMeta{
4570+
Name: "operator-group-1",
4571+
Namespace: operatorNamespace,
4572+
UID: "135e02a5-a7e2-44e7-abaa-88c63838993c",
4573+
},
4574+
Spec: v1.OperatorGroupSpec{
4575+
TargetNamespaces: []string{operatorNamespace},
4576+
},
4577+
},
4578+
k8sObjs: []runtime.Object{
4579+
&corev1.Namespace{
4580+
ObjectMeta: metav1.ObjectMeta{
4581+
Name: operatorNamespace,
4582+
},
4583+
},
4584+
},
4585+
},
4586+
expectError: false,
4587+
expectedConditions: []metav1.Condition{},
4588+
},
4589+
{
4590+
name: "ValidOperatorGroup/ValidServiceAccount",
4591+
initial: initial{
4592+
operatorGroup: &v1.OperatorGroup{
4593+
ObjectMeta: metav1.ObjectMeta{
4594+
Name: "operator-group-1",
4595+
Namespace: operatorNamespace,
4596+
UID: "135e02a5-a7e2-44e7-abaa-88c63838993c",
4597+
},
4598+
Spec: v1.OperatorGroupSpec{
4599+
ServiceAccountName: "sa",
4600+
TargetNamespaces: []string{operatorNamespace},
4601+
},
4602+
},
4603+
k8sObjs: []runtime.Object{
4604+
&corev1.Namespace{
4605+
ObjectMeta: metav1.ObjectMeta{
4606+
Name: operatorNamespace,
4607+
},
4608+
},
4609+
serviceAccount,
4610+
},
4611+
},
4612+
expectError: false,
4613+
expectedConditions: []metav1.Condition{},
4614+
},
4615+
{
4616+
name: "BadOperatorGroup/MissingServiceAccount",
4617+
initial: initial{
4618+
operatorGroup: &v1.OperatorGroup{
4619+
ObjectMeta: metav1.ObjectMeta{
4620+
Name: "operator-group-1",
4621+
Namespace: operatorNamespace,
4622+
UID: "135e02a5-a7e2-44e7-abaa-88c63838993c",
4623+
},
4624+
Spec: v1.OperatorGroupSpec{
4625+
ServiceAccountName: "nonexistingSA",
4626+
TargetNamespaces: []string{operatorNamespace},
4627+
},
4628+
},
4629+
k8sObjs: []runtime.Object{
4630+
&corev1.Namespace{
4631+
ObjectMeta: metav1.ObjectMeta{
4632+
Name: operatorNamespace,
4633+
},
4634+
},
4635+
},
4636+
},
4637+
expectError: true,
4638+
expectedConditions: []metav1.Condition{
4639+
metav1.Condition{
4640+
Type: v1.OperatorGroupServiceAccountCondition,
4641+
Status: metav1.ConditionTrue,
4642+
Reason: v1.OperatorGroupServiceAccountReason,
4643+
Message: "ServiceAccount nonexistingSA not found",
4644+
},
4645+
},
4646+
},
4647+
{
4648+
name: "BadOperatorGroup/MultipleOperatorGroups",
4649+
initial: initial{
4650+
operatorGroup: &v1.OperatorGroup{
4651+
ObjectMeta: metav1.ObjectMeta{
4652+
Name: "operator-group-1",
4653+
Namespace: operatorNamespace,
4654+
UID: "135e02a5-a7e2-44e7-abaa-88c63838993c",
4655+
},
4656+
Spec: v1.OperatorGroupSpec{
4657+
TargetNamespaces: []string{operatorNamespace},
4658+
},
4659+
},
4660+
clientObjs: []runtime.Object{
4661+
&v1.OperatorGroup{
4662+
ObjectMeta: metav1.ObjectMeta{
4663+
Name: "operator-group-2",
4664+
Namespace: operatorNamespace,
4665+
UID: "cdc9643e-7c52-4f7c-ae75-28ccb6aec97d",
4666+
},
4667+
Spec: v1.OperatorGroupSpec{
4668+
TargetNamespaces: []string{operatorNamespace},
4669+
},
4670+
},
4671+
},
4672+
k8sObjs: []runtime.Object{
4673+
&corev1.Namespace{
4674+
ObjectMeta: metav1.ObjectMeta{
4675+
Name: operatorNamespace,
4676+
},
4677+
},
4678+
},
4679+
},
4680+
expectError: true,
4681+
expectedConditions: []metav1.Condition{
4682+
metav1.Condition{
4683+
Type: v1.MutlipleOperatorGroupCondition,
4684+
Status: metav1.ConditionTrue,
4685+
Reason: v1.MultipleOperatorGroupsReason,
4686+
Message: "Multiple OperatorGroup found in the same namespace",
4687+
},
4688+
},
4689+
},
4690+
}
4691+
4692+
for _, tt := range tests {
4693+
t.Run(tt.name, func(t *testing.T) {
4694+
namespaces := []string{}
4695+
// Pick out Namespaces
4696+
for _, obj := range tt.initial.k8sObjs {
4697+
if ns, ok := obj.(*corev1.Namespace); ok {
4698+
namespaces = append(namespaces, ns.GetName())
4699+
}
4700+
}
4701+
4702+
// Append operatorGroup to initialObjs
4703+
tt.initial.clientObjs = append(tt.initial.clientObjs, tt.initial.operatorGroup)
4704+
4705+
// Create test operator
4706+
ctx, cancel := context.WithCancel(context.TODO())
4707+
defer cancel()
4708+
op, err := NewFakeOperator(
4709+
ctx,
4710+
withClock(clockFake),
4711+
withNamespaces(namespaces...),
4712+
withOperatorNamespace(operatorNamespace),
4713+
withClientObjs(tt.initial.clientObjs...),
4714+
withK8sObjs(tt.initial.k8sObjs...),
4715+
)
4716+
require.NoError(t, err)
4717+
4718+
err = op.syncOperatorGroups(tt.initial.operatorGroup)
4719+
if !tt.expectError {
4720+
require.NoError(t, err)
4721+
}
4722+
4723+
// wait on operator group updated status to be in the cache
4724+
err = wait.PollImmediate(1*time.Millisecond, 5*time.Second, func() (bool, error) {
4725+
og, err := op.lister.OperatorsV1().OperatorGroupLister().OperatorGroups(tt.initial.operatorGroup.GetNamespace()).Get(tt.initial.operatorGroup.GetName())
4726+
if err != nil || og == nil {
4727+
return false, err
4728+
}
4729+
return true, nil
4730+
})
4731+
require.NoError(t, err)
4732+
4733+
// sync namespace
4734+
err = op.syncNamespace(opNamespace)
4735+
require.NoError(t, err)
4736+
4737+
operatorGroup, err := op.client.OperatorsV1().OperatorGroups(tt.initial.operatorGroup.GetNamespace()).Get(context.TODO(), tt.initial.operatorGroup.GetName(), metav1.GetOptions{})
4738+
require.NoError(t, err)
4739+
assert.Equal(t, len(tt.expectedConditions), len(operatorGroup.Status.Conditions))
4740+
if len(tt.expectedConditions) > 0 {
4741+
for _, cond := range tt.expectedConditions {
4742+
c := meta.FindStatusCondition(operatorGroup.Status.Conditions, cond.Type)
4743+
assert.Equal(t, cond.Status, c.Status)
4744+
assert.Equal(t, cond.Reason, c.Reason)
4745+
assert.Equal(t, cond.Message, c.Message)
4746+
}
4747+
}
4748+
})
4749+
}
4750+
}
4751+
45404752
func RequireObjectsInCache(t *testing.T, lister operatorlister.OperatorLister, namespace string, objects []runtime.Object, doCompare bool) error {
45414753
for _, object := range objects {
45424754
var err error

pkg/lib/scoped/syncer.go

+25
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77

88
"github.com/sirupsen/logrus"
99
corev1 "k8s.io/api/core/v1"
10+
k8serrors "k8s.io/apimachinery/pkg/api/errors"
11+
meta "k8s.io/apimachinery/pkg/api/meta"
1012
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1113
"k8s.io/apimachinery/pkg/runtime"
1214
"k8s.io/apimachinery/pkg/util/clock"
@@ -62,6 +64,9 @@ func (s *UserDefinedServiceAccountSyncer) SyncOperatorGroup(in *v1.OperatorGroup
6264
return
6365
}
6466

67+
// Remove ServiceAccount condition if existed
68+
meta.RemoveStatusCondition(&in.Status.Conditions, v1.OperatorGroupServiceAccountCondition)
69+
6570
// User must have removed ServiceAccount from the spec. We need to
6671
// rest Status to a nil reference.
6772
out, err = s.update(in, nil)
@@ -74,6 +79,21 @@ func (s *UserDefinedServiceAccountSyncer) SyncOperatorGroup(in *v1.OperatorGroup
7479
// A service account has been specified, we need to update the status.
7580
sa, err := s.client.KubernetesInterface().CoreV1().ServiceAccounts(namespace).Get(context.TODO(), serviceAccountName, metav1.GetOptions{})
7681
if err != nil {
82+
if k8serrors.IsNotFound(err) {
83+
// Set OG's status condition to indicate SA is not found
84+
cond := metav1.Condition{
85+
Type: v1.OperatorGroupServiceAccountCondition,
86+
Status: metav1.ConditionTrue,
87+
Reason: v1.OperatorGroupServiceAccountReason,
88+
Message: fmt.Sprintf("ServiceAccount %s not found", serviceAccountName),
89+
}
90+
91+
meta.SetStatusCondition(&in.Status.Conditions, cond)
92+
_, uerr := s.update(in, nil)
93+
if uerr != nil {
94+
logger.Warnf("fail to upgrade operator group status og=%s with condition %+v: %s", in.GetName(), cond, uerr.Error())
95+
}
96+
}
7797
err = fmt.Errorf("failed to get service account, sa=%s %v", serviceAccountName, err)
7898
return
7999
}
@@ -88,6 +108,11 @@ func (s *UserDefinedServiceAccountSyncer) SyncOperatorGroup(in *v1.OperatorGroup
88108
return
89109
}
90110

111+
// Remove SA not found condition if found
112+
if c := meta.FindStatusCondition(in.Status.Conditions, v1.OperatorGroupServiceAccountCondition); c != nil {
113+
meta.RemoveStatusCondition(&in.Status.Conditions, v1.OperatorGroupServiceAccountCondition)
114+
}
115+
91116
out, err = s.update(in, ref)
92117
if err != nil {
93118
err = fmt.Errorf("failed to set status.serviceAccount, sa=%s %v", serviceAccountName, err)

0 commit comments

Comments
 (0)