From 2dd201275ccd18b8e4aeb57480be16d0ab907ffd Mon Sep 17 00:00:00 2001 From: RainbowMango Date: Fri, 7 Mar 2025 15:43:28 +0800 Subject: [PATCH] Sync APIs from karmada repo based on v1.13.0 Signed-off-by: RainbowMango --- cluster/mutation/mutation_test.go | 368 -------------------- go.mod | 8 +- go.sum | 3 + operator/v1alpha1/type.go | 12 + operator/v1alpha1/zz_generated.deepcopy.go | 7 + policy/v1alpha1/propagation_types.go | 65 ++++ policy/v1alpha1/zz_generated.deepcopy.go | 21 ++ work/v1alpha2/binding_types.go | 31 +- work/v1alpha2/binding_types_helper.go | 21 ++ work/v1alpha2/binding_types_helper_test.go | 384 --------------------- work/v1alpha2/zz_generated.deepcopy.go | 45 ++- 11 files changed, 207 insertions(+), 758 deletions(-) delete mode 100644 cluster/mutation/mutation_test.go delete mode 100644 work/v1alpha2/binding_types_helper_test.go diff --git a/cluster/mutation/mutation_test.go b/cluster/mutation/mutation_test.go deleted file mode 100644 index 313ab71..0000000 --- a/cluster/mutation/mutation_test.go +++ /dev/null @@ -1,368 +0,0 @@ -/* -Copyright 2022 The Karmada Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package mutation - -import ( - "fmt" - "math" - "reflect" - "testing" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - - clusterapis "github.com/karmada-io/api/cluster" -) - -func TestMutateCluster(t *testing.T) { - type args struct { - cluster *clusterapis.Cluster - } - tests := []struct { - name string - args args - fun func(args) error - }{ - { - name: "test mutate cluster Taints", - args: args{ - cluster: &clusterapis.Cluster{ - Spec: clusterapis.ClusterSpec{ - Taints: []corev1.Taint{ - { - Key: "foo", - Value: "abc", - Effect: corev1.TaintEffectNoSchedule, - }, - { - Key: "bar", - Effect: corev1.TaintEffectNoExecute, - }}}}, - }, - fun: func(data args) error { - for i := range data.cluster.Spec.Taints { - if data.cluster.Spec.Taints[i].Effect == corev1.TaintEffectNoExecute && data.cluster.Spec.Taints[i].TimeAdded == nil { - return fmt.Errorf("failed to mutate cluster, taints TimeAdded should not be nil") - } - } - return nil - }, - }, - { - name: "test mutate cluster Zone", - args: args{ - cluster: &clusterapis.Cluster{ - Spec: clusterapis.ClusterSpec{ - Zone: "zone1", - }, - }, - }, - fun: func(data args) error { - if data.cluster.Spec.Zone != "" && len(data.cluster.Spec.Zones) == 0 { - return fmt.Errorf("failed to mutate cluster, zones should not be nil") - } - return nil - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - MutateCluster(tt.args.cluster) - if err := tt.fun(tt.args); err != nil { - t.Error(err) - } - }) - } -} - -func TestStandardizeClusterResourceModels(t *testing.T) { - testCases := map[string]struct { - models []clusterapis.ResourceModel - expectedModels []clusterapis.ResourceModel - }{ - "sort models": { - models: []clusterapis.ResourceModel{ - { - Grade: 2, - Ranges: []clusterapis.ResourceModelRange{ - { - Name: corev1.ResourceCPU, - Min: *resource.NewQuantity(2, resource.DecimalSI), - Max: *resource.NewQuantity(math.MaxInt64, resource.DecimalSI), - }, - }, - }, - { - Grade: 1, - Ranges: []clusterapis.ResourceModelRange{ - { - Name: corev1.ResourceCPU, - Min: *resource.NewQuantity(0, resource.DecimalSI), - Max: *resource.NewQuantity(2, resource.DecimalSI), - }, - }, - }, - }, - expectedModels: []clusterapis.ResourceModel{ - { - Grade: 1, - Ranges: []clusterapis.ResourceModelRange{ - { - Name: corev1.ResourceCPU, - Min: *resource.NewQuantity(0, resource.DecimalSI), - Max: *resource.NewQuantity(2, resource.DecimalSI), - }, - }, - }, - { - Grade: 2, - Ranges: []clusterapis.ResourceModelRange{ - { - Name: corev1.ResourceCPU, - Min: *resource.NewQuantity(2, resource.DecimalSI), - Max: *resource.NewQuantity(math.MaxInt64, resource.DecimalSI), - }, - }, - }, - }, - }, - "start with 0": { - models: []clusterapis.ResourceModel{ - { - Grade: 1, - Ranges: []clusterapis.ResourceModelRange{ - { - Name: corev1.ResourceCPU, - Min: *resource.NewQuantity(1, resource.DecimalSI), - Max: *resource.NewQuantity(math.MaxInt64, resource.DecimalSI), - }, - }, - }, - }, - expectedModels: []clusterapis.ResourceModel{ - { - Grade: 1, - Ranges: []clusterapis.ResourceModelRange{ - { - Name: corev1.ResourceCPU, - Min: *resource.NewQuantity(0, resource.DecimalSI), - Max: *resource.NewQuantity(math.MaxInt64, resource.DecimalSI), - }, - }, - }, - }, - }, - "end with MaxInt64": { - models: []clusterapis.ResourceModel{ - { - Grade: 1, - Ranges: []clusterapis.ResourceModelRange{ - { - Name: corev1.ResourceCPU, - Min: *resource.NewQuantity(0, resource.DecimalSI), - Max: *resource.NewQuantity(2, resource.DecimalSI), - }, - }, - }, - }, - expectedModels: []clusterapis.ResourceModel{ - { - Grade: 1, - Ranges: []clusterapis.ResourceModelRange{ - { - Name: corev1.ResourceCPU, - Min: *resource.NewQuantity(0, resource.DecimalSI), - Max: *resource.NewQuantity(math.MaxInt64, resource.DecimalSI), - }, - }, - }, - }, - }, - } - - for name, testCase := range testCases { - StandardizeClusterResourceModels(testCase.models) - if !reflect.DeepEqual(testCase.models, testCase.expectedModels) { - t.Errorf("expected sorted resource models for %q, but it did not work", name) - return - } - } -} - -func TestSetDefaultClusterResourceModels(t *testing.T) { - type args struct { - cluster *clusterapis.Cluster - } - tests := []struct { - name string - args args - wantModels []clusterapis.ResourceModel - }{ - { - name: "test set default Cluster", - args: args{ - cluster: &clusterapis.Cluster{}, - }, - wantModels: []clusterapis.ResourceModel{ - { - Grade: 0, - Ranges: []clusterapis.ResourceModelRange{ - { - Name: corev1.ResourceCPU, - Min: *resource.NewQuantity(0, resource.DecimalSI), - Max: *resource.NewQuantity(1, resource.DecimalSI), - }, - { - Name: corev1.ResourceMemory, - Min: *resource.NewQuantity(0, resource.BinarySI), - Max: *resource.NewQuantity(4*GB, resource.BinarySI), - }, - }, - }, - { - Grade: 1, - Ranges: []clusterapis.ResourceModelRange{ - { - Name: corev1.ResourceCPU, - Min: *resource.NewQuantity(1, resource.DecimalSI), - Max: *resource.NewQuantity(2, resource.DecimalSI), - }, - { - Name: corev1.ResourceMemory, - Min: *resource.NewQuantity(4*GB, resource.BinarySI), - Max: *resource.NewQuantity(16*GB, resource.BinarySI), - }, - }, - }, - { - Grade: 2, - Ranges: []clusterapis.ResourceModelRange{ - { - Name: corev1.ResourceCPU, - Min: *resource.NewQuantity(2, resource.DecimalSI), - Max: *resource.NewQuantity(4, resource.DecimalSI), - }, - { - Name: corev1.ResourceMemory, - Min: *resource.NewQuantity(16*GB, resource.BinarySI), - Max: *resource.NewQuantity(32*GB, resource.BinarySI), - }, - }, - }, - { - Grade: 3, - Ranges: []clusterapis.ResourceModelRange{ - { - Name: corev1.ResourceCPU, - Min: *resource.NewQuantity(4, resource.DecimalSI), - Max: *resource.NewQuantity(8, resource.DecimalSI), - }, - { - Name: corev1.ResourceMemory, - Min: *resource.NewQuantity(32*GB, resource.BinarySI), - Max: *resource.NewQuantity(64*GB, resource.BinarySI), - }, - }, - }, - { - Grade: 4, - Ranges: []clusterapis.ResourceModelRange{ - { - Name: corev1.ResourceCPU, - Min: *resource.NewQuantity(8, resource.DecimalSI), - Max: *resource.NewQuantity(16, resource.DecimalSI), - }, - { - Name: corev1.ResourceMemory, - Min: *resource.NewQuantity(64*GB, resource.BinarySI), - Max: *resource.NewQuantity(128*GB, resource.BinarySI), - }, - }, - }, - { - Grade: 5, - Ranges: []clusterapis.ResourceModelRange{ - { - Name: corev1.ResourceCPU, - Min: *resource.NewQuantity(16, resource.DecimalSI), - Max: *resource.NewQuantity(32, resource.DecimalSI), - }, - { - Name: corev1.ResourceMemory, - Min: *resource.NewQuantity(128*GB, resource.BinarySI), - Max: *resource.NewQuantity(256*GB, resource.BinarySI), - }, - }, - }, - { - Grade: 6, - Ranges: []clusterapis.ResourceModelRange{ - { - Name: corev1.ResourceCPU, - Min: *resource.NewQuantity(32, resource.DecimalSI), - Max: *resource.NewQuantity(64, resource.DecimalSI), - }, - { - Name: corev1.ResourceMemory, - Min: *resource.NewQuantity(256*GB, resource.BinarySI), - Max: *resource.NewQuantity(512*GB, resource.BinarySI), - }, - }, - }, - { - Grade: 7, - Ranges: []clusterapis.ResourceModelRange{ - { - Name: corev1.ResourceCPU, - Min: *resource.NewQuantity(64, resource.DecimalSI), - Max: *resource.NewQuantity(128, resource.DecimalSI), - }, - { - Name: corev1.ResourceMemory, - Min: *resource.NewQuantity(512*GB, resource.BinarySI), - Max: *resource.NewQuantity(1024*GB, resource.BinarySI), - }, - }, - }, - { - Grade: 8, - Ranges: []clusterapis.ResourceModelRange{ - { - Name: corev1.ResourceCPU, - Min: *resource.NewQuantity(128, resource.DecimalSI), - Max: *resource.NewQuantity(math.MaxInt64, resource.DecimalSI), - }, - { - Name: corev1.ResourceMemory, - Min: *resource.NewQuantity(1024*GB, resource.BinarySI), - Max: *resource.NewQuantity(math.MaxInt64, resource.BinarySI), - }, - }, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(*testing.T) { - SetDefaultClusterResourceModels(tt.args.cluster) - }) - if !reflect.DeepEqual(tt.args.cluster.Spec.ResourceModels, tt.wantModels) { - t.Errorf("SetDefaultClusterResourceModels expected resourceModels %+v, bud get %+v", tt.wantModels, tt.args.cluster.Spec.ResourceModels) - return - } - } -} diff --git a/go.mod b/go.mod index 2ce45a5..76a1014 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/karmada-io/api -go 1.22.9 +go 1.22.12 require ( - k8s.io/api v0.31.2 - k8s.io/apiextensions-apiserver v0.31.2 - k8s.io/apimachinery v0.31.2 + k8s.io/api v0.31.3 + k8s.io/apiextensions-apiserver v0.31.3 + k8s.io/apimachinery v0.31.3 k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 sigs.k8s.io/controller-runtime v0.19.1 ) diff --git a/go.sum b/go.sum index 1cf054a..d89e1ab 100644 --- a/go.sum +++ b/go.sum @@ -83,10 +83,13 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0= k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk= +k8s.io/api v0.31.3/go.mod h1:UJrkIp9pnMOI9K2nlL6vwpxRzzEX5sWgn8kGQe92kCE= k8s.io/apiextensions-apiserver v0.31.2 h1:W8EwUb8+WXBLu56ser5IudT2cOho0gAKeTOnywBLxd0= k8s.io/apiextensions-apiserver v0.31.2/go.mod h1:i+Geh+nGCJEGiCGR3MlBDkS7koHIIKWVfWeRFiOsUcM= +k8s.io/apiextensions-apiserver v0.31.3/go.mod h1:2DSpFhUZZJmn/cr/RweH1cEVVbzFw9YBu4T+U3mf1e4= k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw= k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/apimachinery v0.31.3/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= diff --git a/operator/v1alpha1/type.go b/operator/v1alpha1/type.go index 69731e8..9055ba4 100644 --- a/operator/v1alpha1/type.go +++ b/operator/v1alpha1/type.go @@ -353,6 +353,12 @@ type KarmadaAPIServer struct { // More info: https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/ // +optional FeatureGates map[string]bool `json:"featureGates,omitempty"` + + // SidecarContainers specifies a list of sidecar containers to be deployed + // within the Karmada API server pod. + // This enables users to integrate auxiliary services such as KMS plugins for configuring encryption at rest. + // +optional + SidecarContainers []corev1.Container `json:"sidecarContainers,omitempty"` } // KarmadaAggregatedAPIServer holds settings to karmada-aggregated-apiserver component of the karmada. @@ -648,6 +654,12 @@ type CommonSettings struct { // More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ // +optional Resources corev1.ResourceRequirements `json:"resources,omitempty"` + + // PriorityClassName specifies the priority class name for the component. + // If not specified, it defaults to "system-node-critical". + // +kubebuilder:default="system-node-critical" + // +optional + PriorityClassName string `json:"priorityClassName,omitempty"` } // Image allows to customize the image used for components. diff --git a/operator/v1alpha1/zz_generated.deepcopy.go b/operator/v1alpha1/zz_generated.deepcopy.go index 4bdec62..3bbb0ce 100644 --- a/operator/v1alpha1/zz_generated.deepcopy.go +++ b/operator/v1alpha1/zz_generated.deepcopy.go @@ -341,6 +341,13 @@ func (in *KarmadaAPIServer) DeepCopyInto(out *KarmadaAPIServer) { (*out)[key] = val } } + if in.SidecarContainers != nil { + in, out := &in.SidecarContainers, &out.SidecarContainers + *out = make([]v1.Container, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } diff --git a/policy/v1alpha1/propagation_types.go b/policy/v1alpha1/propagation_types.go index fe9316f..f0bbef4 100644 --- a/policy/v1alpha1/propagation_types.go +++ b/policy/v1alpha1/propagation_types.go @@ -200,6 +200,23 @@ type PropagationSpec struct { // // +optional PreserveResourcesOnDeletion *bool `json:"preserveResourcesOnDeletion,omitempty"` + + // SchedulePriority defines how Karmada should resolve the priority and preemption policy + // for workload scheduling. + // + // This setting is useful for controlling the scheduling behavior of offline workloads. + // By setting a higher or lower priority, users can control which workloads are scheduled first. + // Additionally, it allows specifying a preemption policy where higher-priority workloads can + // preempt lower-priority ones in scenarios of resource contention. + // + // Note: This feature is currently in the alpha stage. The priority-based scheduling functionality is + // controlled by the PriorityBasedScheduling feature gate, and preemption is controlled by the + // PriorityBasedPreemptiveScheduling feature gate. Currently, only priority-based scheduling is + // supported. Preemption functionality is not yet available and will be introduced in future + // releases as the feature matures. + // + // +optional + SchedulePriority *SchedulePriority `json:"schedulePriority,omitempty"` } // ResourceSelector the resources will be selected. @@ -660,6 +677,54 @@ const ( LazyActivation ActivationPreference = "Lazy" ) +// SchedulePriority defines how Karmada should resolve the priority and preemption policy +// for workload scheduling. +type SchedulePriority struct { + // PriorityClassSource specifies where Karmada should look for the PriorityClass definition. + // Available options: + // - KubePriorityClass: Uses Kubernetes PriorityClass (scheduling.k8s.io/v1) + // - PodPriorityClass: Uses PriorityClassName from PodTemplate: PodSpec.PriorityClassName (not yet implemented) + // - FederatedPriorityClass: Uses Karmada FederatedPriorityClass (not yet implemented) + // + // +kubebuilder:validation:Enum=KubePriorityClass + // +required + PriorityClassSource PriorityClassSource `json:"priorityClassSource"` + + // PriorityClassName specifies which PriorityClass to use. Its behavior depends on PriorityClassSource: + // + // Behavior of PriorityClassName: + // + // For KubePriorityClass: + // - When specified: Uses the named Kubernetes PriorityClass. + // + // For PodPriorityClass: + // - Uses PriorityClassName from the PodTemplate. + // - Not yet implemented. + // + // For FederatedPriorityClass: + // - Not yet implemented. + // + // +required + PriorityClassName string `json:"priorityClassName"` +} + +// PriorityClassSource defines the type for PriorityClassSource field. +type PriorityClassSource string + +const ( + // FederatedPriorityClass specifies to use Karmada FederatedPriorityClass for priority resolution. + // This feature is planned for future releases and is currently not implemented. + FederatedPriorityClass PriorityClassSource = "FederatedPriorityClass" + + // KubePriorityClass specifies to use Kubernetes native PriorityClass (scheduling.k8s.io/v1) + // for priority resolution. This is the default source. + KubePriorityClass PriorityClassSource = "KubePriorityClass" + + // PodPriorityClass specifies to use the PriorityClassName defined in the workload's + // PodTemplate for priority resolution. + PodPriorityClass PriorityClassSource = "PodPriorityClass" +) + // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // PropagationPolicyList contains a list of PropagationPolicy. diff --git a/policy/v1alpha1/zz_generated.deepcopy.go b/policy/v1alpha1/zz_generated.deepcopy.go index 2f50916..6d86fad 100644 --- a/policy/v1alpha1/zz_generated.deepcopy.go +++ b/policy/v1alpha1/zz_generated.deepcopy.go @@ -907,6 +907,11 @@ func (in *PropagationSpec) DeepCopyInto(out *PropagationSpec) { *out = new(bool) **out = **in } + if in.SchedulePriority != nil { + in, out := &in.SchedulePriority, &out.SchedulePriority + *out = new(SchedulePriority) + **out = **in + } return } @@ -984,6 +989,22 @@ func (in *RuleWithCluster) DeepCopy() *RuleWithCluster { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SchedulePriority) DeepCopyInto(out *SchedulePriority) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SchedulePriority. +func (in *SchedulePriority) DeepCopy() *SchedulePriority { + if in == nil { + return nil + } + out := new(SchedulePriority) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SpreadConstraint) DeepCopyInto(out *SpreadConstraint) { *out = *in diff --git a/work/v1alpha2/binding_types.go b/work/v1alpha2/binding_types.go index 77d0591..81cc8da 100644 --- a/work/v1alpha2/binding_types.go +++ b/work/v1alpha2/binding_types.go @@ -150,7 +150,7 @@ type ResourceBindingSpec struct { // Suspension declares the policy for suspending different aspects of propagation. // nil means no suspension. no default values. // +optional - Suspension *policyv1alpha1.Suspension `json:"suspension,omitempty"` + Suspension *Suspension `json:"suspension,omitempty"` // PreserveResourcesOnDeletion controls whether resources should be preserved on the // member clusters when the binding object is deleted. @@ -159,6 +159,10 @@ type ResourceBindingSpec struct { // This setting applies to all Work objects created under this binding object. // +optional PreserveResourcesOnDeletion *bool `json:"preserveResourcesOnDeletion,omitempty"` + + // SchedulePriority represents the scheduling priority assigned to workloads. + // +optional + SchedulePriority *SchedulePriority `json:"schedulePriority,omitempty"` } // ObjectReference contains enough information to locate the referenced object inside current cluster. @@ -322,6 +326,31 @@ type BindingSnapshot struct { Clusters []TargetCluster `json:"clusters,omitempty"` } +// Suspension defines the policy for suspending dispatching and scheduling. +type Suspension struct { + policyv1alpha1.Suspension `json:",inline"` + + // Scheduling controls whether scheduling should be suspended, the scheduler will pause scheduling and not + // process resource binding when the value is true and resume scheduling when it's false or nil. + // This is designed for third-party systems to temporarily pause the scheduling of applications, which enabling + // manage resource allocation, prioritize critical workloads, etc. + // It is expected that third-party systems use an admission webhook to suspend scheduling at the time of + // ResourceBinding creation. Once a ResourceBinding has been scheduled, it cannot be paused afterward, as it may + // lead to ineffective suspension. + // +optional + Scheduling *bool `json:"scheduling,omitempty"` +} + +// SchedulePriority represents the scheduling priority assigned to workloads. +type SchedulePriority struct { + // Priority specifies the scheduling priority for the binding. + // Higher values indicate a higher priority. + // If not explicitly set, the default value is 0. + // +kubebuilder:default=0 + // +optional + Priority int32 `json:"priority,omitempty"` +} + // ResourceBindingStatus represents the overall status of the strategy as well as the referenced resources. type ResourceBindingStatus struct { // SchedulerObservedGeneration is the generation(.metadata.generation) observed by the scheduler. diff --git a/work/v1alpha2/binding_types_helper.go b/work/v1alpha2/binding_types_helper.go index e46e7fa..2f87550 100644 --- a/work/v1alpha2/binding_types_helper.go +++ b/work/v1alpha2/binding_types_helper.go @@ -16,6 +16,8 @@ limitations under the License. package v1alpha2 +import policyv1alpha1 "github.com/karmada-io/api/policy/v1alpha1" + // TaskOptions represents options for GracefulEvictionTasks. type TaskOptions struct { purgeMode policyv1alpha1.PurgeMode @@ -192,3 +194,22 @@ func (s *ResourceBindingSpec) GracefulEvictCluster(name string, options *TaskOpt } s.GracefulEvictionTasks = append(s.GracefulEvictionTasks, evictionTask) } + +// SchedulingSuspended tells if the scheduling of ResourceBinding or +// ClusterResourceBinding is suspended. +func (s *ResourceBindingSpec) SchedulingSuspended() bool { + if s == nil || s.Suspension == nil || s.Suspension.Scheduling == nil { + return false + } + + return *s.Suspension.Scheduling +} + +// SchedulePriorityValue returns the scheduling priority declared +// by '.spec.SchedulePriority.Priority'. +func (s *ResourceBindingSpec) SchedulePriorityValue() int32 { + if s.SchedulePriority == nil { + return 0 + } + return s.SchedulePriority.Priority +} diff --git a/work/v1alpha2/binding_types_helper_test.go b/work/v1alpha2/binding_types_helper_test.go deleted file mode 100644 index 07a8f11..0000000 --- a/work/v1alpha2/binding_types_helper_test.go +++ /dev/null @@ -1,384 +0,0 @@ -/* -Copyright 2022 The Karmada Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha2 - -import ( - "reflect" - "testing" - - "k8s.io/utils/ptr" -) - -func TestResourceBindingSpec_TargetContains(t *testing.T) { - tests := []struct { - Name string - Spec ResourceBindingSpec - ClusterName string - Expect bool - }{ - { - Name: "cluster present in target", - Spec: ResourceBindingSpec{Clusters: []TargetCluster{{Name: "m1"}, {Name: "m2"}}}, - ClusterName: "m1", - Expect: true, - }, - { - Name: "cluster not present in target", - Spec: ResourceBindingSpec{Clusters: []TargetCluster{{Name: "m1"}, {Name: "m2"}}}, - ClusterName: "m3", - Expect: false, - }, - { - Name: "cluster is empty", - Spec: ResourceBindingSpec{Clusters: []TargetCluster{{Name: "m1"}, {Name: "m2"}}}, - ClusterName: "", - Expect: false, - }, - { - Name: "target list is empty", - Spec: ResourceBindingSpec{Clusters: []TargetCluster{}}, - ClusterName: "m1", - Expect: false, - }, - } - - for _, test := range tests { - tc := test - t.Run(tc.Name, func(t *testing.T) { - if tc.Spec.TargetContains(tc.ClusterName) != tc.Expect { - t.Fatalf("expect: %v, but got: %v", tc.Expect, tc.Spec.TargetContains(tc.ClusterName)) - } - }) - } -} - -func TestResourceBindingSpec_AssignedReplicasForCluster(t *testing.T) { - tests := []struct { - Name string - Spec ResourceBindingSpec - ClusterName string - ExpectReplicas int32 - }{ - { - Name: "returns valid replicas in case cluster present", - Spec: ResourceBindingSpec{Clusters: []TargetCluster{{Name: "m1", Replicas: 1}, {Name: "m2", Replicas: 2}}}, - ClusterName: "m1", - ExpectReplicas: 1, - }, - { - Name: "returns 0 in case cluster not present", - Spec: ResourceBindingSpec{Clusters: []TargetCluster{{Name: "m1", Replicas: 1}, {Name: "m2", Replicas: 2}}}, - ClusterName: "non-exist", - ExpectReplicas: 0, - }, - } - - for _, test := range tests { - tc := test - t.Run(tc.Name, func(t *testing.T) { - got := tc.Spec.AssignedReplicasForCluster(tc.ClusterName) - if tc.ExpectReplicas != got { - t.Fatalf("expect: %d, but got: %d", tc.ExpectReplicas, got) - } - }) - } -} - -func TestResourceBindingSpec_RemoveCluster(t *testing.T) { - tests := []struct { - Name string - InputSpec ResourceBindingSpec - ClusterName string - ExpectSpec ResourceBindingSpec - }{ - { - Name: "cluster not exist should do nothing", - InputSpec: ResourceBindingSpec{Clusters: []TargetCluster{{Name: "m1"}, {Name: "m2"}, {Name: "m3"}}}, - ClusterName: "no-exist", - ExpectSpec: ResourceBindingSpec{Clusters: []TargetCluster{{Name: "m1"}, {Name: "m2"}, {Name: "m3"}}}, - }, - { - Name: "remove cluster from head", - InputSpec: ResourceBindingSpec{Clusters: []TargetCluster{{Name: "m1"}, {Name: "m2"}, {Name: "m3"}}}, - ClusterName: "m1", - ExpectSpec: ResourceBindingSpec{Clusters: []TargetCluster{{Name: "m2"}, {Name: "m3"}}}, - }, - { - Name: "remove cluster from middle", - InputSpec: ResourceBindingSpec{Clusters: []TargetCluster{{Name: "m1"}, {Name: "m2"}, {Name: "m3"}}}, - ClusterName: "m2", - ExpectSpec: ResourceBindingSpec{Clusters: []TargetCluster{{Name: "m1"}, {Name: "m3"}}}, - }, - { - Name: "remove cluster from tail", - InputSpec: ResourceBindingSpec{Clusters: []TargetCluster{{Name: "m1"}, {Name: "m2"}, {Name: "m3"}}}, - ClusterName: "m3", - ExpectSpec: ResourceBindingSpec{Clusters: []TargetCluster{{Name: "m1"}, {Name: "m2"}}}, - }, - { - Name: "remove cluster from empty list", - InputSpec: ResourceBindingSpec{Clusters: []TargetCluster{}}, - ClusterName: "na", - ExpectSpec: ResourceBindingSpec{Clusters: []TargetCluster{}}, - }, - } - - for _, test := range tests { - tc := test - t.Run(tc.Name, func(t *testing.T) { - tc.InputSpec.RemoveCluster(tc.ClusterName) - if !reflect.DeepEqual(tc.InputSpec.Clusters, tc.ExpectSpec.Clusters) { - t.Fatalf("expect: %v, but got: %v", tc.ExpectSpec.Clusters, tc.InputSpec.Clusters) - } - }) - } -} - -func TestResourceBindingSpec_GracefulEvictCluster(t *testing.T) { - tests := []struct { - Name string - InputSpec ResourceBindingSpec - EvictEvent GracefulEvictionTask - ExpectSpec ResourceBindingSpec - }{ - { - Name: "cluster not exist should do nothing", - InputSpec: ResourceBindingSpec{ - Clusters: []TargetCluster{{Name: "m1"}, {Name: "m2"}, {Name: "m3"}}, - }, - EvictEvent: GracefulEvictionTask{FromCluster: "non-exist"}, - ExpectSpec: ResourceBindingSpec{ - Clusters: []TargetCluster{{Name: "m1"}, {Name: "m2"}, {Name: "m3"}}, - }, - }, - { - Name: "evict cluster from head", - InputSpec: ResourceBindingSpec{ - Clusters: []TargetCluster{{Name: "m1", Replicas: 1}, {Name: "m2", Replicas: 2}, {Name: "m3", Replicas: 3}}, - }, - EvictEvent: GracefulEvictionTask{ - FromCluster: "m1", - PurgeMode: policyv1alpha1.Immediately, - Reason: EvictionReasonTaintUntolerated, - Message: "graceful eviction", - Producer: EvictionProducerTaintManager, - }, - ExpectSpec: ResourceBindingSpec{ - Clusters: []TargetCluster{{Name: "m2", Replicas: 2}, {Name: "m3", Replicas: 3}}, - GracefulEvictionTasks: []GracefulEvictionTask{ - { - FromCluster: "m1", - PurgeMode: policyv1alpha1.Immediately, - Replicas: ptr.To[int32](1), - Reason: EvictionReasonTaintUntolerated, - Message: "graceful eviction", - Producer: EvictionProducerTaintManager, - }, - }, - }, - }, - { - Name: "remove cluster from middle", - InputSpec: ResourceBindingSpec{ - Clusters: []TargetCluster{{Name: "m1", Replicas: 1}, {Name: "m2", Replicas: 2}, {Name: "m3", Replicas: 3}}, - }, - EvictEvent: GracefulEvictionTask{ - FromCluster: "m2", - PurgeMode: policyv1alpha1.Never, - Reason: EvictionReasonTaintUntolerated, - Message: "graceful eviction", - Producer: EvictionProducerTaintManager, - }, - ExpectSpec: ResourceBindingSpec{ - Clusters: []TargetCluster{{Name: "m1", Replicas: 1}, {Name: "m3", Replicas: 3}}, - GracefulEvictionTasks: []GracefulEvictionTask{ - { - FromCluster: "m2", - PurgeMode: policyv1alpha1.Never, - Replicas: ptr.To[int32](2), - Reason: EvictionReasonTaintUntolerated, - Message: "graceful eviction", - Producer: EvictionProducerTaintManager, - }, - }, - }, - }, - { - Name: "remove cluster from tail", - InputSpec: ResourceBindingSpec{ - Clusters: []TargetCluster{{Name: "m1", Replicas: 1}, {Name: "m2", Replicas: 2}, {Name: "m3", Replicas: 3}}, - }, - EvictEvent: GracefulEvictionTask{ - FromCluster: "m3", - PurgeMode: policyv1alpha1.Graciously, - Reason: EvictionReasonTaintUntolerated, - Message: "graceful eviction", - Producer: EvictionProducerTaintManager, - }, - ExpectSpec: ResourceBindingSpec{ - Clusters: []TargetCluster{{Name: "m1", Replicas: 1}, {Name: "m2", Replicas: 2}}, - GracefulEvictionTasks: []GracefulEvictionTask{ - { - FromCluster: "m3", - PurgeMode: policyv1alpha1.Graciously, - Replicas: ptr.To[int32](3), - Reason: EvictionReasonTaintUntolerated, - Message: "graceful eviction", - Producer: EvictionProducerTaintManager, - }, - }, - }, - }, - { - Name: "eviction task should be appended to non-empty tasks", - InputSpec: ResourceBindingSpec{ - Clusters: []TargetCluster{{Name: "m1", Replicas: 1}, {Name: "m2", Replicas: 2}, {Name: "m3", Replicas: 3}}, - GracefulEvictionTasks: []GracefulEvictionTask{{FromCluster: "original-cluster"}}, - }, - EvictEvent: GracefulEvictionTask{ - FromCluster: "m3", - PurgeMode: policyv1alpha1.Graciously, - Reason: EvictionReasonTaintUntolerated, - Message: "graceful eviction", - Producer: EvictionProducerTaintManager, - }, - ExpectSpec: ResourceBindingSpec{ - Clusters: []TargetCluster{{Name: "m1", Replicas: 1}, {Name: "m2", Replicas: 2}}, - GracefulEvictionTasks: []GracefulEvictionTask{ - { - FromCluster: "original-cluster", - }, - { - FromCluster: "m3", - PurgeMode: policyv1alpha1.Graciously, - Replicas: ptr.To[int32](3), - Reason: EvictionReasonTaintUntolerated, - Message: "graceful eviction", - Producer: EvictionProducerTaintManager, - }, - }, - }, - }, - { - Name: "remove cluster from empty list", - InputSpec: ResourceBindingSpec{Clusters: []TargetCluster{}}, - ExpectSpec: ResourceBindingSpec{Clusters: []TargetCluster{}}, - }, - { - Name: "same eviction task should not be appended multiple times", - InputSpec: ResourceBindingSpec{ - Clusters: []TargetCluster{{Name: "m1", Replicas: 1}, {Name: "m2", Replicas: 2}}, - GracefulEvictionTasks: []GracefulEvictionTask{ - { - FromCluster: "m1", - Replicas: ptr.To[int32](1), - Reason: EvictionReasonTaintUntolerated, - Message: "graceful eviction v1", - Producer: EvictionProducerTaintManager, - }, - }, - }, - EvictEvent: GracefulEvictionTask{ - FromCluster: "m1", - PurgeMode: policyv1alpha1.Graciously, - Replicas: ptr.To[int32](1), - Reason: EvictionReasonTaintUntolerated, - Message: "graceful eviction v2", - Producer: EvictionProducerTaintManager, - }, - ExpectSpec: ResourceBindingSpec{ - Clusters: []TargetCluster{{Name: "m2", Replicas: 2}}, - GracefulEvictionTasks: []GracefulEvictionTask{ - { - FromCluster: "m1", - Replicas: ptr.To[int32](1), - Reason: EvictionReasonTaintUntolerated, - Message: "graceful eviction v1", - Producer: EvictionProducerTaintManager, - }, - }, - }, - }, - } - - for _, test := range tests { - tc := test - t.Run(tc.Name, func(t *testing.T) { - tc.InputSpec.GracefulEvictCluster(tc.EvictEvent.FromCluster, NewTaskOptions( - WithPurgeMode(tc.EvictEvent.PurgeMode), - WithProducer(tc.EvictEvent.Producer), - WithReason(tc.EvictEvent.Reason), - WithMessage(tc.EvictEvent.Message))) - - if !reflect.DeepEqual(tc.InputSpec.Clusters, tc.ExpectSpec.Clusters) { - t.Fatalf("expect clusters: %v, but got: %v", tc.ExpectSpec.Clusters, tc.InputSpec.Clusters) - } - if !reflect.DeepEqual(tc.InputSpec.GracefulEvictionTasks, tc.ExpectSpec.GracefulEvictionTasks) { - t.Fatalf("expect tasks: %v, but got: %v", tc.ExpectSpec.GracefulEvictionTasks, tc.InputSpec.GracefulEvictionTasks) - } - }) - } -} - -func TestResourceBindingSpec_ClusterInGracefulEvictionTasks(t *testing.T) { - gracefulEvictionTasks := []GracefulEvictionTask{ - { - FromCluster: "member1", - Producer: EvictionProducerTaintManager, - Reason: EvictionReasonTaintUntolerated, - }, - { - FromCluster: "member2", - Producer: EvictionProducerTaintManager, - Reason: EvictionReasonTaintUntolerated, - }, - } - - tests := []struct { - name string - InputSpec ResourceBindingSpec - targetCluster string - expect bool - }{ - { - name: "targetCluster is in the process of eviction", - InputSpec: ResourceBindingSpec{ - GracefulEvictionTasks: gracefulEvictionTasks, - }, - targetCluster: "member1", - expect: true, - }, - { - name: "targetCluster is not in the process of eviction", - InputSpec: ResourceBindingSpec{ - GracefulEvictionTasks: gracefulEvictionTasks, - }, - targetCluster: "member3", - expect: false, - }, - } - - for _, test := range tests { - tc := test - t.Run(tc.name, func(t *testing.T) { - result := tc.InputSpec.ClusterInGracefulEvictionTasks(tc.targetCluster) - if result != tc.expect { - t.Errorf("expected: %v, but got: %v", tc.expect, result) - } - }) - } -} diff --git a/work/v1alpha2/zz_generated.deepcopy.go b/work/v1alpha2/zz_generated.deepcopy.go index 238ee92..45932cc 100644 --- a/work/v1alpha2/zz_generated.deepcopy.go +++ b/work/v1alpha2/zz_generated.deepcopy.go @@ -362,7 +362,7 @@ func (in *ResourceBindingSpec) DeepCopyInto(out *ResourceBindingSpec) { } if in.Suspension != nil { in, out := &in.Suspension, &out.Suspension - *out = new(v1alpha1.Suspension) + *out = new(Suspension) (*in).DeepCopyInto(*out) } if in.PreserveResourcesOnDeletion != nil { @@ -370,6 +370,11 @@ func (in *ResourceBindingSpec) DeepCopyInto(out *ResourceBindingSpec) { *out = new(bool) **out = **in } + if in.SchedulePriority != nil { + in, out := &in.SchedulePriority, &out.SchedulePriority + *out = new(SchedulePriority) + **out = **in + } return } @@ -417,6 +422,44 @@ func (in *ResourceBindingStatus) DeepCopy() *ResourceBindingStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SchedulePriority) DeepCopyInto(out *SchedulePriority) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SchedulePriority. +func (in *SchedulePriority) DeepCopy() *SchedulePriority { + if in == nil { + return nil + } + out := new(SchedulePriority) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Suspension) DeepCopyInto(out *Suspension) { + *out = *in + in.Suspension.DeepCopyInto(&out.Suspension) + if in.Scheduling != nil { + in, out := &in.Scheduling, &out.Scheduling + *out = new(bool) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Suspension. +func (in *Suspension) DeepCopy() *Suspension { + if in == nil { + return nil + } + out := new(Suspension) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TargetCluster) DeepCopyInto(out *TargetCluster) { *out = *in