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