mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-24 20:24:09 +00:00
Quota admission errors if usage is negative
This commit is contained in:
parent
2fb7cae2be
commit
5cca4b07c6
@ -169,6 +169,18 @@ func IsZero(a api.ResourceList) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsNegative returns the set of resource names that have a negative value.
|
||||||
|
func IsNegative(a api.ResourceList) []api.ResourceName {
|
||||||
|
results := []api.ResourceName{}
|
||||||
|
zero := resource.MustParse("0")
|
||||||
|
for k, v := range a {
|
||||||
|
if v.Cmp(zero) < 0 {
|
||||||
|
results = append(results, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
// ToSet takes a list of resource names and converts to a string set
|
// ToSet takes a list of resource names and converts to a string set
|
||||||
func ToSet(resourceNames []api.ResourceName) sets.String {
|
func ToSet(resourceNames []api.ResourceName) sets.String {
|
||||||
result := sets.NewString()
|
result := sets.NewString()
|
||||||
|
@ -256,3 +256,37 @@ func TestIsZero(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsNegative(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
a api.ResourceList
|
||||||
|
expected []api.ResourceName
|
||||||
|
}{
|
||||||
|
"empty": {
|
||||||
|
a: api.ResourceList{},
|
||||||
|
expected: []api.ResourceName{},
|
||||||
|
},
|
||||||
|
"some-negative": {
|
||||||
|
a: api.ResourceList{
|
||||||
|
api.ResourceCPU: resource.MustParse("-10"),
|
||||||
|
api.ResourceMemory: resource.MustParse("0"),
|
||||||
|
},
|
||||||
|
expected: []api.ResourceName{api.ResourceCPU},
|
||||||
|
},
|
||||||
|
"all-negative": {
|
||||||
|
a: api.ResourceList{
|
||||||
|
api.ResourceCPU: resource.MustParse("-200m"),
|
||||||
|
api.ResourceMemory: resource.MustParse("-1Gi"),
|
||||||
|
},
|
||||||
|
expected: []api.ResourceName{api.ResourceCPU, api.ResourceMemory},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for testName, testCase := range testCases {
|
||||||
|
actual := IsNegative(testCase.a)
|
||||||
|
actualSet := ToSet(actual)
|
||||||
|
expectedSet := ToSet(testCase.expected)
|
||||||
|
if !actualSet.Equal(expectedSet) {
|
||||||
|
t.Errorf("%s expected: %v, actual: %v", testName, expectedSet, actualSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -73,6 +73,15 @@ func validPod(name string, numContainers int, resources api.ResourceRequirements
|
|||||||
return pod
|
return pod
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validPersistentVolumeClaim(name string, resources api.ResourceRequirements) *api.PersistentVolumeClaim {
|
||||||
|
return &api.PersistentVolumeClaim{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: name, Namespace: "test"},
|
||||||
|
Spec: api.PersistentVolumeClaimSpec{
|
||||||
|
Resources: resources,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPrettyPrint(t *testing.T) {
|
func TestPrettyPrint(t *testing.T) {
|
||||||
toResourceList := func(resources map[api.ResourceName]string) api.ResourceList {
|
toResourceList := func(resources map[api.ResourceName]string) api.ResourceList {
|
||||||
resourceList := api.ResourceList{}
|
resourceList := api.ResourceList{}
|
||||||
@ -871,3 +880,49 @@ func TestAdmissionSetsMissingNamespace(t *testing.T) {
|
|||||||
t.Errorf("Got unexpected pod namespace: %q != %q", newPod.Namespace, namespace)
|
t.Errorf("Got unexpected pod namespace: %q != %q", newPod.Namespace, namespace)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestAdmitRejectsNegativeUsage verifies that usage for any measured resource cannot be negative.
|
||||||
|
func TestAdmitRejectsNegativeUsage(t *testing.T) {
|
||||||
|
resourceQuota := &api.ResourceQuota{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: "quota", Namespace: "test", ResourceVersion: "124"},
|
||||||
|
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)
|
||||||
|
evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(kubeClient), nil, 5, stopCh)
|
||||||
|
|
||||||
|
defer utilruntime.HandleCrash()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -358,6 +358,12 @@ func (e *quotaEvaluator) checkRequest(quotas []api.ResourceQuota, a admission.At
|
|||||||
// on updates, we need to subtract the previous measured usage
|
// on updates, we need to subtract the previous measured usage
|
||||||
// if usage shows no change, just return since it has no impact on quota
|
// if usage shows no change, just return since it has no impact on quota
|
||||||
deltaUsage := evaluator.Usage(inputObject)
|
deltaUsage := evaluator.Usage(inputObject)
|
||||||
|
|
||||||
|
// ensure that usage for input object is never negative (this would mean a resource made a negative resource requirement)
|
||||||
|
if negativeUsage := quota.IsNegative(deltaUsage); len(negativeUsage) > 0 {
|
||||||
|
return nil, admission.NewForbidden(a, fmt.Errorf("quota usage is negative for resource(s): %s", prettyPrintResourceNames(negativeUsage)))
|
||||||
|
}
|
||||||
|
|
||||||
if admission.Update == op {
|
if admission.Update == op {
|
||||||
prevItem := a.GetOldObject()
|
prevItem := a.GetOldObject()
|
||||||
if prevItem == nil {
|
if prevItem == nil {
|
||||||
@ -499,6 +505,15 @@ func prettyPrint(item api.ResourceList) string {
|
|||||||
return strings.Join(parts, ",")
|
return strings.Join(parts, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func prettyPrintResourceNames(a []api.ResourceName) string {
|
||||||
|
values := []string{}
|
||||||
|
for _, value := range a {
|
||||||
|
values = append(values, string(value))
|
||||||
|
}
|
||||||
|
sort.Strings(values)
|
||||||
|
return strings.Join(values, ",")
|
||||||
|
}
|
||||||
|
|
||||||
// hasUsageStats returns true if for each hard constraint there is a value for its current usage
|
// hasUsageStats returns true if for each hard constraint there is a value for its current usage
|
||||||
func hasUsageStats(resourceQuota *api.ResourceQuota) bool {
|
func hasUsageStats(resourceQuota *api.ResourceQuota) bool {
|
||||||
for resourceName := range resourceQuota.Status.Hard {
|
for resourceName := range resourceQuota.Status.Hard {
|
||||||
|
Loading…
Reference in New Issue
Block a user