Skip to content

Commit

Permalink
Merge pull request #1710 from alvaroaleman/default-selector
Browse files Browse the repository at this point in the history
✨ Allow configuring a default cache selector
  • Loading branch information
k8s-ci-robot authored Dec 1, 2021
2 parents 8f43f26 + 0b60488 commit 7e7bf8c
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 6 deletions.
11 changes: 9 additions & 2 deletions pkg/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ type Informer interface {
type ObjectSelector internal.Selector

// SelectorsByObject associate a client.Object's GVK to a field/label selector.
// There is also `DefaultSelector` to set a global default (which will be overridden by
// a more specific setting here, if any).
type SelectorsByObject map[client.Object]ObjectSelector

// Options are the optional arguments for creating a new InformersMap object.
Expand Down Expand Up @@ -117,6 +119,10 @@ type Options struct {
// [2] https://pkg.go.dev/k8s.io/apimachinery/pkg/fields#Set
SelectorsByObject SelectorsByObject

// DefaultSelector will be used as selectors for all object types
// that do not have a selector in SelectorsByObject defined.
DefaultSelector ObjectSelector

// UnsafeDisableDeepCopyByObject indicates not to deep copy objects during get or
// list objects per GVK at the specified object.
// Be very careful with this, when enabled you must DeepCopy any object before mutating it,
Expand All @@ -132,7 +138,7 @@ func New(config *rest.Config, opts Options) (Cache, error) {
if err != nil {
return nil, err
}
selectorsByGVK, err := convertToSelectorsByGVK(opts.SelectorsByObject, opts.Scheme)
selectorsByGVK, err := convertToSelectorsByGVK(opts.SelectorsByObject, opts.DefaultSelector, opts.Scheme)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -194,7 +200,7 @@ func defaultOpts(config *rest.Config, opts Options) (Options, error) {
return opts, nil
}

func convertToSelectorsByGVK(selectorsByObject SelectorsByObject, scheme *runtime.Scheme) (internal.SelectorsByGVK, error) {
func convertToSelectorsByGVK(selectorsByObject SelectorsByObject, defaultSelector ObjectSelector, scheme *runtime.Scheme) (internal.SelectorsByGVK, error) {
selectorsByGVK := internal.SelectorsByGVK{}
for object, selector := range selectorsByObject {
gvk, err := apiutil.GVKForObject(object, scheme)
Expand All @@ -203,6 +209,7 @@ func convertToSelectorsByGVK(selectorsByObject SelectorsByObject, scheme *runtim
}
selectorsByGVK[gvk] = internal.Selector(selector)
}
selectorsByGVK[schema.GroupVersionKind{}] = internal.Selector(defaultSelector)
return selectorsByGVK, nil
}

Expand Down
98 changes: 98 additions & 0 deletions pkg/cache/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"reflect"
"sort"
"strconv"

. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
Expand Down Expand Up @@ -73,6 +74,33 @@ func createPodWithLabels(name, namespace string, restartPolicy corev1.RestartPol
return pod
}

func createSvc(name, namespace string, cl client.Client) client.Object {
svc := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{{Port: 1}},
},
}
err := cl.Create(context.Background(), svc)
Expect(err).NotTo(HaveOccurred())
return svc
}

func createSA(name, namespace string, cl client.Client) client.Object {
sa := &corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
}
err := cl.Create(context.Background(), sa)
Expect(err).NotTo(HaveOccurred())
return sa
}

func createPod(name, namespace string, restartPolicy corev1.RestartPolicy) client.Object {
return createPodWithLabels(name, namespace, restartPolicy, nil)
}
Expand All @@ -93,6 +121,76 @@ var _ = Describe("Multi-Namespace Informer Cache", func() {
var _ = Describe("Informer Cache without DeepCopy", func() {
CacheTest(cache.New, cache.Options{UnsafeDisableDeepCopyByObject: cache.DisableDeepCopyByObject{cache.ObjectAll{}: true}})
})
var _ = Describe("Cache with selectors", func() {
defer GinkgoRecover()
var (
informerCache cache.Cache
informerCacheCtx context.Context
informerCacheCancel context.CancelFunc
)

BeforeEach(func() {
informerCacheCtx, informerCacheCancel = context.WithCancel(context.Background())
Expect(cfg).NotTo(BeNil())
cl, err := client.New(cfg, client.Options{})
Expect(err).NotTo(HaveOccurred())
err = ensureNamespace(testNamespaceOne, cl)
Expect(err).NotTo(HaveOccurred())
err = ensureNamespace(testNamespaceTwo, cl)
Expect(err).NotTo(HaveOccurred())
for idx, namespace := range []string{testNamespaceOne, testNamespaceTwo} {
_ = createSA("test-sa-"+strconv.Itoa(idx), namespace, cl)
_ = createSvc("test-svc-"+strconv.Itoa(idx), namespace, cl)
}

opts := cache.Options{
SelectorsByObject: cache.SelectorsByObject{
&corev1.ServiceAccount{}: {Field: fields.OneTermEqualSelector("metadata.namespace", testNamespaceOne)},
},
DefaultSelector: cache.ObjectSelector{Field: fields.OneTermEqualSelector("metadata.namespace", testNamespaceTwo)},
}

By("creating the informer cache")
informerCache, err = cache.New(cfg, opts)
Expect(err).NotTo(HaveOccurred())
By("running the cache and waiting for it to sync")
// pass as an arg so that we don't race between close and re-assign
go func(ctx context.Context) {
defer GinkgoRecover()
Expect(informerCache.Start(ctx)).To(Succeed())
}(informerCacheCtx)
Expect(informerCache.WaitForCacheSync(informerCacheCtx)).To(BeTrue())
})

AfterEach(func() {
ctx := context.Background()
cl, err := client.New(cfg, client.Options{})
Expect(err).NotTo(HaveOccurred())
for idx, namespace := range []string{testNamespaceOne, testNamespaceTwo} {
err = cl.Delete(ctx, &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: "test-sa-" + strconv.Itoa(idx)}})
Expect(err).NotTo(HaveOccurred())
err = cl.Delete(ctx, &corev1.Service{ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: "test-svc-" + strconv.Itoa(idx)}})
Expect(err).NotTo(HaveOccurred())
}
informerCacheCancel()
})

It("Should list serviceaccounts and find exactly one in namespace "+testNamespaceOne, func() {
var sas corev1.ServiceAccountList
err := informerCache.List(informerCacheCtx, &sas)
Expect(err).NotTo(HaveOccurred())
Expect(len(sas.Items)).To(Equal(1))
Expect(sas.Items[0].Namespace).To(Equal(testNamespaceOne))
})

It("Should list services and find exactly one in namespace "+testNamespaceTwo, func() {
var svcs corev1.ServiceList
err := informerCache.List(informerCacheCtx, &svcs)
Expect(err).NotTo(HaveOccurred())
Expect(len(svcs.Items)).To(Equal(1))
Expect(svcs.Items[0].Namespace).To(Equal(testNamespaceTwo))
})
})

func CacheTest(createCacheFunc func(config *rest.Config, opts cache.Options) (cache.Cache, error), opts cache.Options) {
Describe("Cache test", func() {
Expand Down
8 changes: 4 additions & 4 deletions pkg/cache/internal/informers_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,19 +277,19 @@ func createStructuredListWatch(gvk schema.GroupVersionKind, ip *specificInformer
// Create a new ListWatch for the obj
return &cache.ListWatch{
ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) {
ip.selectors[gvk].ApplyToList(&opts)
ip.selectors.forGVK(gvk).ApplyToList(&opts)
res := listObj.DeepCopyObject()
namespace := restrictNamespaceBySelector(ip.namespace, ip.selectors[gvk])
namespace := restrictNamespaceBySelector(ip.namespace, ip.selectors.forGVK(gvk))
isNamespaceScoped := namespace != "" && mapping.Scope.Name() != meta.RESTScopeNameRoot
err := client.Get().NamespaceIfScoped(namespace, isNamespaceScoped).Resource(mapping.Resource.Resource).VersionedParams(&opts, ip.paramCodec).Do(ctx).Into(res)
return res, err
},
// Setup the watch function
WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) {
ip.selectors[gvk].ApplyToList(&opts)
ip.selectors.forGVK(gvk).ApplyToList(&opts)
// Watch needs to be set to true separately
opts.Watch = true
namespace := restrictNamespaceBySelector(ip.namespace, ip.selectors[gvk])
namespace := restrictNamespaceBySelector(ip.namespace, ip.selectors.forGVK(gvk))
isNamespaceScoped := namespace != "" && mapping.Scope.Name() != meta.RESTScopeNameRoot
return client.Get().NamespaceIfScoped(namespace, isNamespaceScoped).Resource(mapping.Resource.Resource).VersionedParams(&opts, ip.paramCodec).Watch(ctx)
},
Expand Down
11 changes: 11 additions & 0 deletions pkg/cache/internal/selector.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ import (
// SelectorsByGVK associate a GroupVersionKind to a field/label selector.
type SelectorsByGVK map[schema.GroupVersionKind]Selector

func (s SelectorsByGVK) forGVK(gvk schema.GroupVersionKind) Selector {
if specific, found := s[gvk]; found {
return specific
}
if defaultSelector, found := s[schema.GroupVersionKind{}]; found {
return defaultSelector
}

return Selector{}
}

// Selector specify the label/field selector to fill in ListOptions.
type Selector struct {
Label labels.Selector
Expand Down

0 comments on commit 7e7bf8c

Please sign in to comment.