Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
99fce15
Add client implementation to test
Sep 20, 2022
ae47942
add tests and changelog
Sep 20, 2022
106c60f
clean up from codegen
Sep 21, 2022
08ba999
Merge master into faster-memory-client
soloio-bulldozer[bot] Sep 21, 2022
50fdad0
move changelog
Sep 21, 2022
6e69a1a
merge
Sep 21, 2022
97639fb
Merge refs/heads/master into faster-memory-client
soloio-bulldozer[bot] Sep 28, 2022
60ba46a
Adding changelog file to new location
Sep 28, 2022
1350dde
Deleting changelog file from old location
Sep 28, 2022
ebe625c
Merge refs/heads/master into faster-memory-client
soloio-bulldozer[bot] Sep 29, 2022
d56a2c1
Adding changelog file to new location
Sep 29, 2022
23d8acd
Deleting changelog file from old location
Sep 29, 2022
afd263c
Merge refs/heads/master into faster-memory-client
soloio-bulldozer[bot] Sep 29, 2022
bd76984
Adding changelog file to new location
Sep 29, 2022
812e8b9
Deleting changelog file from old location
Sep 29, 2022
f6b3d3b
Merge refs/heads/master into faster-memory-client
soloio-bulldozer[bot] Oct 24, 2022
4568474
Adding changelog file to new location
Oct 24, 2022
58f697a
Deleting changelog file from old location
Oct 24, 2022
aefd1b3
Merge refs/heads/master into faster-memory-client
soloio-bulldozer[bot] Dec 14, 2022
e571256
Merge refs/heads/master into faster-memory-client
soloio-bulldozer[bot] Dec 14, 2022
2bd4847
Merge refs/heads/master into faster-memory-client
soloio-bulldozer[bot] Jan 27, 2023
de84169
Adding changelog file to new location
Jan 27, 2023
5ee0acd
Deleting changelog file from old location
Jan 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions changelog/v0.30.10/sharedrefclient.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
changelog:
- type: NEW_FEATURE
resolvesIssue: false
issueLink: https://github.com/solo-io/gloo/issues/7166
description: >
Add an extension of the InMemoryResourceClient, called SharedRefResourceClient that does not clone resources on reads/writes.
10 changes: 10 additions & 0 deletions pkg/api/v1/clients/factory/resource_client_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ func newResourceClient(ctx context.Context, factory ResourceClientFactory, param
return file.NewResourceClient(opts.RootDir, resourceType), nil
case *MemoryResourceClientFactory:
return memory.NewResourceClient(opts.Cache, resourceType), nil
case *SharedRefMemoryResourceClientFactory:
return memory.NewSharedRefResourceClient(opts.Cache, resourceType), nil
case *KubeConfigMapClientFactory:
if opts.Cache == nil {
return nil, errors.Errorf("invalid opts, configmap client requires a kube core cache")
Expand Down Expand Up @@ -193,6 +195,14 @@ func (f *MemoryResourceClientFactory) NewResourceClient(ctx context.Context, par
return newResourceClient(ctx, f, params)
}

type SharedRefMemoryResourceClientFactory struct {
Cache memory.InMemoryResourceCache
}

func (f *SharedRefMemoryResourceClientFactory) NewResourceClient(ctx context.Context, params NewResourceClientParams) (clients.ResourceClient, error) {
return newResourceClient(ctx, f, params)
}

type KubeConfigMapClientFactory struct {
Clientset kubernetes.Interface
Cache cache.KubeCoreCache
Expand Down
76 changes: 75 additions & 1 deletion pkg/api/v1/clients/memory/resource_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ type ResourceClient struct {
resourceType resources.Resource
cache InMemoryResourceCache
}
type SharedRefResourceClient struct {
ResourceClient
}

func NewResourceClient(cache InMemoryResourceCache, resourceType resources.Resource) *ResourceClient {
return &ResourceClient{
Expand All @@ -113,6 +116,15 @@ func NewResourceClient(cache InMemoryResourceCache, resourceType resources.Resou
}
}

func NewSharedRefResourceClient(cache InMemoryResourceCache, resourceType resources.Resource) *SharedRefResourceClient {
return &SharedRefResourceClient{
ResourceClient{
cache: cache,
resourceType: resourceType,
},
}
}

var _ clients.ResourceClient = &ResourceClient{}

func (rc *ResourceClient) Kind() string {
Expand Down Expand Up @@ -141,7 +153,6 @@ func (rc *ResourceClient) Read(namespace, name string, opts clients.ReadOpts) (r
clone := resources.Clone(resource)
return clone, nil
}

func (rc *ResourceClient) Write(resource resources.Resource, opts clients.WriteOpts) (resources.Resource, error) {
opts = opts.WithDefaults()
if err := resources.Validate(resource); err != nil {
Expand Down Expand Up @@ -267,3 +278,66 @@ func newOrIncrementResourceVer(resourceVersion string) string {
}
return fmt.Sprintf("%v", curr+1)
}

func (rc *SharedRefResourceClient) Read(namespace, name string, opts clients.ReadOpts) (resources.Resource, error) {
if err := resources.ValidateName(name); err != nil {
return nil, errors.Wrapf(err, "validation error")
}
opts = opts.WithDefaults()
resource, ok := rc.cache.Get(rc.key(namespace, name))
if !ok {
return nil, errors.NewNotExistErr(namespace, name)
}

return resource, nil
}

func (rc *SharedRefResourceClient) List(namespace string, opts clients.ListOpts) (resources.ResourceList, error) {
opts = opts.WithDefaults()
cachedResources := rc.cache.List(rc.Prefix(namespace))
var resourceList resources.ResourceList
for _, resource := range cachedResources {
if labels.SelectorFromSet(opts.Selector).Matches(labels.Set(resource.GetMetadata().Labels)) {
resourceList = append(resourceList, resource)
}
}

sort.Stable(resourceList)

return resourceList, nil
}

func (rc *SharedRefResourceClient) Write(resource resources.Resource, opts clients.WriteOpts) (resources.Resource, error) {
opts = opts.WithDefaults()
if err := resources.Validate(resource); err != nil {
return nil, errors.Wrapf(err, "validation error")
}

key := rc.key(resource.GetMetadata().GetNamespace(), resource.GetMetadata().GetName())

original, err := rc.Read(
resource.GetMetadata().GetNamespace(),
resource.GetMetadata().GetName(),
clients.ReadOpts{},
)
if original != nil && err == nil {
if !opts.OverwriteExisting {
return nil, errors.NewExistErr(resource.GetMetadata())
}
if resource.GetMetadata().GetResourceVersion() != original.GetMetadata().GetResourceVersion() {
return nil, errors.NewResourceVersionErr(
resource.GetMetadata().GetNamespace(),
resource.GetMetadata().GetName(),
resource.GetMetadata().GetResourceVersion(),
original.GetMetadata().GetResourceVersion(),
)
}
}

// initialize or increment resource version
resource.GetMetadata().ResourceVersion = newOrIncrementResourceVer(resource.GetMetadata().GetResourceVersion())

rc.cache.Set(key, resource)

return resource, nil
}
167 changes: 107 additions & 60 deletions pkg/api/v1/clients/memory/resource_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,77 +18,124 @@ import (
)

var _ = Describe("Base", func() {
var (
client *ResourceClient
)
BeforeEach(func() {
client = NewResourceClient(NewInMemoryResourceCache(), &v1.MockResource{})
})
AfterEach(func() {
})
It("CRUDs resources", func() {
selector := map[string]string{
helpers.TestLabel: helpers.RandString(8),
}
generic.TestCrudClient("ns1", "ns2", client, clients.WatchOpts{
Selector: selector,
Ctx: context.TODO(),
RefreshRate: time.Minute,
Context("Standard in memory client", func() {
var (
client *ResourceClient
)
BeforeEach(func() {
client = NewResourceClient(NewInMemoryResourceCache(), &v1.MockResource{})
})
})
It("should not return pointer to internal object", func() {
obj := &v1.MockResource{
Metadata: &core.Metadata{
Namespace: "ns",
Name: "n",
},
Data: "test",
}
client.Write(obj, clients.WriteOpts{})
ret, err := client.Read("ns", "n", clients.ReadOpts{})
Expect(err).NotTo(HaveOccurred())
Expect(ret).NotTo(BeIdenticalTo(obj))
AfterEach(func() {
})
It("CRUDs resources", func() {
selector := map[string]string{
helpers.TestLabel: helpers.RandString(8),
}
generic.TestCrudClient("ns1", "ns2", client, clients.WatchOpts{
Selector: selector,
Ctx: context.TODO(),
RefreshRate: time.Minute,
})
})
It("should not return pointer to internal object", func() {
obj := &v1.MockResource{
Metadata: &core.Metadata{
Namespace: "ns",
Name: "n",
},
Data: "test",
}
client.Write(obj, clients.WriteOpts{})
ret, err := client.Read("ns", "n", clients.ReadOpts{})
Expect(err).NotTo(HaveOccurred())
Expect(ret).NotTo(BeIdenticalTo(obj))

ret2, err := client.Read("ns", "n", clients.ReadOpts{})
Expect(err).NotTo(HaveOccurred())
Expect(ret).NotTo(BeIdenticalTo(ret2))
ret2, err := client.Read("ns", "n", clients.ReadOpts{})
Expect(err).NotTo(HaveOccurred())
Expect(ret).NotTo(BeIdenticalTo(ret2))

listret, err := client.List("ns", clients.ListOpts{})
Expect(err).NotTo(HaveOccurred())
Expect(listret[0]).NotTo(BeIdenticalTo(obj))
listret, err := client.List("ns", clients.ListOpts{})
Expect(err).NotTo(HaveOccurred())
Expect(listret[0]).NotTo(BeIdenticalTo(obj))

listret2, err := client.List("ns", clients.ListOpts{})
Expect(err).NotTo(HaveOccurred())
Expect(listret[0]).NotTo(BeIdenticalTo(listret2[0]))
})
listret2, err := client.List("ns", clients.ListOpts{})
Expect(err).NotTo(HaveOccurred())
Expect(listret[0]).NotTo(BeIdenticalTo(listret2[0]))
})

Context("Benchmarks", func() {
Measure("it should perform list efficiently", func(b Benchmarker) {
const numobjs = 10000
Context("Benchmarks", func() {
Measure("it should perform list efficiently", func(b Benchmarker) {
const numobjs = 10000

for i := 0; i < numobjs; i++ {
obj := &v1.MockResource{
Metadata: &core.Metadata{
Namespace: "ns",
Name: fmt.Sprintf("n-%v", numobjs-i),
},
Data: strings.Repeat("123", 1000) + fmt.Sprintf("test-%v", i),
for i := 0; i < numobjs; i++ {
obj := &v1.MockResource{
Metadata: &core.Metadata{
Namespace: "ns",
Name: fmt.Sprintf("n-%v", numobjs-i),
},
Data: strings.Repeat("123", 1000) + fmt.Sprintf("test-%v", i),
}
client.Write(obj, clients.WriteOpts{})
}
client.Write(obj, clients.WriteOpts{})
l := clients.ListOpts{}
var output resources.ResourceList
var err error
runtime := b.Time("runtime", func() {
output, err = client.List("ns", l)
})
Expect(err).NotTo(HaveOccurred())
Expect(output).To(HaveLen(numobjs))
Expect(output[0].GetMetadata().Name).To(Equal("n-1"))

Expect(runtime.Seconds()).Should(BeNumerically("<", 0.5), "List() shouldn't take too long.")
}, 10)

})
})

Context("Shared ref memory client", func() {
var (
client *SharedRefResourceClient
)
BeforeEach(func() {
client = NewSharedRefResourceClient(NewInMemoryResourceCache(), &v1.MockResource{})
})
AfterEach(func() {
})
It("CRUDs resources", func() {
selector := map[string]string{
helpers.TestLabel: helpers.RandString(8),
}
l := clients.ListOpts{}
var output resources.ResourceList
var err error
runtime := b.Time("runtime", func() {
output, err = client.List("ns", l)
generic.TestCrudClient("ns1", "ns2", client, clients.WatchOpts{
Selector: selector,
Ctx: context.TODO(),
RefreshRate: time.Minute,
})
})
It("should return pointer to internal object", func() {
obj := &v1.MockResource{
Metadata: &core.Metadata{
Namespace: "ns",
Name: "n",
},
Data: "test",
}
client.Write(obj, clients.WriteOpts{})
ret, err := client.Read("ns", "n", clients.ReadOpts{})
Expect(err).NotTo(HaveOccurred())
Expect(output).To(HaveLen(numobjs))
Expect(output[0].GetMetadata().Name).To(Equal("n-1"))
Expect(ret).To(BeIdenticalTo(obj))

Expect(runtime.Seconds()).Should(BeNumerically("<", 0.5), "List() shouldn't take too long.")
}, 10)
ret2, err := client.Read("ns", "n", clients.ReadOpts{})
Expect(err).NotTo(HaveOccurred())
Expect(ret).To(BeIdenticalTo(ret2))

})
listret, err := client.List("ns", clients.ListOpts{})
Expect(err).NotTo(HaveOccurred())
Expect(listret[0]).To(BeIdenticalTo(obj))

listret2, err := client.List("ns", clients.ListOpts{})
Expect(err).NotTo(HaveOccurred())
Expect(listret[0]).To(BeIdenticalTo(listret2[0]))
})
})
})
9 changes: 5 additions & 4 deletions test/tests/generic/test_crud_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ func TestCrudClient(namespace1, namespace2 string, client ResourceClient, opts c
ExpectWithOffset(testOffset, err).NotTo(HaveOccurred())
ExpectWithOffset(testOffset, list).To(BeEmpty())

r1, err := client.Write(input, clients.WriteOpts{})
r1, err := client.Write(resources.Clone(input), clients.WriteOpts{})
ExpectWithOffset(testOffset, err).NotTo(HaveOccurred())
postWrite(callbacks, r1)

_, err = client.Write(input, clients.WriteOpts{})
ExpectWithOffset(testOffset, err).To(HaveOccurred())
ExpectWithOffset(testOffset, err).To(HaveOccurred(), "Allowed overwriting resource without OverwriteExisting")
ExpectWithOffset(testOffset, errors.IsExist(err)).To(BeTrue())

ExpectWithOffset(testOffset, r1).To(BeAssignableToTypeOf(&v1.MockResource{}))
Expand All @@ -62,7 +62,7 @@ func TestCrudClient(namespace1, namespace2 string, client ResourceClient, opts c
_, err = client.Write(input, clients.WriteOpts{
OverwriteExisting: true,
})
ExpectWithOffset(testOffset, err).To(HaveOccurred())
ExpectWithOffset(testOffset, err).To(HaveOccurred(), "Wrote resource without changed version")

resources.UpdateMetadata(input, func(meta *core.Metadata) {
meta.ResourceVersion = r1.GetMetadata().ResourceVersion
Expand Down Expand Up @@ -99,6 +99,7 @@ func TestCrudClient(namespace1, namespace2 string, client ResourceClient, opts c
},
}
r2, err := client.Write(input, clients.WriteOpts{})
r2 = resources.Clone(r2)
ExpectWithOffset(testOffset, err).NotTo(HaveOccurred())

// with labels
Expand All @@ -125,7 +126,7 @@ func TestCrudClient(namespace1, namespace2 string, client ResourceClient, opts c
meta.ResourceVersion = ""
})
_, err = client.Write(r2, clients.WriteOpts{OverwriteExisting: true})
ExpectWithOffset(testOffset, err).To(HaveOccurred())
ExpectWithOffset(testOffset, err).To(HaveOccurred(), "Did not get version error")

err = client.Delete(namespace1, "adsfw", clients.DeleteOpts{})
ExpectWithOffset(testOffset, err).To(HaveOccurred())
Expand Down