Skip to content

Commit

Permalink
build PreservedLabelState when triggering evition
Browse files Browse the repository at this point in the history
Signed-off-by: changzhen <[email protected]>
  • Loading branch information
XiShanYongYe-Chang committed Nov 27, 2024
1 parent f168061 commit 4a5a80d
Show file tree
Hide file tree
Showing 11 changed files with 645 additions and 82 deletions.
8 changes: 8 additions & 0 deletions api/openapi-spec/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -20098,6 +20098,14 @@
"producer"
],
"properties": {
"clusterBeforeFailover": {
"description": "ClusterBeforeFailover records the clusters where running the application before failover.",
"type": "array",
"items": {
"type": "string",
"default": ""
}
},
"creationTimestamp": {
"description": "CreationTimestamp is a timestamp representing the server time when this object was created. Clients should not set this value to avoid the time inconsistency issue. It is represented in RFC3339 form(like '2021-04-25T10:02:10Z') and is in UTC.\n\nPopulated by the system. Read-only.",
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,12 @@ spec:
description: GracefulEvictionTask represents a graceful eviction
task.
properties:
clusterBeforeFailover:
description: ClusterBeforeFailover records the clusters where
running the application before failover.
items:
type: string
type: array
creationTimestamp:
description: |-
CreationTimestamp is a timestamp representing the server time when this object was
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,12 @@ spec:
description: GracefulEvictionTask represents a graceful eviction
task.
properties:
clusterBeforeFailover:
description: ClusterBeforeFailover records the clusters where
running the application before failover.
items:
type: string
type: array
creationTimestamp:
description: |-
CreationTimestamp is a timestamp representing the server time when this object was
Expand Down
3 changes: 3 additions & 0 deletions pkg/apis/work/v1alpha2/binding_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,9 @@ type GracefulEvictionTask struct {
// Populated by the system. Read-only.
// +optional
CreationTimestamp *metav1.Time `json:"creationTimestamp,omitempty"`

// ClusterBeforeFailover records the clusters where running the application before failover.
ClusterBeforeFailover []string `json:"clusterBeforeFailover,omitempty"`
}

// BindingSnapshot is a snapshot of a ResourceBinding or ClusterResourceBinding.
Expand Down
44 changes: 31 additions & 13 deletions pkg/apis/work/v1alpha2/binding_types_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ import policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"

// TaskOptions represents options for GracefulEvictionTasks.
type TaskOptions struct {
purgeMode policyv1alpha1.PurgeMode
producer string
reason string
message string
gracePeriodSeconds *int32
suppressDeletion *bool
purgeMode policyv1alpha1.PurgeMode
producer string
reason string
message string
gracePeriodSeconds *int32
suppressDeletion *bool
preservedLabelState map[string]string
clustersBeforeFailover []string
}

// Option configures a TaskOptions
Expand Down Expand Up @@ -83,6 +85,20 @@ func WithSuppressDeletion(suppressDeletion *bool) Option {
}
}

// WithPreservedLabelState sets the preservedLabelState for TaskOptions
func WithPreservedLabelState(preservedLabelState map[string]string) Option {
return func(o *TaskOptions) {
o.preservedLabelState = preservedLabelState
}
}

// WithClustersBeforeFailover sets the clustersBeforeFailover for TaskOptions
func WithClustersBeforeFailover(clustersBeforeFailover []string) Option {
return func(o *TaskOptions) {
o.clustersBeforeFailover = clustersBeforeFailover
}
}

// TargetContains checks if specific cluster present on the target list.
func (s *ResourceBindingSpec) TargetContains(name string) bool {
for i := range s.Clusters {
Expand Down Expand Up @@ -163,13 +179,15 @@ func (s *ResourceBindingSpec) GracefulEvictCluster(name string, options *TaskOpt
// build eviction task
evictingCluster := evictCluster.DeepCopy()
evictionTask := GracefulEvictionTask{
FromCluster: evictingCluster.Name,
PurgeMode: options.purgeMode,
Reason: options.reason,
Message: options.message,
Producer: options.producer,
GracePeriodSeconds: options.gracePeriodSeconds,
SuppressDeletion: options.suppressDeletion,
FromCluster: evictingCluster.Name,
PurgeMode: options.purgeMode,
Reason: options.reason,
Message: options.message,
Producer: options.producer,
GracePeriodSeconds: options.gracePeriodSeconds,
SuppressDeletion: options.suppressDeletion,
PreservedLabelState: options.preservedLabelState,
ClusterBeforeFailover: options.clustersBeforeFailover,
}
if evictingCluster.Replicas > 0 {
evictionTask.Replicas = &evictingCluster.Replicas
Expand Down
17 changes: 17 additions & 0 deletions pkg/apis/work/v1alpha2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

112 changes: 112 additions & 0 deletions pkg/controllers/applicationfailover/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,21 @@ limitations under the License.
package applicationfailover

import (
"bytes"
"encoding/json"
"fmt"
"sync"

"github.com/karmada-io/karmada/pkg/features"
"k8s.io/utils/ptr"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/util/jsonpath"
"k8s.io/klog/v2"

policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
)

Expand Down Expand Up @@ -117,3 +126,106 @@ func distinguishUnhealthyClustersWithOthers(aggregatedStatusItems []workv1alpha2

return unhealthyClusters, others
}

func buildPreservedLabelState(statePreservation *policyv1alpha1.StatePreservation, rawStatus []byte) (map[string]string, error) {
results := make(map[string]string, len(statePreservation.Rules))
for _, rule := range statePreservation.Rules {
value, err := parseJSONValue(rawStatus, rule.JSONPath)
if err != nil {
klog.Errorf("Failed to parse value with jsonPath(%s) from status(%v), error: %v",
rule.JSONPath, string(rawStatus), err)
return nil, err
}
results[rule.AliasLabelName] = value
}

return results, nil
}

func parseJSONValue(rawStatus []byte, jsonPath string) (string, error) {
template := jsonPath
j := jsonpath.New(jsonPath)
j.AllowMissingKeys(false)
err := j.Parse(template)
if err != nil {
return "", err
}

buf := new(bytes.Buffer)
unmarshalled := make(map[string]interface{})
_ = json.Unmarshal(rawStatus, &unmarshalled)
err = j.Execute(buf, unmarshalled)
if err != nil {
return "", err
}
return buf.String(), nil
}

func findTargetStatusItemByCluster(aggregatedStatusItems []workv1alpha2.AggregatedStatusItem, cluster string) (workv1alpha2.AggregatedStatusItem, bool) {
if len(aggregatedStatusItems) == 0 {
return workv1alpha2.AggregatedStatusItem{}, false
}

for index, statusItem := range aggregatedStatusItems {
if statusItem.ClusterName == cluster {
return aggregatedStatusItems[index], true
}
}

return workv1alpha2.AggregatedStatusItem{}, false
}

func getClusterNamesFromTargetClusters(targetClusters []workv1alpha2.TargetCluster) []string {
if targetClusters == nil {
return nil
}

clusters := make([]string, 0, len(targetClusters))
for _, targetCluster := range targetClusters {
clusters = append(clusters, targetCluster.Name)
}
return clusters
}

func buildTaskOptions(failoverBehavior *policyv1alpha1.ApplicationFailoverBehavior, aggregatedStatus []workv1alpha2.AggregatedStatusItem, cluster, producer string, clustersBeforeFailover []string) ([]workv1alpha2.Option, error) {
var taskOpts []workv1alpha2.Option
taskOpts = append(taskOpts, workv1alpha2.WithProducer(producer))
taskOpts = append(taskOpts, workv1alpha2.WithReason(workv1alpha2.EvictionReasonApplicationFailure))
taskOpts = append(taskOpts, workv1alpha2.WithPurgeMode(failoverBehavior.PurgeMode))

if failoverBehavior.StatePreservation != nil && len(failoverBehavior.StatePreservation.Rules) != 0 {
targetStatusItem, exist := findTargetStatusItemByCluster(aggregatedStatus, cluster)
if !exist || targetStatusItem.Status == nil || targetStatusItem.Status.Raw == nil {
return nil, fmt.Errorf("the binding status under Cluster(%s) has not been colloected yet ", cluster)
}
preservedLabelState, err := buildPreservedLabelState(failoverBehavior.StatePreservation, targetStatusItem.Status.Raw)
if err != nil {
return nil, err
}
if preservedLabelState != nil {
taskOpts = append(taskOpts, workv1alpha2.WithPreservedLabelState(preservedLabelState))
taskOpts = append(taskOpts, workv1alpha2.WithClustersBeforeFailover(clustersBeforeFailover))
}
}

switch failoverBehavior.PurgeMode {
case policyv1alpha1.Graciously:
if features.FeatureGate.Enabled(features.GracefulEviction) {
taskOpts = append(taskOpts, workv1alpha2.WithGracePeriodSeconds(failoverBehavior.GracePeriodSeconds))
} else {
err := fmt.Errorf("GracefulEviction featureGate must be enabled when purgeMode is %s", policyv1alpha1.Graciously)
klog.Error(err)
return nil, err
}
case policyv1alpha1.Never:
if features.FeatureGate.Enabled(features.GracefulEviction) {
taskOpts = append(taskOpts, workv1alpha2.WithSuppressDeletion(ptr.To[bool](true)))
} else {
err := fmt.Errorf("GracefulEviction featureGate must be enabled when purgeMode is %s", policyv1alpha1.Never)
klog.Error(err)
return nil, err
}
}

return taskOpts, nil
}
Loading

0 comments on commit 4a5a80d

Please sign in to comment.