-
Notifications
You must be signed in to change notification settings - Fork 976
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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" | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. l think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I check helm files , you're right。 |
||
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") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} |
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) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package validate | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The functions in this file should already exist in codebase? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
binpack
is also neededThere was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
containsbinpack
, maybe we should removebinpack
in that file?