Skip to content

Commit e6e61f0

Browse files
authored
Merge pull request #3021 from alvaroaleman/allow
✨ Fake client: Allow adding indexes at runtime
2 parents f4ae040 + 3911ded commit e6e61f0

File tree

2 files changed

+80
-2
lines changed

2 files changed

+80
-2
lines changed

pkg/client/fake/client.go

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ type fakeClient struct {
8484
// indexes maps each GroupVersionKind (GVK) to the indexes registered for that GVK.
8585
// The inner map maps from index name to IndexerFunc.
8686
indexes map[schema.GroupVersionKind]map[string]client.IndexerFunc
87+
// indexesLock must be held when accessing indexes.
88+
indexesLock sync.RWMutex
8789
}
8890

8991
var _ client.WithWatch = &fakeClient{}
@@ -648,10 +650,11 @@ func (c *fakeClient) filterList(list []runtime.Object, gvk schema.GroupVersionKi
648650
func (c *fakeClient) filterWithFields(list []runtime.Object, gvk schema.GroupVersionKind, fs fields.Selector) ([]runtime.Object, error) {
649651
requiresExact := selector.RequiresExactMatch(fs)
650652
if !requiresExact {
651-
return nil, fmt.Errorf("field selector %s is not in one of the two supported forms \"key==val\" or \"key=val\"",
652-
fs)
653+
return nil, fmt.Errorf(`field selector %s is not in one of the two supported forms "key==val" or "key=val"`, fs)
653654
}
654655

656+
c.indexesLock.RLock()
657+
defer c.indexesLock.RUnlock()
655658
// Field selection is mimicked via indexes, so there's no sane answer this function can give
656659
// if there are no indexes registered for the GroupVersionKind of the objects in the list.
657660
indexes := c.indexes[gvk]
@@ -1528,3 +1531,37 @@ func applyScale(obj client.Object, scale *autoscalingv1.Scale) error {
15281531
}
15291532
return nil
15301533
}
1534+
1535+
// AddIndex adds an index to a fake client. It will panic if used with a client that is not a fake client.
1536+
// It will error if there is already an index for given object with the same name as field.
1537+
//
1538+
// It can be used to test code that adds indexes to the cache at runtime.
1539+
func AddIndex(c client.Client, obj runtime.Object, field string, extractValue client.IndexerFunc) error {
1540+
fakeClient, isFakeClient := c.(*fakeClient)
1541+
if !isFakeClient {
1542+
panic("AddIndex can only be used with a fake client")
1543+
}
1544+
fakeClient.indexesLock.Lock()
1545+
defer fakeClient.indexesLock.Unlock()
1546+
1547+
if fakeClient.indexes == nil {
1548+
fakeClient.indexes = make(map[schema.GroupVersionKind]map[string]client.IndexerFunc, 1)
1549+
}
1550+
1551+
gvk, err := apiutil.GVKForObject(obj, fakeClient.scheme)
1552+
if err != nil {
1553+
return fmt.Errorf("failed to get gvk for %T: %w", obj, err)
1554+
}
1555+
1556+
if fakeClient.indexes[gvk] == nil {
1557+
fakeClient.indexes[gvk] = make(map[string]client.IndexerFunc, 1)
1558+
}
1559+
1560+
if fakeClient.indexes[gvk][field] != nil {
1561+
return fmt.Errorf("index %s already exists", field)
1562+
}
1563+
1564+
fakeClient.indexes[gvk][field] = extractValue
1565+
1566+
return nil
1567+
}

pkg/client/fake/client_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,6 +1388,47 @@ var _ = Describe("Fake client", func() {
13881388
Expect(cl.List(context.Background(), list, listOpts)).To(Succeed())
13891389
Expect(list.Items).To(BeEmpty())
13901390
})
1391+
1392+
It("supports adding an index at runtime", func() {
1393+
listOpts := &client.ListOptions{
1394+
FieldSelector: fields.OneTermEqualSelector("metadata.name", "test-deployment-2"),
1395+
}
1396+
list := &appsv1.DeploymentList{}
1397+
err := cl.List(context.Background(), list, listOpts)
1398+
Expect(err).To(HaveOccurred())
1399+
Expect(err.Error()).To(ContainSubstring("no index with name metadata.name has been registered"))
1400+
1401+
err = AddIndex(cl, &appsv1.Deployment{}, "metadata.name", func(obj client.Object) []string {
1402+
return []string{obj.GetName()}
1403+
})
1404+
Expect(err).To(Succeed())
1405+
1406+
Expect(cl.List(context.Background(), list, listOpts)).To(Succeed())
1407+
Expect(list.Items).To(ConsistOf(*dep2))
1408+
})
1409+
1410+
It("Is not a datarace to add and use indexes in parallel", func() {
1411+
wg := sync.WaitGroup{}
1412+
wg.Add(2)
1413+
1414+
listOpts := &client.ListOptions{
1415+
FieldSelector: fields.OneTermEqualSelector("spec.replicas", "2"),
1416+
}
1417+
go func() {
1418+
defer wg.Done()
1419+
defer GinkgoRecover()
1420+
Expect(cl.List(context.Background(), &appsv1.DeploymentList{}, listOpts)).To(Succeed())
1421+
}()
1422+
go func() {
1423+
defer wg.Done()
1424+
defer GinkgoRecover()
1425+
err := AddIndex(cl, &appsv1.Deployment{}, "metadata.name", func(obj client.Object) []string {
1426+
return []string{obj.GetName()}
1427+
})
1428+
Expect(err).To(Succeed())
1429+
}()
1430+
wg.Wait()
1431+
})
13911432
})
13921433
})
13931434

0 commit comments

Comments
 (0)