2016-10-24 10:58:47 +00:00
/ *
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"
2016-12-03 18:57:26 +00:00
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
2016-10-24 10:58:47 +00:00
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/typed/discovery"
"k8s.io/kubernetes/pkg/runtime"
2016-11-21 02:55:31 +00:00
"k8s.io/kubernetes/pkg/runtime/schema"
2016-10-24 10:58:47 +00:00
"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.
2016-12-03 18:57:26 +00:00
func ( d * CachedDiscoveryClient ) ServerResourcesForGroupVersion ( groupVersion string ) ( * metav1 . APIResourceList , error ) {
2016-10-24 10:58:47 +00:00
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 {
2016-12-03 18:57:26 +00:00
cachedResources := & metav1 . APIResourceList { }
2016-10-24 10:58:47 +00:00
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.
2016-12-03 18:57:26 +00:00
func ( d * CachedDiscoveryClient ) ServerResources ( ) ( map [ string ] * metav1 . APIResourceList , error ) {
2016-10-24 10:58:47 +00:00
apiGroups , err := d . ServerGroups ( )
if err != nil {
return nil , err
}
2016-12-03 18:57:26 +00:00
groupVersions := metav1 . ExtractGroupVersions ( apiGroups )
result := map [ string ] * metav1 . APIResourceList { }
2016-10-24 10:58:47 +00:00
for _ , groupVersion := range groupVersions {
resources , err := d . ServerResourcesForGroupVersion ( groupVersion )
if err != nil {
return nil , err
}
result [ groupVersion ] = resources
}
return result , nil
}
2016-12-03 18:57:26 +00:00
func ( d * CachedDiscoveryClient ) ServerGroups ( ) ( * metav1 . APIGroupList , error ) {
2016-10-24 10:58:47 +00:00
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 {
2016-12-03 18:57:26 +00:00
cachedGroups := & metav1 . APIGroupList { }
2016-10-24 10:58:47 +00:00
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 ( )
}
2016-11-21 02:55:31 +00:00
func ( d * CachedDiscoveryClient ) ServerPreferredResources ( ) ( [ ] schema . GroupVersionResource , error ) {
2016-10-24 10:58:47 +00:00
return d . delegate . ServerPreferredResources ( )
}
2016-11-21 02:55:31 +00:00
func ( d * CachedDiscoveryClient ) ServerPreferredNamespacedResources ( ) ( [ ] schema . GroupVersionResource , error ) {
2016-10-24 10:58:47 +00:00
return d . delegate . ServerPreferredNamespacedResources ( )
}
func ( d * CachedDiscoveryClient ) ServerVersion ( ) ( * version . Info , error ) {
return d . delegate . ServerVersion ( )
}
2016-11-21 02:55:31 +00:00
func ( d * CachedDiscoveryClient ) SwaggerSchema ( version schema . GroupVersion ) ( * swagger . ApiDeclaration , error ) {
2016-10-24 10:58:47 +00:00
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 ,
}
}