From 17c3da0b06aacf74ee8dacb7cd071ac55e485f0b Mon Sep 17 00:00:00 2001 From: yue9944882 <291271447@qq.com> Date: Tue, 16 Jul 2019 22:51:27 +0800 Subject: [PATCH] initial REST implementation fmt system code review: addressing review commits doc default --- pkg/apis/flowcontrol/bootstrap/flowschemas.go | 27 + .../bootstrap/prioritylevelconfigurations.go | 27 + pkg/apis/flowcontrol/install/install.go | 2 + pkg/apis/flowcontrol/types.go | 16 + pkg/apis/flowcontrol/v1alpha1/defaults.go | 36 ++ pkg/apis/flowcontrol/validation/validation.go | 274 +++++++++ .../flowcontrol/validation/validation_test.go | 566 ++++++++++++++++++ pkg/registry/flowcontrol/flowschema/doc.go | 18 + .../flowcontrol/flowschema/storage/storage.go | 113 ++++ .../flowcontrol/flowschema/strategy.go | 101 ++++ .../prioritylevelconfiguration/doc.go | 18 + .../storage/storage.go | 114 ++++ .../prioritylevelconfiguration/strategy.go | 101 ++++ .../flowcontrol/rest/storage_flowcontrol.go | 148 +++++ 14 files changed, 1561 insertions(+) create mode 100644 pkg/apis/flowcontrol/bootstrap/flowschemas.go create mode 100644 pkg/apis/flowcontrol/bootstrap/prioritylevelconfigurations.go create mode 100644 pkg/apis/flowcontrol/v1alpha1/defaults.go create mode 100644 pkg/apis/flowcontrol/validation/validation.go create mode 100644 pkg/apis/flowcontrol/validation/validation_test.go create mode 100644 pkg/registry/flowcontrol/flowschema/doc.go create mode 100644 pkg/registry/flowcontrol/flowschema/storage/storage.go create mode 100644 pkg/registry/flowcontrol/flowschema/strategy.go create mode 100644 pkg/registry/flowcontrol/prioritylevelconfiguration/doc.go create mode 100644 pkg/registry/flowcontrol/prioritylevelconfiguration/storage/storage.go create mode 100644 pkg/registry/flowcontrol/prioritylevelconfiguration/strategy.go create mode 100644 pkg/registry/flowcontrol/rest/storage_flowcontrol.go diff --git a/pkg/apis/flowcontrol/bootstrap/flowschemas.go b/pkg/apis/flowcontrol/bootstrap/flowschemas.go new file mode 100644 index 00000000000..94013c203f7 --- /dev/null +++ b/pkg/apis/flowcontrol/bootstrap/flowschemas.go @@ -0,0 +1,27 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bootstrap + +import ( + flowcontrolv1alpha1 "k8s.io/api/flowcontrol/v1alpha1" +) + +// SystemFlowSchemas returns a list of system preset flow schemas +func SystemFlowSchemas() []*flowcontrolv1alpha1.FlowSchema { + // TODO: implement system flow-schemas + return nil +} diff --git a/pkg/apis/flowcontrol/bootstrap/prioritylevelconfigurations.go b/pkg/apis/flowcontrol/bootstrap/prioritylevelconfigurations.go new file mode 100644 index 00000000000..850ec6541a2 --- /dev/null +++ b/pkg/apis/flowcontrol/bootstrap/prioritylevelconfigurations.go @@ -0,0 +1,27 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bootstrap + +import ( + flowcontrolv1alpha1 "k8s.io/api/flowcontrol/v1alpha1" +) + +// SystemPriorityLevelConfigurations returns a list of system preset priority levels +func SystemPriorityLevelConfigurations() []*flowcontrolv1alpha1.PriorityLevelConfiguration { + // TODO: implement system flow-schemas + return nil +} diff --git a/pkg/apis/flowcontrol/install/install.go b/pkg/apis/flowcontrol/install/install.go index 7f2213f92c8..b5a98915bab 100644 --- a/pkg/apis/flowcontrol/install/install.go +++ b/pkg/apis/flowcontrol/install/install.go @@ -23,6 +23,7 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/apis/flowcontrol" + flowcontrolv1alpha1 "k8s.io/kubernetes/pkg/apis/flowcontrol/v1alpha1" ) func init() { @@ -32,4 +33,5 @@ func init() { // Install registers the API group and adds types to a scheme func Install(scheme *runtime.Scheme) { utilruntime.Must(flowcontrol.AddToScheme(scheme)) + utilruntime.Must(flowcontrolv1alpha1.AddToScheme(scheme)) } diff --git a/pkg/apis/flowcontrol/types.go b/pkg/apis/flowcontrol/types.go index a6edf01f961..cf0f79e88c5 100644 --- a/pkg/apis/flowcontrol/types.go +++ b/pkg/apis/flowcontrol/types.go @@ -44,6 +44,22 @@ const ( NonResourceAll = "*" ) +// System preset priority level names +const ( + PriorityLevelConfigurationNameSystemTop = "system-top" + PriorityLevelConfigurationNameWorkloadLow = "workload-low" +) + +// Default settings for flow-schema +const ( + FlowSchemaDefaultMatchingPrecedence int32 = 1000 +) + +// Default settings for priority-level-configuration +const ( + PriorityLevelConfigurationDefaultHandSize int32 = 1 +) + // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/apis/flowcontrol/v1alpha1/defaults.go b/pkg/apis/flowcontrol/v1alpha1/defaults.go new file mode 100644 index 00000000000..68df0fa3880 --- /dev/null +++ b/pkg/apis/flowcontrol/v1alpha1/defaults.go @@ -0,0 +1,36 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + "k8s.io/api/flowcontrol/v1alpha1" + "k8s.io/kubernetes/pkg/apis/flowcontrol" +) + +// SetDefaults_FlowSchema sets default values for flow schema +func SetDefaults_FlowSchema(obj *v1alpha1.FlowSchema) { + if obj.Spec.MatchingPrecedence == 0 { + obj.Spec.MatchingPrecedence = flowcontrol.FlowSchemaDefaultMatchingPrecedence + } +} + +// SetDefaults_FlowSchema sets default values for flow schema +func SetDefaults_PriorityLevelConfiguration(obj *v1alpha1.PriorityLevelConfiguration) { + if obj.Spec.HandSize == 0 { + obj.Spec.HandSize = flowcontrol.PriorityLevelConfigurationDefaultHandSize + } +} diff --git a/pkg/apis/flowcontrol/validation/validation.go b/pkg/apis/flowcontrol/validation/validation.go new file mode 100644 index 00000000000..dbd042025e0 --- /dev/null +++ b/pkg/apis/flowcontrol/validation/validation.go @@ -0,0 +1,274 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package validation + +import ( + "fmt" + "math/big" + + "k8s.io/apimachinery/pkg/api/validation" + apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation/field" + apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" + "k8s.io/kubernetes/pkg/apis/flowcontrol" +) + +var ValidateFlowSchemaName = apimachineryvalidation.NameIsDNSSubdomain + +var ValidatePriorityLevelConfigurationName = apimachineryvalidation.NameIsDNSSubdomain + +var supportedDistinguisherMethods = sets.NewString( + string(flowcontrol.FlowDistinguisherMethodByNamespaceType), + string(flowcontrol.FlowDistinguisherMethodByUserType), +) + +var supportedVerbs = sets.NewString( + flowcontrol.VerbAll, + "get", + "list", + "watch", + "create", + "update", + "delete", + "deletecollection", + "patch", +) + +var supportedSubjectKinds = sets.NewString( + flowcontrol.ServiceAccountKind, + flowcontrol.UserKind, + flowcontrol.GroupKind, +) + +const ( + maxHashBits = 60 +) + +func ValidateFlowSchema(fs *flowcontrol.FlowSchema) field.ErrorList { + allErrs := apivalidation.ValidateObjectMeta(&fs.ObjectMeta, false, ValidateFlowSchemaName, field.NewPath("metadata")) + allErrs = append(allErrs, ValidateFlowSchemaSpec(&fs.Spec, field.NewPath("spec"))...) + allErrs = append(allErrs, ValidateFlowSchemaStatus(&fs.Status, field.NewPath("status"))...) + return allErrs +} + +func ValidateFlowSchemaSpec(spec *flowcontrol.FlowSchemaSpec, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + if spec.MatchingPrecedence < 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("matchingPrecedence"), spec.MatchingPrecedence, "must be positive value")) + } + if spec.DistinguisherMethod != nil { + if !supportedDistinguisherMethods.Has(string(spec.DistinguisherMethod.Type)) { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("distinguisherMethod").Child("type"), spec.DistinguisherMethod, supportedDistinguisherMethods.List())) + } + } + if len(spec.PriorityLevelConfiguration.Name) > 0 { + for _, msg := range ValidatePriorityLevelConfigurationName(spec.PriorityLevelConfiguration.Name, false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("priorityLevelConfiguration").Child("name"), spec.PriorityLevelConfiguration.Name, msg)) + } + } else { + allErrs = append(allErrs, field.Required(fldPath.Child("priorityLevelConfiguration").Child("name"), "must reference to a priority level")) + } + if len(spec.Rules) > 0 { + for i, rule := range spec.Rules { + allErrs = append(allErrs, ValidateFlowSchemaPolicyRuleWithSubjects(&rule, fldPath.Child("rules").Index(i))...) + } + } else { + allErrs = append(allErrs, field.Required(fldPath.Child("rules"), "rules must contain at least one value")) + } + return allErrs +} + +func ValidateFlowSchemaPolicyRuleWithSubjects(rule *flowcontrol.PolicyRuleWithSubjects, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + if len(rule.Subjects) > 0 { + for i, subject := range rule.Subjects { + allErrs = append(allErrs, ValidateFlowSchemaSubject(&subject, fldPath.Child("subjects").Index(i))...) + } + } else { + allErrs = append(allErrs, field.Required(fldPath.Child("subjects"), "subjects must contain at least one value")) + } + allErrs = append(allErrs, ValidateFlowSchemaPolicyRule(&rule.Rule, fldPath.Child("rule"))...) + return allErrs +} + +func ValidateFlowSchemaSubject(subject *flowcontrol.Subject, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + if len(subject.Name) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) + } + switch subject.Kind { + case flowcontrol.ServiceAccountKind: + if len(subject.Name) > 0 { + for _, msg := range validation.ValidateServiceAccountName(subject.Name, false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), subject.Name, msg)) + } + } + if len(subject.APIGroup) > 0 { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("apiGroup"), subject.APIGroup, []string{""})) + } + if len(subject.Namespace) > 0 { + for _, msg := range apimachineryvalidation.ValidateNamespaceName(subject.Namespace, false) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), subject.Namespace, msg)) + } + } else { + allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), "must specify namespace for service account")) + } + case flowcontrol.UserKind: + // TODO(ericchiang): What other restrictions on user name are there? + if subject.APIGroup != flowcontrol.GroupName { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("apiGroup"), subject.APIGroup, []string{flowcontrol.GroupName})) + } + if len(subject.Namespace) > 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), subject.Namespace, "must not set namespace for user-kind subject")) + } + case flowcontrol.GroupKind: + // TODO(ericchiang): What other restrictions on group name are there? + if subject.APIGroup != flowcontrol.GroupName { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("apiGroup"), subject.APIGroup, []string{flowcontrol.GroupName})) + } + if len(subject.Namespace) > 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), subject.Namespace, "must not set namespace for group-kind subject")) + } + default: + allErrs = append(allErrs, field.NotSupported(fldPath.Child("kind"), subject.Kind, supportedSubjectKinds.List())) + } + return allErrs +} + +func ValidateFlowSchemaPolicyRule(rule *flowcontrol.PolicyRule, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + + if len(rule.Verbs) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("verbs"), "verbs must contain at least one value")) + } + + if len(rule.NonResourceURLs) > 0 { + if len(rule.APIGroups) > 0 || len(rule.Resources) > 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("nonResourceURLs"), rule.NonResourceURLs, "rules cannot apply to both regular resources and non-resource URLs")) + } + if hasWildcard(rule.NonResourceURLs) && len(rule.NonResourceURLs) > 1 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("nonResourceURLs"), rule.NonResourceURLs, "if '*' is present, must not specify other non-resource URLs")) + } + return allErrs + } + if len(rule.APIGroups) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("apiGroups"), "resource rules must supply at least one api group")) + } + if len(rule.Resources) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("resources"), "resource rules must supply at least one resource")) + } + + if hasWildcard(rule.Verbs) && len(rule.Verbs) > 1 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("verbs"), rule.Verbs, "if '*' is present, must not specify other verbs")) + } + if hasWildcard(rule.APIGroups) && len(rule.APIGroups) > 1 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("apiGroups"), rule.APIGroups, "if '*' is present, must not specify other api groups")) + } + if hasWildcard(rule.Resources) && len(rule.Resources) > 1 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("resources"), rule.Resources, "if '*' is present, must not specify other resources")) + } + if !supportedVerbs.IsSuperset(sets.NewString(rule.Verbs...)) { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("verbs"), rule.Verbs, supportedVerbs.List())) + } + + return allErrs +} + +func ValidateFlowSchemaStatus(status *flowcontrol.FlowSchemaStatus, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + // conditions will not be validated + return allErrs +} + +func ValidatePriorityLevelConfiguration(pl *flowcontrol.PriorityLevelConfiguration) field.ErrorList { + allErrs := apivalidation.ValidateObjectMeta(&pl.ObjectMeta, false, ValidatePriorityLevelConfigurationName, field.NewPath("metadata")) + allErrs = append(allErrs, ValidatePriorityLevelConfigurationSpec(&pl.Spec, pl.Name, field.NewPath("spec"))...) + allErrs = append(allErrs, ValidatePriorityLevelConfigurationStatus(&pl.Status, field.NewPath("status"))...) + return allErrs +} + +func ValidatePriorityLevelConfigurationSpec(spec *flowcontrol.PriorityLevelConfigurationSpec, name string, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + if name != flowcontrol.PriorityLevelConfigurationNameSystemTop && spec.Exempt { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("exempt"), "must not be exempt")) + } + if name != flowcontrol.PriorityLevelConfigurationNameWorkloadLow && spec.GlobalDefault { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("globalDefault"), "must not be global default")) + } + if !spec.Exempt { + if spec.AssuredConcurrencyShares <= 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("assuredConcurrencyShares"), spec.AssuredConcurrencyShares, "must be positive")) + } + if spec.QueueLengthLimit <= 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("queueLengthLimit"), spec.QueueLengthLimit, "must be positive")) + } + if spec.Queues <= 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("queues"), spec.Queues, "must be positive")) + } + if spec.HandSize < 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("handSize"), spec.HandSize, "must be positive")) + } + } else { + if spec.AssuredConcurrencyShares != 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("assuredConcurrencyShares"), spec.AssuredConcurrencyShares, "must be positive for exempt priority")) + } + if spec.QueueLengthLimit != 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("queueLengthLimit"), spec.QueueLengthLimit, "must be positive for exempt priority")) + } + if spec.Queues != 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("queues"), spec.Queues, "must be positive for exempt priority")) + } + if spec.HandSize != 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("handSize"), spec.HandSize, "must be positive for exempt priority")) + } + } + if spec.HandSize > spec.Queues { + allErrs = append(allErrs, field.Invalid(fldPath.Child("handSize"), spec.HandSize, + fmt.Sprintf("should not be greater than queues (%d)", spec.Queues))) + } + if !validateShuffleShardingParameters(spec.HandSize, spec.Queues) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("handSize"), spec.HandSize, + fmt.Sprintf("falling factorial of handSize (%d) and queues (%d) exceeds %d bits", spec.HandSize, spec.Queues, maxHashBits))) + } + return allErrs +} + +func ValidatePriorityLevelConfigurationStatus(status *flowcontrol.PriorityLevelConfigurationStatus, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + // conditions will not be validated + return allErrs +} + +func validateShuffleShardingParameters(handSize, queues int32) bool { + // TODO: performance impact from bit-int multiplication? + v := big.NewInt(1) + for i := int32(0); i < handSize; i++ { + v.Mul(v, big.NewInt(int64(queues-i))) + } + l := v.BitLen() + return l <= maxHashBits +} + +func hasWildcard(operations []string) bool { + for _, o := range operations { + if o == "*" { + return true + } + } + return false +} diff --git a/pkg/apis/flowcontrol/validation/validation_test.go b/pkg/apis/flowcontrol/validation/validation_test.go new file mode 100644 index 00000000000..49c42bc66a1 --- /dev/null +++ b/pkg/apis/flowcontrol/validation/validation_test.go @@ -0,0 +1,566 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package validation + +import ( + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/kubernetes/pkg/apis/flowcontrol" + "math" + "testing" +) + +func TestFlowSchemaValidation(t *testing.T) { + testCases := []struct { + name string + flowSchema *flowcontrol.FlowSchema + expectedErrors field.ErrorList + }{ + { + name: "empty spec should fail", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.FlowSchemaSpec{}, + }, + expectedErrors: field.ErrorList{ + field.Required(field.NewPath("spec").Child("priorityLevelConfiguration").Child("name"), "must reference to a priority level"), + field.Required(field.NewPath("spec").Child("rules"), "rules must contain at least one value"), + }, + }, + { + name: "missing policy-rule should fail", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.FlowSchemaSpec{ + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", + }, + Rules: []flowcontrol.PolicyRuleWithSubjects{ + { + Subjects: []flowcontrol.Subject{ + { + Kind: flowcontrol.UserKind, + APIGroup: flowcontrol.GroupName, + Name: "noxu", + }, + }, + }, + }, + }, + }, + expectedErrors: field.ErrorList{ + field.Required(field.NewPath("spec").Child("rules").Index(0).Child("rule").Child("verbs"), "verbs must contain at least one value"), + field.Required(field.NewPath("spec").Child("rules").Index(0).Child("rule").Child("apiGroups"), "resource rules must supply at least one api group"), + field.Required(field.NewPath("spec").Child("rules").Index(0).Child("rule").Child("resources"), "resource rules must supply at least one resource"), + }, + }, + { + name: "normal flow-schema w/ * verbs/apiGroups/resources should work", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.FlowSchemaSpec{ + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", + }, + Rules: []flowcontrol.PolicyRuleWithSubjects{ + { + Subjects: []flowcontrol.Subject{ + { + Kind: flowcontrol.UserKind, + APIGroup: flowcontrol.GroupName, + Name: "noxu", + }, + }, + Rule: flowcontrol.PolicyRule{ + Verbs: []string{flowcontrol.VerbAll}, + APIGroups: []string{flowcontrol.APIGroupAll}, + Resources: []string{flowcontrol.ResourceAll}, + }, + }, + }, + }, + }, + expectedErrors: field.ErrorList{}, + }, + { + name: "flow-schema mixes * verbs/apiGroups/resources should fail", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.FlowSchemaSpec{ + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", + }, + Rules: []flowcontrol.PolicyRuleWithSubjects{ + { + Subjects: []flowcontrol.Subject{ + { + Kind: flowcontrol.UserKind, + APIGroup: flowcontrol.GroupName, + Name: "noxu", + }, + }, + Rule: flowcontrol.PolicyRule{ + Verbs: []string{flowcontrol.VerbAll, "create"}, + APIGroups: []string{flowcontrol.APIGroupAll, "tak"}, + Resources: []string{flowcontrol.ResourceAll, "tok"}, + }, + }, + }, + }, + }, + expectedErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("rule").Child("verbs"), []string{"*", "create"}, "if '*' is present, must not specify other verbs"), + field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("rule").Child("apiGroups"), []string{"*", "tak"}, "if '*' is present, must not specify other api groups"), + field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("rule").Child("resources"), []string{"*", "tok"}, "if '*' is present, must not specify other resources"), + }, + }, + { + name: "flow-schema mixes non-resource URLs w/ regular resources should fail", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.FlowSchemaSpec{ + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", + }, + Rules: []flowcontrol.PolicyRuleWithSubjects{ + { + Subjects: []flowcontrol.Subject{ + { + Kind: flowcontrol.UserKind, + APIGroup: flowcontrol.GroupName, + Name: "noxu", + }, + }, + Rule: flowcontrol.PolicyRule{ + Verbs: []string{flowcontrol.VerbAll, "create"}, + APIGroups: []string{flowcontrol.APIGroupAll, "tak"}, + Resources: []string{flowcontrol.ResourceAll, "tok"}, + NonResourceURLs: []string{flowcontrol.NonResourceAll}, + }, + }, + }, + }, + }, + expectedErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("rule").Child("nonResourceURLs"), []string{"*"}, "rules cannot apply to both regular resources and non-resource URLs"), + }, + }, + { + name: "flow-schema mixes * non-resource URLs should fail", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.FlowSchemaSpec{ + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", + }, + Rules: []flowcontrol.PolicyRuleWithSubjects{ + { + Subjects: []flowcontrol.Subject{ + { + Kind: flowcontrol.UserKind, + APIGroup: flowcontrol.GroupName, + Name: "noxu", + }, + }, + Rule: flowcontrol.PolicyRule{ + Verbs: []string{"*"}, + NonResourceURLs: []string{flowcontrol.NonResourceAll, "tik"}, + }, + }, + }, + }, + }, + expectedErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("rule").Child("nonResourceURLs"), []string{"*", "tik"}, "if '*' is present, must not specify other non-resource URLs"), + }, + }, + { + name: "normal flow-schema mixes user should fail", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.FlowSchemaSpec{ + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", + }, + Rules: []flowcontrol.PolicyRuleWithSubjects{ + { + Subjects: []flowcontrol.Subject{ + { + Kind: flowcontrol.UserKind, + APIGroup: flowcontrol.GroupName, + Name: "noxu", + }, + }, + Rule: flowcontrol.PolicyRule{ + Verbs: []string{"*"}, + NonResourceURLs: []string{flowcontrol.NonResourceAll, "tik"}, + }, + }, + }, + }, + }, + expectedErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("rule").Child("nonResourceURLs"), []string{"*", "tik"}, "if '*' is present, must not specify other non-resource URLs"), + }, + }, + { + name: "flow-schema w/ invalid verb should fail", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.FlowSchemaSpec{ + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", + }, + Rules: []flowcontrol.PolicyRuleWithSubjects{ + { + Subjects: []flowcontrol.Subject{ + { + Kind: flowcontrol.UserKind, + APIGroup: flowcontrol.GroupName, + Name: "noxu", + }, + }, + Rule: flowcontrol.PolicyRule{ + Verbs: []string{"feed"}, + APIGroups: []string{flowcontrol.APIGroupAll}, + Resources: []string{flowcontrol.ResourceAll}, + }, + }, + }, + }, + }, + expectedErrors: field.ErrorList{ + field.NotSupported(field.NewPath("spec").Child("rules").Index(0).Child("rule").Child("verbs"), []string{"feed"}, supportedVerbs.List()), + }, + }, + { + name: "flow-schema w/ invalid priority level configuration name should fail", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.FlowSchemaSpec{ + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system+++$$", + }, + Rules: []flowcontrol.PolicyRuleWithSubjects{ + { + Subjects: []flowcontrol.Subject{ + { + Kind: flowcontrol.UserKind, + APIGroup: flowcontrol.GroupName, + Name: "noxu", + }, + }, + Rule: flowcontrol.PolicyRule{ + Verbs: []string{flowcontrol.VerbAll}, + APIGroups: []string{flowcontrol.APIGroupAll}, + Resources: []string{flowcontrol.ResourceAll}, + }, + }, + }, + }, + }, + expectedErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec").Child("priorityLevelConfiguration").Child("name"), "system+++$$", `a DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`), + }, + }, + { + name: "flow-schema w/ service-account kind missing namespace should fail", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.FlowSchemaSpec{ + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", + }, + Rules: []flowcontrol.PolicyRuleWithSubjects{ + { + Subjects: []flowcontrol.Subject{ + { + Kind: flowcontrol.ServiceAccountKind, + Name: "noxu", + }, + }, + Rule: flowcontrol.PolicyRule{ + Verbs: []string{flowcontrol.VerbAll}, + APIGroups: []string{flowcontrol.APIGroupAll}, + Resources: []string{flowcontrol.ResourceAll}, + }, + }, + }, + }, + }, + expectedErrors: field.ErrorList{ + field.Required(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("namespace"), "must specify namespace for service account"), + }, + }, + { + name: "flow-schema missing kind should fail", + flowSchema: &flowcontrol.FlowSchema{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.FlowSchemaSpec{ + PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ + Name: "system-bar", + }, + Rules: []flowcontrol.PolicyRuleWithSubjects{ + { + Subjects: []flowcontrol.Subject{ + { + Kind: "", + }, + }, + Rule: flowcontrol.PolicyRule{ + Verbs: []string{flowcontrol.VerbAll}, + APIGroups: []string{flowcontrol.APIGroupAll}, + Resources: []string{flowcontrol.ResourceAll}, + }, + }, + }, + }, + }, + expectedErrors: field.ErrorList{ + field.Required(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("name"), ""), + field.NotSupported(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("kind"), "", supportedSubjectKinds.List()), + }, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + errs := ValidateFlowSchema(testCase.flowSchema) + if !assert.ElementsMatch(t, testCase.expectedErrors, errs) { + t.Logf("mismatch: %v", cmp.Diff(testCase.expectedErrors, errs)) + } + }) + } +} + +func TestPriorityLevelConfigurationValidation(t *testing.T) { + testCases := []struct { + name string + priorityLevelConfiguration *flowcontrol.PriorityLevelConfiguration + expectedErrors field.ErrorList + }{ + { + name: "normal customized priority level should work", + priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + GlobalDefault: false, + Exempt: false, + AssuredConcurrencyShares: 100, + Queues: 512, + HandSize: 4, + QueueLengthLimit: 100, + }, + }, + expectedErrors: field.ErrorList{}, + }, + { + name: "customized priority level w/ global-default should fail", + priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + GlobalDefault: true, + Exempt: false, + AssuredConcurrencyShares: 100, + Queues: 512, + HandSize: 4, + QueueLengthLimit: 100, + }, + }, + expectedErrors: field.ErrorList{ + field.Forbidden(field.NewPath("spec").Child("globalDefault"), "must not be global default"), + }, + }, + { + name: "system top priority level w/ global-default should work", + priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: flowcontrol.PriorityLevelConfigurationNameWorkloadLow, + }, + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + GlobalDefault: true, + Exempt: false, + AssuredConcurrencyShares: 100, + Queues: 512, + HandSize: 4, + QueueLengthLimit: 100, + }, + }, + expectedErrors: field.ErrorList{}, + }, + { + name: "customized priority level w/ exempt should fail", + priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + GlobalDefault: false, + Exempt: true, + }, + }, + expectedErrors: field.ErrorList{ + field.Forbidden(field.NewPath("spec").Child("exempt"), "must not be exempt"), + }, + }, + { + name: "system low priority level w/ exempt should work", + priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: flowcontrol.PriorityLevelConfigurationNameSystemTop, + }, + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + GlobalDefault: false, + Exempt: true, + }, + }, + expectedErrors: field.ErrorList{}, + }, + { + name: "customized priority level w/ empty spec should fail", + priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.PriorityLevelConfigurationSpec{}, + }, + expectedErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec").Child("assuredConcurrencyShares"), int32(0), "must be positive"), + field.Invalid(field.NewPath("spec").Child("queueLengthLimit"), int32(0), "must be positive"), + field.Invalid(field.NewPath("spec").Child("queues"), int32(0), "must be positive"), + }, + }, + { + name: "customized priority level w/ overflowing handSize/queues should fail 1", + priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + AssuredConcurrencyShares: 100, + QueueLengthLimit: 100, + Queues: 512, + HandSize: 8, + }, + }, + expectedErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec").Child("handSize"), int32(8), "falling factorial of handSize (8) and queues (512) exceeds 60 bits"), + }, + }, + { + name: "customized priority level w/ overflowing handSize/queues should fail 2", + priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + AssuredConcurrencyShares: 100, + QueueLengthLimit: 100, + Queues: 128, + HandSize: 10, + }, + }, + expectedErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec").Child("handSize"), int32(10), "falling factorial of handSize (10) and queues (128) exceeds 60 bits"), + }, + }, + { + name: "customized priority level w/ overflowing handSize/queues should fail 3", + priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + AssuredConcurrencyShares: 100, + QueueLengthLimit: 100, + Queues: math.MaxInt32, + HandSize: 3, + }, + }, + expectedErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec").Child("handSize"), int32(3), "falling factorial of handSize (3) and queues (2147483647) exceeds 60 bits"), + }, + }, + { + name: "customized priority level w/ handSize=2 and queues=max-int32 should work", + priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + AssuredConcurrencyShares: 100, + QueueLengthLimit: 100, + Queues: 1<<30 - 1, // max integer in 30 bits + HandSize: 2, + }, + }, + expectedErrors: field.ErrorList{}, + }, + { + name: "customized priority level w/ handSize greater than queues should fail", + priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "system-foo", + }, + Spec: flowcontrol.PriorityLevelConfigurationSpec{ + AssuredConcurrencyShares: 100, + QueueLengthLimit: 100, + Queues: 7, + HandSize: 8, + }, + }, + expectedErrors: field.ErrorList{ + field.Invalid(field.NewPath("spec").Child("handSize"), int32(8), "should not be greater than queues (7)"), + }, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + errs := ValidatePriorityLevelConfiguration(testCase.priorityLevelConfiguration) + if !assert.ElementsMatch(t, testCase.expectedErrors, errs) { + t.Logf("mismatch: %v", cmp.Diff(testCase.expectedErrors, errs)) + } + }) + } +} diff --git a/pkg/registry/flowcontrol/flowschema/doc.go b/pkg/registry/flowcontrol/flowschema/doc.go new file mode 100644 index 00000000000..074b2aecd94 --- /dev/null +++ b/pkg/registry/flowcontrol/flowschema/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package flowschema provides model implementation of flow-schema api +package flowschema // import "k8s.io/kubernetes/pkg/registry/flowcontrol/flowschema" diff --git a/pkg/registry/flowcontrol/flowschema/storage/storage.go b/pkg/registry/flowcontrol/flowschema/storage/storage.go new file mode 100644 index 00000000000..4a078c8799d --- /dev/null +++ b/pkg/registry/flowcontrol/flowschema/storage/storage.go @@ -0,0 +1,113 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package storage + +import ( + "context" + "errors" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/generic" + genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" + "k8s.io/apiserver/pkg/registry/rest" + "k8s.io/kubernetes/pkg/apis/flowcontrol" + flowcontrolbootstrap "k8s.io/kubernetes/pkg/apis/flowcontrol/bootstrap" + "k8s.io/kubernetes/pkg/printers" + printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" + printerstorage "k8s.io/kubernetes/pkg/printers/storage" + "k8s.io/kubernetes/pkg/registry/flowcontrol/flowschema" +) + +// Container includes dummy storage for RC pods and experimental storage for Scale. +type FlowSchemaStorage struct { + FlowSchema *REST + Status *StatusREST +} + +func NewStorage(optsGetter generic.RESTOptionsGetter) FlowSchemaStorage { + flowSchemaREST, flowSchemaStatusREST := NewREST(optsGetter) + + return FlowSchemaStorage{ + FlowSchema: flowSchemaREST, + Status: flowSchemaStatusREST, + } +} + +// REST implements a RESTStorage for jobs against etcd +type REST struct { + *genericregistry.Store +} + +// NewREST returns a RESTStorage object that will work against Jobs. +func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST) { + store := &genericregistry.Store{ + NewFunc: func() runtime.Object { return &flowcontrol.FlowSchema{} }, + NewListFunc: func() runtime.Object { return &flowcontrol.FlowSchema{} }, + DefaultQualifiedResource: flowcontrol.Resource("flowschemas"), + + CreateStrategy: flowschema.Strategy, + UpdateStrategy: flowschema.Strategy, + DeleteStrategy: flowschema.Strategy, + + // TODO: develop printer handler for flowschema resource + TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)}, + } + options := &generic.StoreOptions{RESTOptions: optsGetter} + if err := store.CompleteWithOptions(options); err != nil { + panic(err) // TODO: Propagate error up + } + + statusStore := *store + statusStore.UpdateStrategy = flowschema.StatusStrategy + + return &REST{store}, &StatusREST{store: &statusStore} +} + +// Delete ensures that system priority classes are not deleted. +func (r *REST) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { + for _, pl := range flowcontrolbootstrap.SystemPriorityLevelConfigurations() { + if name == pl.Name { + return nil, false, apierrors.NewForbidden(flowcontrol.Resource("flowschemas"), pl.Name, errors.New("this is a system flow schema and cannot be deleted")) + } + } + + return r.Store.Delete(ctx, name, deleteValidation, options) +} + +// StatusREST implements the REST endpoint for changing the status of a resourcequota. +type StatusREST struct { + store *genericregistry.Store +} + +// New creates a new Job object. +func (r *StatusREST) New() runtime.Object { + return &flowcontrol.FlowSchema{} +} + +// Get retrieves the object from the storage. It is required to support Patch. +func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + return r.store.Get(ctx, name, options) +} + +// Update alters the status subset of an object. +func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { + // We are explicitly setting forceAllowCreate to false in the call to the underlying storage because + // subresources should never allow create on update. + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options) +} diff --git a/pkg/registry/flowcontrol/flowschema/strategy.go b/pkg/registry/flowcontrol/flowschema/strategy.go new file mode 100644 index 00000000000..d75f517d8cb --- /dev/null +++ b/pkg/registry/flowcontrol/flowschema/strategy.go @@ -0,0 +1,101 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package flowschema + +import ( + "context" + + apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/storage/names" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/apis/flowcontrol" + "k8s.io/kubernetes/pkg/apis/flowcontrol/validation" +) + +// flowSchemaStrategy implements verification logic for FlowSchema. +type flowSchemaStrategy struct { + runtime.ObjectTyper + names.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating Replication Controller objects. +var Strategy = flowSchemaStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} + +// NamespaceScoped returns false because all PriorityClasses are global. +func (flowSchemaStrategy) NamespaceScoped() bool { + return false +} + +// PrepareForCreate clears the status of a flow-schema before creation. +func (flowSchemaStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) { + fl := obj.(*flowcontrol.FlowSchema) + fl.Status = flowcontrol.FlowSchemaStatus{} + fl.Generation = 1 +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (flowSchemaStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { + newFlowSchema := obj.(*flowcontrol.FlowSchema) + oldFlowSchema := old.(*flowcontrol.FlowSchema) + + // Spec updates bump the generation so that we can distinguish between status updates. + if !apiequality.Semantic.DeepEqual(newFlowSchema.Spec, oldFlowSchema.Spec) { + newFlowSchema.Generation = oldFlowSchema.Generation + 1 + } + newFlowSchema.Status = oldFlowSchema.Status +} + +// Validate validates a new flow-schema. +func (flowSchemaStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { + return validation.ValidateFlowSchema(obj.(*flowcontrol.FlowSchema)) +} + +// Canonicalize normalizes the object after validation. +func (flowSchemaStrategy) Canonicalize(obj runtime.Object) { +} + +func (flowSchemaStrategy) AllowUnconditionalUpdate() bool { + return true +} + +// AllowCreateOnUpdate is false for flow-schemas; this means a POST is needed to create one. +func (flowSchemaStrategy) AllowCreateOnUpdate() bool { + return false +} + +// ValidateUpdate is the default update validation for an end user. +func (flowSchemaStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateFlowSchema(obj.(*flowcontrol.FlowSchema)) +} + +type flowSchemaStatusStrategy struct { + flowSchemaStrategy +} + +var StatusStrategy = flowSchemaStatusStrategy{Strategy} + +func (flowSchemaStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { + newFlowSchema := obj.(*flowcontrol.FlowSchema) + oldFlowSchema := old.(*flowcontrol.FlowSchema) + newFlowSchema.Spec = oldFlowSchema.Spec +} + +func (flowSchemaStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateFlowSchema(obj.(*flowcontrol.FlowSchema)) +} diff --git a/pkg/registry/flowcontrol/prioritylevelconfiguration/doc.go b/pkg/registry/flowcontrol/prioritylevelconfiguration/doc.go new file mode 100644 index 00000000000..57c8c47a9ea --- /dev/null +++ b/pkg/registry/flowcontrol/prioritylevelconfiguration/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package prioritylevelconfiguration provides model implementation of priority-level-configuration api +package prioritylevelconfiguration // import "k8s.io/kubernetes/pkg/registry/flowcontrol/prioritylevelconfiguration" diff --git a/pkg/registry/flowcontrol/prioritylevelconfiguration/storage/storage.go b/pkg/registry/flowcontrol/prioritylevelconfiguration/storage/storage.go new file mode 100644 index 00000000000..de201db93de --- /dev/null +++ b/pkg/registry/flowcontrol/prioritylevelconfiguration/storage/storage.go @@ -0,0 +1,114 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package storage + +import ( + "context" + "errors" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/generic" + genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" + "k8s.io/apiserver/pkg/registry/rest" + "k8s.io/kubernetes/pkg/apis/flowcontrol" + flowcontrolbootstrap "k8s.io/kubernetes/pkg/apis/flowcontrol/bootstrap" + "k8s.io/kubernetes/pkg/printers" + printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" + printerstorage "k8s.io/kubernetes/pkg/printers/storage" + "k8s.io/kubernetes/pkg/registry/flowcontrol/flowschema" + "k8s.io/kubernetes/pkg/registry/flowcontrol/prioritylevelconfiguration" +) + +// Container includes dummy storage for RC pods and experimental storage for Scale. +type PriorityLevelConfigurationStorage struct { + PriorityLevelConfiguration *REST + Status *StatusREST +} + +func NewStorage(optsGetter generic.RESTOptionsGetter) PriorityLevelConfigurationStorage { + priorityLevelConfigurationREST, priorityLevelConfigurationStatusREST := NewREST(optsGetter) + + return PriorityLevelConfigurationStorage{ + PriorityLevelConfiguration: priorityLevelConfigurationREST, + Status: priorityLevelConfigurationStatusREST, + } +} + +// REST implements a RESTStorage for jobs against etcd +type REST struct { + *genericregistry.Store +} + +// NewREST returns a RESTStorage object that will work against Jobs. +func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST) { + store := &genericregistry.Store{ + NewFunc: func() runtime.Object { return &flowcontrol.PriorityLevelConfiguration{} }, + NewListFunc: func() runtime.Object { return &flowcontrol.PriorityLevelConfiguration{} }, + DefaultQualifiedResource: flowcontrol.Resource("prioritylevelconfigurations"), + + CreateStrategy: prioritylevelconfiguration.Strategy, + UpdateStrategy: prioritylevelconfiguration.Strategy, + DeleteStrategy: prioritylevelconfiguration.Strategy, + + // TODO: develop printer handler for prioritylevelconfiguration resource + TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)}, + } + options := &generic.StoreOptions{RESTOptions: optsGetter} + if err := store.CompleteWithOptions(options); err != nil { + panic(err) // TODO: Propagate error up + } + + statusStore := *store + statusStore.UpdateStrategy = flowschema.StatusStrategy + + return &REST{store}, &StatusREST{store: &statusStore} +} + +// Delete ensures that system priority classes are not deleted. +func (r *REST) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { + for _, pl := range flowcontrolbootstrap.SystemPriorityLevelConfigurations() { + if name == pl.Name { + return nil, false, apierrors.NewForbidden(flowcontrol.Resource("prioritylevelconfigurations"), pl.Name, errors.New("this is a system priority level and cannot be deleted")) + } + } + + return r.Store.Delete(ctx, name, deleteValidation, options) +} + +// StatusREST implements the REST endpoint for changing the status of a resourcequota. +type StatusREST struct { + store *genericregistry.Store +} + +// New creates a new Job object. +func (r *StatusREST) New() runtime.Object { + return &flowcontrol.FlowSchema{} +} + +// Get retrieves the object from the storage. It is required to support Patch. +func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + return r.store.Get(ctx, name, options) +} + +// Update alters the status subset of an object. +func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { + // We are explicitly setting forceAllowCreate to false in the call to the underlying storage because + // subresources should never allow create on update. + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options) +} diff --git a/pkg/registry/flowcontrol/prioritylevelconfiguration/strategy.go b/pkg/registry/flowcontrol/prioritylevelconfiguration/strategy.go new file mode 100644 index 00000000000..48506cf330d --- /dev/null +++ b/pkg/registry/flowcontrol/prioritylevelconfiguration/strategy.go @@ -0,0 +1,101 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package prioritylevelconfiguration + +import ( + "context" + + apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/storage/names" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/apis/flowcontrol" + "k8s.io/kubernetes/pkg/apis/flowcontrol/validation" +) + +// flowSchemaStrategy implements verification logic for FlowSchema. +type priorityLevelConfigurationStrategy struct { + runtime.ObjectTyper + names.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating Replication Controller objects. +var Strategy = priorityLevelConfigurationStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} + +// NamespaceScoped returns false because all PriorityClasses are global. +func (priorityLevelConfigurationStrategy) NamespaceScoped() bool { + return false +} + +// PrepareForCreate clears the status of a priority-level-configuration before creation. +func (priorityLevelConfigurationStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) { + pl := obj.(*flowcontrol.PriorityLevelConfiguration) + pl.Status = flowcontrol.PriorityLevelConfigurationStatus{} + pl.Generation = 1 +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (priorityLevelConfigurationStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { + newPriorityLevelConfiguration := obj.(*flowcontrol.PriorityLevelConfiguration) + oldPriorityLevelConfiguration := old.(*flowcontrol.PriorityLevelConfiguration) + + // Spec updates bump the generation so that we can distinguish between status updates. + if !apiequality.Semantic.DeepEqual(newPriorityLevelConfiguration.Spec, oldPriorityLevelConfiguration.Spec) { + newPriorityLevelConfiguration.Generation = oldPriorityLevelConfiguration.Generation + 1 + } + newPriorityLevelConfiguration.Status = oldPriorityLevelConfiguration.Status +} + +// Validate validates a new flow-schema. +func (priorityLevelConfigurationStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { + return validation.ValidatePriorityLevelConfiguration(obj.(*flowcontrol.PriorityLevelConfiguration)) +} + +// Canonicalize normalizes the object after validation. +func (priorityLevelConfigurationStrategy) Canonicalize(obj runtime.Object) { +} + +func (priorityLevelConfigurationStrategy) AllowUnconditionalUpdate() bool { + return true +} + +// AllowCreateOnUpdate is false for priority-level-configurations; this means a POST is needed to create one. +func (priorityLevelConfigurationStrategy) AllowCreateOnUpdate() bool { + return false +} + +// ValidateUpdate is the default update validation for an end user. +func (priorityLevelConfigurationStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidatePriorityLevelConfiguration(obj.(*flowcontrol.PriorityLevelConfiguration)) +} + +type priorityLevelConfigurationStatusStrategy struct { + priorityLevelConfigurationStrategy +} + +var StatusStrategy = priorityLevelConfigurationStatusStrategy{Strategy} + +func (priorityLevelConfigurationStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { + newPriorityLevelConfiguration := obj.(*flowcontrol.PriorityLevelConfiguration) + oldPriorityLevelConfiguration := old.(*flowcontrol.PriorityLevelConfiguration) + newPriorityLevelConfiguration.Spec = oldPriorityLevelConfiguration.Spec +} + +func (priorityLevelConfigurationStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidatePriorityLevelConfiguration(obj.(*flowcontrol.PriorityLevelConfiguration)) +} diff --git a/pkg/registry/flowcontrol/rest/storage_flowcontrol.go b/pkg/registry/flowcontrol/rest/storage_flowcontrol.go new file mode 100644 index 00000000000..8e4aeb6ad84 --- /dev/null +++ b/pkg/registry/flowcontrol/rest/storage_flowcontrol.go @@ -0,0 +1,148 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rest + +import ( + "fmt" + "time" + + flowcontrolv1alpha1 "k8s.io/api/flowcontrol/v1alpha1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apiserver/pkg/registry/generic" + "k8s.io/apiserver/pkg/registry/rest" + genericapiserver "k8s.io/apiserver/pkg/server" + serverstorage "k8s.io/apiserver/pkg/server/storage" + flowcontrolclient "k8s.io/client-go/kubernetes/typed/flowcontrol/v1alpha1" + "k8s.io/klog" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/apis/flowcontrol" + "k8s.io/kubernetes/pkg/apis/flowcontrol/bootstrap" + flowschemastore "k8s.io/kubernetes/pkg/registry/flowcontrol/flowschema/storage" + prioritylevelconfigurationstore "k8s.io/kubernetes/pkg/registry/flowcontrol/prioritylevelconfiguration/storage" +) + +const PostStartHookName = "apiserver/bootstrap-system-flowcontrol-configuration" + +type RESTStorageProvider struct{} + +var _ genericapiserver.PostStartHookProvider = RESTStorageProvider{} + +func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) { + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(flowcontrol.GroupName, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs) + + if apiResourceConfigSource.VersionEnabled(flowcontrolv1alpha1.SchemeGroupVersion) { + apiGroupInfo.VersionedResourcesStorageMap[flowcontrolv1alpha1.SchemeGroupVersion.Version] = p.v1alpha1Storage(apiResourceConfigSource, restOptionsGetter) + } + return apiGroupInfo, true +} + +func (p RESTStorageProvider) v1alpha1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) map[string]rest.Storage { + storage := map[string]rest.Storage{} + + // flow-schema + flowSchemaStorage, flowSchemaStatusStorage := flowschemastore.NewREST(restOptionsGetter) + storage["flowschemas"] = flowSchemaStorage + storage["flowschemas/status"] = flowSchemaStatusStorage + + // priority-level-configuration + priorityLevelConfigurationStorage, priorityLevelConfigurationStatusStorage := prioritylevelconfigurationstore.NewREST(restOptionsGetter) + storage["prioritylevelconfigurations"] = priorityLevelConfigurationStorage + storage["prioritylevelconfigurations/status"] = priorityLevelConfigurationStatusStorage + + return storage +} + +func (p RESTStorageProvider) PostStartHook() (string, genericapiserver.PostStartHookFunc, error) { + systemPreset := SystemPresetData{ + FlowSchemas: bootstrap.SystemFlowSchemas(), + PriorityLevelConfigurations: bootstrap.SystemPriorityLevelConfigurations(), + } + // TODO: default flow-schemas and priority levels + return PostStartHookName, systemPreset.EnsureSystemPresetConfiguration(), nil +} + +type SystemPresetData struct { + FlowSchemas []*flowcontrolv1alpha1.FlowSchema + PriorityLevelConfigurations []*flowcontrolv1alpha1.PriorityLevelConfiguration +} + +func (d SystemPresetData) EnsureSystemPresetConfiguration() genericapiserver.PostStartHookFunc { + return func(hookContext genericapiserver.PostStartHookContext) error { + flowcontrolClientSet := flowcontrolclient.NewForConfigOrDie(hookContext.LoopbackClientConfig) + // Adding system priority classes is important. If they fail to add, many critical system + // components may fail and cluster may break. + err := wait.Poll(1*time.Second, 30*time.Second, func() (done bool, err error) { + if err != nil { + utilruntime.HandleError(fmt.Errorf("unable to initialize client: %v", err)) + return false, nil + } + + for _, flowSchema := range d.FlowSchemas { + _, err := flowcontrolClientSet.FlowSchemas().Get(flowSchema.Name, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + _, err := flowcontrolClientSet.FlowSchemas().Create(flowSchema) + if err != nil && !apierrors.IsAlreadyExists(err) { + return false, err + } else if err == nil { + klog.V(6).Infof("created system preset FlowSchema %s", flowSchema.Name) + } else { + klog.V(6).Infof("system preset FlowSchema %s already exists, skipping creating", flowSchema.Name) + } + } else { + // Unable to get the priority class for reasons other than "not found". + klog.Warningf("unable to get FlowSchema %v: %v. Retrying...", flowSchema.Name, err) + return false, nil + } + } + } + for _, priorityLevelConfiguration := range d.PriorityLevelConfigurations { + _, err := flowcontrolClientSet.PriorityLevelConfigurations().Get(priorityLevelConfiguration.Name, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + _, err := flowcontrolClientSet.PriorityLevelConfigurations().Create(priorityLevelConfiguration) + if err != nil && !apierrors.IsAlreadyExists(err) { + return false, err + } else if err == nil { + klog.V(6).Infof("created system preset PriorityLevelConfiguration %s", priorityLevelConfiguration.Name) + } else { + klog.V(6).Infof("system preset PriorityLevelConfiguration %s already exists, skipping creating", priorityLevelConfiguration.Name) + } + } else if err == nil { + // Unable to get the priority class for reasons other than "not found". + klog.Warningf("unable to get PriorityLevelConfiguration %v: %v. Retrying...", priorityLevelConfiguration.Name, err) + return false, nil + } + } + } + klog.V(4).Infof("all system flow-control settings are created successfully or already exist.") + return true, nil + }) + // if we're never able to make it through initialization, kill the API server. + if err != nil { + return fmt.Errorf("unable to add default system flow-control settings: %v", err) + } + return nil + } +} + +func (p RESTStorageProvider) GroupName() string { + return flowcontrol.GroupName +}