1
0
mirror of https://github.com/rancher/steve.git synced 2025-05-12 18:04:20 +00:00

Adding virtual fields

Adds logic which adds virtual fields resources. This allows these fields
to be sorted/filtered on when the SQL cache is enabled. Id and
metadata.state.name were added as the first two fields.
This commit is contained in:
Michael Bolot 2024-07-03 15:45:57 -05:00
parent 32c30149a6
commit 1149920168
15 changed files with 586 additions and 112 deletions

View File

@ -138,7 +138,7 @@ item is included in the list.
**If SQLite caching is enabled** (`server.Options.SQLCache=true`),
filtering is only supported for a subset of attributes:
- `metadata.name`, `metadata.namespace` and `metadata.timestamp` for any resource kind
- `id`, `metadata.name`, `metadata.namespace`, `metadata.state.name`, and `metadata.timestamp` for any resource kind
- a short list of hardcoded attributes for a selection of specific types listed
in [typeSpecificIndexFields](https://github.com/rancher/steve/blob/main/pkg/stores/sqlproxy/proxy_store.go#L52-L58)
- the special string `metadata.fields[N]`, with N starting at 0, for all columns

2
go.mod
View File

@ -22,7 +22,7 @@ require (
github.com/rancher/apiserver v0.0.0-20240708202538-39a6f2535146
github.com/rancher/dynamiclistener v0.6.0
github.com/rancher/kubernetes-provider-detector v0.1.5
github.com/rancher/lasso v0.0.0-20240809125800-8da6f11865d5
github.com/rancher/lasso v0.0.0-20240828170735-d79536cac289
github.com/rancher/norman v0.0.0-20240822182819-60ccfabc4ac5
github.com/rancher/remotedialer v0.3.2
github.com/rancher/wrangler/v3 v3.0.0

4
go.sum
View File

@ -193,8 +193,8 @@ github.com/rancher/dynamiclistener v0.6.0 h1:M7x8Nq+GY0UORULANuW/AH1ocnyZaqlmTuv
github.com/rancher/dynamiclistener v0.6.0/go.mod h1:7VNEQhAwzbYJ08S1MYb6B4vili6K7CcrG4cNZXq1j+s=
github.com/rancher/kubernetes-provider-detector v0.1.5 h1:hWRAsWuJOemzGjz/XrbTlM7QmfO4OedvFE3QwXiH60I=
github.com/rancher/kubernetes-provider-detector v0.1.5/go.mod h1:ypuJS7kP7rUiAn330xG46mj+Nhvym05GM8NqMVekpH0=
github.com/rancher/lasso v0.0.0-20240809125800-8da6f11865d5 h1:qlVhaHTT7wwrI5+AGdkYHpveuoe8Ot4TdQr7LtxmVSk=
github.com/rancher/lasso v0.0.0-20240809125800-8da6f11865d5/go.mod h1:Efx/+BbH3ivmnTPLu5cA3Gc9wT5oyGS0LBcqEuYTx+A=
github.com/rancher/lasso v0.0.0-20240828170735-d79536cac289 h1:gbV7qLOcEgyTgep2ocl8FFhfGOUMQuvfV5OIIENHWT4=
github.com/rancher/lasso v0.0.0-20240828170735-d79536cac289/go.mod h1:Efx/+BbH3ivmnTPLu5cA3Gc9wT5oyGS0LBcqEuYTx+A=
github.com/rancher/norman v0.0.0-20240822182819-60ccfabc4ac5 h1:Z34NXcW0ymdpVBfd1R0vvqTXBh1gCOdPNFtB+RUahQw=
github.com/rancher/norman v0.0.0-20240822182819-60ccfabc4ac5/go.mod h1:dyjfXBsNiroPWOdUZe7diUOUSLf6HQ/r2kEpwH/8zas=
github.com/rancher/remotedialer v0.3.2 h1:kstZbRwPS5gPWpGg8VjEHT2poHtArs+Fc317YM8JCzU=

View File

@ -23,18 +23,19 @@ import (
func DefaultTemplate(clientGetter proxy.ClientGetter,
summaryCache *summarycache.SummaryCache,
asl accesscontrol.AccessSetLookup,
namespaceCache corecontrollers.NamespaceCache) schema.Template {
namespaceCache corecontrollers.NamespaceCache,
sqlCache bool) schema.Template {
return schema.Template{
Store: metricsStore.NewMetricsStore(proxy.NewProxyStore(clientGetter, summaryCache, asl, namespaceCache)),
Formatter: formatter(summaryCache),
Formatter: formatter(summaryCache, sqlCache),
}
}
// DefaultTemplateForStore provides a default schema template which uses a provided, pre-initialized store. Primarily used when creating a Template that uses a Lasso SQL store internally.
func DefaultTemplateForStore(store types.Store, summaryCache *summarycache.SummaryCache) schema.Template {
func DefaultTemplateForStore(store types.Store, summaryCache *summarycache.SummaryCache, sqlCache bool) schema.Template {
return schema.Template{
Store: store,
Formatter: formatter(summaryCache),
Formatter: formatter(summaryCache, sqlCache),
}
}
@ -71,7 +72,7 @@ func selfLink(gvr schema2.GroupVersionResource, meta metav1.Object) (prefix stri
return buf.String()
}
func formatter(summarycache *summarycache.SummaryCache) types.Formatter {
func formatter(summarycache *summarycache.SummaryCache, sqlCache bool) types.Formatter {
return func(request *types.APIRequest, resource *types.RawResource) {
if resource.Schema == nil {
return
@ -104,17 +105,20 @@ func formatter(summarycache *summarycache.SummaryCache) types.Formatter {
}
if unstr, ok := resource.APIObject.Object.(*unstructured.Unstructured); ok {
s, rel := summarycache.SummaryAndRelationship(unstr)
data.PutValue(unstr.Object, map[string]interface{}{
"name": s.State,
"error": s.Error,
"transitioning": s.Transitioning,
"message": strings.Join(s.Message, ":"),
}, "metadata", "state")
data.PutValue(unstr.Object, rel, "metadata", "relationships")
if !sqlCache {
// with the sql cache, these were already added by the indexer
s, rel := summarycache.SummaryAndRelationship(unstr)
data.PutValue(unstr.Object, map[string]interface{}{
"name": s.State,
"error": s.Error,
"transitioning": s.Transitioning,
"message": strings.Join(s.Message, ":"),
}, "metadata", "state")
data.PutValue(unstr.Object, rel, "metadata", "relationships")
summary.NormalizeConditions(unstr)
summary.NormalizeConditions(unstr)
}
includeFields(request, unstr)
excludeFields(request, unstr)
excludeValues(request, unstr)

View File

@ -49,7 +49,7 @@ func DefaultSchemaTemplates(cf *client.Factory,
discovery discovery.DiscoveryInterface,
namespaceCache corecontrollers.NamespaceCache) []schema.Template {
return []schema.Template{
common.DefaultTemplate(cf, summaryCache, lookup, namespaceCache),
common.DefaultTemplate(cf, summaryCache, lookup, namespaceCache, false),
apigroups.Template(discovery),
{
ID: "configmap",
@ -79,7 +79,7 @@ func DefaultSchemaTemplatesForStore(store types.Store,
discovery discovery.DiscoveryInterface) []schema.Template {
return []schema.Template{
common.DefaultTemplateForStore(store, summaryCache),
common.DefaultTemplateForStore(store, summaryCache, true),
apigroups.Template(discovery),
{
ID: "configmap",

View File

@ -0,0 +1,116 @@
// Package common provides cache.TransformFunc's which are common to all types
package common
import (
"fmt"
"strings"
"github.com/rancher/steve/pkg/summarycache"
"github.com/rancher/wrangler/v3/pkg/data"
wranglerSummary "github.com/rancher/wrangler/v3/pkg/summary"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/cache"
)
// SummaryCache provides an interface to get a summary/relationships for an object. Implemented by the summaryCache
// struct from pkg/summarycache
type SummaryCache interface {
SummaryAndRelationship(runtime.Object) (*wranglerSummary.SummarizedObject, []summarycache.Relationship)
}
// DefaultFields produces a VirtualTransformFunc through GetTransform() that applies to all k8s types
type DefaultFields struct {
Cache SummaryCache
}
// GetTransform produces the default transformation func
func (d *DefaultFields) GetTransform() cache.TransformFunc {
return d.transform
}
// transform implements virtual.VirtualTransformFunc, and adds reserved fields/summary
func (d *DefaultFields) transform(obj any) (any, error) {
raw, isSignal, err := getUnstructured(obj)
if isSignal {
return obj, nil
}
if err != nil {
return nil, err
}
raw = addIDField(raw)
raw, err = addSummaryFields(raw, d.Cache)
if err != nil {
return nil, fmt.Errorf("unable to add summary fields: %w", err)
}
return raw, nil
}
// addSummaryFields adds the virtual fields for object state.
func addSummaryFields(raw *unstructured.Unstructured, cache SummaryCache) (*unstructured.Unstructured, error) {
s, relationships := cache.SummaryAndRelationship(raw)
if s != nil {
data.PutValue(raw.Object, map[string]interface{}{
"name": s.State,
"error": s.Error,
"transitioning": s.Transitioning,
"message": strings.Join(s.Message, ":"),
}, "metadata", "state")
}
var rels []any
for _, relationship := range relationships {
rel, err := toMap(relationship)
if err != nil {
return nil, fmt.Errorf("unable to convert relationship to map: %w", err)
}
rels = append(rels, rel)
}
data.PutValue(raw.Object, rels, "metadata", "relationships")
normalizeConditions(raw)
return raw, nil
}
// addIDField adds the ID field based on namespace/name, and moves the current id field to _id if present
func addIDField(raw *unstructured.Unstructured) *unstructured.Unstructured {
objectID := raw.GetName()
namespace := raw.GetNamespace()
if namespace != "" {
objectID = fmt.Sprintf("%s/%s", namespace, objectID)
}
currentIDValue, ok := raw.Object["id"]
if ok {
raw.Object["_id"] = currentIDValue
}
raw.Object["id"] = objectID
return raw
}
func normalizeConditions(raw *unstructured.Unstructured) {
var (
obj data.Object
newConditions []any
)
obj = raw.Object
for _, condition := range obj.Slice("status", "conditions") {
var summary wranglerSummary.Summary
for _, summarizer := range wranglerSummary.ConditionSummarizers {
summary = summarizer(obj, []wranglerSummary.Condition{{Object: condition}}, summary)
}
condition.Set("error", summary.Error)
condition.Set("transitioning", summary.Transitioning)
if condition.String("lastUpdateTime") == "" {
condition.Set("lastUpdateTime", condition.String("lastTransitionTime"))
}
// needs to be reconverted back to a map[string]any or we can have encoding problems with unregistered types
var mapCondition map[string]any = condition
newConditions = append(newConditions, mapCondition)
}
if len(newConditions) > 0 {
obj.SetNested(newConditions, "status", "conditions")
}
}

View File

@ -0,0 +1,188 @@
package common_test
import (
"testing"
"github.com/rancher/steve/pkg/resources/virtual/common"
"github.com/rancher/steve/pkg/summarycache"
"github.com/rancher/wrangler/v3/pkg/summary"
"github.com/stretchr/testify/require"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/cache"
)
func TestTransform(t *testing.T) {
tests := []struct {
name string
input any
hasSummary *summary.SummarizedObject
hasRelationships []summarycache.Relationship
wantOutput any
wantError bool
}{
{
name: "signal error",
input: cache.DeletedFinalStateUnknown{
Key: "some-ns/some-name",
},
wantOutput: cache.DeletedFinalStateUnknown{
Key: "some-ns/some-name",
},
wantError: false,
},
{
name: "not unstructured",
input: map[string]any{
"somekey": "someval",
},
wantError: true,
},
{
name: "add summary + relationships + reserved fields",
hasSummary: &summary.SummarizedObject{
PartialObjectMetadata: v1.PartialObjectMetadata{
ObjectMeta: v1.ObjectMeta{
Name: "testobj",
Namespace: "test-ns",
},
TypeMeta: v1.TypeMeta{
APIVersion: "test.cattle.io/v1",
Kind: "TestResource",
},
},
Summary: summary.Summary{
State: "success",
Transitioning: false,
Error: false,
Message: []string{"resource 1 rolled out", "resource 2 rolled out"},
},
},
hasRelationships: []summarycache.Relationship{
{
ToID: "1345",
ToType: "SomeType",
ToNamespace: "some-ns",
FromID: "78901",
FromType: "TestResource",
Rel: "uses",
},
},
input: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "test.cattle.io/v1",
"kind": "TestResource",
"metadata": map[string]interface{}{
"name": "testobj",
"namespace": "test-ns",
},
"id": "old-id",
},
},
wantOutput: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "test.cattle.io/v1",
"kind": "TestResource",
"metadata": map[string]interface{}{
"name": "testobj",
"namespace": "test-ns",
"state": map[string]interface{}{
"name": "success",
"error": false,
"transitioning": false,
"message": "resource 1 rolled out:resource 2 rolled out",
},
"relationships": []any{
map[string]any{
"toId": "1345",
"toType": "SomeType",
"toNamespace": "some-ns",
"fromId": "78901",
"fromType": "TestResource",
"rel": "uses",
},
},
},
"id": "test-ns/testobj",
"_id": "old-id",
},
},
},
{
name: "add conditions + reserved fields",
input: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "test.cattle.io/v1",
"kind": "TestResource",
"metadata": map[string]interface{}{
"name": "testobj",
"namespace": "test-ns",
},
"status": map[string]interface{}{
"conditions": []interface{}{
map[string]interface{}{
"status": "False",
"reason": "Error",
"message": "some error",
"lastTransitionTime": "2024-01-01",
},
},
},
},
},
wantOutput: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "test.cattle.io/v1",
"kind": "TestResource",
"metadata": map[string]interface{}{
"name": "testobj",
"namespace": "test-ns",
"relationships": []any(nil),
},
"status": map[string]interface{}{
"conditions": []interface{}{
map[string]interface{}{
"status": "False",
"reason": "Error",
"transitioning": false,
"error": true,
"message": "some error",
"lastTransitionTime": "2024-01-01",
"lastUpdateTime": "2024-01-01",
},
},
},
"id": "test-ns/testobj",
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
fakeCache := fakeSummaryCache{
summarizedObject: test.hasSummary,
relationships: test.hasRelationships,
}
df := common.DefaultFields{
Cache: &fakeCache,
}
output, err := df.GetTransform()(test.input)
require.Equal(t, test.wantOutput, output)
if test.wantError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}
type fakeSummaryCache struct {
summarizedObject *summary.SummarizedObject
relationships []summarycache.Relationship
}
func (f *fakeSummaryCache) SummaryAndRelationship(runtime.Object) (*summary.SummarizedObject, []summarycache.Relationship) {
return f.summarizedObject, f.relationships
}

View File

@ -0,0 +1,40 @@
package common
import (
"encoding/json"
"fmt"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/tools/cache"
)
// GetUnstructured retrieves an unstructured object from the provided input. If this is a signal
// object (like cache.DeletedFinalStateUnknown), returns true, indicating that this wasn't an
// unstructured object, but doesn't need to be processed by our transform function
func getUnstructured(obj any) (*unstructured.Unstructured, bool, error) {
raw, ok := obj.(*unstructured.Unstructured)
if !ok {
_, isFinalUnknown := obj.(cache.DeletedFinalStateUnknown)
if isFinalUnknown {
// As documented in the TransformFunc interface
return nil, true, nil
}
return nil, false, fmt.Errorf("object was of type %T, not unstructured", raw)
}
return raw, false, nil
}
// toMap converts an object to a map[string]any which can be stored/retrieved from the cache. Currently
// uses json encoding to take advantage of tag names
func toMap(obj any) (map[string]any, error) {
bytes, err := json.Marshal(obj)
if err != nil {
return nil, fmt.Errorf("unable to marshal object: %w", err)
}
var retObj map[string]any
err = json.Unmarshal(bytes, &retObj)
if err != nil {
return nil, fmt.Errorf("unable to unmarshal object: %w", err)
}
return retObj, nil
}

View File

@ -0,0 +1,28 @@
// Package virtual provides functions/resources to define virtual fields (fields which don't exist in k8s
// but should be visible in the API) on resources
package virtual
import (
"github.com/rancher/steve/pkg/resources/virtual/common"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/tools/cache"
)
// TransformBuilder builds transform functions for specified GVKs through GetTransformFunc
type TransformBuilder struct {
defaultFields *common.DefaultFields
}
// NewTransformBuilder returns a TransformBuilder using the given summary cache
func NewTransformBuilder(cache common.SummaryCache) *TransformBuilder {
return &TransformBuilder{
&common.DefaultFields{
Cache: cache,
},
}
}
// GetTransformFunc retrieves a TransformFunc for a given GVK. Currently only returns a transformFunc for defaultFields
func (t *TransformBuilder) GetTransformFunc(_ schema.GroupVersionKind) cache.TransformFunc {
return t.defaultFields.GetTransform()
}

View File

@ -163,7 +163,7 @@ func setup(ctx context.Context, server *Server) error {
var onSchemasHandler schemacontroller.SchemasHandlerFunc
if server.SQLCache {
s, err := sqlproxy.NewProxyStore(cols, cf, summaryCache, nil)
s, err := sqlproxy.NewProxyStore(cols, cf, summaryCache, summaryCache, nil)
if err != nil {
panic(err)
}

View File

@ -110,7 +110,7 @@ func (s *Store) Delete(apiOp *types.APIRequest, schema *types.APISchema, id stri
if err != nil {
return types.APIObject{}, err
}
return ToAPI(schema, obj, warnings), nil
return ToAPI(schema, obj, warnings, types.ReservedFields), nil
}
// ByID looks up a single object by its ID.
@ -124,7 +124,7 @@ func (s *Store) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string
if err != nil {
return types.APIObject{}, err
}
return ToAPI(schema, obj, warnings), nil
return ToAPI(schema, obj, warnings, types.ReservedFields), nil
}
func (s *Store) listPartition(ctx context.Context, apiOp *types.APIRequest, schema *types.APISchema, partition Partition,
@ -226,7 +226,7 @@ func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.AP
for _, item := range list {
item := item.DeepCopy()
result.Objects = append(result.Objects, ToAPI(schema, item, nil))
result.Objects = append(result.Objects, ToAPI(schema, item, nil, types.ReservedFields))
}
result.Pages = pages
@ -266,7 +266,7 @@ func (s *Store) Create(apiOp *types.APIRequest, schema *types.APISchema, data ty
if err != nil {
return types.APIObject{}, err
}
return ToAPI(schema, obj, warnings), nil
return ToAPI(schema, obj, warnings, types.ReservedFields), nil
}
// Update updates a single object in the store.
@ -280,7 +280,7 @@ func (s *Store) Update(apiOp *types.APIRequest, schema *types.APISchema, data ty
if err != nil {
return types.APIObject{}, err
}
return ToAPI(schema, obj, warnings), nil
return ToAPI(schema, obj, warnings, types.ReservedFields), nil
}
// Watch returns a channel of events for a list or resource.
@ -326,13 +326,13 @@ func (s *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, wr types
return response, nil
}
func ToAPI(schema *types.APISchema, obj runtime.Object, warnings []types.Warning) types.APIObject {
func ToAPI(schema *types.APISchema, obj runtime.Object, warnings []types.Warning, reservedFields map[string]bool) types.APIObject {
if obj == nil || reflect.ValueOf(obj).IsNil() {
return types.APIObject{}
}
if unstr, ok := obj.(*unstructured.Unstructured); ok {
obj = moveToUnderscore(unstr)
obj = moveToUnderscore(unstr, reservedFields)
}
apiObject := types.APIObject{
@ -356,12 +356,12 @@ func ToAPI(schema *types.APISchema, obj runtime.Object, warnings []types.Warning
return apiObject
}
func moveToUnderscore(obj *unstructured.Unstructured) *unstructured.Unstructured {
func moveToUnderscore(obj *unstructured.Unstructured, reservedFields map[string]bool) *unstructured.Unstructured {
if obj == nil {
return nil
}
for k := range types.ReservedFields {
for k := range reservedFields {
v, ok := obj.Object[k]
if ok {
delete(obj.Object, k)
@ -393,7 +393,7 @@ func ToAPIEvent(apiOp *types.APIRequest, schema *types.APISchema, event watch.Ev
return apiEvent
}
apiEvent.Object = ToAPI(schema, event.Object, nil)
apiEvent.Object = ToAPI(schema, event.Object, nil, types.ReservedFields)
m, err := meta.Accessor(event.Object)
if err != nil {

View File

@ -24,8 +24,9 @@ type SchemaColumnSetter interface {
// Store implements types.proxyStore for partitions.
type Store struct {
Partitioner Partitioner
asl accesscontrol.AccessSetLookup
Partitioner Partitioner
asl accesscontrol.AccessSetLookup
sqlReservedFields map[string]bool
}
// NewStore creates a types.proxyStore implementation with a partitioner
@ -36,6 +37,14 @@ func NewStore(store UnstructuredStore, asl accesscontrol.AccessSetLookup) *Store
},
asl: asl,
}
sqlReservedFields := map[string]bool{}
for key, value := range types.ReservedFields {
if key == "id" {
continue
}
sqlReservedFields[key] = value
}
s.sqlReservedFields = sqlReservedFields
return s
}
@ -48,7 +57,7 @@ func (s *Store) Delete(apiOp *types.APIRequest, schema *types.APISchema, id stri
if err != nil {
return types.APIObject{}, err
}
return partition.ToAPI(schema, obj, warnings), nil
return partition.ToAPI(schema, obj, warnings, types.ReservedFields), nil
}
// ByID looks up a single object by its ID.
@ -59,7 +68,7 @@ func (s *Store) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string
if err != nil {
return types.APIObject{}, err
}
return partition.ToAPI(schema, obj, warnings), nil
return partition.ToAPI(schema, obj, warnings, types.ReservedFields), nil
}
// List returns a list of objects across all applicable partitions.
@ -85,7 +94,8 @@ func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.AP
for _, item := range list {
item := item.DeepCopy()
result.Objects = append(result.Objects, partition.ToAPI(schema, item, nil))
// the sql cache automatically adds the ID through a transformFunc. Because of this, we have a different set of reserved fields for the SQL cache
result.Objects = append(result.Objects, partition.ToAPI(schema, item, nil, s.sqlReservedFields))
}
result.Revision = ""
@ -101,7 +111,7 @@ func (s *Store) Create(apiOp *types.APIRequest, schema *types.APISchema, data ty
if err != nil {
return types.APIObject{}, err
}
return partition.ToAPI(schema, obj, warnings), nil
return partition.ToAPI(schema, obj, warnings, types.ReservedFields), nil
}
// Update updates a single object in the store.
@ -112,7 +122,7 @@ func (s *Store) Update(apiOp *types.APIRequest, schema *types.APISchema, data ty
if err != nil {
return types.APIObject{}, err
}
return partition.ToAPI(schema, obj, warnings), nil
return partition.ToAPI(schema, obj, warnings, types.ReservedFields), nil
}
// Watch returns a channel of events for a list or resource.

View File

@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/rancher/steve/pkg/stores/sqlproxy (interfaces: Cache,ClientGetter,CacheFactory,SchemaColumnSetter,RelationshipNotifier)
// Source: github.com/rancher/steve/pkg/stores/sqlproxy (interfaces: Cache,ClientGetter,CacheFactory,SchemaColumnSetter,RelationshipNotifier,TransformBuilder)
// Package sqlproxy is a generated GoMock package.
package sqlproxy
@ -19,6 +19,7 @@ import (
dynamic "k8s.io/client-go/dynamic"
kubernetes "k8s.io/client-go/kubernetes"
rest "k8s.io/client-go/rest"
cache "k8s.io/client-go/tools/cache"
)
// MockCache is a mock of Cache interface.
@ -257,18 +258,18 @@ func (m *MockCacheFactory) EXPECT() *MockCacheFactoryMockRecorder {
}
// CacheFor mocks base method.
func (m *MockCacheFactory) CacheFor(arg0 [][]string, arg1 dynamic.ResourceInterface, arg2 schema.GroupVersionKind, arg3 bool) (factory.Cache, error) {
func (m *MockCacheFactory) CacheFor(arg0 [][]string, arg1 cache.TransformFunc, arg2 dynamic.ResourceInterface, arg3 schema.GroupVersionKind, arg4 bool) (factory.Cache, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CacheFor", arg0, arg1, arg2, arg3)
ret := m.ctrl.Call(m, "CacheFor", arg0, arg1, arg2, arg3, arg4)
ret0, _ := ret[0].(factory.Cache)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CacheFor indicates an expected call of CacheFor.
func (mr *MockCacheFactoryMockRecorder) CacheFor(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
func (mr *MockCacheFactoryMockRecorder) CacheFor(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CacheFor", reflect.TypeOf((*MockCacheFactory)(nil).CacheFor), arg0, arg1, arg2, arg3)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CacheFor", reflect.TypeOf((*MockCacheFactory)(nil).CacheFor), arg0, arg1, arg2, arg3, arg4)
}
// Reset mocks base method.
@ -358,3 +359,40 @@ func (mr *MockRelationshipNotifierMockRecorder) OnInboundRelationshipChange(arg0
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnInboundRelationshipChange", reflect.TypeOf((*MockRelationshipNotifier)(nil).OnInboundRelationshipChange), arg0, arg1, arg2)
}
// MockTransformBuilder is a mock of TransformBuilder interface.
type MockTransformBuilder struct {
ctrl *gomock.Controller
recorder *MockTransformBuilderMockRecorder
}
// MockTransformBuilderMockRecorder is the mock recorder for MockTransformBuilder.
type MockTransformBuilderMockRecorder struct {
mock *MockTransformBuilder
}
// NewMockTransformBuilder creates a new mock instance.
func NewMockTransformBuilder(ctrl *gomock.Controller) *MockTransformBuilder {
mock := &MockTransformBuilder{ctrl: ctrl}
mock.recorder = &MockTransformBuilderMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockTransformBuilder) EXPECT() *MockTransformBuilderMockRecorder {
return m.recorder
}
// GetTransformFunc mocks base method.
func (m *MockTransformBuilder) GetTransformFunc(arg0 schema.GroupVersionKind) cache.TransformFunc {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetTransformFunc", arg0)
ret0, _ := ret[0].(cache.TransformFunc)
return ret0
}
// GetTransformFunc indicates an expected call of GetTransformFunc.
func (mr *MockTransformBuilderMockRecorder) GetTransformFunc(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTransformFunc", reflect.TypeOf((*MockTransformBuilder)(nil).GetTransformFunc), arg0)
}

View File

@ -22,6 +22,8 @@ import (
"github.com/rancher/lasso/pkg/cache/sql/partition"
"github.com/rancher/steve/pkg/attributes"
"github.com/rancher/steve/pkg/resources/common"
"github.com/rancher/steve/pkg/resources/virtual"
virtualCommon "github.com/rancher/steve/pkg/resources/virtual/common"
metricsStore "github.com/rancher/steve/pkg/stores/metrics"
"github.com/rancher/steve/pkg/stores/sqlpartition/listprocessor"
"github.com/rancher/steve/pkg/stores/sqlproxy/tablelistconvert"
@ -42,6 +44,7 @@ import (
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
)
const watchTimeoutEnv = "CATTLE_WATCH_TIMEOUT_SECONDS"
@ -57,6 +60,10 @@ var (
"management.cattle.io_v3_Node": {{`status`, `nodeName`}},
}
commonIndexFields = [][]string{
{`id`},
{`metadata`, `state`, `name`},
}
baseNSSchema = types.APISchema{
Schema: &schemas.Schema{
Attributes: map[string]interface{}{
@ -118,29 +125,35 @@ type RelationshipNotifier interface {
OnInboundRelationshipChange(ctx context.Context, schema *types.APISchema, namespace string) <-chan *summary.Relationship
}
type TransformBuilder interface {
GetTransformFunc(gvk schema.GroupVersionKind) cache.TransformFunc
}
type Store struct {
clientGetter ClientGetter
notifier RelationshipNotifier
cacheFactory CacheFactory
cfInitializer CacheFactoryInitializer
namespaceCache Cache
lock sync.Mutex
columnSetter SchemaColumnSetter
clientGetter ClientGetter
notifier RelationshipNotifier
cacheFactory CacheFactory
cfInitializer CacheFactoryInitializer
namespaceCache Cache
lock sync.Mutex
columnSetter SchemaColumnSetter
transformBuilder TransformBuilder
}
type CacheFactoryInitializer func() (CacheFactory, error)
type CacheFactory interface {
CacheFor(fields [][]string, client dynamic.ResourceInterface, gvk schema.GroupVersionKind, namespaced bool) (factory.Cache, error)
CacheFor(fields [][]string, transform cache.TransformFunc, client dynamic.ResourceInterface, gvk schema.GroupVersionKind, namespaced bool) (factory.Cache, error)
Reset() error
}
// NewProxyStore returns a Store implemented directly on top of kubernetes.
func NewProxyStore(c SchemaColumnSetter, clientGetter ClientGetter, notifier RelationshipNotifier, factory CacheFactory) (*Store, error) {
func NewProxyStore(c SchemaColumnSetter, clientGetter ClientGetter, notifier RelationshipNotifier, scache virtualCommon.SummaryCache, factory CacheFactory) (*Store, error) {
store := &Store{
clientGetter: clientGetter,
notifier: notifier,
columnSetter: c,
clientGetter: clientGetter,
notifier: notifier,
columnSetter: c,
transformBuilder: virtual.NewTransformBuilder(scache),
}
if factory == nil {
@ -197,14 +210,18 @@ func (s *Store) initializeNamespaceCache() error {
return err
}
gvk := attributes.GVK(&nsSchema)
// get fields from schema's columns
fields := getFieldsFromSchema(&nsSchema)
// get any type-specific fields that steve is interested in
fields = append(fields, getFieldForGVK(attributes.GVK(&nsSchema))...)
fields = append(fields, getFieldForGVK(gvk)...)
// get the type-specifc transform func
transformFunc := s.transformBuilder.GetTransformFunc(gvk)
// get the ns informer
nsInformer, err := s.cacheFactory.CacheFor(fields, &tablelistconvert.Client{ResourceInterface: client}, attributes.GVK(&nsSchema), false)
nsInformer, err := s.cacheFactory.CacheFor(fields, transformFunc, &tablelistconvert.Client{ResourceInterface: client}, attributes.GVK(&nsSchema), false)
if err != nil {
return err
}
@ -214,7 +231,13 @@ func (s *Store) initializeNamespaceCache() error {
}
func getFieldForGVK(gvk schema.GroupVersionKind) [][]string {
return typeSpecificIndexedFields[keyFromGVK(gvk)]
fields := [][]string{}
fields = append(fields, commonIndexFields...)
typeFields := typeSpecificIndexedFields[keyFromGVK(gvk)]
if typeFields != nil {
fields = append(fields, typeFields...)
}
return fields
}
func keyFromGVK(gvk schema.GroupVersionKind) string {
@ -623,10 +646,12 @@ func (s *Store) ListByPartitions(apiOp *types.APIRequest, schema *types.APISchem
if err != nil {
return nil, 0, "", err
}
gvk := attributes.GVK(schema)
fields := getFieldsFromSchema(schema)
fields = append(fields, getFieldForGVK(attributes.GVK(schema))...)
fields = append(fields, getFieldForGVK(gvk)...)
transformFunc := s.transformBuilder.GetTransformFunc(gvk)
inf, err := s.cacheFactory.CacheFor(fields, &tablelistconvert.Client{ResourceInterface: client}, attributes.GVK(schema), attributes.Namespaced(schema))
inf, err := s.cacheFactory.CacheFor(fields, transformFunc, &tablelistconvert.Client{ResourceInterface: client}, attributes.GVK(schema), attributes.Namespaced(schema))
if err != nil {
return nil, 0, "", err
}

View File

@ -37,7 +37,7 @@ import (
clientgotesting "k8s.io/client-go/testing"
)
//go:generate mockgen --build_flags=--mod=mod -package sqlproxy -destination ./proxy_mocks_test.go github.com/rancher/steve/pkg/stores/sqlproxy Cache,ClientGetter,CacheFactory,SchemaColumnSetter,RelationshipNotifier
//go:generate mockgen --build_flags=--mod=mod -package sqlproxy -destination ./proxy_mocks_test.go github.com/rancher/steve/pkg/stores/sqlproxy Cache,ClientGetter,CacheFactory,SchemaColumnSetter,RelationshipNotifier,TransformBuilder
//go:generate mockgen --build_flags=--mod=mod -package sqlproxy -destination ./sql_informer_mocks_test.go github.com/rancher/lasso/pkg/cache/sql/informer ByOptionsLister
//go:generate mockgen --build_flags=--mod=mod -package sqlproxy -destination ./dynamic_mocks_test.go k8s.io/client-go/dynamic ResourceInterface
@ -74,9 +74,9 @@ func TestNewProxyStore(t *testing.T) {
nsSchema := baseNSSchema
scc.EXPECT().SetColumns(context.Background(), &nsSchema).Return(nil)
cg.EXPECT().TableAdminClient(nil, &nsSchema, "", &WarningBuffer{}).Return(ri, nil)
cf.EXPECT().CacheFor([][]string{{"metadata", "labels[field.cattle.io/projectId]"}}, &tablelistconvert.Client{ResourceInterface: ri}, attributes.GVK(&nsSchema), false).Return(c, nil)
cf.EXPECT().CacheFor([][]string{{`id`}, {`metadata`, `state`, `name`}, {"metadata", "labels[field.cattle.io/projectId]"}}, gomock.Any(), &tablelistconvert.Client{ResourceInterface: ri}, attributes.GVK(&nsSchema), false).Return(c, nil)
s, err := NewProxyStore(scc, cg, rn, cf)
s, err := NewProxyStore(scc, cg, rn, nil, cf)
assert.Nil(t, err)
assert.Equal(t, scc, s.columnSetter)
assert.Equal(t, cg, s.clientGetter)
@ -97,7 +97,7 @@ func TestNewProxyStore(t *testing.T) {
nsSchema := baseNSSchema
scc.EXPECT().SetColumns(context.Background(), &nsSchema).Return(fmt.Errorf("error"))
s, err := NewProxyStore(scc, cg, rn, cf)
s, err := NewProxyStore(scc, cg, rn, nil, cf)
assert.Nil(t, err)
assert.Equal(t, scc, s.columnSetter)
assert.Equal(t, cg, s.clientGetter)
@ -119,7 +119,7 @@ func TestNewProxyStore(t *testing.T) {
scc.EXPECT().SetColumns(context.Background(), &nsSchema).Return(nil)
cg.EXPECT().TableAdminClient(nil, &nsSchema, "", &WarningBuffer{}).Return(nil, fmt.Errorf("error"))
s, err := NewProxyStore(scc, cg, rn, cf)
s, err := NewProxyStore(scc, cg, rn, nil, cf)
assert.Nil(t, err)
assert.Equal(t, scc, s.columnSetter)
assert.Equal(t, cg, s.clientGetter)
@ -141,9 +141,9 @@ func TestNewProxyStore(t *testing.T) {
nsSchema := baseNSSchema
scc.EXPECT().SetColumns(context.Background(), &nsSchema).Return(nil)
cg.EXPECT().TableAdminClient(nil, &nsSchema, "", &WarningBuffer{}).Return(ri, nil)
cf.EXPECT().CacheFor([][]string{{"metadata", "labels[field.cattle.io/projectId]"}}, &tablelistconvert.Client{ResourceInterface: ri}, attributes.GVK(&nsSchema), false).Return(factory.Cache{}, fmt.Errorf("error"))
cf.EXPECT().CacheFor([][]string{{`id`}, {`metadata`, `state`, `name`}, {"metadata", "labels[field.cattle.io/projectId]"}}, gomock.Any(), &tablelistconvert.Client{ResourceInterface: ri}, attributes.GVK(&nsSchema), false).Return(factory.Cache{}, fmt.Errorf("error"))
s, err := NewProxyStore(scc, cg, rn, cf)
s, err := NewProxyStore(scc, cg, rn, nil, cf)
assert.Nil(t, err)
assert.Equal(t, scc, s.columnSetter)
assert.Equal(t, cg, s.clientGetter)
@ -173,6 +173,7 @@ func TestListByPartitions(t *testing.T) {
cf := NewMockCacheFactory(gomock.NewController(t))
ri := NewMockResourceInterface(gomock.NewController(t))
bloi := NewMockByOptionsLister(gomock.NewController(t))
tb := NewMockTransformBuilder(gomock.NewController(t))
inf := &informer.Informer{
ByOptionsLister: bloi,
}
@ -180,9 +181,10 @@ func TestListByPartitions(t *testing.T) {
ByOptionsLister: inf,
}
s := &Store{
namespaceCache: nsi,
clientGetter: cg,
cacheFactory: cf,
namespaceCache: nsi,
clientGetter: cg,
cacheFactory: cf,
transformBuilder: tb,
}
var partitions []partition.Partition
req := &types.APIRequest{
@ -230,7 +232,8 @@ func TestListByPartitions(t *testing.T) {
assert.Nil(t, err)
cg.EXPECT().TableAdminClient(req, schema, "", &WarningBuffer{}).Return(ri, nil)
// This tests that fields are being extracted from schema columns and the type specific fields map
cf.EXPECT().CacheFor([][]string{{"some", "field"}, {"gvk", "specific", "fields"}}, &tablelistconvert.Client{ResourceInterface: ri}, attributes.GVK(schema), attributes.Namespaced(schema)).Return(c, nil)
cf.EXPECT().CacheFor([][]string{{"some", "field"}, {`id`}, {`metadata`, `state`, `name`}, {"gvk", "specific", "fields"}}, gomock.Any(), &tablelistconvert.Client{ResourceInterface: ri}, attributes.GVK(schema), attributes.Namespaced(schema)).Return(c, nil)
tb.EXPECT().GetTransformFunc(attributes.GVK(schema)).Return(func(obj interface{}) (interface{}, error) { return obj, nil })
bloi.EXPECT().ListByOptions(req.Context(), opts, partitions, req.Namespace).Return(listToReturn, len(listToReturn.Items), "", nil)
list, total, contToken, err := s.ListByPartitions(req, schema, partitions)
assert.Nil(t, err)
@ -245,11 +248,13 @@ func TestListByPartitions(t *testing.T) {
nsi := NewMockCache(gomock.NewController(t))
cg := NewMockClientGetter(gomock.NewController(t))
cf := NewMockCacheFactory(gomock.NewController(t))
tb := NewMockTransformBuilder(gomock.NewController(t))
s := &Store{
namespaceCache: nsi,
clientGetter: cg,
cacheFactory: cf,
namespaceCache: nsi,
clientGetter: cg,
cacheFactory: cf,
transformBuilder: tb,
}
var partitions []partition.Partition
req := &types.APIRequest{
@ -309,11 +314,13 @@ func TestListByPartitions(t *testing.T) {
nsi := NewMockCache(gomock.NewController(t))
cg := NewMockClientGetter(gomock.NewController(t))
cf := NewMockCacheFactory(gomock.NewController(t))
tb := NewMockTransformBuilder(gomock.NewController(t))
s := &Store{
namespaceCache: nsi,
clientGetter: cg,
cacheFactory: cf,
namespaceCache: nsi,
clientGetter: cg,
cacheFactory: cf,
transformBuilder: tb,
}
var partitions []partition.Partition
req := &types.APIRequest{
@ -372,11 +379,13 @@ func TestListByPartitions(t *testing.T) {
cg := NewMockClientGetter(gomock.NewController(t))
cf := NewMockCacheFactory(gomock.NewController(t))
ri := NewMockResourceInterface(gomock.NewController(t))
tb := NewMockTransformBuilder(gomock.NewController(t))
s := &Store{
namespaceCache: nsi,
clientGetter: cg,
cacheFactory: cf,
namespaceCache: nsi,
clientGetter: cg,
cacheFactory: cf,
transformBuilder: tb,
}
var partitions []partition.Partition
req := &types.APIRequest{
@ -424,7 +433,8 @@ func TestListByPartitions(t *testing.T) {
assert.Nil(t, err)
cg.EXPECT().TableAdminClient(req, schema, "", &WarningBuffer{}).Return(ri, nil)
// This tests that fields are being extracted from schema columns and the type specific fields map
cf.EXPECT().CacheFor([][]string{{"some", "field"}, {"gvk", "specific", "fields"}}, &tablelistconvert.Client{ResourceInterface: ri}, attributes.GVK(schema), attributes.Namespaced(schema)).Return(factory.Cache{}, fmt.Errorf("error"))
tb.EXPECT().GetTransformFunc(attributes.GVK(schema)).Return(func(obj interface{}) (interface{}, error) { return obj, nil })
cf.EXPECT().CacheFor([][]string{{"some", "field"}, {`id`}, {`metadata`, `state`, `name`}, {"gvk", "specific", "fields"}}, gomock.Any(), &tablelistconvert.Client{ResourceInterface: ri}, attributes.GVK(schema), attributes.Namespaced(schema)).Return(factory.Cache{}, fmt.Errorf("error"))
_, _, _, err = s.ListByPartitions(req, schema, partitions)
assert.NotNil(t, err)
@ -439,6 +449,7 @@ func TestListByPartitions(t *testing.T) {
cf := NewMockCacheFactory(gomock.NewController(t))
ri := NewMockResourceInterface(gomock.NewController(t))
bloi := NewMockByOptionsLister(gomock.NewController(t))
tb := NewMockTransformBuilder(gomock.NewController(t))
inf := &informer.Informer{
ByOptionsLister: bloi,
}
@ -446,9 +457,10 @@ func TestListByPartitions(t *testing.T) {
ByOptionsLister: inf,
}
s := &Store{
namespaceCache: nsi,
clientGetter: cg,
cacheFactory: cf,
namespaceCache: nsi,
clientGetter: cg,
cacheFactory: cf,
transformBuilder: tb,
}
var partitions []partition.Partition
req := &types.APIRequest{
@ -496,8 +508,9 @@ func TestListByPartitions(t *testing.T) {
assert.Nil(t, err)
cg.EXPECT().TableAdminClient(req, schema, "", &WarningBuffer{}).Return(ri, nil)
// This tests that fields are being extracted from schema columns and the type specific fields map
cf.EXPECT().CacheFor([][]string{{"some", "field"}, {"gvk", "specific", "fields"}}, &tablelistconvert.Client{ResourceInterface: ri}, attributes.GVK(schema), attributes.Namespaced(schema)).Return(c, nil)
cf.EXPECT().CacheFor([][]string{{"some", "field"}, {`id`}, {`metadata`, `state`, `name`}, {"gvk", "specific", "fields"}}, gomock.Any(), &tablelistconvert.Client{ResourceInterface: ri}, attributes.GVK(schema), attributes.Namespaced(schema)).Return(c, nil)
bloi.EXPECT().ListByOptions(req.Context(), opts, partitions, req.Namespace).Return(nil, 0, "", fmt.Errorf("error"))
tb.EXPECT().GetTransformFunc(attributes.GVK(schema)).Return(func(obj interface{}) (interface{}, error) { return obj, nil })
_, _, _, err = s.ListByPartitions(req, schema, partitions)
assert.NotNil(t, err)
@ -523,19 +536,22 @@ func TestReset(t *testing.T) {
cf := NewMockCacheFactory(gomock.NewController(t))
cs := NewMockSchemaColumnSetter(gomock.NewController(t))
ri := NewMockResourceInterface(gomock.NewController(t))
tb := NewMockTransformBuilder(gomock.NewController(t))
nsc2 := factory.Cache{}
s := &Store{
namespaceCache: nsc,
clientGetter: cg,
cacheFactory: cf,
columnSetter: cs,
cfInitializer: func() (CacheFactory, error) { return cf, nil },
namespaceCache: nsc,
clientGetter: cg,
cacheFactory: cf,
columnSetter: cs,
cfInitializer: func() (CacheFactory, error) { return cf, nil },
transformBuilder: tb,
}
nsSchema := baseNSSchema
cf.EXPECT().Reset().Return(nil)
cs.EXPECT().SetColumns(gomock.Any(), gomock.Any()).Return(nil)
cg.EXPECT().TableAdminClient(nil, &nsSchema, "", &WarningBuffer{}).Return(ri, nil)
cf.EXPECT().CacheFor([][]string{{"metadata", "labels[field.cattle.io/projectId]"}}, &tablelistconvert.Client{ResourceInterface: ri}, attributes.GVK(&nsSchema), false).Return(nsc2, nil)
cf.EXPECT().CacheFor([][]string{{`id`}, {`metadata`, `state`, `name`}, {"metadata", "labels[field.cattle.io/projectId]"}}, gomock.Any(), &tablelistconvert.Client{ResourceInterface: ri}, attributes.GVK(&nsSchema), false).Return(nsc2, nil)
tb.EXPECT().GetTransformFunc(attributes.GVK(&nsSchema)).Return(func(obj interface{}) (interface{}, error) { return obj, nil })
err := s.Reset()
assert.Nil(t, err)
assert.Equal(t, nsc2, s.namespaceCache)
@ -548,13 +564,15 @@ func TestReset(t *testing.T) {
cg := NewMockClientGetter(gomock.NewController(t))
cf := NewMockCacheFactory(gomock.NewController(t))
cs := NewMockSchemaColumnSetter(gomock.NewController(t))
tb := NewMockTransformBuilder(gomock.NewController(t))
s := &Store{
namespaceCache: nsi,
clientGetter: cg,
cacheFactory: cf,
columnSetter: cs,
cfInitializer: func() (CacheFactory, error) { return cf, nil },
namespaceCache: nsi,
clientGetter: cg,
cacheFactory: cf,
columnSetter: cs,
cfInitializer: func() (CacheFactory, error) { return cf, nil },
transformBuilder: tb,
}
cf.EXPECT().Reset().Return(fmt.Errorf("error"))
@ -569,13 +587,15 @@ func TestReset(t *testing.T) {
cg := NewMockClientGetter(gomock.NewController(t))
cf := NewMockCacheFactory(gomock.NewController(t))
cs := NewMockSchemaColumnSetter(gomock.NewController(t))
tb := NewMockTransformBuilder(gomock.NewController(t))
s := &Store{
namespaceCache: nsi,
clientGetter: cg,
cacheFactory: cf,
columnSetter: cs,
cfInitializer: func() (CacheFactory, error) { return cf, nil },
namespaceCache: nsi,
clientGetter: cg,
cacheFactory: cf,
columnSetter: cs,
cfInitializer: func() (CacheFactory, error) { return cf, nil },
transformBuilder: tb,
}
cf.EXPECT().Reset().Return(nil)
@ -591,13 +611,15 @@ func TestReset(t *testing.T) {
cg := NewMockClientGetter(gomock.NewController(t))
cf := NewMockCacheFactory(gomock.NewController(t))
cs := NewMockSchemaColumnSetter(gomock.NewController(t))
tb := NewMockTransformBuilder(gomock.NewController(t))
s := &Store{
namespaceCache: nsi,
clientGetter: cg,
cacheFactory: cf,
columnSetter: cs,
cfInitializer: func() (CacheFactory, error) { return cf, nil },
namespaceCache: nsi,
clientGetter: cg,
cacheFactory: cf,
columnSetter: cs,
cfInitializer: func() (CacheFactory, error) { return cf, nil },
transformBuilder: tb,
}
nsSchema := baseNSSchema
@ -616,20 +638,23 @@ func TestReset(t *testing.T) {
cf := NewMockCacheFactory(gomock.NewController(t))
cs := NewMockSchemaColumnSetter(gomock.NewController(t))
ri := NewMockResourceInterface(gomock.NewController(t))
tb := NewMockTransformBuilder(gomock.NewController(t))
s := &Store{
namespaceCache: nsc,
clientGetter: cg,
cacheFactory: cf,
columnSetter: cs,
cfInitializer: func() (CacheFactory, error) { return cf, nil },
namespaceCache: nsc,
clientGetter: cg,
cacheFactory: cf,
columnSetter: cs,
cfInitializer: func() (CacheFactory, error) { return cf, nil },
transformBuilder: tb,
}
nsSchema := baseNSSchema
cf.EXPECT().Reset().Return(nil)
cs.EXPECT().SetColumns(gomock.Any(), gomock.Any()).Return(nil)
cg.EXPECT().TableAdminClient(nil, &nsSchema, "", &WarningBuffer{}).Return(ri, nil)
cf.EXPECT().CacheFor([][]string{{"metadata", "labels[field.cattle.io/projectId]"}}, &tablelistconvert.Client{ResourceInterface: ri}, attributes.GVK(&nsSchema), false).Return(factory.Cache{}, fmt.Errorf("error"))
cf.EXPECT().CacheFor([][]string{{`id`}, {`metadata`, `state`, `name`}, {"metadata", "labels[field.cattle.io/projectId]"}}, gomock.Any(), &tablelistconvert.Client{ResourceInterface: ri}, attributes.GVK(&nsSchema), false).Return(factory.Cache{}, fmt.Errorf("error"))
tb.EXPECT().GetTransformFunc(attributes.GVK(&nsSchema)).Return(func(obj interface{}) (interface{}, error) { return obj, nil })
err := s.Reset()
assert.NotNil(t, err)
},