Skip to content

Commit ec865ef

Browse files
committed
fixup! refactor into designed declarative approach
1 parent 9327597 commit ec865ef

File tree

1 file changed

+100
-68
lines changed

1 file changed

+100
-68
lines changed

providers/gce/gce_loadbalancer_external.go

Lines changed: 100 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -121,37 +121,50 @@ func (g *Cloud) ensureExternalLoadBalancer(clusterName string, clusterID string,
121121
return nil, fmt.Errorf("load balancer with multiple protocols (%s) is not supported when AlphaFeatureMultiProtocolLB is disabled", strings.Join(protocols, ","))
122122
}
123123

124+
existingFRs, err := g.getExistingForwardingRules(loadBalancerName)
125+
if err != nil {
126+
return nil, fmt.Errorf("error getting existing forwarding rules for %s: %v", loadBalancerName, err)
127+
}
128+
124129
var fwdRuleIP string
125130
// We need to determine the IP address for all forwarding rules.
126131
// We check if any of the forwarding rules already exist and use their IP.
127-
for protocol := range groupedPorts {
128-
frName := g.getProtocolForwardingRuleName(loadBalancerName, protocol)
129-
fwd, err := g.GetRegionForwardingRule(frName, g.region)
130-
if err != nil && !isNotFound(err) {
131-
return nil, err
132-
}
133-
if fwd != nil {
134-
fwdRuleIP = fwd.IPAddress
135-
break
136-
}
137-
}
138-
// If no forwarding rule exists, check for the old one.
139-
if fwdRuleIP == "" {
140-
fwd, err := g.GetRegionForwardingRule(loadBalancerName, g.region)
141-
if err != nil && !isNotFound(err) {
142-
return nil, err
143-
}
144-
if fwd != nil {
145-
fwdRuleIP = fwd.IPAddress
132+
for _, fr := range existingFRs {
133+
if fr.IPAddress != "" {
134+
if fwdRuleIP == "" {
135+
fwdRuleIP = fr.IPAddress
136+
} else if fwdRuleIP != fr.IPAddress {
137+
return nil, fmt.Errorf("found multiple forwarding rules with different IP addresses (%s and %s) for load balancer %s", fwdRuleIP, fr.IPAddress, loadBalancerName)
138+
}
146139
}
147140
}
148141

149142
// Make sure we know which IP address will be used and have properly reserved
150143
// it as static before moving forward with the rest of our operations.
144+
//
145+
// We use static IP addresses when updating a load balancer to ensure that we
146+
// can replace the load balancer's other components without changing the
147+
// address its service is reachable on. We do it this way rather than always
148+
// keeping the static IP around even though this is more complicated because
149+
// it makes it less likely that we'll run into quota issues. Only 7 static
150+
// IP addresses are allowed per region by default.
151+
//
152+
// We could let an IP be allocated for us when the forwarding rule is created,
153+
// but we need the IP to set up the firewall rule, and we want to keep the
154+
// forwarding rule creation as the last thing that needs to be done in this
155+
// function in order to maintain the invariant that "if the forwarding rule
156+
// exists, the LB has been fully created".
151157
ipAddressToUse := ""
152-
isUserOwnedIP := false
153-
isSafeToReleaseIP := false
154158

159+
// Through this process we try to keep track of whether it is safe to
160+
// release the IP that was allocated. If the user specifically asked for
161+
// an IP, we assume they are managing it themselves. Otherwise, we will
162+
// release the IP in case of early-terminating failure or upon successful
163+
// creating of the LB.
164+
// TODO(#36535): boil this logic down into a set of component functions
165+
// and key the flag values off of errors returned.
166+
isUserOwnedIP := false // if this is set, we never release the IP
167+
isSafeToReleaseIP := false
155168
defer func() {
156169
if isUserOwnedIP {
157170
return
@@ -195,6 +208,12 @@ func (g *Cloud) ensureExternalLoadBalancer(clusterName string, clusterID string,
195208
ipAddressToUse = ipAddr
196209
}
197210

211+
// Now we have the IP address we can build the desired forwarding rules.
212+
desiredFRs, err := g.buildDesiredForwardingRules(loadBalancerName, serviceName.String(), ipAddressToUse, g.targetPoolURL(loadBalancerName), apiService, netTier, existingFRs)
213+
if err != nil {
214+
return nil, fmt.Errorf("error building desired forwarding rules for %s: %v", loadBalancerName, err)
215+
}
216+
198217
// Deal with the firewall next. The reason we do this here rather than last
199218
// is because the forwarding rule is used as the indicator that the load
200219
// balancer is fully created - it's what getLoadBalancer checks for.
@@ -257,33 +276,42 @@ func (g *Cloud) ensureExternalLoadBalancer(clusterName string, clusterID string,
257276
} else {
258277
klog.V(4).Infof("ensureExternalLoadBalancer(%s): Service needs nodes health checks.", lbRefStr)
259278
if hcLocalTrafficExisting != nil {
279+
// This logic exists to detect a transition from OnlyLocal to non-OnlyLocal service
280+
// and turn on the tpNeedsRecreation flag to delete/recreate fwdrule/tpool updating the
281+
// target pool to use nodes health check.
260282
klog.V(2).Infof("ensureExternalLoadBalancer(%s): Updating from local traffic health checks to nodes health checks.", lbRefStr)
261283
hcToDelete = hcLocalTrafficExisting
262284
tpNeedsRecreation = true
263285
}
264286
hcToCreate = makeHTTPHealthCheck(MakeNodesHealthCheckName(clusterID), GetNodesHealthCheckPath(), GetNodesHealthCheckPort())
265287
}
266288

267-
if err := g.ensureTargetPoolAndHealthCheck(tpExists, tpNeedsRecreation, apiService, loadBalancerName, clusterID, ipAddressToUse, hosts, hcToCreate, hcToDelete); err != nil {
268-
return nil, err
269-
}
270-
271-
// Forwarding rule logic
272-
// We build the desired forwarding rules and then sync them with what exists.
273-
existingFRs, err := g.getExistingForwardingRules(loadBalancerName)
274-
if err != nil {
275-
return nil, fmt.Errorf("error getting existing forwarding rules for %s: %v", loadBalancerName, err)
276-
}
289+
// Now we get to some slightly more interesting logic.
290+
// First, neither target pools nor forwarding rules can be updated in place -
291+
// they have to be deleted and recreated.
292+
// Second, forwarding rules are layered on top of target pools in that you
293+
// can't delete a target pool that's currently in use by a forwarding rule.
294+
// Thus, we have to tear down a forwarding rule if either it or the target
295+
// pool needs to be updated.
296+
if len(existingFRs) > 0 && tpNeedsRecreation {
297+
// Begin critical section. If we have to delete the forwarding rule,
298+
// and something should fail before we recreate it, don't release the
299+
// IP. That way we can come back to it later.
300+
isSafeToReleaseIP = false
301+
302+
for _, fr := range existingFRs {
303+
if err := g.DeleteRegionForwardingRule(fr.Name, g.region); err != nil && !isNotFound(err) {
304+
return nil, fmt.Errorf("failed to delete existing forwarding rule for load balancer (%s) update: %v", lbRefStr, err)
305+
}
306+
}
307+
klog.Infof("ensureExternalLoadBalancer(%s): Deleted forwarding rule(s).", lbRefStr)
277308

278-
// Get the old forwarding rule for backward compatibility.
279-
var oldFwdRule *compute.ForwardingRule
280-
if fwd, ok := existingFRs[loadBalancerName]; ok {
281-
oldFwdRule = fwd
309+
// Clear the existing forwarding rules so we don't try to delete them again.
310+
existingFRs = nil
282311
}
283312

284-
desiredFRs, err := g.buildDesiredForwardingRules(loadBalancerName, serviceName.String(), ipAddressToUse, g.targetPoolURL(loadBalancerName), apiService, netTier, oldFwdRule)
285-
if err != nil {
286-
return nil, fmt.Errorf("error building desired forwarding rules for %s: %v", loadBalancerName, err)
313+
if err := g.ensureTargetPoolAndHealthCheck(tpExists, tpNeedsRecreation, apiService, loadBalancerName, clusterID, ipAddressToUse, hosts, hcToCreate, hcToDelete); err != nil {
314+
return nil, err
287315
}
288316

289317
// Delete unwanted forwarding rules.
@@ -299,28 +327,25 @@ func (g *Cloud) ensureExternalLoadBalancer(clusterName string, clusterID string,
299327
// Create or update forwarding rules.
300328
for _, desiredFR := range desiredFRs {
301329
existingFR, exists := existingFRs[desiredFR.Name]
302-
303-
needsUpdate := false
304330
if exists {
305-
if !g.forwardingRulesEqual(existingFR, desiredFR) {
306-
needsUpdate = true
331+
if g.forwardingRulesEqual(existingFR, desiredFR) {
332+
continue
307333
}
308334
}
309335

310-
if !exists || needsUpdate || tpNeedsRecreation {
311-
if exists {
312-
klog.Infof("ensureExternalLoadBalancer(%s): Deleting forwarding rule %s to update.", lbRefStr, desiredFR.Name)
313-
if err := g.DeleteRegionForwardingRule(desiredFR.Name, g.region); err != nil && !isNotFound(err) {
314-
return nil, err
315-
}
316-
}
317-
klog.Infof("ensureExternalLoadBalancer(%s): Creating forwarding rule %s for protocol %s, IP %s (tier: %s).", lbRefStr, desiredFR.Name, desiredFR.IPProtocol, ipAddressToUse, netTier)
318-
// The desiredFR is a complete spec, so we can just pass it to CreateRegionForwardingRule.
319-
if err := g.CreateRegionForwardingRule(desiredFR, g.region); err != nil && !isHTTPErrorCode(err, http.StatusConflict) {
320-
return nil, fmt.Errorf("failed to create forwarding rule for load balancer (%s): %v", lbRefStr, err)
336+
// We can't update a forwarding rule in place, so we have to delete it and recreate it.
337+
if exists {
338+
klog.Infof("ensureExternalLoadBalancer(%s): Deleting forwarding rule %s to update.", lbRefStr, desiredFR.Name)
339+
if err := g.DeleteRegionForwardingRule(desiredFR.Name, g.region); err != nil && !isNotFound(err) {
340+
return nil, err
321341
}
322-
klog.Infof("ensureExternalLoadBalancer(%s): Created forwarding rule %s.", lbRefStr, desiredFR.Name)
323342
}
343+
klog.Infof("ensureExternalLoadBalancer(%s): Creating forwarding rule %s for protocol %s, IP %s (tier: %s).", lbRefStr, desiredFR.Name, desiredFR.IPProtocol, ipAddressToUse, netTier)
344+
// The desiredFR is a complete spec, so we can just pass it to CreateRegionForwardingRule.
345+
if err := g.CreateRegionForwardingRule(desiredFR, g.region); err != nil && !isHTTPErrorCode(err, http.StatusConflict) {
346+
return nil, fmt.Errorf("failed to create forwarding rule for load balancer (%s): %v", lbRefStr, err)
347+
}
348+
klog.Infof("ensureExternalLoadBalancer(%s): Created forwarding rule %s.", lbRefStr, desiredFR.Name)
324349
}
325350

326351
isSafeToReleaseIP = true
@@ -339,19 +364,32 @@ func (g *Cloud) getExistingForwardingRules(loadBalancerName string) (map[string]
339364

340365
existingFRs := make(map[string]*compute.ForwardingRule)
341366
for _, fr := range frs {
342-
if strings.HasPrefix(fr.Name, loadBalancerName) {
367+
isMatch := false
368+
if fr.Name == loadBalancerName {
369+
isMatch = true
370+
} else if fr.Name == g.getProtocolForwardingRuleName(loadBalancerName, v1.Protocol(fr.IPProtocol)) {
371+
isMatch = true
372+
}
373+
374+
if isMatch {
343375
existingFRs[fr.Name] = fr
344376
}
345377
}
346378
return existingFRs, nil
347379
}
348380

349381
// buildDesiredForwardingRules builds the desired forwarding rules for the given load balancer.
350-
func (g *Cloud) buildDesiredForwardingRules(loadBalancerName, serviceName, ipAddress, targetPoolURL string, apiService *v1.Service, netTier cloud.NetworkTier, oldFwdRule *compute.ForwardingRule) (map[string]*compute.ForwardingRule, error) {
382+
func (g *Cloud) buildDesiredForwardingRules(loadBalancerName, serviceName, ipAddress, targetPoolURL string, apiService *v1.Service, netTier cloud.NetworkTier, existingFRs map[string]*compute.ForwardingRule) (map[string]*compute.ForwardingRule, error) {
351383
desiredFRs := make(map[string]*compute.ForwardingRule)
352384
groupedPorts := groupPortsByProtocol(apiService.Spec.Ports)
353385
desc := makeServiceDescription(serviceName)
354386

387+
// Get the old forwarding rule for backward compatibility.
388+
var oldFwdRule *compute.ForwardingRule
389+
if fwd, ok := existingFRs[loadBalancerName]; ok {
390+
oldFwdRule = fwd
391+
}
392+
355393
for protocol, protocolPorts := range groupedPorts {
356394
var frName string
357395
if g.AlphaFeatureGate.Enabled(AlphaFeatureMultiProtocolLB) {
@@ -415,7 +453,6 @@ func (g *Cloud) forwardingRulesEqual(existing, desired *compute.ForwardingRule)
415453
return true
416454
}
417455

418-
419456
// updateExternalLoadBalancer is the external implementation of LoadBalancer.UpdateLoadBalancer.
420457
func (g *Cloud) updateExternalLoadBalancer(clusterName string, service *v1.Service, nodes []*v1.Node) error {
421458
// Process services with LoadBalancerClass "networking.gke.io/l4-regional-external-legacy" used for this controller.
@@ -500,17 +537,16 @@ func (g *Cloud) ensureExternalLoadBalancerDeleted(clusterName, clusterID string,
500537
}
501538

502539
// TODO: Always or just with alpha feature flag?
503-
frs, err := g.ListRegionForwardingRules(g.region)
540+
existingFRs, err := g.getExistingForwardingRules(loadBalancerName)
504541
if err != nil {
505542
return err
506543
}
544+
507545
var deleteErrs []error
508-
for _, fr := range frs {
509-
if strings.HasPrefix(fr.Name, loadBalancerName) {
510-
klog.Infof("ensureExternalLoadBalancerDeleted(%s): Deleting forwarding rule %s.", lbRefStr, fr.Name)
511-
if err := ignoreNotFound(g.DeleteRegionForwardingRule(fr.Name, g.region)); err != nil {
512-
deleteErrs = append(deleteErrs, err)
513-
}
546+
for _, fr := range existingFRs {
547+
klog.Infof("ensureExternalLoadBalancerDeleted(%s): Deleting forwarding rule %s.", lbRefStr, fr.Name)
548+
if err := ignoreNotFound(g.DeleteRegionForwardingRule(fr.Name, g.region)); err != nil {
549+
deleteErrs = append(deleteErrs, err)
514550
}
515551
}
516552
if len(deleteErrs) > 0 {
@@ -908,8 +944,6 @@ func (g *Cloud) ensureHTTPHealthCheck(name, path string, port int32) (hc *comput
908944
return hc, nil
909945
}
910946

911-
912-
913947
// Doesn't check whether the hosts have changed, since host updating is handled
914948
// separately.
915949
func (g *Cloud) targetPoolNeedsRecreation(name, region string, affinityType v1.ServiceAffinity) (exists bool, needsRecreation bool, err error) {
@@ -1145,8 +1179,6 @@ func (g *Cloud) ensureHTTPHealthCheckFirewall(svc *v1.Service, serviceName, ipAd
11451179
return nil
11461180
}
11471181

1148-
1149-
11501182
func (g *Cloud) createFirewall(svc *v1.Service, name, desc, destinationIP string, sourceRanges utilnet.IPNetSet, ports []v1.ServicePort, hosts []*gceInstance) error {
11511183
firewall, err := g.firewallObject(name, desc, destinationIP, sourceRanges, ports, hosts)
11521184
if err != nil {

0 commit comments

Comments
 (0)