Skip to content

Commit 46dbf07

Browse files
build PreservedLabelState when triggering evition
Signed-off-by: changzhen <[email protected]>
1 parent f168061 commit 46dbf07

File tree

11 files changed

+644
-82
lines changed

11 files changed

+644
-82
lines changed

api/openapi-spec/swagger.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20098,6 +20098,14 @@
2009820098
"producer"
2009920099
],
2010020100
"properties": {
20101+
"clustersBeforeFailover": {
20102+
"description": "ClustersBeforeFailover records the clusters where running the application before failover.",
20103+
"type": "array",
20104+
"items": {
20105+
"type": "string",
20106+
"default": ""
20107+
}
20108+
},
2010120109
"creationTimestamp": {
2010220110
"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.",
2010320111
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time"

charts/karmada/_crds/bases/work/work.karmada.io_clusterresourcebindings.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,12 @@ spec:
401401
description: GracefulEvictionTask represents a graceful eviction
402402
task.
403403
properties:
404+
clustersBeforeFailover:
405+
description: ClustersBeforeFailover records the clusters where
406+
running the application before failover.
407+
items:
408+
type: string
409+
type: array
404410
creationTimestamp:
405411
description: |-
406412
CreationTimestamp is a timestamp representing the server time when this object was

charts/karmada/_crds/bases/work/work.karmada.io_resourcebindings.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,12 @@ spec:
401401
description: GracefulEvictionTask represents a graceful eviction
402402
task.
403403
properties:
404+
clustersBeforeFailover:
405+
description: ClustersBeforeFailover records the clusters where
406+
running the application before failover.
407+
items:
408+
type: string
409+
type: array
404410
creationTimestamp:
405411
description: |-
406412
CreationTimestamp is a timestamp representing the server time when this object was

pkg/apis/work/v1alpha2/binding_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,9 @@ type GracefulEvictionTask struct {
300300
// Populated by the system. Read-only.
301301
// +optional
302302
CreationTimestamp *metav1.Time `json:"creationTimestamp,omitempty"`
303+
304+
// ClustersBeforeFailover records the clusters where running the application before failover.
305+
ClustersBeforeFailover []string `json:"clustersBeforeFailover,omitempty"`
303306
}
304307

305308
// BindingSnapshot is a snapshot of a ResourceBinding or ClusterResourceBinding.

pkg/apis/work/v1alpha2/binding_types_helper.go

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ import policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
2020

2121
// TaskOptions represents options for GracefulEvictionTasks.
2222
type TaskOptions struct {
23-
purgeMode policyv1alpha1.PurgeMode
24-
producer string
25-
reason string
26-
message string
27-
gracePeriodSeconds *int32
28-
suppressDeletion *bool
23+
purgeMode policyv1alpha1.PurgeMode
24+
producer string
25+
reason string
26+
message string
27+
gracePeriodSeconds *int32
28+
suppressDeletion *bool
29+
preservedLabelState map[string]string
30+
clustersBeforeFailover []string
2931
}
3032

3133
// Option configures a TaskOptions
@@ -83,6 +85,20 @@ func WithSuppressDeletion(suppressDeletion *bool) Option {
8385
}
8486
}
8587

88+
// WithPreservedLabelState sets the preservedLabelState for TaskOptions
89+
func WithPreservedLabelState(preservedLabelState map[string]string) Option {
90+
return func(o *TaskOptions) {
91+
o.preservedLabelState = preservedLabelState
92+
}
93+
}
94+
95+
// WithClustersBeforeFailover sets the clustersBeforeFailover for TaskOptions
96+
func WithClustersBeforeFailover(clustersBeforeFailover []string) Option {
97+
return func(o *TaskOptions) {
98+
o.clustersBeforeFailover = clustersBeforeFailover
99+
}
100+
}
101+
86102
// TargetContains checks if specific cluster present on the target list.
87103
func (s *ResourceBindingSpec) TargetContains(name string) bool {
88104
for i := range s.Clusters {
@@ -163,13 +179,15 @@ func (s *ResourceBindingSpec) GracefulEvictCluster(name string, options *TaskOpt
163179
// build eviction task
164180
evictingCluster := evictCluster.DeepCopy()
165181
evictionTask := GracefulEvictionTask{
166-
FromCluster: evictingCluster.Name,
167-
PurgeMode: options.purgeMode,
168-
Reason: options.reason,
169-
Message: options.message,
170-
Producer: options.producer,
171-
GracePeriodSeconds: options.gracePeriodSeconds,
172-
SuppressDeletion: options.suppressDeletion,
182+
FromCluster: evictingCluster.Name,
183+
PurgeMode: options.purgeMode,
184+
Reason: options.reason,
185+
Message: options.message,
186+
Producer: options.producer,
187+
GracePeriodSeconds: options.gracePeriodSeconds,
188+
SuppressDeletion: options.suppressDeletion,
189+
PreservedLabelState: options.preservedLabelState,
190+
ClustersBeforeFailover: options.clustersBeforeFailover,
173191
}
174192
if evictingCluster.Replicas > 0 {
175193
evictionTask.Replicas = &evictingCluster.Replicas

pkg/apis/work/v1alpha2/zz_generated.deepcopy.go

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/controllers/applicationfailover/common.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,21 @@ limitations under the License.
1717
package applicationfailover
1818

1919
import (
20+
"bytes"
21+
"encoding/json"
22+
"fmt"
2023
"sync"
2124

2225
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2326
"k8s.io/apimachinery/pkg/types"
2427
"k8s.io/apimachinery/pkg/util/sets"
28+
"k8s.io/client-go/util/jsonpath"
29+
"k8s.io/klog/v2"
30+
"k8s.io/utils/ptr"
2531

32+
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
2633
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
34+
"github.com/karmada-io/karmada/pkg/features"
2735
)
2836

2937
type workloadUnhealthyMap struct {
@@ -117,3 +125,106 @@ func distinguishUnhealthyClustersWithOthers(aggregatedStatusItems []workv1alpha2
117125

118126
return unhealthyClusters, others
119127
}
128+
129+
func buildPreservedLabelState(statePreservation *policyv1alpha1.StatePreservation, rawStatus []byte) (map[string]string, error) {
130+
results := make(map[string]string, len(statePreservation.Rules))
131+
for _, rule := range statePreservation.Rules {
132+
value, err := parseJSONValue(rawStatus, rule.JSONPath)
133+
if err != nil {
134+
klog.Errorf("Failed to parse value with jsonPath(%s) from status(%v), error: %v",
135+
rule.JSONPath, string(rawStatus), err)
136+
return nil, err
137+
}
138+
results[rule.AliasLabelName] = value
139+
}
140+
141+
return results, nil
142+
}
143+
144+
func parseJSONValue(rawStatus []byte, jsonPath string) (string, error) {
145+
template := jsonPath
146+
j := jsonpath.New(jsonPath)
147+
j.AllowMissingKeys(false)
148+
err := j.Parse(template)
149+
if err != nil {
150+
return "", err
151+
}
152+
153+
buf := new(bytes.Buffer)
154+
unmarshalled := make(map[string]interface{})
155+
_ = json.Unmarshal(rawStatus, &unmarshalled)
156+
err = j.Execute(buf, unmarshalled)
157+
if err != nil {
158+
return "", err
159+
}
160+
return buf.String(), nil
161+
}
162+
163+
func findTargetStatusItemByCluster(aggregatedStatusItems []workv1alpha2.AggregatedStatusItem, cluster string) (workv1alpha2.AggregatedStatusItem, bool) {
164+
if len(aggregatedStatusItems) == 0 {
165+
return workv1alpha2.AggregatedStatusItem{}, false
166+
}
167+
168+
for index, statusItem := range aggregatedStatusItems {
169+
if statusItem.ClusterName == cluster {
170+
return aggregatedStatusItems[index], true
171+
}
172+
}
173+
174+
return workv1alpha2.AggregatedStatusItem{}, false
175+
}
176+
177+
func getClusterNamesFromTargetClusters(targetClusters []workv1alpha2.TargetCluster) []string {
178+
if targetClusters == nil {
179+
return nil
180+
}
181+
182+
clusters := make([]string, 0, len(targetClusters))
183+
for _, targetCluster := range targetClusters {
184+
clusters = append(clusters, targetCluster.Name)
185+
}
186+
return clusters
187+
}
188+
189+
func buildTaskOptions(failoverBehavior *policyv1alpha1.ApplicationFailoverBehavior, aggregatedStatus []workv1alpha2.AggregatedStatusItem, cluster, producer string, clustersBeforeFailover []string) ([]workv1alpha2.Option, error) {
190+
var taskOpts []workv1alpha2.Option
191+
taskOpts = append(taskOpts, workv1alpha2.WithProducer(producer))
192+
taskOpts = append(taskOpts, workv1alpha2.WithReason(workv1alpha2.EvictionReasonApplicationFailure))
193+
taskOpts = append(taskOpts, workv1alpha2.WithPurgeMode(failoverBehavior.PurgeMode))
194+
195+
if failoverBehavior.StatePreservation != nil && len(failoverBehavior.StatePreservation.Rules) != 0 {
196+
targetStatusItem, exist := findTargetStatusItemByCluster(aggregatedStatus, cluster)
197+
if !exist || targetStatusItem.Status == nil || targetStatusItem.Status.Raw == nil {
198+
return nil, fmt.Errorf("the application status has not yet been collected from Cluster(%s)", cluster)
199+
}
200+
preservedLabelState, err := buildPreservedLabelState(failoverBehavior.StatePreservation, targetStatusItem.Status.Raw)
201+
if err != nil {
202+
return nil, err
203+
}
204+
if preservedLabelState != nil {
205+
taskOpts = append(taskOpts, workv1alpha2.WithPreservedLabelState(preservedLabelState))
206+
taskOpts = append(taskOpts, workv1alpha2.WithClustersBeforeFailover(clustersBeforeFailover))
207+
}
208+
}
209+
210+
switch failoverBehavior.PurgeMode {
211+
case policyv1alpha1.Graciously:
212+
if features.FeatureGate.Enabled(features.GracefulEviction) {
213+
taskOpts = append(taskOpts, workv1alpha2.WithGracePeriodSeconds(failoverBehavior.GracePeriodSeconds))
214+
} else {
215+
err := fmt.Errorf("GracefulEviction featureGate must be enabled when purgeMode is %s", policyv1alpha1.Graciously)
216+
klog.Error(err)
217+
return nil, err
218+
}
219+
case policyv1alpha1.Never:
220+
if features.FeatureGate.Enabled(features.GracefulEviction) {
221+
taskOpts = append(taskOpts, workv1alpha2.WithSuppressDeletion(ptr.To[bool](true)))
222+
} else {
223+
err := fmt.Errorf("GracefulEviction featureGate must be enabled when purgeMode is %s", policyv1alpha1.Never)
224+
klog.Error(err)
225+
return nil, err
226+
}
227+
}
228+
229+
return taskOpts, nil
230+
}

0 commit comments

Comments
 (0)