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  }