package cache
import (
"fmt"
"time"
kapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/cache"
kcoreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/watch"
"github.com/golang/glog"
projectapi "github.com/openshift/origin/pkg/project/api"
"github.com/openshift/origin/pkg/util/labelselector"
)
// NewProjectCache returns a non-initialized ProjectCache. The cache needs to be run to begin functioning
func NewProjectCache(client kcoreclient.NamespaceInterface, defaultNodeSelector string) *ProjectCache {
return &ProjectCache{
Client: client,
DefaultNodeSelector: defaultNodeSelector,
}
}
type ProjectCache struct {
Client kcoreclient.NamespaceInterface
Store cache.Indexer
DefaultNodeSelector string
}
func (p *ProjectCache) GetNamespace(name string) (*kapi.Namespace, error) {
key := &kapi.Namespace{ObjectMeta: kapi.ObjectMeta{Name: name}}
// check for namespace in the cache
namespaceObj, exists, err := p.Store.Get(key)
if err != nil {
return nil, err
}
if !exists {
// give the cache time to observe a recent namespace creation
time.Sleep(50 * time.Millisecond)
namespaceObj, exists, err = p.Store.Get(key)
if err != nil {
return nil, err
}
if exists {
glog.V(4).Infof("found %s in cache after waiting", name)
}
}
var namespace *kapi.Namespace
if exists {
namespace = namespaceObj.(*kapi.Namespace)
} else {
// Our watch maybe latent, so we make a best effort to get the object, and only fail if not found
namespace, err = p.Client.Get(name)
// the namespace does not exist, so prevent create and update in that namespace
if err != nil {
return nil, fmt.Errorf("namespace %s does not exist", name)
}
glog.V(4).Infof("found %s via storage lookup", name)
}
return namespace, nil
}
func (p *ProjectCache) GetNodeSelector(namespace *kapi.Namespace) string {
selector := ""
found := false
if len(namespace.ObjectMeta.Annotations) > 0 {
if ns, ok := namespace.ObjectMeta.Annotations[projectapi.ProjectNodeSelector]; ok {
selector = ns
found = true
}
}
if !found {
selector = p.DefaultNodeSelector
}
return selector
}
func (p *ProjectCache) GetNodeSelectorMap(namespace *kapi.Namespace) (map[string]string, error) {
selector := p.GetNodeSelector(namespace)
labelsMap, err := labelselector.Parse(selector)
if err != nil {
return map[string]string{}, err
}
return labelsMap, nil
}
// Run builds the store that backs this cache and runs the backing reflector
func (c *ProjectCache) Run() {
store := NewCacheStore(cache.MetaNamespaceKeyFunc)
reflector := cache.NewReflector(
&cache.ListWatch{
ListFunc: func(options kapi.ListOptions) (runtime.Object, error) {
return c.Client.List(options)
},
WatchFunc: func(options kapi.ListOptions) (watch.Interface, error) {
return c.Client.Watch(options)
},
},
&kapi.Namespace{},
store,
0,
)
reflector.Run()
c.Store = store
}
// Running determines if the cache is initialized and running
func (c *ProjectCache) Running() bool {
return c.Store != nil
}
// NewFake is used for testing purpose only
func NewFake(c kcoreclient.NamespaceInterface, store cache.Indexer, defaultNodeSelector string) *ProjectCache {
return &ProjectCache{
Client: c,
Store: store,
DefaultNodeSelector: defaultNodeSelector,
}
}
// NewCacheStore creates an Indexer store with the given key function
func NewCacheStore(keyFn cache.KeyFunc) cache.Indexer {
return cache.NewIndexer(keyFn, cache.Indexers{
"requester": indexNamespaceByRequester,
})
}
// indexNamespaceByRequester returns the requester for a given namespace object as an index value
func indexNamespaceByRequester(obj interface{}) ([]string, error) {
requester := obj.(*kapi.Namespace).Annotations[projectapi.ProjectRequester]
return []string{requester}, nil
}