...

Source file src/k8s.io/kubernetes/pkg/registry/core/service/strategy.go

Documentation: k8s.io/kubernetes/pkg/registry/core/service

     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  	// Drop condition for TrafficDistribution field.
   124  	isTrafficDistributionInUse := (oldSvc != nil && oldSvc.Spec.TrafficDistribution != nil)
   125  	if !utilfeature.DefaultFeatureGate.Enabled(features.ServiceTrafficDistribution) && !isTrafficDistributionInUse {
   126  		newSvc.Spec.TrafficDistribution = nil
   127  	}
   128  }
   129  
   130  type serviceStatusStrategy struct {
   131  	svcStrategy
   132  }
   133  
   134  // StatusStrategy wraps and exports the used svcStrategy for the storage package.
   135  var StatusStrategy = serviceStatusStrategy{Strategy}
   136  
   137  // GetResetFields returns the set of fields that get reset by the strategy
   138  // and should not be modified by the user.
   139  func (serviceStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
   140  	fields := map[fieldpath.APIVersion]*fieldpath.Set{
   141  		"v1": fieldpath.NewSet(
   142  			fieldpath.MakePathOrDie("spec"),
   143  		),
   144  	}
   145  
   146  	return fields
   147  }
   148  
   149  // PrepareForUpdate clears fields that are not allowed to be set by end users on update of status
   150  func (serviceStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
   151  	newService := obj.(*api.Service)
   152  	oldService := old.(*api.Service)
   153  
   154  	dropServiceStatusDisabledFields(newService, oldService)
   155  	// status changes are not allowed to update spec
   156  	newService.Spec = oldService.Spec
   157  }
   158  
   159  // ValidateUpdate is the default update validation for an end user updating status
   160  func (serviceStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
   161  	return validation.ValidateServiceStatusUpdate(obj.(*api.Service), old.(*api.Service))
   162  }
   163  
   164  // WarningsOnUpdate returns warnings for the given update.
   165  func (serviceStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
   166  	return nil
   167  }
   168  
   169  // dropServiceStatusDisabledFields drops fields that are not used if their associated feature gates
   170  // are not enabled.  The typical pattern is:
   171  //
   172  //	if !utilfeature.DefaultFeatureGate.Enabled(features.MyFeature) && !myFeatureInUse(oldSvc) {
   173  //	    newSvc.Status.MyFeature = nil
   174  //	}
   175  func dropServiceStatusDisabledFields(newSvc *api.Service, oldSvc *api.Service) {
   176  	if !utilfeature.DefaultFeatureGate.Enabled(features.LoadBalancerIPMode) && !loadbalancerIPModeInUse(oldSvc) {
   177  		for i := range newSvc.Status.LoadBalancer.Ingress {
   178  			newSvc.Status.LoadBalancer.Ingress[i].IPMode = nil
   179  		}
   180  	}
   181  }
   182  
   183  // returns true when the LoadBalancer Ingress IPMode fields are in use.
   184  func loadbalancerIPModeInUse(svc *api.Service) bool {
   185  	if svc == nil {
   186  		return false
   187  	}
   188  	for _, ing := range svc.Status.LoadBalancer.Ingress {
   189  		if ing.IPMode != nil {
   190  			return true
   191  		}
   192  	}
   193  	return false
   194  }
   195  
   196  func sameStringSlice(a []string, b []string) bool {
   197  	if len(a) != len(b) {
   198  		return false
   199  	}
   200  	for i := range a {
   201  		if a[i] != b[i] {
   202  			return false
   203  		}
   204  	}
   205  	return true
   206  }
   207  
   208  // This is an unusual case.  Service has a number of inter-related fields and
   209  // in order to avoid breaking clients we try really hard to infer what users
   210  // mean when they change them.
   211  //
   212  // Services are effectively a discriminated union, where `type` is the
   213  // discriminator. Some fields just don't make sense with some types, so we
   214  // clear them.
   215  //
   216  // As a rule, we almost never change user input.  This can get tricky when APIs
   217  // evolve and new dependent fields are added.  This specific case includes
   218  // fields that are allocated from a pool and need to be released.  Anyone who
   219  // is contemplating copying this pattern should think REALLY hard about almost
   220  // any other option.
   221  func dropTypeDependentFields(newSvc *api.Service, oldSvc *api.Service) {
   222  	// For now we are only wiping on updates.  This minimizes potential
   223  	// confusion since many of the cases we are handling here are pretty niche.
   224  	if oldSvc == nil {
   225  		return
   226  	}
   227  
   228  	// In all of these cases we only want to wipe a field if we a) know it no
   229  	// longer applies; b) might have initialized it automatically; c) know the
   230  	// user did not ALSO try to change it (in which case it should fail in
   231  	// validation).
   232  
   233  	// If the user is switching to a type that does not need a value in
   234  	// clusterIP/clusterIPs (even "None" counts as a value), we might be able
   235  	// to wipe some fields.
   236  	if needsClusterIP(oldSvc) && !needsClusterIP(newSvc) {
   237  		if sameClusterIPs(oldSvc, newSvc) {
   238  			// These will be deallocated later.
   239  			newSvc.Spec.ClusterIP = ""
   240  			newSvc.Spec.ClusterIPs = nil
   241  		}
   242  		if sameIPFamilies(oldSvc, newSvc) {
   243  			newSvc.Spec.IPFamilies = nil
   244  		}
   245  		if sameIPFamilyPolicy(oldSvc, newSvc) {
   246  			newSvc.Spec.IPFamilyPolicy = nil
   247  		}
   248  	}
   249  
   250  	// If the user is switching to a type that doesn't use NodePorts AND they
   251  	// did not change any NodePort values, we can wipe them.  They will be
   252  	// deallocated later.
   253  	if needsNodePort(oldSvc) && !needsNodePort(newSvc) && sameNodePorts(oldSvc, newSvc) {
   254  		for i := range newSvc.Spec.Ports {
   255  			newSvc.Spec.Ports[i].NodePort = 0
   256  		}
   257  	}
   258  
   259  	// If the user is switching to a case that doesn't use HealthCheckNodePort AND they
   260  	// did not change the HealthCheckNodePort value, we can wipe it.  It will
   261  	// be deallocated later.
   262  	if needsHCNodePort(oldSvc) && !needsHCNodePort(newSvc) && sameHCNodePort(oldSvc, newSvc) {
   263  		newSvc.Spec.HealthCheckNodePort = 0
   264  	}
   265  
   266  	// If a user is switching to a type that doesn't need allocatedLoadBalancerNodePorts AND they did not change
   267  	// this field, it is safe to drop it.
   268  	if oldSvc.Spec.Type == api.ServiceTypeLoadBalancer && newSvc.Spec.Type != api.ServiceTypeLoadBalancer {
   269  		if newSvc.Spec.AllocateLoadBalancerNodePorts != nil && oldSvc.Spec.AllocateLoadBalancerNodePorts != nil {
   270  			if *oldSvc.Spec.AllocateLoadBalancerNodePorts == *newSvc.Spec.AllocateLoadBalancerNodePorts {
   271  				newSvc.Spec.AllocateLoadBalancerNodePorts = nil
   272  			}
   273  		}
   274  	}
   275  
   276  	// If a user is switching to a type that doesn't need LoadBalancerClass AND they did not change
   277  	// this field, it is safe to drop it.
   278  	if canSetLoadBalancerClass(oldSvc) && !canSetLoadBalancerClass(newSvc) && sameLoadBalancerClass(oldSvc, newSvc) {
   279  		newSvc.Spec.LoadBalancerClass = nil
   280  	}
   281  
   282  	// If a user is switching to a type that doesn't need ExternalTrafficPolicy
   283  	// AND they did not change this field, it is safe to drop it.
   284  	if serviceapi.ExternallyAccessible(oldSvc) && !serviceapi.ExternallyAccessible(newSvc) && sameExternalTrafficPolicy(oldSvc, newSvc) {
   285  		newSvc.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicy("")
   286  	}
   287  
   288  	// NOTE: there are other fields like `selector` which we could wipe.
   289  	// Historically we did not wipe them and they are not allocated from
   290  	// finite pools, so we are (currently) choosing to leave them alone.
   291  
   292  	// Clear the load-balancer status if it is no longer appropriate.  Although
   293  	// LB de-provisioning is actually asynchronous, we don't need to expose the
   294  	// user to that complexity.
   295  	if newSvc.Spec.Type != api.ServiceTypeLoadBalancer {
   296  		newSvc.Status.LoadBalancer = api.LoadBalancerStatus{}
   297  	}
   298  }
   299  
   300  func needsClusterIP(svc *api.Service) bool {
   301  	if svc.Spec.Type == api.ServiceTypeExternalName {
   302  		return false
   303  	}
   304  	return true
   305  }
   306  
   307  func sameClusterIPs(oldSvc, newSvc *api.Service) bool {
   308  	sameSingular := oldSvc.Spec.ClusterIP == newSvc.Spec.ClusterIP
   309  	samePlural := sameStringSlice(oldSvc.Spec.ClusterIPs, newSvc.Spec.ClusterIPs)
   310  	return sameSingular && samePlural
   311  }
   312  
   313  func sameIPFamilies(oldSvc, newSvc *api.Service) bool {
   314  	return reflect.DeepEqual(oldSvc.Spec.IPFamilies, newSvc.Spec.IPFamilies)
   315  }
   316  
   317  func getIPFamilyPolicy(svc *api.Service) string {
   318  	if svc.Spec.IPFamilyPolicy == nil {
   319  		return ""
   320  	}
   321  	return string(*svc.Spec.IPFamilyPolicy)
   322  }
   323  
   324  func sameIPFamilyPolicy(oldSvc, newSvc *api.Service) bool {
   325  	return getIPFamilyPolicy(oldSvc) == getIPFamilyPolicy(newSvc)
   326  }
   327  
   328  func needsNodePort(svc *api.Service) bool {
   329  	if svc.Spec.Type == api.ServiceTypeNodePort || svc.Spec.Type == api.ServiceTypeLoadBalancer {
   330  		return true
   331  	}
   332  	return false
   333  }
   334  
   335  func sameNodePorts(oldSvc, newSvc *api.Service) bool {
   336  	// Helper to make a set of NodePort values.
   337  	allNodePorts := func(svc *api.Service) sets.Int {
   338  		out := sets.NewInt()
   339  		for i := range svc.Spec.Ports {
   340  			if svc.Spec.Ports[i].NodePort != 0 {
   341  				out.Insert(int(svc.Spec.Ports[i].NodePort))
   342  			}
   343  		}
   344  		return out
   345  	}
   346  
   347  	oldPorts := allNodePorts(oldSvc)
   348  	newPorts := allNodePorts(newSvc)
   349  
   350  	// Users can add, remove, or modify ports, as long as they don't add any
   351  	// net-new NodePorts.
   352  	return oldPorts.IsSuperset(newPorts)
   353  }
   354  
   355  func needsHCNodePort(svc *api.Service) bool {
   356  	if svc.Spec.Type != api.ServiceTypeLoadBalancer {
   357  		return false
   358  	}
   359  	if svc.Spec.ExternalTrafficPolicy != api.ServiceExternalTrafficPolicyLocal {
   360  		return false
   361  	}
   362  	return true
   363  }
   364  
   365  func sameHCNodePort(oldSvc, newSvc *api.Service) bool {
   366  	return oldSvc.Spec.HealthCheckNodePort == newSvc.Spec.HealthCheckNodePort
   367  }
   368  
   369  func canSetLoadBalancerClass(svc *api.Service) bool {
   370  	return svc.Spec.Type == api.ServiceTypeLoadBalancer
   371  }
   372  
   373  func sameLoadBalancerClass(oldSvc, newSvc *api.Service) bool {
   374  	if (oldSvc.Spec.LoadBalancerClass == nil) != (newSvc.Spec.LoadBalancerClass == nil) {
   375  		return false
   376  	}
   377  	if oldSvc.Spec.LoadBalancerClass == nil {
   378  		return true // both are nil
   379  	}
   380  	return *oldSvc.Spec.LoadBalancerClass == *newSvc.Spec.LoadBalancerClass
   381  }
   382  
   383  func sameExternalTrafficPolicy(oldSvc, newSvc *api.Service) bool {
   384  	return oldSvc.Spec.ExternalTrafficPolicy == newSvc.Spec.ExternalTrafficPolicy
   385  }
   386  

View as plain text