k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/registry/core/service/storage/storage.go (about) 1 /* 2 Copyright 2015 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 storage 18 19 import ( 20 "context" 21 "fmt" 22 "math/rand" 23 "net" 24 "net/http" 25 "net/url" 26 "strconv" 27 28 "k8s.io/apimachinery/pkg/api/errors" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/runtime" 31 utilnet "k8s.io/apimachinery/pkg/util/net" 32 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 33 "k8s.io/apimachinery/pkg/util/sets" 34 genericapirequest "k8s.io/apiserver/pkg/endpoints/request" 35 "k8s.io/apiserver/pkg/registry/generic" 36 genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" 37 "k8s.io/apiserver/pkg/registry/rest" 38 "k8s.io/apiserver/pkg/util/dryrun" 39 "k8s.io/klog/v2" 40 api "k8s.io/kubernetes/pkg/apis/core" 41 "k8s.io/kubernetes/pkg/printers" 42 printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" 43 printerstorage "k8s.io/kubernetes/pkg/printers/storage" 44 svcreg "k8s.io/kubernetes/pkg/registry/core/service" 45 "k8s.io/kubernetes/pkg/registry/core/service/ipallocator" 46 "k8s.io/kubernetes/pkg/registry/core/service/portallocator" 47 netutil "k8s.io/utils/net" 48 "sigs.k8s.io/structured-merge-diff/v4/fieldpath" 49 ) 50 51 type EndpointsStorage interface { 52 rest.Getter 53 rest.GracefulDeleter 54 } 55 56 type PodStorage interface { 57 rest.Getter 58 } 59 60 type REST struct { 61 *genericregistry.Store 62 primaryIPFamily api.IPFamily 63 secondaryIPFamily api.IPFamily 64 alloc Allocators 65 endpoints EndpointsStorage 66 pods PodStorage 67 proxyTransport http.RoundTripper 68 } 69 70 var ( 71 _ rest.CategoriesProvider = &REST{} 72 _ rest.ShortNamesProvider = &REST{} 73 _ rest.StorageVersionProvider = &REST{} 74 _ rest.ResetFieldsStrategy = &REST{} 75 _ rest.Redirector = &REST{} 76 ) 77 78 // NewREST returns a REST object that will work against services. 79 func NewREST( 80 optsGetter generic.RESTOptionsGetter, 81 serviceIPFamily api.IPFamily, 82 ipAllocs map[api.IPFamily]ipallocator.Interface, 83 portAlloc portallocator.Interface, 84 endpoints EndpointsStorage, 85 pods PodStorage, 86 proxyTransport http.RoundTripper) (*REST, *StatusREST, *svcreg.ProxyREST, error) { 87 88 store := &genericregistry.Store{ 89 NewFunc: func() runtime.Object { return &api.Service{} }, 90 NewListFunc: func() runtime.Object { return &api.ServiceList{} }, 91 PredicateFunc: svcreg.Matcher, 92 DefaultQualifiedResource: api.Resource("services"), 93 SingularQualifiedResource: api.Resource("service"), 94 ReturnDeletedObject: true, 95 96 CreateStrategy: svcreg.Strategy, 97 UpdateStrategy: svcreg.Strategy, 98 DeleteStrategy: svcreg.Strategy, 99 ResetFieldsStrategy: svcreg.Strategy, 100 101 TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)}, 102 } 103 options := &generic.StoreOptions{ 104 RESTOptions: optsGetter, 105 AttrFunc: svcreg.GetAttrs, 106 } 107 if err := store.CompleteWithOptions(options); err != nil { 108 return nil, nil, nil, err 109 } 110 111 statusStore := *store 112 statusStore.UpdateStrategy = svcreg.StatusStrategy 113 statusStore.ResetFieldsStrategy = svcreg.StatusStrategy 114 115 var primaryIPFamily api.IPFamily = serviceIPFamily 116 var secondaryIPFamily api.IPFamily = "" // sentinel value 117 if len(ipAllocs) > 1 { 118 secondaryIPFamily = otherFamily(serviceIPFamily) 119 } 120 genericStore := &REST{ 121 Store: store, 122 primaryIPFamily: primaryIPFamily, 123 secondaryIPFamily: secondaryIPFamily, 124 alloc: makeAlloc(serviceIPFamily, ipAllocs, portAlloc), 125 endpoints: endpoints, 126 pods: pods, 127 proxyTransport: proxyTransport, 128 } 129 store.Decorator = genericStore.defaultOnRead 130 store.AfterDelete = genericStore.afterDelete 131 store.BeginCreate = genericStore.beginCreate 132 store.BeginUpdate = genericStore.beginUpdate 133 134 // users can patch the status to remove the finalizer, 135 // hence statusStore must participate on the AfterDelete 136 // hook to release the allocated resources 137 statusStore.AfterDelete = genericStore.afterDelete 138 139 return genericStore, &StatusREST{store: &statusStore}, &svcreg.ProxyREST{Redirector: genericStore, ProxyTransport: proxyTransport}, nil 140 } 141 142 // otherFamily returns the non-selected IPFamily. This assumes the input is 143 // valid. 144 func otherFamily(fam api.IPFamily) api.IPFamily { 145 if fam == api.IPv4Protocol { 146 return api.IPv6Protocol 147 } 148 return api.IPv4Protocol 149 } 150 151 var ( 152 _ rest.ShortNamesProvider = &REST{} 153 _ rest.CategoriesProvider = &REST{} 154 ) 155 156 // ShortNames implements the ShortNamesProvider interface. Returns a list of short names for a resource. 157 func (r *REST) ShortNames() []string { 158 return []string{"svc"} 159 } 160 161 // Categories implements the CategoriesProvider interface. Returns a list of categories a resource is part of. 162 func (r *REST) Categories() []string { 163 return []string{"all"} 164 } 165 166 // Destroy cleans up everything on shutdown. 167 func (r *REST) Destroy() { 168 r.Store.Destroy() 169 r.alloc.Destroy() 170 } 171 172 // StatusREST implements the REST endpoint for changing the status of a service. 173 type StatusREST struct { 174 store *genericregistry.Store 175 } 176 177 func (r *StatusREST) New() runtime.Object { 178 return &api.Service{} 179 } 180 181 // Destroy cleans up resources on shutdown. 182 func (r *StatusREST) Destroy() { 183 // Given that underlying store is shared with REST, 184 // we don't destroy it here explicitly. 185 } 186 187 // Get retrieves the object from the storage. It is required to support Patch. 188 func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { 189 return r.store.Get(ctx, name, options) 190 } 191 192 // Update alters the status subset of an object. 193 func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { 194 // We are explicitly setting forceAllowCreate to false in the call to the underlying storage because 195 // subresources should never allow create on update. 196 return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options) 197 } 198 199 func (r *StatusREST) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { 200 return r.store.ConvertToTable(ctx, object, tableOptions) 201 } 202 203 // GetResetFields implements rest.ResetFieldsStrategy 204 func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { 205 return r.store.GetResetFields() 206 } 207 208 // We have a lot of functions that take a pair of "before" and "after" or 209 // "oldSvc" and "newSvc" args. Convention across the codebase is to pass them 210 // as (new, old), but it's easy to screw up when they are the same type. 211 // 212 // These types force us to pay attention. If the order of the arguments 213 // matters, please receive them as: 214 // func something(after After, before Before) { 215 // oldSvc, newSvc := before.Service, after.Service 216 // 217 // If the order of arguments DOES NOT matter, please receive them as: 218 // func something(lhs, rhs *api.Service) { 219 220 type Before struct { 221 *api.Service 222 } 223 type After struct { 224 *api.Service 225 } 226 227 // defaultOnRead sets interlinked fields that were not previously set on read. 228 // We can't do this in the normal defaulting path because that same logic 229 // applies on Get, Create, and Update, but we need to distinguish between them. 230 // 231 // This will be called on both Service and ServiceList types. 232 func (r *REST) defaultOnRead(obj runtime.Object) { 233 switch s := obj.(type) { 234 case *api.Service: 235 r.defaultOnReadService(s) 236 case *api.ServiceList: 237 r.defaultOnReadServiceList(s) 238 default: 239 // This was not an object we can default. This is not an error, as the 240 // caching layer can pass through here, too. 241 } 242 } 243 244 // defaultOnReadServiceList defaults a ServiceList. 245 func (r *REST) defaultOnReadServiceList(serviceList *api.ServiceList) { 246 if serviceList == nil { 247 return 248 } 249 250 for i := range serviceList.Items { 251 r.defaultOnReadService(&serviceList.Items[i]) 252 } 253 } 254 255 // defaultOnReadService defaults a single Service. 256 func (r *REST) defaultOnReadService(service *api.Service) { 257 if service == nil { 258 return 259 } 260 261 // We might find Services that were written before ClusterIP became plural. 262 // We still want to present a consistent view of them. 263 normalizeClusterIPs(After{service}, Before{nil}) 264 265 // Set ipFamilies and ipFamilyPolicy if needed. 266 r.defaultOnReadIPFamilies(service) 267 268 // We unintentionally defaulted internalTrafficPolicy when it's not needed 269 // for the ExternalName type. It's too late to change the field in storage, 270 // but we can drop the field when read. 271 defaultOnReadInternalTrafficPolicy(service) 272 } 273 274 func defaultOnReadInternalTrafficPolicy(service *api.Service) { 275 if service.Spec.Type == api.ServiceTypeExternalName { 276 service.Spec.InternalTrafficPolicy = nil 277 } 278 } 279 280 func (r *REST) defaultOnReadIPFamilies(service *api.Service) { 281 // ExternalName does not need this. 282 if !needsClusterIP(service) { 283 return 284 } 285 286 // If IPFamilies is set, we assume IPFamilyPolicy is also set (it should 287 // not be possible to have one and not the other), and therefore we don't 288 // need further defaulting. Likewise, if IPFamilies is *not* set, we 289 // assume IPFamilyPolicy can't be set either. 290 if len(service.Spec.IPFamilies) > 0 { 291 return 292 } 293 294 singleStack := api.IPFamilyPolicySingleStack 295 requireDualStack := api.IPFamilyPolicyRequireDualStack 296 297 if service.Spec.ClusterIP == api.ClusterIPNone { 298 // Headless. 299 if len(service.Spec.Selector) == 0 { 300 // Headless + selectorless is a special-case. 301 // 302 // At this stage we don't know what kind of endpoints (specifically 303 // their IPFamilies) the user has assigned to this selectorless 304 // service. We assume it has dual-stack and we default it to 305 // RequireDualStack on any cluster (single- or dual-stack 306 // configured). 307 service.Spec.IPFamilyPolicy = &requireDualStack 308 service.Spec.IPFamilies = []api.IPFamily{r.primaryIPFamily, otherFamily(r.primaryIPFamily)} 309 } else { 310 // Headless + selector - default to single. 311 service.Spec.IPFamilyPolicy = &singleStack 312 service.Spec.IPFamilies = []api.IPFamily{r.primaryIPFamily} 313 } 314 } else { 315 // Headful: init ipFamilies from clusterIPs. 316 service.Spec.IPFamilies = make([]api.IPFamily, len(service.Spec.ClusterIPs)) 317 for idx, ip := range service.Spec.ClusterIPs { 318 if netutil.IsIPv6String(ip) { 319 service.Spec.IPFamilies[idx] = api.IPv6Protocol 320 } else { 321 service.Spec.IPFamilies[idx] = api.IPv4Protocol 322 } 323 } 324 if len(service.Spec.IPFamilies) == 1 { 325 service.Spec.IPFamilyPolicy = &singleStack 326 } else if len(service.Spec.IPFamilies) == 2 { 327 // It shouldn't be possible to get here, but just in case. 328 service.Spec.IPFamilyPolicy = &requireDualStack 329 } 330 } 331 } 332 333 func (r *REST) afterDelete(obj runtime.Object, options *metav1.DeleteOptions) { 334 svc := obj.(*api.Service) 335 336 // Normally this defaulting is done automatically, but the hook (Decorator) 337 // is called at the end of this process, and we want the fully-formed 338 // object. 339 r.defaultOnReadService(svc) 340 341 // Only perform the cleanup if this is a non-dryrun deletion 342 if !dryrun.IsDryRun(options.DryRun) { 343 // It would be better if we had the caller context, but that changes 344 // this hook signature. 345 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), svc.Namespace) 346 // TODO: This is clumsy. It was added for fear that the endpoints 347 // controller might lag, and we could end up rusing the service name 348 // with old endpoints. We should solve that better and remove this, or 349 // else we should do this for EndpointSlice, too. 350 _, _, err := r.endpoints.Delete(ctx, svc.Name, rest.ValidateAllObjectFunc, &metav1.DeleteOptions{}) 351 if err != nil && !errors.IsNotFound(err) { 352 klog.Errorf("delete service endpoints %s/%s failed: %v", svc.Name, svc.Namespace, err) 353 } 354 355 r.alloc.releaseAllocatedResources(svc) 356 } 357 } 358 359 func (r *REST) beginCreate(ctx context.Context, obj runtime.Object, options *metav1.CreateOptions) (genericregistry.FinishFunc, error) { 360 svc := obj.(*api.Service) 361 362 // Make sure ClusterIP and ClusterIPs are in sync. This has to happen 363 // early, before anyone looks at them. 364 normalizeClusterIPs(After{svc}, Before{nil}) 365 366 // Allocate IPs and ports. If we had a transactional store, this would just 367 // be part of the larger transaction. We don't have that, so we have to do 368 // it manually. This has to happen here and not in any earlier hooks (e.g. 369 // defaulting) because it needs to be aware of flags and be able to access 370 // API storage. 371 txn, err := r.alloc.allocateCreate(svc, dryrun.IsDryRun(options.DryRun)) 372 if err != nil { 373 return nil, err 374 } 375 376 // Our cleanup callback 377 finish := func(_ context.Context, success bool) { 378 if success { 379 txn.Commit() 380 } else { 381 txn.Revert() 382 } 383 } 384 385 return finish, nil 386 } 387 388 func (r *REST) beginUpdate(ctx context.Context, obj, oldObj runtime.Object, options *metav1.UpdateOptions) (genericregistry.FinishFunc, error) { 389 newSvc := obj.(*api.Service) 390 oldSvc := oldObj.(*api.Service) 391 392 // Make sure the existing object has all fields we expect to be defaulted. 393 // This might not be true if the saved object predates these fields (the 394 // Decorator hook is not called on 'old' in the update path. 395 r.defaultOnReadService(oldSvc) 396 397 // Fix up allocated values that the client may have not specified (for 398 // idempotence). 399 patchAllocatedValues(After{newSvc}, Before{oldSvc}) 400 401 // Make sure ClusterIP and ClusterIPs are in sync. This has to happen 402 // early, before anyone looks at them. 403 normalizeClusterIPs(After{newSvc}, Before{oldSvc}) 404 405 // Allocate and initialize fields. 406 txn, err := r.alloc.allocateUpdate(After{newSvc}, Before{oldSvc}, dryrun.IsDryRun(options.DryRun)) 407 if err != nil { 408 return nil, err 409 } 410 411 // Our cleanup callback 412 finish := func(_ context.Context, success bool) { 413 if success { 414 txn.Commit() 415 } else { 416 txn.Revert() 417 } 418 } 419 420 return finish, nil 421 } 422 423 // ResourceLocation returns a URL to which one can send traffic for the specified service. 424 func (r *REST) ResourceLocation(ctx context.Context, id string) (*url.URL, http.RoundTripper, error) { 425 // Allow ID as "svcname", "svcname:port", or "scheme:svcname:port". 426 svcScheme, svcName, portStr, valid := utilnet.SplitSchemeNamePort(id) 427 if !valid { 428 return nil, nil, errors.NewBadRequest(fmt.Sprintf("invalid service request %q", id)) 429 } 430 431 // If a port *number* was specified, find the corresponding service port name 432 if portNum, err := strconv.ParseInt(portStr, 10, 64); err == nil { 433 obj, err := r.Get(ctx, svcName, &metav1.GetOptions{}) 434 if err != nil { 435 return nil, nil, err 436 } 437 svc := obj.(*api.Service) 438 found := false 439 for _, svcPort := range svc.Spec.Ports { 440 if int64(svcPort.Port) == portNum { 441 // use the declared port's name 442 portStr = svcPort.Name 443 found = true 444 break 445 } 446 } 447 if !found { 448 return nil, nil, errors.NewServiceUnavailable(fmt.Sprintf("no service port %d found for service %q", portNum, svcName)) 449 } 450 } 451 452 obj, err := r.endpoints.Get(ctx, svcName, &metav1.GetOptions{}) 453 if err != nil { 454 return nil, nil, err 455 } 456 eps := obj.(*api.Endpoints) 457 if len(eps.Subsets) == 0 { 458 return nil, nil, errors.NewServiceUnavailable(fmt.Sprintf("no endpoints available for service %q", svcName)) 459 } 460 // Pick a random Subset to start searching from. 461 ssSeed := rand.Intn(len(eps.Subsets)) 462 // Find a Subset that has the port. 463 for ssi := 0; ssi < len(eps.Subsets); ssi++ { 464 ss := &eps.Subsets[(ssSeed+ssi)%len(eps.Subsets)] 465 if len(ss.Addresses) == 0 { 466 continue 467 } 468 for i := range ss.Ports { 469 if ss.Ports[i].Name == portStr { 470 addrSeed := rand.Intn(len(ss.Addresses)) 471 // This is a little wonky, but it's expensive to test for the presence of a Pod 472 // So we repeatedly try at random and validate it, this means that for an invalid 473 // service with a lot of endpoints we're going to potentially make a lot of calls, 474 // but in the expected case we'll only make one. 475 for try := 0; try < len(ss.Addresses); try++ { 476 addr := ss.Addresses[(addrSeed+try)%len(ss.Addresses)] 477 // We only proxy to addresses that are actually pods. 478 if err := isValidAddress(ctx, &addr, r.pods); err != nil { 479 utilruntime.HandleError(fmt.Errorf("Address %v isn't valid (%v)", addr, err)) 480 continue 481 } 482 ip := addr.IP 483 port := int(ss.Ports[i].Port) 484 return &url.URL{ 485 Scheme: svcScheme, 486 Host: net.JoinHostPort(ip, strconv.Itoa(port)), 487 }, r.proxyTransport, nil 488 } 489 utilruntime.HandleError(fmt.Errorf("Failed to find a valid address, skipping subset: %v", ss)) 490 } 491 } 492 } 493 return nil, nil, errors.NewServiceUnavailable(fmt.Sprintf("no endpoints available for service %q", id)) 494 } 495 496 func isValidAddress(ctx context.Context, addr *api.EndpointAddress, pods rest.Getter) error { 497 if addr.TargetRef == nil { 498 return fmt.Errorf("Address has no target ref, skipping: %v", addr) 499 } 500 if genericapirequest.NamespaceValue(ctx) != addr.TargetRef.Namespace { 501 return fmt.Errorf("Address namespace doesn't match context namespace") 502 } 503 obj, err := pods.Get(ctx, addr.TargetRef.Name, &metav1.GetOptions{}) 504 if err != nil { 505 return err 506 } 507 pod, ok := obj.(*api.Pod) 508 if !ok { 509 return fmt.Errorf("failed to cast to pod: %v", obj) 510 } 511 if pod == nil { 512 return fmt.Errorf("pod is missing, skipping (%s/%s)", addr.TargetRef.Namespace, addr.TargetRef.Name) 513 } 514 for _, podIP := range pod.Status.PodIPs { 515 if podIP.IP == addr.IP { 516 return nil 517 } 518 } 519 return fmt.Errorf("pod ip(s) doesn't match endpoint ip, skipping: %v vs %s (%s/%s)", pod.Status.PodIPs, addr.IP, addr.TargetRef.Namespace, addr.TargetRef.Name) 520 } 521 522 // normalizeClusterIPs adjust clusterIPs based on ClusterIP. This must not 523 // consider any other fields. 524 func normalizeClusterIPs(after After, before Before) { 525 oldSvc, newSvc := before.Service, after.Service 526 527 // In all cases here, we don't need to over-think the inputs. Validation 528 // will be called on the new object soon enough. All this needs to do is 529 // try to divine what user meant with these linked fields. The below 530 // is verbosely written for clarity. 531 532 // **** IMPORTANT ***** 533 // as a governing rule. User must (either) 534 // -- Use singular only (old client) 535 // -- singular and plural fields (new clients) 536 537 if oldSvc == nil { 538 // This was a create operation. 539 // User specified singular and not plural (e.g. an old client), so init 540 // plural for them. 541 if len(newSvc.Spec.ClusterIP) > 0 && len(newSvc.Spec.ClusterIPs) == 0 { 542 newSvc.Spec.ClusterIPs = []string{newSvc.Spec.ClusterIP} 543 return 544 } 545 546 // we don't init singular based on plural because 547 // new client must use both fields 548 549 // Either both were not specified (will be allocated) or both were 550 // specified (will be validated). 551 return 552 } 553 554 // This was an update operation 555 556 // ClusterIPs were cleared by an old client which was trying to patch 557 // some field and didn't provide ClusterIPs 558 if len(oldSvc.Spec.ClusterIPs) > 0 && len(newSvc.Spec.ClusterIPs) == 0 { 559 // if ClusterIP is the same, then it is an old client trying to 560 // patch service and didn't provide ClusterIPs 561 if oldSvc.Spec.ClusterIP == newSvc.Spec.ClusterIP { 562 newSvc.Spec.ClusterIPs = oldSvc.Spec.ClusterIPs 563 } 564 } 565 566 // clusterIP is not the same 567 if oldSvc.Spec.ClusterIP != newSvc.Spec.ClusterIP { 568 // this is a client trying to clear it 569 if len(oldSvc.Spec.ClusterIP) > 0 && len(newSvc.Spec.ClusterIP) == 0 { 570 // if clusterIPs are the same, then clear on their behalf 571 if sameClusterIPs(oldSvc, newSvc) { 572 newSvc.Spec.ClusterIPs = nil 573 } 574 575 // if they provided nil, then we are fine (handled by patching case above) 576 // if they changed it then validation will catch it 577 } else { 578 // ClusterIP has changed but not cleared *and* ClusterIPs are the same 579 // then we set ClusterIPs based on ClusterIP 580 if sameClusterIPs(oldSvc, newSvc) { 581 newSvc.Spec.ClusterIPs = []string{newSvc.Spec.ClusterIP} 582 } 583 } 584 } 585 } 586 587 // patchAllocatedValues allows clients to avoid a read-modify-write cycle while 588 // preserving values that we allocated on their behalf. For example, they 589 // might create a Service without specifying the ClusterIP, in which case we 590 // allocate one. If they resubmit that same YAML, we want it to succeed. 591 func patchAllocatedValues(after After, before Before) { 592 oldSvc, newSvc := before.Service, after.Service 593 594 if needsClusterIP(oldSvc) && needsClusterIP(newSvc) { 595 if newSvc.Spec.ClusterIP == "" { 596 newSvc.Spec.ClusterIP = oldSvc.Spec.ClusterIP 597 } 598 if len(newSvc.Spec.ClusterIPs) == 0 && len(oldSvc.Spec.ClusterIPs) > 0 { 599 newSvc.Spec.ClusterIPs = oldSvc.Spec.ClusterIPs 600 } 601 } 602 603 if needsNodePort(oldSvc) && needsNodePort(newSvc) { 604 nodePortsUsed := func(svc *api.Service) sets.Int32 { 605 used := sets.NewInt32() 606 for _, p := range svc.Spec.Ports { 607 if p.NodePort != 0 { 608 used.Insert(p.NodePort) 609 } 610 } 611 return used 612 } 613 614 // Build a set of all the ports in oldSvc that are also in newSvc. We know 615 // we can't patch these values. 616 used := nodePortsUsed(oldSvc).Intersection(nodePortsUsed(newSvc)) 617 618 // Map NodePorts by name. The user may have changed other properties 619 // of the port, but we won't see that here. 620 np := map[string]int32{} 621 for i := range oldSvc.Spec.Ports { 622 p := &oldSvc.Spec.Ports[i] 623 np[p.Name] = p.NodePort 624 } 625 626 // If newSvc is missing values, try to patch them in when we know them and 627 // they haven't been used for another port. 628 629 for i := range newSvc.Spec.Ports { 630 p := &newSvc.Spec.Ports[i] 631 if p.NodePort == 0 { 632 oldVal := np[p.Name] 633 if !used.Has(oldVal) { 634 p.NodePort = oldVal 635 } 636 } 637 } 638 } 639 640 if needsHCNodePort(oldSvc) && needsHCNodePort(newSvc) { 641 if newSvc.Spec.HealthCheckNodePort == 0 { 642 newSvc.Spec.HealthCheckNodePort = oldSvc.Spec.HealthCheckNodePort 643 } 644 } 645 } 646 647 func needsClusterIP(svc *api.Service) bool { 648 if svc.Spec.Type == api.ServiceTypeExternalName { 649 return false 650 } 651 return true 652 } 653 654 func needsNodePort(svc *api.Service) bool { 655 if svc.Spec.Type == api.ServiceTypeNodePort { 656 return true 657 } 658 if svc.Spec.Type == api.ServiceTypeLoadBalancer && 659 (svc.Spec.AllocateLoadBalancerNodePorts == nil || *svc.Spec.AllocateLoadBalancerNodePorts) { 660 return true 661 } 662 return false 663 } 664 665 func needsHCNodePort(svc *api.Service) bool { 666 if svc.Spec.Type != api.ServiceTypeLoadBalancer { 667 return false 668 } 669 if svc.Spec.ExternalTrafficPolicy != api.ServiceExternalTrafficPolicyLocal { 670 return false 671 } 672 return true 673 }