Apply filters to PublisherController

- Ask the controller package to only call the Sync() method for
  the specific objects in which this controller is interested
This commit is contained in:
Ryan Richard 2020-07-30 17:16:09 -07:00
parent 5aebb76146
commit 733f80b7ae
2 changed files with 177 additions and 25 deletions

View File

@ -30,6 +30,25 @@ const (
configName = "placeholder-name-config" configName = "placeholder-name-config"
) )
func nameAndNamespaceExactMatchFilterFactory(name, namespace string) controller.FilterFuncs {
objMatchesFunc := func(obj metav1.Object) bool {
return obj.GetName() == name && obj.GetNamespace() == namespace
}
return controller.FilterFuncs{
AddFunc: objMatchesFunc,
UpdateFunc: func(oldObj, newObj metav1.Object) bool {
return objMatchesFunc(oldObj) || objMatchesFunc(newObj)
},
DeleteFunc: objMatchesFunc,
}
}
// Same signature as controller.WithInformer().
type withInformerOptionFunc func(
getter controller.InformerGetter,
filter controller.Filter,
opt controller.InformerOption) controller.Option
type publisherController struct { type publisherController struct {
namespace string namespace string
placeholderClient placeholderclientset.Interface placeholderClient placeholderclientset.Interface
@ -42,6 +61,7 @@ func NewPublisherController(
placeholderClient placeholderclientset.Interface, placeholderClient placeholderclientset.Interface,
configMapInformer corev1informers.ConfigMapInformer, configMapInformer corev1informers.ConfigMapInformer,
loginDiscoveryConfigInformer placeholderv1alpha1informers.LoginDiscoveryConfigInformer, loginDiscoveryConfigInformer placeholderv1alpha1informers.LoginDiscoveryConfigInformer,
withInformer withInformerOptionFunc,
) controller.Controller { ) controller.Controller {
return controller.New( return controller.New(
controller.Config{ controller.Config{
@ -53,14 +73,14 @@ func NewPublisherController(
loginDiscoveryConfigInformer: loginDiscoveryConfigInformer, loginDiscoveryConfigInformer: loginDiscoveryConfigInformer,
}, },
}, },
controller.WithInformer( withInformer(
configMapInformer, configMapInformer,
controller.FilterFuncs{}, // TODO fix this and write tests nameAndNamespaceExactMatchFilterFactory(clusterInfoName, clusterInfoNamespace),
controller.InformerOption{}, controller.InformerOption{},
), ),
controller.WithInformer( withInformer(
loginDiscoveryConfigInformer, loginDiscoveryConfigInformer,
controller.FilterFuncs{}, // TODO fix this and write tests nameAndNamespaceExactMatchFilterFactory(configName, namespace),
controller.InformerOption{}, controller.InformerOption{},
), ),
) )

View File

@ -29,8 +29,157 @@ import (
placeholderinformers "github.com/suzerain-io/placeholder-name-client-go/pkg/generated/informers/externalversions" placeholderinformers "github.com/suzerain-io/placeholder-name-client-go/pkg/generated/informers/externalversions"
) )
func TestRun(t *testing.T) { type ObservableWithInformerOption struct {
spec.Run(t, "publisher", func(t *testing.T, when spec.G, it spec.S) { InformerToFilterMap map[controller.InformerGetter]controller.Filter
}
func NewObservableWithInformerOption() *ObservableWithInformerOption {
return &ObservableWithInformerOption{
InformerToFilterMap: make(map[controller.InformerGetter]controller.Filter),
}
}
func (owi *ObservableWithInformerOption) WithInformer(
getter controller.InformerGetter,
filter controller.Filter,
opt controller.InformerOption) controller.Option {
owi.InformerToFilterMap[getter] = filter
return controller.WithInformer(getter, filter, opt)
}
func TestInformerFilters(t *testing.T) {
spec.Run(t, "informer filters", func(t *testing.T, when spec.G, it spec.S) {
const installedInNamespace = "some-namespace"
var r *require.Assertions
var observableWithInformerOption *ObservableWithInformerOption
var configMapInformerFilter controller.Filter
var loginDiscoveryConfigInformerFilter controller.Filter
it.Before(func() {
r = require.New(t)
observableWithInformerOption = NewObservableWithInformerOption()
configMapInformer := kubeinformers.NewSharedInformerFactory(nil, 0).Core().V1().ConfigMaps()
loginDiscoveryConfigInformer := placeholderinformers.NewSharedInformerFactory(nil, 0).Placeholder().V1alpha1().LoginDiscoveryConfigs()
_ = NewPublisherController(
installedInNamespace,
nil,
configMapInformer,
loginDiscoveryConfigInformer,
observableWithInformerOption.WithInformer, // make it possible to observe the behavior of the Filters
)
configMapInformerFilter = observableWithInformerOption.InformerToFilterMap[configMapInformer]
loginDiscoveryConfigInformerFilter = observableWithInformerOption.InformerToFilterMap[loginDiscoveryConfigInformer]
})
when("watching ConfigMap objects", func() {
var subject controller.Filter
var target, wrongNamespace, wrongName, unrelated *corev1.ConfigMap
it.Before(func() {
subject = configMapInformerFilter
target = &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "cluster-info", Namespace: "kube-public"}}
wrongNamespace = &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "cluster-info", Namespace: "wrong-namespace"}}
wrongName = &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "wrong-name", Namespace: "kube-public"}}
unrelated = &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "wrong-name", Namespace: "wrong-namespace"}}
})
when("the target ConfigMap changes", func() {
it("returns true to trigger the sync method", func() {
r.True(subject.Add(target))
r.True(subject.Update(target, unrelated))
r.True(subject.Update(unrelated, target))
r.True(subject.Delete(target))
})
})
when("a ConfigMap from another namespace changes", func() {
it("returns false to avoid triggering the sync method", func() {
r.False(subject.Add(wrongNamespace))
r.False(subject.Update(wrongNamespace, unrelated))
r.False(subject.Update(unrelated, wrongNamespace))
r.False(subject.Delete(wrongNamespace))
})
})
when("a ConfigMap with a different name changes", func() {
it("returns false to avoid triggering the sync method", func() {
r.False(subject.Add(wrongName))
r.False(subject.Update(wrongName, unrelated))
r.False(subject.Update(unrelated, wrongName))
r.False(subject.Delete(wrongName))
})
})
when("a ConfigMap with a different name and a different namespace changes", func() {
it("returns false to avoid triggering the sync method", func() {
r.False(subject.Add(unrelated))
r.False(subject.Update(unrelated, unrelated))
r.False(subject.Delete(unrelated))
})
})
})
when("watching LoginDiscoveryConfig objects", func() {
var subject controller.Filter
var target, wrongNamespace, wrongName, unrelated *placeholderv1alpha1.LoginDiscoveryConfig
it.Before(func() {
subject = loginDiscoveryConfigInformerFilter
target = &placeholderv1alpha1.LoginDiscoveryConfig{
ObjectMeta: metav1.ObjectMeta{Name: "placeholder-name-config", Namespace: installedInNamespace},
}
wrongNamespace = &placeholderv1alpha1.LoginDiscoveryConfig{
ObjectMeta: metav1.ObjectMeta{Name: "placeholder-name-config", Namespace: "wrong-namespace"},
}
wrongName = &placeholderv1alpha1.LoginDiscoveryConfig{
ObjectMeta: metav1.ObjectMeta{Name: "wrong-name", Namespace: installedInNamespace},
}
unrelated = &placeholderv1alpha1.LoginDiscoveryConfig{
ObjectMeta: metav1.ObjectMeta{Name: "wrong-name", Namespace: "wrong-namespace"},
}
})
when("the target LoginDiscoveryConfig changes", func() {
it("returns true to trigger the sync method", func() {
r.True(subject.Add(target))
r.True(subject.Update(target, unrelated))
r.True(subject.Update(unrelated, target))
r.True(subject.Delete(target))
})
})
when("a LoginDiscoveryConfig from another namespace changes", func() {
it("returns false to avoid triggering the sync method", func() {
r.False(subject.Add(wrongNamespace))
r.False(subject.Update(wrongNamespace, unrelated))
r.False(subject.Update(unrelated, wrongNamespace))
r.False(subject.Delete(wrongNamespace))
})
})
when("a LoginDiscoveryConfig with a different name changes", func() {
it("returns false to avoid triggering the sync method", func() {
r.False(subject.Add(wrongName))
r.False(subject.Update(wrongName, unrelated))
r.False(subject.Update(unrelated, wrongName))
r.False(subject.Delete(wrongName))
})
})
when("a LoginDiscoveryConfig with a different name and a different namespace changes", func() {
it("returns false to avoid triggering the sync method", func() {
r.False(subject.Add(unrelated))
r.False(subject.Update(unrelated, unrelated))
r.False(subject.Delete(unrelated))
})
})
})
}, spec.Parallel(), spec.Report(report.Terminal{}))
}
func TestSync(t *testing.T) {
spec.Run(t, "Sync", func(t *testing.T, when spec.G, it spec.S) {
const installedInNamespace = "some-namespace" const installedInNamespace = "some-namespace"
var r *require.Assertions var r *require.Assertions
@ -89,6 +238,7 @@ func TestRun(t *testing.T) {
placeholderAPIClient, placeholderAPIClient,
kubeInformers.Core().V1().ConfigMaps(), kubeInformers.Core().V1().ConfigMaps(),
placeholderInformers.Placeholder().V1alpha1().LoginDiscoveryConfigs(), placeholderInformers.Placeholder().V1alpha1().LoginDiscoveryConfigs(),
controller.WithInformer,
) )
syncContext = &controller.Context{ syncContext = &controller.Context{
@ -131,7 +281,7 @@ func TestRun(t *testing.T) {
}) })
when("the LoginDiscoveryConfig does not already exist", func() { when("the LoginDiscoveryConfig does not already exist", func() {
it.Focus("creates a LoginDiscoveryConfig", func() { it("creates a LoginDiscoveryConfig", func() {
startInformersAndController() startInformersAndController()
err := controller.TestSync(t, subject, *syncContext) err := controller.TestSync(t, subject, *syncContext)
r.NoError(err) r.NoError(err)
@ -287,23 +437,5 @@ func TestRun(t *testing.T) {
r.Empty(placeholderAPIClient.Actions()) r.Empty(placeholderAPIClient.Actions())
}) })
}) })
when("getting the cluster-info ConfigMap in the kube-public namespace fails", func() {
it.Before(func() {
kubeInformerClient.PrependReactor(
"get",
"configmaps",
func(_ coretesting.Action) (bool, runtime.Object, error) {
return true, nil, errors.New("get failed")
},
)
})
it("returns an error", func() {
startInformersAndController()
err := controller.TestSync(t, subject, *syncContext)
r.EqualError(err, "failed to get cluster-info configmap: get failed")
})
})
}, spec.Parallel(), spec.Report(report.Terminal{})) }, spec.Parallel(), spec.Report(report.Terminal{}))
} }