Skip to content

Commit e5f8a62

Browse files
committed
Enhance Windows host interface creation timeout
Introduce an exponential backoff retry mechanism for host interface creation on Windows. Under high pod churn or node rollout stress, Windows HNS can be slow to create the virtual network adapter, causing the hardcoded 1-minute timeout to fail and leave OVS ports permanently misconfigured. Also add a stress E2E test to verify Antrea agent rollout recovery under high pod density on Windows. Signed-off-by: Shuyang Xin <shuyang.xin@broadcom.com>
1 parent acdfd42 commit e5f8a62

4 files changed

Lines changed: 489 additions & 26 deletions

File tree

ci/jenkins/test.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,7 @@ function run_e2e_windows {
746746
mkdir -p `pwd`/antrea-test-logs
747747

748748
echo "====== Run test with e2e test ======"
749-
go test -v antrea.io/antrea/v2/test/e2e --logs-export-dir `pwd`/antrea-test-logs --provider remote -timeout=50m --prometheus
749+
go test -v antrea.io/antrea/v2/test/e2e --logs-export-dir `pwd`/antrea-test-logs --provider remote -timeout=500m --prometheus
750750
if [[ "$?" != "0" ]]; then
751751
TEST_FAILURE=true
752752
fi

pkg/agent/cniserver/interface_configuration_windows.go

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -466,34 +466,65 @@ func (ic *ifConfigurator) addPostInterfaceCreateHook(containerID, endpointName s
466466
go func() {
467467
ifaceName := fmt.Sprintf("vEthernet (%s)", endpointName)
468468
var err error
469-
pollErr := wait.PollUntilContextTimeout(context.TODO(), 100*time.Millisecond, 60*time.Second, true,
470-
func(ctx context.Context) (bool, error) {
471-
containerAccess.lockContainer(containerID)
472-
defer containerAccess.unlockContainer(containerID)
473-
currentEP, ok := ic.getEndpoint(endpointName)
474-
if !ok {
475-
klog.InfoS("HNSEndpoint doesn't exist in cache, exit current goroutine", "HNSEndpoint", endpointName)
476-
return true, nil
477-
}
478-
if currentEP.Id != expectedEP.Id {
479-
klog.InfoS("Detected HNSEndpoint change, exit current goroutine", "HNSEndpoint", endpointName)
469+
startTime := time.Now()
470+
471+
// Exponential backoff for host interface creation retries.
472+
// We retry up to 3 times (steps) with an initial delay of 2 seconds,
473+
// doubling the delay each time (20s -> 40s -> 80s -> 160s -> 320s) with a mild random jitter.
474+
backoff := wait.Backoff{
475+
Steps: 5,
476+
Duration: 20 * time.Second,
477+
Factor: 2.0,
478+
Jitter: 0.1,
479+
}
480+
481+
attempt := 0
482+
backoffErr := wait.ExponentialBackoff(backoff, func() (bool, error) {
483+
attempt++
484+
klog.V(2).InfoS("Starting poll attempt for host interface", "interface", ifaceName, "attempt", attempt)
485+
486+
pollStart := time.Now()
487+
pollErr := wait.PollUntilContextTimeout(context.TODO(), 100*time.Millisecond, 60*time.Second, true,
488+
func(ctx context.Context) (bool, error) {
489+
containerAccess.lockContainer(containerID)
490+
defer containerAccess.unlockContainer(containerID)
491+
currentEP, ok := ic.getEndpoint(endpointName)
492+
if !ok {
493+
klog.InfoS("HNSEndpoint doesn't exist in cache, exit current goroutine", "HNSEndpoint", endpointName)
494+
return true, nil
495+
}
496+
if currentEP.Id != expectedEP.Id {
497+
klog.InfoS("Detected HNSEndpoint change, exit current goroutine", "HNSEndpoint", endpointName)
498+
return true, nil
499+
}
500+
if !hostInterfaceExistsFunc(ifaceName) {
501+
klog.V(4).InfoS("Waiting for interface to be created", "interface", ifaceName)
502+
return false, nil
503+
}
504+
if err = hook(); err != nil {
505+
return false, err
506+
}
480507
return true, nil
481-
}
482-
if !hostInterfaceExistsFunc(ifaceName) {
483-
klog.V(2).InfoS("Waiting for interface to be created", "interface", ifaceName)
484-
return false, nil
485-
}
486-
if err = hook(); err != nil {
487-
return false, err
488-
}
508+
})
509+
510+
pollDuration := time.Since(pollStart)
511+
totalDuration := time.Since(startTime)
512+
513+
if pollErr == nil {
514+
klog.InfoS("Successfully configured host interface", "interface", ifaceName, "attempt", attempt, "pollDuration", pollDuration.String(), "totalDuration", totalDuration.String())
489515
return true, nil
490-
})
516+
}
517+
518+
klog.InfoS("Failed to wait for host interface creation, will retry with backoff", "interface", ifaceName, "attempt", attempt, "pollDuration", pollDuration.String(), "totalDuration", totalDuration.String(), "err", err, "pollErr", pollErr)
519+
return false, nil
520+
})
491521

492-
if pollErr != nil {
522+
if backoffErr != nil {
523+
totalDuration := time.Since(startTime)
493524
if err != nil {
494-
klog.ErrorS(err, "Failed to execute postInterfaceCreateHook", "interface", ifaceName)
525+
klog.ErrorS(err, "Failed to execute postInterfaceCreateHook after all retries", "interface", ifaceName, "totalDuration", totalDuration.String())
495526
} else {
496-
klog.ErrorS(pollErr, "Failed to wait for host interface creation in 1min", "interface", ifaceName)
527+
klog.ErrorS(backoffErr, "Failed to wait for host interface creation after all retries", "interface", ifaceName, "totalDuration", totalDuration.String())
497528
}
498529
}
499530
}()

test/e2e/framework.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1415,7 +1415,7 @@ func (data *TestData) waitForAntreaDaemonSetPods(timeout time.Duration) error {
14151415
})
14161416
if wait.Interrupted(err) {
14171417
_, stdout, _, _ := data.provider.RunCommandOnNode(controlPlaneNodeName(), fmt.Sprintf("kubectl -n %s describe pod", antreaNamespace))
1418-
return fmt.Errorf("antrea-agent DaemonSet not ready within %v; kubectl describe pod output: %v", defaultTimeout, stdout)
1418+
return fmt.Errorf("antrea-agent DaemonSet not ready within %v; kubectl describe pod output: %v", timeout, stdout)
14191419
} else if err != nil {
14201420
return err
14211421
}
@@ -1483,6 +1483,10 @@ func (data *TestData) CreateClient(kubeconfigPath string) error {
14831483
if err != nil {
14841484
return fmt.Errorf("error when building kube config: %v", err)
14851485
}
1486+
// Increase QPS and Burst to prevent client-side throttling during high-concurrency stress tests.
1487+
kubeConfig.QPS = 100
1488+
kubeConfig.Burst = 200
1489+
14861490
clientset, err := kubernetes.NewForConfig(kubeConfig)
14871491
if err != nil {
14881492
return fmt.Errorf("error when creating kubernetes client: %v", err)
@@ -1581,6 +1585,7 @@ type PodBuilder struct {
15811585
ResourceLimits corev1.ResourceList
15821586
ReadinessProbe *corev1.Probe
15831587
DnsConfig *corev1.PodDNSConfig
1588+
RestartPolicy corev1.RestartPolicy
15841589
}
15851590

15861591
func NewPodBuilder(name, ns, image string) *PodBuilder {
@@ -1718,6 +1723,11 @@ func (b *PodBuilder) WithReadinessProbe(probe *corev1.Probe) *PodBuilder {
17181723
return b
17191724
}
17201725

1726+
func (b *PodBuilder) WithRestartPolicy(policy corev1.RestartPolicy) *PodBuilder {
1727+
b.RestartPolicy = policy
1728+
return b
1729+
}
1730+
17211731
// WithCustomDNSConfig adds a custom DNS Configuration to the Pod spec.
17221732
// It ensures that the DNSPolicy is set to 'None' and assigns the provided DNSConfig.
17231733
func (b *PodBuilder) WithCustomDNSConfig(dnsConfig *corev1.PodDNSConfig) *PodBuilder {
@@ -1730,6 +1740,10 @@ func (b *PodBuilder) Create(data *TestData) error {
17301740
if containerName == "" {
17311741
containerName = getImageName(b.Image)
17321742
}
1743+
policy := b.RestartPolicy
1744+
if policy == "" {
1745+
policy = corev1.RestartPolicyNever
1746+
}
17331747
podSpec := corev1.PodSpec{
17341748
Containers: []corev1.Container{
17351749
{
@@ -1752,7 +1766,7 @@ func (b *PodBuilder) Create(data *TestData) error {
17521766
},
17531767
},
17541768
Volumes: b.Volumes,
1755-
RestartPolicy: corev1.RestartPolicyNever,
1769+
RestartPolicy: policy,
17561770
HostNetwork: b.HostNetwork,
17571771
ServiceAccountName: b.ServiceAccountName,
17581772
// Set it to 1s for immediate shutdown to reduce test run time and to avoid affecting subsequent tests.

0 commit comments

Comments
 (0)