mirror of
https://github.com/kubernetes/client-go.git
synced 2025-06-23 13:47:19 +00:00
Merge pull request #70994 from mborsz/cache
Refactor the memcached discovery client Kubernetes-commit: 3f9673bf5d6d9adeeca5eb349c83ece6095a41ba
This commit is contained in:
commit
c9a54130ee
104
Godeps/Godeps.json
generated
104
Godeps/Godeps.json
generated
@ -1,7 +1,7 @@
|
||||
{
|
||||
"ImportPath": "k8s.io/client-go",
|
||||
"GoVersion": "go1.11",
|
||||
"GodepVersion": "v80",
|
||||
"GodepVersion": "v80-k8s-r1",
|
||||
"Packages": [
|
||||
"./..."
|
||||
],
|
||||
@ -404,207 +404,207 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/api/apitesting",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/api/apitesting/fuzzer",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/api/apitesting/roundtrip",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/api/equality",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/api/errors",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/api/meta",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/api/resource",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/fuzzer",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/internalversion",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1beta1",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/conversion",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/conversion/queryparams",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/fields",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/labels",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/json",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/protobuf",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/recognizer",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/streaming",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/versioning",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/selection",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/types",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/cache",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/clock",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/diff",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/errors",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/framer",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/httpstream",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/httpstream/spdy",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/intstr",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/json",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/mergepatch",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/naming",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/net",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/remotecommand",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/runtime",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/sets",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/strategicpatch",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/validation",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/validation/field",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/wait",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/util/yaml",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/version",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/pkg/watch",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/json",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/netutil",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery/third_party/forked/golang/reflect",
|
||||
"Rev": "4d029f0333996cf231080e108e0bd1ece2a94d9f"
|
||||
"Rev": "d04500c8c3dda9c980b668c57abc2ca61efcf5c4"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/klog",
|
||||
|
@ -19,10 +19,14 @@ package cached
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/googleapis/gnostic/OpenAPIv2"
|
||||
|
||||
errorsutil "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
@ -30,40 +34,87 @@ import (
|
||||
restclient "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type cacheEntry struct {
|
||||
resourceList *metav1.APIResourceList
|
||||
err error
|
||||
}
|
||||
|
||||
// memCacheClient can Invalidate() to stay up-to-date with discovery
|
||||
// information.
|
||||
//
|
||||
// TODO: Switch to a watch interface. Right now it will poll anytime
|
||||
// Invalidate() is called.
|
||||
// TODO: Switch to a watch interface. Right now it will poll after each
|
||||
// Invalidate() call.
|
||||
type memCacheClient struct {
|
||||
delegate discovery.DiscoveryInterface
|
||||
|
||||
lock sync.RWMutex
|
||||
groupToServerResources map[string]*metav1.APIResourceList
|
||||
groupToServerResources map[string]*cacheEntry
|
||||
groupList *metav1.APIGroupList
|
||||
cacheValid bool
|
||||
}
|
||||
|
||||
// Error Constants
|
||||
var (
|
||||
ErrCacheEmpty = errors.New("the cache has not been filled yet")
|
||||
ErrCacheNotFound = errors.New("not found")
|
||||
)
|
||||
|
||||
var _ discovery.CachedDiscoveryInterface = &memCacheClient{}
|
||||
|
||||
// isTransientConnectionError checks whether given error is "Connection refused" or
|
||||
// "Connection reset" error which usually means that apiserver is temporarily
|
||||
// unavailable.
|
||||
func isTransientConnectionError(err error) bool {
|
||||
urlError, ok := err.(*url.Error)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
opError, ok := urlError.Err.(*net.OpError)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
errno, ok := opError.Err.(syscall.Errno)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return errno == syscall.ECONNREFUSED || errno == syscall.ECONNRESET
|
||||
}
|
||||
|
||||
func isTransientError(err error) bool {
|
||||
if isTransientConnectionError(err) {
|
||||
return true
|
||||
}
|
||||
|
||||
if t, ok := err.(errorsutil.APIStatus); ok && t.Status().Code >= 500 {
|
||||
return true
|
||||
}
|
||||
|
||||
return errorsutil.IsTooManyRequests(err)
|
||||
}
|
||||
|
||||
// ServerResourcesForGroupVersion returns the supported resources for a group and version.
|
||||
func (d *memCacheClient) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
|
||||
d.lock.RLock()
|
||||
defer d.lock.RUnlock()
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
if !d.cacheValid {
|
||||
return nil, ErrCacheEmpty
|
||||
if err := d.refreshLocked(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
cachedVal, ok := d.groupToServerResources[groupVersion]
|
||||
if !ok {
|
||||
return nil, ErrCacheNotFound
|
||||
}
|
||||
return cachedVal, nil
|
||||
|
||||
if cachedVal.err != nil && isTransientError(cachedVal.err) {
|
||||
r, err := d.serverResourcesForGroupVersion(groupVersion)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("couldn't get resource list for %v: %v", groupVersion, err))
|
||||
}
|
||||
cachedVal = &cacheEntry{r, err}
|
||||
d.groupToServerResources[groupVersion] = cachedVal
|
||||
}
|
||||
|
||||
return cachedVal.resourceList, cachedVal.err
|
||||
}
|
||||
|
||||
// ServerResources returns the supported resources for all groups and versions.
|
||||
@ -72,10 +123,12 @@ func (d *memCacheClient) ServerResources() ([]*metav1.APIResourceList, error) {
|
||||
}
|
||||
|
||||
func (d *memCacheClient) ServerGroups() (*metav1.APIGroupList, error) {
|
||||
d.lock.RLock()
|
||||
defer d.lock.RUnlock()
|
||||
if d.groupList == nil {
|
||||
return nil, ErrCacheEmpty
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
if !d.cacheValid {
|
||||
if err := d.refreshLocked(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return d.groupList, nil
|
||||
}
|
||||
@ -103,49 +156,59 @@ func (d *memCacheClient) OpenAPISchema() (*openapi_v2.Document, error) {
|
||||
func (d *memCacheClient) Fresh() bool {
|
||||
d.lock.RLock()
|
||||
defer d.lock.RUnlock()
|
||||
// Fresh is supposed to tell the caller whether or not to retry if the cache
|
||||
// fails to find something. The idea here is that Invalidate will be called
|
||||
// periodically and therefore we'll always be returning the latest data. (And
|
||||
// in the future we can watch and stay even more up-to-date.) So we only
|
||||
// return false if the cache has never been filled.
|
||||
// Return whether the cache is populated at all. It is still possible that
|
||||
// a single entry is missing due to transient errors and the attempt to read
|
||||
// that entry will trigger retry.
|
||||
return d.cacheValid
|
||||
}
|
||||
|
||||
// Invalidate refreshes the cache, blocking calls until the cache has been
|
||||
// refreshed. It would be trivial to make a version that does this in the
|
||||
// background while continuing to respond to requests if needed.
|
||||
// Invalidate enforces that no cached data that is older than the current time
|
||||
// is used.
|
||||
func (d *memCacheClient) Invalidate() {
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
d.cacheValid = false
|
||||
d.groupToServerResources = nil
|
||||
d.groupList = nil
|
||||
}
|
||||
|
||||
// refreshLocked refreshes the state of cache. The caller must hold d.lock for
|
||||
// writing.
|
||||
func (d *memCacheClient) refreshLocked() error {
|
||||
// TODO: Could this multiplicative set of calls be replaced by a single call
|
||||
// to ServerResources? If it's possible for more than one resulting
|
||||
// APIResourceList to have the same GroupVersion, the lists would need merged.
|
||||
gl, err := d.delegate.ServerGroups()
|
||||
if err != nil || len(gl.Groups) == 0 {
|
||||
utilruntime.HandleError(fmt.Errorf("couldn't get current server API group list; will keep using cached value. (%v)", err))
|
||||
return
|
||||
utilruntime.HandleError(fmt.Errorf("couldn't get current server API group list: %v", err))
|
||||
return err
|
||||
}
|
||||
|
||||
rl := map[string]*metav1.APIResourceList{}
|
||||
rl := map[string]*cacheEntry{}
|
||||
for _, g := range gl.Groups {
|
||||
for _, v := range g.Versions {
|
||||
r, err := d.delegate.ServerResourcesForGroupVersion(v.GroupVersion)
|
||||
if err != nil || len(r.APIResources) == 0 {
|
||||
r, err := d.serverResourcesForGroupVersion(v.GroupVersion)
|
||||
rl[v.GroupVersion] = &cacheEntry{r, err}
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("couldn't get resource list for %v: %v", v.GroupVersion, err))
|
||||
if cur, ok := d.groupToServerResources[v.GroupVersion]; ok {
|
||||
// retain the existing list, if we had it.
|
||||
r = cur
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
rl[v.GroupVersion] = r
|
||||
}
|
||||
}
|
||||
|
||||
d.groupToServerResources, d.groupList = rl, gl
|
||||
d.cacheValid = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *memCacheClient) serverResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
|
||||
r, err := d.delegate.ServerResourcesForGroupVersion(groupVersion)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
if len(r.APIResources) == 0 {
|
||||
return r, fmt.Errorf("Got empty response for: %v", groupVersion)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// NewMemCacheClient creates a new CachedDiscoveryInterface which caches
|
||||
@ -156,6 +219,6 @@ func (d *memCacheClient) Invalidate() {
|
||||
func NewMemCacheClient(delegate discovery.DiscoveryInterface) discovery.CachedDiscoveryInterface {
|
||||
return &memCacheClient{
|
||||
delegate: delegate,
|
||||
groupToServerResources: map[string]*metav1.APIResourceList{},
|
||||
groupToServerResources: map[string]*cacheEntry{},
|
||||
}
|
||||
}
|
||||
|
@ -18,27 +18,35 @@ package cached
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
errorsutil "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/discovery/fake"
|
||||
)
|
||||
|
||||
type resourceMapEntry struct {
|
||||
list *metav1.APIResourceList
|
||||
err error
|
||||
}
|
||||
|
||||
type fakeDiscovery struct {
|
||||
*fake.FakeDiscovery
|
||||
|
||||
lock sync.Mutex
|
||||
groupList *metav1.APIGroupList
|
||||
resourceMap map[string]*metav1.APIResourceList
|
||||
lock sync.Mutex
|
||||
groupList *metav1.APIGroupList
|
||||
groupListErr error
|
||||
resourceMap map[string]*resourceMapEntry
|
||||
}
|
||||
|
||||
func (c *fakeDiscovery) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if rl, ok := c.resourceMap[groupVersion]; ok {
|
||||
return rl, nil
|
||||
return rl.list, rl.err
|
||||
}
|
||||
return nil, errors.New("doesn't exist")
|
||||
}
|
||||
@ -49,7 +57,7 @@ func (c *fakeDiscovery) ServerGroups() (*metav1.APIGroupList, error) {
|
||||
if c.groupList == nil {
|
||||
return nil, errors.New("doesn't exist")
|
||||
}
|
||||
return c.groupList, nil
|
||||
return c.groupList, c.groupListErr
|
||||
}
|
||||
|
||||
func TestClient(t *testing.T) {
|
||||
@ -63,33 +71,37 @@ func TestClient(t *testing.T) {
|
||||
}},
|
||||
}},
|
||||
},
|
||||
resourceMap: map[string]*metav1.APIResourceList{
|
||||
resourceMap: map[string]*resourceMapEntry{
|
||||
"astronomy/v8beta1": {
|
||||
GroupVersion: "astronomy/v8beta1",
|
||||
APIResources: []metav1.APIResource{{
|
||||
Name: "dwarfplanets",
|
||||
SingularName: "dwarfplanet",
|
||||
Namespaced: true,
|
||||
Kind: "DwarfPlanet",
|
||||
ShortNames: []string{"dp"},
|
||||
}},
|
||||
list: &metav1.APIResourceList{
|
||||
GroupVersion: "astronomy/v8beta1",
|
||||
APIResources: []metav1.APIResource{{
|
||||
Name: "dwarfplanets",
|
||||
SingularName: "dwarfplanet",
|
||||
Namespaced: true,
|
||||
Kind: "DwarfPlanet",
|
||||
ShortNames: []string{"dp"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
c := NewMemCacheClient(fake)
|
||||
g, err := c.ServerGroups()
|
||||
if err == nil {
|
||||
t.Errorf("Unexpected non-error.")
|
||||
}
|
||||
if c.Fresh() {
|
||||
t.Errorf("Expected not fresh.")
|
||||
}
|
||||
|
||||
c.Invalidate()
|
||||
g, err := c.ServerGroups()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if !c.Fresh() {
|
||||
t.Errorf("Expected fresh.")
|
||||
}
|
||||
c.Invalidate()
|
||||
if c.Fresh() {
|
||||
t.Errorf("Expected not fresh.")
|
||||
}
|
||||
|
||||
g, err = c.ServerGroups()
|
||||
if err != nil {
|
||||
@ -98,25 +110,30 @@ func TestClient(t *testing.T) {
|
||||
if e, a := fake.groupList, g; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %#v, got %#v", e, a)
|
||||
}
|
||||
if !c.Fresh() {
|
||||
t.Errorf("Expected fresh.")
|
||||
}
|
||||
r, err := c.ServerResourcesForGroupVersion("astronomy/v8beta1")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if e, a := fake.resourceMap["astronomy/v8beta1"], r; !reflect.DeepEqual(e, a) {
|
||||
if e, a := fake.resourceMap["astronomy/v8beta1"].list, r; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %#v, got %#v", e, a)
|
||||
}
|
||||
|
||||
fake.lock.Lock()
|
||||
fake.resourceMap = map[string]*metav1.APIResourceList{
|
||||
fake.resourceMap = map[string]*resourceMapEntry{
|
||||
"astronomy/v8beta1": {
|
||||
GroupVersion: "astronomy/v8beta1",
|
||||
APIResources: []metav1.APIResource{{
|
||||
Name: "stars",
|
||||
SingularName: "star",
|
||||
Namespaced: true,
|
||||
Kind: "Star",
|
||||
ShortNames: []string{"s"},
|
||||
}},
|
||||
list: &metav1.APIResourceList{
|
||||
GroupVersion: "astronomy/v8beta1",
|
||||
APIResources: []metav1.APIResource{{
|
||||
Name: "stars",
|
||||
SingularName: "star",
|
||||
Namespaced: true,
|
||||
Kind: "Star",
|
||||
ShortNames: []string{"s"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
fake.lock.Unlock()
|
||||
@ -126,7 +143,235 @@ func TestClient(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if e, a := fake.resourceMap["astronomy/v8beta1"], r; !reflect.DeepEqual(e, a) {
|
||||
if e, a := fake.resourceMap["astronomy/v8beta1"].list, r; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %#v, got %#v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerGroupsFails(t *testing.T) {
|
||||
fake := &fakeDiscovery{
|
||||
groupList: &metav1.APIGroupList{
|
||||
Groups: []metav1.APIGroup{{
|
||||
Name: "astronomy",
|
||||
Versions: []metav1.GroupVersionForDiscovery{{
|
||||
GroupVersion: "astronomy/v8beta1",
|
||||
Version: "v8beta1",
|
||||
}},
|
||||
}},
|
||||
},
|
||||
groupListErr: errors.New("some error"),
|
||||
resourceMap: map[string]*resourceMapEntry{
|
||||
"astronomy/v8beta1": {
|
||||
list: &metav1.APIResourceList{
|
||||
GroupVersion: "astronomy/v8beta1",
|
||||
APIResources: []metav1.APIResource{{
|
||||
Name: "dwarfplanets",
|
||||
SingularName: "dwarfplanet",
|
||||
Namespaced: true,
|
||||
Kind: "DwarfPlanet",
|
||||
ShortNames: []string{"dp"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
c := NewMemCacheClient(fake)
|
||||
if c.Fresh() {
|
||||
t.Errorf("Expected not fresh.")
|
||||
}
|
||||
_, err := c.ServerGroups()
|
||||
if err == nil {
|
||||
t.Errorf("Expected error")
|
||||
}
|
||||
if c.Fresh() {
|
||||
t.Errorf("Expected not fresh.")
|
||||
}
|
||||
fake.lock.Lock()
|
||||
fake.groupListErr = nil
|
||||
fake.lock.Unlock()
|
||||
r, err := c.ServerResourcesForGroupVersion("astronomy/v8beta1")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if e, a := fake.resourceMap["astronomy/v8beta1"].list, r; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %#v, got %#v", e, a)
|
||||
}
|
||||
if !c.Fresh() {
|
||||
t.Errorf("Expected not fresh.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPartialPermanentFailure(t *testing.T) {
|
||||
fake := &fakeDiscovery{
|
||||
groupList: &metav1.APIGroupList{
|
||||
Groups: []metav1.APIGroup{{
|
||||
Name: "astronomy",
|
||||
Versions: []metav1.GroupVersionForDiscovery{{
|
||||
GroupVersion: "astronomy/v8beta1",
|
||||
Version: "v8beta1",
|
||||
}, {
|
||||
GroupVersion: "astronomy2/v8beta1",
|
||||
Version: "v8beta1",
|
||||
}},
|
||||
}},
|
||||
},
|
||||
resourceMap: map[string]*resourceMapEntry{
|
||||
"astronomy/v8beta1": {
|
||||
err: errors.New("some permanent error"),
|
||||
},
|
||||
"astronomy2/v8beta1": {
|
||||
list: &metav1.APIResourceList{
|
||||
GroupVersion: "astronomy2/v8beta1",
|
||||
APIResources: []metav1.APIResource{{
|
||||
Name: "dwarfplanets",
|
||||
SingularName: "dwarfplanet",
|
||||
Namespaced: true,
|
||||
Kind: "DwarfPlanet",
|
||||
ShortNames: []string{"dp"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
c := NewMemCacheClient(fake)
|
||||
if c.Fresh() {
|
||||
t.Errorf("Expected not fresh.")
|
||||
}
|
||||
r, err := c.ServerResourcesForGroupVersion("astronomy2/v8beta1")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if e, a := fake.resourceMap["astronomy2/v8beta1"].list, r; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %#v, got %#v", e, a)
|
||||
}
|
||||
_, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1")
|
||||
if err == nil {
|
||||
t.Errorf("Expected error, got nil")
|
||||
}
|
||||
|
||||
fake.lock.Lock()
|
||||
fake.resourceMap["astronomy/v8beta1"] = &resourceMapEntry{
|
||||
list: &metav1.APIResourceList{
|
||||
GroupVersion: "astronomy/v8beta1",
|
||||
APIResources: []metav1.APIResource{{
|
||||
Name: "dwarfplanets",
|
||||
SingularName: "dwarfplanet",
|
||||
Namespaced: true,
|
||||
Kind: "DwarfPlanet",
|
||||
ShortNames: []string{"dp"},
|
||||
}},
|
||||
},
|
||||
err: nil,
|
||||
}
|
||||
fake.lock.Unlock()
|
||||
// We don't retry permanent errors, so it should fail.
|
||||
_, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1")
|
||||
if err == nil {
|
||||
t.Errorf("Expected error, got nil")
|
||||
}
|
||||
c.Invalidate()
|
||||
|
||||
// After Invalidate, we should retry.
|
||||
r, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if e, a := fake.resourceMap["astronomy/v8beta1"].list, r; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %#v, got %#v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPartialRetryableFailure(t *testing.T) {
|
||||
fake := &fakeDiscovery{
|
||||
groupList: &metav1.APIGroupList{
|
||||
Groups: []metav1.APIGroup{{
|
||||
Name: "astronomy",
|
||||
Versions: []metav1.GroupVersionForDiscovery{{
|
||||
GroupVersion: "astronomy/v8beta1",
|
||||
Version: "v8beta1",
|
||||
}, {
|
||||
GroupVersion: "astronomy2/v8beta1",
|
||||
Version: "v8beta1",
|
||||
}},
|
||||
}},
|
||||
},
|
||||
resourceMap: map[string]*resourceMapEntry{
|
||||
"astronomy/v8beta1": {
|
||||
err: &errorsutil.StatusError{
|
||||
ErrStatus: metav1.Status{
|
||||
Message: "Some retryable error",
|
||||
Code: int32(http.StatusServiceUnavailable),
|
||||
Reason: metav1.StatusReasonServiceUnavailable,
|
||||
},
|
||||
},
|
||||
},
|
||||
"astronomy2/v8beta1": {
|
||||
list: &metav1.APIResourceList{
|
||||
GroupVersion: "astronomy2/v8beta1",
|
||||
APIResources: []metav1.APIResource{{
|
||||
Name: "dwarfplanets",
|
||||
SingularName: "dwarfplanet",
|
||||
Namespaced: true,
|
||||
Kind: "DwarfPlanet",
|
||||
ShortNames: []string{"dp"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
c := NewMemCacheClient(fake)
|
||||
if c.Fresh() {
|
||||
t.Errorf("Expected not fresh.")
|
||||
}
|
||||
r, err := c.ServerResourcesForGroupVersion("astronomy2/v8beta1")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if e, a := fake.resourceMap["astronomy2/v8beta1"].list, r; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %#v, got %#v", e, a)
|
||||
}
|
||||
_, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1")
|
||||
if err == nil {
|
||||
t.Errorf("Expected error, got nil")
|
||||
}
|
||||
|
||||
fake.lock.Lock()
|
||||
fake.resourceMap["astronomy/v8beta1"] = &resourceMapEntry{
|
||||
list: &metav1.APIResourceList{
|
||||
GroupVersion: "astronomy/v8beta1",
|
||||
APIResources: []metav1.APIResource{{
|
||||
Name: "dwarfplanets",
|
||||
SingularName: "dwarfplanet",
|
||||
Namespaced: true,
|
||||
Kind: "DwarfPlanet",
|
||||
ShortNames: []string{"dp"},
|
||||
}},
|
||||
},
|
||||
err: nil,
|
||||
}
|
||||
fake.lock.Unlock()
|
||||
// We should retry retryable error even without Invalidate() being called,
|
||||
// so no error is expected.
|
||||
r, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1")
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
if e, a := fake.resourceMap["astronomy/v8beta1"].list, r; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %#v, got %#v", e, a)
|
||||
}
|
||||
|
||||
// Check that the last result was cached and we don't retry further.
|
||||
fake.lock.Lock()
|
||||
fake.resourceMap["astronomy/v8beta1"].err = errors.New("some permanent error")
|
||||
fake.lock.Unlock()
|
||||
r, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1")
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
if e, a := fake.resourceMap["astronomy/v8beta1"].list, r; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %#v, got %#v", e, a)
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/googleapis/gnostic/OpenAPIv2"
|
||||
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@ -60,6 +60,9 @@ type DiscoveryInterface interface {
|
||||
}
|
||||
|
||||
// CachedDiscoveryInterface is a DiscoveryInterface with cache invalidation and freshness.
|
||||
// Note that If the ServerResourcesForGroupVersion method returns a cache miss
|
||||
// error, the user needs to explicitly call Invalidate to clear the cache,
|
||||
// otherwise the same cache miss error will be returned next time.
|
||||
type CachedDiscoveryInterface interface {
|
||||
DiscoveryInterface
|
||||
// Fresh is supposed to tell the caller whether or not to retry if the cache
|
||||
@ -68,7 +71,8 @@ type CachedDiscoveryInterface interface {
|
||||
// TODO: this needs to be revisited, this interface can't be locked properly
|
||||
// and doesn't make a lot of sense.
|
||||
Fresh() bool
|
||||
// Invalidate enforces that no cached data is used in the future that is older than the current time.
|
||||
// Invalidate enforces that no cached data that is older than the current time
|
||||
// is used.
|
||||
Invalidate()
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user