From 6b2a6c7497baffb79802250675d87b9abbb1e3ea Mon Sep 17 00:00:00 2001 From: Logan Ballard Date: Fri, 26 Sep 2025 12:26:34 -0400 Subject: [PATCH 1/7] cleaned out dir, removed the GH actions to prevent unexpected behavior, reran all codegen --- .github/workflows/helm.yaml | 54 +++++++++---------- .../versioned/fake/clientset_generated.go | 13 ++++- .../scheduling/v1alpha1/scheduling_client.go | 12 ++--- .../scheduling/v1alpha1/elasticquota.go | 16 +++++- .../scheduling/v1alpha1/podgroup.go | 16 +++++- 5 files changed, 69 insertions(+), 42 deletions(-) diff --git a/.github/workflows/helm.yaml b/.github/workflows/helm.yaml index 819415be63..343b81268a 100644 --- a/.github/workflows/helm.yaml +++ b/.github/workflows/helm.yaml @@ -1,28 +1,28 @@ -name: Releases -on: - push: - tags: - - v* +# name: Releases +# on: +# push: +# tags: +# - v* -jobs: - releases: - runs-on: ubuntu-latest - permissions: - contents: write - if: github.event_name != 'pull_request' - steps: - - uses: actions/checkout@v4 - - uses: azure/setup-helm@v3 - with: - version: 'latest' - token: ${{ secrets.GITHUB_TOKEN }} - id: install - - name: Package Helm Chart - if: ${{ github.repository == 'kubernetes-sigs/scheduler-plugins' }} - run: | - helm package ./manifests/install/charts/as-a-second-scheduler - - name: Upload to Release - uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') - with: - files: "*.tgz" +# jobs: +# releases: +# runs-on: ubuntu-latest +# permissions: +# contents: write +# if: github.event_name != 'pull_request' +# steps: +# - uses: actions/checkout@v4 +# - uses: azure/setup-helm@v3 +# with: +# version: 'latest' +# token: ${{ secrets.GITHUB_TOKEN }} +# id: install +# - name: Package Helm Chart +# if: ${{ github.repository == 'kubernetes-sigs/scheduler-plugins' }} +# run: | +# helm package ./manifests/install/charts/as-a-second-scheduler +# - name: Upload to Release +# uses: softprops/action-gh-release@v1 +# if: startsWith(github.ref, 'refs/tags/') +# with: +# files: "*.tgz" diff --git a/pkg/generated/clientset/versioned/fake/clientset_generated.go b/pkg/generated/clientset/versioned/fake/clientset_generated.go index 7f1d311a07..285b787ef2 100644 --- a/pkg/generated/clientset/versioned/fake/clientset_generated.go +++ b/pkg/generated/clientset/versioned/fake/clientset_generated.go @@ -19,6 +19,7 @@ limitations under the License. package fake import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/discovery" @@ -50,9 +51,13 @@ func NewSimpleClientset(objects ...runtime.Object) *Clientset { cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} cs.AddReactor("*", "*", testing.ObjectReaction(o)) cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { + var opts metav1.ListOptions + if watchActcion, ok := action.(testing.WatchActionImpl); ok { + opts = watchActcion.ListOptions + } gvr := action.GetResource() ns := action.GetNamespace() - watch, err := o.Watch(gvr, ns) + watch, err := o.Watch(gvr, ns, opts) if err != nil { return false, nil, err } @@ -99,9 +104,13 @@ func NewClientset(objects ...runtime.Object) *Clientset { cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} cs.AddReactor("*", "*", testing.ObjectReaction(o)) cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { + var opts metav1.ListOptions + if watchActcion, ok := action.(testing.WatchActionImpl); ok { + opts = watchActcion.ListOptions + } gvr := action.GetResource() ns := action.GetNamespace() - watch, err := o.Watch(gvr, ns) + watch, err := o.Watch(gvr, ns, opts) if err != nil { return false, nil, err } diff --git a/pkg/generated/clientset/versioned/typed/scheduling/v1alpha1/scheduling_client.go b/pkg/generated/clientset/versioned/typed/scheduling/v1alpha1/scheduling_client.go index 82124a4e01..1344c8c1f8 100644 --- a/pkg/generated/clientset/versioned/typed/scheduling/v1alpha1/scheduling_client.go +++ b/pkg/generated/clientset/versioned/typed/scheduling/v1alpha1/scheduling_client.go @@ -50,9 +50,7 @@ func (c *SchedulingV1alpha1Client) PodGroups(namespace string) PodGroupInterface // where httpClient was generated with rest.HTTPClientFor(c). func NewForConfig(c *rest.Config) (*SchedulingV1alpha1Client, error) { config := *c - if err := setConfigDefaults(&config); err != nil { - return nil, err - } + setConfigDefaults(&config) httpClient, err := rest.HTTPClientFor(&config) if err != nil { return nil, err @@ -64,9 +62,7 @@ func NewForConfig(c *rest.Config) (*SchedulingV1alpha1Client, error) { // Note the http client provided takes precedence over the configured transport values. func NewForConfigAndClient(c *rest.Config, h *http.Client) (*SchedulingV1alpha1Client, error) { config := *c - if err := setConfigDefaults(&config); err != nil { - return nil, err - } + setConfigDefaults(&config) client, err := rest.RESTClientForConfigAndClient(&config, h) if err != nil { return nil, err @@ -89,7 +85,7 @@ func New(c rest.Interface) *SchedulingV1alpha1Client { return &SchedulingV1alpha1Client{c} } -func setConfigDefaults(config *rest.Config) error { +func setConfigDefaults(config *rest.Config) { gv := schedulingv1alpha1.SchemeGroupVersion config.GroupVersion = &gv config.APIPath = "/apis" @@ -98,8 +94,6 @@ func setConfigDefaults(config *rest.Config) error { if config.UserAgent == "" { config.UserAgent = rest.DefaultKubernetesUserAgent() } - - return nil } // RESTClient returns a RESTClient that is used to communicate diff --git a/pkg/generated/informers/externalversions/scheduling/v1alpha1/elasticquota.go b/pkg/generated/informers/externalversions/scheduling/v1alpha1/elasticquota.go index 6f29935e3e..a6510bba01 100644 --- a/pkg/generated/informers/externalversions/scheduling/v1alpha1/elasticquota.go +++ b/pkg/generated/informers/externalversions/scheduling/v1alpha1/elasticquota.go @@ -62,13 +62,25 @@ func NewFilteredElasticQuotaInformer(client versioned.Interface, namespace strin if tweakListOptions != nil { tweakListOptions(&options) } - return client.SchedulingV1alpha1().ElasticQuotas(namespace).List(context.TODO(), options) + return client.SchedulingV1alpha1().ElasticQuotas(namespace).List(context.Background(), options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { tweakListOptions(&options) } - return client.SchedulingV1alpha1().ElasticQuotas(namespace).Watch(context.TODO(), options) + return client.SchedulingV1alpha1().ElasticQuotas(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.SchedulingV1alpha1().ElasticQuotas(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.SchedulingV1alpha1().ElasticQuotas(namespace).Watch(ctx, options) }, }, &apisschedulingv1alpha1.ElasticQuota{}, diff --git a/pkg/generated/informers/externalversions/scheduling/v1alpha1/podgroup.go b/pkg/generated/informers/externalversions/scheduling/v1alpha1/podgroup.go index 3284174acd..01848977f6 100644 --- a/pkg/generated/informers/externalversions/scheduling/v1alpha1/podgroup.go +++ b/pkg/generated/informers/externalversions/scheduling/v1alpha1/podgroup.go @@ -62,13 +62,25 @@ func NewFilteredPodGroupInformer(client versioned.Interface, namespace string, r if tweakListOptions != nil { tweakListOptions(&options) } - return client.SchedulingV1alpha1().PodGroups(namespace).List(context.TODO(), options) + return client.SchedulingV1alpha1().PodGroups(namespace).List(context.Background(), options) }, WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { tweakListOptions(&options) } - return client.SchedulingV1alpha1().PodGroups(namespace).Watch(context.TODO(), options) + return client.SchedulingV1alpha1().PodGroups(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.SchedulingV1alpha1().PodGroups(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.SchedulingV1alpha1().PodGroups(namespace).Watch(ctx, options) }, }, &apisschedulingv1alpha1.PodGroup{}, From 12d152a742e1fed50f310bb2c50b73dc6e81c955 Mon Sep 17 00:00:00 2001 From: Logan Ballard Date: Fri, 26 Sep 2025 12:45:19 -0400 Subject: [PATCH 2/7] adding scaffolding, reruning code gen, commenting out all the other plugins that aren't used --- apis/config/register.go | 1 + apis/config/types.go | 12 ++++++ apis/config/v1/defaults.go | 13 ++++++ apis/config/v1/types.go | 13 ++++++ apis/config/v1/zz_generated.conversion.go | 32 +++++++++++++++ apis/config/v1/zz_generated.deepcopy.go | 25 ++++++++++++ apis/config/v1/zz_generated.defaults.go | 5 +++ apis/config/zz_generated.deepcopy.go | 25 ++++++++++++ cmd/scheduler/main.go | 50 +++++++++-------------- pkg/loosebinpack/README.md | 0 pkg/loosebinpack/loosebinpack.go | 37 +++++++++++++++++ 11 files changed, 182 insertions(+), 31 deletions(-) create mode 100644 pkg/loosebinpack/README.md create mode 100644 pkg/loosebinpack/loosebinpack.go diff --git a/apis/config/register.go b/apis/config/register.go index 57a33a0cc1..d5a328b0a2 100644 --- a/apis/config/register.go +++ b/apis/config/register.go @@ -45,6 +45,7 @@ func addKnownTypes(scheme *runtime.Scheme) error { &NetworkOverheadArgs{}, &SySchedArgs{}, &PeaksArgs{}, + &LooseBinPackArgs{}, ) return nil } diff --git a/apis/config/types.go b/apis/config/types.go index d249608336..a847bd292c 100644 --- a/apis/config/types.go +++ b/apis/config/types.go @@ -298,3 +298,15 @@ type PowerModel struct { // Power = K0 + K1 * e ^(K2 * x) : where x is utilisation // Idle power of node will be K0 + K1 } + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// LooseBinPackArgs holds arguments used to configure LooseBinPack plugin. +type LooseBinPackArgs struct { + metav1.TypeMeta `json:",inline"` + + // CPU Utilization Threshold in percentage (0-100) + CpuThresholdPercent int64 `json:"cpuThresholdPercent,omitempty"` + // Memory Utilization Threshold in percentage (0-100) + MemoryThresholdPercent int64 `json:"memoryThresholdPercent,omitempty"` +} diff --git a/apis/config/v1/defaults.go b/apis/config/v1/defaults.go index 245f2e6aed..6a51b02406 100644 --- a/apis/config/v1/defaults.go +++ b/apis/config/v1/defaults.go @@ -250,3 +250,16 @@ func SetDefaults_SySchedArgs(obj *SySchedArgs) { obj.DefaultProfileName = &DefaultSySchedProfileName } } + +// SetDefaults_LooseBinPackArgs sets the default parameters for the LooseBinPack plugin +func SetDefaults_LooseBinPackArgs(obj *LooseBinPackArgs) { + if obj.CpuThresholdPercent == 0 { + defaultCpuThresholdPercent := int64(85) + obj.CpuThresholdPercent = defaultCpuThresholdPercent + } + + if obj.MemoryThresholdPercent == 0 { + defaultMemoryThresholdPercent := int64(85) + obj.MemoryThresholdPercent = defaultMemoryThresholdPercent + } +} diff --git a/apis/config/v1/types.go b/apis/config/v1/types.go index 569491ab2e..524b061c8d 100644 --- a/apis/config/v1/types.go +++ b/apis/config/v1/types.go @@ -297,3 +297,16 @@ type PowerModel struct { // Power = K0 + K1 * e ^(K2 * x) : where x is utilisation // Idle power of node will be K0 + K1 } + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:defaulter-gen=true + +// LooseBinPackArgs holds arguments used to configure LooseBinPack plugin. +type LooseBinPackArgs struct { + metav1.TypeMeta `json:",inline"` + + // CPU Utilization Threshold in percentage (0-100) + CpuThresholdPercent int64 `json:"cpuThresholdPercent,omitempty"` + // Memory Utilization Threshold in percentage (0-100) + MemoryThresholdPercent int64 `json:"memoryThresholdPercent,omitempty"` +} diff --git a/apis/config/v1/zz_generated.conversion.go b/apis/config/v1/zz_generated.conversion.go index 2d2acf591f..9ededb62f6 100644 --- a/apis/config/v1/zz_generated.conversion.go +++ b/apis/config/v1/zz_generated.conversion.go @@ -60,6 +60,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*LooseBinPackArgs)(nil), (*config.LooseBinPackArgs)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_LooseBinPackArgs_To_config_LooseBinPackArgs(a.(*LooseBinPackArgs), b.(*config.LooseBinPackArgs), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*config.LooseBinPackArgs)(nil), (*LooseBinPackArgs)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_config_LooseBinPackArgs_To_v1_LooseBinPackArgs(a.(*config.LooseBinPackArgs), b.(*LooseBinPackArgs), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*LowRiskOverCommitmentArgs)(nil), (*config.LowRiskOverCommitmentArgs)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_LowRiskOverCommitmentArgs_To_config_LowRiskOverCommitmentArgs(a.(*LowRiskOverCommitmentArgs), b.(*config.LowRiskOverCommitmentArgs), scope) }); err != nil { @@ -269,6 +279,28 @@ func Convert_config_LoadVariationRiskBalancingArgs_To_v1_LoadVariationRiskBalanc return autoConvert_config_LoadVariationRiskBalancingArgs_To_v1_LoadVariationRiskBalancingArgs(in, out, s) } +func autoConvert_v1_LooseBinPackArgs_To_config_LooseBinPackArgs(in *LooseBinPackArgs, out *config.LooseBinPackArgs, s conversion.Scope) error { + out.CpuThresholdPercent = in.CpuThresholdPercent + out.MemoryThresholdPercent = in.MemoryThresholdPercent + return nil +} + +// Convert_v1_LooseBinPackArgs_To_config_LooseBinPackArgs is an autogenerated conversion function. +func Convert_v1_LooseBinPackArgs_To_config_LooseBinPackArgs(in *LooseBinPackArgs, out *config.LooseBinPackArgs, s conversion.Scope) error { + return autoConvert_v1_LooseBinPackArgs_To_config_LooseBinPackArgs(in, out, s) +} + +func autoConvert_config_LooseBinPackArgs_To_v1_LooseBinPackArgs(in *config.LooseBinPackArgs, out *LooseBinPackArgs, s conversion.Scope) error { + out.CpuThresholdPercent = in.CpuThresholdPercent + out.MemoryThresholdPercent = in.MemoryThresholdPercent + return nil +} + +// Convert_config_LooseBinPackArgs_To_v1_LooseBinPackArgs is an autogenerated conversion function. +func Convert_config_LooseBinPackArgs_To_v1_LooseBinPackArgs(in *config.LooseBinPackArgs, out *LooseBinPackArgs, s conversion.Scope) error { + return autoConvert_config_LooseBinPackArgs_To_v1_LooseBinPackArgs(in, out, s) +} + func autoConvert_v1_LowRiskOverCommitmentArgs_To_config_LowRiskOverCommitmentArgs(in *LowRiskOverCommitmentArgs, out *config.LowRiskOverCommitmentArgs, s conversion.Scope) error { if err := Convert_v1_TrimaranSpec_To_config_TrimaranSpec(&in.TrimaranSpec, &out.TrimaranSpec, s); err != nil { return err diff --git a/apis/config/v1/zz_generated.deepcopy.go b/apis/config/v1/zz_generated.deepcopy.go index e13aba3cee..e0b6b84bce 100644 --- a/apis/config/v1/zz_generated.deepcopy.go +++ b/apis/config/v1/zz_generated.deepcopy.go @@ -98,6 +98,31 @@ func (in *LoadVariationRiskBalancingArgs) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LooseBinPackArgs) DeepCopyInto(out *LooseBinPackArgs) { + *out = *in + out.TypeMeta = in.TypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LooseBinPackArgs. +func (in *LooseBinPackArgs) DeepCopy() *LooseBinPackArgs { + if in == nil { + return nil + } + out := new(LooseBinPackArgs) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LooseBinPackArgs) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LowRiskOverCommitmentArgs) DeepCopyInto(out *LowRiskOverCommitmentArgs) { *out = *in diff --git a/apis/config/v1/zz_generated.defaults.go b/apis/config/v1/zz_generated.defaults.go index b13649bd9e..15f61db5b7 100644 --- a/apis/config/v1/zz_generated.defaults.go +++ b/apis/config/v1/zz_generated.defaults.go @@ -33,6 +33,7 @@ func RegisterDefaults(scheme *runtime.Scheme) error { scheme.AddTypeDefaultingFunc(&LoadVariationRiskBalancingArgs{}, func(obj interface{}) { SetObjectDefaults_LoadVariationRiskBalancingArgs(obj.(*LoadVariationRiskBalancingArgs)) }) + scheme.AddTypeDefaultingFunc(&LooseBinPackArgs{}, func(obj interface{}) { SetObjectDefaults_LooseBinPackArgs(obj.(*LooseBinPackArgs)) }) scheme.AddTypeDefaultingFunc(&LowRiskOverCommitmentArgs{}, func(obj interface{}) { SetObjectDefaults_LowRiskOverCommitmentArgs(obj.(*LowRiskOverCommitmentArgs)) }) scheme.AddTypeDefaultingFunc(&NetworkOverheadArgs{}, func(obj interface{}) { SetObjectDefaults_NetworkOverheadArgs(obj.(*NetworkOverheadArgs)) }) scheme.AddTypeDefaultingFunc(&NodeResourceTopologyMatchArgs{}, func(obj interface{}) { @@ -56,6 +57,10 @@ func SetObjectDefaults_LoadVariationRiskBalancingArgs(in *LoadVariationRiskBalan SetDefaults_LoadVariationRiskBalancingArgs(in) } +func SetObjectDefaults_LooseBinPackArgs(in *LooseBinPackArgs) { + SetDefaults_LooseBinPackArgs(in) +} + func SetObjectDefaults_LowRiskOverCommitmentArgs(in *LowRiskOverCommitmentArgs) { SetDefaults_LowRiskOverCommitmentArgs(in) } diff --git a/apis/config/zz_generated.deepcopy.go b/apis/config/zz_generated.deepcopy.go index 393afe2be1..460e8bd403 100644 --- a/apis/config/zz_generated.deepcopy.go +++ b/apis/config/zz_generated.deepcopy.go @@ -78,6 +78,31 @@ func (in *LoadVariationRiskBalancingArgs) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LooseBinPackArgs) DeepCopyInto(out *LooseBinPackArgs) { + *out = *in + out.TypeMeta = in.TypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LooseBinPackArgs. +func (in *LooseBinPackArgs) DeepCopy() *LooseBinPackArgs { + if in == nil { + return nil + } + out := new(LooseBinPackArgs) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LooseBinPackArgs) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LowRiskOverCommitmentArgs) DeepCopyInto(out *LowRiskOverCommitmentArgs) { *out = *in diff --git a/cmd/scheduler/main.go b/cmd/scheduler/main.go index 7c915be2cd..8753ac5f1a 100644 --- a/cmd/scheduler/main.go +++ b/cmd/scheduler/main.go @@ -24,23 +24,9 @@ import ( _ "k8s.io/component-base/metrics/prometheus/version" // for version metric registration "k8s.io/kubernetes/cmd/kube-scheduler/app" - "sigs.k8s.io/scheduler-plugins/pkg/capacityscheduling" - "sigs.k8s.io/scheduler-plugins/pkg/coscheduling" - "sigs.k8s.io/scheduler-plugins/pkg/networkaware/networkoverhead" - "sigs.k8s.io/scheduler-plugins/pkg/networkaware/topologicalsort" - "sigs.k8s.io/scheduler-plugins/pkg/noderesources" - "sigs.k8s.io/scheduler-plugins/pkg/noderesourcetopology" - "sigs.k8s.io/scheduler-plugins/pkg/podstate" - "sigs.k8s.io/scheduler-plugins/pkg/preemptiontoleration" - "sigs.k8s.io/scheduler-plugins/pkg/qos" - "sigs.k8s.io/scheduler-plugins/pkg/sysched" - "sigs.k8s.io/scheduler-plugins/pkg/trimaran/loadvariationriskbalancing" - "sigs.k8s.io/scheduler-plugins/pkg/trimaran/lowriskovercommitment" - "sigs.k8s.io/scheduler-plugins/pkg/trimaran/peaks" - "sigs.k8s.io/scheduler-plugins/pkg/trimaran/targetloadpacking" - // Ensure scheme package is initialized. _ "sigs.k8s.io/scheduler-plugins/apis/config/scheme" + "sigs.k8s.io/scheduler-plugins/pkg/loosebinpack" ) func main() { @@ -48,22 +34,24 @@ func main() { // Later they can consist of scheduler profile(s) and hence // used by various kinds of workloads. command := app.NewSchedulerCommand( - app.WithPlugin(capacityscheduling.Name, capacityscheduling.New), - app.WithPlugin(coscheduling.Name, coscheduling.New), - app.WithPlugin(loadvariationriskbalancing.Name, loadvariationriskbalancing.New), - app.WithPlugin(networkoverhead.Name, networkoverhead.New), - app.WithPlugin(topologicalsort.Name, topologicalsort.New), - app.WithPlugin(noderesources.AllocatableName, noderesources.NewAllocatable), - app.WithPlugin(noderesourcetopology.Name, noderesourcetopology.New), - app.WithPlugin(preemptiontoleration.Name, preemptiontoleration.New), - app.WithPlugin(targetloadpacking.Name, targetloadpacking.New), - app.WithPlugin(lowriskovercommitment.Name, lowriskovercommitment.New), - app.WithPlugin(sysched.Name, sysched.New), - app.WithPlugin(peaks.Name, peaks.New), - // Sample plugins below. - // app.WithPlugin(crossnodepreemption.Name, crossnodepreemption.New), - app.WithPlugin(podstate.Name, podstate.New), - app.WithPlugin(qos.Name, qos.New), + // app.WithPlugin(capacityscheduling.Name, capacityscheduling.New), + // app.WithPlugin(coscheduling.Name, coscheduling.New), + // app.WithPlugin(loadvariationriskbalancing.Name, loadvariationriskbalancing.New), + // app.WithPlugin(networkoverhead.Name, networkoverhead.New), + // app.WithPlugin(topologicalsort.Name, topologicalsort.New), + // app.WithPlugin(noderesources.AllocatableName, noderesources.NewAllocatable), + // app.WithPlugin(noderesourcetopology.Name, noderesourcetopology.New), + // app.WithPlugin(preemptiontoleration.Name, preemptiontoleration.New), + // app.WithPlugin(targetloadpacking.Name, targetloadpacking.New), + // app.WithPlugin(lowriskovercommitment.Name, lowriskovercommitment.New), + // app.WithPlugin(sysched.Name, sysched.New), + // app.WithPlugin(peaks.Name, peaks.New), + // // Sample plugins below. + // // app.WithPlugin(crossnodepreemption.Name, crossnodepreemption.New), + // app.WithPlugin(podstate.Name, podstate.New), + // app.WithPlugin(qos.Name, qos.New), + + app.WithPlugin(loosebinpack.Name, loosebinpack.New), ) code := cli.Run(command) diff --git a/pkg/loosebinpack/README.md b/pkg/loosebinpack/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pkg/loosebinpack/loosebinpack.go b/pkg/loosebinpack/loosebinpack.go new file mode 100644 index 0000000000..7e9719eb57 --- /dev/null +++ b/pkg/loosebinpack/loosebinpack.go @@ -0,0 +1,37 @@ +package loosebinpack + +import ( + "context" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/kubernetes/pkg/scheduler/framework" +) + +type LooseBinPack struct { + // TODO: Add fields here +} + +const Name = "LooseBinPack" + +var _ = framework.ScorePlugin(&LooseBinPack{}) + +func New(ctx context.Context, obj runtime.Object, h framework.Handle) (framework.Plugin, error) { + return &LooseBinPack{}, nil +} + +func (lbp *LooseBinPack) Name() string { + return Name +} + +func (lbp *LooseBinPack) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) (int64, *framework.Status) { + return 0, nil +} + +func (lbp *LooseBinPack) ScoreExtensions() framework.ScoreExtensions { + return nil +} + +func (lbp *LooseBinPack) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status { + return nil +} From b99187e968ba0a31adb29ec2a947229fe9f3db00 Mon Sep 17 00:00:00 2001 From: Logan Ballard Date: Fri, 26 Sep 2025 13:35:27 -0400 Subject: [PATCH 3/7] working unit tests, need to add ability to see if scheduling the pod under consideration would put the node over utilization --- pkg/loosebinpack/loosebinpack.go | 63 ++++++++++--- pkg/loosebinpack/loosebinpack_test.go | 128 ++++++++++++++++++++++++++ 2 files changed, 180 insertions(+), 11 deletions(-) create mode 100644 pkg/loosebinpack/loosebinpack_test.go diff --git a/pkg/loosebinpack/loosebinpack.go b/pkg/loosebinpack/loosebinpack.go index 7e9719eb57..45e4d5b70c 100644 --- a/pkg/loosebinpack/loosebinpack.go +++ b/pkg/loosebinpack/loosebinpack.go @@ -2,36 +2,77 @@ package loosebinpack import ( "context" + "errors" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/klog/v2" "k8s.io/kubernetes/pkg/scheduler/framework" + "sigs.k8s.io/scheduler-plugins/apis/config" ) type LooseBinPack struct { - // TODO: Add fields here + logger klog.Logger + cpuThresholdPercent float64 + memoryThresholdPercent float64 } const Name = "LooseBinPack" -var _ = framework.ScorePlugin(&LooseBinPack{}) +var ( + ErrNodeNotFound = errors.New("node not found") + ErrInvalidArgs = errors.New("invalid args") + ErrCpuUtilizationTooHigh = errors.New("cpu utilization too high") + ErrMemoryUtilizationTooHigh = errors.New("memory utilization too high") +) + +var _ = framework.FilterPlugin(&LooseBinPack{}) func New(ctx context.Context, obj runtime.Object, h framework.Handle) (framework.Plugin, error) { - return &LooseBinPack{}, nil + logger := klog.FromContext(ctx).WithValues("plugin", Name) + logger.V(4).Info("Creating new instance of the NetworkOverhead plugin") + + args, ok := obj.(*config.LooseBinPackArgs) + if !ok { + logger.V(4).Error(ErrInvalidArgs, "args", obj) + return nil, ErrInvalidArgs + } + + return &LooseBinPack{ + logger: logger, + cpuThresholdPercent: float64(args.CpuThresholdPercent) / 100, + memoryThresholdPercent: float64(args.MemoryThresholdPercent) / 100, + }, nil } func (lbp *LooseBinPack) Name() string { return Name } -func (lbp *LooseBinPack) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) (int64, *framework.Status) { - return 0, nil -} +func (lbp *LooseBinPack) Filter(ctx context.Context, _ *framework.CycleState, _ *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status { + if nodeInfo.Node() == nil { + lbp.logger.V(4).Error(ErrNodeNotFound, ErrNodeNotFound.Error()) + return framework.NewStatus(framework.Error, ErrNodeNotFound.Error()) + } -func (lbp *LooseBinPack) ScoreExtensions() framework.ScoreExtensions { - return nil -} + nodeAllocatableCpu := nodeInfo.Allocatable.MilliCPU + nodeAllocatableMemory := nodeInfo.Allocatable.Memory + + nodeRequestedCpu := nodeInfo.Requested.MilliCPU + nodeRequestedMemory := nodeInfo.Requested.Memory + + nodeCpuUtilization := float64(nodeRequestedCpu) / float64(nodeAllocatableCpu) + nodeMemoryUtilization := float64(nodeRequestedMemory) / float64(nodeAllocatableMemory) + + if nodeCpuUtilization > float64(lbp.cpuThresholdPercent) { + lbp.logger.V(4).Error(ErrCpuUtilizationTooHigh, ErrCpuUtilizationTooHigh.Error(), "node", klog.KObj(nodeInfo.Node())) + return framework.NewStatus(framework.Unschedulable, ErrCpuUtilizationTooHigh.Error()) + } + + if nodeMemoryUtilization > float64(lbp.memoryThresholdPercent) { + lbp.logger.V(4).Error(ErrMemoryUtilizationTooHigh, ErrMemoryUtilizationTooHigh.Error(), "node", klog.KObj(nodeInfo.Node())) + return framework.NewStatus(framework.Unschedulable, ErrMemoryUtilizationTooHigh.Error()) + } -func (lbp *LooseBinPack) NormalizeScore(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status { - return nil + return framework.NewStatus(framework.Success) } diff --git a/pkg/loosebinpack/loosebinpack_test.go b/pkg/loosebinpack/loosebinpack_test.go new file mode 100644 index 0000000000..18fca3ef55 --- /dev/null +++ b/pkg/loosebinpack/loosebinpack_test.go @@ -0,0 +1,128 @@ +package loosebinpack + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/pkg/scheduler/framework" + "sigs.k8s.io/scheduler-plugins/apis/config" +) + +func TestNew(t *testing.T) { + pluginArgs := &config.LooseBinPackArgs{ + CpuThresholdPercent: 85, + MemoryThresholdPercent: 85, + } + _, err := New(t.Context(), pluginArgs, nil) + + assert.NoError(t, err) +} + +func TestNodeFilter(t *testing.T) { + tt := []struct { + name string + cpuThresholdPercent int64 + memoryThresholdPercent int64 + nodeInfo *framework.NodeInfo + expectedStatus framework.Status + }{ + { + name: "node with cpu and memory utilization below threshold", + cpuThresholdPercent: 85, + memoryThresholdPercent: 85, + nodeInfo: makeNodeInfo("node1", 1000, 1000, 100, 100), + expectedStatus: *framework.NewStatus(framework.Success), + }, + { + name: "node with cpu and memory utilization above threshold", + cpuThresholdPercent: 85, + memoryThresholdPercent: 85, + nodeInfo: makeNodeInfo("node1", 1000, 1000, 1000, 1000), + expectedStatus: *framework.NewStatus(framework.Unschedulable), + }, + { + name: "node with memory utilization above threshold", + cpuThresholdPercent: 85, + memoryThresholdPercent: 85, + nodeInfo: makeNodeInfo("node1", 1000, 1000, 100, 1000), + expectedStatus: *framework.NewStatus(framework.Unschedulable), + }, + { + name: "node with cpu utilization above threshold", + cpuThresholdPercent: 85, + memoryThresholdPercent: 85, + nodeInfo: makeNodeInfo("node1", 1000, 1000, 1000, 100), + expectedStatus: *framework.NewStatus(framework.Unschedulable), + }, + { + name: "node with different thresholdes, both pass", + cpuThresholdPercent: 50, + memoryThresholdPercent: 75, + nodeInfo: makeNodeInfo("node1", 1000, 1000, 100, 100), + expectedStatus: *framework.NewStatus(framework.Success), + }, + { + name: "node with different thresholdes, CPU too high", + cpuThresholdPercent: 50, + memoryThresholdPercent: 75, + nodeInfo: makeNodeInfo("node1", 1000, 1000, 501, 100), + expectedStatus: *framework.NewStatus(framework.Unschedulable), + }, + + { + name: "node with different thresholdes, Memory too high", + cpuThresholdPercent: 50, + memoryThresholdPercent: 75, + nodeInfo: makeNodeInfo("node1", 1000, 1000, 499, 751), + expectedStatus: *framework.NewStatus(framework.Unschedulable), + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + filterPlugin, err := makePlugin(t.Context(), tc.cpuThresholdPercent, tc.memoryThresholdPercent) + assert.NoError(t, err) + status := filterPlugin.Filter(t.Context(), nil, nil, tc.nodeInfo) + assert.Equal(t, tc.expectedStatus.Code(), status.Code()) + }) + } +} + +func makeNodeInfo(name string, allocatableCpu, allocatableMemory, requestedCpu, requestedMemory int64) *framework.NodeInfo { + n := framework.NewNodeInfo() + n.SetNode(&v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + }) + n.Allocatable = &framework.Resource{ + MilliCPU: allocatableCpu, + Memory: allocatableMemory, + } + n.Requested = &framework.Resource{ + MilliCPU: requestedCpu, + Memory: requestedMemory, + } + return n +} + +func makePlugin(ctx context.Context, cpuThresholdPercent, memoryThresholdPercent int64) (framework.FilterPlugin, error) { + pluginArgs := &config.LooseBinPackArgs{ + CpuThresholdPercent: cpuThresholdPercent, + MemoryThresholdPercent: memoryThresholdPercent, + } + plugin, err := New(ctx, pluginArgs, nil) + if err != nil { + return nil, err + } + filterPlugin, ok := plugin.(framework.FilterPlugin) + if !ok { + return nil, fmt.Errorf("plugin is not a filter plugin") + } + + return filterPlugin, nil +} From eb9003cb335d4eff3a03ee6a4437a9f7742af5f2 Mon Sep 17 00:00:00 2001 From: Logan Ballard Date: Fri, 26 Sep 2025 14:07:35 -0400 Subject: [PATCH 4/7] adding unit tests and ability for pod to be considered --- .github/PULL_REQUEST_TEMPLATE.md | 50 -------------- pkg/loosebinpack/loosebinpack.go | 31 ++++++--- pkg/loosebinpack/loosebinpack_test.go | 99 +++++++++++++++++++-------- 3 files changed, 94 insertions(+), 86 deletions(-) delete mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index bbb828bc2e..0000000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,50 +0,0 @@ - - -#### What type of PR is this? - - - -#### What this PR does / why we need it: - -#### Which issue(s) this PR fixes: - -Fixes # - -#### Special notes for your reviewer: - -#### Does this PR introduce a user-facing change? - -```release-note - -``` diff --git a/pkg/loosebinpack/loosebinpack.go b/pkg/loosebinpack/loosebinpack.go index 45e4d5b70c..0b7c8dd061 100644 --- a/pkg/loosebinpack/loosebinpack.go +++ b/pkg/loosebinpack/loosebinpack.go @@ -5,6 +5,7 @@ import ( "errors" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/runtime" "k8s.io/klog/v2" "k8s.io/kubernetes/pkg/scheduler/framework" @@ -21,16 +22,18 @@ const Name = "LooseBinPack" var ( ErrNodeNotFound = errors.New("node not found") + ErrPodNotFound = errors.New("pod not found") ErrInvalidArgs = errors.New("invalid args") ErrCpuUtilizationTooHigh = errors.New("cpu utilization too high") ErrMemoryUtilizationTooHigh = errors.New("memory utilization too high") + ErrPodNoContainers = errors.New("pod has no containers") ) var _ = framework.FilterPlugin(&LooseBinPack{}) func New(ctx context.Context, obj runtime.Object, h framework.Handle) (framework.Plugin, error) { logger := klog.FromContext(ctx).WithValues("plugin", Name) - logger.V(4).Info("Creating new instance of the NetworkOverhead plugin") + logger.V(4).Info("Creating loosebinpack plugin") args, ok := obj.(*config.LooseBinPackArgs) if !ok { @@ -49,20 +52,32 @@ func (lbp *LooseBinPack) Name() string { return Name } -func (lbp *LooseBinPack) Filter(ctx context.Context, _ *framework.CycleState, _ *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status { +func (lbp *LooseBinPack) Filter(ctx context.Context, _ *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status { if nodeInfo.Node() == nil { lbp.logger.V(4).Error(ErrNodeNotFound, ErrNodeNotFound.Error()) return framework.NewStatus(framework.Error, ErrNodeNotFound.Error()) } - nodeAllocatableCpu := nodeInfo.Allocatable.MilliCPU - nodeAllocatableMemory := nodeInfo.Allocatable.Memory + if pod == nil { + lbp.logger.V(4).Error(ErrPodNotFound, ErrPodNotFound.Error()) + return framework.NewStatus(framework.Error, ErrPodNotFound.Error()) + } + + if len(pod.Spec.Containers) == 0 { + lbp.logger.V(4).Error(ErrPodNoContainers, ErrPodNoContainers.Error()) + return framework.NewStatus(framework.Error, ErrPodNoContainers.Error()) + } - nodeRequestedCpu := nodeInfo.Requested.MilliCPU - nodeRequestedMemory := nodeInfo.Requested.Memory + nodeRequestedCpuAfterPod := nodeInfo.Requested.MilliCPU + nodeRequestedMemoryAfterPod := nodeInfo.Requested.Memory + + for _, container := range pod.Spec.Containers { + nodeRequestedCpuAfterPod += container.Resources.Requests.Cpu().ScaledValue(resource.Milli) + nodeRequestedMemoryAfterPod += container.Resources.Requests.Memory().Value() + } - nodeCpuUtilization := float64(nodeRequestedCpu) / float64(nodeAllocatableCpu) - nodeMemoryUtilization := float64(nodeRequestedMemory) / float64(nodeAllocatableMemory) + nodeCpuUtilization := float64(nodeRequestedCpuAfterPod) / float64(nodeInfo.Allocatable.MilliCPU) + nodeMemoryUtilization := float64(nodeRequestedMemoryAfterPod) / float64(nodeInfo.Allocatable.Memory) if nodeCpuUtilization > float64(lbp.cpuThresholdPercent) { lbp.logger.V(4).Error(ErrCpuUtilizationTooHigh, ErrCpuUtilizationTooHigh.Error(), "node", klog.KObj(nodeInfo.Node())) diff --git a/pkg/loosebinpack/loosebinpack_test.go b/pkg/loosebinpack/loosebinpack_test.go index 18fca3ef55..57f96301b5 100644 --- a/pkg/loosebinpack/loosebinpack_test.go +++ b/pkg/loosebinpack/loosebinpack_test.go @@ -3,10 +3,12 @@ package loosebinpack import ( "context" "fmt" + "strconv" "testing" "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/kubernetes/pkg/scheduler/framework" "sigs.k8s.io/scheduler-plugins/apis/config" @@ -25,13 +27,23 @@ func TestNew(t *testing.T) { func TestNodeFilter(t *testing.T) { tt := []struct { name string + pod *v1.Pod cpuThresholdPercent int64 memoryThresholdPercent int64 nodeInfo *framework.NodeInfo expectedStatus framework.Status }{ + { + name: "pod with no containers", + pod: makePod("pod1"), + cpuThresholdPercent: 85, + memoryThresholdPercent: 85, + nodeInfo: makeNodeInfo("node1", 1000, 1000, 100, 100), + expectedStatus: *framework.NewStatus(framework.Error, ErrPodNoContainers.Error()), + }, { name: "node with cpu and memory utilization below threshold", + pod: makePod("pod1", containerReq{CpuReq: 100, MemoryReq: 100}), cpuThresholdPercent: 85, memoryThresholdPercent: 85, nodeInfo: makeNodeInfo("node1", 1000, 1000, 100, 100), @@ -39,46 +51,43 @@ func TestNodeFilter(t *testing.T) { }, { name: "node with cpu and memory utilization above threshold", + pod: makePod("pod1", containerReq{CpuReq: 100, MemoryReq: 100}), cpuThresholdPercent: 85, memoryThresholdPercent: 85, - nodeInfo: makeNodeInfo("node1", 1000, 1000, 1000, 1000), - expectedStatus: *framework.NewStatus(framework.Unschedulable), + nodeInfo: makeNodeInfo("node1", 1000, 1000, 900, 900), + expectedStatus: *framework.NewStatus(framework.Unschedulable, ErrCpuUtilizationTooHigh.Error()), }, { - name: "node with memory utilization above threshold", + name: "node with cpu and memory utilization below threshold until pod is added", + pod: makePod("pod1", containerReq{CpuReq: 100, MemoryReq: 100}), cpuThresholdPercent: 85, memoryThresholdPercent: 85, - nodeInfo: makeNodeInfo("node1", 1000, 1000, 100, 1000), - expectedStatus: *framework.NewStatus(framework.Unschedulable), + nodeInfo: makeNodeInfo("node1", 1000, 1000, 800, 800), + expectedStatus: *framework.NewStatus(framework.Unschedulable, ErrCpuUtilizationTooHigh.Error()), }, { - name: "node with cpu utilization above threshold", + name: "node with cpu and memory utilization below threshold until pod is added - two containers. first would not cause failure, second will", + pod: makePod("pod1", containerReq{CpuReq: 100, MemoryReq: 100}, containerReq{CpuReq: 100, MemoryReq: 100}), cpuThresholdPercent: 85, memoryThresholdPercent: 85, - nodeInfo: makeNodeInfo("node1", 1000, 1000, 1000, 100), - expectedStatus: *framework.NewStatus(framework.Unschedulable), + nodeInfo: makeNodeInfo("node1", 1000, 1000, 700, 700), + expectedStatus: *framework.NewStatus(framework.Unschedulable, ErrCpuUtilizationTooHigh.Error()), }, { - name: "node with different thresholdes, both pass", - cpuThresholdPercent: 50, - memoryThresholdPercent: 75, - nodeInfo: makeNodeInfo("node1", 1000, 1000, 100, 100), + name: "node with cpu and memory utilization below threshold - two containers. first would not cause failure, second wont either", + pod: makePod("pod1", containerReq{CpuReq: 100, MemoryReq: 100}, containerReq{CpuReq: 100, MemoryReq: 100}), + cpuThresholdPercent: 85, + memoryThresholdPercent: 85, + nodeInfo: makeNodeInfo("node1", 1000, 1000, 600, 600), expectedStatus: *framework.NewStatus(framework.Success), }, { - name: "node with different thresholdes, CPU too high", - cpuThresholdPercent: 50, - memoryThresholdPercent: 75, - nodeInfo: makeNodeInfo("node1", 1000, 1000, 501, 100), - expectedStatus: *framework.NewStatus(framework.Unschedulable), - }, - - { - name: "node with different thresholdes, Memory too high", - cpuThresholdPercent: 50, - memoryThresholdPercent: 75, - nodeInfo: makeNodeInfo("node1", 1000, 1000, 499, 751), - expectedStatus: *framework.NewStatus(framework.Unschedulable), + name: "node with cpu and memory utilization below threshold - two containers. first would not cause failure, second will cause CPU threshold failure", + pod: makePod("pod1", containerReq{CpuReq: 100, MemoryReq: 100}, containerReq{CpuReq: 151, MemoryReq: 100}), + cpuThresholdPercent: 85, + memoryThresholdPercent: 85, + nodeInfo: makeNodeInfo("node1", 1000, 1000, 600, 600), + expectedStatus: *framework.NewStatus(framework.Unschedulable, ErrCpuUtilizationTooHigh.Error()), }, } @@ -86,12 +95,46 @@ func TestNodeFilter(t *testing.T) { t.Run(tc.name, func(t *testing.T) { filterPlugin, err := makePlugin(t.Context(), tc.cpuThresholdPercent, tc.memoryThresholdPercent) assert.NoError(t, err) - status := filterPlugin.Filter(t.Context(), nil, nil, tc.nodeInfo) + status := filterPlugin.Filter(t.Context(), nil, tc.pod, tc.nodeInfo) assert.Equal(t, tc.expectedStatus.Code(), status.Code()) + assert.Equal(t, tc.expectedStatus.Message(), status.Message()) }) } } +type containerReq struct { + CpuReq int64 + MemoryReq int64 +} + +func makeContainer(name string, cpuReq, memoryReq int64) v1.Container { + return v1.Container{ + Name: name, + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: resource.MustParse(strconv.FormatInt(cpuReq, 10) + "m"), + v1.ResourceMemory: resource.MustParse(strconv.FormatInt(memoryReq, 10) + "Ki"), + }, + }, + } +} + +func makePod(name string, containerReq ...containerReq) *v1.Pod { + containers := make([]v1.Container, len(containerReq)) + for i, req := range containerReq { + containers[i] = makeContainer(fmt.Sprintf("%s-%d", name, i), req.CpuReq, req.MemoryReq) + } + + return &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: v1.PodSpec{ + Containers: containers, + }, + } +} + func makeNodeInfo(name string, allocatableCpu, allocatableMemory, requestedCpu, requestedMemory int64) *framework.NodeInfo { n := framework.NewNodeInfo() n.SetNode(&v1.Node{ @@ -101,11 +144,11 @@ func makeNodeInfo(name string, allocatableCpu, allocatableMemory, requestedCpu, }) n.Allocatable = &framework.Resource{ MilliCPU: allocatableCpu, - Memory: allocatableMemory, + Memory: allocatableMemory * 1024, } n.Requested = &framework.Resource{ MilliCPU: requestedCpu, - Memory: requestedMemory, + Memory: requestedMemory * 1024, } return n } From cc7bc0b9f85e3fc92ff13a191db86a8c3b175759 Mon Sep 17 00:00:00 2001 From: Logan Ballard Date: Fri, 26 Sep 2025 16:38:31 -0400 Subject: [PATCH 5/7] registering v1 --- apis/config/v1/register.go | 1 + manifests/loosebinpack/scheduler-config.yaml | 34 ++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 manifests/loosebinpack/scheduler-config.yaml diff --git a/apis/config/v1/register.go b/apis/config/v1/register.go index ba97d7a07f..fec0784d32 100644 --- a/apis/config/v1/register.go +++ b/apis/config/v1/register.go @@ -47,6 +47,7 @@ func addKnownTypes(scheme *runtime.Scheme) error { &NetworkOverheadArgs{}, &SySchedArgs{}, &PeaksArgs{}, + &LooseBinPackArgs{}, ) return nil } diff --git a/manifests/loosebinpack/scheduler-config.yaml b/manifests/loosebinpack/scheduler-config.yaml new file mode 100644 index 0000000000..74f0f77550 --- /dev/null +++ b/manifests/loosebinpack/scheduler-config.yaml @@ -0,0 +1,34 @@ +apiVersion: kubescheduler.config.k8s.io/v1 +kind: KubeSchedulerConfiguration +leaderElection: + leaderElect: true + resourceName: optimize-utilization-scheduler-2 + resourceNamespace: kube-scheduling +profiles: + - pluginConfig: + - args: + apiVersion: kubescheduler.config.k8s.io/v1 + kind: NodeResourcesFitArgs + scoringStrategy: + resources: + - name: cpu + weight: 4 + - name: memory + weight: 1 + type: MostAllocated + name: NodeResourcesFit + - args: + apiVersion: kubescheduler.config.k8s.io/v1 + kind: LooseBinPackArgs + cpuThresholdPercent: 85 + memoryThresholdPercent: 85 + name: LooseBinPack + plugins: + filter: + enabled: + - name: LooseBinPack + score: + enabled: + - name: NodeResourcesFit + weight: 1 + schedulerName: optimize-utilization-scheduler-2 From 8c0f3c1e66b258c7aed0bb01132d097b10a39bf6 Mon Sep 17 00:00:00 2001 From: Logan Ballard Date: Fri, 26 Sep 2025 17:55:37 -0400 Subject: [PATCH 6/7] regenerating types --- Makefile | 2 +- apis/config/types.go | 6 +++--- apis/config/v1/types.go | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index f356763378..deed7dce6c 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ RELEASE_IMAGE:=kube-scheduler:$(RELEASE_VERSION) RELEASE_CONTROLLER_IMAGE:=controller:$(RELEASE_VERSION) GO_BASE_IMAGE?=golang:$(GO_VERSION) DISTROLESS_BASE_IMAGE?=gcr.io/distroless/static:nonroot -EXTRA_ARGS="" +EXTRA_ARGS?="" # VERSION is the scheduler's version # diff --git a/apis/config/types.go b/apis/config/types.go index a847bd292c..a16a95a06c 100644 --- a/apis/config/types.go +++ b/apis/config/types.go @@ -303,10 +303,10 @@ type PowerModel struct { // LooseBinPackArgs holds arguments used to configure LooseBinPack plugin. type LooseBinPackArgs struct { - metav1.TypeMeta `json:",inline"` + metav1.TypeMeta // CPU Utilization Threshold in percentage (0-100) - CpuThresholdPercent int64 `json:"cpuThresholdPercent,omitempty"` + CpuThresholdPercent int64 // Memory Utilization Threshold in percentage (0-100) - MemoryThresholdPercent int64 `json:"memoryThresholdPercent,omitempty"` + MemoryThresholdPercent int64 } diff --git a/apis/config/v1/types.go b/apis/config/v1/types.go index 524b061c8d..82b8ca6dd4 100644 --- a/apis/config/v1/types.go +++ b/apis/config/v1/types.go @@ -299,7 +299,6 @@ type PowerModel struct { } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// +k8s:defaulter-gen=true // LooseBinPackArgs holds arguments used to configure LooseBinPack plugin. type LooseBinPackArgs struct { From fb84889942ad66a3b457e6142da1d7c14ef67419 Mon Sep 17 00:00:00 2001 From: Logan Ballard Date: Mon, 29 Sep 2025 10:17:58 -0400 Subject: [PATCH 7/7] adding config load test --- cmd/scheduler/main_test.go | 45 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/cmd/scheduler/main_test.go b/cmd/scheduler/main_test.go index fcbd7870eb..67582f912d 100644 --- a/cmd/scheduler/main_test.go +++ b/cmd/scheduler/main_test.go @@ -36,6 +36,7 @@ import ( "sigs.k8s.io/scheduler-plugins/pkg/capacityscheduling" "sigs.k8s.io/scheduler-plugins/pkg/coscheduling" + "sigs.k8s.io/scheduler-plugins/pkg/loosebinpack" "sigs.k8s.io/scheduler-plugins/pkg/networkaware/networkoverhead" "sigs.k8s.io/scheduler-plugins/pkg/networkaware/topologicalsort" "sigs.k8s.io/scheduler-plugins/pkg/noderesources" @@ -428,6 +429,31 @@ profiles: t.Fatal(err) } + // LooseBinPack plugin config + looseBinPackConfig := filepath.Join(tmpDir, "looseBinPack.yaml") + if err := os.WriteFile(looseBinPackConfig, []byte(fmt.Sprintf(` +apiVersion: kubescheduler.config.k8s.io/v1 +kind: KubeSchedulerConfiguration +clientConnection: + kubeconfig: "%s" +profiles: +- plugins: + filter: + enabled: + - name: LooseBinPack + disabled: + - name: "*" + pluginConfig: + - name: LooseBinPack + args: + apiVersion: kubescheduler.config.k8s.io/v1 + kind: LooseBinPackArgs + cpuThresholdPercent: 90 + memoryThresholdPercent: 90 +`, configKubeconfig)), os.FileMode(0600)); err != nil { + t.Fatal(err) + } + // multiple profiles config multiProfilesConfig := filepath.Join(tmpDir, "multi-profiles.yaml") if err := os.WriteFile(multiProfilesConfig, []byte(fmt.Sprintf(` @@ -726,6 +752,25 @@ profiles: }, }, }, + { + name: "single profile config - LooseBinPack with args", + flags: []string{"--config", looseBinPackConfig}, + registryOptions: []app.Option{app.WithPlugin(loosebinpack.Name, loosebinpack.New)}, + wantPlugins: map[string]*config.Plugins{ + "default-scheduler": { + PreEnqueue: defaults.ExpandedPluginsV1.PreEnqueue, + QueueSort: defaults.ExpandedPluginsV1.QueueSort, + Bind: defaults.ExpandedPluginsV1.Bind, + PreFilter: defaults.ExpandedPluginsV1.PreFilter, + Filter: config.PluginSet{Enabled: []config.Plugin{{Name: loosebinpack.Name}}}, + PostFilter: defaults.ExpandedPluginsV1.PostFilter, + PreScore: defaults.ExpandedPluginsV1.PreScore, + Score: defaults.ExpandedPluginsV1.Score, + Reserve: defaults.ExpandedPluginsV1.Reserve, + PreBind: defaults.ExpandedPluginsV1.PreBind, + }, + }, + }, // TODO: add a multi profile test. // Ref: test "plugin config with multiple profiles" in // https://github.com/kubernetes/kubernetes/blob/master/cmd/kube-scheduler/app/server_test.go