Skip to content

Commit f831e3d

Browse files
committed
multiNamespaceCache: support custom newCache funcs per namespace
Signed-off-by: Joe Lanford <[email protected]>
1 parent f035121 commit f831e3d

File tree

3 files changed

+333
-20
lines changed

3 files changed

+333
-20
lines changed

pkg/cache/cache.go

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,14 @@ import (
2222
"time"
2323

2424
"k8s.io/apimachinery/pkg/api/meta"
25+
"k8s.io/apimachinery/pkg/fields"
26+
"k8s.io/apimachinery/pkg/labels"
2527
"k8s.io/apimachinery/pkg/runtime"
2628
"k8s.io/apimachinery/pkg/runtime/schema"
2729
"k8s.io/client-go/kubernetes/scheme"
2830
"k8s.io/client-go/rest"
2931
toolscache "k8s.io/client-go/tools/cache"
32+
3033
"sigs.k8s.io/controller-runtime/pkg/cache/internal"
3134
"sigs.k8s.io/controller-runtime/pkg/client"
3235
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
@@ -188,14 +191,69 @@ func BuilderWithOptions(options Options) NewCacheFunc {
188191
if options.Namespace == "" {
189192
options.Namespace = opts.Namespace
190193
}
191-
if opts.Resync == nil {
192-
opts.Resync = options.Resync
194+
for obj, selector := range opts.SelectorsByObject {
195+
options.SelectorsByObject[obj] = combineSelector(options.SelectorsByObject[obj], selector)
196+
}
197+
options.DefaultSelector = combineSelector(options.DefaultSelector, opts.DefaultSelector)
198+
for obj, deepCopy := range opts.UnsafeDisableDeepCopyByObject {
199+
if _, ok := options.UnsafeDisableDeepCopyByObject[obj]; !ok {
200+
options.UnsafeDisableDeepCopyByObject[obj] = deepCopy
201+
}
202+
}
203+
for obj, transform := range opts.TransformByObject {
204+
if _, ok := options.TransformByObject[obj]; !ok {
205+
options.TransformByObject[obj] = transform
206+
}
207+
}
208+
if options.DefaultTransform == nil {
209+
options.DefaultTransform = opts.DefaultTransform
193210
}
194-
195211
return New(config, options)
196212
}
197213
}
198214

215+
func combineSelector(selectors ...ObjectSelector) ObjectSelector {
216+
ls := make([]labels.Selector, 0, len(selectors))
217+
fs := make([]fields.Selector, 0, len(selectors))
218+
for _, s := range selectors {
219+
ls = append(ls, s.Label)
220+
fs = append(fs, s.Field)
221+
}
222+
return ObjectSelector{
223+
Label: combineLabelSelectors(ls...),
224+
Field: combineFieldSelectors(fs...),
225+
}
226+
}
227+
228+
func combineLabelSelectors(ls ...labels.Selector) labels.Selector {
229+
allReqs := labels.Requirements{}
230+
for _, l := range ls {
231+
if l == nil {
232+
continue
233+
}
234+
reqs, _ := l.Requirements()
235+
allReqs = append(allReqs, reqs...)
236+
}
237+
return labels.NewSelector().Add(allReqs...)
238+
}
239+
240+
func combineFieldSelectors(fs ...fields.Selector) fields.Selector {
241+
nonNil := fs[:0]
242+
for _, f := range fs {
243+
if f == nil {
244+
continue
245+
}
246+
nonNil = append(nonNil, f)
247+
}
248+
if len(nonNil) == 0 {
249+
return nil
250+
}
251+
if len(nonNil) == 1 {
252+
return nonNil[0]
253+
}
254+
return fields.AndSelectors(nonNil...)
255+
}
256+
199257
func defaultOpts(config *rest.Config, opts Options) (Options, error) {
200258
// Use the default Kubernetes Scheme if unset
201259
if opts.Scheme == nil {

pkg/cache/cache_test.go

Lines changed: 197 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import (
2626
. "github.com/onsi/ginkgo"
2727
. "github.com/onsi/ginkgo/extensions/table"
2828
. "github.com/onsi/gomega"
29-
3029
corev1 "k8s.io/api/core/v1"
3130
apierrors "k8s.io/apimachinery/pkg/api/errors"
3231
"k8s.io/apimachinery/pkg/api/meta"
@@ -124,6 +123,203 @@ var _ = Describe("Informer Cache without DeepCopy", func() {
124123
CacheTest(cache.New, cache.Options{UnsafeDisableDeepCopyByObject: cache.DisableDeepCopyByObject{cache.ObjectAll{}: true}})
125124
})
126125

126+
var _ = Describe("ByNamespace Cache", func() {
127+
defer GinkgoRecover()
128+
var (
129+
informerCache cache.Cache
130+
informerCacheCtx context.Context
131+
informerCacheCancel context.CancelFunc
132+
133+
pod1a client.Object
134+
pod1b client.Object
135+
pod2a client.Object
136+
pod2b client.Object
137+
pod2c client.Object
138+
pod3a client.Object
139+
pod3b client.Object
140+
)
141+
142+
BeforeEach(func() {
143+
informerCacheCtx, informerCacheCancel = context.WithCancel(context.Background())
144+
Expect(cfg).NotTo(BeNil())
145+
cl, err := client.New(cfg, client.Options{})
146+
Expect(err).NotTo(HaveOccurred())
147+
err = ensureNamespace(testNamespaceOne, cl)
148+
Expect(err).NotTo(HaveOccurred())
149+
err = ensureNamespace(testNamespaceTwo, cl)
150+
Expect(err).NotTo(HaveOccurred())
151+
err = ensureNamespace(testNamespaceThree, cl)
152+
Expect(err).NotTo(HaveOccurred())
153+
err = ensureNode(testNodeOne, cl)
154+
Expect(err).NotTo(HaveOccurred())
155+
156+
// namespace 1 stuff
157+
pod1a = createPod("pod-1a", testNamespaceOne, corev1.RestartPolicyNever) // matches (everything matches)
158+
pod1b = createPodWithLabels("pod-1b", testNamespaceOne, corev1.RestartPolicyNever, map[string]string{"other-match": "true"}) // matches (everything matches)
159+
160+
// namespace 2 stuff
161+
pod2a = createPodWithLabels("pod-2a", testNamespaceTwo, corev1.RestartPolicyNever, map[string]string{"ns2-match": "false"}) // no match (does not match ns2 label selector)
162+
pod2b = createPodWithLabels("pod-2b", testNamespaceTwo, corev1.RestartPolicyNever, map[string]string{"ns2-match": "true"}) // matches (matches ns2 label selector)
163+
pod2c = createPodWithLabels("pod-2c", testNamespaceTwo, corev1.RestartPolicyNever, map[string]string{"other-match": "true"}) // no match (does not match ns2 label selector)
164+
165+
// namespace 3 stuff
166+
pod3a = createPodWithLabels("pod-3a", testNamespaceThree, corev1.RestartPolicyNever, map[string]string{"other-match": "false"}) // no match (does not match default cache label selector)
167+
pod3b = createPodWithLabels("pod-3b", testNamespaceThree, corev1.RestartPolicyNever, map[string]string{"other-match": "true"}) // matches (matches default cache label selector)
168+
169+
By("creating the informer cache")
170+
informerCache, err = cache.BuilderByNamespace(cache.ByNamespaceOptions{
171+
NewNamespaceCaches: map[string]cache.NewCacheFunc{
172+
// Everything in ns1
173+
testNamespaceOne: cache.New,
174+
175+
// Only things in ns2 with label "ns2-match"="true"
176+
testNamespaceTwo: cache.BuilderWithOptions(cache.Options{
177+
DefaultSelector: cache.ObjectSelector{
178+
Label: labels.Set{"ns2-match": "true"}.AsSelector(),
179+
},
180+
}),
181+
},
182+
// For all other namespaces, match "other-match"="true"
183+
NewDefaultNamespaceCache: cache.BuilderWithOptions(cache.Options{
184+
DefaultSelector: cache.ObjectSelector{
185+
Label: labels.Set{"other-match": "true"}.AsSelector(),
186+
},
187+
}),
188+
// For cluster-scoped objects, only match metadata.name = "test-node-1"
189+
NewClusterCache: cache.BuilderWithOptions(cache.Options{
190+
DefaultSelector: cache.ObjectSelector{
191+
Field: fields.OneTermEqualSelector("metadata.name", testNodeOne),
192+
},
193+
}),
194+
})(cfg, cache.Options{})
195+
Expect(err).NotTo(HaveOccurred())
196+
By("running the cache and waiting for it to sync")
197+
// pass as an arg so that we don't race between close and re-assign
198+
go func(ctx context.Context) {
199+
defer GinkgoRecover()
200+
Expect(informerCache.Start(ctx)).To(Succeed())
201+
}(informerCacheCtx)
202+
Expect(informerCache.WaitForCacheSync(informerCacheCtx)).To(BeTrue())
203+
})
204+
Describe("Get", func() {
205+
It("should get an item from a namespace cache", func() {
206+
pod := &corev1.Pod{}
207+
err := informerCache.Get(informerCacheCtx, client.ObjectKeyFromObject(pod1a), pod)
208+
Expect(err).NotTo(HaveOccurred())
209+
})
210+
It("should get an item from the default namespace cache", func() {
211+
pod := &corev1.Pod{}
212+
err := informerCache.Get(informerCacheCtx, client.ObjectKeyFromObject(pod3b), pod)
213+
Expect(err).NotTo(HaveOccurred())
214+
})
215+
It("should get a cluster-scoped item", func() {
216+
node := &corev1.Node{}
217+
err := informerCache.Get(informerCacheCtx, client.ObjectKey{Name: testNodeOne}, node)
218+
Expect(err).NotTo(HaveOccurred())
219+
})
220+
It("should not find an item from a namespace-specific cache if it is not matched", func() {
221+
pod := &corev1.Pod{}
222+
err := informerCache.Get(informerCacheCtx, client.ObjectKeyFromObject(pod2a), pod)
223+
Expect(apierrors.IsNotFound(err)).To(BeTrue())
224+
})
225+
It("should not find an item from the default namespace cache if it is not matched", func() {
226+
pod := &corev1.Pod{}
227+
err := informerCache.Get(informerCacheCtx, client.ObjectKeyFromObject(pod3a), pod)
228+
Expect(apierrors.IsNotFound(err)).To(BeTrue())
229+
})
230+
It("should not find an item at the cluster-scope if it is not matched", func() {
231+
ns := &corev1.Namespace{}
232+
err := informerCache.Get(informerCacheCtx, client.ObjectKey{Name: testNamespaceOne}, ns)
233+
Expect(apierrors.IsNotFound(err)).To(BeTrue())
234+
})
235+
})
236+
Describe("List", func() {
237+
When("Request is cluster-scoped", func() {
238+
It("Should list all pods and find exactly four", func() {
239+
var pods corev1.PodList
240+
err := informerCache.List(informerCacheCtx, &pods)
241+
Expect(err).NotTo(HaveOccurred())
242+
sort.Slice(pods.Items, func(i, j int) bool {
243+
if pods.Items[i].Namespace != pods.Items[j].Namespace {
244+
return pods.Items[i].Namespace < pods.Items[j].Namespace
245+
}
246+
return pods.Items[i].Name < pods.Items[j].Name
247+
})
248+
249+
Expect(pods.Items).To(HaveLen(4))
250+
Expect(pods.Items[0].Namespace).To(Equal(testNamespaceOne))
251+
Expect(pods.Items[0].Name).To(Equal("pod-1a"))
252+
Expect(pods.Items[1].Namespace).To(Equal(testNamespaceOne))
253+
Expect(pods.Items[1].Name).To(Equal("pod-1b"))
254+
Expect(pods.Items[2].Namespace).To(Equal(testNamespaceTwo))
255+
Expect(pods.Items[2].Name).To(Equal("pod-2b"))
256+
Expect(pods.Items[3].Namespace).To(Equal(testNamespaceThree))
257+
Expect(pods.Items[3].Name).To(Equal("pod-3b"))
258+
})
259+
It("Should list nodes and find exactly one", func() {
260+
var nodes corev1.NodeList
261+
err := informerCache.List(informerCacheCtx, &nodes)
262+
Expect(err).NotTo(HaveOccurred())
263+
Expect(nodes.Items).To(HaveLen(1))
264+
Expect(nodes.Items[0].Namespace).To(Equal(""))
265+
Expect(nodes.Items[0].Name).To(Equal(testNodeOne))
266+
})
267+
It("Should list namespaces and find none", func() {
268+
var namespaces corev1.NamespaceList
269+
err := informerCache.List(informerCacheCtx, &namespaces)
270+
Expect(err).NotTo(HaveOccurred())
271+
Expect(namespaces.Items).To(HaveLen(0))
272+
})
273+
})
274+
When("Request is namespace-scoped", func() {
275+
It("Should list pods in namespace one", func() {
276+
var pods corev1.PodList
277+
err := informerCache.List(informerCacheCtx, &pods, client.InNamespace(testNamespaceOne))
278+
Expect(err).NotTo(HaveOccurred())
279+
sort.Slice(pods.Items, func(i, j int) bool {
280+
if pods.Items[i].Namespace != pods.Items[j].Namespace {
281+
return pods.Items[i].Namespace < pods.Items[j].Namespace
282+
}
283+
return pods.Items[i].Name < pods.Items[j].Name
284+
})
285+
286+
Expect(pods.Items).To(HaveLen(2))
287+
Expect(pods.Items[0].Namespace).To(Equal(testNamespaceOne))
288+
Expect(pods.Items[0].Name).To(Equal("pod-1a"))
289+
Expect(pods.Items[1].Namespace).To(Equal(testNamespaceOne))
290+
Expect(pods.Items[1].Name).To(Equal("pod-1b"))
291+
})
292+
It("Should list pods in namespace two", func() {
293+
var pods corev1.PodList
294+
err := informerCache.List(informerCacheCtx, &pods, client.InNamespace(testNamespaceTwo))
295+
Expect(err).NotTo(HaveOccurred())
296+
Expect(pods.Items).To(HaveLen(1))
297+
Expect(pods.Items[0].Namespace).To(Equal(testNamespaceTwo))
298+
Expect(pods.Items[0].Name).To(Equal("pod-2b"))
299+
})
300+
It("Should list pods in namespace three", func() {
301+
var pods corev1.PodList
302+
err := informerCache.List(informerCacheCtx, &pods, client.InNamespace(testNamespaceThree))
303+
Expect(err).NotTo(HaveOccurred())
304+
Expect(pods.Items).To(HaveLen(1))
305+
Expect(pods.Items[0].Namespace).To(Equal(testNamespaceThree))
306+
Expect(pods.Items[0].Name).To(Equal("pod-3b"))
307+
})
308+
})
309+
})
310+
311+
AfterEach(func() {
312+
deletePod(pod1a)
313+
deletePod(pod1b)
314+
deletePod(pod2a)
315+
deletePod(pod2b)
316+
deletePod(pod2c)
317+
deletePod(pod3a)
318+
deletePod(pod3b)
319+
informerCacheCancel()
320+
})
321+
})
322+
127323
var _ = Describe("Cache with transformers", func() {
128324
var (
129325
informerCache cache.Cache

0 commit comments

Comments
 (0)