Skip to content

Commit 6b9064a

Browse files
hrakclaude
andcommitted
fix: Validate target IP before tearing down existing load balancer
Add a pre-flight validatePublicIPAvailable() check in EnsureLoadBalancer when switching to a user-specified IP. This prevents leaving the service in a broken state if the target IP is invalid or unavailable — the old config is preserved and the error is returned immediately. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0ff04f3 commit 6b9064a

1 file changed

Lines changed: 32 additions & 1 deletion

File tree

cloudstack/cloudstack_loadbalancer.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,13 @@ func (cs *CSCloud) EnsureLoadBalancer(ctx context.Context, clusterName string, s
142142
cs.eventRecorder.Event(service, corev1.EventTypeNormal, "CreatedLoadBalancer", msg)
143143
klog.Info(msg)
144144
} else if service.Spec.LoadBalancerIP != "" && service.Spec.LoadBalancerIP != lb.ipAddr {
145-
// LoadBalancerIP was specified and it's different from the current IP
145+
// LoadBalancerIP was specified and it's different from the current IP.
146+
// Validate the target IP exists before tearing down the old config to avoid
147+
// leaving the service in a broken state if the new IP is invalid.
148+
if err := lb.validatePublicIPAvailable(service.Spec.LoadBalancerIP); err != nil {
149+
return nil, fmt.Errorf("cannot switch load balancer to IP %s: %w", service.Spec.LoadBalancerIP, err)
150+
}
151+
146152
// Release the old IP first
147153
klog.V(4).Infof("Deleting firewall rules for old ip and releasing old load balancer IP %v, switching to specified IP %v", lb.ipAddr, service.Spec.LoadBalancerIP)
148154

@@ -671,6 +677,31 @@ func (lb *loadBalancer) getLoadBalancerIP(loadBalancerIP string) error {
671677
return lb.associatePublicIPAddress()
672678
}
673679

680+
// validatePublicIPAvailable checks that the given IP address exists in CloudStack
681+
// without modifying any load balancer state. Used as a pre-flight check before
682+
// tearing down an existing configuration.
683+
func (lb *loadBalancer) validatePublicIPAvailable(ip string) error {
684+
p := lb.Address.NewListPublicIpAddressesParams()
685+
p.SetIpaddress(ip)
686+
p.SetAllocatedonly(false)
687+
p.SetListall(true)
688+
689+
if lb.projectID != "" {
690+
p.SetProjectid(lb.projectID)
691+
}
692+
693+
l, err := lb.Address.ListPublicIpAddresses(p)
694+
if err != nil {
695+
return fmt.Errorf("error looking up IP address %v: %w", ip, err)
696+
}
697+
698+
if l.Count != 1 {
699+
return fmt.Errorf("IP address %v not found (got %d results)", ip, l.Count)
700+
}
701+
702+
return nil
703+
}
704+
674705
// getPublicIPAddressID retrieves the ID of the given IP, and sets the address and its ID.
675706
func (lb *loadBalancer) getPublicIPAddress(loadBalancerIP string) error {
676707
klog.V(4).Infof("Retrieve load balancer IP details: %v", loadBalancerIP)

0 commit comments

Comments
 (0)