k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/registry/core/service/strategy.go (about)

     1  /*
     2  Copyright 2014 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 service
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"reflect"
    23  
    24  	"k8s.io/apimachinery/pkg/fields"
    25  	"k8s.io/apimachinery/pkg/labels"
    26  	"k8s.io/apimachinery/pkg/runtime"
    27  	"k8s.io/apimachinery/pkg/util/sets"
    28  	"k8s.io/apimachinery/pkg/util/validation/field"
    29  	"k8s.io/apiserver/pkg/registry/generic"
    30  	pkgstorage "k8s.io/apiserver/pkg/storage"
    31  	"k8s.io/apiserver/pkg/storage/names"
    32  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    33  	"k8s.io/kubernetes/pkg/api/legacyscheme"
    34  	serviceapi "k8s.io/kubernetes/pkg/api/service"
    35  	api "k8s.io/kubernetes/pkg/apis/core"
    36  	"k8s.io/kubernetes/pkg/apis/core/validation"
    37  	"k8s.io/kubernetes/pkg/features"
    38  
    39  	"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
    40  )
    41  
    42  // svcStrategy implements behavior for Services
    43  type svcStrategy struct {
    44  	runtime.ObjectTyper
    45  	names.NameGenerator
    46  }
    47  
    48  // Strategy is the default logic that applies when creating and updating Services
    49  // objects via the REST API.
    50  var Strategy = svcStrategy{legacyscheme.Scheme, names.SimpleNameGenerator}
    51  
    52  // NamespaceScoped is true for services.
    53  func (svcStrategy) NamespaceScoped() bool {
    54  	return true
    55  }
    56  
    57  // GetResetFields returns the set of fields that get reset by the strategy
    58  // and should not be modified by the user.
    59  func (svcStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
    60  	fields := map[fieldpath.APIVersion]*fieldpath.Set{
    61  		"v1": fieldpath.NewSet(
    62  			fieldpath.MakePathOrDie("status"),
    63  		),
    64  	}
    65  
    66  	return fields
    67  }
    68  
    69  // PrepareForCreate sets contextual defaults and clears fields that are not allowed to be set by end users on creation.
    70  func (svcStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
    71  	service := obj.(*api.Service)
    72  	service.Status = api.ServiceStatus{}
    73  
    74  	dropServiceDisabledFields(service, nil)
    75  }
    76  
    77  // PrepareForUpdate sets contextual defaults and clears fields that are not allowed to be set by end users on update.
    78  func (svcStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
    79  	newService := obj.(*api.Service)
    80  	oldService := old.(*api.Service)
    81  	newService.Status = oldService.Status
    82  
    83  	dropServiceDisabledFields(newService, oldService)
    84  	dropTypeDependentFields(newService, oldService)
    85  }
    86  
    87  // Validate validates a new service.
    88  func (svcStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
    89  	service := obj.(*api.Service)
    90  	allErrs := validation.ValidateServiceCreate(service)
    91  	return allErrs
    92  }
    93  
    94  // WarningsOnCreate returns warnings for the creation of the given object.
    95  func (svcStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string {
    96  	return serviceapi.GetWarningsForService(obj.(*api.Service), nil)
    97  }
    98  
    99  // Canonicalize normalizes the object after validation.
   100  func (svcStrategy) Canonicalize(obj runtime.Object) {
   101  }
   102  
   103  func (svcStrategy) AllowCreateOnUpdate() bool {
   104  	return true
   105  }
   106  
   107  func (strategy svcStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
   108  	allErrs := validation.ValidateServiceUpdate(obj.(*api.Service), old.(*api.Service))
   109  	return allErrs
   110  }
   111  
   112  // WarningsOnUpdate returns warnings for the given update.
   113  func (svcStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
   114  	return serviceapi.GetWarningsForService(obj.(*api.Service), old.(*api.Service))
   115  }
   116  
   117  func (svcStrategy) AllowUnconditionalUpdate() bool {
   118  	return true
   119  }
   120  
   121  // dropServiceDisabledFields drops fields that are not used if their associated feature gates
   122  // are not enabled.  The typical pattern is:
   123  //
   124  //	if !utilfeature.DefaultFeatureGate.Enabled(features.MyFeature) && !myFeatureInUse(oldSvc) {
   125  //	    newSvc.Spec.MyFeature = nil
   126  //	}
   127  func dropServiceDisabledFields(newSvc *api.Service, oldSvc *api.Service) {
   128  	// Drop condition for TrafficDistribution field.
   129  	isTrafficDistributionInUse := (oldSvc != nil && oldSvc.Spec.TrafficDistribution != nil)
   130  	if !utilfeature.DefaultFeatureGate.Enabled(features.ServiceTrafficDistribution) && !isTrafficDistributionInUse {
   131  		newSvc.Spec.TrafficDistribution = nil
   132  	}
   133  }
   134  
   135  type serviceStatusStrategy struct {
   136  	svcStrategy
   137  }
   138  
   139  // StatusStrategy wraps and exports the used svcStrategy for the storage package.
   140  var StatusStrategy = serviceStatusStrategy{Strategy}
   141  
   142  // GetResetFields returns the set of fields that get reset by the strategy
   143  // and should not be modified by the user.
   144  func (serviceStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
   145  	fields := map[fieldpath.APIVersion]*fieldpath.Set{
   146  		"v1": fieldpath.NewSet(
   147  			fieldpath.MakePathOrDie("spec"),
   148  		),
   149  	}
   150  
   151  	return fields
   152  }
   153  
   154  // PrepareForUpdate clears fields that are not allowed to be set by end users on update of status
   155  func (serviceStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
   156  	newService := obj.(*api.Service)
   157  	oldService := old.(*api.Service)
   158  
   159  	dropServiceStatusDisabledFields(newService, oldService)
   160  	// status changes are not allowed to update spec
   161  	newService.Spec = oldService.Spec
   162  }
   163  
   164  // ValidateUpdate is the default update validation for an end user updating status
   165  func (serviceStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
   166  	return validation.ValidateServiceStatusUpdate(obj.(*api.Service), old.(*api.Service))
   167  }
   168  
   169  // WarningsOnUpdate returns warnings for the given update.
   170  func (serviceStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
   171  	return nil
   172  }
   173  
   174  // GetAttrs returns labels and fields of a given object for filtering purposes.
   175  func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
   176  	service, ok := obj.(*api.Service)
   177  	if !ok {
   178  		return nil, nil, fmt.Errorf("not a service")
   179  	}
   180  	return service.Labels, SelectableFields(service), nil
   181  }
   182  
   183  // Matcher returns a selection predicate for a given label and field selector.
   184  func Matcher(label labels.Selector, field fields.Selector) pkgstorage.SelectionPredicate {
   185  	return pkgstorage.SelectionPredicate{
   186  		Label:    label,
   187  		Field:    field,
   188  		GetAttrs: GetAttrs,
   189  	}
   190  }
   191  
   192  // SelectableFields returns a field set that can be used for filter selection
   193  func SelectableFields(service *api.Service) fields.Set {
   194  	objectMetaFieldsSet := generic.ObjectMetaFieldsSet(&service.ObjectMeta, true)
   195  	serviceSpecificFieldsSet := fields.Set{
   196  		"spec.clusterIP": service.Spec.ClusterIP,
   197  		"spec.type":      string(service.Spec.Type),
   198  	}
   199  	return generic.MergeFieldsSets(objectMetaFieldsSet, serviceSpecificFieldsSet)
   200  }
   201  
   202  // dropServiceStatusDisabledFields drops fields that are not used if their associated feature gates
   203  // are not enabled.  The typical pattern is:
   204  //
   205  //	if !utilfeature.DefaultFeatureGate.Enabled(features.MyFeature) && !myFeatureInUse(oldSvc) {
   206  //	    newSvc.Status.MyFeature = nil
   207  //	}
   208  func dropServiceStatusDisabledFields(newSvc *api.Service, oldSvc *api.Service) {
   209  	if !utilfeature.DefaultFeatureGate.Enabled(features.LoadBalancerIPMode) && !loadbalancerIPModeInUse(oldSvc) {
   210  		for i := range newSvc.Status.LoadBalancer.Ingress {
   211  			newSvc.Status.LoadBalancer.Ingress[i].IPMode = nil
   212  		}
   213  	}
   214  }
   215  
   216  // returns true when the LoadBalancer Ingress IPMode fields are in use.
   217  func loadbalancerIPModeInUse(svc *api.Service) bool {
   218  	if svc == nil {
   219  		return false
   220  	}
   221  	for _, ing := range svc.Status.LoadBalancer.Ingress {
   222  		if ing.IPMode != nil {
   223  			return true
   224  		}
   225  	}
   226  	return false
   227  }
   228  
   229  func sameStringSlice(a []string, b []string) bool {
   230  	if len(a) != len(b) {
   231  		return false
   232  	}
   233  	for i := range a {
   234  		if a[i] != b[i] {
   235  			return false
   236  		}
   237  	}
   238  	return true
   239  }
   240  
   241  // This is an unusual case.  Service has a number of inter-related fields and
   242  // in order to avoid breaking clients we try really hard to infer what users
   243  // mean when they change them.
   244  //
   245  // Services are effectively a discriminated union, where `type` is the
   246  // discriminator. Some fields just don't make sense with some types, so we
   247  // clear them.
   248  //
   249  // As a rule, we almost never change user input.  This can get tricky when APIs
   250  // evolve and new dependent fields are added.  This specific case includes
   251  // fields that are allocated from a pool and need to be released.  Anyone who
   252  // is contemplating copying this pattern should think REALLY hard about almost
   253  // any other option.
   254  func dropTypeDependentFields(newSvc *api.Service, oldSvc *api.Service) {
   255  	// For now we are only wiping on updates.  This minimizes potential
   256  	// confusion since many of the cases we are handling here are pretty niche.
   257  	if oldSvc == nil {
   258  		return
   259  	}
   260  
   261  	// In all of these cases we only want to wipe a field if we a) know it no
   262  	// longer applies; b) might have initialized it automatically; c) know the
   263  	// user did not ALSO try to change it (in which case it should fail in
   264  	// validation).
   265  
   266  	// If the user is switching to a type that does not need a value in
   267  	// clusterIP/clusterIPs (even "None" counts as a value), we might be able
   268  	// to wipe some fields.
   269  	if needsClusterIP(oldSvc) && !needsClusterIP(newSvc) {
   270  		if sameClusterIPs(oldSvc, newSvc) {
   271  			// These will be deallocated later.
   272  			newSvc.Spec.ClusterIP = ""
   273  			newSvc.Spec.ClusterIPs = nil
   274  		}
   275  		if sameIPFamilies(oldSvc, newSvc) {
   276  			newSvc.Spec.IPFamilies = nil
   277  		}
   278  		if sameIPFamilyPolicy(oldSvc, newSvc) {
   279  			newSvc.Spec.IPFamilyPolicy = nil
   280  		}
   281  	}
   282  
   283  	// If the user is switching to a type that doesn't use NodePorts AND they
   284  	// did not change any NodePort values, we can wipe them.  They will be
   285  	// deallocated later.
   286  	if needsNodePort(oldSvc) && !needsNodePort(newSvc) && sameNodePorts(oldSvc, newSvc) {
   287  		for i := range newSvc.Spec.Ports {
   288  			newSvc.Spec.Ports[i].NodePort = 0
   289  		}
   290  	}
   291  
   292  	// If the user is switching to a case that doesn't use HealthCheckNodePort AND they
   293  	// did not change the HealthCheckNodePort value, we can wipe it.  It will
   294  	// be deallocated later.
   295  	if needsHCNodePort(oldSvc) && !needsHCNodePort(newSvc) && sameHCNodePort(oldSvc, newSvc) {
   296  		newSvc.Spec.HealthCheckNodePort = 0
   297  	}
   298  
   299  	// If a user is switching to a type that doesn't need allocatedLoadBalancerNodePorts AND they did not change
   300  	// this field, it is safe to drop it.
   301  	if oldSvc.Spec.Type == api.ServiceTypeLoadBalancer && newSvc.Spec.Type != api.ServiceTypeLoadBalancer {
   302  		if newSvc.Spec.AllocateLoadBalancerNodePorts != nil && oldSvc.Spec.AllocateLoadBalancerNodePorts != nil {
   303  			if *oldSvc.Spec.AllocateLoadBalancerNodePorts == *newSvc.Spec.AllocateLoadBalancerNodePorts {
   304  				newSvc.Spec.AllocateLoadBalancerNodePorts = nil
   305  			}
   306  		}
   307  	}
   308  
   309  	// If a user is switching to a type that doesn't need LoadBalancerClass AND they did not change
   310  	// this field, it is safe to drop it.
   311  	if canSetLoadBalancerClass(oldSvc) && !canSetLoadBalancerClass(newSvc) && sameLoadBalancerClass(oldSvc, newSvc) {
   312  		newSvc.Spec.LoadBalancerClass = nil
   313  	}
   314  
   315  	// If a user is switching to a type that doesn't need ExternalTrafficPolicy
   316  	// AND they did not change this field, it is safe to drop it.
   317  	if serviceapi.ExternallyAccessible(oldSvc) && !serviceapi.ExternallyAccessible(newSvc) && sameExternalTrafficPolicy(oldSvc, newSvc) {
   318  		newSvc.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicy("")
   319  	}
   320  
   321  	// NOTE: there are other fields like `selector` which we could wipe.
   322  	// Historically we did not wipe them and they are not allocated from
   323  	// finite pools, so we are (currently) choosing to leave them alone.
   324  
   325  	// Clear the load-balancer status if it is no longer appropriate.  Although
   326  	// LB de-provisioning is actually asynchronous, we don't need to expose the
   327  	// user to that complexity.
   328  	if newSvc.Spec.Type != api.ServiceTypeLoadBalancer {
   329  		newSvc.Status.LoadBalancer = api.LoadBalancerStatus{}
   330  	}
   331  }
   332  
   333  func needsClusterIP(svc *api.Service) bool {
   334  	if svc.Spec.Type == api.ServiceTypeExternalName {
   335  		return false
   336  	}
   337  	return true
   338  }
   339  
   340  func sameClusterIPs(oldSvc, newSvc *api.Service) bool {
   341  	sameSingular := oldSvc.Spec.ClusterIP == newSvc.Spec.ClusterIP
   342  	samePlural := sameStringSlice(oldSvc.Spec.ClusterIPs, newSvc.Spec.ClusterIPs)
   343  	return sameSingular && samePlural
   344  }
   345  
   346  func sameIPFamilies(oldSvc, newSvc *api.Service) bool {
   347  	return reflect.DeepEqual(oldSvc.Spec.IPFamilies, newSvc.Spec.IPFamilies)
   348  }
   349  
   350  func getIPFamilyPolicy(svc *api.Service) string {
   351  	if svc.Spec.IPFamilyPolicy == nil {
   352  		return ""
   353  	}
   354  	return string(*svc.Spec.IPFamilyPolicy)
   355  }
   356  
   357  func sameIPFamilyPolicy(oldSvc, newSvc *api.Service) bool {
   358  	return getIPFamilyPolicy(oldSvc) == getIPFamilyPolicy(newSvc)
   359  }
   360  
   361  func needsNodePort(svc *api.Service) bool {
   362  	if svc.Spec.Type == api.ServiceTypeNodePort || svc.Spec.Type == api.ServiceTypeLoadBalancer {
   363  		return true
   364  	}
   365  	return false
   366  }
   367  
   368  func sameNodePorts(oldSvc, newSvc *api.Service) bool {
   369  	// Helper to make a set of NodePort values.
   370  	allNodePorts := func(svc *api.Service) sets.Int {
   371  		out := sets.NewInt()
   372  		for i := range svc.Spec.Ports {
   373  			if svc.Spec.Ports[i].NodePort != 0 {
   374  				out.Insert(int(svc.Spec.Ports[i].NodePort))
   375  			}
   376  		}
   377  		return out
   378  	}
   379  
   380  	oldPorts := allNodePorts(oldSvc)
   381  	newPorts := allNodePorts(newSvc)
   382  
   383  	// Users can add, remove, or modify ports, as long as they don't add any
   384  	// net-new NodePorts.
   385  	return oldPorts.IsSuperset(newPorts)
   386  }
   387  
   388  func needsHCNodePort(svc *api.Service) bool {
   389  	if svc.Spec.Type != api.ServiceTypeLoadBalancer {
   390  		return false
   391  	}
   392  	if svc.Spec.ExternalTrafficPolicy != api.ServiceExternalTrafficPolicyLocal {
   393  		return false
   394  	}
   395  	return true
   396  }
   397  
   398  func sameHCNodePort(oldSvc, newSvc *api.Service) bool {
   399  	return oldSvc.Spec.HealthCheckNodePort == newSvc.Spec.HealthCheckNodePort
   400  }
   401  
   402  func canSetLoadBalancerClass(svc *api.Service) bool {
   403  	return svc.Spec.Type == api.ServiceTypeLoadBalancer
   404  }
   405  
   406  func sameLoadBalancerClass(oldSvc, newSvc *api.Service) bool {
   407  	if (oldSvc.Spec.LoadBalancerClass == nil) != (newSvc.Spec.LoadBalancerClass == nil) {
   408  		return false
   409  	}
   410  	if oldSvc.Spec.LoadBalancerClass == nil {
   411  		return true // both are nil
   412  	}
   413  	return *oldSvc.Spec.LoadBalancerClass == *newSvc.Spec.LoadBalancerClass
   414  }
   415  
   416  func sameExternalTrafficPolicy(oldSvc, newSvc *api.Service) bool {
   417  	return oldSvc.Spec.ExternalTrafficPolicy == newSvc.Spec.ExternalTrafficPolicy
   418  }