Skip to content

Commit 1c25e8a

Browse files
committed
Tag floating IPs to try to prevent orphaned IPs
1 parent 7ff1d63 commit 1c25e8a

File tree

5 files changed

+86
-146
lines changed

5 files changed

+86
-146
lines changed

api/v1alpha8/openstackfloatingippool_types.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ limitations under the License.
1717
package v1alpha8
1818

1919
import (
20+
"fmt"
21+
2022
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2123
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
2224
)
@@ -117,6 +119,10 @@ func (r *OpenStackFloatingIPPool) SetConditions(conditions clusterv1.Conditions)
117119
r.Status.Conditions = conditions
118120
}
119121

122+
func (r *OpenStackFloatingIPPool) GetFloatingIPTag() string {
123+
return fmt.Sprintf("cluster-api-provider-openstack-fip-pool-%s", r.Name)
124+
}
125+
120126
func init() {
121127
SchemeBuilder.Register(&OpenStackFloatingIPPool{}, &OpenStackFloatingIPPoolList{})
122128
}

config/rbac/role.yaml

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -137,23 +137,3 @@ rules:
137137
- list
138138
- update
139139
- watch
140-
- apiGroups:
141-
- ipam.cluster.x-k8x.io.cluster.x-k8s.io
142-
resources:
143-
- ipaddresses
144-
verbs:
145-
- create
146-
- delete
147-
- get
148-
- list
149-
- patch
150-
- update
151-
- watch
152-
- apiGroups:
153-
- ipam.cluster.x-k8x.io.cluster.x-k8s.io
154-
resources:
155-
- ipaddresses/status
156-
verbs:
157-
- get
158-
- patch
159-
- update

controllers/ipaddress_controller.go

Lines changed: 0 additions & 125 deletions
This file was deleted.

controllers/openstackfloatingippool_controller.go

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ import (
2020
"context"
2121
"errors"
2222
"fmt"
23+
"time"
2324

2425
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external"
2526
corev1 "k8s.io/api/core/v1"
2627
apierrors "k8s.io/apimachinery/pkg/api/errors"
2728
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2829
"k8s.io/apimachinery/pkg/runtime"
30+
"k8s.io/apimachinery/pkg/util/wait"
2931
"k8s.io/client-go/tools/record"
3032
"k8s.io/utils/pointer"
3133
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
@@ -47,6 +49,15 @@ const (
4749
openStackFloatingIPPool = "OpenStackFloatingIPPool"
4850
)
4951

52+
var (
53+
backoff = wait.Backoff{
54+
Steps: 4,
55+
Duration: 10 * time.Millisecond,
56+
Factor: 5.0,
57+
Jitter: 0.1,
58+
}
59+
)
60+
5061
// OpenStackFloatingIPPoolReconciler reconciles a OpenStackFloatingIPPool object.
5162
type OpenStackFloatingIPPoolReconciler struct {
5263
Client client.Client
@@ -158,7 +169,16 @@ func (r *OpenStackFloatingIPPoolReconciler) Reconcile(ctx context.Context, req c
158169
},
159170
}
160171

161-
if err = r.Client.Create(ctx, ipAddress); err != nil {
172+
// Retry creating the IPAddress object
173+
err = wait.ExponentialBackoff(backoff, func() (bool, error) {
174+
if err := r.Client.Create(ctx, ipAddress); err != nil {
175+
return false, nil
176+
}
177+
return true, nil
178+
})
179+
if err != nil {
180+
// If we failed to create the IPAddress, there might be an IP leak in OpenStack if we also failed to tag the IP after creation
181+
scope.Logger().Error(err, "Failed to create IPAddress", "ip", ip)
162182
return ctrl.Result{}, err
163183
}
164184
} else {
@@ -283,6 +303,7 @@ func (r *OpenStackFloatingIPPoolReconciler) reconcileIPAddresses(ctx context.Con
283303
}
284304

285305
func (r *OpenStackFloatingIPPoolReconciler) getIP(scope scope.Scope, pool *infrav1.OpenStackFloatingIPPool) (string, error) {
306+
// There's a potential leak of IPs here, if the reconcile loop fails after we claim an IP but before we create the IPAddress object.
286307
var ip string
287308

288309
networkingService, err := networking.NewService(scope)
@@ -291,6 +312,21 @@ func (r *OpenStackFloatingIPPoolReconciler) getIP(scope scope.Scope, pool *infra
291312
return "", err
292313
}
293314

315+
// Get tagged floating IPs and add them to the available IPs if they are not present in either the available IPs or the claimed IPs
316+
// This is done to prevent leaking floating IPs if to prevent leaking floating IPs if the floating IP was created but the IPAddress object was not
317+
taggedFIPs, err := networkingService.GetFloatingIPsByTag(pool.GetFloatingIPTag())
318+
if err != nil {
319+
scope.Logger().Error(err, "Failed to get floating IPs by tag", "pool", pool.Name)
320+
return "", err
321+
}
322+
for _, taggedIp := range taggedFIPs {
323+
if contains(pool.Status.AvailableIPs, taggedIp.FloatingIP) || contains(pool.Status.ClaimedIPs, taggedIp.FloatingIP) {
324+
continue
325+
}
326+
scope.Logger().Info("Tagged floating IP found that was not known to the pool, adding it to the pool", "ip", taggedIp.FloatingIP)
327+
pool.Status.AvailableIPs = append(pool.Status.AvailableIPs, taggedIp.FloatingIP)
328+
}
329+
294330
if len(pool.Status.AvailableIPs) > 0 {
295331
ip = pool.Status.AvailableIPs[0]
296332
pool.Status.AvailableIPs = pool.Status.AvailableIPs[1:]
@@ -315,6 +351,22 @@ func (r *OpenStackFloatingIPPoolReconciler) getIP(scope scope.Scope, pool *infra
315351
}
316352
return "", err
317353
}
354+
defer func() {
355+
tag := pool.GetFloatingIPTag()
356+
357+
err := wait.ExponentialBackoff(backoff, func() (bool, error) {
358+
if err := networkingService.TagFloatingIP(fp.FloatingIP, tag); err != nil {
359+
scope.Logger().Error(err, "Failed to tag floating, retrying", "ip", fp.FloatingIP, "tag", tag)
360+
return false, err
361+
}
362+
return true, nil
363+
})
364+
365+
if err != nil {
366+
scope.Logger().Error(err, "Failed to tag floating IP", "ip", fp.FloatingIP, "tag", tag)
367+
}
368+
}()
369+
318370
conditions.MarkTrue(pool, infrav1.OpenstackFloatingIPPoolReadyCondition)
319371

320372
ip = fp.FloatingIP

pkg/cloud/services/networking/floatingip.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,33 @@ func (s *Service) CreateFloatingIPForPool(pool *infrav1.OpenStackFloatingIPPool)
8787
return fp, nil
8888
}
8989

90+
func (s *Service) TagFloatingIP(ip string, tag string) error {
91+
fip, err := s.GetFloatingIP(ip)
92+
if err != nil {
93+
return err
94+
}
95+
if fip == nil {
96+
return nil
97+
}
98+
99+
mc := metrics.NewMetricPrometheusContext("floating_ip", "update")
100+
_, err = s.client.ReplaceAllAttributesTags("floatingips", fip.ID, attributestags.ReplaceAllOpts{
101+
Tags: []string{tag},
102+
})
103+
if mc.ObserveRequest(err) != nil {
104+
return err
105+
}
106+
return nil
107+
}
108+
109+
func (s *Service) GetFloatingIPsByTag(tag string) ([]floatingips.FloatingIP, error) {
110+
fipList, err := s.client.ListFloatingIP(floatingips.ListOpts{Tags: tag})
111+
if err != nil {
112+
return nil, err
113+
}
114+
return fipList, nil
115+
}
116+
90117
func (s *Service) GetFloatingIP(ip string) (*floatingips.FloatingIP, error) {
91118
fpList, err := s.client.ListFloatingIP(floatingips.ListOpts{FloatingIP: ip})
92119
if err != nil {

0 commit comments

Comments
 (0)