Skip to content

Commit d3bfaf2

Browse files
authored
Only update namespaces when OperatorGroup labels need to change. (#2809)
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 d3bfaf2

File tree

2 files changed

+219
-13
lines changed

2 files changed

+219
-13
lines changed

pkg/controller/operators/olm/operator.go

+35-13
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

+184
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.
@@ -229,6 +231,18 @@ func withRegObjs(regObjs ...runtime.Object) fakeOperatorOption {
229231
}
230232
}
231233

234+
func withActionLog(log *[]clienttesting.Action) fakeOperatorOption {
235+
return func(config *fakeOperatorConfig) {
236+
config.actionLog = log
237+
}
238+
}
239+
240+
func withLogger(logger *logrus.Logger) fakeOperatorOption {
241+
return func(config *fakeOperatorConfig) {
242+
config.logger = logger
243+
}
244+
}
245+
232246
// NewFakeOperator creates and starts a new operator using fake clients.
233247
func NewFakeOperator(ctx context.Context, options ...fakeOperatorOption) (*Operator, error) {
234248
// Apply options to default config
@@ -247,6 +261,7 @@ func NewFakeOperator(ctx context.Context, options ...fakeOperatorOption) (*Opera
247261
recorder: &record.FakeRecorder{},
248262
// default expected namespaces
249263
namespaces: []string{"default", "kube-system", "kube-public"},
264+
actionLog: &[]clienttesting.Action{},
250265
}
251266
for _, option := range options {
252267
option(config)
@@ -258,6 +273,10 @@ func NewFakeOperator(ctx context.Context, options ...fakeOperatorOption) (*Opera
258273
// For now, directly use a SimpleClientset instead.
259274
k8sClientFake := k8sfake.NewSimpleClientset(config.k8sObjs...)
260275
k8sClientFake.Resources = apiResourcesForObjects(append(config.extObjs, config.regObjs...))
276+
k8sClientFake.PrependReactor("*", "*", clienttesting.ReactionFunc(func(action clienttesting.Action) (bool, runtime.Object, error) {
277+
*config.actionLog = append(*config.actionLog, action)
278+
return false, nil, nil
279+
}))
261280
config.operatorClient = operatorclient.NewClient(k8sClientFake, apiextensionsfake.NewSimpleClientset(config.extObjs...), apiregistrationfake.NewSimpleClientset(config.regObjs...))
262281
config.configClient = configfake.NewSimpleClientset()
263282

@@ -3936,6 +3955,171 @@ func TestUpdates(t *testing.T) {
39363955
}
39373956
}
39383957

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

0 commit comments

Comments
 (0)