karmada/pkg/search/proxy/store/resource_cache.go

199 lines
6.5 KiB
Go

package store
import (
"context"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/features"
"k8s.io/apiserver/pkg/registry/generic"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/storage"
cacherstorage "k8s.io/apiserver/pkg/storage/cacher"
"k8s.io/apiserver/pkg/storage/storagebackend"
"k8s.io/apiserver/pkg/storage/storagebackend/factory"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/tools/cache"
"k8s.io/klog/v2"
)
// resourceCache cache one kind resource from single member cluster
type resourceCache struct {
*genericregistry.Store
clusterName string
resource schema.GroupVersionResource
multiNS *MultiNamespace
}
func (c *resourceCache) stop() {
klog.Infof("Stop store for %s %s", c.clusterName, c.resource)
go c.Store.DestroyFunc()
}
func newResourceCache(clusterName string, gvr schema.GroupVersionResource, gvk schema.GroupVersionKind, singularName string,
namespaced bool, multiNS *MultiNamespace, newClientFunc func() (dynamic.NamespaceableResourceInterface, error)) (*resourceCache, error) {
s := &genericregistry.Store{
DefaultQualifiedResource: gvr.GroupResource(),
SingularQualifiedResource: schema.GroupResource{Group: gvr.Group, Resource: singularName},
NewFunc: func() runtime.Object {
o := &unstructured.Unstructured{}
o.SetAPIVersion(gvk.GroupVersion().String())
o.SetKind(gvk.Kind)
return o
},
NewListFunc: func() runtime.Object {
o := &unstructured.UnstructuredList{}
o.SetAPIVersion(gvk.GroupVersion().String())
// TODO: it's unsafe guesses kind name for resource list
o.SetKind(gvk.Kind + "List")
return o
},
TableConvertor: rest.NewDefaultTableConvertor(gvr.GroupResource()),
// CreateStrategy tells whether the resource is namespaced.
// see: vendor/k8s.io/apiserver/pkg/registry/generic/registry/store.go#L1310-L1318
CreateStrategy: restCreateStrategy(namespaced),
// Assign `DeleteStrategy` to pass vendor/k8s.io/apiserver/pkg/registry/generic/registry/store.go#L1320-L1322
DeleteStrategy: restDeleteStrategy,
}
err := s.CompleteWithOptions(&generic.StoreOptions{
RESTOptions: &generic.RESTOptions{
StorageConfig: &storagebackend.ConfigForResource{
Config: storagebackend.Config{
Paging: utilfeature.DefaultFeatureGate.Enabled(features.APIListChunking),
Codec: unstructured.UnstructuredJSONScheme,
},
GroupResource: gvr.GroupResource(),
},
ResourcePrefix: gvr.Group + "/" + gvr.Resource,
Decorator: storageWithCacher(gvr, multiNS, newClientFunc, defaultVersioner),
},
AttrFunc: getAttrsFunc(namespaced),
})
if err != nil {
return nil, err
}
return &resourceCache{
clusterName: clusterName,
Store: s,
resource: gvr,
multiNS: multiNS,
}, nil
}
func storageWithCacher(gvr schema.GroupVersionResource, multiNS *MultiNamespace, newClientFunc func() (dynamic.NamespaceableResourceInterface, error), versioner storage.Versioner) generic.StorageDecorator {
return func(
storageConfig *storagebackend.ConfigForResource,
resourcePrefix string,
keyFunc func(obj runtime.Object) (string, error),
newFunc func() runtime.Object,
newListFunc func() runtime.Object,
getAttrsFunc storage.AttrFunc,
triggerFuncs storage.IndexerFuncs,
indexers *cache.Indexers) (storage.Interface, factory.DestroyFunc, error) {
cacherConfig := cacherstorage.Config{
Storage: newStore(gvr, multiNS, newClientFunc, versioner, resourcePrefix),
Versioner: versioner,
GroupResource: storageConfig.GroupResource,
ResourcePrefix: resourcePrefix,
KeyFunc: keyFunc,
GetAttrsFunc: getAttrsFunc,
Indexers: indexers,
NewFunc: newFunc,
NewListFunc: newListFunc,
Codec: storageConfig.Codec,
}
cacher, err := cacherstorage.NewCacherFromConfig(cacherConfig)
if err != nil {
return nil, nil, err
}
return cacher, cacher.Stop, nil
}
}
var (
restCreateStrategyForNamespaced = &simpleRESTCreateStrategy{namespaced: true}
restCreateStrategyForCluster = &simpleRESTCreateStrategy{namespaced: false}
restDeleteStrategy = &simpleRESTDeleteStrategy{RESTDeleteStrategy: runtime.NewScheme()}
defaultVersioner = storage.APIObjectVersioner{}
)
type simpleRESTDeleteStrategy struct {
rest.RESTDeleteStrategy
}
type simpleRESTCreateStrategy struct {
namespaced bool
}
func (s *simpleRESTCreateStrategy) NamespaceScoped() bool {
return s.namespaced
}
func (s *simpleRESTCreateStrategy) ObjectKinds(runtime.Object) ([]schema.GroupVersionKind, bool, error) {
panic("simpleRESTCreateStrategy.ObjectKinds is not supported")
}
func (s *simpleRESTCreateStrategy) Recognizes(schema.GroupVersionKind) bool {
panic("simpleRESTCreateStrategy.Recognizes is not supported")
}
func (s *simpleRESTCreateStrategy) GenerateName(string) string {
panic("simpleRESTCreateStrategy.GenerateName is not supported")
}
func (s *simpleRESTCreateStrategy) PrepareForCreate(context.Context, runtime.Object) {
panic("simpleRESTCreateStrategy.PrepareForCreate is not supported")
}
func (s *simpleRESTCreateStrategy) Validate(context.Context, runtime.Object) field.ErrorList {
panic("simpleRESTCreateStrategy.Validate is not supported")
}
func (s *simpleRESTCreateStrategy) WarningsOnCreate(context.Context, runtime.Object) []string {
panic("simpleRESTCreateStrategy.WarningsOnCreate is not supported")
}
func (s *simpleRESTCreateStrategy) Canonicalize(runtime.Object) {
panic("simpleRESTCreateStrategy.Canonicalize is not supported")
}
func restCreateStrategy(namespaced bool) rest.RESTCreateStrategy {
if namespaced {
return restCreateStrategyForNamespaced
}
return restCreateStrategyForCluster
}
func getAttrsFunc(namespaced bool) func(runtime.Object) (labels.Set, fields.Set, error) {
return func(object runtime.Object) (label labels.Set, field fields.Set, err error) {
accessor, err := meta.Accessor(object)
if err != nil {
return nil, nil, err
}
label = accessor.GetLabels()
// In proxy, fieldSelector only support name and namespace
if namespaced {
field = fields.Set{
"metadata.name": accessor.GetName(),
"metadata.namespace": accessor.GetNamespace(),
}
} else {
field = fields.Set{
"metadata.name": accessor.GetName(),
}
}
return label, field, nil
}
}