mirror of https://github.com/k3s-io/k3s
253 lines
7.2 KiB
Go
253 lines
7.2 KiB
Go
![]() |
/*
|
||
|
Copyright 2016 The Kubernetes Authors.
|
||
|
|
||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
you may not use this file except in compliance with the License.
|
||
|
You may obtain a copy of the License at
|
||
|
|
||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
||
|
Unless required by applicable law or agreed to in writing, software
|
||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
See the License for the specific language governing permissions and
|
||
|
limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package util
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"io/ioutil"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"sync"
|
||
|
"time"
|
||
|
|
||
|
"github.com/emicklei/go-restful/swagger"
|
||
|
"github.com/golang/glog"
|
||
|
|
||
|
"k8s.io/kubernetes/pkg/api"
|
||
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||
|
"k8s.io/kubernetes/pkg/client/restclient"
|
||
|
"k8s.io/kubernetes/pkg/client/typed/discovery"
|
||
|
"k8s.io/kubernetes/pkg/runtime"
|
||
|
"k8s.io/kubernetes/pkg/version"
|
||
|
)
|
||
|
|
||
|
// CachedDiscoveryClient implements the functions that discovery server-supported API groups,
|
||
|
// versions and resources.
|
||
|
type CachedDiscoveryClient struct {
|
||
|
delegate discovery.DiscoveryInterface
|
||
|
|
||
|
// cacheDirectory is the directory where discovery docs are held. It must be unique per host:port combination to work well.
|
||
|
cacheDirectory string
|
||
|
|
||
|
// ttl is how long the cache should be considered valid
|
||
|
ttl time.Duration
|
||
|
|
||
|
// mutex protects the variables below
|
||
|
mutex sync.Mutex
|
||
|
|
||
|
// ourFiles are all filenames of cache files created by this process
|
||
|
ourFiles map[string]struct{}
|
||
|
// invalidated is true if all cache files should be ignored that are not ours (e.g. after Invalidate() was called)
|
||
|
invalidated bool
|
||
|
// fresh is true if all used cache files were ours
|
||
|
fresh bool
|
||
|
}
|
||
|
|
||
|
var _ discovery.CachedDiscoveryInterface = &CachedDiscoveryClient{}
|
||
|
|
||
|
// ServerResourcesForGroupVersion returns the supported resources for a group and version.
|
||
|
func (d *CachedDiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (*unversioned.APIResourceList, error) {
|
||
|
filename := filepath.Join(d.cacheDirectory, groupVersion, "serverresources.json")
|
||
|
cachedBytes, err := d.getCachedFile(filename)
|
||
|
// don't fail on errors, we either don't have a file or won't be able to run the cached check. Either way we can fallback.
|
||
|
if err == nil {
|
||
|
cachedResources := &unversioned.APIResourceList{}
|
||
|
if err := runtime.DecodeInto(api.Codecs.UniversalDecoder(), cachedBytes, cachedResources); err == nil {
|
||
|
glog.V(6).Infof("returning cached discovery info from %v", filename)
|
||
|
return cachedResources, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
liveResources, err := d.delegate.ServerResourcesForGroupVersion(groupVersion)
|
||
|
if err != nil {
|
||
|
return liveResources, err
|
||
|
}
|
||
|
|
||
|
if err := d.writeCachedFile(filename, liveResources); err != nil {
|
||
|
glog.V(3).Infof("failed to write cache to %v due to %v", filename, err)
|
||
|
}
|
||
|
|
||
|
return liveResources, nil
|
||
|
}
|
||
|
|
||
|
// ServerResources returns the supported resources for all groups and versions.
|
||
|
func (d *CachedDiscoveryClient) ServerResources() (map[string]*unversioned.APIResourceList, error) {
|
||
|
apiGroups, err := d.ServerGroups()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
groupVersions := unversioned.ExtractGroupVersions(apiGroups)
|
||
|
result := map[string]*unversioned.APIResourceList{}
|
||
|
for _, groupVersion := range groupVersions {
|
||
|
resources, err := d.ServerResourcesForGroupVersion(groupVersion)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
result[groupVersion] = resources
|
||
|
}
|
||
|
return result, nil
|
||
|
}
|
||
|
|
||
|
func (d *CachedDiscoveryClient) ServerGroups() (*unversioned.APIGroupList, error) {
|
||
|
filename := filepath.Join(d.cacheDirectory, "servergroups.json")
|
||
|
cachedBytes, err := d.getCachedFile(filename)
|
||
|
// don't fail on errors, we either don't have a file or won't be able to run the cached check. Either way we can fallback.
|
||
|
if err == nil {
|
||
|
cachedGroups := &unversioned.APIGroupList{}
|
||
|
if err := runtime.DecodeInto(api.Codecs.UniversalDecoder(), cachedBytes, cachedGroups); err == nil {
|
||
|
glog.V(6).Infof("returning cached discovery info from %v", filename)
|
||
|
return cachedGroups, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
liveGroups, err := d.delegate.ServerGroups()
|
||
|
if err != nil {
|
||
|
return liveGroups, err
|
||
|
}
|
||
|
|
||
|
if err := d.writeCachedFile(filename, liveGroups); err != nil {
|
||
|
glog.V(3).Infof("failed to write cache to %v due to %v", filename, err)
|
||
|
}
|
||
|
|
||
|
return liveGroups, nil
|
||
|
}
|
||
|
|
||
|
func (d *CachedDiscoveryClient) getCachedFile(filename string) ([]byte, error) {
|
||
|
// after invalidation ignore cache files not created by this process
|
||
|
d.mutex.Lock()
|
||
|
_, ourFile := d.ourFiles[filename]
|
||
|
if d.invalidated && !ourFile {
|
||
|
d.mutex.Unlock()
|
||
|
return nil, errors.New("cache invalidated")
|
||
|
}
|
||
|
d.mutex.Unlock()
|
||
|
|
||
|
file, err := os.Open(filename)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
fileInfo, err := file.Stat()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
if time.Now().After(fileInfo.ModTime().Add(d.ttl)) {
|
||
|
return nil, errors.New("cache expired")
|
||
|
}
|
||
|
|
||
|
// the cache is present and its valid. Try to read and use it.
|
||
|
cachedBytes, err := ioutil.ReadAll(file)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
d.mutex.Lock()
|
||
|
defer d.mutex.Unlock()
|
||
|
d.fresh = d.fresh && ourFile
|
||
|
|
||
|
return cachedBytes, nil
|
||
|
}
|
||
|
|
||
|
func (d *CachedDiscoveryClient) writeCachedFile(filename string, obj runtime.Object) error {
|
||
|
if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
bytes, err := runtime.Encode(api.Codecs.LegacyCodec(), obj)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
f, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename)+".")
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer os.Remove(f.Name())
|
||
|
_, err = f.Write(bytes)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
err = f.Chmod(0755)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
name := f.Name()
|
||
|
err = f.Close()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// atomic rename
|
||
|
d.mutex.Lock()
|
||
|
defer d.mutex.Unlock()
|
||
|
err = os.Rename(name, filename)
|
||
|
if err == nil {
|
||
|
d.ourFiles[filename] = struct{}{}
|
||
|
}
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
func (d *CachedDiscoveryClient) RESTClient() restclient.Interface {
|
||
|
return d.delegate.RESTClient()
|
||
|
}
|
||
|
|
||
|
func (d *CachedDiscoveryClient) ServerPreferredResources() ([]unversioned.GroupVersionResource, error) {
|
||
|
return d.delegate.ServerPreferredResources()
|
||
|
}
|
||
|
|
||
|
func (d *CachedDiscoveryClient) ServerPreferredNamespacedResources() ([]unversioned.GroupVersionResource, error) {
|
||
|
return d.delegate.ServerPreferredNamespacedResources()
|
||
|
}
|
||
|
|
||
|
func (d *CachedDiscoveryClient) ServerVersion() (*version.Info, error) {
|
||
|
return d.delegate.ServerVersion()
|
||
|
}
|
||
|
|
||
|
func (d *CachedDiscoveryClient) SwaggerSchema(version unversioned.GroupVersion) (*swagger.ApiDeclaration, error) {
|
||
|
return d.delegate.SwaggerSchema(version)
|
||
|
}
|
||
|
|
||
|
func (d *CachedDiscoveryClient) Fresh() bool {
|
||
|
d.mutex.Lock()
|
||
|
defer d.mutex.Unlock()
|
||
|
|
||
|
return d.fresh
|
||
|
}
|
||
|
|
||
|
func (d *CachedDiscoveryClient) Invalidate() {
|
||
|
d.mutex.Lock()
|
||
|
defer d.mutex.Unlock()
|
||
|
|
||
|
d.ourFiles = map[string]struct{}{}
|
||
|
d.fresh = true
|
||
|
d.invalidated = true
|
||
|
}
|
||
|
|
||
|
// NewCachedDiscoveryClient creates a new DiscoveryClient. cacheDirectory is the directory where discovery docs are held. It must be unique per host:port combination to work well.
|
||
|
func NewCachedDiscoveryClient(delegate discovery.DiscoveryInterface, cacheDirectory string, ttl time.Duration) *CachedDiscoveryClient {
|
||
|
return &CachedDiscoveryClient{
|
||
|
delegate: delegate,
|
||
|
cacheDirectory: cacheDirectory,
|
||
|
ttl: ttl,
|
||
|
ourFiles: map[string]struct{}{},
|
||
|
fresh: true,
|
||
|
}
|
||
|
}
|