2015-01-26 04:34:30 +00:00
|
|
|
/*
|
2016-06-03 00:25:58 +00:00
|
|
|
Copyright 2014 The Kubernetes Authors.
|
2015-01-26 04:34:30 +00:00
|
|
|
|
|
|
|
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 resourcequota
|
2015-01-26 18:43:55 +00:00
|
|
|
|
|
|
|
import (
|
2015-08-13 14:19:27 +00:00
|
|
|
"strconv"
|
2016-02-22 16:15:09 +00:00
|
|
|
"strings"
|
2015-01-26 18:43:55 +00:00
|
|
|
"testing"
|
2016-02-22 16:15:09 +00:00
|
|
|
"time"
|
|
|
|
|
2016-05-10 20:00:24 +00:00
|
|
|
lru "github.com/hashicorp/golang-lru"
|
2015-01-26 18:43:55 +00:00
|
|
|
|
2017-01-17 03:38:19 +00:00
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
2017-01-11 14:09:48 +00:00
|
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
2017-01-17 21:42:13 +00:00
|
|
|
"k8s.io/apiserver/pkg/admission"
|
2015-08-05 22:03:47 +00:00
|
|
|
"k8s.io/kubernetes/pkg/api"
|
|
|
|
"k8s.io/kubernetes/pkg/api/resource"
|
2015-09-03 21:40:58 +00:00
|
|
|
"k8s.io/kubernetes/pkg/client/cache"
|
2016-02-16 22:16:45 +00:00
|
|
|
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
2016-04-13 22:33:15 +00:00
|
|
|
testcore "k8s.io/kubernetes/pkg/client/testing/core"
|
2016-03-11 14:40:01 +00:00
|
|
|
"k8s.io/kubernetes/pkg/quota"
|
|
|
|
"k8s.io/kubernetes/pkg/quota/generic"
|
2016-02-22 16:15:09 +00:00
|
|
|
"k8s.io/kubernetes/pkg/quota/install"
|
2015-01-26 18:43:55 +00:00
|
|
|
)
|
|
|
|
|
2015-08-13 14:19:27 +00:00
|
|
|
func getResourceList(cpu, memory string) api.ResourceList {
|
|
|
|
res := api.ResourceList{}
|
2015-01-25 04:19:36 +00:00
|
|
|
if cpu != "" {
|
2015-08-13 14:19:27 +00:00
|
|
|
res[api.ResourceCPU] = resource.MustParse(cpu)
|
2015-01-25 04:19:36 +00:00
|
|
|
}
|
|
|
|
if memory != "" {
|
2015-08-13 14:19:27 +00:00
|
|
|
res[api.ResourceMemory] = resource.MustParse(memory)
|
2015-01-25 04:19:36 +00:00
|
|
|
}
|
2015-08-13 14:19:27 +00:00
|
|
|
return res
|
|
|
|
}
|
2015-01-25 04:19:36 +00:00
|
|
|
|
2015-08-13 14:19:27 +00:00
|
|
|
func getResourceRequirements(requests, limits api.ResourceList) api.ResourceRequirements {
|
|
|
|
res := api.ResourceRequirements{}
|
|
|
|
res.Requests = requests
|
|
|
|
res.Limits = limits
|
2015-01-25 04:19:36 +00:00
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
2015-08-13 14:19:27 +00:00
|
|
|
func validPod(name string, numContainers int, resources api.ResourceRequirements) *api.Pod {
|
|
|
|
pod := &api.Pod{
|
2017-01-17 03:38:19 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "test"},
|
2015-08-13 14:19:27 +00:00
|
|
|
Spec: api.PodSpec{},
|
|
|
|
}
|
|
|
|
pod.Spec.Containers = make([]api.Container, 0, numContainers)
|
|
|
|
for i := 0; i < numContainers; i++ {
|
|
|
|
pod.Spec.Containers = append(pod.Spec.Containers, api.Container{
|
|
|
|
Image: "foo:V" + strconv.Itoa(i),
|
|
|
|
Resources: resources,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return pod
|
|
|
|
}
|
|
|
|
|
2016-08-10 20:35:22 +00:00
|
|
|
func validPersistentVolumeClaim(name string, resources api.ResourceRequirements) *api.PersistentVolumeClaim {
|
|
|
|
return &api.PersistentVolumeClaim{
|
2017-01-17 03:38:19 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "test"},
|
2016-08-10 20:35:22 +00:00
|
|
|
Spec: api.PersistentVolumeClaimSpec{
|
|
|
|
Resources: resources,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-04 20:16:47 +00:00
|
|
|
func TestPrettyPrint(t *testing.T) {
|
|
|
|
toResourceList := func(resources map[api.ResourceName]string) api.ResourceList {
|
|
|
|
resourceList := api.ResourceList{}
|
|
|
|
for key, value := range resources {
|
|
|
|
resourceList[key] = resource.MustParse(value)
|
|
|
|
}
|
|
|
|
return resourceList
|
|
|
|
}
|
|
|
|
testCases := []struct {
|
|
|
|
input api.ResourceList
|
|
|
|
expected string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
input: toResourceList(map[api.ResourceName]string{
|
|
|
|
api.ResourceCPU: "100m",
|
|
|
|
}),
|
|
|
|
expected: "cpu=100m",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
input: toResourceList(map[api.ResourceName]string{
|
|
|
|
api.ResourcePods: "10",
|
|
|
|
api.ResourceServices: "10",
|
|
|
|
api.ResourceReplicationControllers: "10",
|
|
|
|
api.ResourceServicesNodePorts: "10",
|
|
|
|
api.ResourceRequestsCPU: "100m",
|
|
|
|
api.ResourceRequestsMemory: "100Mi",
|
|
|
|
api.ResourceLimitsCPU: "100m",
|
|
|
|
api.ResourceLimitsMemory: "100Mi",
|
|
|
|
}),
|
|
|
|
expected: "limits.cpu=100m,limits.memory=100Mi,pods=10,replicationcontrollers=10,requests.cpu=100m,requests.memory=100Mi,services=10,services.nodeports=10",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for i, testCase := range testCases {
|
|
|
|
result := prettyPrint(testCase.input)
|
|
|
|
if result != testCase.expected {
|
|
|
|
t.Errorf("Pretty print did not give stable sorted output[%d], expected %v, but got %v", i, testCase.expected, result)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-22 16:15:09 +00:00
|
|
|
// TestAdmissionIgnoresDelete verifies that the admission controller ignores delete operations
|
2015-01-26 18:43:55 +00:00
|
|
|
func TestAdmissionIgnoresDelete(t *testing.T) {
|
2016-02-22 16:15:09 +00:00
|
|
|
kubeClient := fake.NewSimpleClientset()
|
2016-12-21 21:16:16 +00:00
|
|
|
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc})
|
2016-05-10 20:00:24 +00:00
|
|
|
stopCh := make(chan struct{})
|
|
|
|
defer close(stopCh)
|
2016-12-21 21:16:16 +00:00
|
|
|
|
|
|
|
quotaAccessor, _ := newQuotaAccessor(kubeClient)
|
|
|
|
quotaAccessor.indexer = indexer
|
|
|
|
go quotaAccessor.Run(stopCh)
|
|
|
|
evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, 5, stopCh)
|
|
|
|
|
|
|
|
handler := "aAdmission{
|
|
|
|
Handler: admission.NewHandler(admission.Create, admission.Update),
|
|
|
|
evaluator: evaluator,
|
2016-02-22 16:15:09 +00:00
|
|
|
}
|
2015-01-26 18:43:55 +00:00
|
|
|
namespace := "default"
|
2016-12-21 21:16:16 +00:00
|
|
|
err := handler.Admit(admission.NewAttributesRecord(nil, nil, api.Kind("Pod").WithVersion("version"), namespace, "name", api.Resource("pods").WithVersion("version"), "", admission.Delete, nil))
|
2015-01-26 18:43:55 +00:00
|
|
|
if err != nil {
|
2015-05-15 14:48:33 +00:00
|
|
|
t.Errorf("ResourceQuota should admit all deletes: %v", err)
|
2015-01-26 18:43:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-22 16:15:09 +00:00
|
|
|
// TestAdmissionIgnoresSubresources verifies that the admission controller ignores subresources
|
|
|
|
// It verifies that creation of a pod that would have exceeded quota is properly failed
|
|
|
|
// It verifies that create operations to a subresource that would have exceeded quota would succeed
|
2015-06-18 20:03:40 +00:00
|
|
|
func TestAdmissionIgnoresSubresources(t *testing.T) {
|
2016-02-22 16:15:09 +00:00
|
|
|
resourceQuota := &api.ResourceQuota{}
|
|
|
|
resourceQuota.Name = "quota"
|
|
|
|
resourceQuota.Namespace = "test"
|
|
|
|
resourceQuota.Status = api.ResourceQuotaStatus{
|
2015-06-18 20:03:40 +00:00
|
|
|
Hard: api.ResourceList{},
|
|
|
|
Used: api.ResourceList{},
|
|
|
|
}
|
2016-02-22 16:15:09 +00:00
|
|
|
resourceQuota.Status.Hard[api.ResourceMemory] = resource.MustParse("2Gi")
|
|
|
|
resourceQuota.Status.Used[api.ResourceMemory] = resource.MustParse("1Gi")
|
|
|
|
kubeClient := fake.NewSimpleClientset(resourceQuota)
|
|
|
|
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc})
|
2016-05-10 20:00:24 +00:00
|
|
|
stopCh := make(chan struct{})
|
|
|
|
defer close(stopCh)
|
2016-05-26 13:59:48 +00:00
|
|
|
|
|
|
|
quotaAccessor, _ := newQuotaAccessor(kubeClient)
|
|
|
|
quotaAccessor.indexer = indexer
|
|
|
|
go quotaAccessor.Run(stopCh)
|
2016-11-18 20:54:08 +00:00
|
|
|
evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, 5, stopCh)
|
2016-05-26 13:59:48 +00:00
|
|
|
|
2016-02-22 16:15:09 +00:00
|
|
|
handler := "aAdmission{
|
2016-04-08 20:32:08 +00:00
|
|
|
Handler: admission.NewHandler(admission.Create, admission.Update),
|
|
|
|
evaluator: evaluator,
|
2016-02-22 16:15:09 +00:00
|
|
|
}
|
2016-04-08 20:32:08 +00:00
|
|
|
indexer.Add(resourceQuota)
|
2015-08-13 14:19:27 +00:00
|
|
|
newPod := validPod("123", 1, getResourceRequirements(getResourceList("100m", "2Gi"), getResourceList("", "")))
|
2016-04-29 01:21:35 +00:00
|
|
|
err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
2015-06-18 20:03:40 +00:00
|
|
|
if err == nil {
|
|
|
|
t.Errorf("Expected an error because the pod exceeded allowed quota")
|
|
|
|
}
|
2016-04-29 01:21:35 +00:00
|
|
|
err = handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "subresource", admission.Create, nil))
|
2015-06-18 20:03:40 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Did not expect an error because the action went to a subresource: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-22 16:15:09 +00:00
|
|
|
// TestAdmitBelowQuotaLimit verifies that a pod when created has its usage reflected on the quota
|
|
|
|
func TestAdmitBelowQuotaLimit(t *testing.T) {
|
|
|
|
resourceQuota := &api.ResourceQuota{
|
2017-01-17 03:38:19 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "quota", Namespace: "test", ResourceVersion: "124"},
|
2016-02-22 16:15:09 +00:00
|
|
|
Status: api.ResourceQuotaStatus{
|
|
|
|
Hard: api.ResourceList{
|
|
|
|
api.ResourceCPU: resource.MustParse("3"),
|
|
|
|
api.ResourceMemory: resource.MustParse("100Gi"),
|
|
|
|
api.ResourcePods: resource.MustParse("5"),
|
|
|
|
},
|
|
|
|
Used: api.ResourceList{
|
|
|
|
api.ResourceCPU: resource.MustParse("1"),
|
|
|
|
api.ResourceMemory: resource.MustParse("50Gi"),
|
|
|
|
api.ResourcePods: resource.MustParse("3"),
|
|
|
|
},
|
2015-01-26 18:43:55 +00:00
|
|
|
},
|
|
|
|
}
|
2016-02-22 16:15:09 +00:00
|
|
|
kubeClient := fake.NewSimpleClientset(resourceQuota)
|
|
|
|
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc})
|
2016-05-10 20:00:24 +00:00
|
|
|
stopCh := make(chan struct{})
|
|
|
|
defer close(stopCh)
|
2016-05-26 13:59:48 +00:00
|
|
|
|
|
|
|
quotaAccessor, _ := newQuotaAccessor(kubeClient)
|
|
|
|
quotaAccessor.indexer = indexer
|
|
|
|
go quotaAccessor.Run(stopCh)
|
2016-11-18 20:54:08 +00:00
|
|
|
evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, 5, stopCh)
|
2016-05-26 13:59:48 +00:00
|
|
|
|
2016-02-22 16:15:09 +00:00
|
|
|
handler := "aAdmission{
|
2016-04-08 20:32:08 +00:00
|
|
|
Handler: admission.NewHandler(admission.Create, admission.Update),
|
|
|
|
evaluator: evaluator,
|
2016-02-22 16:15:09 +00:00
|
|
|
}
|
2016-04-08 20:32:08 +00:00
|
|
|
indexer.Add(resourceQuota)
|
2016-02-22 16:15:09 +00:00
|
|
|
newPod := validPod("allowed-pod", 1, getResourceRequirements(getResourceList("100m", "2Gi"), getResourceList("", "")))
|
2016-04-29 01:21:35 +00:00
|
|
|
err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
2016-02-22 16:15:09 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Unexpected error: %v", err)
|
|
|
|
}
|
|
|
|
if len(kubeClient.Actions()) == 0 {
|
|
|
|
t.Errorf("Expected a client action")
|
|
|
|
}
|
2015-01-26 18:43:55 +00:00
|
|
|
|
2016-02-22 16:15:09 +00:00
|
|
|
expectedActionSet := sets.NewString(
|
|
|
|
strings.Join([]string{"update", "resourcequotas", "status"}, "-"),
|
|
|
|
)
|
|
|
|
actionSet := sets.NewString()
|
|
|
|
for _, action := range kubeClient.Actions() {
|
2016-04-13 22:33:15 +00:00
|
|
|
actionSet.Insert(strings.Join([]string{action.GetVerb(), action.GetResource().Resource, action.GetSubresource()}, "-"))
|
2016-02-22 16:15:09 +00:00
|
|
|
}
|
|
|
|
if !actionSet.HasAll(expectedActionSet.List()...) {
|
|
|
|
t.Errorf("Expected actions:\n%v\n but got:\n%v\nDifference:\n%v", expectedActionSet, actionSet, expectedActionSet.Difference(actionSet))
|
|
|
|
}
|
|
|
|
|
2016-04-08 20:32:08 +00:00
|
|
|
decimatedActions := removeListWatch(kubeClient.Actions())
|
|
|
|
lastActionIndex := len(decimatedActions) - 1
|
|
|
|
usage := decimatedActions[lastActionIndex].(testcore.UpdateAction).GetObject().(*api.ResourceQuota)
|
2016-02-22 16:15:09 +00:00
|
|
|
expectedUsage := api.ResourceQuota{
|
|
|
|
Status: api.ResourceQuotaStatus{
|
|
|
|
Hard: api.ResourceList{
|
|
|
|
api.ResourceCPU: resource.MustParse("3"),
|
|
|
|
api.ResourceMemory: resource.MustParse("100Gi"),
|
|
|
|
api.ResourcePods: resource.MustParse("5"),
|
|
|
|
},
|
|
|
|
Used: api.ResourceList{
|
|
|
|
api.ResourceCPU: resource.MustParse("1100m"),
|
|
|
|
api.ResourceMemory: resource.MustParse("52Gi"),
|
|
|
|
api.ResourcePods: resource.MustParse("4"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for k, v := range expectedUsage.Status.Used {
|
|
|
|
actual := usage.Status.Used[k]
|
|
|
|
actualValue := actual.String()
|
|
|
|
expectedValue := v.String()
|
|
|
|
if expectedValue != actualValue {
|
|
|
|
t.Errorf("Usage Used: Key: %v, Expected: %v, Actual: %v", k, expectedValue, actualValue)
|
2015-08-13 14:19:27 +00:00
|
|
|
}
|
2015-01-26 18:43:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-03 19:45:28 +00:00
|
|
|
// TestAdmitHandlesOldObjects verifies that admit handles updates correctly with old objects
|
|
|
|
func TestAdmitHandlesOldObjects(t *testing.T) {
|
|
|
|
// in this scenario, the old quota was based on a service type=loadbalancer
|
|
|
|
resourceQuota := &api.ResourceQuota{
|
2017-01-17 03:38:19 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "quota", Namespace: "test", ResourceVersion: "124"},
|
2016-06-03 19:45:28 +00:00
|
|
|
Status: api.ResourceQuotaStatus{
|
|
|
|
Hard: api.ResourceList{
|
|
|
|
api.ResourceServices: resource.MustParse("10"),
|
|
|
|
api.ResourceServicesLoadBalancers: resource.MustParse("10"),
|
|
|
|
api.ResourceServicesNodePorts: resource.MustParse("10"),
|
|
|
|
},
|
|
|
|
Used: api.ResourceList{
|
|
|
|
api.ResourceServices: resource.MustParse("1"),
|
|
|
|
api.ResourceServicesLoadBalancers: resource.MustParse("1"),
|
|
|
|
api.ResourceServicesNodePorts: resource.MustParse("0"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// start up quota system
|
|
|
|
kubeClient := fake.NewSimpleClientset(resourceQuota)
|
|
|
|
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc})
|
|
|
|
stopCh := make(chan struct{})
|
|
|
|
defer close(stopCh)
|
2016-05-26 13:59:48 +00:00
|
|
|
|
|
|
|
quotaAccessor, _ := newQuotaAccessor(kubeClient)
|
|
|
|
quotaAccessor.indexer = indexer
|
|
|
|
go quotaAccessor.Run(stopCh)
|
2016-11-18 20:54:08 +00:00
|
|
|
evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, 5, stopCh)
|
2016-05-26 13:59:48 +00:00
|
|
|
|
2016-06-03 19:45:28 +00:00
|
|
|
handler := "aAdmission{
|
|
|
|
Handler: admission.NewHandler(admission.Create, admission.Update),
|
|
|
|
evaluator: evaluator,
|
|
|
|
}
|
|
|
|
indexer.Add(resourceQuota)
|
|
|
|
|
|
|
|
// old service was a load balancer, but updated version is a node port.
|
2016-08-18 17:41:33 +00:00
|
|
|
existingService := &api.Service{
|
2017-01-17 03:38:19 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "service", Namespace: "test", ResourceVersion: "1"},
|
2016-06-03 19:45:28 +00:00
|
|
|
Spec: api.ServiceSpec{Type: api.ServiceTypeLoadBalancer},
|
|
|
|
}
|
|
|
|
newService := &api.Service{
|
2017-01-17 03:38:19 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "service", Namespace: "test"},
|
2016-07-22 15:50:29 +00:00
|
|
|
Spec: api.ServiceSpec{
|
|
|
|
Type: api.ServiceTypeNodePort,
|
|
|
|
Ports: []api.ServicePort{{Port: 1234}},
|
|
|
|
},
|
2016-06-03 19:45:28 +00:00
|
|
|
}
|
2016-08-18 17:41:33 +00:00
|
|
|
err := handler.Admit(admission.NewAttributesRecord(newService, existingService, api.Kind("Service").WithVersion("version"), newService.Namespace, newService.Name, api.Resource("services").WithVersion("version"), "", admission.Update, nil))
|
2016-06-03 19:45:28 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Unexpected error: %v", err)
|
|
|
|
}
|
|
|
|
if len(kubeClient.Actions()) == 0 {
|
|
|
|
t.Errorf("Expected a client action")
|
|
|
|
}
|
|
|
|
|
|
|
|
// the only action should have been to update the quota (since we should not have fetched the previous item)
|
|
|
|
expectedActionSet := sets.NewString(
|
|
|
|
strings.Join([]string{"update", "resourcequotas", "status"}, "-"),
|
|
|
|
)
|
|
|
|
actionSet := sets.NewString()
|
|
|
|
for _, action := range kubeClient.Actions() {
|
|
|
|
actionSet.Insert(strings.Join([]string{action.GetVerb(), action.GetResource().Resource, action.GetSubresource()}, "-"))
|
|
|
|
}
|
|
|
|
if !actionSet.HasAll(expectedActionSet.List()...) {
|
|
|
|
t.Errorf("Expected actions:\n%v\n but got:\n%v\nDifference:\n%v", expectedActionSet, actionSet, expectedActionSet.Difference(actionSet))
|
|
|
|
}
|
|
|
|
|
|
|
|
// verify usage decremented the loadbalancer, and incremented the nodeport, but kept the service the same.
|
|
|
|
decimatedActions := removeListWatch(kubeClient.Actions())
|
|
|
|
lastActionIndex := len(decimatedActions) - 1
|
|
|
|
usage := decimatedActions[lastActionIndex].(testcore.UpdateAction).GetObject().(*api.ResourceQuota)
|
|
|
|
expectedUsage := api.ResourceQuota{
|
|
|
|
Status: api.ResourceQuotaStatus{
|
|
|
|
Hard: api.ResourceList{
|
|
|
|
api.ResourceServices: resource.MustParse("10"),
|
|
|
|
api.ResourceServicesLoadBalancers: resource.MustParse("10"),
|
|
|
|
api.ResourceServicesNodePorts: resource.MustParse("10"),
|
|
|
|
},
|
|
|
|
Used: api.ResourceList{
|
|
|
|
api.ResourceServices: resource.MustParse("1"),
|
|
|
|
api.ResourceServicesLoadBalancers: resource.MustParse("0"),
|
|
|
|
api.ResourceServicesNodePorts: resource.MustParse("1"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for k, v := range expectedUsage.Status.Used {
|
|
|
|
actual := usage.Status.Used[k]
|
|
|
|
actualValue := actual.String()
|
|
|
|
expectedValue := v.String()
|
|
|
|
if expectedValue != actualValue {
|
|
|
|
t.Errorf("Usage Used: Key: %v, Expected: %v, Actual: %v", k, expectedValue, actualValue)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-22 14:36:41 +00:00
|
|
|
// TestAdmitHandlesCreatingUpdates verifies that admit handles updates which behave as creates
|
|
|
|
func TestAdmitHandlesCreatingUpdates(t *testing.T) {
|
|
|
|
// in this scenario, there is an existing service
|
|
|
|
resourceQuota := &api.ResourceQuota{
|
2017-01-17 03:38:19 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "quota", Namespace: "test", ResourceVersion: "124"},
|
2016-08-22 14:36:41 +00:00
|
|
|
Status: api.ResourceQuotaStatus{
|
|
|
|
Hard: api.ResourceList{
|
|
|
|
api.ResourceServices: resource.MustParse("10"),
|
|
|
|
api.ResourceServicesLoadBalancers: resource.MustParse("10"),
|
|
|
|
api.ResourceServicesNodePorts: resource.MustParse("10"),
|
|
|
|
},
|
|
|
|
Used: api.ResourceList{
|
|
|
|
api.ResourceServices: resource.MustParse("1"),
|
|
|
|
api.ResourceServicesLoadBalancers: resource.MustParse("1"),
|
|
|
|
api.ResourceServicesNodePorts: resource.MustParse("0"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// start up quota system
|
|
|
|
kubeClient := fake.NewSimpleClientset(resourceQuota)
|
|
|
|
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc})
|
|
|
|
stopCh := make(chan struct{})
|
|
|
|
defer close(stopCh)
|
|
|
|
|
|
|
|
quotaAccessor, _ := newQuotaAccessor(kubeClient)
|
|
|
|
quotaAccessor.indexer = indexer
|
|
|
|
go quotaAccessor.Run(stopCh)
|
2016-11-18 20:54:08 +00:00
|
|
|
evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, 5, stopCh)
|
2016-08-22 14:36:41 +00:00
|
|
|
|
|
|
|
handler := "aAdmission{
|
|
|
|
Handler: admission.NewHandler(admission.Create, admission.Update),
|
|
|
|
evaluator: evaluator,
|
|
|
|
}
|
|
|
|
indexer.Add(resourceQuota)
|
|
|
|
|
|
|
|
// old service didn't exist, so this update is actually a create
|
|
|
|
oldService := &api.Service{
|
2017-01-17 03:38:19 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "service", Namespace: "test", ResourceVersion: ""},
|
2016-08-22 14:36:41 +00:00
|
|
|
Spec: api.ServiceSpec{Type: api.ServiceTypeLoadBalancer},
|
|
|
|
}
|
|
|
|
newService := &api.Service{
|
2017-01-17 03:38:19 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "service", Namespace: "test"},
|
2016-08-22 14:36:41 +00:00
|
|
|
Spec: api.ServiceSpec{
|
|
|
|
Type: api.ServiceTypeNodePort,
|
|
|
|
Ports: []api.ServicePort{{Port: 1234}},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
err := handler.Admit(admission.NewAttributesRecord(newService, oldService, api.Kind("Service").WithVersion("version"), newService.Namespace, newService.Name, api.Resource("services").WithVersion("version"), "", admission.Update, nil))
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Unexpected error: %v", err)
|
|
|
|
}
|
|
|
|
if len(kubeClient.Actions()) == 0 {
|
|
|
|
t.Errorf("Expected a client action")
|
|
|
|
}
|
|
|
|
|
|
|
|
// the only action should have been to update the quota (since we should not have fetched the previous item)
|
|
|
|
expectedActionSet := sets.NewString(
|
|
|
|
strings.Join([]string{"update", "resourcequotas", "status"}, "-"),
|
|
|
|
)
|
|
|
|
actionSet := sets.NewString()
|
|
|
|
for _, action := range kubeClient.Actions() {
|
|
|
|
actionSet.Insert(strings.Join([]string{action.GetVerb(), action.GetResource().Resource, action.GetSubresource()}, "-"))
|
|
|
|
}
|
|
|
|
if !actionSet.HasAll(expectedActionSet.List()...) {
|
|
|
|
t.Errorf("Expected actions:\n%v\n but got:\n%v\nDifference:\n%v", expectedActionSet, actionSet, expectedActionSet.Difference(actionSet))
|
|
|
|
}
|
|
|
|
|
|
|
|
// verify that the "old" object was ignored for calculating the new usage
|
|
|
|
decimatedActions := removeListWatch(kubeClient.Actions())
|
|
|
|
lastActionIndex := len(decimatedActions) - 1
|
|
|
|
usage := decimatedActions[lastActionIndex].(testcore.UpdateAction).GetObject().(*api.ResourceQuota)
|
|
|
|
expectedUsage := api.ResourceQuota{
|
|
|
|
Status: api.ResourceQuotaStatus{
|
|
|
|
Hard: api.ResourceList{
|
|
|
|
api.ResourceServices: resource.MustParse("10"),
|
|
|
|
api.ResourceServicesLoadBalancers: resource.MustParse("10"),
|
|
|
|
api.ResourceServicesNodePorts: resource.MustParse("10"),
|
|
|
|
},
|
|
|
|
Used: api.ResourceList{
|
|
|
|
api.ResourceServices: resource.MustParse("2"),
|
|
|
|
api.ResourceServicesLoadBalancers: resource.MustParse("1"),
|
|
|
|
api.ResourceServicesNodePorts: resource.MustParse("1"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for k, v := range expectedUsage.Status.Used {
|
|
|
|
actual := usage.Status.Used[k]
|
|
|
|
actualValue := actual.String()
|
|
|
|
expectedValue := v.String()
|
|
|
|
if expectedValue != actualValue {
|
|
|
|
t.Errorf("Usage Used: Key: %v, Expected: %v, Actual: %v", k, expectedValue, actualValue)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-22 16:15:09 +00:00
|
|
|
// TestAdmitExceedQuotaLimit verifies that if a pod exceeded allowed usage that its rejected during admission.
|
|
|
|
func TestAdmitExceedQuotaLimit(t *testing.T) {
|
|
|
|
resourceQuota := &api.ResourceQuota{
|
2017-01-17 03:38:19 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "quota", Namespace: "test", ResourceVersion: "124"},
|
2016-02-22 16:15:09 +00:00
|
|
|
Status: api.ResourceQuotaStatus{
|
|
|
|
Hard: api.ResourceList{
|
|
|
|
api.ResourceCPU: resource.MustParse("3"),
|
|
|
|
api.ResourceMemory: resource.MustParse("100Gi"),
|
|
|
|
api.ResourcePods: resource.MustParse("5"),
|
|
|
|
},
|
|
|
|
Used: api.ResourceList{
|
|
|
|
api.ResourceCPU: resource.MustParse("1"),
|
|
|
|
api.ResourceMemory: resource.MustParse("50Gi"),
|
|
|
|
api.ResourcePods: resource.MustParse("3"),
|
|
|
|
},
|
|
|
|
},
|
2015-01-26 18:43:55 +00:00
|
|
|
}
|
2016-02-22 16:15:09 +00:00
|
|
|
kubeClient := fake.NewSimpleClientset(resourceQuota)
|
|
|
|
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc})
|
2016-05-10 20:00:24 +00:00
|
|
|
stopCh := make(chan struct{})
|
|
|
|
defer close(stopCh)
|
2016-05-26 13:59:48 +00:00
|
|
|
|
|
|
|
quotaAccessor, _ := newQuotaAccessor(kubeClient)
|
|
|
|
quotaAccessor.indexer = indexer
|
|
|
|
go quotaAccessor.Run(stopCh)
|
2016-11-18 20:54:08 +00:00
|
|
|
evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, 5, stopCh)
|
2016-05-26 13:59:48 +00:00
|
|
|
|
2016-02-22 16:15:09 +00:00
|
|
|
handler := "aAdmission{
|
2016-04-08 20:32:08 +00:00
|
|
|
Handler: admission.NewHandler(admission.Create, admission.Update),
|
|
|
|
evaluator: evaluator,
|
2016-02-22 16:15:09 +00:00
|
|
|
}
|
2016-04-08 20:32:08 +00:00
|
|
|
indexer.Add(resourceQuota)
|
2016-02-22 16:15:09 +00:00
|
|
|
newPod := validPod("not-allowed-pod", 1, getResourceRequirements(getResourceList("3", "2Gi"), getResourceList("", "")))
|
2016-04-29 01:21:35 +00:00
|
|
|
err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
2016-02-22 16:15:09 +00:00
|
|
|
if err == nil {
|
|
|
|
t.Errorf("Expected an error exceeding quota")
|
2015-01-26 18:43:55 +00:00
|
|
|
}
|
2016-02-22 16:15:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TestAdmitEnforceQuotaConstraints verifies that if a quota tracks a particular resource that that resource is
|
|
|
|
// specified on the pod. In this case, we create a quota that tracks cpu request, memory request, and memory limit.
|
|
|
|
// We ensure that a pod that does not specify a memory limit that it fails in admission.
|
|
|
|
func TestAdmitEnforceQuotaConstraints(t *testing.T) {
|
|
|
|
resourceQuota := &api.ResourceQuota{
|
2017-01-17 03:38:19 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "quota", Namespace: "test", ResourceVersion: "124"},
|
2016-02-22 16:15:09 +00:00
|
|
|
Status: api.ResourceQuotaStatus{
|
|
|
|
Hard: api.ResourceList{
|
|
|
|
api.ResourceCPU: resource.MustParse("3"),
|
|
|
|
api.ResourceMemory: resource.MustParse("100Gi"),
|
|
|
|
api.ResourceLimitsMemory: resource.MustParse("200Gi"),
|
|
|
|
api.ResourcePods: resource.MustParse("5"),
|
|
|
|
},
|
|
|
|
Used: api.ResourceList{
|
|
|
|
api.ResourceCPU: resource.MustParse("1"),
|
|
|
|
api.ResourceMemory: resource.MustParse("50Gi"),
|
|
|
|
api.ResourceLimitsMemory: resource.MustParse("100Gi"),
|
|
|
|
api.ResourcePods: resource.MustParse("3"),
|
|
|
|
},
|
|
|
|
},
|
2015-01-26 18:43:55 +00:00
|
|
|
}
|
2016-02-22 16:15:09 +00:00
|
|
|
kubeClient := fake.NewSimpleClientset(resourceQuota)
|
|
|
|
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc})
|
2016-05-10 20:00:24 +00:00
|
|
|
stopCh := make(chan struct{})
|
|
|
|
defer close(stopCh)
|
2016-05-26 13:59:48 +00:00
|
|
|
|
|
|
|
quotaAccessor, _ := newQuotaAccessor(kubeClient)
|
|
|
|
quotaAccessor.indexer = indexer
|
|
|
|
go quotaAccessor.Run(stopCh)
|
2016-11-18 20:54:08 +00:00
|
|
|
evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, 5, stopCh)
|
2016-05-26 13:59:48 +00:00
|
|
|
|
2016-02-22 16:15:09 +00:00
|
|
|
handler := "aAdmission{
|
2016-04-08 20:32:08 +00:00
|
|
|
Handler: admission.NewHandler(admission.Create, admission.Update),
|
|
|
|
evaluator: evaluator,
|
2016-02-22 16:15:09 +00:00
|
|
|
}
|
2016-04-08 20:32:08 +00:00
|
|
|
indexer.Add(resourceQuota)
|
2016-05-11 18:30:46 +00:00
|
|
|
// verify all values are specified as required on the quota
|
2016-02-22 16:15:09 +00:00
|
|
|
newPod := validPod("not-allowed-pod", 1, getResourceRequirements(getResourceList("100m", "2Gi"), getResourceList("200m", "")))
|
2016-04-29 01:21:35 +00:00
|
|
|
err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
2016-02-22 16:15:09 +00:00
|
|
|
if err == nil {
|
|
|
|
t.Errorf("Expected an error because the pod does not specify a memory limit")
|
2016-05-11 18:30:46 +00:00
|
|
|
}
|
|
|
|
// verify the requests and limits are actually valid (in this case, we fail because the limits < requests)
|
|
|
|
newPod = validPod("not-allowed-pod", 1, getResourceRequirements(getResourceList("200m", "2Gi"), getResourceList("100m", "1Gi")))
|
2016-04-29 01:21:35 +00:00
|
|
|
err = handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
2016-05-11 18:30:46 +00:00
|
|
|
if err == nil {
|
|
|
|
t.Errorf("Expected an error because the pod does not specify a memory limit")
|
2015-01-26 18:43:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-22 16:15:09 +00:00
|
|
|
// TestAdmitPodInNamespaceWithoutQuota ensures that if a namespace has no quota, that a pod can get in
|
|
|
|
func TestAdmitPodInNamespaceWithoutQuota(t *testing.T) {
|
|
|
|
resourceQuota := &api.ResourceQuota{
|
2017-01-17 03:38:19 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "quota", Namespace: "other", ResourceVersion: "124"},
|
2016-02-22 16:15:09 +00:00
|
|
|
Status: api.ResourceQuotaStatus{
|
|
|
|
Hard: api.ResourceList{
|
|
|
|
api.ResourceCPU: resource.MustParse("3"),
|
|
|
|
api.ResourceMemory: resource.MustParse("100Gi"),
|
|
|
|
api.ResourceLimitsMemory: resource.MustParse("200Gi"),
|
|
|
|
api.ResourcePods: resource.MustParse("5"),
|
|
|
|
},
|
|
|
|
Used: api.ResourceList{
|
|
|
|
api.ResourceCPU: resource.MustParse("1"),
|
|
|
|
api.ResourceMemory: resource.MustParse("50Gi"),
|
|
|
|
api.ResourceLimitsMemory: resource.MustParse("100Gi"),
|
|
|
|
api.ResourcePods: resource.MustParse("3"),
|
|
|
|
},
|
|
|
|
},
|
2015-01-26 18:43:55 +00:00
|
|
|
}
|
2016-02-22 16:15:09 +00:00
|
|
|
kubeClient := fake.NewSimpleClientset(resourceQuota)
|
|
|
|
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc})
|
|
|
|
liveLookupCache, err := lru.New(100)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-05-10 20:00:24 +00:00
|
|
|
stopCh := make(chan struct{})
|
|
|
|
defer close(stopCh)
|
2016-05-26 13:59:48 +00:00
|
|
|
|
|
|
|
quotaAccessor, _ := newQuotaAccessor(kubeClient)
|
|
|
|
quotaAccessor.indexer = indexer
|
|
|
|
quotaAccessor.liveLookupCache = liveLookupCache
|
|
|
|
go quotaAccessor.Run(stopCh)
|
2016-11-18 20:54:08 +00:00
|
|
|
evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, 5, stopCh)
|
2016-05-26 13:59:48 +00:00
|
|
|
|
2016-02-22 16:15:09 +00:00
|
|
|
handler := "aAdmission{
|
2016-04-08 20:32:08 +00:00
|
|
|
Handler: admission.NewHandler(admission.Create, admission.Update),
|
|
|
|
evaluator: evaluator,
|
2016-02-22 16:15:09 +00:00
|
|
|
}
|
|
|
|
// Add to the index
|
2016-04-08 20:32:08 +00:00
|
|
|
indexer.Add(resourceQuota)
|
2016-02-22 16:15:09 +00:00
|
|
|
newPod := validPod("not-allowed-pod", 1, getResourceRequirements(getResourceList("100m", "2Gi"), getResourceList("200m", "")))
|
|
|
|
// Add to the lru cache so we do not do a live client lookup
|
|
|
|
liveLookupCache.Add(newPod.Namespace, liveLookupEntry{expiry: time.Now().Add(time.Duration(30 * time.Second)), items: []*api.ResourceQuota{}})
|
2016-04-29 01:21:35 +00:00
|
|
|
err = handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
2016-02-22 16:15:09 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Did not expect an error because the pod is in a different namespace than the quota")
|
2015-01-26 18:43:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-22 16:15:09 +00:00
|
|
|
// TestAdmitBelowTerminatingQuotaLimit ensures that terminating pods are charged to the right quota.
|
|
|
|
// It creates a terminating and non-terminating quota, and creates a terminating pod.
|
|
|
|
// It ensures that the terminating quota is incremented, and the non-terminating quota is not.
|
|
|
|
func TestAdmitBelowTerminatingQuotaLimit(t *testing.T) {
|
|
|
|
resourceQuotaNonTerminating := &api.ResourceQuota{
|
2017-01-17 03:38:19 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "quota-non-terminating", Namespace: "test", ResourceVersion: "124"},
|
2016-02-22 16:15:09 +00:00
|
|
|
Spec: api.ResourceQuotaSpec{
|
|
|
|
Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeNotTerminating},
|
|
|
|
},
|
|
|
|
Status: api.ResourceQuotaStatus{
|
|
|
|
Hard: api.ResourceList{
|
|
|
|
api.ResourceCPU: resource.MustParse("3"),
|
|
|
|
api.ResourceMemory: resource.MustParse("100Gi"),
|
|
|
|
api.ResourcePods: resource.MustParse("5"),
|
|
|
|
},
|
|
|
|
Used: api.ResourceList{
|
|
|
|
api.ResourceCPU: resource.MustParse("1"),
|
|
|
|
api.ResourceMemory: resource.MustParse("50Gi"),
|
|
|
|
api.ResourcePods: resource.MustParse("3"),
|
2015-01-26 18:43:55 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2016-02-22 16:15:09 +00:00
|
|
|
resourceQuotaTerminating := &api.ResourceQuota{
|
2017-01-17 03:38:19 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "quota-terminating", Namespace: "test", ResourceVersion: "124"},
|
2016-02-22 16:15:09 +00:00
|
|
|
Spec: api.ResourceQuotaSpec{
|
|
|
|
Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeTerminating},
|
|
|
|
},
|
|
|
|
Status: api.ResourceQuotaStatus{
|
|
|
|
Hard: api.ResourceList{
|
|
|
|
api.ResourceCPU: resource.MustParse("3"),
|
|
|
|
api.ResourceMemory: resource.MustParse("100Gi"),
|
|
|
|
api.ResourcePods: resource.MustParse("5"),
|
|
|
|
},
|
|
|
|
Used: api.ResourceList{
|
|
|
|
api.ResourceCPU: resource.MustParse("1"),
|
|
|
|
api.ResourceMemory: resource.MustParse("50Gi"),
|
|
|
|
api.ResourcePods: resource.MustParse("3"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
kubeClient := fake.NewSimpleClientset(resourceQuotaTerminating, resourceQuotaNonTerminating)
|
|
|
|
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc})
|
2016-05-10 20:00:24 +00:00
|
|
|
stopCh := make(chan struct{})
|
|
|
|
defer close(stopCh)
|
2016-05-26 13:59:48 +00:00
|
|
|
|
|
|
|
quotaAccessor, _ := newQuotaAccessor(kubeClient)
|
|
|
|
quotaAccessor.indexer = indexer
|
|
|
|
go quotaAccessor.Run(stopCh)
|
2016-11-18 20:54:08 +00:00
|
|
|
evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, 5, stopCh)
|
2016-05-26 13:59:48 +00:00
|
|
|
|
2016-02-22 16:15:09 +00:00
|
|
|
handler := "aAdmission{
|
2016-04-08 20:32:08 +00:00
|
|
|
Handler: admission.NewHandler(admission.Create, admission.Update),
|
|
|
|
evaluator: evaluator,
|
2016-02-22 16:15:09 +00:00
|
|
|
}
|
2016-04-08 20:32:08 +00:00
|
|
|
indexer.Add(resourceQuotaNonTerminating)
|
|
|
|
indexer.Add(resourceQuotaTerminating)
|
2016-02-22 16:15:09 +00:00
|
|
|
|
|
|
|
// create a pod that has an active deadline
|
|
|
|
newPod := validPod("allowed-pod", 1, getResourceRequirements(getResourceList("100m", "2Gi"), getResourceList("", "")))
|
|
|
|
activeDeadlineSeconds := int64(30)
|
|
|
|
newPod.Spec.ActiveDeadlineSeconds = &activeDeadlineSeconds
|
2016-04-29 01:21:35 +00:00
|
|
|
err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
2015-01-26 18:43:55 +00:00
|
|
|
if err != nil {
|
2015-05-15 14:48:33 +00:00
|
|
|
t.Errorf("Unexpected error: %v", err)
|
2015-01-26 18:43:55 +00:00
|
|
|
}
|
2016-02-22 16:15:09 +00:00
|
|
|
if len(kubeClient.Actions()) == 0 {
|
|
|
|
t.Errorf("Expected a client action")
|
2015-01-26 18:43:55 +00:00
|
|
|
}
|
2016-02-22 16:15:09 +00:00
|
|
|
|
|
|
|
expectedActionSet := sets.NewString(
|
|
|
|
strings.Join([]string{"update", "resourcequotas", "status"}, "-"),
|
|
|
|
)
|
|
|
|
actionSet := sets.NewString()
|
|
|
|
for _, action := range kubeClient.Actions() {
|
2016-04-13 22:33:15 +00:00
|
|
|
actionSet.Insert(strings.Join([]string{action.GetVerb(), action.GetResource().Resource, action.GetSubresource()}, "-"))
|
2016-02-22 16:15:09 +00:00
|
|
|
}
|
|
|
|
if !actionSet.HasAll(expectedActionSet.List()...) {
|
|
|
|
t.Errorf("Expected actions:\n%v\n but got:\n%v\nDifference:\n%v", expectedActionSet, actionSet, expectedActionSet.Difference(actionSet))
|
2015-01-26 18:43:55 +00:00
|
|
|
}
|
|
|
|
|
2016-04-08 20:32:08 +00:00
|
|
|
decimatedActions := removeListWatch(kubeClient.Actions())
|
|
|
|
lastActionIndex := len(decimatedActions) - 1
|
|
|
|
usage := decimatedActions[lastActionIndex].(testcore.UpdateAction).GetObject().(*api.ResourceQuota)
|
2016-02-22 16:15:09 +00:00
|
|
|
|
|
|
|
// ensure only the quota-terminating was updated
|
|
|
|
if usage.Name != resourceQuotaTerminating.Name {
|
|
|
|
t.Errorf("Incremented the wrong quota, expected %v, actual %v", resourceQuotaTerminating.Name, usage.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
expectedUsage := api.ResourceQuota{
|
|
|
|
Status: api.ResourceQuotaStatus{
|
|
|
|
Hard: api.ResourceList{
|
|
|
|
api.ResourceCPU: resource.MustParse("3"),
|
|
|
|
api.ResourceMemory: resource.MustParse("100Gi"),
|
|
|
|
api.ResourcePods: resource.MustParse("5"),
|
|
|
|
},
|
|
|
|
Used: api.ResourceList{
|
|
|
|
api.ResourceCPU: resource.MustParse("1100m"),
|
|
|
|
api.ResourceMemory: resource.MustParse("52Gi"),
|
|
|
|
api.ResourcePods: resource.MustParse("4"),
|
2015-01-26 18:43:55 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2016-02-22 16:15:09 +00:00
|
|
|
for k, v := range expectedUsage.Status.Used {
|
|
|
|
actual := usage.Status.Used[k]
|
|
|
|
actualValue := actual.String()
|
|
|
|
expectedValue := v.String()
|
|
|
|
if expectedValue != actualValue {
|
|
|
|
t.Errorf("Usage Used: Key: %v, Expected: %v, Actual: %v", k, expectedValue, actualValue)
|
|
|
|
}
|
2015-01-26 18:43:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-22 16:15:09 +00:00
|
|
|
// TestAdmitBelowBestEffortQuotaLimit creates a best effort and non-best effort quota.
|
|
|
|
// It verifies that best effort pods are properly scoped to the best effort quota document.
|
|
|
|
func TestAdmitBelowBestEffortQuotaLimit(t *testing.T) {
|
|
|
|
resourceQuotaBestEffort := &api.ResourceQuota{
|
2017-01-17 03:38:19 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "quota-besteffort", Namespace: "test", ResourceVersion: "124"},
|
2016-02-22 16:15:09 +00:00
|
|
|
Spec: api.ResourceQuotaSpec{
|
|
|
|
Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeBestEffort},
|
|
|
|
},
|
|
|
|
Status: api.ResourceQuotaStatus{
|
|
|
|
Hard: api.ResourceList{
|
|
|
|
api.ResourcePods: resource.MustParse("5"),
|
|
|
|
},
|
|
|
|
Used: api.ResourceList{
|
|
|
|
api.ResourcePods: resource.MustParse("3"),
|
2015-01-26 18:43:55 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2016-02-22 16:15:09 +00:00
|
|
|
resourceQuotaNotBestEffort := &api.ResourceQuota{
|
2017-01-17 03:38:19 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "quota-not-besteffort", Namespace: "test", ResourceVersion: "124"},
|
2016-02-22 16:15:09 +00:00
|
|
|
Spec: api.ResourceQuotaSpec{
|
|
|
|
Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeNotBestEffort},
|
|
|
|
},
|
|
|
|
Status: api.ResourceQuotaStatus{
|
|
|
|
Hard: api.ResourceList{
|
|
|
|
api.ResourcePods: resource.MustParse("5"),
|
|
|
|
},
|
|
|
|
Used: api.ResourceList{
|
|
|
|
api.ResourcePods: resource.MustParse("3"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
kubeClient := fake.NewSimpleClientset(resourceQuotaBestEffort, resourceQuotaNotBestEffort)
|
|
|
|
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc})
|
2016-05-10 20:00:24 +00:00
|
|
|
stopCh := make(chan struct{})
|
|
|
|
defer close(stopCh)
|
2016-05-26 13:59:48 +00:00
|
|
|
|
|
|
|
quotaAccessor, _ := newQuotaAccessor(kubeClient)
|
|
|
|
quotaAccessor.indexer = indexer
|
|
|
|
go quotaAccessor.Run(stopCh)
|
2016-11-18 20:54:08 +00:00
|
|
|
evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, 5, stopCh)
|
2016-05-26 13:59:48 +00:00
|
|
|
|
2016-02-22 16:15:09 +00:00
|
|
|
handler := "aAdmission{
|
2016-04-08 20:32:08 +00:00
|
|
|
Handler: admission.NewHandler(admission.Create, admission.Update),
|
|
|
|
evaluator: evaluator,
|
2016-02-22 16:15:09 +00:00
|
|
|
}
|
2016-04-08 20:32:08 +00:00
|
|
|
indexer.Add(resourceQuotaBestEffort)
|
|
|
|
indexer.Add(resourceQuotaNotBestEffort)
|
2016-02-22 16:15:09 +00:00
|
|
|
|
|
|
|
// create a pod that is best effort because it does not make a request for anything
|
|
|
|
newPod := validPod("allowed-pod", 1, getResourceRequirements(getResourceList("", ""), getResourceList("", "")))
|
2016-04-29 01:21:35 +00:00
|
|
|
err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
2015-01-26 18:43:55 +00:00
|
|
|
if err != nil {
|
2015-05-15 14:48:33 +00:00
|
|
|
t.Errorf("Unexpected error: %v", err)
|
2015-01-26 18:43:55 +00:00
|
|
|
}
|
2016-02-22 16:15:09 +00:00
|
|
|
expectedActionSet := sets.NewString(
|
|
|
|
strings.Join([]string{"update", "resourcequotas", "status"}, "-"),
|
|
|
|
)
|
|
|
|
actionSet := sets.NewString()
|
|
|
|
for _, action := range kubeClient.Actions() {
|
2016-04-13 22:33:15 +00:00
|
|
|
actionSet.Insert(strings.Join([]string{action.GetVerb(), action.GetResource().Resource, action.GetSubresource()}, "-"))
|
2015-01-26 18:43:55 +00:00
|
|
|
}
|
2016-02-22 16:15:09 +00:00
|
|
|
if !actionSet.HasAll(expectedActionSet.List()...) {
|
|
|
|
t.Errorf("Expected actions:\n%v\n but got:\n%v\nDifference:\n%v", expectedActionSet, actionSet, expectedActionSet.Difference(actionSet))
|
2015-01-26 18:43:55 +00:00
|
|
|
}
|
2016-04-08 20:32:08 +00:00
|
|
|
decimatedActions := removeListWatch(kubeClient.Actions())
|
|
|
|
lastActionIndex := len(decimatedActions) - 1
|
|
|
|
usage := decimatedActions[lastActionIndex].(testcore.UpdateAction).GetObject().(*api.ResourceQuota)
|
2015-01-26 18:43:55 +00:00
|
|
|
|
2016-02-22 16:15:09 +00:00
|
|
|
if usage.Name != resourceQuotaBestEffort.Name {
|
|
|
|
t.Errorf("Incremented the wrong quota, expected %v, actual %v", resourceQuotaBestEffort.Name, usage.Name)
|
2015-01-26 18:43:55 +00:00
|
|
|
}
|
2015-04-08 21:03:56 +00:00
|
|
|
|
2016-02-22 16:15:09 +00:00
|
|
|
expectedUsage := api.ResourceQuota{
|
|
|
|
Status: api.ResourceQuotaStatus{
|
|
|
|
Hard: api.ResourceList{
|
|
|
|
api.ResourcePods: resource.MustParse("5"),
|
|
|
|
},
|
|
|
|
Used: api.ResourceList{
|
|
|
|
api.ResourcePods: resource.MustParse("4"),
|
2015-04-08 21:03:56 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2016-02-22 16:15:09 +00:00
|
|
|
for k, v := range expectedUsage.Status.Used {
|
|
|
|
actual := usage.Status.Used[k]
|
|
|
|
actualValue := actual.String()
|
|
|
|
expectedValue := v.String()
|
|
|
|
if expectedValue != actualValue {
|
|
|
|
t.Errorf("Usage Used: Key: %v, Expected: %v, Actual: %v", k, expectedValue, actualValue)
|
|
|
|
}
|
2015-04-08 21:03:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-08 20:32:08 +00:00
|
|
|
func removeListWatch(in []testcore.Action) []testcore.Action {
|
|
|
|
decimatedActions := []testcore.Action{}
|
|
|
|
// list and watch resource quota is done to maintain our cache, so that's expected. Remove them from results
|
|
|
|
for i := range in {
|
|
|
|
if in[i].Matches("list", "resourcequotas") || in[i].Matches("watch", "resourcequotas") {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
decimatedActions = append(decimatedActions, in[i])
|
|
|
|
}
|
|
|
|
return decimatedActions
|
|
|
|
}
|
|
|
|
|
2016-02-22 16:15:09 +00:00
|
|
|
// TestAdmitBestEffortQuotaLimitIgnoresBurstable validates that a besteffort quota does not match a resource
|
|
|
|
// guaranteed pod.
|
|
|
|
func TestAdmitBestEffortQuotaLimitIgnoresBurstable(t *testing.T) {
|
|
|
|
resourceQuota := &api.ResourceQuota{
|
2017-01-17 03:38:19 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "quota-besteffort", Namespace: "test", ResourceVersion: "124"},
|
2016-02-22 16:15:09 +00:00
|
|
|
Spec: api.ResourceQuotaSpec{
|
|
|
|
Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeBestEffort},
|
|
|
|
},
|
|
|
|
Status: api.ResourceQuotaStatus{
|
|
|
|
Hard: api.ResourceList{
|
|
|
|
api.ResourcePods: resource.MustParse("5"),
|
|
|
|
},
|
|
|
|
Used: api.ResourceList{
|
|
|
|
api.ResourcePods: resource.MustParse("3"),
|
2015-04-08 21:03:56 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2016-02-22 16:15:09 +00:00
|
|
|
kubeClient := fake.NewSimpleClientset(resourceQuota)
|
|
|
|
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc})
|
2016-05-10 20:00:24 +00:00
|
|
|
stopCh := make(chan struct{})
|
|
|
|
defer close(stopCh)
|
2016-05-26 13:59:48 +00:00
|
|
|
|
|
|
|
quotaAccessor, _ := newQuotaAccessor(kubeClient)
|
|
|
|
quotaAccessor.indexer = indexer
|
|
|
|
go quotaAccessor.Run(stopCh)
|
2016-11-18 20:54:08 +00:00
|
|
|
evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, 5, stopCh)
|
2016-05-26 13:59:48 +00:00
|
|
|
|
2016-02-22 16:15:09 +00:00
|
|
|
handler := "aAdmission{
|
2016-04-08 20:32:08 +00:00
|
|
|
Handler: admission.NewHandler(admission.Create, admission.Update),
|
|
|
|
evaluator: evaluator,
|
2016-02-22 16:15:09 +00:00
|
|
|
}
|
2016-04-08 20:32:08 +00:00
|
|
|
indexer.Add(resourceQuota)
|
2016-02-22 16:15:09 +00:00
|
|
|
newPod := validPod("allowed-pod", 1, getResourceRequirements(getResourceList("100m", "1Gi"), getResourceList("", "")))
|
2016-04-29 01:21:35 +00:00
|
|
|
err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
2016-02-22 16:15:09 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Unexpected error: %v", err)
|
|
|
|
}
|
2016-04-08 20:32:08 +00:00
|
|
|
|
|
|
|
decimatedActions := removeListWatch(kubeClient.Actions())
|
|
|
|
if len(decimatedActions) != 0 {
|
|
|
|
t.Errorf("Expected no client actions because the incoming pod did not match best effort quota: %v", kubeClient.Actions())
|
2015-04-08 21:03:56 +00:00
|
|
|
}
|
|
|
|
}
|
2015-11-10 17:01:08 +00:00
|
|
|
|
2016-02-22 16:15:09 +00:00
|
|
|
func TestHasUsageStats(t *testing.T) {
|
|
|
|
testCases := map[string]struct {
|
|
|
|
a api.ResourceQuota
|
|
|
|
expected bool
|
2015-11-10 17:01:08 +00:00
|
|
|
}{
|
2016-02-22 16:15:09 +00:00
|
|
|
"empty": {
|
|
|
|
a: api.ResourceQuota{Status: api.ResourceQuotaStatus{Hard: api.ResourceList{}}},
|
|
|
|
expected: true,
|
2015-11-10 17:01:08 +00:00
|
|
|
},
|
2016-02-22 16:15:09 +00:00
|
|
|
"hard-only": {
|
|
|
|
a: api.ResourceQuota{
|
|
|
|
Status: api.ResourceQuotaStatus{
|
|
|
|
Hard: api.ResourceList{
|
|
|
|
api.ResourceMemory: resource.MustParse("1Gi"),
|
|
|
|
},
|
|
|
|
Used: api.ResourceList{},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expected: false,
|
2015-11-10 17:01:08 +00:00
|
|
|
},
|
2016-02-22 16:15:09 +00:00
|
|
|
"hard-used": {
|
|
|
|
a: api.ResourceQuota{
|
|
|
|
Status: api.ResourceQuotaStatus{
|
|
|
|
Hard: api.ResourceList{
|
|
|
|
api.ResourceMemory: resource.MustParse("1Gi"),
|
|
|
|
},
|
|
|
|
Used: api.ResourceList{
|
|
|
|
api.ResourceMemory: resource.MustParse("500Mi"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expected: true,
|
2015-11-10 17:01:08 +00:00
|
|
|
},
|
|
|
|
}
|
2016-02-22 16:15:09 +00:00
|
|
|
for testName, testCase := range testCases {
|
|
|
|
if result := hasUsageStats(&testCase.a); result != testCase.expected {
|
|
|
|
t.Errorf("%s expected: %v, actual: %v", testName, testCase.expected, result)
|
2015-11-10 17:01:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-03-11 14:40:01 +00:00
|
|
|
|
|
|
|
// TestAdmissionSetsMissingNamespace verifies that if an object lacks a
|
|
|
|
// namespace, it will be set.
|
|
|
|
func TestAdmissionSetsMissingNamespace(t *testing.T) {
|
|
|
|
namespace := "test"
|
|
|
|
resourceQuota := &api.ResourceQuota{
|
2017-01-17 03:38:19 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "quota", Namespace: namespace, ResourceVersion: "124"},
|
2016-03-11 14:40:01 +00:00
|
|
|
Status: api.ResourceQuotaStatus{
|
|
|
|
Hard: api.ResourceList{
|
2016-12-07 20:44:16 +00:00
|
|
|
api.ResourcePods: resource.MustParse("3"),
|
2016-03-11 14:40:01 +00:00
|
|
|
},
|
|
|
|
Used: api.ResourceList{
|
2016-12-07 20:44:16 +00:00
|
|
|
api.ResourcePods: resource.MustParse("1"),
|
2016-03-11 14:40:01 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
kubeClient := fake.NewSimpleClientset(resourceQuota)
|
|
|
|
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc})
|
|
|
|
|
2016-12-07 20:44:16 +00:00
|
|
|
// create a dummy evaluator so we can trigger quota
|
|
|
|
podEvaluator := &generic.ObjectCountEvaluator{
|
|
|
|
AllowCreateOnUpdate: false,
|
|
|
|
InternalGroupKind: api.Kind("Pod"),
|
|
|
|
ResourceName: api.ResourcePods,
|
2016-03-11 14:40:01 +00:00
|
|
|
}
|
|
|
|
registry := &generic.GenericRegistry{
|
2016-11-21 02:55:31 +00:00
|
|
|
InternalEvaluators: map[schema.GroupKind]quota.Evaluator{
|
2016-03-11 14:40:01 +00:00
|
|
|
podEvaluator.GroupKind(): podEvaluator,
|
|
|
|
},
|
|
|
|
}
|
2016-05-10 20:00:24 +00:00
|
|
|
stopCh := make(chan struct{})
|
|
|
|
defer close(stopCh)
|
2016-05-26 13:59:48 +00:00
|
|
|
|
|
|
|
quotaAccessor, _ := newQuotaAccessor(kubeClient)
|
|
|
|
quotaAccessor.indexer = indexer
|
|
|
|
go quotaAccessor.Run(stopCh)
|
2016-11-18 20:54:08 +00:00
|
|
|
evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, 5, stopCh)
|
2016-05-26 13:59:48 +00:00
|
|
|
evaluator.(*quotaEvaluator).registry = registry
|
|
|
|
|
2016-03-11 14:40:01 +00:00
|
|
|
handler := "aAdmission{
|
2016-04-08 20:32:08 +00:00
|
|
|
Handler: admission.NewHandler(admission.Create, admission.Update),
|
|
|
|
evaluator: evaluator,
|
2016-03-11 14:40:01 +00:00
|
|
|
}
|
2016-04-08 20:32:08 +00:00
|
|
|
indexer.Add(resourceQuota)
|
2016-03-11 14:40:01 +00:00
|
|
|
newPod := validPod("pod-without-namespace", 1, getResourceRequirements(getResourceList("1", "2Gi"), getResourceList("", "")))
|
|
|
|
|
|
|
|
// unset the namespace
|
|
|
|
newPod.ObjectMeta.Namespace = ""
|
|
|
|
|
2016-04-29 01:21:35 +00:00
|
|
|
err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
2016-03-11 14:40:01 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Got unexpected error: %v", err)
|
|
|
|
}
|
|
|
|
if newPod.Namespace != namespace {
|
|
|
|
t.Errorf("Got unexpected pod namespace: %q != %q", newPod.Namespace, namespace)
|
|
|
|
}
|
|
|
|
}
|
2016-08-10 20:35:22 +00:00
|
|
|
|
|
|
|
// TestAdmitRejectsNegativeUsage verifies that usage for any measured resource cannot be negative.
|
|
|
|
func TestAdmitRejectsNegativeUsage(t *testing.T) {
|
|
|
|
resourceQuota := &api.ResourceQuota{
|
2017-01-17 03:38:19 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "quota", Namespace: "test", ResourceVersion: "124"},
|
2016-08-10 20:35:22 +00:00
|
|
|
Status: api.ResourceQuotaStatus{
|
|
|
|
Hard: api.ResourceList{
|
|
|
|
api.ResourcePersistentVolumeClaims: resource.MustParse("3"),
|
|
|
|
api.ResourceRequestsStorage: resource.MustParse("100Gi"),
|
|
|
|
},
|
|
|
|
Used: api.ResourceList{
|
|
|
|
api.ResourcePersistentVolumeClaims: resource.MustParse("1"),
|
|
|
|
api.ResourceRequestsStorage: resource.MustParse("10Gi"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
kubeClient := fake.NewSimpleClientset(resourceQuota)
|
|
|
|
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc})
|
|
|
|
stopCh := make(chan struct{})
|
|
|
|
defer close(stopCh)
|
|
|
|
|
|
|
|
quotaAccessor, _ := newQuotaAccessor(kubeClient)
|
|
|
|
quotaAccessor.indexer = indexer
|
|
|
|
go quotaAccessor.Run(stopCh)
|
2016-11-18 20:54:08 +00:00
|
|
|
evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, 5, stopCh)
|
2016-08-10 20:35:22 +00:00
|
|
|
|
|
|
|
handler := "aAdmission{
|
|
|
|
Handler: admission.NewHandler(admission.Create, admission.Update),
|
|
|
|
evaluator: evaluator,
|
|
|
|
}
|
|
|
|
indexer.Add(resourceQuota)
|
|
|
|
// verify quota rejects negative pvc storage requests
|
|
|
|
newPvc := validPersistentVolumeClaim("not-allowed-pvc", getResourceRequirements(api.ResourceList{api.ResourceStorage: resource.MustParse("-1Gi")}, api.ResourceList{}))
|
|
|
|
err := handler.Admit(admission.NewAttributesRecord(newPvc, nil, api.Kind("PersistentVolumeClaim").WithVersion("version"), newPvc.Namespace, newPvc.Name, api.Resource("persistentvolumeclaims").WithVersion("version"), "", admission.Create, nil))
|
|
|
|
if err == nil {
|
|
|
|
t.Errorf("Expected an error because the pvc has negative storage usage")
|
|
|
|
}
|
|
|
|
|
|
|
|
// verify quota accepts non-negative pvc storage requests
|
|
|
|
newPvc = validPersistentVolumeClaim("not-allowed-pvc", getResourceRequirements(api.ResourceList{api.ResourceStorage: resource.MustParse("1Gi")}, api.ResourceList{}))
|
|
|
|
err = handler.Admit(admission.NewAttributesRecord(newPvc, nil, api.Kind("PersistentVolumeClaim").WithVersion("version"), newPvc.Namespace, newPvc.Name, api.Resource("persistentvolumeclaims").WithVersion("version"), "", admission.Create, nil))
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Unexpected error: %v", err)
|
|
|
|
}
|
|
|
|
}
|
2016-08-17 19:02:33 +00:00
|
|
|
|
|
|
|
// TestAdmitWhenUnrelatedResourceExceedsQuota verifies that if resource X exceeds quota, it does not prohibit resource Y from admission.
|
|
|
|
func TestAdmitWhenUnrelatedResourceExceedsQuota(t *testing.T) {
|
|
|
|
resourceQuota := &api.ResourceQuota{
|
2017-01-17 03:38:19 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "quota", Namespace: "test", ResourceVersion: "124"},
|
2016-08-17 19:02:33 +00:00
|
|
|
Status: api.ResourceQuotaStatus{
|
|
|
|
Hard: api.ResourceList{
|
|
|
|
api.ResourceServices: resource.MustParse("3"),
|
|
|
|
api.ResourcePods: resource.MustParse("4"),
|
|
|
|
},
|
|
|
|
Used: api.ResourceList{
|
|
|
|
api.ResourceServices: resource.MustParse("4"),
|
|
|
|
api.ResourcePods: resource.MustParse("1"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
kubeClient := fake.NewSimpleClientset(resourceQuota)
|
|
|
|
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc})
|
|
|
|
stopCh := make(chan struct{})
|
|
|
|
defer close(stopCh)
|
|
|
|
|
|
|
|
quotaAccessor, _ := newQuotaAccessor(kubeClient)
|
|
|
|
quotaAccessor.indexer = indexer
|
|
|
|
go quotaAccessor.Run(stopCh)
|
2016-11-18 20:54:08 +00:00
|
|
|
evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, 5, stopCh)
|
2016-08-17 19:02:33 +00:00
|
|
|
|
|
|
|
handler := "aAdmission{
|
|
|
|
Handler: admission.NewHandler(admission.Create, admission.Update),
|
|
|
|
evaluator: evaluator,
|
|
|
|
}
|
|
|
|
indexer.Add(resourceQuota)
|
|
|
|
|
|
|
|
// create a pod that should pass existing quota
|
|
|
|
newPod := validPod("allowed-pod", 1, getResourceRequirements(getResourceList("", ""), getResourceList("", "")))
|
|
|
|
err := handler.Admit(admission.NewAttributesRecord(newPod, nil, api.Kind("Pod").WithVersion("version"), newPod.Namespace, newPod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Unexpected error: %v", err)
|
|
|
|
}
|
|
|
|
}
|