Skip to content

Commit 2d1f5da

Browse files
committed
Only update namespaces when OperatorGroup labels need to change.
Even when the update request is a semantic no-op, it generates a little apiserver and etcd load. Also, other controllers are watching namespaces and will perform redundant work in response to the generated watch event and resourceVersion bump. Signed-off-by: Ben Luddy <[email protected]>
1 parent 0b7970c commit 2d1f5da

File tree

2 files changed

+213
-13
lines changed

2 files changed

+213
-13
lines changed

pkg/controller/operators/olm/operator.go

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -902,36 +902,58 @@ func (a *Operator) syncNamespace(obj interface{}) error {
902902
"name": namespace.GetName(),
903903
})
904904

905-
// Remove existing OperatorGroup labels
906-
for label := range namespace.GetLabels() {
907-
if operatorsv1.IsOperatorGroupLabel(label) {
908-
delete(namespace.Labels, label)
909-
}
910-
}
911-
912905
operatorGroupList, err := a.lister.OperatorsV1().OperatorGroupLister().List(labels.Everything())
913906
if err != nil {
914907
logger.WithError(err).Warn("lister failed")
915908
return err
916909
}
917910

911+
desiredGroupLabels := make(map[string]string)
918912
for _, group := range operatorGroupList {
919913
namespaceSet := NewNamespaceSet(group.Status.Namespaces)
920914

921915
// Apply the label if not an All Namespaces OperatorGroup.
922916
if namespaceSet.Contains(namespace.GetName()) && !namespaceSet.IsAllNamespaces() {
923-
if namespace.Labels == nil {
924-
namespace.Labels = make(map[string]string, 1)
925-
}
926-
ogLabelKey, ogLabelValue, err := group.OGLabelKeyAndValue()
917+
k, v, err := group.OGLabelKeyAndValue()
927918
if err != nil {
928919
return err
929920
}
930-
namespace.Labels[ogLabelKey] = ogLabelValue
921+
desiredGroupLabels[k] = v
922+
}
923+
}
924+
925+
if changed := func() bool {
926+
for ke, ve := range namespace.Labels {
927+
if !operatorsv1.IsOperatorGroupLabel(ke) {
928+
continue
929+
}
930+
if vd, ok := desiredGroupLabels[ke]; !ok || vd != ve {
931+
return true
932+
}
931933
}
934+
for kd, vd := range desiredGroupLabels {
935+
if ve, ok := namespace.Labels[kd]; !ok || ve != vd {
936+
return true
937+
}
938+
}
939+
return false
940+
}(); !changed {
941+
return nil
942+
}
943+
944+
namespace = namespace.DeepCopy()
945+
for k := range namespace.Labels {
946+
if operatorsv1.IsOperatorGroupLabel(k) {
947+
delete(namespace.Labels, k)
948+
}
949+
}
950+
if namespace.Labels == nil && len(desiredGroupLabels) > 0 {
951+
namespace.Labels = make(map[string]string)
952+
}
953+
for k, v := range desiredGroupLabels {
954+
namespace.Labels[k] = v
932955
}
933956

934-
// Update the Namespace
935957
_, err = a.opClient.KubernetesInterface().CoreV1().Namespaces().Update(context.TODO(), namespace, metav1.UpdateOptions{})
936958

937959
return err

pkg/controller/operators/olm/operator_test.go

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import (
6565
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/queueinformer"
6666
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/scoped"
6767
opregistry "github.com/operator-framework/operator-registry/pkg/registry"
68+
clienttesting "k8s.io/client-go/testing"
6869
)
6970

7071
type TestStrategy struct{}
@@ -166,6 +167,7 @@ type fakeOperatorConfig struct {
166167
k8sObjs []runtime.Object
167168
extObjs []runtime.Object
168169
regObjs []runtime.Object
170+
actionLog *[]clienttesting.Action
169171
}
170172

171173
// fakeOperatorOption applies an option to the given fake operator configuration.
@@ -214,6 +216,7 @@ func withClientObjs(clientObjs ...runtime.Object) fakeOperatorOption {
214216
func withK8sObjs(k8sObjs ...runtime.Object) fakeOperatorOption {
215217
return func(config *fakeOperatorConfig) {
216218
config.k8sObjs = k8sObjs
219+
217220
}
218221
}
219222

@@ -229,6 +232,12 @@ func withRegObjs(regObjs ...runtime.Object) fakeOperatorOption {
229232
}
230233
}
231234

235+
func withActionLog(log *[]clienttesting.Action) fakeOperatorOption {
236+
return func(config *fakeOperatorConfig) {
237+
config.actionLog = log
238+
}
239+
}
240+
232241
// NewFakeOperator creates and starts a new operator using fake clients.
233242
func NewFakeOperator(ctx context.Context, options ...fakeOperatorOption) (*Operator, error) {
234243
// Apply options to default config
@@ -247,6 +256,7 @@ func NewFakeOperator(ctx context.Context, options ...fakeOperatorOption) (*Opera
247256
recorder: &record.FakeRecorder{},
248257
// default expected namespaces
249258
namespaces: []string{"default", "kube-system", "kube-public"},
259+
actionLog: &[]clienttesting.Action{},
250260
}
251261
for _, option := range options {
252262
option(config)
@@ -258,6 +268,10 @@ func NewFakeOperator(ctx context.Context, options ...fakeOperatorOption) (*Opera
258268
// For now, directly use a SimpleClientset instead.
259269
k8sClientFake := k8sfake.NewSimpleClientset(config.k8sObjs...)
260270
k8sClientFake.Resources = apiResourcesForObjects(append(config.extObjs, config.regObjs...))
271+
k8sClientFake.PrependReactor("*", "*", clienttesting.ReactionFunc(func(action clienttesting.Action) (bool, runtime.Object, error) {
272+
*config.actionLog = append(*config.actionLog, action)
273+
return false, nil, nil
274+
}))
261275
config.operatorClient = operatorclient.NewClient(k8sClientFake, apiextensionsfake.NewSimpleClientset(config.extObjs...), apiregistrationfake.NewSimpleClientset(config.regObjs...))
262276
config.configClient = configfake.NewSimpleClientset()
263277

@@ -3936,6 +3950,170 @@ func TestUpdates(t *testing.T) {
39363950
}
39373951
}
39383952

3953+
type tDotLogWriter struct {
3954+
*testing.T
3955+
}
3956+
3957+
func (w tDotLogWriter) Write(p []byte) (int, error) {
3958+
w.T.Logf("%s", string(p))
3959+
return len(p), nil
3960+
}
3961+
3962+
func testLogrusLogger(t *testing.T) *logrus.Logger {
3963+
l := logrus.New()
3964+
l.SetOutput(tDotLogWriter{t})
3965+
return l
3966+
}
3967+
3968+
func TestSyncNamespace(t *testing.T) {
3969+
namespace := func(name string, labels map[string]string) corev1.Namespace {
3970+
return corev1.Namespace{
3971+
ObjectMeta: metav1.ObjectMeta{
3972+
Name: name,
3973+
Labels: labels,
3974+
},
3975+
}
3976+
}
3977+
3978+
operatorgroup := func(name string, targets []string) operatorsv1.OperatorGroup {
3979+
return operatorsv1.OperatorGroup{
3980+
ObjectMeta: metav1.ObjectMeta{
3981+
Name: name,
3982+
UID: types.UID(fmt.Sprintf("%s-uid", name)),
3983+
},
3984+
Status: operatorsv1.OperatorGroupStatus{
3985+
Namespaces: targets,
3986+
},
3987+
}
3988+
}
3989+
3990+
for _, tc := range []struct {
3991+
name string
3992+
before corev1.Namespace
3993+
operatorgroups []operatorsv1.OperatorGroup
3994+
noop bool
3995+
expected []string
3996+
}{
3997+
{
3998+
name: "adds missing labels",
3999+
before: namespace("test-namespace", map[string]string{"unrelated": ""}),
4000+
operatorgroups: []operatorsv1.OperatorGroup{
4001+
operatorgroup("test-group-1", []string{"test-namespace"}),
4002+
operatorgroup("test-group-2", []string{"test-namespace"}),
4003+
},
4004+
expected: []string{
4005+
"olm.operatorgroup.uid/test-group-1-uid",
4006+
"olm.operatorgroup.uid/test-group-2-uid",
4007+
"unrelated",
4008+
},
4009+
},
4010+
{
4011+
name: "removes stale labels",
4012+
before: namespace("test-namespace", map[string]string{
4013+
"olm.operatorgroup.uid/test-group-1-uid": "",
4014+
"olm.operatorgroup.uid/test-group-2-uid": "",
4015+
}),
4016+
operatorgroups: []operatorsv1.OperatorGroup{
4017+
operatorgroup("test-group-2", []string{"test-namespace"}),
4018+
},
4019+
expected: []string{
4020+
"olm.operatorgroup.uid/test-group-2-uid",
4021+
},
4022+
},
4023+
{
4024+
name: "does not add label if namespace is not a target namespace",
4025+
before: namespace("test-namespace", nil),
4026+
operatorgroups: []operatorsv1.OperatorGroup{
4027+
operatorgroup("test-group-1", []string{"test-namespace"}),
4028+
operatorgroup("test-group-2", []string{"not-test-namespace"}),
4029+
},
4030+
expected: []string{
4031+
"olm.operatorgroup.uid/test-group-1-uid",
4032+
},
4033+
},
4034+
{
4035+
name: "no update if labels are in sync",
4036+
before: namespace("test-namespace", map[string]string{
4037+
"olm.operatorgroup.uid/test-group-1-uid": "",
4038+
"olm.operatorgroup.uid/test-group-2-uid": "",
4039+
}),
4040+
operatorgroups: []operatorsv1.OperatorGroup{
4041+
operatorgroup("test-group-1", []string{"test-namespace"}),
4042+
operatorgroup("test-group-2", []string{"test-namespace"}),
4043+
},
4044+
noop: true,
4045+
expected: []string{
4046+
"olm.operatorgroup.uid/test-group-1-uid",
4047+
"olm.operatorgroup.uid/test-group-2-uid",
4048+
},
4049+
},
4050+
} {
4051+
t.Run(tc.name, func(t *testing.T) {
4052+
ctx, cancel := context.WithCancel(context.Background())
4053+
defer cancel()
4054+
4055+
var ogs []runtime.Object
4056+
for i := range tc.operatorgroups {
4057+
ogs = append(ogs, &tc.operatorgroups[i])
4058+
}
4059+
4060+
var actions []clienttesting.Action
4061+
4062+
o, err := NewFakeOperator(
4063+
ctx,
4064+
withClientObjs(ogs...),
4065+
withK8sObjs(&tc.before),
4066+
withActionLog(&actions),
4067+
)
4068+
if err != nil {
4069+
t.Fatalf("setup failed: %v", err)
4070+
}
4071+
4072+
actions = actions[:0]
4073+
4074+
err = o.syncNamespace(&tc.before)
4075+
if err != nil {
4076+
t.Fatalf("unexpected error: %v", err)
4077+
}
4078+
4079+
if tc.noop {
4080+
for _, action := range actions {
4081+
if action.GetResource().Resource != "namespaces" {
4082+
continue
4083+
}
4084+
if namer, ok := action.(interface{ GetName() string }); ok {
4085+
if namer.GetName() != tc.before.Name {
4086+
continue
4087+
}
4088+
} else if objer, ok := action.(interface{ GetObject() runtime.Object }); ok {
4089+
if namer, ok := objer.GetObject().(interface{ GetName() string }); ok {
4090+
if namer.GetName() != tc.before.Name {
4091+
continue
4092+
}
4093+
}
4094+
}
4095+
t.Errorf("unexpected client operation: %v", action)
4096+
}
4097+
}
4098+
4099+
after, err := o.opClient.KubernetesInterface().CoreV1().Namespaces().Get(ctx, tc.before.Name, metav1.GetOptions{})
4100+
if err != nil {
4101+
t.Fatalf("unexpected error: %v", err)
4102+
}
4103+
4104+
if len(after.Labels) != len(tc.expected) {
4105+
t.Errorf("expected %d labels, got %d", len(tc.expected), len(after.Labels))
4106+
}
4107+
4108+
for _, l := range tc.expected {
4109+
if _, ok := after.Labels[l]; !ok {
4110+
t.Errorf("missing expected label %q", l)
4111+
}
4112+
}
4113+
})
4114+
}
4115+
}
4116+
39394117
func TestSyncOperatorGroups(t *testing.T) {
39404118
logrus.SetLevel(logrus.WarnLevel)
39414119
clockFake := utilclocktesting.NewFakeClock(time.Date(2006, time.January, 2, 15, 4, 5, 0, time.FixedZone("MST", -7*3600)))

0 commit comments

Comments
 (0)