Change:
<!-- This form is for bug reports and feature requests ONLY! If you're looking for help check and the . --Is this a BUG REPORT or FEATURE REQUEST?: /kind bug /sig network /area cloudprovider What happened: The cloud provider interface that should . The GCE provider does this for but not for . Every other in-tree provider implements by fully updating the load balancer including updated backend nodes: , , , And the following out-of-tree providers do as well and Oracle (not oss yet). What you expected to happen: I expect that all cloud providers implement by creating or fully updating the load balancer if it already exists. Related discussion: /cc
/assign Make sense to make implementation in GCE consistent with others for the ease of up-coming service controller enhancement. This sounds like a 1.10 task.
is this task blocking you on another attempt at refactoring the service controller sync logic?
yup

Discussion:
portStr = append(portStr, fmt.Sprintf("%s/%d", p.Protocol, p.Port)) } <del> affinityType := apiService.Spec.SessionAffinity </del> serviceName := types.NamespacedName{Namespace: apiService.Namespace, Name: apiService.Name} lbRefStr := fmt.Sprintf("%v(%v)", loadBalancerName, serviceName) glog.V(2).Infof("ensureExternalLoadBalancer(%s, %v, %v, %v, %v, %v)", lbRefStr, gce.region, requestedIP, portStr, hostNames, apiService.Annotations)

} } <del> tpExists, tpNeedsUpdate, err := gce.targetPoolNeedsUpdate(loadBalancerName, gce.region, affinityType) </del> <ins> tpExists, tpNeedsRecreation, err := gce.targetPoolNeedsRecreation(loadBalancerName, gce.region, apiService.Spec.SessionAffinity) </ins> if err != nil { return nil, err }

glog.V(4).Infof("ensureExternalLoadBalancer(%s): Service needs local traffic health checks on: %d%s.", lbRefStr, healthCheckNodePort, path) if hcLocalTrafficExisting == nil { // This logic exists to detect a transition for non-OnlyLocal to OnlyLocal service <del> // turn on the tpNeedsUpdate flag to delete/recreate fwdrule/tpool updating the </del> <ins> // turn on the tpNeedsRecreation flag to delete/recreate fwdrule/tpool updating the </ins> // target pool to use local traffic health check. glog.V(2).Infof("ensureExternalLoadBalancer(%s): Updating from nodes health checks to local traffic health checks.", lbRefStr) if supportsNodesHealthCheck { hcToDelete = makeHttpHealthCheck(MakeNodesHealthCheckName(clusterID), GetNodesHealthCheckPath(), GetNodesHealthCheckPort()) } <del> tpNeedsUpdate = true </del> <ins> tpNeedsRecreation = true </ins> } hcToCreate = makeHttpHealthCheck(loadBalancerName, path, healthCheckNodePort) } else { glog.V(4).Infof("ensureExternalLoadBalancer(%s): Service needs nodes health checks.", lbRefStr) if hcLocalTrafficExisting != nil { // This logic exists to detect a transition from OnlyLocal to non-OnlyLocal service <del> // and turn on the tpNeedsUpdate flag to delete/recreate fwdrule/tpool updating the </del> <ins> // and turn on the tpNeedsRecreation flag to delete/recreate fwdrule/tpool updating the </ins> // target pool to use nodes health check. glog.V(2).Infof("ensureExternalLoadBalancer(%s): Updating from local traffic health checks to nodes health checks.", lbRefStr) hcToDelete = hcLocalTrafficExisting <del> tpNeedsUpdate = true </del> <ins> tpNeedsRecreation = true </ins> } if supportsNodesHealthCheck { hcToCreate = makeHttpHealthCheck(MakeNodesHealthCheckName(clusterID), GetNodesHealthCheckPath(), GetNodesHealthCheckPort())

// can't delete a target pool that's currently in use by a forwarding rule. // Thus, we have to tear down the forwarding rule if either it or the target // pool needs to be updated. <del> if fwdRuleExists && (fwdRuleNeedsUpdate || tpNeedsUpdate) { </del> <ins> if fwdRuleExists && (fwdRuleNeedsUpdate || tpNeedsRecreation) { </ins> // Begin critical section. If we have to delete the forwarding rule, // and something should fail before we recreate it, don't release the // IP.  That way we can come back to it later.

} glog.Infof("ensureExternalLoadBalancer(%s): Deleted forwarding rule.", lbRefStr) } <del> if tpExists && tpNeedsUpdate { // Pass healthchecks to DeleteExternalTargetPoolAndChecks to cleanup health checks after cleaning up the target pool itself. var hcNames []string if hcToDelete != nil { hcNames = append(hcNames, hcToDelete.Name) } if err := gce.DeleteExternalTargetPoolAndChecks(apiService, loadBalancerName, gce.region, clusterID, hcNames...); err != nil { return nil, fmt.Errorf("failed to delete existing target pool for load balancer (%s) update: %v", lbRefStr, err) } glog.Infof("ensureExternalLoadBalancer(%s): Deleted target pool.", lbRefStr) } // Once we've deleted the resources (if necessary), build them back up (or for // the first time if they're new). if tpNeedsUpdate { createInstances := hosts if len(hosts) > maxTargetPoolCreateInstances { createInstances = createInstances[:maxTargetPoolCreateInstances] } // Pass healthchecks to createTargetPool which needs them as health check links in the target pool if err := gce.createTargetPool(apiService, loadBalancerName, serviceName.String(), ipAddressToUse, gce.region, clusterID, createInstances, affinityType, hcToCreate); err != nil { return nil, fmt.Errorf("failed to create target pool for load balancer (%s): %v", lbRefStr, err) } if hcToCreate != nil { glog.Infof("ensureExternalLoadBalancer(%s): Created health checks %v.", lbRefStr, hcToCreate.Name) } if len(hosts) <= maxTargetPoolCreateInstances { glog.Infof("ensureExternalLoadBalancer(%s): Created target pool.", lbRefStr) } else { glog.Infof("ensureExternalLoadBalancer(%s): Created initial target pool (now updating with %d hosts).", lbRefStr, len(hosts)-maxTargetPoolCreateInstances) created := sets.NewString() for _, host := range createInstances { created.Insert(host.makeComparableHostPath()) } if err := gce.updateTargetPool(loadBalancerName, created, hosts); err != nil { return nil, fmt.Errorf("failed to update target pool for load balancer (%s): %v", lbRefStr, err) } glog.Infof("ensureExternalLoadBalancer(%s): Updated target pool (with %d hosts).", lbRefStr, len(hosts)-maxTargetPoolCreateInstances) } </del> <ins> if err := gce.ensureTargetPoolAndHealthCheck(tpExists, tpNeedsRecreation, apiService, loadBalancerName, clusterID, ipAddressToUse, hosts, hcToCreate, hcToDelete); err != nil { return nil, err </ins> } <del> if tpNeedsUpdate || fwdRuleNeedsUpdate { </del> <ins> if tpNeedsRecreation || fwdRuleNeedsUpdate { </ins> glog.Infof("ensureExternalLoadBalancer(%s): Creating forwarding rule, IP %s (tier: %s).", lbRefStr, ipAddressToUse, netTier) if err := createForwardingRule(gce, loadBalancerName, serviceName.String(), gce.region, ipAddressToUse, gce.targetPoolURL(loadBalancerName), ports, netTier); err != nil { return nil, fmt.Errorf("failed to create forwarding rule for load balancer (%s): %v", lbRefStr, err)

} loadBalancerName := cloudprovider.GetLoadBalancerName(service) <del> pool, err := gce.GetTargetPool(loadBalancerName, gce.region) if err != nil { return err } existing := sets.NewString() for _, instance := range pool.Instances { existing.Insert(hostURLToComparablePath(instance)) } return gce.updateTargetPool(loadBalancerName, existing, hosts) </del> <ins> return gce.updateTargetPool(loadBalancerName, hosts) </ins> } // ensureExternalLoadBalancerDeleted is the external implementation of LoadBalancer.EnsureLoadBalancerDeleted

return false, fmt.Errorf("requested ip %q is neither static nor assigned to the LB", requestedIP) } <del> func (gce *GCECloud) createTargetPool(svc *v1.Service, name, serviceName, ipAddress, region, clusterID string, hosts []*gceInstance, affinityType v1.ServiceAffinity, hc *compute.HttpHealthCheck) error { </del> <ins> func (gce *GCECloud) ensureTargetPoolAndHealthCheck(tpExists, tpNeedsRecreation bool, svc *v1.Service, loadBalancerName, clusterID, ipAddressToUse string, hosts []*gceInstance, hcToCreate, hcToDelete *compute.HttpHealthCheck) error { serviceName := types.NamespacedName{Namespace: svc.Namespace, Name: svc.Name} lbRefStr := fmt.Sprintf("%v(%v)", loadBalancerName, serviceName) if tpExists && tpNeedsRecreation { // Pass healthchecks to DeleteExternalTargetPoolAndChecks to cleanup health checks after cleaning up the target pool itself. var hcNames []string if hcToDelete != nil { hcNames = append(hcNames, hcToDelete.Name) } if err := gce.DeleteExternalTargetPoolAndChecks(svc, loadBalancerName, gce.region, clusterID, hcNames...); err != nil { return fmt.Errorf("failed to delete existing target pool for load balancer (%s) update: %v", lbRefStr, err) } glog.Infof("ensureTargetPoolAndHealthCheck(%s): Deleted target pool.", lbRefStr) } // Once we've deleted the resources (if necessary), build them back up (or for // the first time if they're new). if tpNeedsRecreation { createInstances := hosts if len(hosts) > maxTargetPoolCreateInstances { createInstances = createInstances[:maxTargetPoolCreateInstances] } if err := gce.createTargetPoolAndHealthCheck(svc, loadBalancerName, serviceName.String(), ipAddressToUse, gce.region, clusterID, createInstances, hcToCreate); err != nil { return fmt.Errorf("failed to create target pool for load balancer (%s): %v", lbRefStr, err) } if hcToCreate != nil { glog.Infof("ensureTargetPoolAndHealthCheck(%s): Created health checks %v.", lbRefStr, hcToCreate.Name) } if len(hosts) <= maxTargetPoolCreateInstances { glog.Infof("ensureTargetPoolAndHealthCheck(%s): Created target pool.", lbRefStr) } else { glog.Infof("ensureTargetPoolAndHealthCheck(%s): Created initial target pool (now updating the remaining %d hosts).", lbRefStr, len(hosts)-maxTargetPoolCreateInstances) if err := gce.updateTargetPool(loadBalancerName, hosts); err != nil { return fmt.Errorf("failed to update target pool for load balancer (%s): %v", lbRefStr, err) } glog.Infof("ensureTargetPoolAndHealthCheck(%s): Updated target pool (with %d hosts).", lbRefStr, len(hosts)-maxTargetPoolCreateInstances) } } else if tpExists { // Ensure hosts are updated even if there is no other changes required on target pool. if err := gce.updateTargetPool(loadBalancerName, hosts); err != nil { return fmt.Errorf("failed to update target pool for load balancer (%s): %v", lbRefStr, err) } glog.Infof("ensureTargetPoolAndHealthCheck(%s): Updated target pool (with %d hosts).", lbRefStr, len(hosts)) } return nil } func (gce *GCECloud) createTargetPoolAndHealthCheck(svc *v1.Service, name, serviceName, ipAddress, region, clusterID string, hosts []*gceInstance, hc *compute.HttpHealthCheck) error { </ins> // health check management is coupled with targetPools to prevent leaks. A // target pool is the only thing that requires a health check, so we delete // associated checks on teardown, and ensure checks on setup.

Name:            name, Description:     fmt.Sprintf(`{"kubernetes.io/service-name":"%s"}`, serviceName), Instances:       instances, <del> SessionAffinity: translateAffinityType(affinityType), </del> <ins> SessionAffinity: translateAffinityType(svc.Spec.SessionAffinity), </ins> HealthChecks:    hcLinks, }

return nil } <del> func (gce *GCECloud) updateTargetPool(loadBalancerName string, existing sets.String, hosts []*gceInstance) error { </del> <ins> func (gce *GCECloud) updateTargetPool(loadBalancerName string, hosts []*gceInstance) error { pool, err := gce.GetTargetPool(loadBalancerName, gce.region) if err != nil { return err } existing := sets.NewString() for _, instance := range pool.Instances { existing.Insert(hostURLToComparablePath(instance)) } </ins> var toAdd []*compute.InstanceReference var toRemove []*compute.InstanceReference for _, host := range hosts {

// Doesn't check whether the hosts have changed, since host updating is handled // separately. <del> func (gce *GCECloud) targetPoolNeedsUpdate(name, region string, affinityType v1.ServiceAffinity) (exists bool, needsUpdate bool, err error) { </del> <ins> func (gce *GCECloud) targetPoolNeedsRecreation(name, region string, affinityType v1.ServiceAffinity) (exists bool, needsRecreation bool, err error) { </ins> tp, err := gce.GetTargetPool(name, region) if err != nil { if isHTTPErrorCode(err, http.StatusNotFound) {