Karmada ClusterPropagationPolicy 端到端测试覆盖解析:基础传播、优先级与删除场景全指南
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 分组中。每个用例普遍遵循统一的四段式模式:
BeforeEach构造资源与策略:通过 test/helper 中的NewClusterPropagationPolicy构造策略对象,通过NewDeployment/NewClusterRole/NewCustomResourceDefinition构造资源模板;- 创建策略与资源:调用
framework.CreateClusterPropagationPolicy创建策略、创建待传播资源; DeferCleanup注册清理:用例结束后自动删除策略与资源;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)的实现逻辑是:
- 创建只选中
deployment01的策略并等待其出现在目标集群; - 调用
framework.UpdateClusterPropagationPolicy把ResourceSelectors更新为[deployment01, deployment02]; - 断言
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 逐步执行,每个步骤删除上一优先级策略后再断言下一个策略接管:
- 删除
priorityMatchName后,断言 Deployment 注解clusterpropagationpolicy.karmada.io/name等于priorityMatchLabelSelector; - 再删除
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
})
它同时检查两处对象的元数据:
- 资源模板(Deployment / CRD)上的
clusterpropagationpolicy.karmada.io/permanent-id标签与clusterpropagationpolicy.karmada.io/name注解必须被移除; - 引用绑定对象上的同样标签/注解必须被移除——命名空间资源检查
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 的传播链路,是理解策略字段到运行时行为的极佳例证:
- 策略层:将
policy.Spec.Suspension.Dispatching置为true并UpdateClusterPropagationPolicyWithSpec; - 绑定层:断言对应 ClusterResourceBinding 的
spec.suspension.dispatching为 true——策略字段被同步到绑定对象; - Work 层:断言成员集群执行空间(
names.GenerateExecutionSpaceName(targetMember))中的 Work 满足util.IsWorkSuspendDispatching(work); - 状态层:断言 Work 的
WorkDispatching条件为ConditionFalse——分发被真正挂起。
对应 API 定义中 Suspension 结构 的注释:dispatching: true 表示停止向所有集群传播,且不能与 dispatchingOnClusters 共存。该用例展示了 e2e 从“策略声明”到“Work 实际状态”的多层贯通验证模式。
九、如何运行与复用这些测试
这些用例是 Karmada e2e 测试套件的一部分,由 suite.go 聚合进 test/e2e/suites 体系。运行前需要:
- 通过 hack/local-up-karmada.sh 或 hack/deploy-karmada.sh 拉起 Karmada 控制面与至少两个成员集群(优先级用例依赖
framework.ClusterNames()[0]与[1]两个不同集群做迁移断言); - 参考 run-e2e.sh 以
-ginkgo.focus=ClusterPropagationPolicy之类的参数聚焦本组用例; - 用例内部大量使用
gomega.Eventually轮询断言与pollTimeout/pollInterval,以容忍控制器异步 reconcile 带来的时序差异。
对于希望在自研多集群方案中复刻此类验证的读者,可直接复用本文梳理的断言模式:创建策略 → 等待资源出现 → 编辑策略 → 等待增量生效/清理 → 删除策略 → 验证元数据清理与重新接管,这一模式同样适用于 PropagationPolicy(见 propagationpolicy_test.go 与 propagationpolicy_test.md)。
结语
从覆盖文档与源码的对照可以看出,ClusterPropagationPolicy 的 e2e 测试并非简单的“资源能传过去”冒烟验证,而是系统性地锚定了策略引擎的关键行为契约:动态 selector 编辑的增量生效、placement 变更触发的调度迁移、显式/隐式三级优先级裁决、策略删除后的元数据清理与重建接管,以及 suspension 向绑定与 Work 层的层层下放。这套覆盖设计既是 Karmada 传播能力的质量防线,也为使用者理解 ClusterPropagationPolicy 的运行时语义提供了可执行、可复现的权威参考。