Skip to content

Commit 155fa38

Browse files
committed
fix(e2e): eliminate race between VIP finalizer addition and WaitToBeReady
Root cause: handleAddVirtualIP called createOrUpdateVipCR to set Status.V4ip (triggering WaitToBeReady), but the finalizer was only added later in handleUpdateVirtualIP (triggered by a subsequent update event). This created a race window where CreateSync could return a VIP object with V4ip set but without the finalizer. Fix 1 (controller): In createOrUpdateVipCR's else branch, add the finalizer atomically in the same Update() call that sets spec/status, so the VIP is fully initialized in one API operation. Fix 2 (test framework): Update WaitToBeReady to require both an IP address AND the controller finalizer before declaring a VIP ready, ensuring tests only proceed with a fully-initialized VIP. Fix 3 (test): Add ginkgo.DeferCleanup for testVipName inside the It block so the VIP is deleted even on test failure, preventing the AfterEach subnetClient.DeleteSync from timing out. Signed-off-by: Mengxin Liu <liumengxinfly@gmail.com>
1 parent aa67657 commit 155fa38

File tree

3 files changed

+20
-4
lines changed

3 files changed

+20
-4
lines changed

pkg/controller/vip.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,8 +424,14 @@ func (c *Controller) createOrUpdateVipCR(key, ns, subnet, v4ip, v6ip, mac string
424424
vip.Status.V6ip = v6ip
425425
vip.Status.Mac = mac
426426
vip.Status.Type = vip.Spec.Type
427-
// TODO:// Ready = true as subnet.Status.Ready
428-
// Update with labels, spec and status in one call
427+
428+
// Ensure finalizer is added atomically with status initialization,
429+
// preventing a race where WaitToBeReady returns (V4ip is set) before
430+
// handleUpdateVirtualIP has a chance to add the finalizer.
431+
controllerutil.RemoveFinalizer(vip, util.DeprecatedFinalizerName)
432+
controllerutil.AddFinalizer(vip, util.KubeOVNControllerFinalizer)
433+
434+
// Update with labels, spec, status, and finalizer in one call
429435
if _, err := c.config.KubeOvnClient.KubeovnV1().Vips().Update(context.Background(), vip, metav1.UpdateOptions{}); err != nil {
430436
err := fmt.Errorf("failed to update vip '%s', %w", key, err)
431437
klog.Error(err)

test/e2e/framework/vip.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"slices"
78
"time"
89

910
"github.com/onsi/ginkgo/v2"
@@ -58,10 +59,14 @@ func (c *VipClient) CreateSync(vip *apiv1.Vip) *apiv1.Vip {
5859
}
5960

6061
// WaitToBeReady returns whether the ovn vip is ready within timeout.
62+
// A VIP is considered ready when it has an IP assigned AND has the controller finalizer,
63+
// ensuring the controller has fully processed the VIP before tests proceed.
6164
func (c *VipClient) WaitToBeReady(name string, timeout time.Duration) bool {
6265
Logf("Waiting up to %v for ovn vip %s to be ready", timeout, name)
6366
for start := time.Now(); time.Since(start) < timeout; time.Sleep(poll) {
64-
if c.Get(name).Status.V4ip != "" || c.Get(name).Status.V6ip != "" {
67+
vip := c.Get(name)
68+
if (vip.Status.V4ip != "" || vip.Status.V6ip != "") &&
69+
slices.Contains(vip.GetFinalizers(), util.KubeOVNControllerFinalizer) {
6570
Logf("ovn vip %s is ready", name)
6671
return true
6772
}

test/e2e/vip/e2e_test.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,13 @@ var _ = framework.Describe("[group:vip]", func() {
219219
testVipName := "test-vip-finalizer-" + framework.RandomSuffix()
220220
testVip := makeOvnVip(namespaceName, testVipName, subnetName, "", "", "")
221221
testVip = vipClient.CreateSync(testVip)
222+
// Ensure the VIP is cleaned up even if the test fails early,
223+
// otherwise the subnet deletion in AfterEach will time out.
224+
ginkgo.DeferCleanup(func() {
225+
vipClient.DeleteSync(testVipName)
226+
})
222227

223-
// Verify VIP has finalizer
228+
// Verify VIP has finalizer (CreateSync now waits for both IP and finalizer)
224229
framework.ExpectContainElement(testVip.Finalizers, util.KubeOVNControllerFinalizer)
225230

226231
ginkgo.By("3. Wait for subnet status to be updated after VIP creation")

0 commit comments

Comments
 (0)