Skip to content

Commit 7f4339b

Browse files
author
Ricardo Pchevuzinske Katz
committed
Validate the cache contains the created address before moving on
IPAM controller relies on controller runtime client, which is cached, to list the existing address before allocating a new IP. When the cache is too slow to reflect the changes, we may end up with an IPAddressClaim reconciliation happening before the previous reconciliation address acknowledge by the cache, which may end with duplicated IP address allocations. This change enforces that the Reconcile function waits for the cached client to be aware of the IPAddress before allowing the reconciliation to move on to the next reconciliation request
1 parent 01f9a1e commit 7f4339b

File tree

1 file changed

+22
-0
lines changed

1 file changed

+22
-0
lines changed

pkg/ipamutil/reconciler.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ package ipamutil
33
import (
44
"context"
55
"fmt"
6+
"time"
67

78
"github.com/pkg/errors"
89
corev1 "k8s.io/api/core/v1"
910
apierrors "k8s.io/apimachinery/pkg/api/errors"
1011
"k8s.io/apimachinery/pkg/runtime"
1112
"k8s.io/apimachinery/pkg/types"
1213
kerrors "k8s.io/apimachinery/pkg/util/errors"
14+
"k8s.io/apimachinery/pkg/util/wait"
1315
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
1416
ipamv1 "sigs.k8s.io/cluster-api/exp/ipam/api/v1beta1"
1517
clusterutil "sigs.k8s.io/cluster-api/util"
@@ -245,6 +247,26 @@ func (r *ClaimReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ct
245247
return unwrapResult(res), err
246248
}
247249

250+
// We need to ensure the address is properly watched by controller-runtime client (as it is cached) before moving
251+
// to the next reconciliation request.
252+
// This is required, otherwise if the next "EnsureAddress" is called too fast, or the controller-runtime cached client informer
253+
// is too slow, we may end up not seeing that the IP Address is being used and can provide duplicated IPs.
254+
err = wait.PollUntilContextTimeout(ctx, 5*time.Millisecond, 5*time.Second, true, func(ctx context.Context) (bool, error) {
255+
key := client.ObjectKeyFromObject(&address)
256+
if err := r.Client.Get(ctx, key, &ipamv1.IPAddress{}); err != nil {
257+
if apierrors.IsNotFound(err) {
258+
return false, nil
259+
}
260+
return false, err
261+
}
262+
return true, nil
263+
})
264+
265+
if err != nil {
266+
log.Error(err, "failed waiting for IPAddress %s/%s to be visible in the cache after create", "namespace", address.GetNamespace(), "name", address.GetName())
267+
return ctrl.Result{}, errors.Wrapf(err, "failed waiting for IPAddress %s/%s to be visible in the cache after create", address.GetNamespace(), address.GetName())
268+
}
269+
248270
log.Info(fmt.Sprintf("IPAddress %s/%s (%s) has been %s", address.Namespace, address.Name, address.Spec.Address, operationResult),
249271
"IPAddressClaim", fmt.Sprintf("%s/%s", claim.Namespace, claim.Name))
250272

0 commit comments

Comments
 (0)