Commit 3c0b35d6 authored by derekwaynecarr's avatar derekwaynecarr

Add LimitRange informer

parent 19e9f7e4
...@@ -203,3 +203,42 @@ func (s *StoreToReplicationControllerLister) GetPodControllers(pod *api.Pod) (co ...@@ -203,3 +203,42 @@ func (s *StoreToReplicationControllerLister) GetPodControllers(pod *api.Pod) (co
} }
return return
} }
// StoreToLimitRangeLister helps list limit ranges
type StoreToLimitRangeLister struct {
Indexer Indexer
}
func (s *StoreToLimitRangeLister) List(selector labels.Selector) (ret []*api.LimitRange, err error) {
err = ListAll(s.Indexer, selector, func(m interface{}) {
ret = append(ret, m.(*api.LimitRange))
})
return ret, err
}
func (s *StoreToLimitRangeLister) LimitRanges(namespace string) storeLimitRangesNamespacer {
return storeLimitRangesNamespacer{s.Indexer, namespace}
}
type storeLimitRangesNamespacer struct {
indexer Indexer
namespace string
}
func (s storeLimitRangesNamespacer) List(selector labels.Selector) (ret []*api.LimitRange, err error) {
err = ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*api.LimitRange))
})
return ret, err
}
func (s storeLimitRangesNamespacer) Get(name string) (*api.LimitRange, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(api.Resource("limitrange"), name)
}
return obj.(*api.LimitRange), nil
}
...@@ -205,6 +205,42 @@ func (f *pvInformer) Lister() *cache.StoreToPVFetcher { ...@@ -205,6 +205,42 @@ func (f *pvInformer) Lister() *cache.StoreToPVFetcher {
return &cache.StoreToPVFetcher{Store: informer.GetStore()} return &cache.StoreToPVFetcher{Store: informer.GetStore()}
} }
//*****************************************************************************
// LimitRangeInformer is type of SharedIndexInformer which watches and lists all limit ranges.
// Interface provides constructor for informer and lister for limit ranges.
type LimitRangeInformer interface {
Informer() cache.SharedIndexInformer
Lister() *cache.StoreToLimitRangeLister
}
type limitRangeInformer struct {
*sharedInformerFactory
}
// Informer checks whether pvcInformer exists in sharedInformerFactory and if not, it creates new informer of type
// limitRangeInformer and connects it to sharedInformerFactory
func (f *limitRangeInformer) Informer() cache.SharedIndexInformer {
f.lock.Lock()
defer f.lock.Unlock()
informerType := reflect.TypeOf(&api.LimitRange{})
informer, exists := f.informers[informerType]
if exists {
return informer
}
informer = NewLimitRangeInformer(f.client, f.defaultResync)
f.informers[informerType] = informer
return informer
}
// Lister returns lister for limitRangeInformer
func (f *limitRangeInformer) Lister() *cache.StoreToLimitRangeLister {
informer := f.Informer()
return &cache.StoreToLimitRangeLister{Indexer: informer.GetIndexer()}
}
// NewPodInformer returns a SharedIndexInformer that lists and watches all pods // NewPodInformer returns a SharedIndexInformer that lists and watches all pods
func NewPodInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { func NewPodInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
sharedIndexInformer := cache.NewSharedIndexInformer( sharedIndexInformer := cache.NewSharedIndexInformer(
...@@ -295,3 +331,21 @@ func NewNamespaceInformer(client clientset.Interface, resyncPeriod time.Duration ...@@ -295,3 +331,21 @@ func NewNamespaceInformer(client clientset.Interface, resyncPeriod time.Duration
return sharedIndexInformer return sharedIndexInformer
} }
// NewLimitRangeInformer returns a SharedIndexInformer that lists and watches all LimitRanges
func NewLimitRangeInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
sharedIndexInformer := cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options api.ListOptions) (runtime.Object, error) {
return client.Core().LimitRanges(api.NamespaceAll).List(options)
},
WatchFunc: func(options api.ListOptions) (watch.Interface, error) {
return client.Core().LimitRanges(api.NamespaceAll).Watch(options)
},
},
&api.LimitRange{},
resyncPeriod,
cache.Indexers{})
return sharedIndexInformer
}
...@@ -45,6 +45,8 @@ type SharedInformerFactory interface { ...@@ -45,6 +45,8 @@ type SharedInformerFactory interface {
ClusterRoleBindings() ClusterRoleBindingInformer ClusterRoleBindings() ClusterRoleBindingInformer
Roles() RoleInformer Roles() RoleInformer
RoleBindings() RoleBindingInformer RoleBindings() RoleBindingInformer
LimitRanges() LimitRangeInformer
} }
type sharedInformerFactory struct { type sharedInformerFactory struct {
...@@ -106,6 +108,7 @@ func (f *sharedInformerFactory) PersistentVolumes() PVInformer { ...@@ -106,6 +108,7 @@ func (f *sharedInformerFactory) PersistentVolumes() PVInformer {
return &pvInformer{sharedInformerFactory: f} return &pvInformer{sharedInformerFactory: f}
} }
// DaemonSets returns a SharedIndexInformer that lists and watches all daemon sets.
func (f *sharedInformerFactory) DaemonSets() DaemonSetInformer { func (f *sharedInformerFactory) DaemonSets() DaemonSetInformer {
return &daemonSetInformer{sharedInformerFactory: f} return &daemonSetInformer{sharedInformerFactory: f}
} }
...@@ -133,3 +136,8 @@ func (f *sharedInformerFactory) Roles() RoleInformer { ...@@ -133,3 +136,8 @@ func (f *sharedInformerFactory) Roles() RoleInformer {
func (f *sharedInformerFactory) RoleBindings() RoleBindingInformer { func (f *sharedInformerFactory) RoleBindings() RoleBindingInformer {
return &roleBindingInformer{sharedInformerFactory: f} return &roleBindingInformer{sharedInformerFactory: f}
} }
// LimitRanges returns a SharedIndexInformer that lists and watches all limit ranges.
func (f *sharedInformerFactory) LimitRanges() LimitRangeInformer {
return &limitRangeInformer{sharedInformerFactory: f}
}
...@@ -26,15 +26,16 @@ import ( ...@@ -26,15 +26,16 @@ import (
lru "github.com/hashicorp/golang-lru" lru "github.com/hashicorp/golang-lru"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/controller/informers"
"k8s.io/kubernetes/pkg/admission" "k8s.io/kubernetes/pkg/admission"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/client/cache" "k8s.io/kubernetes/pkg/client/cache"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
utilerrors "k8s.io/kubernetes/pkg/util/errors" utilerrors "k8s.io/kubernetes/pkg/util/errors"
"k8s.io/kubernetes/pkg/watch"
) )
const ( const (
...@@ -52,7 +53,7 @@ type limitRanger struct { ...@@ -52,7 +53,7 @@ type limitRanger struct {
*admission.Handler *admission.Handler
client clientset.Interface client clientset.Interface
actions LimitRangerActions actions LimitRangerActions
indexer cache.Indexer lister *cache.StoreToLimitRangeLister
// liveLookups holds the last few live lookups we've done to help ammortize cost on repeated lookup failures. // liveLookups holds the last few live lookups we've done to help ammortize cost on repeated lookup failures.
// This let's us handle the case of latent caches, by looking up actual results for a namespace on cache miss/no results. // This let's us handle the case of latent caches, by looking up actual results for a namespace on cache miss/no results.
...@@ -66,6 +67,19 @@ type liveLookupEntry struct { ...@@ -66,6 +67,19 @@ type liveLookupEntry struct {
items []*api.LimitRange items []*api.LimitRange
} }
func (l *limitRanger) SetInformerFactory(f informers.SharedInformerFactory) {
limitRangeInformer := f.LimitRanges().Informer()
l.SetReadyFunc(limitRangeInformer.HasSynced)
l.lister = f.LimitRanges().Lister()
}
func (l *limitRanger) Validate() error {
if l.lister == nil {
return fmt.Errorf("missing limitRange lister")
}
return nil
}
// Admit admits resources into cluster that do not violate any defined LimitRange in the namespace // Admit admits resources into cluster that do not violate any defined LimitRange in the namespace
func (l *limitRanger) Admit(a admission.Attributes) (err error) { func (l *limitRanger) Admit(a admission.Attributes) (err error) {
if !l.actions.SupportsAttributes(a) { if !l.actions.SupportsAttributes(a) {
...@@ -81,13 +95,7 @@ func (l *limitRanger) Admit(a admission.Attributes) (err error) { ...@@ -81,13 +95,7 @@ func (l *limitRanger) Admit(a admission.Attributes) (err error) {
} }
} }
key := &api.LimitRange{ items, err := l.lister.LimitRanges(a.GetNamespace()).List(labels.Everything())
ObjectMeta: api.ObjectMeta{
Namespace: a.GetNamespace(),
Name: "",
},
}
items, err := l.indexer.Index("namespace", key)
if err != nil { if err != nil {
return admission.NewForbidden(a, fmt.Errorf("unable to %s %v at this time because there was an error enforcing limit ranges", a.GetOperation(), a.GetResource())) return admission.NewForbidden(a, fmt.Errorf("unable to %s %v at this time because there was an error enforcing limit ranges", a.GetOperation(), a.GetResource()))
} }
...@@ -122,7 +130,7 @@ func (l *limitRanger) Admit(a admission.Attributes) (err error) { ...@@ -122,7 +130,7 @@ func (l *limitRanger) Admit(a admission.Attributes) (err error) {
// ensure it meets each prescribed min/max // ensure it meets each prescribed min/max
for i := range items { for i := range items {
limitRange := items[i].(*api.LimitRange) limitRange := items[i]
if !l.actions.SupportsLimit(limitRange) { if !l.actions.SupportsLimit(limitRange) {
continue continue
...@@ -143,17 +151,6 @@ func NewLimitRanger(client clientset.Interface, actions LimitRangerActions) (adm ...@@ -143,17 +151,6 @@ func NewLimitRanger(client clientset.Interface, actions LimitRangerActions) (adm
return nil, err return nil, err
} }
lw := &cache.ListWatch{
ListFunc: func(options api.ListOptions) (runtime.Object, error) {
return client.Core().LimitRanges(api.NamespaceAll).List(options)
},
WatchFunc: func(options api.ListOptions) (watch.Interface, error) {
return client.Core().LimitRanges(api.NamespaceAll).Watch(options)
},
}
indexer, reflector := cache.NewNamespaceKeyedIndexerAndReflector(lw, &api.LimitRange{}, 0)
reflector.Run()
if actions == nil { if actions == nil {
actions = &DefaultLimitRangerActions{} actions = &DefaultLimitRangerActions{}
} }
...@@ -162,7 +159,6 @@ func NewLimitRanger(client clientset.Interface, actions LimitRangerActions) (adm ...@@ -162,7 +159,6 @@ func NewLimitRanger(client clientset.Interface, actions LimitRangerActions) (adm
Handler: admission.NewHandler(admission.Create, admission.Update), Handler: admission.NewHandler(admission.Create, admission.Update),
client: client, client: client,
actions: actions, actions: actions,
indexer: indexer,
liveLookupCache: liveLookupCache, liveLookupCache: liveLookupCache,
liveTTL: time.Duration(30 * time.Second), liveTTL: time.Duration(30 * time.Second),
}, nil }, nil
......
...@@ -17,6 +17,7 @@ limitations under the License. ...@@ -17,6 +17,7 @@ limitations under the License.
package limitranger package limitranger
import ( import (
"fmt"
"strconv" "strconv"
"testing" "testing"
"time" "time"
...@@ -24,10 +25,13 @@ import ( ...@@ -24,10 +25,13 @@ import (
"k8s.io/kubernetes/pkg/admission" "k8s.io/kubernetes/pkg/admission"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/client/cache" "k8s.io/kubernetes/pkg/api/unversioned"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
"k8s.io/kubernetes/pkg/client/testing/core"
"github.com/hashicorp/golang-lru" "k8s.io/kubernetes/pkg/controller/informers"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/wait"
) )
func getComputeResourceList(cpu, memory string) api.ResourceList { func getComputeResourceList(cpu, memory string) api.ResourceList {
...@@ -522,53 +526,15 @@ func TestPodLimitFuncApplyDefault(t *testing.T) { ...@@ -522,53 +526,15 @@ func TestPodLimitFuncApplyDefault(t *testing.T) {
} }
func TestLimitRangerIgnoresSubresource(t *testing.T) { func TestLimitRangerIgnoresSubresource(t *testing.T) {
client := fake.NewSimpleClientset()
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc})
handler := &limitRanger{
Handler: admission.NewHandler(admission.Create, admission.Update),
client: client,
actions: &DefaultLimitRangerActions{},
indexer: indexer,
}
limitRange := validLimitRangeNoDefaults() limitRange := validLimitRangeNoDefaults()
testPod := validPod("testPod", 1, api.ResourceRequirements{}) mockClient := newMockClientForTest([]api.LimitRange{limitRange})
handler, informerFactory, err := newHandlerForTest(mockClient)
indexer.Add(&limitRange)
err := handler.Admit(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
if err == nil {
t.Errorf("Expected an error since the pod did not specify resource limits in its update call")
}
err = handler.Admit(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "status", admission.Update, nil))
if err != nil { if err != nil {
t.Errorf("Should have ignored calls to any subresource of pod %v", err) t.Errorf("unexpected error initializing handler: %v", err)
} }
informerFactory.Start(wait.NeverStop)
}
func TestLimitRangerCacheMisses(t *testing.T) {
liveLookupCache, err := lru.New(10000)
if err != nil {
t.Fatal(err)
}
client := fake.NewSimpleClientset()
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc})
handler := &limitRanger{
Handler: admission.NewHandler(admission.Create, admission.Update),
client: client,
actions: &DefaultLimitRangerActions{},
indexer: indexer,
liveLookupCache: liveLookupCache,
}
limitRange := validLimitRangeNoDefaults()
testPod := validPod("testPod", 1, api.ResourceRequirements{}) testPod := validPod("testPod", 1, api.ResourceRequirements{})
// add to the lru cache
liveLookupCache.Add(limitRange.Namespace, liveLookupEntry{expiry: time.Now().Add(time.Duration(30 * time.Second)), items: []*api.LimitRange{&limitRange}})
err = handler.Admit(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Update, nil)) err = handler.Admit(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
if err == nil { if err == nil {
t.Errorf("Expected an error since the pod did not specify resource limits in its update call") t.Errorf("Expected an error since the pod did not specify resource limits in its update call")
...@@ -578,27 +544,19 @@ func TestLimitRangerCacheMisses(t *testing.T) { ...@@ -578,27 +544,19 @@ func TestLimitRangerCacheMisses(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("Should have ignored calls to any subresource of pod %v", err) t.Errorf("Should have ignored calls to any subresource of pod %v", err)
} }
}
func TestLimitRangerCacheAndLRUMisses(t *testing.T) { }
liveLookupCache, err := lru.New(10000)
if err != nil {
t.Fatal(err)
}
func TestLimitRangerAdmitPod(t *testing.T) {
limitRange := validLimitRangeNoDefaults() limitRange := validLimitRangeNoDefaults()
client := fake.NewSimpleClientset(&limitRange) mockClient := newMockClientForTest([]api.LimitRange{limitRange})
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc}) handler, informerFactory, err := newHandlerForTest(mockClient)
handler := &limitRanger{ if err != nil {
Handler: admission.NewHandler(admission.Create, admission.Update), t.Errorf("unexpected error initializing handler: %v", err)
client: client,
actions: &DefaultLimitRangerActions{},
indexer: indexer,
liveLookupCache: liveLookupCache,
} }
informerFactory.Start(wait.NeverStop)
testPod := validPod("testPod", 1, api.ResourceRequirements{}) testPod := validPod("testPod", 1, api.ResourceRequirements{})
err = handler.Admit(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Update, nil)) err = handler.Admit(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
if err == nil { if err == nil {
t.Errorf("Expected an error since the pod did not specify resource limits in its update call") t.Errorf("Expected an error since the pod did not specify resource limits in its update call")
...@@ -610,37 +568,36 @@ func TestLimitRangerCacheAndLRUMisses(t *testing.T) { ...@@ -610,37 +568,36 @@ func TestLimitRangerCacheAndLRUMisses(t *testing.T) {
} }
} }
func TestLimitRangerCacheAndLRUExpiredMisses(t *testing.T) { // newMockClientForTest creates a mock client that returns a client configured for the specified list of limit ranges
liveLookupCache, err := lru.New(10000) func newMockClientForTest(limitRanges []api.LimitRange) *fake.Clientset {
if err != nil { mockClient := &fake.Clientset{}
t.Fatal(err) mockClient.AddReactor("list", "limitranges", func(action core.Action) (bool, runtime.Object, error) {
} limitRangeList := &api.LimitRangeList{
ListMeta: unversioned.ListMeta{
limitRange := validLimitRangeNoDefaults() ResourceVersion: fmt.Sprintf("%d", len(limitRanges)),
client := fake.NewSimpleClientset(&limitRange) },
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc}) }
handler := &limitRanger{ for index, value := range limitRanges {
Handler: admission.NewHandler(admission.Create, admission.Update), value.ResourceVersion = fmt.Sprintf("%d", index)
client: client, limitRangeList.Items = append(limitRangeList.Items, value)
actions: &DefaultLimitRangerActions{}, }
indexer: indexer, return true, limitRangeList, nil
liveLookupCache: liveLookupCache, })
} return mockClient
}
testPod := validPod("testPod", 1, api.ResourceRequirements{})
// add to the lru cache
liveLookupCache.Add(limitRange.Namespace, liveLookupEntry{expiry: time.Now().Add(time.Duration(-30 * time.Second)), items: []*api.LimitRange{}})
err = handler.Admit(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
if err == nil {
t.Errorf("Expected an error since the pod did not specify resource limits in its update call")
}
err = handler.Admit(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "status", admission.Update, nil)) // newHandlerForTest returns a handler configured for testing.
func newHandlerForTest(c clientset.Interface) (admission.Interface, informers.SharedInformerFactory, error) {
f := informers.NewSharedInformerFactory(c, 5*time.Minute)
handler, err := NewLimitRanger(c, &DefaultLimitRangerActions{})
if err != nil { if err != nil {
t.Errorf("Should have ignored calls to any subresource of pod %v", err) return nil, f, err
} }
plugins := []admission.Interface{handler}
pluginInitializer := admission.NewPluginInitializer(f, nil)
pluginInitializer.Initialize(plugins)
err = admission.Validate(plugins)
return handler, f, err
} }
func validPersistentVolumeClaim(name string, resources api.ResourceRequirements) api.PersistentVolumeClaim { func validPersistentVolumeClaim(name string, resources api.ResourceRequirements) api.PersistentVolumeClaim {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment