Skip to content

Commit c1b696d

Browse files
authored
Merge pull request kubernetes#87313 from MikeSpreitzer/apf-validation
Update validation for API Priority and Fairness
2 parents 66178b9 + 47ad6db commit c1b696d

File tree

10 files changed

+678
-17
lines changed

10 files changed

+678
-17
lines changed

pkg/apis/flowcontrol/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ filegroup(
2929
srcs = [
3030
":package-srcs",
3131
"//pkg/apis/flowcontrol/install:all-srcs",
32+
"//pkg/apis/flowcontrol/internalbootstrap:all-srcs",
3233
"//pkg/apis/flowcontrol/util:all-srcs",
3334
"//pkg/apis/flowcontrol/v1alpha1:all-srcs",
3435
"//pkg/apis/flowcontrol/validation:all-srcs",
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
2+
3+
go_library(
4+
name = "go_default_library",
5+
srcs = ["default-internal.go"],
6+
importpath = "k8s.io/kubernetes/pkg/apis/flowcontrol/internalbootstrap",
7+
visibility = ["//visibility:public"],
8+
deps = [
9+
"//pkg/apis/flowcontrol:go_default_library",
10+
"//pkg/apis/flowcontrol/install:go_default_library",
11+
"//staging/src/k8s.io/api/flowcontrol/v1alpha1:go_default_library",
12+
"//staging/src/k8s.io/apimachinery/pkg/conversion:go_default_library",
13+
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
14+
"//staging/src/k8s.io/apiserver/pkg/apis/flowcontrol/bootstrap:go_default_library",
15+
],
16+
)
17+
18+
filegroup(
19+
name = "package-srcs",
20+
srcs = glob(["**"]),
21+
tags = ["automanaged"],
22+
visibility = ["//visibility:private"],
23+
)
24+
25+
filegroup(
26+
name = "all-srcs",
27+
srcs = [":package-srcs"],
28+
tags = ["automanaged"],
29+
visibility = ["//visibility:public"],
30+
)
31+
32+
go_test(
33+
name = "go_default_test",
34+
srcs = ["defaults_test.go"],
35+
embed = [":go_default_library"],
36+
deps = [
37+
"//staging/src/k8s.io/api/flowcontrol/v1alpha1:go_default_library",
38+
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
39+
"//staging/src/k8s.io/apiserver/pkg/apis/flowcontrol/bootstrap:go_default_library",
40+
],
41+
)
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
Copyright 2019 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package internalbootstrap
18+
19+
import (
20+
fcv1a1 "k8s.io/api/flowcontrol/v1alpha1"
21+
"k8s.io/apimachinery/pkg/conversion"
22+
"k8s.io/apimachinery/pkg/runtime"
23+
"k8s.io/apiserver/pkg/apis/flowcontrol/bootstrap"
24+
"k8s.io/kubernetes/pkg/apis/flowcontrol"
25+
"k8s.io/kubernetes/pkg/apis/flowcontrol/install"
26+
)
27+
28+
// MandatoryFlowSchemas holds the untyped renditions of the mandatory
29+
// flow schemas. In this map the key is the schema's name and the
30+
// value is the `*FlowSchema`. Nobody should mutate anything
31+
// reachable from this map.
32+
var MandatoryFlowSchemas = internalizeFSes(bootstrap.MandatoryFlowSchemas)
33+
34+
// MandatoryPriorityLevelConfigurations holds the untyped renditions of the
35+
// mandatory priority level configuration objects. In this map the
36+
// key is the object's name and the value is the
37+
// `*PriorityLevelConfiguration`. Nobody should mutate anything
38+
// reachable from this map.
39+
var MandatoryPriorityLevelConfigurations = internalizePLs(bootstrap.MandatoryPriorityLevelConfigurations)
40+
41+
// NewAPFScheme constructs and returns a Scheme configured to handle
42+
// the API object types that are used to configure API Priority and
43+
// Fairness
44+
func NewAPFScheme() *runtime.Scheme {
45+
scheme := runtime.NewScheme()
46+
install.Install(scheme)
47+
return scheme
48+
}
49+
50+
func internalizeFSes(exts []*fcv1a1.FlowSchema) map[string]*flowcontrol.FlowSchema {
51+
ans := make(map[string]*flowcontrol.FlowSchema, len(exts))
52+
converter := NewAPFScheme().Converter()
53+
for _, ext := range exts {
54+
var untyped flowcontrol.FlowSchema
55+
err := converter.Convert(ext, &untyped, 0, &conversion.Meta{})
56+
if err != nil {
57+
panic(err)
58+
}
59+
ans[ext.Name] = &untyped
60+
}
61+
return ans
62+
}
63+
64+
func internalizePLs(exts []*fcv1a1.PriorityLevelConfiguration) map[string]*flowcontrol.PriorityLevelConfiguration {
65+
ans := make(map[string]*flowcontrol.PriorityLevelConfiguration, len(exts))
66+
converter := NewAPFScheme().Converter()
67+
for _, ext := range exts {
68+
var untyped flowcontrol.PriorityLevelConfiguration
69+
err := converter.Convert(ext, &untyped, 0, &conversion.Meta{})
70+
if err != nil {
71+
panic(err)
72+
}
73+
ans[ext.Name] = &untyped
74+
}
75+
return ans
76+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package internalbootstrap
18+
19+
import (
20+
"testing"
21+
22+
fcv1a1 "k8s.io/api/flowcontrol/v1alpha1"
23+
apiequality "k8s.io/apimachinery/pkg/api/equality"
24+
"k8s.io/apiserver/pkg/apis/flowcontrol/bootstrap"
25+
)
26+
27+
func TestMandatoryAlreadyDefaulted(t *testing.T) {
28+
scheme := NewAPFScheme()
29+
for _, obj := range bootstrap.MandatoryFlowSchemas {
30+
obj2 := obj.DeepCopyObject().(*fcv1a1.FlowSchema)
31+
scheme.Default(obj2)
32+
if apiequality.Semantic.DeepEqual(obj, obj2) {
33+
t.Logf("Defaulting makes no change to %#+v", *obj)
34+
} else {
35+
t.Errorf("Defaulting changed %#+v to %#+v", *obj, *obj2)
36+
}
37+
}
38+
for _, obj := range bootstrap.MandatoryPriorityLevelConfigurations {
39+
obj2 := obj.DeepCopyObject().(*fcv1a1.PriorityLevelConfiguration)
40+
scheme.Default(obj2)
41+
if apiequality.Semantic.DeepEqual(obj, obj2) {
42+
t.Logf("Defaulting makes no change to %#+v", *obj)
43+
} else {
44+
t.Errorf("Defaulting changed %#+v to %#+v", *obj, *obj2)
45+
}
46+
}
47+
}

pkg/apis/flowcontrol/types.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ const (
3333

3434
// System preset priority level names
3535
const (
36-
PriorityLevelConfigurationNameExempt = "exempt"
36+
PriorityLevelConfigurationNameExempt = "exempt"
37+
PriorityLevelConfigurationNameCatchAll = "catch-all"
38+
FlowSchemaNameExempt = "exempt"
39+
FlowSchemaNameCatchAll = "catch-all"
3740
)
3841

3942
// Conditions

pkg/apis/flowcontrol/validation/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ go_library(
88
deps = [
99
"//pkg/apis/core/validation:go_default_library",
1010
"//pkg/apis/flowcontrol:go_default_library",
11+
"//pkg/apis/flowcontrol/internalbootstrap:go_default_library",
12+
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
1113
"//staging/src/k8s.io/apimachinery/pkg/api/validation:go_default_library",
1214
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
1315
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
@@ -23,6 +25,7 @@ go_test(
2325
"//pkg/apis/flowcontrol:go_default_library",
2426
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
2527
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
28+
"//staging/src/k8s.io/apiserver/pkg/authentication/user:go_default_library",
2629
"//vendor/github.com/google/go-cmp/cmp:go_default_library",
2730
"//vendor/github.com/stretchr/testify/assert:go_default_library",
2831
],

pkg/apis/flowcontrol/validation/validation.go

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@ import (
2020
"fmt"
2121
"strings"
2222

23-
"k8s.io/apimachinery/pkg/api/validation"
23+
apiequality "k8s.io/apimachinery/pkg/api/equality"
2424
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
2525
"k8s.io/apimachinery/pkg/util/sets"
2626
"k8s.io/apimachinery/pkg/util/validation/field"
2727
"k8s.io/apiserver/pkg/util/shufflesharding"
2828
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
2929
"k8s.io/kubernetes/pkg/apis/flowcontrol"
30+
"k8s.io/kubernetes/pkg/apis/flowcontrol/internalbootstrap"
3031
)
3132

3233
// ValidateFlowSchemaName validates name for flow-schema.
@@ -73,7 +74,17 @@ var supportedLimitResponseType = sets.NewString(
7374
// ValidateFlowSchema validates the content of flow-schema
7475
func ValidateFlowSchema(fs *flowcontrol.FlowSchema) field.ErrorList {
7576
allErrs := apivalidation.ValidateObjectMeta(&fs.ObjectMeta, false, ValidateFlowSchemaName, field.NewPath("metadata"))
76-
allErrs = append(allErrs, ValidateFlowSchemaSpec(&fs.Spec, field.NewPath("spec"))...)
77+
specPath := field.NewPath("spec")
78+
allErrs = append(allErrs, ValidateFlowSchemaSpec(fs.Name, &fs.Spec, specPath)...)
79+
if mand, ok := internalbootstrap.MandatoryFlowSchemas[fs.Name]; ok {
80+
// Check for almost exact equality. This is a pretty
81+
// strict test, and it is OK in this context because both
82+
// sides of this comparison are intended to ultimately
83+
// come from the same code.
84+
if !apiequality.Semantic.DeepEqual(fs.Spec, mand.Spec) {
85+
allErrs = append(allErrs, field.Invalid(specPath, fs.Spec, fmt.Sprintf("spec of '%s' must equal the fixed value", fs.Name)))
86+
}
87+
}
7788
allErrs = append(allErrs, ValidateFlowSchemaStatus(&fs.Status, field.NewPath("status"))...)
7889
return allErrs
7990
}
@@ -84,14 +95,17 @@ func ValidateFlowSchemaUpdate(old, fs *flowcontrol.FlowSchema) field.ErrorList {
8495
}
8596

8697
// ValidateFlowSchemaSpec validates the content of flow-schema's spec
87-
func ValidateFlowSchemaSpec(spec *flowcontrol.FlowSchemaSpec, fldPath *field.Path) field.ErrorList {
98+
func ValidateFlowSchemaSpec(fsName string, spec *flowcontrol.FlowSchemaSpec, fldPath *field.Path) field.ErrorList {
8899
var allErrs field.ErrorList
89100
if spec.MatchingPrecedence <= 0 {
90101
allErrs = append(allErrs, field.Invalid(fldPath.Child("matchingPrecedence"), spec.MatchingPrecedence, "must be a positive value"))
91102
}
92103
if spec.MatchingPrecedence > flowcontrol.FlowSchemaMaxMatchingPrecedence {
93104
allErrs = append(allErrs, field.Invalid(fldPath.Child("matchingPrecedence"), spec.MatchingPrecedence, fmt.Sprintf("must not be greater than %v", flowcontrol.FlowSchemaMaxMatchingPrecedence)))
94105
}
106+
if (spec.MatchingPrecedence == 1) && (fsName != flowcontrol.FlowSchemaNameExempt) {
107+
allErrs = append(allErrs, field.Invalid(fldPath.Child("matchingPrecedence"), spec.MatchingPrecedence, "only the schema named 'exempt' may have matchingPrecedence 1"))
108+
}
95109
if spec.DistinguisherMethod != nil {
96110
if !supportedDistinguisherMethods.Has(string(spec.DistinguisherMethod.Type)) {
97111
allErrs = append(allErrs, field.NotSupported(fldPath.Child("distinguisherMethod").Child("type"), spec.DistinguisherMethod, supportedDistinguisherMethods.List()))
@@ -139,10 +153,28 @@ func ValidateFlowSchemaSubject(subject *flowcontrol.Subject, fldPath *field.Path
139153
switch subject.Kind {
140154
case flowcontrol.SubjectKindServiceAccount:
141155
allErrs = append(allErrs, ValidateServiceAccountSubject(subject.ServiceAccount, fldPath.Child("serviceAccount"))...)
156+
if subject.User != nil {
157+
allErrs = append(allErrs, field.Forbidden(fldPath.Child("user"), "user is forbidden when subject kind is not 'User'"))
158+
}
159+
if subject.Group != nil {
160+
allErrs = append(allErrs, field.Forbidden(fldPath.Child("group"), "group is forbidden when subject kind is not 'Group'"))
161+
}
142162
case flowcontrol.SubjectKindUser:
143163
allErrs = append(allErrs, ValidateUserSubject(subject.User, fldPath.Child("user"))...)
164+
if subject.ServiceAccount != nil {
165+
allErrs = append(allErrs, field.Forbidden(fldPath.Child("serviceAccount"), "serviceAccount is forbidden when subject kind is not 'ServiceAccount'"))
166+
}
167+
if subject.Group != nil {
168+
allErrs = append(allErrs, field.Forbidden(fldPath.Child("group"), "group is forbidden when subject kind is not 'Group'"))
169+
}
144170
case flowcontrol.SubjectKindGroup:
145171
allErrs = append(allErrs, ValidateGroupSubject(subject.Group, fldPath.Child("group"))...)
172+
if subject.ServiceAccount != nil {
173+
allErrs = append(allErrs, field.Forbidden(fldPath.Child("serviceAccount"), "serviceAccount is forbidden when subject kind is not 'ServiceAccount'"))
174+
}
175+
if subject.User != nil {
176+
allErrs = append(allErrs, field.Forbidden(fldPath.Child("user"), "user is forbidden when subject kind is not 'User'"))
177+
}
146178
default:
147179
allErrs = append(allErrs, field.NotSupported(fldPath.Child("kind"), subject.Kind, supportedSubjectKinds.List()))
148180
}
@@ -152,10 +184,13 @@ func ValidateFlowSchemaSubject(subject *flowcontrol.Subject, fldPath *field.Path
152184
// ValidateServiceAccountSubject validates subject of "ServiceAccount" kind
153185
func ValidateServiceAccountSubject(subject *flowcontrol.ServiceAccountSubject, fldPath *field.Path) field.ErrorList {
154186
var allErrs field.ErrorList
187+
if subject == nil {
188+
return append(allErrs, field.Required(fldPath, "serviceAccount is required when subject kind is 'ServiceAccount'"))
189+
}
155190
if len(subject.Name) == 0 {
156191
allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
157192
} else if subject.Name != flowcontrol.NameAll {
158-
for _, msg := range validation.ValidateServiceAccountName(subject.Name, false) {
193+
for _, msg := range apimachineryvalidation.ValidateServiceAccountName(subject.Name, false) {
159194
allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), subject.Name, msg))
160195
}
161196
}
@@ -174,6 +209,9 @@ func ValidateServiceAccountSubject(subject *flowcontrol.ServiceAccountSubject, f
174209
// ValidateUserSubject validates subject of "User" kind
175210
func ValidateUserSubject(subject *flowcontrol.UserSubject, fldPath *field.Path) field.ErrorList {
176211
var allErrs field.ErrorList
212+
if subject == nil {
213+
return append(allErrs, field.Required(fldPath, "user is required when subject kind is 'User'"))
214+
}
177215
if len(subject.Name) == 0 {
178216
allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
179217
}
@@ -183,6 +221,9 @@ func ValidateUserSubject(subject *flowcontrol.UserSubject, fldPath *field.Path)
183221
// ValidateGroupSubject validates subject of "Group" kind
184222
func ValidateGroupSubject(subject *flowcontrol.GroupSubject, fldPath *field.Path) field.ErrorList {
185223
var allErrs field.ErrorList
224+
if subject == nil {
225+
return append(allErrs, field.Required(fldPath, "group is required when subject kind is 'Group'"))
226+
}
186227
if len(subject.Name) == 0 {
187228
allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
188229
}
@@ -298,7 +339,17 @@ func ValidateFlowSchemaCondition(condition *flowcontrol.FlowSchemaCondition, fld
298339
// ValidatePriorityLevelConfiguration validates priority-level-configuration.
299340
func ValidatePriorityLevelConfiguration(pl *flowcontrol.PriorityLevelConfiguration) field.ErrorList {
300341
allErrs := apivalidation.ValidateObjectMeta(&pl.ObjectMeta, false, ValidatePriorityLevelConfigurationName, field.NewPath("metadata"))
301-
allErrs = append(allErrs, ValidatePriorityLevelConfigurationSpec(&pl.Spec, pl.Name, field.NewPath("spec"))...)
342+
specPath := field.NewPath("spec")
343+
allErrs = append(allErrs, ValidatePriorityLevelConfigurationSpec(&pl.Spec, pl.Name, specPath)...)
344+
if mand, ok := internalbootstrap.MandatoryPriorityLevelConfigurations[pl.Name]; ok {
345+
// Check for almost exact equality. This is a pretty
346+
// strict test, and it is OK in this context because both
347+
// sides of this comparison are intended to ultimately
348+
// come from the same code.
349+
if !apiequality.Semantic.DeepEqual(pl.Spec, mand.Spec) {
350+
allErrs = append(allErrs, field.Invalid(specPath, pl.Spec, fmt.Sprintf("spec of '%s' must equal the fixed value", pl.Name)))
351+
}
352+
}
302353
allErrs = append(allErrs, ValidatePriorityLevelConfigurationStatus(&pl.Status, field.NewPath("status"))...)
303354
return allErrs
304355
}
@@ -311,14 +362,17 @@ func ValidatePriorityLevelConfigurationUpdate(old, pl *flowcontrol.PriorityLevel
311362
// ValidatePriorityLevelConfigurationSpec validates priority-level-configuration's spec.
312363
func ValidatePriorityLevelConfigurationSpec(spec *flowcontrol.PriorityLevelConfigurationSpec, name string, fldPath *field.Path) field.ErrorList {
313364
var allErrs field.ErrorList
365+
if (name == flowcontrol.PriorityLevelConfigurationNameExempt) != (spec.Type == flowcontrol.PriorityLevelEnablementExempt) {
366+
allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), spec.Type, "type must be 'Exempt' if and only if name is 'exempt'"))
367+
}
314368
switch spec.Type {
315369
case flowcontrol.PriorityLevelEnablementExempt:
316370
if spec.Limited != nil {
317371
allErrs = append(allErrs, field.Forbidden(fldPath.Child("limited"), "must be nil if the type is not Limited"))
318372
}
319373
case flowcontrol.PriorityLevelEnablementLimited:
320374
if spec.Limited == nil {
321-
allErrs = append(allErrs, field.Required(fldPath.Child("limited"), "must not be empty"))
375+
allErrs = append(allErrs, field.Required(fldPath.Child("limited"), "must not be empty when type is Limited"))
322376
} else {
323377
allErrs = append(allErrs, ValidateLimitedPriorityLevelConfiguration(spec.Limited, fldPath.Child("limited"))...)
324378
}
@@ -344,11 +398,11 @@ func ValidateLimitResponse(lr flowcontrol.LimitResponse, fldPath *field.Path) fi
344398
switch lr.Type {
345399
case flowcontrol.LimitResponseTypeReject:
346400
if lr.Queuing != nil {
347-
allErrs = append(allErrs, field.Forbidden(fldPath.Child("queuing"), "must be nil if the type is not Limited"))
401+
allErrs = append(allErrs, field.Forbidden(fldPath.Child("queuing"), "must be nil if limited.limitResponse.type is not Limited"))
348402
}
349403
case flowcontrol.LimitResponseTypeQueue:
350404
if lr.Queuing == nil {
351-
allErrs = append(allErrs, field.Required(fldPath.Child("queuing"), "must not be empty"))
405+
allErrs = append(allErrs, field.Required(fldPath.Child("queuing"), "must not be empty if limited.limitResponse.type is Limited"))
352406
} else {
353407
allErrs = append(allErrs, ValidatePriorityLevelQueuingConfiguration(lr.Queuing, fldPath.Child("queuing"))...)
354408
}

0 commit comments

Comments
 (0)