Karmada ClusterPropagationPolicy 端到端测试覆盖解析:基础传播、优先级与删除场景全指南

原创2026-09-17 15:29:351,715 阅读
文章标签:云原生容器编排集群管理微服务后端高可用

Karmada ClusterPropagationPolicy 端到端测试覆盖解析:基础传播、优先级与删除场景全指南

ClusterPropagationPolicy(集群级传播策略)是 Karmada 多云多集群编排中面向集群级(Cluster 作用域)资源的传播控制核心对象,它决定了 CRD、ClusterRole、ClusterRoleBinding 等资源应被分发到哪些成员集群、如何匹配资源模板,以及当多个策略同时命中时由谁胜出。本文以仓库中的 clusterpropagationpolicy_test.md 覆盖文档为主体,结合其对应的 clusterpropagationpolicy_test.go 实现、传播策略 API 定义 与 e2e 测试框架,系统梳理 ClusterPropagationPolicy 的五类端到端测试场景(基础传播、高级传播、隐式优先级、显式优先级、删除解绑),并解析测试背后所验证的传播语义、策略优先级规则与标签/注解清理机制。读完本文,你将能够理解 Karmada 如何对 ClusterPropagationPolicy 做质量保障,并掌握这些测试所锚定的策略行为与可复用的 e2e 断言模式。

一、背景:ClusterPropagationPolicy 是什么,为什么需要专门的 e2e 覆盖

在 Karmada 中,资源传播由两类策略驱动:

  • PropagationPolicy(命名空间级):只能匹配当前命名空间内的资源;
  • ClusterPropagationPolicy(集群级):可以匹配任意命名空间与**集群作用域(Cluster-Scoped)**资源,例如 CustomResourceDefinition(CRD)、ClusterRole、ClusterRoleBinding。

从 API 定义看,ClusterPropagationPolicy 结构与 PropagationPolicy 完全一致,只是资源作用域为 Cluster:

// +kubebuilder:resource:scope="Cluster"
// ClusterPropagationPolicy represents the desire propagation policy of resources.
// 详见 pkg/apis/policy/v1alpha1/propagation_types.go 的 ClusterPropagationPolicy 定义
type ClusterPropagationPolicy struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	// Spec represents the desired behavior of ClusterPropagationPolicy.
	// +required
	Spec PropagationSpec `json:"spec"`
}

PropagationSpec 中与本文测试场景强相关的核心字段包括:

字段 说明 默认值
resourceSelectors 用于选择资源的规则,必填且 MinItems=1;空选择器会被拒绝,以避免 Secret 等敏感资源被误传播 无
propagateDeps 是否自动传播被引用资源(如 Deployment 引用的 ConfigMap/Secret) false
placement 选择目标成员集群的规则(ClusterAffinity / SpreadConstraints / ReplicaScheduling 等) 空(任意集群)
priority 显式优先级,数值越大优先级越高 0
preemption 是否抢占已绑定策略,取值 Always / Never Never
suspension 是否挂起调度/分发 无
conflictResolution 目标集群已存在同名资源时的处理方式,Abort / Overwrite Abort

覆盖文档 clusterpropagationpolicy_test.md 正是围绕这些语义,把 e2e 用例按“基础传播 / 高级传播 / 隐式优先级 / 显式优先级 / 删除”五类组织成测试覆盖清单,而 clusterpropagationpolicy_test.go 给出了每个用例的具体实现。

二、测试的组织方式:Ginkgo 套件结构与命名约定

所有用例集中在 [BasicCase]、[CornerCase]、[AdvancedCase]、[ImplicitPriority]、[ExplicitPriority]、[DeleteCase]、[Suspension] 等 ginkgo.Describe 分组中。每个用例普遍遵循统一的四段式模式:

  1. BeforeEach 构造资源与策略:通过 test/helper 中的 NewClusterPropagationPolicy 构造策略对象,通过 NewDeployment / NewClusterRole / NewCustomResourceDefinition 构造资源模板;
  2. 创建策略与资源:调用 framework.CreateClusterPropagationPolicy 创建策略、创建待传播资源;
  3. DeferCleanup 注册清理:用例结束后自动删除策略与资源;
  4. It 中断言:用 framework.WaitXXXPresentOnCluster(s)FitWith 轮询成员集群,验证资源是否按预期出现/消失/携带特定标签注解。

框架层 clusterpropagationpolicy.go 提供了 CreateClusterPropagationPolicy、RemoveClusterPropagationPolicy、PatchClusterPropagationPolicy、UpdateClusterPropagationPolicyWithSpec、UpdateClusterPropagationPolicy 等封装,其中更新 resourceSelectors 时使用 retry.RetryOnConflict 处理并发冲突,保证用例在高并发 e2e 环境下的稳定性。

三、基础传播测试:CRD / ClusterRole / ClusterRoleBinding / Deployment 四类资源

覆盖文档的第一张表列出 4 个基础用例:

Test Case E2E Describe Text 说明
Test the propagation policy for CRD crd propagation testing 集群作用域资源传播
Test the propagation policy for clusterRole clusterRole propagation testing 集群作用域资源传播
Test the propagation policy for clusterRoleBinding clusterRoleBinding propagation testing 集群作用域资源传播
Test the propagation policy for deployment deployment propagation testing 命名空间作用域资源传播

这组用例验证的是 ClusterPropagationPolicy 的最基本能力:一个资源模板 + 一个带 placement.clusterAffinity.clusterNames 的策略,资源应被分发到所有目标成员集群。

从源码看各用例的关键断言:

  • CRD(clusterpropagationpolicy_test.go 中 CustomResourceDefinition propagation testing):随机生成 example-<rand>.karmada.io 的 CRD,创建 CRD 与策略后调用 framework.WaitCRDPresentOnClusters 断言 CRD 出现在所有成员集群上;
  • ClusterRole / ClusterRoleBinding:创建后调用 WaitClusterRolePresentOnClustersFitWith / WaitClusterRoleBindingPresentOnClustersFitWith 断言出现,且断言函数直接返回 true(即仅验证“存在”,不校验字段差异);
  • Deployment(Deployment propagation testing):这是唯一带变更验证的基础用例——先用 WaitDeploymentPresentOnClusterFitWith 验证副本数一致,再通过 framework.UpdateDeploymentReplicas 把副本数改为 updateDeploymentReplicas,最后再次轮询断言成员集群上的 Deployment 副本数已同步更新。这验证了 Karmada 对已传播资源的持续同步(reconcile)能力,而不仅是创建时的一次性传播。

值得注意的是,[CornerCase] 分组中的 Deployment 用例重复了上述“传播 + 副本数更新”验证,说明该场景被刻意双写,作为基础路径的兜底回归。

四、高级传播测试:动态编辑 resourceSelectors、propagateDeps 与 placement

覆盖文档第二张表列出 7 个“高级”用例,全部围绕策略创建之后再次编辑的行为:

Test Case E2E Describe Text
Test add resourceSelector of the propagation policy for the deployment add resourceSelectors item(namespace scope)
Test update resourceSelector of the propagation policy for the deployment update resourceSelectors item(namespace scope)
Test add resourceSelector of the propagation policy for the clusterRole add resourceSelectors item(cluster scope)
Test update resourceSelector of the propagation policy for the clusterRole update resourceSelectors item(cluster scope)
Test update propagateDeps of the propagation policy for deployment update policy propagateDeps(namespace scope)
Test update placement of the propagation policy for deployment update policy placement(namespace scope)
Test update placement of the propagation policy for clusterRole update policy placement(cluster scope)

4.1 新增与替换 resourceSelectors

命名空间作用域场景(add resourceSelectors item)的实现逻辑是:

  1. 创建只选中 deployment01 的策略并等待其出现在目标集群;
  2. 调用 framework.UpdateClusterPropagationPolicy 把 ResourceSelectors 更新为 [deployment01, deployment02];
  3. 断言 deployment02 随后被传播到目标集群——验证 selector 增量生效。

update resourceSelectors item 用例则相反:把 selector 从 deployment01 替换为 deployment02,断言:

  • deployment02 出现在目标集群;
  • deployment01 上不再携带 policyv1alpha1.ClusterPropagationPolicyPermanentIDLabel(clusterpropagationpolicy.karmada.io/permanent-id)。

这里验证的是资源从策略解绑后的元数据清理:当一个资源模板不再被任何策略选中时,Karmada 会移除它身上标记归属关系的永久 ID 标签。集群作用域场景(ClusterRole)的 add/update resourceSelectors item 逻辑完全对称,只是把 ResourceBinding 换成 ClusterResourceBinding、把 Deployment 换成 ClusterRole,验证 cluster 作用域资源的同样语义。

4.2 更新 propagateDeps

该用例先通过 gomega.Eventually 轮询断言:以 ClusterPropagationPolicyPermanentIDLabel 为 LabelSelector 能找到对应 ResourceBinding(即绑定关系已建立),然后向 /spec/propagateDeps 发 JSON Patch 设为 true,最终断言 bindings.Items[0].Spec.PropagateDeps == true——策略的依赖传播开关被正确下放到绑定对象。

propagateDeps 在 API 定义中的语义是:当 Deployment 引用 ConfigMap/Secret 时,设置为 true 后这些被引用资源无需显式写进 resourceSelectors,会随 Deployment 一起传播,并在故障转移场景中一起迁移。

4.3 更新 placement:调度目标迁移

update policy placement 用例体现了 Karmada 对调度结果变更的响应:

patch := []map[string]any{
	{
		"op":    "replace",
		"path":  "/spec/placement",
		"value": updatedPlacement, // clusterAffinity.clusterNames 由 targetMember 改为 updatedMember
	},
}
framework.PatchClusterPropagationPolicy(karmadaClient, policy.Name, patch, types.JSONPatchType)
framework.WaitDeploymentDisappearOnCluster(targetMember, deployment.Namespace, deployment.Name)
framework.WaitDeploymentPresentOnClusterFitWith(updatedMember, deployment.Namespace, deployment.Name,
	func(*appsv1.Deployment) bool { return true })

关键断言是:修改 placement 后,资源必须从原成员集群消失、在新成员集群出现——即调度迁移(migration)闭环。cluster 作用域版本(ClusterRole)验证同样的行为。这类用例直接锚定了 placement 字段(propagation_types.go 中定义的 ClusterAffinity、SpreadConstraints 等结构)的运行时语义。

五、隐式优先级测试:priorityMatchName / priorityMatchLabel / priorityMatchAll

当多个 ClusterPropagationPolicy 同时命中同一个资源模板时,Karmada 必须选出唯一胜者。覆盖文档第三张表对应 [ImplicitPriority] 分组:

Test Case E2E Describe Text
Test the priorityMatchName/priorityMatchLabel/priorityMatchAll implicit priority propagation for deployment priorityMatchName/priorityMatchLabel/priorityMatchAll testing

该用例(源码注释明确“Set it to run sequentially to avoid affecting other test cases”,故使用 framework.SerialDescribe 串行执行)构造了三个没有显式 priority 的策略,它们分别通过不同精确度匹配同一个带 priority: implicit-priority 标签的 Deployment:

  • policyMatchName:通过 name 精确匹配;
  • policyMatchLabelSelector:通过 labelSelector 匹配;
  • policyPriorityMatchAll:只写 apiVersion + kind + namespace(匹配该命名空间下该 Kind 的全部资源)。

测试分三个 ginkgo.By 逐步执行,每个步骤删除上一优先级策略后再断言下一个策略接管:

  1. 删除 priorityMatchName 后,断言 Deployment 注解 clusterpropagationpolicy.karmada.io/name 等于 priorityMatchLabelSelector;
  2. 再删除 priorityMatchLabelSelector,断言注解变为 priorityMatchAll。

这里验证的隐式优先级规则与 PropagationSpec.Priority 的注释 完全一致:

两个策略 priority 相同时,resourceSelectors 匹配越精确者胜出:

  • 按 name 匹配(resourceSelector.name)优先级高于按 labelSelector 匹配;
  • 按 labelSelector 匹配高于仅按 apiVersion + kind 匹配;
  • 若仍然无法区分,则按策略名字母序较低者胜出(如 'bar' 高于 'foo')。

同时,用例通过删除策略验证了策略删除后的重新匹配:旧策略被移除,次优策略立即接管资源,且资源的归属注解(clusterpropagationpolicy.karmada.io/name)被改写为新策略名。

六、显式优先级测试:高优先级抢占与同优先级决胜

覆盖文档第四张表对应 [ExplicitPriority] 分组,包含 2 个用例:

Test Case E2E Describe Text
Test the high explicit/low priority/implicit priority propagation for deployment high explicit/low priority/implicit priority ClusterPropagationPolicy propagation testing
Test the same explicit priority propagation for deployment same explicit priority ClusterPropagationPolicy propagation testing

6.1 高显式优先级 > 低显式优先级 > 隐式优先级

该用例通过 testhelper.NewExplicitPriorityClusterPropagationPolicy(name, selectors, placement, priority) 构造三个策略:

  • policyHigherPriorityLabelSelector:显式优先级 2,用 labelSelector 匹配;
  • policyLowerPriorityMatchName:显式优先级 1,用 name 匹配;
  • policyImplicitPriorityMatchName:无显式优先级(即 0),用 name 匹配。

尽管按“隐式优先级规则” name 匹配比 labelSelector 更精确,但显式 priority 的数值比较优先于隐式精确度——最终断言 Deployment 的归属注解等于 higherPriorityLabelSelector(优先级 2 的策略)。这正是源码注释所描述的裁决顺序:

多个 CPP 命中同一对象时,选择显式 priority 最高者;若显式 priority 相同,再比较隐式优先级。

用例还在创建策略后 time.Sleep(time.Second * 5) 并注明“we tested and find that it takes about 1s before cache synced”——等待 karmada-controller 的 informer 缓存同步,避免缓存未同步导致的断言抖动,这是编写这类竞态敏感 e2e 用例的实用经验。

6.2 同显式优先级:隐式规则决胜

第二个用例构造两个显式 priority 均为 1 的策略:

  • policyExplicitPriorityLabelSelector:labelSelector 匹配;
  • policyExplicitPriorityMatchName:name 匹配。

显式优先级并列时回到隐式规则,name 匹配更精确,因此断言最终生效的是 explicitPriorityMatchName。这两个用例组合起来,完整验证了 preemption / priority 相关设计 中“显式优先 → 隐式精确度 → 字母序”的三级裁决链。

七、删除场景测试:解绑、标签/注解清理与重建接管

覆盖文档第五张表对应 [DeleteCase] 分组,4 个用例覆盖命名空间与集群两类作用域:

Test Case E2E Describe Text
Test delete clusterpropagationpolicy for deployment delete ClusterPropagationPolicy and check whether labels and annotations are deleted correctly(namespace scope)
Test delete clusterpropagationpolicy for deployment and create a new one delete the old ClusterPropagationPolicy to unbind and create a new one(namespace scope)
Test delete clusterpropagationpolicy for CRD delete ClusterPropagationPolicy and check whether labels and annotations are deleted correctly(cluster scope)
Test delete clusterpropagationpolicy for CRD and create a new one delete the old ClusterPropagationPolicy to unbind and create a new one(cluster scope)

7.1 删除策略后清理归属标记

“删除并检查标签/注解正确清理”用例(namespace 作用域)的关键断言:

framework.RemoveClusterPropagationPolicy(karmadaClient, policy.Name)
framework.WaitDeploymentFitWith(kubeClient, deployment.Namespace, deployment.Name, func(dep *appsv1.Deployment) bool {
	if dep.Labels != nil && dep.Labels[policyv1alpha1.ClusterPropagationPolicyPermanentIDLabel] != "" {
		return false
	}
	if dep.Annotations != nil && dep.Annotations[policyv1alpha1.ClusterPropagationPolicyAnnotation] != "" {
		return false
	}
	return true
})

它同时检查两处对象的元数据:

  1. 资源模板(Deployment / CRD)上的 clusterpropagationpolicy.karmada.io/permanent-id 标签与 clusterpropagationpolicy.karmada.io/name 注解必须被移除;
  2. 引用绑定对象上的同样标签/注解必须被移除——命名空间资源检查 ResourceBinding(绑定名由 names.GenerateBindingName(kind, name) 生成),集群资源检查 ClusterResourceBinding。

这两个常量定义于 well_known_constants.go:

ClusterPropagationPolicyPermanentIDLabel = "clusterpropagationpolicy.karmada.io/permanent-id"
ClusterPropagationPolicyAnnotation      = "clusterpropagationpolicy.karmada.io/name"

注意该用例测试的是策略删除,而 preserveResourcesOnDeletion 字段(见 PropagationSpec)控制的是资源模板删除时是否保留成员集群上的副本,二者语义不同。

7.2 删除旧策略并创建新策略:重新接管

“delete the old ... and create a new one”用例模拟了策略替换的完整流程:删除旧策略 → 创建指向同一资源但目标集群不同的新策略 → 断言:

  • 资源模板与 ResourceBinding/ClusterResourceBinding 的归属注解已更新为新策略名;
  • 资源从旧目标集群消失、出现在新目标集群(WaitDeploymentDisappearOnCluster + WaitDeploymentPresentOnClusterFitWith)。

这组用例保证了“策略更替 → 解绑 → 迁移 → 重新绑定”的全链路行为正确,防止资源在策略删除后变成“孤儿”或元数据残留。

八、补充:Suspension 挂起分发测试

覆盖文档虽未列出,但 clusterpropagationpolicy_test.go 末尾的 [Suspension] 分组补充验证了 spec.suspension.dispatching 的传播链路,是理解策略字段到运行时行为的极佳例证:

  1. 策略层:将 policy.Spec.Suspension.Dispatching 置为 true 并 UpdateClusterPropagationPolicyWithSpec;
  2. 绑定层:断言对应 ClusterResourceBinding 的 spec.suspension.dispatching 为 true——策略字段被同步到绑定对象;
  3. Work 层:断言成员集群执行空间(names.GenerateExecutionSpaceName(targetMember))中的 Work 满足 util.IsWorkSuspendDispatching(work);
  4. 状态层:断言 Work 的 WorkDispatching 条件为 ConditionFalse——分发被真正挂起。

对应 API 定义中 Suspension 结构 的注释:dispatching: true 表示停止向所有集群传播,且不能与 dispatchingOnClusters 共存。该用例展示了 e2e 从“策略声明”到“Work 实际状态”的多层贯通验证模式。

九、如何运行与复用这些测试

这些用例是 Karmada e2e 测试套件的一部分,由 suite.go 聚合进 test/e2e/suites 体系。运行前需要:

  1. 通过 hack/local-up-karmada.sh 或 hack/deploy-karmada.sh 拉起 Karmada 控制面与至少两个成员集群(优先级用例依赖 framework.ClusterNames()[0] 与 [1] 两个不同集群做迁移断言);
  2. 参考 run-e2e.sh 以 -ginkgo.focus=ClusterPropagationPolicy 之类的参数聚焦本组用例;
  3. 用例内部大量使用 gomega.Eventually 轮询断言与 pollTimeout/pollInterval,以容忍控制器异步 reconcile 带来的时序差异。

对于希望在自研多集群方案中复刻此类验证的读者,可直接复用本文梳理的断言模式:创建策略 → 等待资源出现 → 编辑策略 → 等待增量生效/清理 → 删除策略 → 验证元数据清理与重新接管,这一模式同样适用于 PropagationPolicy(见 propagationpolicy_test.go 与 propagationpolicy_test.md)。

结语

从覆盖文档与源码的对照可以看出,ClusterPropagationPolicy 的 e2e 测试并非简单的“资源能传过去”冒烟验证,而是系统性地锚定了策略引擎的关键行为契约:动态 selector 编辑的增量生效、placement 变更触发的调度迁移、显式/隐式三级优先级裁决、策略删除后的元数据清理与重建接管,以及 suspension 向绑定与 Work 层的层层下放。这套覆盖设计既是 Karmada 传播能力的质量防线,也为使用者理解 ClusterPropagationPolicy 的运行时语义提供了可执行、可复现的权威参考。

登录后查看全文
karmada