Skip to content

Commit

Permalink
feat: add the sameplacementaffinity plugin (filtering) (#449)
Browse files Browse the repository at this point in the history
  • Loading branch information
zhiying-lin authored Jul 28, 2023
1 parent ccd1d6f commit 68cac9f
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 0 deletions.
29 changes: 29 additions & 0 deletions pkg/scheduler/framework/plugins/sameplacementaffinity/filtering.go
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)
}
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 pkg/scheduler/framework/plugins/sameplacementaffinity/plugin.go
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
}

0 comments on commit 68cac9f

Please sign in to comment.