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 }