You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在controller-runtime的envTest中等待工作队列排空以验证控制器映射函数逻辑

如何在controller-runtime的envTest中等待工作队列排空以验证控制器映射函数逻辑

兄弟,我太懂你这种靠sleep混日子的测试有多闹心了——要么跑快了测挂,要么跑慢了拖时间,完全不稳定。你要的是确保容器的初始reconcile完全处理完,再创建Item来验证你的ItemToContainer映射函数对吧?给你几个靠谱的办法,再也不用依赖sleep了:

方法一:直接监控控制器的工作队列(推荐)

controller-runtime的Controller实例其实可以暴露它的工作队列,我们可以直接检查队列长度,直到队列排空。唯一要做的是在测试中能拿到你的Controller实例:

步骤1:保存Controller实例

在你设置控制器的地方,把创建好的Controller保存到一个测试能访问的变量里(比如全局变量,或者通过测试Setup函数返回):

// 定义一个测试包内可见的变量
var containerCtrl controller.Controller

func SetupContainerController(mgr ctrl.Manager, reconciler *ContainerReconciler) error {
    var err error
    // 创建控制器时把实例保存下来
    containerCtrl, err = ctrl.NewControllerManagedBy(mgr).
        For(&foov1.Container{}).
        Watches(
            &clusterv1.Item{},
            handler.EnqueueRequestsFromMapFunc(reconciler.ItemToContainer),
        ).
        Complete(reconciler)
    return err
}

步骤2:写一个等待队列排空的工具函数

这个函数会循环检查队列长度,直到队列为空或者超时:

func waitForControllerQueueEmpty(ctx context.Context, ctrl controller.Controller, timeout time.Duration) error {
    deadline := time.Now().Add(timeout)
    for time.Now().Before(deadline) {
        // 尝试将Controller转为暴露Queue的类型(controller-runtime内部实现支持这个)
        if queueHolder, ok := ctrl.(interface{ Queue() workqueue.RateLimitingInterface }); ok {
            queue := queueHolder.Queue()
            if queue.Len() == 0 {
                // 队列空了之后再等100ms,确保正在处理的最后一个reconcile也完成
                time.Sleep(100 * time.Millisecond)
                // 再检查一次队列,防止新任务被加入
                if queue.Len() == 0 {
                    return nil
                }
            }
        } else {
            return fmt.Errorf("controller does not expose its work queue")
        }
        // 每次检查间隔50ms,避免占用太多CPU
        time.Sleep(50 * time.Millisecond)
    }
    return fmt.Errorf("controller queue did not empty within %v timeout", timeout)
}

步骤3:在测试中使用

现在你可以替换掉sleep,用这个函数等待队列排空:

// 创建容器
Expect(testEnv.Create(ctx, container)).To(Succeed())
// 等待容器的初始reconcile完全处理完,队列排空
Expect(waitForControllerQueueEmpty(ctx, containerCtrl, 5*time.Second)).To(Succeed())
// 现在再创建Item,确保触发的是ItemToContainer映射的reconcile
Expect(testEnv.Create(ctx, item)).To(Succeed())

方法二:利用资源状态字段(需要修改控制器)

如果你的控制器已经在资源的Status字段里记录了最后一次reconcile的时间(比如LastReconciledTime),你可以等待这个字段更新,以此判断reconcile完成:

步骤1:在控制器里更新状态

在你的Reconcile函数最后,更新容器的Status.LastReconciledTime:

func (r *ContainerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    // ... 你的reconcile逻辑 ...

    // 更新最后reconcile时间
    container.Status.LastReconciledTime = metav1.NewTime(time.Now())
    if err := r.Status().Update(ctx, &container); err != nil {
        return ctrl.Result{}, err
    }

    return ctrl.Result{}, nil
}

步骤2:写等待状态更新的函数

func waitForContainerReconciled(ctx context.Context, client client.Client, container *foov1.Container, timeout time.Duration) error {
    deadline := time.Now().Add(timeout)
    for time.Now().Before(deadline) {
        var latestContainer foov1.Container
        if err := client.Get(ctx, types.NamespacedName{Name: container.Name, Namespace: container.Namespace}, &latestContainer); err != nil {
            return err
        }
        // 检查最后reconcile时间是否晚于容器创建时间
        if latestContainer.Status.LastReconciledTime != nil && latestContainer.Status.LastReconciledTime.After(container.CreationTimestamp.Time) {
            return nil
        }
        time.Sleep(100 * time.Millisecond)
    }
    return fmt.Errorf("container %s/%s was not reconciled within %v", container.Namespace, container.Name, timeout)
}

步骤3:在测试中使用

Expect(testEnv.Create(ctx, container)).To(Succeed())
// 等待容器完成第一次reconcile
Expect(waitForContainerReconciled(ctx, testEnv.Client, container, 5*time.Second)).To(Succeed())
// 再创建Item验证映射函数
Expect(testEnv.Create(ctx, item)).To(Succeed())

注意事项

  • 方法一不需要修改控制器代码,是测试友好的方案;方法二需要控制器支持状态更新,但能更准确地判断reconcile是否完成(队列排空不代表reconcile逻辑执行完,只是任务出队了)
  • 无论用哪种方法,都要设置合理的超时时间,避免测试无限挂起
  • 不要用太短的检查间隔,避免占用过多CPU资源

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 11:43:06