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 }