k8s.io/kubernetes@v1.29.3/pkg/quota/v1/evaluator/core/services.go (about) 1 /* 2 Copyright 2016 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 core 18 19 import ( 20 "fmt" 21 22 corev1 "k8s.io/api/core/v1" 23 "k8s.io/apimachinery/pkg/api/resource" 24 "k8s.io/apimachinery/pkg/runtime" 25 "k8s.io/apimachinery/pkg/runtime/schema" 26 "k8s.io/apiserver/pkg/admission" 27 quota "k8s.io/apiserver/pkg/quota/v1" 28 "k8s.io/apiserver/pkg/quota/v1/generic" 29 api "k8s.io/kubernetes/pkg/apis/core" 30 k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" 31 ) 32 33 // the name used for object count quota 34 var serviceObjectCountName = generic.ObjectCountQuotaResourceNameFor(corev1.SchemeGroupVersion.WithResource("services").GroupResource()) 35 36 // serviceResources are the set of resources managed by quota associated with services. 37 var serviceResources = []corev1.ResourceName{ 38 serviceObjectCountName, 39 corev1.ResourceServices, 40 corev1.ResourceServicesNodePorts, 41 corev1.ResourceServicesLoadBalancers, 42 } 43 44 // NewServiceEvaluator returns an evaluator that can evaluate services. 45 func NewServiceEvaluator(f quota.ListerForResourceFunc) quota.Evaluator { 46 listFuncByNamespace := generic.ListResourceUsingListerFunc(f, corev1.SchemeGroupVersion.WithResource("services")) 47 serviceEvaluator := &serviceEvaluator{listFuncByNamespace: listFuncByNamespace} 48 return serviceEvaluator 49 } 50 51 // serviceEvaluator knows how to measure usage for services. 52 type serviceEvaluator struct { 53 // knows how to list items by namespace 54 listFuncByNamespace generic.ListFuncByNamespace 55 } 56 57 // Constraints verifies that all required resources are present on the item 58 func (p *serviceEvaluator) Constraints(required []corev1.ResourceName, item runtime.Object) error { 59 // this is a no-op for services 60 return nil 61 } 62 63 // GroupResource that this evaluator tracks 64 func (p *serviceEvaluator) GroupResource() schema.GroupResource { 65 return corev1.SchemeGroupVersion.WithResource("services").GroupResource() 66 } 67 68 // Handles returns true of the evaluator should handle the specified operation. 69 func (p *serviceEvaluator) Handles(a admission.Attributes) bool { 70 operation := a.GetOperation() 71 // We handle create and update because a service type can change. 72 return admission.Create == operation || admission.Update == operation 73 } 74 75 // Matches returns true if the evaluator matches the specified quota with the provided input item 76 func (p *serviceEvaluator) Matches(resourceQuota *corev1.ResourceQuota, item runtime.Object) (bool, error) { 77 return generic.Matches(resourceQuota, item, p.MatchingResources, generic.MatchesNoScopeFunc) 78 } 79 80 // MatchingResources takes the input specified list of resources and returns the set of resources it matches. 81 func (p *serviceEvaluator) MatchingResources(input []corev1.ResourceName) []corev1.ResourceName { 82 return quota.Intersection(input, serviceResources) 83 } 84 85 // MatchingScopes takes the input specified list of scopes and input object. Returns the set of scopes resource matches. 86 func (p *serviceEvaluator) MatchingScopes(item runtime.Object, scopes []corev1.ScopedResourceSelectorRequirement) ([]corev1.ScopedResourceSelectorRequirement, error) { 87 return []corev1.ScopedResourceSelectorRequirement{}, nil 88 } 89 90 // UncoveredQuotaScopes takes the input matched scopes which are limited by configuration and the matched quota scopes. 91 // It returns the scopes which are in limited scopes but don't have a corresponding covering quota scope 92 func (p *serviceEvaluator) UncoveredQuotaScopes(limitedScopes []corev1.ScopedResourceSelectorRequirement, matchedQuotaScopes []corev1.ScopedResourceSelectorRequirement) ([]corev1.ScopedResourceSelectorRequirement, error) { 93 return []corev1.ScopedResourceSelectorRequirement{}, nil 94 } 95 96 // convert the input object to an internal service object or error. 97 func toExternalServiceOrError(obj runtime.Object) (*corev1.Service, error) { 98 svc := &corev1.Service{} 99 switch t := obj.(type) { 100 case *corev1.Service: 101 svc = t 102 case *api.Service: 103 if err := k8s_api_v1.Convert_core_Service_To_v1_Service(t, svc, nil); err != nil { 104 return nil, err 105 } 106 default: 107 return nil, fmt.Errorf("expect *api.Service or *v1.Service, got %v", t) 108 } 109 return svc, nil 110 } 111 112 // Usage knows how to measure usage associated with services 113 func (p *serviceEvaluator) Usage(item runtime.Object) (corev1.ResourceList, error) { 114 result := corev1.ResourceList{} 115 svc, err := toExternalServiceOrError(item) 116 if err != nil { 117 return result, err 118 } 119 ports := len(svc.Spec.Ports) 120 // default service usage 121 result[serviceObjectCountName] = *(resource.NewQuantity(1, resource.DecimalSI)) 122 result[corev1.ResourceServices] = *(resource.NewQuantity(1, resource.DecimalSI)) 123 result[corev1.ResourceServicesLoadBalancers] = resource.Quantity{Format: resource.DecimalSI} 124 result[corev1.ResourceServicesNodePorts] = resource.Quantity{Format: resource.DecimalSI} 125 switch svc.Spec.Type { 126 case corev1.ServiceTypeNodePort: 127 // node port services need to count node ports 128 value := resource.NewQuantity(int64(ports), resource.DecimalSI) 129 result[corev1.ResourceServicesNodePorts] = *value 130 case corev1.ServiceTypeLoadBalancer: 131 // load balancer services need to count node ports. If creation of node ports 132 // is suppressed only ports with explicit NodePort values are counted. 133 // nodeports won't be allocated yet, so we can't simply count the actual values. 134 // We need to look at the intent. 135 if svc.Spec.AllocateLoadBalancerNodePorts != nil && 136 *svc.Spec.AllocateLoadBalancerNodePorts == false { 137 result[corev1.ResourceServicesNodePorts] = *portsWithNodePorts(svc) 138 } else { 139 value := resource.NewQuantity(int64(ports), resource.DecimalSI) 140 result[corev1.ResourceServicesNodePorts] = *value 141 } 142 result[corev1.ResourceServicesLoadBalancers] = *(resource.NewQuantity(1, resource.DecimalSI)) 143 } 144 return result, nil 145 } 146 147 func portsWithNodePorts(svc *corev1.Service) *resource.Quantity { 148 count := 0 149 for _, p := range svc.Spec.Ports { 150 if p.NodePort != 0 { 151 count++ 152 } 153 } 154 return resource.NewQuantity(int64(count), resource.DecimalSI) 155 } 156 157 // UsageStats calculates aggregate usage for the object. 158 func (p *serviceEvaluator) UsageStats(options quota.UsageStatsOptions) (quota.UsageStats, error) { 159 return generic.CalculateUsageStats(options, p.listFuncByNamespace, generic.MatchesNoScopeFunc, p.Usage) 160 } 161 162 var _ quota.Evaluator = &serviceEvaluator{} 163 164 // GetQuotaServiceType returns ServiceType if the service type is eligible to track against a quota, nor return "" 165 func GetQuotaServiceType(service *corev1.Service) corev1.ServiceType { 166 switch service.Spec.Type { 167 case corev1.ServiceTypeNodePort: 168 return corev1.ServiceTypeNodePort 169 case corev1.ServiceTypeLoadBalancer: 170 return corev1.ServiceTypeLoadBalancer 171 } 172 return corev1.ServiceType("") 173 }