Skip to content

Commit 88675e4

Browse files
committed
fix: requeue on now or in the past
1 parent 518bd8d commit 88675e4

2 files changed

Lines changed: 130 additions & 2 deletions

File tree

internal/controller/domain_controller.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,14 @@ func (r *DomainReconciler) Reconcile(ctx context.Context, req mcreconcile.Reques
147147
}
148148
}
149149

150-
if wake != nil && wake.After(now) {
151-
return ctrl.Result{RequeueAfter: wake.Sub(now).Truncate(time.Second)}, nil
150+
if wake != nil {
151+
// If the wake time is in the future, schedule a requeue after the remaining duration.
152+
if wake.After(now) {
153+
return ctrl.Result{RequeueAfter: wake.Sub(now).Truncate(time.Second)}, nil
154+
}
155+
// If the wake time is now or already in the past, use a minimal positive RequeueAfter
156+
// to avoid the deprecated Requeue flag.
157+
return ctrl.Result{RequeueAfter: 1 * time.Second}, nil
152158
}
153159

154160
return ctrl.Result{}, nil

internal/controller/domain_controller_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,128 @@ func TestRegistration_Apex_UsesRDAPNameservers(t *testing.T) {
556556
assert.True(t, res.RequeueAfter > 0)
557557
}
558558

559+
func TestVerification_RequeueImmediate_WhenWakeDueOrPast(t *testing.T) {
560+
s := runtime.NewScheme()
561+
_ = scheme.AddToScheme(s)
562+
_ = networkingv1alpha.AddToScheme(s)
563+
564+
// Fixed reference time
565+
now := time.Date(2025, 10, 22, 15, 40, 14, 0, time.UTC)
566+
567+
// Domain has existing verification scaffold and next attempt is due NOW
568+
dom := newDomain("default", "due-now", func(d *networkingv1alpha.Domain) {
569+
d.Status.Verification = &networkingv1alpha.DomainVerificationStatus{
570+
DNSRecord: networkingv1alpha.DNSVerificationRecord{
571+
Name: "_dnsverify.example.com",
572+
Type: "TXT",
573+
Content: "token",
574+
},
575+
NextVerificationAttempt: metav1.Time{Time: now},
576+
}
577+
})
578+
579+
cl := fake.NewClientBuilder().WithScheme(s).WithObjects(dom).WithStatusSubresource(dom).Build()
580+
mgr := &fakeMockManager{cl: cl}
581+
582+
// Configure zero retry interval and zero jitter to force nextAttempt == now
583+
operatorConfig := config.NetworkServicesOperator{
584+
DomainVerification: config.DomainVerificationConfig{
585+
RetryIntervals: []config.RetryInterval{{Interval: metav1.Duration{Duration: 0}}},
586+
RetryJitterMaxFactor: 0,
587+
},
588+
DomainRegistration: config.DomainRegistrationConfig{ // prevent nil panics in registration
589+
LookupTimeout: &metav1.Duration{Duration: 3 * time.Second},
590+
RefreshInterval: &metav1.Duration{Duration: time.Hour},
591+
JitterMaxFactor: 0.1,
592+
RetryBackoff: &metav1.Duration{Duration: time.Minute},
593+
},
594+
}
595+
596+
r := &DomainReconciler{
597+
mgr: mgr,
598+
Config: operatorConfig,
599+
timeNow: func() time.Time { return now },
600+
// Stubs to avoid real I/O
601+
httpGet: func(ctx context.Context, url string) ([]byte, *http.Response, error) {
602+
return nil, nil, fmt.Errorf("not implemented")
603+
},
604+
lookupTXT: func(ctx context.Context, name string) ([]string, error) { return nil, &net.DNSError{IsNotFound: true} },
605+
rdapDo: func(ctx context.Context, req *rdap.Request) (*rdap.Response, error) {
606+
return &rdap.Response{Object: &rdap.Domain{LDHName: "example.com"}}, nil
607+
},
608+
lookupNS: func(ctx context.Context, name string) ([]*net.NS, error) { return nil, &net.DNSError{IsNotFound: true} },
609+
lookupIP: func(ctx context.Context, name string) ([]net.IPAddr, error) { return nil, nil },
610+
rdapQueryIP: func(ctx context.Context, ip string) (*rdap.IPNetwork, error) { return nil, nil },
611+
}
612+
613+
res, err := r.Reconcile(context.Background(), mcreconcile.Request{ClusterName: "test", Request: reconcile.Request{NamespacedName: client.ObjectKeyFromObject(dom)}})
614+
assert.NoError(t, err)
615+
616+
// With the fix, we should requeue immediately when wake time is now/past
617+
assert.Equal(t, time.Duration(1*time.Second), res.RequeueAfter)
618+
}
619+
620+
func TestVerification_RequeueImmediate_WhenWakeInPast(t *testing.T) {
621+
s := runtime.NewScheme()
622+
_ = scheme.AddToScheme(s)
623+
_ = networkingv1alpha.AddToScheme(s)
624+
625+
// Fixed reference time
626+
now := time.Date(2025, 10, 22, 15, 40, 14, 0, time.UTC)
627+
628+
// Domain has existing verification scaffold and next attempt was due 1s ago
629+
dom := newDomain("default", "due-past", func(d *networkingv1alpha.Domain) {
630+
d.Status.Verification = &networkingv1alpha.DomainVerificationStatus{
631+
DNSRecord: networkingv1alpha.DNSVerificationRecord{
632+
Name: "_dnsverify.example.com",
633+
Type: "TXT",
634+
Content: "token",
635+
},
636+
NextVerificationAttempt: metav1.Time{Time: now.Add(-1 * time.Second)},
637+
}
638+
})
639+
640+
cl := fake.NewClientBuilder().WithScheme(s).WithObjects(dom).WithStatusSubresource(dom).Build()
641+
mgr := &fakeMockManager{cl: cl}
642+
643+
operatorConfig := config.NetworkServicesOperator{
644+
DomainVerification: config.DomainVerificationConfig{
645+
RetryIntervals: []config.RetryInterval{{Interval: metav1.Duration{Duration: 0}}},
646+
RetryJitterMaxFactor: 0,
647+
},
648+
DomainRegistration: config.DomainRegistrationConfig{
649+
LookupTimeout: &metav1.Duration{Duration: 3 * time.Second},
650+
RefreshInterval: &metav1.Duration{Duration: time.Hour},
651+
JitterMaxFactor: 0.1,
652+
RetryBackoff: &metav1.Duration{Duration: time.Minute},
653+
},
654+
}
655+
656+
r := &DomainReconciler{
657+
mgr: mgr,
658+
Config: operatorConfig,
659+
timeNow: func() time.Time { return now },
660+
// Stubs to avoid real I/O
661+
httpGet: func(ctx context.Context, url string) ([]byte, *http.Response, error) {
662+
return nil, nil, fmt.Errorf("not implemented")
663+
},
664+
lookupTXT: func(ctx context.Context, name string) ([]string, error) { return nil, &net.DNSError{IsNotFound: true} },
665+
rdapDo: func(ctx context.Context, req *rdap.Request) (*rdap.Response, error) {
666+
return &rdap.Response{Object: &rdap.Domain{LDHName: "example.com"}}, nil
667+
},
668+
lookupNS: func(ctx context.Context, name string) ([]*net.NS, error) { return nil, &net.DNSError{IsNotFound: true} },
669+
lookupIP: func(ctx context.Context, name string) ([]net.IPAddr, error) { return nil, nil },
670+
rdapQueryIP: func(ctx context.Context, ip string) (*rdap.IPNetwork, error) { return nil, nil },
671+
}
672+
673+
res, err := r.Reconcile(context.Background(), mcreconcile.Request{ClusterName: "test", Request: reconcile.Request{NamespacedName: client.ObjectKeyFromObject(dom)}})
674+
assert.NoError(t, err)
675+
676+
// With the fix, we should requeue immediately when wake time is past
677+
assert.True(t, res.Requeue)
678+
assert.Equal(t, time.Duration(0), res.RequeueAfter)
679+
}
680+
559681
func TestRegistration_Subdomain_DelegationOverridesApexNS(t *testing.T) {
560682
s := runtime.NewScheme()
561683
_ = scheme.AddToScheme(s)

0 commit comments

Comments
 (0)