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

fix: issue#1261 config "overcommit-factor" less than 1 leads scheduler crash #1267

Closed
wants to merge 1 commit into from
Closed
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
1 change: 1 addition & 0 deletions cmd/webhook-manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"volcano.sh/volcano/cmd/webhook-manager/app"
"volcano.sh/volcano/cmd/webhook-manager/app/options"

_ "volcano.sh/volcano/pkg/webhooks/admission/configmap/validate"
_ "volcano.sh/volcano/pkg/webhooks/admission/jobs/mutate"
_ "volcano.sh/volcano/pkg/webhooks/admission/jobs/validate"
_ "volcano.sh/volcano/pkg/webhooks/admission/pods"
Expand Down
1 change: 1 addition & 0 deletions pkg/scheduler/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ tiers:
- plugins:
- name: priority
- name: gang
- name: conformance
- plugins:
- name: drf
- name: predicates
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

binpack is also needed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was attend to add binpack because it is not fit all scenarios, so it wasn't added.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default cm under installer/volcano-development.yaml contains binpack, maybe we should remove binpack in that file?

Expand Down
143 changes: 143 additions & 0 deletions pkg/webhooks/admission/configmap/validate/admit_cm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package validate

import (
"fmt"

yaml "gopkg.in/yaml.v2"

"k8s.io/api/admission/v1beta1"
whv1beta1 "k8s.io/api/admissionregistration/v1beta1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog"
"strings"
"volcano.sh/volcano/pkg/webhooks/schema"
"volcano.sh/volcano/pkg/webhooks/util"

"volcano.sh/volcano/pkg/scheduler/conf"
"volcano.sh/volcano/pkg/webhooks/router"
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dependency order needed to formated?


const (
configmapName = "volcano-scheduler-configmap"
enqueueName = "enqueue"
overCommitFactor = "overcommit-factor"
overCommitFactorMinVal = 1.0
)

func init() {
router.RegisterAdmission(service)
}

var service = &router.AdmissionService{
Path: "/configmaps/validate",
Func: AdmitConfigmaps,

Config: config,

ValidatingConfig: &whv1beta1.ValidatingWebhookConfiguration{
Webhooks: []whv1beta1.ValidatingWebhook{{
Name: "validateconfigmap.volcano.sh",
Rules: []whv1beta1.RuleWithOperations{
{
Operations: []whv1beta1.OperationType{whv1beta1.Create, whv1beta1.Update},
Rule: whv1beta1.Rule{
APIGroups: []string{""},
APIVersions: []string{"v1"},
Resources: []string{"configmaps"},
},
},
},
}},
},
}

var config = &router.AdmissionServiceConfig{}

// AdmitConfigmaps is to admit configMap for volcano scheduler and return response.
func AdmitConfigmaps(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
klog.V(3).Infof("admitting configmap -- %s", ar.Request.Operation)

cm, err := schema.DecodeConfigmap(ar.Request.Object, ar.Request.Resource)
if err != nil {
return util.ToAdmissionResponse(err)
}

var msg string
reviewResponse := v1beta1.AdmissionResponse{}
reviewResponse.Allowed = true

switch ar.Request.Operation {
case v1beta1.Create, v1beta1.Update:
msg = validateConfigmap(cm, &reviewResponse)
default:
err := fmt.Errorf("expect operation to be 'CREATE' or 'UPDATE'")
return util.ToAdmissionResponse(err)
}

if !reviewResponse.Allowed {
reviewResponse.Result = &metav1.Status{Message: strings.TrimSpace(msg)}
}
return &reviewResponse
}

/*
allow config to create and update when
1. configmap name must be "volcano-scheduler-configmap"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l think volcano-scheduler-configmap is not a necessity, it's defined by user

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I check helm files , you're right。
configmap name Transfer to admission webhook through ENV , is it OK ??

2. config data must be only one
3. enqueue argument "overcommit-factor" must be greater than 1 if existed
*/
func validateConfigmap(cm *v1.ConfigMap, reviewResponse *v1beta1.AdmissionResponse) string {
msg := ""
if cm.Name != configmapName {
return msg
}

confKey := ""
confNum := 0
for key := range cm.Data {
if strings.HasSuffix(key, ".conf") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also .conf is not a necessity

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1,it watch all configmap, we must filter them, so it is necessary to adjust the configmap name
2。i think it is necessary to certain restrictions on the format of scheduler configmap data

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay got it, I have no opinion on this change, but this introduces some name restrictions indeed.

confNum++
confKey = key
}
}

if confNum != 1 {
reviewResponse.Allowed = false
return fmt.Errorf("%s data format is err, config file = %d ", configmapName, confNum).Error()
}

data := cm.Data[confKey]
schedulerConf := &conf.SchedulerConfiguration{}
buf := make([]byte, len(data))
copy(buf, data)

if err := yaml.Unmarshal(buf, schedulerConf); err != nil {
reviewResponse.Allowed = false
return fmt.Errorf("%s data %s Unmarshal failed", configmapName, confKey).Error()
}

err := enqueueConfCheck(schedulerConf.Configurations)
if err != nil {
reviewResponse.Allowed = false
return err.Error()
}

return msg
}

// check enqueue action's legality
func enqueueConfCheck(configurations []conf.Configuration) error {
actionArg := GetArgOfActionFromConf(configurations, enqueueName)
if actionArg == nil {
return nil
}

var factor float64
actionArg.GetFloat64(&factor, overCommitFactor)
if factor < overCommitFactorMinVal {
return fmt.Errorf("enqueue argument %s must be more than %v", overCommitFactor, overCommitFactorMinVal)
}

return nil
}
184 changes: 184 additions & 0 deletions pkg/webhooks/admission/configmap/validate/admit_cm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package validate

import (
"k8s.io/api/admission/v1beta1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"strings"
"testing"
)

func TestValidatePod(t *testing.T) {
testCases := []struct {
Name string
ConfigMap v1.ConfigMap
ExpectErr bool
reviewResponse v1beta1.AdmissionResponse
ret string
}{
{
Name: "check whether it is volcano config",
ConfigMap: v1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-err-name.conf",
},
Data: map[string]string{
"default-scheduler.conf": `
actions: "enqueue, allocate, backfill"
tiers:
- plugins:
- name: priority
- name: gang
- name: conformance
- plugins:
- name: drf
- name: predicates
- name: proportion
- name: nodeorder
`,
},
},
reviewResponse: v1beta1.AdmissionResponse{Allowed: true},
ret: "",
ExpectErr: false,
},
{
Name: "check whether it exists more one config data",
ConfigMap: v1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: configmapName,
},
Data: map[string]string{
"default-scheduler.conf": `
actions: "enqueue, allocate, backfill"
tiers:
- plugins:
- name: priority
- name: gang
- name: conformance
- plugins:
- name: drf
- name: predicates
- name: proportion
- name: nodeorder
`,
"ief-scheduler.conf": `
actions: "enqueue, allocate, backfill"
tiers:
- plugins:
- name: priority
- name: gang
- name: conformance
- plugins:
- name: drf
- name: predicates
- name: proportion
- name: nodeorder
`,
},
},
reviewResponse: v1beta1.AdmissionResponse{Allowed: false},
ret: "data format is err",
ExpectErr: true,
},
{
Name: "check whether it is volcano config",
ConfigMap: v1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: configmapName,
},
Data: map[string]string{
"default-scheduler.conf": `
actions: "enqueue, allocate, backfill"
configurations:
- name: enqueue
arguments:
overcommit-factor: 1.2
tiers:
- plugins:
- name: priority
- name: gang
- name: conformance
- plugins:
- name: drf
- name: predicates
- name: proportion
- name: nodeorder
`,
},
},
reviewResponse: v1beta1.AdmissionResponse{Allowed: true},
ret: "",
ExpectErr: false,
},
{
Name: "check whether it is volcano config",
ConfigMap: v1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: configmapName,
},
Data: map[string]string{
"default-scheduler.conf": `
actions: "enqueue, allocate, backfill"
configurations:
- name: enqueue
arguments:
overcommit-factor: 0.8
tiers:
- plugins:
- name: priority
- name: gang
- name: conformance
- plugins:
- name: drf
- name: predicates
- name: proportion
- name: nodeorder
`,
},
},
reviewResponse: v1beta1.AdmissionResponse{Allowed: false},
ret: "must be more than",
ExpectErr: true,
},
}

for _, testCase := range testCases {
ret := validateConfigmap(&testCase.ConfigMap, &testCase.reviewResponse)
if testCase.ExpectErr == true && ret == "" {
t.Errorf("%s: test case Expect error msg :%s, but got nil.", testCase.Name, testCase.ret)
}

if testCase.ExpectErr == true && testCase.reviewResponse.Allowed != false {
t.Errorf("%s: test case Expect Allowed as false but got true.", testCase.Name)
}

if testCase.ExpectErr == true && !strings.Contains(ret, testCase.ret) {
t.Errorf("%s: test case Expect error msg :%s, but got diff error %v", testCase.Name, testCase.ret, ret)
}

if testCase.ExpectErr == false && ret != "" {
t.Errorf("%s: test case Expect no error, but got error %v", testCase.Name, ret)
}

if testCase.ExpectErr == false && testCase.reviewResponse.Allowed != true {
t.Errorf("%s: test case Expect Allowed as true but got false. %v", testCase.Name, testCase.reviewResponse)
}
}
}
43 changes: 43 additions & 0 deletions pkg/webhooks/admission/configmap/validate/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package validate
Copy link
Member

@shinytang6 shinytang6 Feb 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The functions in this file should already exist in codebase?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes , but it is in scheduler folder, so I repeat some


import (
"strconv"

"k8s.io/klog"

"volcano.sh/volcano/pkg/scheduler/conf"
)

// Arguments map
type Arguments map[string]string

// GetArgOfActionFromConf return argument of action reading from configuration of schedule
func GetArgOfActionFromConf(configurations []conf.Configuration, actionName string) Arguments {
for _, c := range configurations {
if c.Name == actionName {
return c.Arguments
}
}

return nil
}

// GetFloat64 get the float64 value from string
func (a Arguments) GetFloat64(ptr *float64, key string) {
if ptr == nil {
return
}

argv, ok := a[key]
if !ok || len(argv) == 0 {
return
}

value, err := strconv.ParseFloat(argv, 64)
if err != nil {
klog.Warningf("Could not parse argument: %s for key %s, with err %v", argv, key, err)
return
}

*ptr = value
}
Loading