initial REST implementation

fmt

system

code

review: addressing review commits

doc

default
This commit is contained in:
yue9944882
2019-07-16 22:51:27 +08:00
committed by Mike Spreitzer
parent 3926050733
commit 17c3da0b06
14 changed files with 1561 additions and 0 deletions

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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))
}

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -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))
}
})
}
}

View File

@@ -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"

View File

@@ -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)
}

View File

@@ -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))
}

View File

@@ -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"

View File

@@ -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)
}

View File

@@ -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))
}

View File

@@ -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
}