diff --git a/pkg/client/fake.go b/pkg/client/fake.go index 0c4c209fa44..42bd2e5f36d 100644 --- a/pkg/client/fake.go +++ b/pkg/client/fake.go @@ -36,6 +36,7 @@ type FakeAction struct { type Fake struct { Actions []FakeAction PodsList api.PodList + CtrlList api.ReplicationControllerList Ctrl api.ReplicationController ServiceList api.ServiceList EndpointsList api.EndpointsList diff --git a/pkg/client/fake_replication_controllers.go b/pkg/client/fake_replication_controllers.go index 589cac62b79..17a1f02f9df 100644 --- a/pkg/client/fake_replication_controllers.go +++ b/pkg/client/fake_replication_controllers.go @@ -31,7 +31,7 @@ type FakeReplicationControllers struct { func (c *FakeReplicationControllers) List(selector labels.Selector) (*api.ReplicationControllerList, error) { c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "list-controllers"}) - return &api.ReplicationControllerList{}, nil + return api.Scheme.CopyOrDie(&c.Fake.CtrlList).(*api.ReplicationControllerList), nil } func (c *FakeReplicationControllers) Get(name string) (*api.ReplicationController, error) { diff --git a/plugin/pkg/admission/resourcequota/admission.go b/plugin/pkg/admission/resourcequota/admission.go index dfd1c88cd91..3093f95f5fe 100644 --- a/plugin/pkg/admission/resourcequota/admission.go +++ b/plugin/pkg/admission/resourcequota/admission.go @@ -32,7 +32,7 @@ import ( func init() { admission.RegisterPlugin("ResourceQuota", func(client client.Interface, config io.Reader) (admission.Interface, error) { - return "a{client: client}, nil + return NewResourceQuota(client), nil }) } @@ -40,6 +40,10 @@ type quota struct { client client.Interface } +func NewResourceQuota(client client.Interface) admission.Interface { + return "a{client: client} +} + var kindToResourceName = map[string]api.ResourceName{ "pods": api.ResourcePods, "services": api.ResourceServices, diff --git a/plugin/pkg/admission/resourcequota/admission_test.go b/plugin/pkg/admission/resourcequota/admission_test.go index 45930005154..77b06ce4377 100644 --- a/plugin/pkg/admission/resourcequota/admission_test.go +++ b/plugin/pkg/admission/resourcequota/admission_test.go @@ -15,3 +15,350 @@ limitations under the License. */ package resourcequota + +import ( + "testing" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/admission" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource" + "github.com/GoogleCloudPlatform/kubernetes/pkg/client" +) + +func TestAdmissionIgnoresDelete(t *testing.T) { + namespace := "default" + handler := NewResourceQuota(&client.Fake{}) + err := handler.Admit(admission.NewAttributesRecord(nil, namespace, "pods", "DELETE")) + if err != nil { + t.Errorf("ResourceQuota should admit all deletes", err) + } +} + +func TestIncrementUsagePods(t *testing.T) { + namespace := "default" + client := &client.Fake{ + PodsList: api.PodList{ + Items: []api.Pod{ + { + ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "vol"}}, + Containers: []api.Container{{Name: "ctr", Image: "image", Memory: resource.MustParse("1Gi"), CPU: resource.MustParse("100m")}}, + }, + }, + }, + }, + } + status := &api.ResourceQuotaStatus{ + Hard: api.ResourceList{}, + Used: api.ResourceList{}, + } + r := api.ResourcePods + status.Hard[r] = resource.MustParse("2") + status.Used[r] = resource.MustParse("1") + dirty, err := IncrementUsage(admission.NewAttributesRecord(&api.Pod{}, namespace, "pods", "CREATE"), status, client) + if err != nil { + t.Errorf("Unexpected error", err) + } + if !dirty { + t.Errorf("Expected the status to get incremented, therefore should have been dirty") + } + quantity := status.Used[r] + if quantity.Value() != int64(2) { + t.Errorf("Expected new item count to be 2, but was %s", quantity.String()) + } +} + +func TestIncrementUsageMemory(t *testing.T) { + namespace := "default" + client := &client.Fake{ + PodsList: api.PodList{ + Items: []api.Pod{ + { + ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "vol"}}, + Containers: []api.Container{{Name: "ctr", Image: "image", Memory: resource.MustParse("1Gi"), CPU: resource.MustParse("100m")}}, + }, + }, + }, + }, + } + status := &api.ResourceQuotaStatus{ + Hard: api.ResourceList{}, + Used: api.ResourceList{}, + } + r := api.ResourceMemory + status.Hard[r] = resource.MustParse("2Gi") + status.Used[r] = resource.MustParse("1Gi") + + newPod := &api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "vol"}}, + Containers: []api.Container{{Name: "ctr", Image: "image", Memory: resource.MustParse("1Gi"), CPU: resource.MustParse("100m")}}, + }} + dirty, err := IncrementUsage(admission.NewAttributesRecord(newPod, namespace, "pods", "CREATE"), status, client) + if err != nil { + t.Errorf("Unexpected error", err) + } + if !dirty { + t.Errorf("Expected the status to get incremented, therefore should have been dirty") + } + expectedVal := resource.MustParse("2Gi") + quantity := status.Used[r] + if quantity.Value() != expectedVal.Value() { + t.Errorf("Expected %v was %v", expectedVal.Value(), quantity.Value()) + } +} + +func TestExceedUsageMemory(t *testing.T) { + namespace := "default" + client := &client.Fake{ + PodsList: api.PodList{ + Items: []api.Pod{ + { + ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "vol"}}, + Containers: []api.Container{{Name: "ctr", Image: "image", Memory: resource.MustParse("1Gi"), CPU: resource.MustParse("100m")}}, + }, + }, + }, + }, + } + status := &api.ResourceQuotaStatus{ + Hard: api.ResourceList{}, + Used: api.ResourceList{}, + } + r := api.ResourceMemory + status.Hard[r] = resource.MustParse("2Gi") + status.Used[r] = resource.MustParse("1Gi") + + newPod := &api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "vol"}}, + Containers: []api.Container{{Name: "ctr", Image: "image", Memory: resource.MustParse("3Gi"), CPU: resource.MustParse("100m")}}, + }} + _, err := IncrementUsage(admission.NewAttributesRecord(newPod, namespace, "pods", "CREATE"), status, client) + if err == nil { + t.Errorf("Expected memory usage exceeded error") + } +} + +func TestIncrementUsageCPU(t *testing.T) { + namespace := "default" + client := &client.Fake{ + PodsList: api.PodList{ + Items: []api.Pod{ + { + ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "vol"}}, + Containers: []api.Container{{Name: "ctr", Image: "image", Memory: resource.MustParse("1Gi"), CPU: resource.MustParse("100m")}}, + }, + }, + }, + }, + } + status := &api.ResourceQuotaStatus{ + Hard: api.ResourceList{}, + Used: api.ResourceList{}, + } + r := api.ResourceCPU + status.Hard[r] = resource.MustParse("200m") + status.Used[r] = resource.MustParse("100m") + + newPod := &api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "vol"}}, + Containers: []api.Container{{Name: "ctr", Image: "image", Memory: resource.MustParse("1Gi"), CPU: resource.MustParse("100m")}}, + }} + dirty, err := IncrementUsage(admission.NewAttributesRecord(newPod, namespace, "pods", "CREATE"), status, client) + if err != nil { + t.Errorf("Unexpected error", err) + } + if !dirty { + t.Errorf("Expected the status to get incremented, therefore should have been dirty") + } + expectedVal := resource.MustParse("200m") + quantity := status.Used[r] + if quantity.Value() != expectedVal.Value() { + t.Errorf("Expected %v was %v", expectedVal.Value(), quantity.Value()) + } +} + +func TestExceedUsageCPU(t *testing.T) { + namespace := "default" + client := &client.Fake{ + PodsList: api.PodList{ + Items: []api.Pod{ + { + ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "vol"}}, + Containers: []api.Container{{Name: "ctr", Image: "image", Memory: resource.MustParse("1Gi"), CPU: resource.MustParse("100m")}}, + }, + }, + }, + }, + } + status := &api.ResourceQuotaStatus{ + Hard: api.ResourceList{}, + Used: api.ResourceList{}, + } + r := api.ResourceCPU + status.Hard[r] = resource.MustParse("200m") + status.Used[r] = resource.MustParse("100m") + + newPod := &api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "vol"}}, + Containers: []api.Container{{Name: "ctr", Image: "image", Memory: resource.MustParse("1Gi"), CPU: resource.MustParse("500m")}}, + }} + _, err := IncrementUsage(admission.NewAttributesRecord(newPod, namespace, "pods", "CREATE"), status, client) + if err == nil { + t.Errorf("Expected CPU usage exceeded error") + } +} + +func TestExceedUsagePods(t *testing.T) { + namespace := "default" + client := &client.Fake{ + PodsList: api.PodList{ + Items: []api.Pod{ + { + ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "vol"}}, + Containers: []api.Container{{Name: "ctr", Image: "image", Memory: resource.MustParse("1Gi"), CPU: resource.MustParse("100m")}}, + }, + }, + }, + }, + } + status := &api.ResourceQuotaStatus{ + Hard: api.ResourceList{}, + Used: api.ResourceList{}, + } + r := api.ResourcePods + status.Hard[r] = resource.MustParse("1") + status.Used[r] = resource.MustParse("1") + _, err := IncrementUsage(admission.NewAttributesRecord(&api.Pod{}, namespace, "pods", "CREATE"), status, client) + if err == nil { + t.Errorf("Expected error because this would exceed your quota") + } +} + +func TestIncrementUsageServices(t *testing.T) { + namespace := "default" + client := &client.Fake{ + ServiceList: api.ServiceList{ + Items: []api.Service{ + { + ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, + }, + }, + }, + } + status := &api.ResourceQuotaStatus{ + Hard: api.ResourceList{}, + Used: api.ResourceList{}, + } + r := api.ResourceServices + status.Hard[r] = resource.MustParse("2") + status.Used[r] = resource.MustParse("1") + dirty, err := IncrementUsage(admission.NewAttributesRecord(&api.Service{}, namespace, "services", "CREATE"), status, client) + if err != nil { + t.Errorf("Unexpected error", err) + } + if !dirty { + t.Errorf("Expected the status to get incremented, therefore should have been dirty") + } + quantity := status.Used[r] + if quantity.Value() != int64(2) { + t.Errorf("Expected new item count to be 2, but was %s", quantity.String()) + } +} + +func TestExceedUsageServices(t *testing.T) { + namespace := "default" + client := &client.Fake{ + ServiceList: api.ServiceList{ + Items: []api.Service{ + { + ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, + }, + }, + }, + } + status := &api.ResourceQuotaStatus{ + Hard: api.ResourceList{}, + Used: api.ResourceList{}, + } + r := api.ResourceServices + status.Hard[r] = resource.MustParse("1") + status.Used[r] = resource.MustParse("1") + _, err := IncrementUsage(admission.NewAttributesRecord(&api.Service{}, namespace, "services", "CREATE"), status, client) + if err == nil { + t.Errorf("Expected error because this would exceed usage") + } +} + +func TestIncrementUsageReplicationControllers(t *testing.T) { + namespace := "default" + client := &client.Fake{ + CtrlList: api.ReplicationControllerList{ + Items: []api.ReplicationController{ + { + ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, + }, + }, + }, + } + status := &api.ResourceQuotaStatus{ + Hard: api.ResourceList{}, + Used: api.ResourceList{}, + } + r := api.ResourceReplicationControllers + status.Hard[r] = resource.MustParse("2") + status.Used[r] = resource.MustParse("1") + dirty, err := IncrementUsage(admission.NewAttributesRecord(&api.ReplicationController{}, namespace, "replicationControllers", "CREATE"), status, client) + if err != nil { + t.Errorf("Unexpected error", err) + } + if !dirty { + t.Errorf("Expected the status to get incremented, therefore should have been dirty") + } + quantity := status.Used[r] + if quantity.Value() != int64(2) { + t.Errorf("Expected new item count to be 2, but was %s", quantity.String()) + } +} + +func TestExceedUsageReplicationControllers(t *testing.T) { + namespace := "default" + client := &client.Fake{ + CtrlList: api.ReplicationControllerList{ + Items: []api.ReplicationController{ + { + ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace}, + }, + }, + }, + } + status := &api.ResourceQuotaStatus{ + Hard: api.ResourceList{}, + Used: api.ResourceList{}, + } + r := api.ResourceReplicationControllers + status.Hard[r] = resource.MustParse("1") + status.Used[r] = resource.MustParse("1") + _, err := IncrementUsage(admission.NewAttributesRecord(&api.ReplicationController{}, namespace, "replicationControllers", "CREATE"), status, client) + if err == nil { + t.Errorf("Expected error for exceeding hard limits") + } +}