Skip to content

Commit c0e245f

Browse files
authored
Merge pull request kubernetes#85268 from yue9944882/feat/flow-control-defaulting
Bootstrap flow-control objects
2 parents 7f21287 + 70dea6e commit c0e245f

File tree

14 files changed

+900
-11
lines changed

14 files changed

+900
-11
lines changed

api/openapi-spec/swagger.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/apis/flowcontrol/types.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ const (
4343
PriorityLevelConfigurationConditionConcurrencyShared = "ConcurrencyShared"
4444
)
4545

46+
// Constants used by api validation.
47+
const (
48+
FlowSchemaMaxMatchingPrecedence int32 = 10000
49+
)
50+
4651
// +genclient
4752
// +genclient:nonNamespaced
4853
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@@ -88,8 +93,8 @@ type FlowSchemaSpec struct {
8893
PriorityLevelConfiguration PriorityLevelConfigurationReference
8994
// `matchingPrecedence` is used to choose among the FlowSchemas that match a given request. The chosen
9095
// FlowSchema is among those with the numerically lowest (which we take to be logically highest)
91-
// MatchingPrecedence. Each MatchingPrecedence value must be non-negative.
92-
// Note that if the precedence is not specified or zero, it will be set to 1000 as default.
96+
// MatchingPrecedence. Each MatchingPrecedence value must be ranged in [1,10000].
97+
// Note that if the precedence is not specified, it will be set to 1000 as default.
9398
// +optional
9499
MatchingPrecedence int32
95100
// `distinguisherMethod` defines how to compute the flow distinguisher for requests that match this schema.

pkg/apis/flowcontrol/validation/validation.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,10 @@ func ValidateFlowSchemaUpdate(old, fs *flowcontrol.FlowSchema) field.ErrorList {
8787
func ValidateFlowSchemaSpec(spec *flowcontrol.FlowSchemaSpec, fldPath *field.Path) field.ErrorList {
8888
var allErrs field.ErrorList
8989
if spec.MatchingPrecedence <= 0 {
90-
allErrs = append(allErrs, field.Invalid(fldPath.Child("matchingPrecedence"), spec.MatchingPrecedence, "must be positive value"))
90+
allErrs = append(allErrs, field.Invalid(fldPath.Child("matchingPrecedence"), spec.MatchingPrecedence, "must be a positive value"))
91+
}
92+
if spec.MatchingPrecedence > flowcontrol.FlowSchemaMaxMatchingPrecedence {
93+
allErrs = append(allErrs, field.Invalid(fldPath.Child("matchingPrecedence"), spec.MatchingPrecedence, fmt.Sprintf("must not be greater than %v", flowcontrol.FlowSchemaMaxMatchingPrecedence)))
9194
}
9295
if spec.DistinguisherMethod != nil {
9396
if !supportedDistinguisherMethods.Has(string(spec.DistinguisherMethod.Type)) {

pkg/apis/flowcontrol/validation/validation_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,41 @@ func TestFlowSchemaValidation(t *testing.T) {
547547
field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("resourceRules").Index(0).Child("namespaces").Index(0), "-foo", nsErrIntro+`a DNS-1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')`),
548548
},
549549
},
550+
{
551+
name: "MatchingPrecedence must not be greater than 10000",
552+
flowSchema: &flowcontrol.FlowSchema{
553+
ObjectMeta: metav1.ObjectMeta{
554+
Name: "system-foo",
555+
},
556+
Spec: flowcontrol.FlowSchemaSpec{
557+
MatchingPrecedence: 10001,
558+
PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{
559+
Name: "system-bar",
560+
},
561+
Rules: []flowcontrol.PolicyRulesWithSubjects{
562+
{
563+
Subjects: []flowcontrol.Subject{
564+
{
565+
Kind: flowcontrol.SubjectKindUser,
566+
User: &flowcontrol.UserSubject{Name: "noxu"},
567+
},
568+
},
569+
ResourceRules: []flowcontrol.ResourcePolicyRule{
570+
{
571+
Verbs: []string{flowcontrol.VerbAll},
572+
APIGroups: []string{flowcontrol.APIGroupAll},
573+
Resources: []string{flowcontrol.ResourceAll},
574+
Namespaces: []string{flowcontrol.NamespaceEvery},
575+
},
576+
},
577+
},
578+
},
579+
},
580+
},
581+
expectedErrors: field.ErrorList{
582+
field.Invalid(field.NewPath("spec").Child("matchingPrecedence"), int32(10001), "must not be greater than 10000"),
583+
},
584+
},
550585
}
551586
for _, testCase := range testCases {
552587
t.Run(testCase.name, func(t *testing.T) {

pkg/registry/flowcontrol/rest/BUILD

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
load("@io_bazel_rules_go//go:def.bzl", "go_library")
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
22

33
go_library(
44
name = "go_default_library",
@@ -8,13 +8,21 @@ go_library(
88
deps = [
99
"//pkg/api/legacyscheme:go_default_library",
1010
"//pkg/apis/flowcontrol:go_default_library",
11+
"//pkg/apis/flowcontrol/v1alpha1:go_default_library",
1112
"//pkg/registry/flowcontrol/flowschema/storage:go_default_library",
1213
"//pkg/registry/flowcontrol/prioritylevelconfiguration/storage:go_default_library",
1314
"//staging/src/k8s.io/api/flowcontrol/v1alpha1:go_default_library",
15+
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
16+
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
17+
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
18+
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
19+
"//staging/src/k8s.io/apiserver/pkg/apis/flowcontrol/bootstrap:go_default_library",
1420
"//staging/src/k8s.io/apiserver/pkg/registry/generic:go_default_library",
1521
"//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library",
1622
"//staging/src/k8s.io/apiserver/pkg/server:go_default_library",
1723
"//staging/src/k8s.io/apiserver/pkg/server/storage:go_default_library",
24+
"//staging/src/k8s.io/client-go/kubernetes/typed/flowcontrol/v1alpha1:go_default_library",
25+
"//vendor/k8s.io/klog:go_default_library",
1826
],
1927
)
2028

@@ -31,3 +39,17 @@ filegroup(
3139
tags = ["automanaged"],
3240
visibility = ["//visibility:public"],
3341
)
42+
43+
go_test(
44+
name = "go_default_test",
45+
srcs = ["storage_flowcontrol_test.go"],
46+
embed = [":go_default_library"],
47+
deps = [
48+
"//pkg/apis/flowcontrol/v1alpha1:go_default_library",
49+
"//staging/src/k8s.io/api/flowcontrol/v1alpha1:go_default_library",
50+
"//staging/src/k8s.io/apiserver/pkg/apis/flowcontrol/bootstrap:go_default_library",
51+
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
52+
"//vendor/github.com/stretchr/testify/assert:go_default_library",
53+
"//vendor/github.com/stretchr/testify/require:go_default_library",
54+
],
55+
)

pkg/registry/flowcontrol/rest/storage_flowcontrol.go

Lines changed: 180 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,36 @@ limitations under the License.
1717
package rest
1818

1919
import (
20+
"fmt"
21+
"time"
22+
2023
flowcontrolv1alpha1 "k8s.io/api/flowcontrol/v1alpha1"
24+
"k8s.io/apimachinery/pkg/api/equality"
25+
apierrors "k8s.io/apimachinery/pkg/api/errors"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"k8s.io/apimachinery/pkg/util/wait"
28+
flowcontrolbootstrap "k8s.io/apiserver/pkg/apis/flowcontrol/bootstrap"
2129
"k8s.io/apiserver/pkg/registry/generic"
2230
"k8s.io/apiserver/pkg/registry/rest"
2331
genericapiserver "k8s.io/apiserver/pkg/server"
2432
serverstorage "k8s.io/apiserver/pkg/server/storage"
33+
flowcontrolclient "k8s.io/client-go/kubernetes/typed/flowcontrol/v1alpha1"
34+
"k8s.io/klog"
2535
"k8s.io/kubernetes/pkg/api/legacyscheme"
2636
"k8s.io/kubernetes/pkg/apis/flowcontrol"
37+
flowcontrolapisv1alpha1 "k8s.io/kubernetes/pkg/apis/flowcontrol/v1alpha1"
2738
flowschemastore "k8s.io/kubernetes/pkg/registry/flowcontrol/flowschema/storage"
2839
prioritylevelconfigurationstore "k8s.io/kubernetes/pkg/registry/flowcontrol/prioritylevelconfiguration/storage"
2940
)
3041

31-
// RESTStorageProvider implements
42+
var _ genericapiserver.PostStartHookProvider = RESTStorageProvider{}
43+
44+
// RESTStorageProvider is a provider of REST storage
3245
type RESTStorageProvider struct{}
3346

47+
// PostStartHookName is the name of the post-start-hook provided by flow-control storage
48+
const PostStartHookName = "apiserver/bootstrap-system-flowcontrol-configuration"
49+
3450
// NewRESTStorage creates a new rest storage for flow-control api models.
3551
func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool, error) {
3652
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(flowcontrol.GroupName, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs)
@@ -71,3 +87,166 @@ func (p RESTStorageProvider) v1alpha1Storage(apiResourceConfigSource serverstora
7187
func (p RESTStorageProvider) GroupName() string {
7288
return flowcontrol.GroupName
7389
}
90+
91+
func (p RESTStorageProvider) PostStartHook() (string, genericapiserver.PostStartHookFunc, error) {
92+
return PostStartHookName, func(hookContext genericapiserver.PostStartHookContext) error {
93+
flowcontrolClientSet := flowcontrolclient.NewForConfigOrDie(hookContext.LoopbackClientConfig)
94+
go func() {
95+
const retryCreatingSuggestedSettingsInterval = time.Second
96+
_ = wait.PollImmediateUntil(
97+
retryCreatingSuggestedSettingsInterval,
98+
func() (bool, error) {
99+
shouldEnsureSuggested, err := lastMandatoryExists(flowcontrolClientSet)
100+
if err != nil {
101+
klog.Errorf("failed getting exempt flow-schema, will retry later: %v", err)
102+
return false, nil
103+
}
104+
if !shouldEnsureSuggested {
105+
return true, nil
106+
}
107+
err = ensure(
108+
flowcontrolClientSet,
109+
flowcontrolbootstrap.SuggestedFlowSchemas,
110+
flowcontrolbootstrap.SuggestedPriorityLevelConfigurations)
111+
if err != nil {
112+
klog.Errorf("failed ensuring suggested settings, will retry later: %v", err)
113+
return false, nil
114+
}
115+
return true, nil
116+
},
117+
hookContext.StopCh)
118+
const retryCreatingMandatorySettingsInterval = time.Minute
119+
_ = wait.PollImmediateUntil(
120+
retryCreatingMandatorySettingsInterval,
121+
func() (bool, error) {
122+
if err := upgrade(
123+
flowcontrolClientSet,
124+
flowcontrolbootstrap.MandatoryFlowSchemas,
125+
// Note: the "exempt" priority-level is supposed tobe the last item in the pre-defined
126+
// list, so that a crash in the midst of the first kube-apiserver startup does not prevent
127+
// the full initial set of objects from being created.
128+
flowcontrolbootstrap.MandatoryPriorityLevelConfigurations,
129+
); err != nil {
130+
klog.Errorf("failed creating mandatory flowcontrol settings: %v", err)
131+
return false, nil
132+
}
133+
return false, nil // always retry
134+
},
135+
hookContext.StopCh)
136+
}()
137+
return nil
138+
}, nil
139+
140+
}
141+
142+
// Returns false if there's a "exempt" priority-level existing in the cluster, otherwise returns a true
143+
// if the "exempt" priority-level is not found.
144+
func lastMandatoryExists(flowcontrolClientSet flowcontrolclient.FlowcontrolV1alpha1Interface) (bool, error) {
145+
if _, err := flowcontrolClientSet.PriorityLevelConfigurations().Get(flowcontrol.PriorityLevelConfigurationNameExempt, metav1.GetOptions{}); err != nil {
146+
if apierrors.IsNotFound(err) {
147+
return true, nil
148+
}
149+
return false, err
150+
}
151+
return false, nil
152+
}
153+
154+
func ensure(flowcontrolClientSet flowcontrolclient.FlowcontrolV1alpha1Interface, flowSchemas []*flowcontrolv1alpha1.FlowSchema, priorityLevels []*flowcontrolv1alpha1.PriorityLevelConfiguration) error {
155+
for _, flowSchema := range flowSchemas {
156+
_, err := flowcontrolClientSet.FlowSchemas().Create(flowSchema)
157+
if apierrors.IsAlreadyExists(err) {
158+
klog.V(3).Infof("system preset FlowSchema %s already exists, skipping creating", flowSchema.Name)
159+
continue
160+
}
161+
if err != nil {
162+
return fmt.Errorf("cannot create FlowSchema %s due to %v", flowSchema.Name, err)
163+
}
164+
klog.V(3).Infof("created system preset FlowSchema %s", flowSchema.Name)
165+
}
166+
for _, priorityLevelConfiguration := range priorityLevels {
167+
_, err := flowcontrolClientSet.PriorityLevelConfigurations().Create(priorityLevelConfiguration)
168+
if apierrors.IsAlreadyExists(err) {
169+
klog.V(3).Infof("system preset PriorityLevelConfiguration %s already exists, skipping creating", priorityLevelConfiguration.Name)
170+
continue
171+
}
172+
if err != nil {
173+
return fmt.Errorf("cannot create PriorityLevelConfiguration %s due to %v", priorityLevelConfiguration.Name, err)
174+
}
175+
klog.V(3).Infof("created system preset PriorityLevelConfiguration %s", priorityLevelConfiguration.Name)
176+
}
177+
return nil
178+
}
179+
180+
func upgrade(flowcontrolClientSet flowcontrolclient.FlowcontrolV1alpha1Interface, flowSchemas []*flowcontrolv1alpha1.FlowSchema, priorityLevels []*flowcontrolv1alpha1.PriorityLevelConfiguration) error {
181+
for _, expectedFlowSchema := range flowSchemas {
182+
actualFlowSchema, err := flowcontrolClientSet.FlowSchemas().Get(expectedFlowSchema.Name, metav1.GetOptions{})
183+
if err == nil {
184+
// TODO(yue9944882): extract existing version from label and compare
185+
// TODO(yue9944882): create w/ version string attached
186+
identical, err := flowSchemaHasWrongSpec(expectedFlowSchema, actualFlowSchema)
187+
if err != nil {
188+
return fmt.Errorf("failed checking if mandatory FlowSchema %s is up-to-date due to %v, will retry later", expectedFlowSchema.Name, err)
189+
}
190+
if !identical {
191+
if _, err := flowcontrolClientSet.FlowSchemas().Update(expectedFlowSchema); err != nil {
192+
return fmt.Errorf("failed upgrading mandatory FlowSchema %s due to %v, will retry later", expectedFlowSchema.Name, err)
193+
}
194+
}
195+
continue
196+
}
197+
if !apierrors.IsNotFound(err) {
198+
return fmt.Errorf("failed getting FlowSchema %s due to %v, will retry later", expectedFlowSchema.Name, err)
199+
}
200+
_, err = flowcontrolClientSet.FlowSchemas().Create(expectedFlowSchema)
201+
if apierrors.IsAlreadyExists(err) {
202+
klog.V(3).Infof("system preset FlowSchema %s already exists, skipping creating", expectedFlowSchema.Name)
203+
continue
204+
}
205+
if err != nil {
206+
return fmt.Errorf("cannot create FlowSchema %s due to %v", expectedFlowSchema.Name, err)
207+
}
208+
klog.V(3).Infof("created system preset FlowSchema %s", expectedFlowSchema.Name)
209+
}
210+
for _, expectedPriorityLevelConfiguration := range priorityLevels {
211+
actualPriorityLevelConfiguration, err := flowcontrolClientSet.PriorityLevelConfigurations().Get(expectedPriorityLevelConfiguration.Name, metav1.GetOptions{})
212+
if err == nil {
213+
// TODO(yue9944882): extract existing version from label and compare
214+
// TODO(yue9944882): create w/ version string attached
215+
identical, err := priorityLevelHasWrongSpec(expectedPriorityLevelConfiguration, actualPriorityLevelConfiguration)
216+
if err != nil {
217+
return fmt.Errorf("failed checking if mandatory PriorityLevelConfiguration %s is up-to-date due to %v, will retry later", expectedPriorityLevelConfiguration.Name, err)
218+
}
219+
if !identical {
220+
if _, err := flowcontrolClientSet.PriorityLevelConfigurations().Update(expectedPriorityLevelConfiguration); err != nil {
221+
return fmt.Errorf("failed upgrading mandatory PriorityLevelConfiguration %s due to %v, will retry later", expectedPriorityLevelConfiguration.Name, err)
222+
}
223+
}
224+
continue
225+
}
226+
if !apierrors.IsNotFound(err) {
227+
return fmt.Errorf("failed getting PriorityLevelConfiguration %s due to %v, will retry later", expectedPriorityLevelConfiguration.Name, err)
228+
}
229+
_, err = flowcontrolClientSet.PriorityLevelConfigurations().Create(expectedPriorityLevelConfiguration)
230+
if apierrors.IsAlreadyExists(err) {
231+
klog.V(3).Infof("system preset PriorityLevelConfiguration %s already exists, skipping creating", expectedPriorityLevelConfiguration.Name)
232+
continue
233+
}
234+
if err != nil {
235+
return fmt.Errorf("cannot create PriorityLevelConfiguration %s due to %v", expectedPriorityLevelConfiguration.Name, err)
236+
}
237+
klog.V(3).Infof("created system preset PriorityLevelConfiguration %s", expectedPriorityLevelConfiguration.Name)
238+
}
239+
return nil
240+
}
241+
242+
func flowSchemaHasWrongSpec(expected, actual *flowcontrolv1alpha1.FlowSchema) (bool, error) {
243+
copiedExpectedFlowSchema := expected.DeepCopy()
244+
flowcontrolapisv1alpha1.SetObjectDefaults_FlowSchema(copiedExpectedFlowSchema)
245+
return !equality.Semantic.DeepEqual(copiedExpectedFlowSchema.Spec, actual.Spec), nil
246+
}
247+
248+
func priorityLevelHasWrongSpec(expected, actual *flowcontrolv1alpha1.PriorityLevelConfiguration) (bool, error) {
249+
copiedExpectedPriorityLevel := expected.DeepCopy()
250+
flowcontrolapisv1alpha1.SetObjectDefaults_PriorityLevelConfiguration(copiedExpectedPriorityLevel)
251+
return !equality.Semantic.DeepEqual(copiedExpectedPriorityLevel.Spec, actual.Spec), nil
252+
}

0 commit comments

Comments
 (0)