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

feat: cluster-level resource scheduling suspend and resume capabilities #5937

Merged
merged 2 commits into from
Jan 2, 2025
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
6 changes: 5 additions & 1 deletion api/openapi-spec/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -20435,7 +20435,7 @@
}
},
"com.github.karmada-io.karmada.pkg.apis.work.v1alpha2.Suspension": {
"description": "Suspension defines the policy for suspending of propagation.",
"description": "Suspension defines the policy for suspending dispatching and scheduling.",
"type": "object",
"properties": {
"dispatching": {
Expand All @@ -20445,6 +20445,10 @@
"dispatchingOnClusters": {
"description": "DispatchingOnClusters declares a list of clusters to which the dispatching should be suspended. Note: Can not co-exist with Dispatching which is used to suspend all.",
"$ref": "#/definitions/com.github.karmada-io.karmada.pkg.apis.policy.v1alpha1.SuspendClusters"
},
"scheduling": {
"description": "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.",
"type": "boolean"
}
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1281,6 +1281,16 @@ spec:
type: string
type: array
type: object
scheduling:
description: |-
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.
type: boolean
type: object
required:
- resource
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1281,6 +1281,16 @@ spec:
type: string
type: array
type: object
scheduling:
description: |-
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.
type: boolean
type: object
required:
- resource
Expand Down
12 changes: 11 additions & 1 deletion pkg/apis/work/v1alpha2/binding_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,9 +322,19 @@ type BindingSnapshot struct {
Clusters []TargetCluster `json:"clusters,omitempty"`
}

// Suspension defines the policy for suspending of propagation.
// 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"`
}

// ResourceBindingStatus represents the overall status of the strategy as well as the referenced resources.
Expand Down
5 changes: 5 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.

9 changes: 8 additions & 1 deletion pkg/generated/openapi/zz_generated.openapi.go

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

6 changes: 6 additions & 0 deletions pkg/scheduler/event_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,16 @@ func (s *Scheduler) resourceBindingEventFilter(obj interface{}) bool {
if !schedulerNameFilter(s.schedulerName, t.Spec.SchedulerName) {
return false
}
if util.IsBindingSuspendScheduling(t) {
return false
}
case *workv1alpha2.ClusterResourceBinding:
if !schedulerNameFilter(s.schedulerName, t.Spec.SchedulerName) {
return false
}
if util.IsClusterBindingSuspendScheduling(t) {
return false
}
}

return util.GetLabelValue(accessor.GetLabels(), policyv1alpha1.PropagationPolicyPermanentIDLabel) != "" ||
Expand Down
41 changes: 30 additions & 11 deletions pkg/scheduler/event_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/cache"
"k8s.io/utils/ptr"

clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
Expand All @@ -40,65 +41,65 @@ func TestResourceBindingEventFilter(t *testing.T) {
{
name: "ResourceBinding: Matching scheduler name, no labels",
schedulerName: "test-scheduler",
obj: createResourceBinding("test-rb", "test-scheduler", nil),
obj: createResourceBinding("test-rb", "test-scheduler", nil, nil),
expectedResult: false,
},
{
name: "ResourceBinding: Non-matching scheduler name",
schedulerName: "test-scheduler",
obj: createResourceBinding("test-rb", "other-scheduler", nil),
obj: createResourceBinding("test-rb", "other-scheduler", nil, nil),
expectedResult: false,
},
{
name: "ResourceBinding: Matching scheduler name, with PropagationPolicyPermanentIDLabel",
schedulerName: "test-scheduler",
obj: createResourceBinding("test-rb", "test-scheduler", map[string]string{
policyv1alpha1.PropagationPolicyPermanentIDLabel: "test-id",
}),
}, nil),
expectedResult: true,
},
{
name: "ResourceBinding: Matching scheduler name, with ClusterPropagationPolicyPermanentIDLabel",
schedulerName: "test-scheduler",
obj: createResourceBinding("test-rb", "test-scheduler", map[string]string{
policyv1alpha1.ClusterPropagationPolicyPermanentIDLabel: "test-id",
}),
}, nil),
expectedResult: true,
},
{
name: "ResourceBinding: Matching scheduler name, with BindingManagedByLabel",
schedulerName: "test-scheduler",
obj: createResourceBinding("test-rb", "test-scheduler", map[string]string{
workv1alpha2.BindingManagedByLabel: "test-manager",
}),
}, nil),
expectedResult: true,
},
{
name: "ResourceBinding: Matching scheduler name, with empty PropagationPolicyPermanentIDLabel",
schedulerName: "test-scheduler",
obj: createResourceBinding("test-rb", "test-scheduler", map[string]string{
policyv1alpha1.PropagationPolicyPermanentIDLabel: "",
}),
}, nil),
expectedResult: false,
},
{
name: "ClusterResourceBinding: Matching scheduler name, no labels",
schedulerName: "test-scheduler",
obj: createClusterResourceBinding("test-crb", "test-scheduler", nil),
obj: createClusterResourceBinding("test-crb", "test-scheduler", nil, nil),
expectedResult: false,
},
{
name: "ClusterResourceBinding: Non-matching scheduler name",
schedulerName: "test-scheduler",
obj: createClusterResourceBinding("test-crb", "other-scheduler", nil),
obj: createClusterResourceBinding("test-crb", "other-scheduler", nil, nil),
expectedResult: false,
},
{
name: "ClusterResourceBinding: Matching scheduler name, with ClusterPropagationPolicyPermanentIDLabel",
schedulerName: "test-scheduler",
obj: createClusterResourceBinding("test-crb", "test-scheduler", map[string]string{
policyv1alpha1.ClusterPropagationPolicyPermanentIDLabel: "test-id",
}),
}, nil),
expectedResult: true,
},
{
Expand All @@ -113,6 +114,22 @@ func TestResourceBindingEventFilter(t *testing.T) {
obj: "not-a-valid-object",
expectedResult: false,
},
{
name: "ResourceBinding suspended",
schedulerName: "test-scheduler",
obj: createResourceBinding("test-rb", "test-scheduler", map[string]string{
workv1alpha2.BindingManagedByLabel: "test-manager",
}, &workv1alpha2.Suspension{Scheduling: ptr.To(true)}),
expectedResult: false,
},
{
name: "ClusterResourceBinding suspended",
schedulerName: "test-scheduler",
obj: createClusterResourceBinding("test-crb", "test-scheduler", map[string]string{
policyv1alpha1.ClusterPropagationPolicyPermanentIDLabel: "test-id",
}, &workv1alpha2.Suspension{Scheduling: ptr.To(true)}),
expectedResult: false,
},
}

for _, tc := range testCases {
Expand Down Expand Up @@ -404,26 +421,28 @@ func createCluster(name string, generation int64, labels map[string]string) *clu
}
}

func createResourceBinding(name, schedulerName string, labels map[string]string) *workv1alpha2.ResourceBinding {
func createResourceBinding(name, schedulerName string, labels map[string]string, suspension *workv1alpha2.Suspension) *workv1alpha2.ResourceBinding {
return &workv1alpha2.ResourceBinding{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: labels,
},
Spec: workv1alpha2.ResourceBindingSpec{
SchedulerName: schedulerName,
Suspension: suspension,
},
}
}

func createClusterResourceBinding(name, schedulerName string, labels map[string]string) *workv1alpha2.ClusterResourceBinding {
func createClusterResourceBinding(name, schedulerName string, labels map[string]string, suspension *workv1alpha2.Suspension) *workv1alpha2.ClusterResourceBinding {
return &workv1alpha2.ClusterResourceBinding{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: labels,
},
Spec: workv1alpha2.ResourceBindingSpec{
SchedulerName: schedulerName,
Suspension: suspension,
},
}
}
Expand Down
16 changes: 16 additions & 0 deletions pkg/util/binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,19 @@ func RescheduleRequired(rescheduleTriggeredAt, lastScheduledTime *metav1.Time) b
}
return rescheduleTriggeredAt.After(lastScheduledTime.Time)
}

// IsBindingSuspendScheduling tells whether resource binding is scheduling suspended.
func IsBindingSuspendScheduling(rb *workv1alpha2.ResourceBinding) bool {
if rb == nil || rb.Spec.Suspension == nil || rb.Spec.Suspension.Scheduling == nil {
return false
}
return *rb.Spec.Suspension.Scheduling
}

// IsClusterBindingSuspendScheduling tells whether cluster resource binding is scheduling suspended.
func IsClusterBindingSuspendScheduling(crb *workv1alpha2.ClusterResourceBinding) bool {
if crb == nil || crb.Spec.Suspension == nil || crb.Spec.Suspension.Scheduling == nil {
return false
}
return *crb.Spec.Suspension.Scheduling
}
Loading