-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add the sameplacementaffinity plugin (filtering) (#449)
- Loading branch information
1 parent
ccd1d6f
commit 68cac9f
Showing
3 changed files
with
219 additions
and
0 deletions.
There are no files selected for viewing
29 changes: 29 additions & 0 deletions
29
pkg/scheduler/framework/plugins/sameplacementaffinity/filtering.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/* | ||
Copyright (c) Microsoft Corporation. | ||
Licensed under the MIT license. | ||
*/ | ||
|
||
package sameplacementaffinity | ||
|
||
import ( | ||
"context" | ||
|
||
fleetv1beta1 "go.goms.io/fleet/apis/placement/v1beta1" | ||
"go.goms.io/fleet/pkg/scheduler/framework" | ||
) | ||
|
||
// Filter allows the plugin to connect to the Filter extension point in the scheduling framework. | ||
func (p *Plugin) Filter( | ||
_ context.Context, | ||
state framework.CycleStatePluginReadWriter, | ||
_ *fleetv1beta1.ClusterSchedulingPolicySnapshot, | ||
cluster *fleetv1beta1.MemberCluster, | ||
) (status *framework.Status) { | ||
if !state.HasScheduledOrBoundBindingFor(cluster.Name) { | ||
// all done. | ||
return nil | ||
} | ||
|
||
reason := "resource placement has already been scheduled or bounded on the cluster" | ||
return framework.NewNonErrorStatus(framework.ClusterUnschedulable, p.Name(), reason) | ||
} |
113 changes: 113 additions & 0 deletions
113
pkg/scheduler/framework/plugins/sameplacementaffinity/filtering_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
/* | ||
Copyright (c) Microsoft Corporation. | ||
Licensed under the MIT license. | ||
*/ | ||
|
||
package sameplacementaffinity | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"github.com/google/go-cmp/cmp/cmpopts" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
|
||
fleetv1beta1 "go.goms.io/fleet/apis/placement/v1beta1" | ||
"go.goms.io/fleet/pkg/scheduler/framework" | ||
) | ||
|
||
const ( | ||
clusterName = "member-1" | ||
) | ||
|
||
var ( | ||
cmpStatusOptions = cmp.Options{ | ||
cmpopts.IgnoreFields(framework.Status{}, "reasons", "err"), | ||
cmp.AllowUnexported(framework.Status{}), | ||
} | ||
defaultPluginName = defaultPluginOptions.name | ||
) | ||
|
||
func TestFilter(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
scheduledOrBoundBindings []*fleetv1beta1.ClusterResourceBinding | ||
want *framework.Status | ||
}{ | ||
{ | ||
name: "placement has already been scheduled", | ||
scheduledOrBoundBindings: []*fleetv1beta1.ClusterResourceBinding{ | ||
{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "binding1", | ||
}, | ||
Spec: fleetv1beta1.ResourceBindingSpec{ | ||
TargetCluster: clusterName, | ||
State: fleetv1beta1.BindingStateScheduled, | ||
}, | ||
}, | ||
{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "binding2", | ||
}, | ||
Spec: fleetv1beta1.ResourceBindingSpec{ | ||
TargetCluster: "another-cluster", | ||
State: fleetv1beta1.BindingStateScheduled, | ||
}, | ||
}, | ||
}, | ||
want: framework.NewNonErrorStatus(framework.ClusterUnschedulable, defaultPluginName), | ||
}, | ||
{ | ||
name: "placement has already been bounded", | ||
scheduledOrBoundBindings: []*fleetv1beta1.ClusterResourceBinding{ | ||
{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "binding1", | ||
}, | ||
Spec: fleetv1beta1.ResourceBindingSpec{ | ||
TargetCluster: clusterName, | ||
State: fleetv1beta1.BindingStateBound, | ||
}, | ||
}, | ||
}, | ||
want: framework.NewNonErrorStatus(framework.ClusterUnschedulable, defaultPluginName), | ||
}, | ||
{ | ||
name: "no bindings", | ||
scheduledOrBoundBindings: []*fleetv1beta1.ClusterResourceBinding{}, | ||
want: nil, | ||
}, | ||
{ | ||
name: "placement has been bounded/scheduled on other cluster", | ||
scheduledOrBoundBindings: []*fleetv1beta1.ClusterResourceBinding{ | ||
{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "binding1", | ||
}, | ||
Spec: fleetv1beta1.ResourceBindingSpec{ | ||
TargetCluster: "another-cluster", | ||
State: fleetv1beta1.BindingStateBound, | ||
}, | ||
}, | ||
}, | ||
want: nil, | ||
}, | ||
} | ||
for _, tc := range tests { | ||
t.Run(tc.name, func(t *testing.T) { | ||
p := New() | ||
state := framework.NewCycleState(nil, nil, tc.scheduledOrBoundBindings) | ||
cluster := fleetv1beta1.MemberCluster{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: clusterName, | ||
}, | ||
} | ||
got := p.Filter(context.Background(), state, nil, &cluster) | ||
if diff := cmp.Diff(tc.want, got, cmpStatusOptions); diff != "" { | ||
t.Errorf("Filter() status mismatch (-want, +got):\n%s", diff) | ||
} | ||
}) | ||
} | ||
} |
77 changes: 77 additions & 0 deletions
77
pkg/scheduler/framework/plugins/sameplacementaffinity/plugin.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
/* | ||
Copyright (c) Microsoft Corporation. | ||
Licensed under the MIT license. | ||
*/ | ||
|
||
// Package sameplacementaffinity features a scheduler plugin that filters out any cluster that has been already scheduled/bounded | ||
// to the resource placement and prefers the same cluster which has an obsolete binding. | ||
package sameplacementaffinity | ||
|
||
import "go.goms.io/fleet/pkg/scheduler/framework" | ||
|
||
// Plugin is the scheduler plugin that enforces the same placement affinity and anti-affinity. | ||
// "Affinity" means a scheduler prefers the cluster which has an obsolete binding of the same placement in order to | ||
// minimize interruption between different scheduling runs. | ||
// "Anti-Affinity" means a scheduler filters out the cluster which has been already scheduled/bounded as two placements | ||
// cannot be scheduled on a cluster. | ||
type Plugin struct { | ||
// The name of the plugin. | ||
name string | ||
|
||
// The framework handle. | ||
handle framework.Handle | ||
} | ||
|
||
var ( | ||
// Verify that Plugin can connect to relevant extension points at compile time. | ||
// | ||
// This plugin leverages the following the extension points: | ||
// * Filter | ||
// * Score | ||
// | ||
// Note that successful connection to any of the extension points implies that the | ||
// plugin already implements the Plugin interface. | ||
_ framework.FilterPlugin = &Plugin{} | ||
// TODO | ||
// _ framework.ScorePlugin = &Plugin{} | ||
) | ||
|
||
type samePlacementAntiAffinityPluginOptions struct { | ||
// The name of the plugin. | ||
name string | ||
} | ||
|
||
type Option func(*samePlacementAntiAffinityPluginOptions) | ||
|
||
var defaultPluginOptions = samePlacementAntiAffinityPluginOptions{ | ||
name: "SamePlacementAntiAffinity", | ||
} | ||
|
||
// WithName sets the name of the plugin. | ||
func WithName(name string) Option { | ||
return func(o *samePlacementAntiAffinityPluginOptions) { | ||
o.name = name | ||
} | ||
} | ||
|
||
// New returns a new Plugin. | ||
func New(opts ...Option) Plugin { | ||
options := defaultPluginOptions | ||
for _, opt := range opts { | ||
opt(&options) | ||
} | ||
|
||
return Plugin{ | ||
name: options.name, | ||
} | ||
} | ||
|
||
// Name returns the name of the plugin. | ||
func (p *Plugin) Name() string { | ||
return p.name | ||
} | ||
|
||
// SetUpWithFramework sets up this plugin with a scheduler framework. | ||
func (p *Plugin) SetUpWithFramework(handle framework.Handle) { | ||
p.handle = handle | ||
} |