k8s.io/apiserver@v0.31.1/pkg/admission/plugin/resourcequota/admission_test.go (about) 1 /* 2 Copyright 2020 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package resourcequota 18 19 import ( 20 "context" 21 "errors" 22 "testing" 23 24 corev1 "k8s.io/api/core/v1" 25 "k8s.io/apimachinery/pkg/api/resource" 26 "k8s.io/apimachinery/pkg/runtime/schema" 27 "k8s.io/apiserver/pkg/admission" 28 v1 "k8s.io/apiserver/pkg/admission/plugin/resourcequota/apis/resourcequota/v1" 29 "k8s.io/apiserver/pkg/quota/v1/generic" 30 ) 31 32 func TestPrettyPrint(t *testing.T) { 33 toResourceList := func(resources map[corev1.ResourceName]string) corev1.ResourceList { 34 resourceList := corev1.ResourceList{} 35 for key, value := range resources { 36 resourceList[key] = resource.MustParse(value) 37 } 38 return resourceList 39 } 40 testCases := []struct { 41 input corev1.ResourceList 42 expected string 43 }{ 44 { 45 input: toResourceList(map[corev1.ResourceName]string{ 46 corev1.ResourceCPU: "100m", 47 }), 48 expected: "cpu=100m", 49 }, 50 { 51 input: toResourceList(map[corev1.ResourceName]string{ 52 corev1.ResourcePods: "10", 53 corev1.ResourceServices: "10", 54 corev1.ResourceReplicationControllers: "10", 55 corev1.ResourceServicesNodePorts: "10", 56 corev1.ResourceRequestsCPU: "100m", 57 corev1.ResourceRequestsMemory: "100Mi", 58 corev1.ResourceLimitsCPU: "100m", 59 corev1.ResourceLimitsMemory: "100Mi", 60 }), 61 expected: "limits.cpu=100m,limits.memory=100Mi,pods=10,replicationcontrollers=10,requests.cpu=100m,requests.memory=100Mi,services=10,services.nodeports=10", 62 }, 63 } 64 for i, testCase := range testCases { 65 result := prettyPrint(testCase.input) 66 if result != testCase.expected { 67 t.Errorf("Pretty print did not give stable sorted output[%d], expected %v, but got %v", i, testCase.expected, result) 68 } 69 } 70 } 71 72 func TestHasUsageStats(t *testing.T) { 73 testCases := map[string]struct { 74 a corev1.ResourceQuota 75 relevant []corev1.ResourceName 76 expected bool 77 }{ 78 "empty": { 79 a: corev1.ResourceQuota{Status: corev1.ResourceQuotaStatus{Hard: corev1.ResourceList{}}}, 80 relevant: []corev1.ResourceName{corev1.ResourceMemory}, 81 expected: true, 82 }, 83 "hard-only": { 84 a: corev1.ResourceQuota{ 85 Status: corev1.ResourceQuotaStatus{ 86 Hard: corev1.ResourceList{ 87 corev1.ResourceMemory: resource.MustParse("1Gi"), 88 }, 89 Used: corev1.ResourceList{}, 90 }, 91 }, 92 relevant: []corev1.ResourceName{corev1.ResourceMemory}, 93 expected: false, 94 }, 95 "hard-used": { 96 a: corev1.ResourceQuota{ 97 Status: corev1.ResourceQuotaStatus{ 98 Hard: corev1.ResourceList{ 99 corev1.ResourceMemory: resource.MustParse("1Gi"), 100 }, 101 Used: corev1.ResourceList{ 102 corev1.ResourceMemory: resource.MustParse("500Mi"), 103 }, 104 }, 105 }, 106 relevant: []corev1.ResourceName{corev1.ResourceMemory}, 107 expected: true, 108 }, 109 "hard-used-relevant": { 110 a: corev1.ResourceQuota{ 111 Status: corev1.ResourceQuotaStatus{ 112 Hard: corev1.ResourceList{ 113 corev1.ResourceMemory: resource.MustParse("1Gi"), 114 corev1.ResourcePods: resource.MustParse("1"), 115 }, 116 Used: corev1.ResourceList{ 117 corev1.ResourceMemory: resource.MustParse("500Mi"), 118 }, 119 }, 120 }, 121 relevant: []corev1.ResourceName{corev1.ResourceMemory}, 122 expected: true, 123 }, 124 } 125 for testName, testCase := range testCases { 126 if result := hasUsageStats(&testCase.a, testCase.relevant); result != testCase.expected { 127 t.Errorf("%s expected: %v, actual: %v", testName, testCase.expected, result) 128 } 129 } 130 } 131 132 type fakeEvaluator struct{} 133 134 func (fakeEvaluator) Evaluate(a admission.Attributes) error { 135 return errors.New("should not be called") 136 } 137 138 func TestExcludedOperations(t *testing.T) { 139 a := &QuotaAdmission{ 140 evaluator: fakeEvaluator{}, 141 } 142 testCases := []struct { 143 desc string 144 attr admission.Attributes 145 }{ 146 { 147 "subresource", 148 admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, "namespace", "name", schema.GroupVersionResource{}, "subresource", admission.Create, nil, false, nil), 149 }, 150 { 151 "non-namespaced resource", 152 admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, "", "namespace", schema.GroupVersionResource{}, "", admission.Create, nil, false, nil), 153 }, 154 { 155 "namespace creation", 156 admission.NewAttributesRecord(nil, nil, v1.SchemeGroupVersion.WithKind("Namespace"), "namespace", "namespace", schema.GroupVersionResource{}, "", admission.Create, nil, false, nil), 157 }, 158 } 159 for _, test := range testCases { 160 if err := a.Validate(context.TODO(), test.attr, nil); err != nil { 161 t.Errorf("Test case: %q. Expected no error but got: %v", test.desc, err) 162 } 163 } 164 } 165 166 func TestInitializationOrder(t *testing.T) { 167 a := &QuotaAdmission{} 168 169 qca := generic.NewConfiguration(nil, nil) 170 a.SetQuotaConfiguration(qca) 171 172 if err := a.ValidateInitialization(); err != stopChUnconfiguredErr { 173 t.Errorf("unexpected error: %v", err) 174 } 175 }