Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build PreservedLabelState when triggering evition in RB/CRB application controller #5887

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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": {
"clustersBeforeFailover": {
"description": "ClustersBeforeFailover 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:
clustersBeforeFailover:
description: ClustersBeforeFailover 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:
clustersBeforeFailover:
description: ClustersBeforeFailover 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"`

// ClustersBeforeFailover records the clusters where running the application before failover.
ClustersBeforeFailover []string `json:"clustersBeforeFailover,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,
ClustersBeforeFailover: 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.

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

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

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"
"k8s.io/utils/ptr"

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

type workloadUnhealthyMap struct {
Expand Down Expand Up @@ -117,3 +125,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 application status has not yet been collected from Cluster(%s)", 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