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  }