Skip to content

Commit 70e09f2

Browse files
authored
Merge pull request kubernetes#88842 from angao/fit-arg
add args for NodeResourcesFit plugin
2 parents 896da22 + b21b298 commit 70e09f2

File tree

9 files changed

+176
-21
lines changed

9 files changed

+176
-21
lines changed

pkg/kubelet/lifecycle/predicate.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ package lifecycle
1919
import (
2020
"fmt"
2121

22-
"k8s.io/api/core/v1"
22+
v1 "k8s.io/api/core/v1"
2323
"k8s.io/klog/v2"
2424
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
2525
"k8s.io/kubernetes/pkg/kubelet/util/format"
@@ -227,7 +227,7 @@ func GeneralPredicates(pod *v1.Pod, nodeInfo *schedulerframework.NodeInfo) ([]Pr
227227
}
228228

229229
var reasons []PredicateFailureReason
230-
for _, r := range noderesources.Fits(pod, nodeInfo, nil) {
230+
for _, r := range noderesources.Fits(pod, nodeInfo) {
231231
reasons = append(reasons, &InsufficientResourceError{
232232
ResourceName: r.ResourceName,
233233
Requested: r.Requested,

pkg/scheduler/apis/config/types_pluginargs.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ type NodeResourcesFitArgs struct {
5757
// IgnoredResources is the list of resources that NodeResources fit filter
5858
// should ignore.
5959
IgnoredResources []string
60+
// IgnoredResourceGroups defines the list of resource groups that NodeResources fit filter should ignore.
61+
// e.g. if group is ["example.com"], it will ignore all resource names that begin
62+
// with "example.com", such as "example.com/aaa" and "example.com/bbb".
63+
// A resource group name can't contain '/'.
64+
IgnoredResourceGroups []string
6065
}
6166

6267
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

pkg/scheduler/apis/config/v1beta1/zz_generated.conversion.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/scheduler/apis/config/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/scheduler/framework/plugins/noderesources/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ go_library(
2323
"//staging/src/k8s.io/api/core/v1:go_default_library",
2424
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
2525
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
26+
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/validation:go_default_library",
2627
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
2728
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
29+
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
2830
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
2931
"//vendor/k8s.io/klog/v2:go_default_library",
3032
],

pkg/scheduler/framework/plugins/noderesources/fit.go

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ package noderesources
1919
import (
2020
"context"
2121
"fmt"
22+
"strings"
2223

2324
v1 "k8s.io/api/core/v1"
25+
metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
2426
"k8s.io/apimachinery/pkg/runtime"
2527
"k8s.io/apimachinery/pkg/util/sets"
28+
"k8s.io/apimachinery/pkg/util/validation/field"
2629
utilfeature "k8s.io/apiserver/pkg/util/feature"
2730
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
2831
"k8s.io/kubernetes/pkg/features"
@@ -44,7 +47,8 @@ const (
4447

4548
// Fit is a plugin that checks if a node has sufficient resources.
4649
type Fit struct {
47-
ignoredResources sets.String
50+
ignoredResources sets.String
51+
ignoredResourceGroups sets.String
4852
}
4953

5054
// preFilterState computed at PreFilter and used at Filter.
@@ -62,16 +66,48 @@ func (f *Fit) Name() string {
6266
return FitName
6367
}
6468

69+
func validateFitArgs(args config.NodeResourcesFitArgs) error {
70+
var allErrs field.ErrorList
71+
resPath := field.NewPath("ignoredResources")
72+
for i, res := range args.IgnoredResources {
73+
path := resPath.Index(i)
74+
if errs := metav1validation.ValidateLabelName(res, path); len(errs) != 0 {
75+
allErrs = append(allErrs, errs...)
76+
}
77+
}
78+
79+
groupPath := field.NewPath("ignoredResourceGroups")
80+
for i, group := range args.IgnoredResourceGroups {
81+
path := groupPath.Index(i)
82+
if strings.Contains(group, "/") {
83+
allErrs = append(allErrs, field.Invalid(path, group, "resource group name can't contain '/'"))
84+
}
85+
if errs := metav1validation.ValidateLabelName(group, path); len(errs) != 0 {
86+
allErrs = append(allErrs, errs...)
87+
}
88+
}
89+
90+
if len(allErrs) == 0 {
91+
return nil
92+
}
93+
return allErrs.ToAggregate()
94+
}
95+
6596
// NewFit initializes a new plugin and returns it.
6697
func NewFit(plArgs runtime.Object, _ framework.FrameworkHandle) (framework.Plugin, error) {
6798
args, err := getFitArgs(plArgs)
6899
if err != nil {
69100
return nil, err
70101
}
71-
fit := &Fit{
72-
ignoredResources: sets.NewString(args.IgnoredResources...),
102+
103+
if err := validateFitArgs(args); err != nil {
104+
return nil, err
73105
}
74-
return fit, nil
106+
107+
return &Fit{
108+
ignoredResources: sets.NewString(args.IgnoredResources...),
109+
ignoredResourceGroups: sets.NewString(args.IgnoredResourceGroups...),
110+
}, nil
75111
}
76112

77113
func getFitArgs(obj runtime.Object) (config.NodeResourcesFitArgs, error) {
@@ -162,7 +198,7 @@ func (f *Fit) Filter(ctx context.Context, cycleState *framework.CycleState, pod
162198
return framework.NewStatus(framework.Error, err.Error())
163199
}
164200

165-
insufficientResources := fitsRequest(s, nodeInfo, f.ignoredResources)
201+
insufficientResources := fitsRequest(s, nodeInfo, f.ignoredResources, f.ignoredResourceGroups)
166202

167203
if len(insufficientResources) != 0 {
168204
// We will keep all failure reasons.
@@ -187,11 +223,11 @@ type InsufficientResource struct {
187223
}
188224

189225
// Fits checks if node have enough resources to host the pod.
190-
func Fits(pod *v1.Pod, nodeInfo *framework.NodeInfo, ignoredExtendedResources sets.String) []InsufficientResource {
191-
return fitsRequest(computePodResourceRequest(pod), nodeInfo, ignoredExtendedResources)
226+
func Fits(pod *v1.Pod, nodeInfo *framework.NodeInfo) []InsufficientResource {
227+
return fitsRequest(computePodResourceRequest(pod), nodeInfo, nil, nil)
192228
}
193229

194-
func fitsRequest(podRequest *preFilterState, nodeInfo *framework.NodeInfo, ignoredExtendedResources sets.String) []InsufficientResource {
230+
func fitsRequest(podRequest *preFilterState, nodeInfo *framework.NodeInfo, ignoredExtendedResources, ignoredResourceGroups sets.String) []InsufficientResource {
195231
insufficientResources := make([]InsufficientResource, 0, 4)
196232

197233
allowedPodNumber := nodeInfo.Allocatable.AllowedPodNumber
@@ -205,10 +241,6 @@ func fitsRequest(podRequest *preFilterState, nodeInfo *framework.NodeInfo, ignor
205241
})
206242
}
207243

208-
if ignoredExtendedResources == nil {
209-
ignoredExtendedResources = sets.NewString()
210-
}
211-
212244
if podRequest.MilliCPU == 0 &&
213245
podRequest.Memory == 0 &&
214246
podRequest.EphemeralStorage == 0 &&
@@ -246,9 +278,13 @@ func fitsRequest(podRequest *preFilterState, nodeInfo *framework.NodeInfo, ignor
246278

247279
for rName, rQuant := range podRequest.ScalarResources {
248280
if v1helper.IsExtendedResourceName(rName) {
249-
// If this resource is one of the extended resources that should be
250-
// ignored, we will skip checking it.
251-
if ignoredExtendedResources.Has(string(rName)) {
281+
// If this resource is one of the extended resources that should be ignored, we will skip checking it.
282+
// rName is guaranteed to have a slash due to API validation.
283+
var rNamePrefix string
284+
if ignoredResourceGroups.Len() > 0 {
285+
rNamePrefix = strings.Split(string(rName), "/")[0]
286+
}
287+
if ignoredExtendedResources.Has(string(rName)) || ignoredResourceGroups.Has(rNamePrefix) {
252288
continue
253289
}
254290
}

pkg/scheduler/framework/plugins/noderesources/fit_test.go

Lines changed: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ import (
2020
"context"
2121
"fmt"
2222
"reflect"
23+
"strings"
2324
"testing"
2425

25-
"k8s.io/kubernetes/pkg/scheduler/apis/config"
26-
2726
v1 "k8s.io/api/core/v1"
2827
"k8s.io/apimachinery/pkg/api/resource"
2928
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
29+
"k8s.io/kubernetes/pkg/scheduler/apis/config"
3030
framework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1"
3131
)
3232

@@ -367,6 +367,31 @@ func TestEnoughRequests(t *testing.T) {
367367
wantStatus: framework.NewStatus(framework.Unschedulable, getErrReason(v1.ResourceMemory)),
368368
wantInsufficientResources: []InsufficientResource{{v1.ResourceMemory, getErrReason(v1.ResourceMemory), 16, 5, 20}},
369369
},
370+
{
371+
pod: newResourcePod(
372+
framework.Resource{
373+
MilliCPU: 1,
374+
Memory: 1,
375+
ScalarResources: map[v1.ResourceName]int64{
376+
extendedResourceB: 1,
377+
kubernetesIOResourceA: 1,
378+
}}),
379+
nodeInfo: framework.NewNodeInfo(newResourcePod(framework.Resource{MilliCPU: 0, Memory: 0})),
380+
args: config.NodeResourcesFitArgs{
381+
IgnoredResourceGroups: []string{"example.com"},
382+
},
383+
name: "skip checking ignored extended resource via resource groups",
384+
wantStatus: framework.NewStatus(framework.Unschedulable, fmt.Sprintf("Insufficient %v", kubernetesIOResourceA)),
385+
wantInsufficientResources: []InsufficientResource{
386+
{
387+
ResourceName: kubernetesIOResourceA,
388+
Reason: fmt.Sprintf("Insufficient %v", kubernetesIOResourceA),
389+
Requested: 1,
390+
Used: 0,
391+
Capacity: 0,
392+
},
393+
},
394+
},
370395
}
371396

372397
for _, test := range enoughPodsTests {
@@ -389,9 +414,9 @@ func TestEnoughRequests(t *testing.T) {
389414
t.Errorf("status does not match: %v, want: %v", gotStatus, test.wantStatus)
390415
}
391416

392-
gotInsufficientResources := Fits(test.pod, test.nodeInfo, p.(*Fit).ignoredResources)
417+
gotInsufficientResources := fitsRequest(computePodResourceRequest(test.pod), test.nodeInfo, p.(*Fit).ignoredResources, p.(*Fit).ignoredResourceGroups)
393418
if !reflect.DeepEqual(gotInsufficientResources, test.wantInsufficientResources) {
394-
t.Errorf("insufficient resources do not match: %v, want: %v", gotInsufficientResources, test.wantInsufficientResources)
419+
t.Errorf("insufficient resources do not match: %+v, want: %v", gotInsufficientResources, test.wantInsufficientResources)
395420
}
396421
})
397422
}
@@ -529,3 +554,72 @@ func TestStorageRequests(t *testing.T) {
529554
}
530555

531556
}
557+
558+
func TestValidateFitArgs(t *testing.T) {
559+
argsTest := []struct {
560+
name string
561+
args config.NodeResourcesFitArgs
562+
expect string
563+
}{
564+
{
565+
name: "IgnoredResources: too long value",
566+
args: config.NodeResourcesFitArgs{
567+
IgnoredResources: []string{fmt.Sprintf("longvalue%s", strings.Repeat("a", 64))},
568+
},
569+
expect: "name part must be no more than 63 characters",
570+
},
571+
{
572+
name: "IgnoredResources: name is empty",
573+
args: config.NodeResourcesFitArgs{
574+
IgnoredResources: []string{"example.com/"},
575+
},
576+
expect: "name part must be non-empty",
577+
},
578+
{
579+
name: "IgnoredResources: name has too many slash",
580+
args: config.NodeResourcesFitArgs{
581+
IgnoredResources: []string{"example.com/aaa/bbb"},
582+
},
583+
expect: "a qualified name must consist of alphanumeric characters",
584+
},
585+
{
586+
name: "IgnoredResources: valid args",
587+
args: config.NodeResourcesFitArgs{
588+
IgnoredResources: []string{"example.com"},
589+
},
590+
},
591+
{
592+
name: "IgnoredResourceGroups: valid args ",
593+
args: config.NodeResourcesFitArgs{
594+
IgnoredResourceGroups: []string{"example.com"},
595+
},
596+
},
597+
{
598+
name: "IgnoredResourceGroups: illegal args",
599+
args: config.NodeResourcesFitArgs{
600+
IgnoredResourceGroups: []string{"example.com/"},
601+
},
602+
expect: "name part must be non-empty",
603+
},
604+
{
605+
name: "IgnoredResourceGroups: name is too long",
606+
args: config.NodeResourcesFitArgs{
607+
IgnoredResourceGroups: []string{strings.Repeat("a", 64)},
608+
},
609+
expect: "name part must be no more than 63 characters",
610+
},
611+
{
612+
name: "IgnoredResourceGroups: name cannot be contain slash",
613+
args: config.NodeResourcesFitArgs{
614+
IgnoredResourceGroups: []string{"example.com/aa"},
615+
},
616+
expect: "resource group name can't contain '/'",
617+
},
618+
}
619+
620+
for _, test := range argsTest {
621+
if err := validateFitArgs(test.args); err != nil && !strings.Contains(err.Error(), test.expect) {
622+
t.Errorf("case[%v]: error details do not include %v", test.name, err)
623+
}
624+
}
625+
}

staging/src/k8s.io/kube-scheduler/config/v1beta1/types_pluginargs.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ type NodeResourcesFitArgs struct {
6262
// should ignore.
6363
// +listType=atomic
6464
IgnoredResources []string `json:"ignoredResources,omitempty"`
65+
// IgnoredResourceGroups defines the list of resource groups that NodeResources fit filter should ignore.
66+
// e.g. if group is ["example.com"], it will ignore all resource names that begin
67+
// with "example.com", such as "example.com/aaa" and "example.com/bbb".
68+
// A resource group name can't contain '/'.
69+
// +listType=atomic
70+
IgnoredResourceGroups []string `json:"ignoredResourceGroups,omitempty"`
6571
}
6672

6773
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

staging/src/k8s.io/kube-scheduler/config/v1beta1/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)