Skip to content

Commit

Permalink
implementation of the LazyActivation preference for Policy
Browse files Browse the repository at this point in the history
Signed-off-by: chaosi-zju <chaosi@zju.edu.cn>
  • Loading branch information
chaosi-zju committed Jan 25, 2024
1 parent f054313 commit 205c5e5
Show file tree
Hide file tree
Showing 13 changed files with 297 additions and 34 deletions.
4 changes: 4 additions & 0 deletions api/openapi-spec/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -17148,6 +17148,10 @@
"resourceSelectors"
],
"properties": {
"activationPreference": {
"description": "ActivationPreference declares when the placement of this Policy will be synced to the ResourceBinding.\n\nThe value \"Immediate\" means the matched resource should sync the placement to ResourceBinding immediately.\n\nThe value \"Lazy\" means the matched resource should delay to sync the placement to ResourceBinding until the resource itself is modified. (In 1:N scenario where one Policy manages many resource templates, changes to a Policy typically affect numerous applications simultaneously. If there are errors in the Policy configuration, it could lead to widespread failures. By using this setting to make modified placement delay synchronization from Policy to ResourceBinding, the changed Policy can be gradually rolled out through iterative modifications to resource templates.)\n\nIn a word, The impact of Policy changes on the bondage relationship between resources and Policy is always immediate, but whether the resource needs to synchronize the latest placement to the binding immediately is determined by this field. If the field is unset, the default strategy is \"Immediate\".",
"type": "string"
},
"association": {
"description": "Association tells if relevant resources should be selected automatically. e.g. a ConfigMap referred by a Deployment. default false. Deprecated: in favor of PropagateDeps.",
"type": "boolean"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,25 @@ spec:
spec:
description: Spec represents the desired behavior of ClusterPropagationPolicy.
properties:
activationPreference:
description: "ActivationPreference declares when the placement of
this Policy will be synced to the ResourceBinding. \n The value
\"Immediate\" means the matched resource should sync the placement
to ResourceBinding immediately. \n The value \"Lazy\" means the
matched resource should delay to sync the placement to ResourceBinding
until the resource itself is modified. (In 1:N scenario where one
Policy manages many resource templates, changes to a Policy typically
affect numerous applications simultaneously. If there are errors
in the Policy configuration, it could lead to widespread failures.
By using this setting to make modified placement delay synchronization
from Policy to ResourceBinding, the changed Policy can be gradually
rolled out through iterative modifications to resource templates.)
\n In a word, The impact of Policy changes on the bondage relationship
between resources and Policy is always immediate, but whether the
resource needs to synchronize the latest placement to the binding
immediately is determined by this field. If the field is unset,
the default strategy is \"Immediate\"."
type: string
association:
description: 'Association tells if relevant resources should be selected
automatically. e.g. a ConfigMap referred by a Deployment. default
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,25 @@ spec:
spec:
description: Spec represents the desired behavior of PropagationPolicy.
properties:
activationPreference:
description: "ActivationPreference declares when the placement of
this Policy will be synced to the ResourceBinding. \n The value
\"Immediate\" means the matched resource should sync the placement
to ResourceBinding immediately. \n The value \"Lazy\" means the
matched resource should delay to sync the placement to ResourceBinding
until the resource itself is modified. (In 1:N scenario where one
Policy manages many resource templates, changes to a Policy typically
affect numerous applications simultaneously. If there are errors
in the Policy configuration, it could lead to widespread failures.
By using this setting to make modified placement delay synchronization
from Policy to ResourceBinding, the changed Policy can be gradually
rolled out through iterative modifications to resource templates.)
\n In a word, The impact of Policy changes on the bondage relationship
between resources and Policy is always immediate, but whether the
resource needs to synchronize the latest placement to the binding
immediately is determined by this field. If the field is unset,
the default strategy is \"Immediate\"."
type: string
association:
description: 'Association tells if relevant resources should be selected
automatically. e.g. a ConfigMap referred by a Deployment. default
Expand Down
26 changes: 26 additions & 0 deletions pkg/apis/policy/v1alpha1/propagation_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,24 @@ type PropagationSpec struct {
// +kubebuilder:validation:Enum=Abort;Overwrite
// +optional
ConflictResolution ConflictResolution `json:"conflictResolution,omitempty"`

// ActivationPreference declares when the placement of this Policy will be synced to the ResourceBinding.
//
// The value "Immediate" means the matched resource should sync the placement to ResourceBinding immediately.
//
// The value "Lazy" means the matched resource should delay to sync the placement to ResourceBinding until
// the resource itself is modified. (In 1:N scenario where one Policy manages many resource templates,
// changes to a Policy typically affect numerous applications simultaneously. If there are errors in the Policy
// configuration, it could lead to widespread failures. By using this setting to make modified placement delay
// synchronization from Policy to ResourceBinding, the changed Policy can be gradually rolled out through
// iterative modifications to resource templates.)
//
// In a word, The impact of Policy changes on the bondage relationship between resources and Policy is always immediate,
// but whether the resource needs to synchronize the latest placement to the binding immediately is determined by this field.
// If the field is unset, the default strategy is "Immediate".
//
// +optional
ActivationPreference ActivationPreference `json:"activationPreference,omitempty"`
}

// ResourceSelector the resources will be selected.
Expand Down Expand Up @@ -518,6 +536,14 @@ const (
ConflictAbort ConflictResolution = "Abort"
)

// ActivationPreference declares when the placement of this Policy will be synced to the ResourceBinding.
type ActivationPreference string

const (
// LazyActivation the matched resource should delay to sync the placement to ResourceBinding until the resource itself is modified.
LazyActivation ActivationPreference = "Lazy"
)

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// PropagationPolicyList contains a list of PropagationPolicy.
Expand Down
79 changes: 62 additions & 17 deletions pkg/detector/detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ func (d *ResourceDetector) Start(ctx context.Context) error {

detectorWorkerOptions := util.Options{
Name: "resource detector",
KeyFunc: ClusterWideKeyFunc,
KeyFunc: ResourceItemKeyFunc,
ReconcileFunc: d.Reconcile,
RateLimiterOptions: d.RateLimiterOptions,
}
Expand Down Expand Up @@ -226,11 +226,14 @@ func (d *ResourceDetector) NeedLeaderElection() bool {
// Reconcile performs a full reconciliation for the object referred to by the key.
// The key will be re-queued if an error is non-nil.
func (d *ResourceDetector) Reconcile(key util.QueueKey) error {
clusterWideKey, ok := key.(keys.ClusterWideKey)
clusterWideKeyWithConfig, ok := key.(keys.ClusterWideKeyWithConfig)
if !ok {
klog.Error("Invalid key")
return fmt.Errorf("invalid key")
}

clusterWideKey := clusterWideKeyWithConfig.ClusterWideKey
resourceChangeByKarmada := clusterWideKeyWithConfig.ResourceChangeByKarmada
klog.Infof("Reconciling object: %s", clusterWideKey)

object, err := d.GetUnstructuredObject(clusterWideKey)
Expand All @@ -243,7 +246,7 @@ func (d *ResourceDetector) Reconcile(key util.QueueKey) error {
// currently we do that by setting owner reference to derived objects.
return nil
}
klog.Errorf("Failed to get unstructured object(%s), error: %v", clusterWideKey, err)
klog.Errorf("Failed to get unstructured object(%s), error: %v", clusterWideKeyWithConfig, err)
return err
}

Expand All @@ -255,7 +258,7 @@ func (d *ResourceDetector) Reconcile(key util.QueueKey) error {
return nil
}

return d.propagateResource(object, clusterWideKey)
return d.propagateResource(object, clusterWideKey, resourceChangeByKarmada)
}

// EventFilter tells if an object should be taken care of.
Expand Down Expand Up @@ -320,7 +323,7 @@ func (d *ResourceDetector) OnAdd(obj interface{}) {
if !ok {
return
}
d.Processor.Enqueue(runtimeObj)
d.Processor.Enqueue(ResourceItem{Obj: runtimeObj})
}

// OnUpdate handles object update event and push the object to queue.
Expand All @@ -337,12 +340,25 @@ func (d *ResourceDetector) OnUpdate(oldObj, newObj interface{}) {
return
}

newRuntimeObj, ok := newObj.(runtime.Object)
if !ok {
klog.Errorf("Failed to assert newObj as runtime.Object")
return
}

if !eventfilter.SpecificationChanged(unstructuredOldObj, unstructuredNewObj) {
klog.V(4).Infof("Ignore update event of object (kind=%s, %s/%s) as specification no change", unstructuredOldObj.GetKind(), unstructuredOldObj.GetNamespace(), unstructuredOldObj.GetName())
return
}

d.OnAdd(newObj)
resourceChangeByKarmada := eventfilter.ResourceChangeByKarmada(unstructuredOldObj, unstructuredNewObj)

resourceItem := ResourceItem{
Obj: newRuntimeObj,
ResourceChangeByKarmada: resourceChangeByKarmada,
}

d.Processor.Enqueue(resourceItem)
}

// OnDelete handles object delete event and push the object to queue.
Expand Down Expand Up @@ -407,7 +423,8 @@ func (d *ResourceDetector) LookForMatchedClusterPolicy(object *unstructured.Unst
}

// ApplyPolicy starts propagate the object referenced by object key according to PropagationPolicy.
func (d *ResourceDetector) ApplyPolicy(object *unstructured.Unstructured, objectKey keys.ClusterWideKey, policy *policyv1alpha1.PropagationPolicy) (err error) {
func (d *ResourceDetector) ApplyPolicy(object *unstructured.Unstructured, objectKey keys.ClusterWideKey,
resourceChangeByKarmada bool, policy *policyv1alpha1.PropagationPolicy) (err error) {
start := time.Now()
klog.Infof("Applying policy(%s/%s) for object: %s", policy.Namespace, policy.Name, objectKey)
var operationResult controllerutil.OperationResult
Expand All @@ -426,6 +443,15 @@ func (d *ResourceDetector) ApplyPolicy(object *unstructured.Unstructured, object
return err
}

// If this Reconcile action is triggered by Karmada itself and the current bound Policy is lazy activation preference,
// resource will delay to sync the placement from Policy to Binding util resource is updated by User.
if resourceChangeByKarmada && util.IsLazyActivationEnabled(policy.Spec.ActivationPreference) {
operationResult = controllerutil.OperationResultNone
klog.Infof("Skip refresh Binding for the change of resource (%s/%s) is from Karmada and activation "+
"preference of current bound policy (%s) is enabled.", object.GetNamespace(), object.GetName(), policy.Name)
return nil
}

policyLabels := map[string]string{
policyv1alpha1.PropagationPolicyNamespaceLabel: policy.GetNamespace(),
policyv1alpha1.PropagationPolicyNameLabel: policy.GetName(),
Expand Down Expand Up @@ -497,7 +523,8 @@ func (d *ResourceDetector) ApplyPolicy(object *unstructured.Unstructured, object

// ApplyClusterPolicy starts propagate the object referenced by object key according to ClusterPropagationPolicy.
// nolint:gocyclo
func (d *ResourceDetector) ApplyClusterPolicy(object *unstructured.Unstructured, objectKey keys.ClusterWideKey, policy *policyv1alpha1.ClusterPropagationPolicy) (err error) {
func (d *ResourceDetector) ApplyClusterPolicy(object *unstructured.Unstructured, objectKey keys.ClusterWideKey,
resourceChangeByKarmada bool, policy *policyv1alpha1.ClusterPropagationPolicy) (err error) {
start := time.Now()
klog.Infof("Applying cluster policy(%s) for object: %s", policy.Name, objectKey)
var operationResult controllerutil.OperationResult
Expand All @@ -516,6 +543,15 @@ func (d *ResourceDetector) ApplyClusterPolicy(object *unstructured.Unstructured,
return err
}

// If this Reconcile action is triggered by Karmada itself and the current bound Policy is lazy activation preference,
// resource will delay to sync the placement from Policy to Binding util resource is updated by User.
if resourceChangeByKarmada && util.IsLazyActivationEnabled(policy.Spec.ActivationPreference) {
operationResult = controllerutil.OperationResultNone
klog.Infof("Skip refresh Binding for the change of resource (%s/%s) is from Karmada and activation "+
"preference of current bound cluster policy (%s) is enabled.", object.GetNamespace(), object.GetName(), policy.Name)
return nil
}

policyLabels := map[string]string{
policyv1alpha1.ClusterPropagationPolicyLabel: policy.GetName(),
policyv1alpha1.ClusterPropagationPolicyPermanentIDLabel: policyID,
Expand Down Expand Up @@ -1124,7 +1160,7 @@ func (d *ResourceDetector) ReconcileClusterPropagationPolicy(key util.QueueKey)
}

// HandlePropagationPolicyDeletion handles PropagationPolicy delete event.
// After a policy is removed, the label marked on relevant resource template will be removed(which gives
// After a policy is removed, the label marked on relevant resource template will be removed (which gives
// the resource template a change to match another policy).
//
// Note: The relevant ResourceBinding will continue to exist until the resource template is gone.
Expand Down Expand Up @@ -1161,7 +1197,7 @@ func (d *ResourceDetector) HandlePropagationPolicyDeletion(policyNS string, poli
}

// HandleClusterPropagationPolicyDeletion handles ClusterPropagationPolicy delete event.
// After a policy is removed, the label marked on relevant resource template will be removed(which gives
// After a policy is removed, the label marked on relevant resource template will be removed (which gives
// the resource template a change to match another policy).
//
// Note: The relevant ClusterResourceBinding or ResourceBinding will continue to exist until the resource template is gone.
Expand Down Expand Up @@ -1234,6 +1270,8 @@ func (d *ResourceDetector) HandleClusterPropagationPolicyDeletion(policyName str
// from waiting list and throw the object to it's reconcile queue. If not, do nothing.
// Finally, handle the propagation policy preemption process if preemption is enabled.
func (d *ResourceDetector) HandlePropagationPolicyCreationOrUpdate(policy *policyv1alpha1.PropagationPolicy) error {
// If the Policy's ResourceSelectors change, causing certain resources to no longer match the Policy, the label marked
// on relevant resource template will be removed (which gives the resource template a change to match another policy).
err := d.cleanPPUnmatchedResourceBindings(policy.Namespace, policy.Name, policy.Spec.ResourceSelectors)
if err != nil {
return err
Expand All @@ -1251,9 +1289,10 @@ func (d *ResourceDetector) HandlePropagationPolicyCreationOrUpdate(policy *polic
if err != nil {
return err
}
d.Processor.Add(resourceKey)
d.Processor.Add(keys.ClusterWideKeyWithConfig{ClusterWideKey: resourceKey, ResourceChangeByKarmada: true})
}

// check whether there are matched RT in waiting list, is so, add it to processor
matchedKeys := d.GetMatching(policy.Spec.ResourceSelectors)
klog.Infof("Matched %d resources by policy(%s/%s)", len(matchedKeys), policy.Namespace, policy.Name)

Expand All @@ -1268,10 +1307,12 @@ func (d *ResourceDetector) HandlePropagationPolicyCreationOrUpdate(policy *polic

for _, key := range matchedKeys {
d.RemoveWaiting(key)
d.Processor.Add(key)
d.Processor.Add(keys.ClusterWideKeyWithConfig{ClusterWideKey: key, ResourceChangeByKarmada: true})
}

// if preemption is enabled, handle the preemption process.
// If preemption is enabled, handle the preemption process.
// If this policy succeeds in preempting resource managed by other policy, the label marked on relevant resource
// will be replaced, which gives the resource template a change to match to this policy.
if preemptionEnabled(policy.Spec.Preemption) {
return d.handlePropagationPolicyPreemption(policy)
}
Expand All @@ -1286,6 +1327,8 @@ func (d *ResourceDetector) HandlePropagationPolicyCreationOrUpdate(policy *polic
// from waiting list and throw the object to it's reconcile queue. If not, do nothing.
// Finally, handle the cluster propagation policy preemption process if preemption is enabled.
func (d *ResourceDetector) HandleClusterPropagationPolicyCreationOrUpdate(policy *policyv1alpha1.ClusterPropagationPolicy) error {
// If the Policy's ResourceSelectors change, causing certain resources to no longer match the Policy, the label marked
// on relevant resource template will be removed (which gives the resource template a change to match another policy).
err := d.cleanCPPUnmatchedResourceBindings(policy.Name, policy.Spec.ResourceSelectors)
if err != nil {
return err
Expand All @@ -1312,14 +1355,14 @@ func (d *ResourceDetector) HandleClusterPropagationPolicyCreationOrUpdate(policy
if err != nil {
return err
}
d.Processor.Add(resourceKey)
d.Processor.Add(keys.ClusterWideKeyWithConfig{ClusterWideKey: resourceKey, ResourceChangeByKarmada: true})
}
for _, crb := range clusterResourceBindings.Items {
resourceKey, err := helper.ConstructClusterWideKey(crb.Spec.Resource)
if err != nil {
return err
}
d.Processor.Add(resourceKey)
d.Processor.Add(keys.ClusterWideKeyWithConfig{ClusterWideKey: resourceKey, ResourceChangeByKarmada: true})
}

matchedKeys := d.GetMatching(policy.Spec.ResourceSelectors)
Expand All @@ -1336,10 +1379,12 @@ func (d *ResourceDetector) HandleClusterPropagationPolicyCreationOrUpdate(policy

for _, key := range matchedKeys {
d.RemoveWaiting(key)
d.Processor.Add(key)
d.Processor.Add(keys.ClusterWideKeyWithConfig{ClusterWideKey: key, ResourceChangeByKarmada: true})
}

// if preemption is enabled, handle the preemption process.
// If preemption is enabled, handle the preemption process.
// If this policy succeeds in preempting resource managed by other policy, the label marked on relevant resource
// will be replaced, which gives the resource template a change to match to this policy.
if preemptionEnabled(policy.Spec.Preemption) {
return d.handleClusterPropagationPolicyPreemption(policy)
}
Expand Down
34 changes: 34 additions & 0 deletions pkg/detector/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ limitations under the License.
package detector

import (
"fmt"

"k8s.io/apimachinery/pkg/runtime"

"github.com/karmada-io/karmada/pkg/util"
"github.com/karmada-io/karmada/pkg/util/fedinformer/keys"
)
Expand All @@ -25,3 +29,33 @@ import (
func ClusterWideKeyFunc(obj interface{}) (util.QueueKey, error) {
return keys.ClusterWideKeyFunc(obj)
}

const (
// ObjectChangedByKarmada the key name for a bool value which describes whether the object is changed by Karmada
ObjectChangedByKarmada = "ObjectChangedByKarmada"
)

// ResourceItem a object key with certain extended config
type ResourceItem struct {
Obj runtime.Object
ResourceChangeByKarmada bool
}

// ResourceItemKeyFunc generates a ClusterWideKeyWithConfig for object.
func ResourceItemKeyFunc(obj interface{}) (util.QueueKey, error) {
var err error
key := keys.ClusterWideKeyWithConfig{}

resourceItem, ok := obj.(ResourceItem)
if !ok {
return key, fmt.Errorf("failed to assert object as ResourceItem")
}

key.ResourceChangeByKarmada = resourceItem.ResourceChangeByKarmada
key.ClusterWideKey, err = keys.ClusterWideKeyFunc(resourceItem.Obj)
if err != nil {
return key, err
}

return key, nil
}
Loading

0 comments on commit 205c5e5

Please sign in to comment.