mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 12:43:23 +00:00
Merge pull request #50925 from staebler/server-event-rate-limiter
Automatic merge from submit-queue (batch tested with PRs 51805, 51725, 50925, 51474, 51638) Limit events accepted by API Server **What this PR does / why we need it**: This PR adds the ability to limit events processed by an API server. Limits can be set globally on a server, per-namespace, per-user, and per-source+object. This is needed to prevent badly-configured or misbehaving players from making a cluster unstable. Please see https://github.com/kubernetes/community/pull/945. **Release Note:** ```release-note Adds a new alpha EventRateLimit admission control that is used to limit the number of event queries that are accepted by the API Server. ```
This commit is contained in:
commit
4d42f80382
@ -26,6 +26,7 @@ go_library(
|
|||||||
"//plugin/pkg/admission/antiaffinity:go_default_library",
|
"//plugin/pkg/admission/antiaffinity:go_default_library",
|
||||||
"//plugin/pkg/admission/defaulttolerationseconds:go_default_library",
|
"//plugin/pkg/admission/defaulttolerationseconds:go_default_library",
|
||||||
"//plugin/pkg/admission/deny:go_default_library",
|
"//plugin/pkg/admission/deny:go_default_library",
|
||||||
|
"//plugin/pkg/admission/eventratelimit:go_default_library",
|
||||||
"//plugin/pkg/admission/exec:go_default_library",
|
"//plugin/pkg/admission/exec:go_default_library",
|
||||||
"//plugin/pkg/admission/gc:go_default_library",
|
"//plugin/pkg/admission/gc:go_default_library",
|
||||||
"//plugin/pkg/admission/imagepolicy:go_default_library",
|
"//plugin/pkg/admission/imagepolicy:go_default_library",
|
||||||
|
@ -30,6 +30,7 @@ import (
|
|||||||
"k8s.io/kubernetes/plugin/pkg/admission/antiaffinity"
|
"k8s.io/kubernetes/plugin/pkg/admission/antiaffinity"
|
||||||
"k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds"
|
"k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds"
|
||||||
"k8s.io/kubernetes/plugin/pkg/admission/deny"
|
"k8s.io/kubernetes/plugin/pkg/admission/deny"
|
||||||
|
"k8s.io/kubernetes/plugin/pkg/admission/eventratelimit"
|
||||||
"k8s.io/kubernetes/plugin/pkg/admission/exec"
|
"k8s.io/kubernetes/plugin/pkg/admission/exec"
|
||||||
"k8s.io/kubernetes/plugin/pkg/admission/gc"
|
"k8s.io/kubernetes/plugin/pkg/admission/gc"
|
||||||
"k8s.io/kubernetes/plugin/pkg/admission/imagepolicy"
|
"k8s.io/kubernetes/plugin/pkg/admission/imagepolicy"
|
||||||
@ -59,6 +60,7 @@ func RegisterAllAdmissionPlugins(plugins *admission.Plugins) {
|
|||||||
antiaffinity.Register(plugins)
|
antiaffinity.Register(plugins)
|
||||||
defaulttolerationseconds.Register(plugins)
|
defaulttolerationseconds.Register(plugins)
|
||||||
deny.Register(plugins)
|
deny.Register(plugins)
|
||||||
|
eventratelimit.Register(plugins)
|
||||||
exec.Register(plugins)
|
exec.Register(plugins)
|
||||||
gc.Register(plugins)
|
gc.Register(plugins)
|
||||||
imagepolicy.Register(plugins)
|
imagepolicy.Register(plugins)
|
||||||
|
@ -466,6 +466,8 @@ pkg/volume/util
|
|||||||
pkg/volume/vsphere_volume
|
pkg/volume/vsphere_volume
|
||||||
plugin/cmd/kube-scheduler/app
|
plugin/cmd/kube-scheduler/app
|
||||||
plugin/pkg/admission/antiaffinity
|
plugin/pkg/admission/antiaffinity
|
||||||
|
plugin/pkg/admission/eventratelimit/apis/eventratelimit
|
||||||
|
plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1
|
||||||
plugin/pkg/admission/initialization
|
plugin/pkg/admission/initialization
|
||||||
plugin/pkg/admission/initialresources
|
plugin/pkg/admission/initialresources
|
||||||
plugin/pkg/admission/limitranger
|
plugin/pkg/admission/limitranger
|
||||||
|
@ -17,6 +17,7 @@ filegroup(
|
|||||||
"//plugin/pkg/admission/antiaffinity:all-srcs",
|
"//plugin/pkg/admission/antiaffinity:all-srcs",
|
||||||
"//plugin/pkg/admission/defaulttolerationseconds:all-srcs",
|
"//plugin/pkg/admission/defaulttolerationseconds:all-srcs",
|
||||||
"//plugin/pkg/admission/deny:all-srcs",
|
"//plugin/pkg/admission/deny:all-srcs",
|
||||||
|
"//plugin/pkg/admission/eventratelimit:all-srcs",
|
||||||
"//plugin/pkg/admission/exec:all-srcs",
|
"//plugin/pkg/admission/exec:all-srcs",
|
||||||
"//plugin/pkg/admission/gc:all-srcs",
|
"//plugin/pkg/admission/gc:all-srcs",
|
||||||
"//plugin/pkg/admission/imagepolicy:all-srcs",
|
"//plugin/pkg/admission/imagepolicy:all-srcs",
|
||||||
|
74
plugin/pkg/admission/eventratelimit/BUILD
Normal file
74
plugin/pkg/admission/eventratelimit/BUILD
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_library",
|
||||||
|
"go_test",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = [
|
||||||
|
"admission_test.go",
|
||||||
|
"cache_test.go",
|
||||||
|
],
|
||||||
|
library = ":go_default_library",
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/api:go_default_library",
|
||||||
|
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit:go_default_library",
|
||||||
|
"//vendor/github.com/hashicorp/golang-lru:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
||||||
|
"//vendor/k8s.io/client-go/util/flowcontrol:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"admission.go",
|
||||||
|
"cache.go",
|
||||||
|
"clock.go",
|
||||||
|
"config.go",
|
||||||
|
"doc.go",
|
||||||
|
"limitenforcer.go",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/api:go_default_library",
|
||||||
|
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit:go_default_library",
|
||||||
|
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit/install:go_default_library",
|
||||||
|
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1:go_default_library",
|
||||||
|
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation:go_default_library",
|
||||||
|
"//vendor/github.com/hashicorp/golang-lru:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
||||||
|
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||||
|
"//vendor/k8s.io/client-go/util/flowcontrol:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [
|
||||||
|
":package-srcs",
|
||||||
|
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit:all-srcs",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
)
|
92
plugin/pkg/admission/eventratelimit/admission.go
Normal file
92
plugin/pkg/admission/eventratelimit/admission.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 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 eventratelimit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"k8s.io/apiserver/pkg/admission"
|
||||||
|
"k8s.io/client-go/util/flowcontrol"
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
eventratelimitapi "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit"
|
||||||
|
"k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Register registers a plugin
|
||||||
|
func Register(plugins *admission.Plugins) {
|
||||||
|
plugins.Register("EventRateLimit",
|
||||||
|
func(config io.Reader) (admission.Interface, error) {
|
||||||
|
// load the configuration provided (if any)
|
||||||
|
configuration, err := LoadConfiguration(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// validate the configuration (if any)
|
||||||
|
if configuration != nil {
|
||||||
|
if errs := validation.ValidateConfiguration(configuration); len(errs) != 0 {
|
||||||
|
return nil, errs.ToAggregate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newEventRateLimit(configuration, realClock{})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// eventRateLimitAdmission implements an admission controller that can enforce event rate limits
|
||||||
|
type eventRateLimitAdmission struct {
|
||||||
|
*admission.Handler
|
||||||
|
// limitEnforcers is the collection of limit enforcers. There is one limit enforcer for each
|
||||||
|
// active limit type. As there are 4 limit types, the length of the array will be at most 4.
|
||||||
|
// The array is read-only after construction.
|
||||||
|
limitEnforcers []*limitEnforcer
|
||||||
|
}
|
||||||
|
|
||||||
|
// newEventRateLimit configures an admission controller that can enforce event rate limits
|
||||||
|
func newEventRateLimit(config *eventratelimitapi.Configuration, clock flowcontrol.Clock) (admission.Interface, error) {
|
||||||
|
limitEnforcers := make([]*limitEnforcer, 0, len(config.Limits))
|
||||||
|
for _, limitConfig := range config.Limits {
|
||||||
|
enforcer, err := newLimitEnforcer(limitConfig, clock)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
limitEnforcers = append(limitEnforcers, enforcer)
|
||||||
|
}
|
||||||
|
|
||||||
|
eventRateLimitAdmission := &eventRateLimitAdmission{
|
||||||
|
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||||
|
limitEnforcers: limitEnforcers,
|
||||||
|
}
|
||||||
|
|
||||||
|
return eventRateLimitAdmission, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Admit makes admission decisions while enforcing event rate limits
|
||||||
|
func (a *eventRateLimitAdmission) Admit(attr admission.Attributes) (err error) {
|
||||||
|
// ignore all operations that do not correspond to an Event kind
|
||||||
|
if attr.GetKind().GroupKind() != api.Kind("Event") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var rejectionError error
|
||||||
|
// give each limit enforcer a chance to reject the event
|
||||||
|
for _, enforcer := range a.limitEnforcers {
|
||||||
|
if err := enforcer.accept(attr); err != nil {
|
||||||
|
rejectionError = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rejectionError
|
||||||
|
}
|
502
plugin/pkg/admission/eventratelimit/admission_test.go
Normal file
502
plugin/pkg/admission/eventratelimit/admission_test.go
Normal file
@ -0,0 +1,502 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 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 eventratelimit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/apimachinery/pkg/util/clock"
|
||||||
|
"k8s.io/apiserver/pkg/admission"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
eventratelimitapi "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
qps = 1
|
||||||
|
eventKind = "Event"
|
||||||
|
nonEventKind = "NonEvent"
|
||||||
|
)
|
||||||
|
|
||||||
|
// attributesForRequest generates the admission.Attributes that for the specified request
|
||||||
|
func attributesForRequest(rq request) admission.Attributes {
|
||||||
|
return admission.NewAttributesRecord(
|
||||||
|
rq.event,
|
||||||
|
nil,
|
||||||
|
api.Kind(rq.kind).WithVersion("version"),
|
||||||
|
rq.namespace,
|
||||||
|
"name",
|
||||||
|
api.Resource("resource").WithVersion("version"),
|
||||||
|
"",
|
||||||
|
admission.Create,
|
||||||
|
&user.DefaultInfo{Name: rq.username})
|
||||||
|
}
|
||||||
|
|
||||||
|
type request struct {
|
||||||
|
kind string
|
||||||
|
namespace string
|
||||||
|
username string
|
||||||
|
event *api.Event
|
||||||
|
delay time.Duration
|
||||||
|
accepted bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRequest(kind string) request {
|
||||||
|
return request{
|
||||||
|
kind: kind,
|
||||||
|
accepted: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEventRequest() request {
|
||||||
|
return newRequest(eventKind)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNonEventRequest() request {
|
||||||
|
return newRequest(nonEventKind)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r request) withNamespace(namespace string) request {
|
||||||
|
r.namespace = namespace
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r request) withEvent(event *api.Event) request {
|
||||||
|
r.event = event
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r request) withEventComponent(component string) request {
|
||||||
|
return r.withEvent(&api.Event{
|
||||||
|
Source: api.EventSource{
|
||||||
|
Component: component,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r request) withUser(name string) request {
|
||||||
|
r.username = name
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r request) blocked() request {
|
||||||
|
r.accepted = false
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// withDelay will adjust the clock to simulate the specified delay, in seconds
|
||||||
|
func (r request) withDelay(delayInSeconds int) request {
|
||||||
|
r.delay = time.Duration(delayInSeconds) * time.Second
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// createSourceAndObjectKeyInclusionRequests creates a series of requests that can be used
|
||||||
|
// to test that a particular part of the event is included in the source+object key
|
||||||
|
func createSourceAndObjectKeyInclusionRequests(eventFactory func(label string) *api.Event) []request {
|
||||||
|
return []request{
|
||||||
|
newEventRequest().withEvent(eventFactory("A")),
|
||||||
|
newEventRequest().withEvent(eventFactory("A")).blocked(),
|
||||||
|
newEventRequest().withEvent(eventFactory("B")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEventRateLimiting(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
serverBurst int32
|
||||||
|
namespaceBurst int32
|
||||||
|
namespaceCacheSize int32
|
||||||
|
sourceAndObjectBurst int32
|
||||||
|
sourceAndObjectCacheSize int32
|
||||||
|
userBurst int32
|
||||||
|
userCacheSize int32
|
||||||
|
requests []request
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "event not blocked when tokens available",
|
||||||
|
serverBurst: 3,
|
||||||
|
requests: []request{
|
||||||
|
newEventRequest(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-event not blocked",
|
||||||
|
serverBurst: 3,
|
||||||
|
requests: []request{
|
||||||
|
newNonEventRequest(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "event blocked after tokens exhausted",
|
||||||
|
serverBurst: 3,
|
||||||
|
requests: []request{
|
||||||
|
newEventRequest(),
|
||||||
|
newEventRequest(),
|
||||||
|
newEventRequest(),
|
||||||
|
newEventRequest().blocked(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-event not blocked after tokens exhausted",
|
||||||
|
serverBurst: 3,
|
||||||
|
requests: []request{
|
||||||
|
newEventRequest(),
|
||||||
|
newEventRequest(),
|
||||||
|
newEventRequest(),
|
||||||
|
newNonEventRequest(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-events should not count against limit",
|
||||||
|
serverBurst: 3,
|
||||||
|
requests: []request{
|
||||||
|
newEventRequest(),
|
||||||
|
newEventRequest(),
|
||||||
|
newNonEventRequest(),
|
||||||
|
newEventRequest(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "event accepted after token refill",
|
||||||
|
serverBurst: 3,
|
||||||
|
requests: []request{
|
||||||
|
newEventRequest(),
|
||||||
|
newEventRequest(),
|
||||||
|
newEventRequest(),
|
||||||
|
newEventRequest().blocked(),
|
||||||
|
newEventRequest().withDelay(1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "event blocked by namespace limits",
|
||||||
|
serverBurst: 100,
|
||||||
|
namespaceBurst: 3,
|
||||||
|
namespaceCacheSize: 10,
|
||||||
|
requests: []request{
|
||||||
|
newEventRequest().withNamespace("A"),
|
||||||
|
newEventRequest().withNamespace("A"),
|
||||||
|
newEventRequest().withNamespace("A"),
|
||||||
|
newEventRequest().withNamespace("A").blocked(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "event from other namespace not blocked",
|
||||||
|
serverBurst: 100,
|
||||||
|
namespaceBurst: 3,
|
||||||
|
namespaceCacheSize: 10,
|
||||||
|
requests: []request{
|
||||||
|
newEventRequest().withNamespace("A"),
|
||||||
|
newEventRequest().withNamespace("A"),
|
||||||
|
newEventRequest().withNamespace("A"),
|
||||||
|
newEventRequest().withNamespace("B"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "events from other namespaces should not count against limit",
|
||||||
|
serverBurst: 100,
|
||||||
|
namespaceBurst: 3,
|
||||||
|
namespaceCacheSize: 10,
|
||||||
|
requests: []request{
|
||||||
|
newEventRequest().withNamespace("A"),
|
||||||
|
newEventRequest().withNamespace("A"),
|
||||||
|
newEventRequest().withNamespace("B"),
|
||||||
|
newEventRequest().withNamespace("A"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "event accepted after namespace token refill",
|
||||||
|
serverBurst: 100,
|
||||||
|
namespaceBurst: 3,
|
||||||
|
namespaceCacheSize: 10,
|
||||||
|
requests: []request{
|
||||||
|
newEventRequest().withNamespace("A"),
|
||||||
|
newEventRequest().withNamespace("A"),
|
||||||
|
newEventRequest().withNamespace("A"),
|
||||||
|
newEventRequest().withNamespace("A").blocked(),
|
||||||
|
newEventRequest().withNamespace("A").withDelay(1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "event from other namespaces should not clear namespace limits",
|
||||||
|
serverBurst: 100,
|
||||||
|
namespaceBurst: 3,
|
||||||
|
namespaceCacheSize: 10,
|
||||||
|
requests: []request{
|
||||||
|
newEventRequest().withNamespace("A"),
|
||||||
|
newEventRequest().withNamespace("A"),
|
||||||
|
newEventRequest().withNamespace("A"),
|
||||||
|
newEventRequest().withNamespace("B"),
|
||||||
|
newEventRequest().withNamespace("A").blocked(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "namespace limits from lru namespace should clear when cache size exceeded",
|
||||||
|
serverBurst: 100,
|
||||||
|
namespaceBurst: 3,
|
||||||
|
namespaceCacheSize: 2,
|
||||||
|
requests: []request{
|
||||||
|
newEventRequest().withNamespace("A"),
|
||||||
|
newEventRequest().withNamespace("A"),
|
||||||
|
newEventRequest().withNamespace("B"),
|
||||||
|
newEventRequest().withNamespace("B"),
|
||||||
|
newEventRequest().withNamespace("B"),
|
||||||
|
newEventRequest().withNamespace("A"),
|
||||||
|
newEventRequest().withNamespace("B").blocked(),
|
||||||
|
newEventRequest().withNamespace("A").blocked(),
|
||||||
|
// This should clear out namespace B from the lru cache
|
||||||
|
newEventRequest().withNamespace("C"),
|
||||||
|
newEventRequest().withNamespace("A").blocked(),
|
||||||
|
newEventRequest().withNamespace("B"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "event blocked by source+object limits",
|
||||||
|
serverBurst: 100,
|
||||||
|
sourceAndObjectBurst: 3,
|
||||||
|
sourceAndObjectCacheSize: 10,
|
||||||
|
requests: []request{
|
||||||
|
newEventRequest().withEventComponent("A"),
|
||||||
|
newEventRequest().withEventComponent("A"),
|
||||||
|
newEventRequest().withEventComponent("A"),
|
||||||
|
newEventRequest().withEventComponent("A").blocked(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "event from other source+object not blocked",
|
||||||
|
serverBurst: 100,
|
||||||
|
sourceAndObjectBurst: 3,
|
||||||
|
sourceAndObjectCacheSize: 10,
|
||||||
|
requests: []request{
|
||||||
|
newEventRequest().withEventComponent("A"),
|
||||||
|
newEventRequest().withEventComponent("A"),
|
||||||
|
newEventRequest().withEventComponent("A"),
|
||||||
|
newEventRequest().withEventComponent("B"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "events from other source+object should not count against limit",
|
||||||
|
serverBurst: 100,
|
||||||
|
sourceAndObjectBurst: 3,
|
||||||
|
sourceAndObjectCacheSize: 10,
|
||||||
|
requests: []request{
|
||||||
|
newEventRequest().withEventComponent("A"),
|
||||||
|
newEventRequest().withEventComponent("A"),
|
||||||
|
newEventRequest().withEventComponent("B"),
|
||||||
|
newEventRequest().withEventComponent("A"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "event accepted after source+object token refill",
|
||||||
|
serverBurst: 100,
|
||||||
|
sourceAndObjectBurst: 3,
|
||||||
|
sourceAndObjectCacheSize: 10,
|
||||||
|
requests: []request{
|
||||||
|
newEventRequest().withEventComponent("A"),
|
||||||
|
newEventRequest().withEventComponent("A"),
|
||||||
|
newEventRequest().withEventComponent("A"),
|
||||||
|
newEventRequest().withEventComponent("A").blocked(),
|
||||||
|
newEventRequest().withEventComponent("A").withDelay(1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "event from other source+object should not clear source+object limits",
|
||||||
|
serverBurst: 100,
|
||||||
|
sourceAndObjectBurst: 3,
|
||||||
|
sourceAndObjectCacheSize: 10,
|
||||||
|
requests: []request{
|
||||||
|
newEventRequest().withEventComponent("A"),
|
||||||
|
newEventRequest().withEventComponent("A"),
|
||||||
|
newEventRequest().withEventComponent("A"),
|
||||||
|
newEventRequest().withEventComponent("B"),
|
||||||
|
newEventRequest().withEventComponent("A").blocked(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "source+object limits from lru source+object should clear when cache size exceeded",
|
||||||
|
serverBurst: 100,
|
||||||
|
sourceAndObjectBurst: 3,
|
||||||
|
sourceAndObjectCacheSize: 2,
|
||||||
|
requests: []request{
|
||||||
|
newEventRequest().withEventComponent("A"),
|
||||||
|
newEventRequest().withEventComponent("A"),
|
||||||
|
newEventRequest().withEventComponent("B"),
|
||||||
|
newEventRequest().withEventComponent("B"),
|
||||||
|
newEventRequest().withEventComponent("B"),
|
||||||
|
newEventRequest().withEventComponent("A"),
|
||||||
|
newEventRequest().withEventComponent("B").blocked(),
|
||||||
|
newEventRequest().withEventComponent("A").blocked(),
|
||||||
|
// This should clear out component B from the lru cache
|
||||||
|
newEventRequest().withEventComponent("C"),
|
||||||
|
newEventRequest().withEventComponent("A").blocked(),
|
||||||
|
newEventRequest().withEventComponent("B"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "source host should be included in source+object key",
|
||||||
|
serverBurst: 100,
|
||||||
|
sourceAndObjectBurst: 1,
|
||||||
|
sourceAndObjectCacheSize: 10,
|
||||||
|
requests: createSourceAndObjectKeyInclusionRequests(func(label string) *api.Event {
|
||||||
|
return &api.Event{Source: api.EventSource{Host: label}}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "involved object kind should be included in source+object key",
|
||||||
|
serverBurst: 100,
|
||||||
|
sourceAndObjectBurst: 1,
|
||||||
|
sourceAndObjectCacheSize: 10,
|
||||||
|
requests: createSourceAndObjectKeyInclusionRequests(func(label string) *api.Event {
|
||||||
|
return &api.Event{InvolvedObject: api.ObjectReference{Kind: label}}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "involved object namespace should be included in source+object key",
|
||||||
|
serverBurst: 100,
|
||||||
|
sourceAndObjectBurst: 1,
|
||||||
|
sourceAndObjectCacheSize: 10,
|
||||||
|
requests: createSourceAndObjectKeyInclusionRequests(func(label string) *api.Event {
|
||||||
|
return &api.Event{InvolvedObject: api.ObjectReference{Namespace: label}}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "involved object name should be included in source+object key",
|
||||||
|
serverBurst: 100,
|
||||||
|
sourceAndObjectBurst: 1,
|
||||||
|
sourceAndObjectCacheSize: 10,
|
||||||
|
requests: createSourceAndObjectKeyInclusionRequests(func(label string) *api.Event {
|
||||||
|
return &api.Event{InvolvedObject: api.ObjectReference{Name: label}}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "involved object UID should be included in source+object key",
|
||||||
|
serverBurst: 100,
|
||||||
|
sourceAndObjectBurst: 1,
|
||||||
|
sourceAndObjectCacheSize: 10,
|
||||||
|
requests: createSourceAndObjectKeyInclusionRequests(func(label string) *api.Event {
|
||||||
|
return &api.Event{InvolvedObject: api.ObjectReference{UID: types.UID(label)}}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "involved object APIVersion should be included in source+object key",
|
||||||
|
serverBurst: 100,
|
||||||
|
sourceAndObjectBurst: 1,
|
||||||
|
sourceAndObjectCacheSize: 10,
|
||||||
|
requests: createSourceAndObjectKeyInclusionRequests(func(label string) *api.Event {
|
||||||
|
return &api.Event{InvolvedObject: api.ObjectReference{APIVersion: label}}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "event blocked by user limits",
|
||||||
|
userBurst: 3,
|
||||||
|
userCacheSize: 10,
|
||||||
|
requests: []request{
|
||||||
|
newEventRequest().withUser("A"),
|
||||||
|
newEventRequest().withUser("A"),
|
||||||
|
newEventRequest().withUser("A"),
|
||||||
|
newEventRequest().withUser("A").blocked(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "event from other user not blocked",
|
||||||
|
requests: []request{
|
||||||
|
newEventRequest().withUser("A"),
|
||||||
|
newEventRequest().withUser("A"),
|
||||||
|
newEventRequest().withUser("A"),
|
||||||
|
newEventRequest().withUser("B"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "events from other user should not count against limit",
|
||||||
|
requests: []request{
|
||||||
|
newEventRequest().withUser("A"),
|
||||||
|
newEventRequest().withUser("A"),
|
||||||
|
newEventRequest().withUser("B"),
|
||||||
|
newEventRequest().withUser("A"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
clock := clock.NewFakeClock(time.Now())
|
||||||
|
config := &eventratelimitapi.Configuration{}
|
||||||
|
if tc.serverBurst > 0 {
|
||||||
|
serverLimit := eventratelimitapi.Limit{
|
||||||
|
Type: eventratelimitapi.ServerLimitType,
|
||||||
|
QPS: qps,
|
||||||
|
Burst: tc.serverBurst,
|
||||||
|
}
|
||||||
|
config.Limits = append(config.Limits, serverLimit)
|
||||||
|
}
|
||||||
|
if tc.namespaceBurst > 0 {
|
||||||
|
namespaceLimit := eventratelimitapi.Limit{
|
||||||
|
Type: eventratelimitapi.NamespaceLimitType,
|
||||||
|
Burst: tc.namespaceBurst,
|
||||||
|
QPS: qps,
|
||||||
|
CacheSize: tc.namespaceCacheSize,
|
||||||
|
}
|
||||||
|
config.Limits = append(config.Limits, namespaceLimit)
|
||||||
|
}
|
||||||
|
if tc.userBurst > 0 {
|
||||||
|
userLimit := eventratelimitapi.Limit{
|
||||||
|
Type: eventratelimitapi.UserLimitType,
|
||||||
|
Burst: tc.userBurst,
|
||||||
|
QPS: qps,
|
||||||
|
CacheSize: tc.userCacheSize,
|
||||||
|
}
|
||||||
|
config.Limits = append(config.Limits, userLimit)
|
||||||
|
}
|
||||||
|
if tc.sourceAndObjectBurst > 0 {
|
||||||
|
sourceAndObjectLimit := eventratelimitapi.Limit{
|
||||||
|
Type: eventratelimitapi.SourceAndObjectLimitType,
|
||||||
|
Burst: tc.sourceAndObjectBurst,
|
||||||
|
QPS: qps,
|
||||||
|
CacheSize: tc.sourceAndObjectCacheSize,
|
||||||
|
}
|
||||||
|
config.Limits = append(config.Limits, sourceAndObjectLimit)
|
||||||
|
}
|
||||||
|
eventratelimit, err := newEventRateLimit(config, clock)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%v: Could not create EventRateLimit: %v", tc.name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for rqIndex, rq := range tc.requests {
|
||||||
|
if rq.delay > 0 {
|
||||||
|
clock.Step(rq.delay)
|
||||||
|
}
|
||||||
|
attributes := attributesForRequest(rq)
|
||||||
|
err = eventratelimit.Admit(attributes)
|
||||||
|
if rq.accepted != (err == nil) {
|
||||||
|
expectedAction := "admitted"
|
||||||
|
if !rq.accepted {
|
||||||
|
expectedAction = "blocked"
|
||||||
|
}
|
||||||
|
t.Fatalf("%v: Request %v should have been %v: %v", tc.name, rqIndex, expectedAction, err)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
statusErr, ok := err.(*errors.StatusError)
|
||||||
|
if ok && statusErr.ErrStatus.Code != errors.StatusTooManyRequests {
|
||||||
|
t.Fatalf("%v: Request %v should yield a 429 response: %v", tc.name, rqIndex, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_library",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"doc.go",
|
||||||
|
"register.go",
|
||||||
|
"types.go",
|
||||||
|
"zz_generated.deepcopy.go",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [
|
||||||
|
":package-srcs",
|
||||||
|
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit/install:all-srcs",
|
||||||
|
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1:all-srcs",
|
||||||
|
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation:all-srcs",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
)
|
7
plugin/pkg/admission/eventratelimit/apis/eventratelimit/OWNERS
Executable file
7
plugin/pkg/admission/eventratelimit/apis/eventratelimit/OWNERS
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
reviewers:
|
||||||
|
- deads2k
|
||||||
|
- derekwaynecarr
|
||||||
|
approvers:
|
||||||
|
- deads2k
|
||||||
|
- derekwaynecarr
|
||||||
|
- smarterclayton
|
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen=package,register
|
||||||
|
|
||||||
|
package eventratelimit // import "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit"
|
@ -0,0 +1,34 @@
|
|||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_library",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["install.go"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit:go_default_library",
|
||||||
|
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
)
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 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 install installs the experimental API group, making it available as
|
||||||
|
// an option to all of the API encoding/decoding machinery.
|
||||||
|
package install
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/apimachinery/announced"
|
||||||
|
"k8s.io/apimachinery/pkg/apimachinery/registered"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
internalapi "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit"
|
||||||
|
versionedapi "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Install registers the API group and adds types to a scheme
|
||||||
|
func Install(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *registered.APIRegistrationManager, scheme *runtime.Scheme) {
|
||||||
|
if err := announced.NewGroupMetaFactory(
|
||||||
|
&announced.GroupMetaFactoryArgs{
|
||||||
|
GroupName: internalapi.GroupName,
|
||||||
|
VersionPreferenceOrder: []string{versionedapi.SchemeGroupVersion.Version},
|
||||||
|
AddInternalObjectsToScheme: internalapi.AddToScheme,
|
||||||
|
},
|
||||||
|
announced.VersionToSchemeFunc{
|
||||||
|
versionedapi.SchemeGroupVersion.Version: versionedapi.AddToScheme,
|
||||||
|
},
|
||||||
|
).Announce(groupFactoryRegistry).RegisterAndEnable(registry, scheme); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 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 eventratelimit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
||||||
|
AddToScheme = SchemeBuilder.AddToScheme
|
||||||
|
)
|
||||||
|
|
||||||
|
// GroupName is the group name use in this package
|
||||||
|
const GroupName = "eventratelimit.admission.k8s.io"
|
||||||
|
|
||||||
|
// SchemeGroupVersion is group version used to register these objects
|
||||||
|
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
|
||||||
|
|
||||||
|
// Kind takes an unqualified kind and returns a Group qualified GroupKind
|
||||||
|
func Kind(kind string) schema.GroupKind {
|
||||||
|
return SchemeGroupVersion.WithKind(kind).GroupKind()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resource takes an unqualified resource and returns a Group qualified GroupResource
|
||||||
|
func Resource(resource string) schema.GroupResource {
|
||||||
|
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||||
|
}
|
||||||
|
|
||||||
|
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||||
|
// TODO this will get cleaned up with the scheme types are fixed
|
||||||
|
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||||
|
&Configuration{},
|
||||||
|
)
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 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 eventratelimit
|
||||||
|
|
||||||
|
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
|
// LimitType is the type of the limit (e.g., per-namespace)
|
||||||
|
type LimitType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ServerLimitType is a type of limit where there is one bucket shared by
|
||||||
|
// all of the event queries received by the API Server.
|
||||||
|
ServerLimitType LimitType = "Server"
|
||||||
|
// NamespaceLimitType is a type of limit where there is one bucket used by
|
||||||
|
// each namespace
|
||||||
|
NamespaceLimitType LimitType = "Namespace"
|
||||||
|
// UserLimitType is a type of limit where there is one bucket used by each
|
||||||
|
// user
|
||||||
|
UserLimitType LimitType = "User"
|
||||||
|
// SourceAndObjectLimitType is a type of limit where there is one bucket used
|
||||||
|
// by each combination of source and involved object of the event.
|
||||||
|
SourceAndObjectLimitType LimitType = "SourceAndObject"
|
||||||
|
)
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// Configuration provides configuration for the EventRateLimit admission
|
||||||
|
// controller.
|
||||||
|
type Configuration struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
|
||||||
|
// limits are the limits to place on event queries received.
|
||||||
|
// Limits can be placed on events received server-wide, per namespace,
|
||||||
|
// per user, and per source+object.
|
||||||
|
// At least one limit is required.
|
||||||
|
Limits []Limit `json:"limits"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit is the configuration for a particular limit type
|
||||||
|
type Limit struct {
|
||||||
|
// type is the type of limit to which this configuration applies
|
||||||
|
Type LimitType `json:"type"`
|
||||||
|
|
||||||
|
// qps is the number of event queries per second that are allowed for this
|
||||||
|
// type of limit. The qps and burst fields are used together to determine if
|
||||||
|
// a particular event query is accepted. The qps determines how many queries
|
||||||
|
// are accepted once the burst amount of queries has been exhausted.
|
||||||
|
QPS int32 `json:"qps"`
|
||||||
|
|
||||||
|
// burst is the burst number of event queries that are allowed for this type
|
||||||
|
// of limit. The qps and burst fields are used together to determine if a
|
||||||
|
// particular event query is accepted. The burst determines the maximum size
|
||||||
|
// of the allowance granted for a particular bucket. For example, if the burst
|
||||||
|
// is 10 and the qps is 3, then the admission control will accept 10 queries
|
||||||
|
// before blocking any queries. Every second, 3 more queries will be allowed.
|
||||||
|
// If some of that allowance is not used, then it will roll over to the next
|
||||||
|
// second, until the maximum allowance of 10 is reached.
|
||||||
|
Burst int32 `json:"burst"`
|
||||||
|
|
||||||
|
// cacheSize is the size of the LRU cache for this type of limit. If a bucket
|
||||||
|
// is evicted from the cache, then the allowance for that bucket is reset. If
|
||||||
|
// more queries are later received for an evicted bucket, then that bucket
|
||||||
|
// will re-enter the cache with a clean slate, giving that bucket a full
|
||||||
|
// allowance of burst queries.
|
||||||
|
//
|
||||||
|
// The default cache size is 4096.
|
||||||
|
//
|
||||||
|
// If limitType is 'server', then cacheSize is ignored.
|
||||||
|
// +optional
|
||||||
|
CacheSize int32 `json:"cacheSize,omitempty"`
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_library",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = [
|
||||||
|
"defaults.go",
|
||||||
|
"doc.go",
|
||||||
|
"register.go",
|
||||||
|
"types.go",
|
||||||
|
"zz_generated.conversion.go",
|
||||||
|
"zz_generated.deepcopy.go",
|
||||||
|
"zz_generated.defaults.go",
|
||||||
|
],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
)
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 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 kruntime "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
|
func addDefaultingFuncs(scheme *kruntime.Scheme) error {
|
||||||
|
return RegisterDefaults(scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetDefaults_Configuration(obj *Configuration) {}
|
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen=package,register
|
||||||
|
// +k8s:conversion-gen=k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit
|
||||||
|
// +k8s:defaulter-gen=TypeMeta
|
||||||
|
|
||||||
|
// Package v1alpha1 is the v1alpha1 version of the API.
|
||||||
|
// +groupName=eventratelimit.admission.k8s.io
|
||||||
|
package v1alpha1 // import "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1"
|
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 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/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GroupName is the group name use in this package
|
||||||
|
const GroupName = "eventratelimit.admission.k8s.io"
|
||||||
|
|
||||||
|
// SchemeGroupVersion is group version used to register these objects
|
||||||
|
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api.
|
||||||
|
// localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes.
|
||||||
|
SchemeBuilder runtime.SchemeBuilder
|
||||||
|
localSchemeBuilder = &SchemeBuilder
|
||||||
|
AddToScheme = localSchemeBuilder.AddToScheme
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// We only register manually written functions here. The registration of the
|
||||||
|
// generated functions takes place in the generated files. The separation
|
||||||
|
// makes the code compile even when the generated files are missing.
|
||||||
|
localSchemeBuilder.Register(addKnownTypes, addDefaultingFuncs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||||
|
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||||
|
&Configuration{},
|
||||||
|
)
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 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 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
|
// LimitType is the type of the limit (e.g., per-namespace)
|
||||||
|
type LimitType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ServerLimitType is a type of limit where there is one bucket shared by
|
||||||
|
// all of the event queries received by the API Server.
|
||||||
|
ServerLimitType LimitType = "Server"
|
||||||
|
// NamespaceLimitType is a type of limit where there is one bucket used by
|
||||||
|
// each namespace
|
||||||
|
NamespaceLimitType LimitType = "Namespace"
|
||||||
|
// UserLimitType is a type of limit where there is one bucket used by each
|
||||||
|
// user
|
||||||
|
UserLimitType LimitType = "User"
|
||||||
|
// SourceAndObjectLimitType is a type of limit where there is one bucket used
|
||||||
|
// by each combination of source and involved object of the event.
|
||||||
|
SourceAndObjectLimitType LimitType = "SourceAndObject"
|
||||||
|
)
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// Configuration provides configuration for the EventRateLimit admission
|
||||||
|
// controller.
|
||||||
|
type Configuration struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
|
||||||
|
// limits are the limits to place on event queries received.
|
||||||
|
// Limits can be placed on events received server-wide, per namespace,
|
||||||
|
// per user, and per source+object.
|
||||||
|
// At least one limit is required.
|
||||||
|
Limits []Limit `json:"limits"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit is the configuration for a particular limit type
|
||||||
|
type Limit struct {
|
||||||
|
// type is the type of limit to which this configuration applies
|
||||||
|
Type LimitType `json:"type"`
|
||||||
|
|
||||||
|
// qps is the number of event queries per second that are allowed for this
|
||||||
|
// type of limit. The qps and burst fields are used together to determine if
|
||||||
|
// a particular event query is accepted. The qps determines how many queries
|
||||||
|
// are accepted once the burst amount of queries has been exhausted.
|
||||||
|
QPS int32 `json:"qps"`
|
||||||
|
|
||||||
|
// burst is the burst number of event queries that are allowed for this type
|
||||||
|
// of limit. The qps and burst fields are used together to determine if a
|
||||||
|
// particular event query is accepted. The burst determines the maximum size
|
||||||
|
// of the allowance granted for a particular bucket. For example, if the burst
|
||||||
|
// is 10 and the qps is 3, then the admission control will accept 10 queries
|
||||||
|
// before blocking any queries. Every second, 3 more queries will be allowed.
|
||||||
|
// If some of that allowance is not used, then it will roll over to the next
|
||||||
|
// second, until the maximum allowance of 10 is reached.
|
||||||
|
Burst int32 `json:"burst"`
|
||||||
|
|
||||||
|
// cacheSize is the size of the LRU cache for this type of limit. If a bucket
|
||||||
|
// is evicted from the cache, then the allowance for that bucket is reset. If
|
||||||
|
// more queries are later received for an evicted bucket, then that bucket
|
||||||
|
// will re-enter the cache with a clean slate, giving that bucket a full
|
||||||
|
// allowance of burst queries.
|
||||||
|
//
|
||||||
|
// The default cache size is 4096.
|
||||||
|
//
|
||||||
|
// If limitType is 'server', then cacheSize is ignored.
|
||||||
|
// +optional
|
||||||
|
CacheSize int32 `json:"cacheSize,omitempty"`
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2017 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This file was autogenerated by conversion-gen. Do not edit it manually!
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
conversion "k8s.io/apimachinery/pkg/conversion"
|
||||||
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
eventratelimit "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit"
|
||||||
|
unsafe "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
localSchemeBuilder.Register(RegisterConversions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterConversions adds conversion functions to the given scheme.
|
||||||
|
// Public to allow building arbitrary schemes.
|
||||||
|
func RegisterConversions(scheme *runtime.Scheme) error {
|
||||||
|
return scheme.AddGeneratedConversionFuncs(
|
||||||
|
Convert_v1alpha1_Configuration_To_eventratelimit_Configuration,
|
||||||
|
Convert_eventratelimit_Configuration_To_v1alpha1_Configuration,
|
||||||
|
Convert_v1alpha1_Limit_To_eventratelimit_Limit,
|
||||||
|
Convert_eventratelimit_Limit_To_v1alpha1_Limit,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1alpha1_Configuration_To_eventratelimit_Configuration(in *Configuration, out *eventratelimit.Configuration, s conversion.Scope) error {
|
||||||
|
out.Limits = *(*[]eventratelimit.Limit)(unsafe.Pointer(&in.Limits))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1alpha1_Configuration_To_eventratelimit_Configuration is an autogenerated conversion function.
|
||||||
|
func Convert_v1alpha1_Configuration_To_eventratelimit_Configuration(in *Configuration, out *eventratelimit.Configuration, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1alpha1_Configuration_To_eventratelimit_Configuration(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_eventratelimit_Configuration_To_v1alpha1_Configuration(in *eventratelimit.Configuration, out *Configuration, s conversion.Scope) error {
|
||||||
|
out.Limits = *(*[]Limit)(unsafe.Pointer(&in.Limits))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_eventratelimit_Configuration_To_v1alpha1_Configuration is an autogenerated conversion function.
|
||||||
|
func Convert_eventratelimit_Configuration_To_v1alpha1_Configuration(in *eventratelimit.Configuration, out *Configuration, s conversion.Scope) error {
|
||||||
|
return autoConvert_eventratelimit_Configuration_To_v1alpha1_Configuration(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1alpha1_Limit_To_eventratelimit_Limit(in *Limit, out *eventratelimit.Limit, s conversion.Scope) error {
|
||||||
|
out.Type = eventratelimit.LimitType(in.Type)
|
||||||
|
out.QPS = in.QPS
|
||||||
|
out.Burst = in.Burst
|
||||||
|
out.CacheSize = in.CacheSize
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1alpha1_Limit_To_eventratelimit_Limit is an autogenerated conversion function.
|
||||||
|
func Convert_v1alpha1_Limit_To_eventratelimit_Limit(in *Limit, out *eventratelimit.Limit, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1alpha1_Limit_To_eventratelimit_Limit(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_eventratelimit_Limit_To_v1alpha1_Limit(in *eventratelimit.Limit, out *Limit, s conversion.Scope) error {
|
||||||
|
out.Type = LimitType(in.Type)
|
||||||
|
out.QPS = in.QPS
|
||||||
|
out.Burst = in.Burst
|
||||||
|
out.CacheSize = in.CacheSize
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_eventratelimit_Limit_To_v1alpha1_Limit is an autogenerated conversion function.
|
||||||
|
func Convert_eventratelimit_Limit_To_v1alpha1_Limit(in *eventratelimit.Limit, out *Limit, s conversion.Scope) error {
|
||||||
|
return autoConvert_eventratelimit_Limit_To_v1alpha1_Limit(in, out, s)
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2017 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This file was autogenerated by deepcopy-gen. Do not edit it manually!
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
conversion "k8s.io/apimachinery/pkg/conversion"
|
||||||
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
reflect "reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
SchemeBuilder.Register(RegisterDeepCopies)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterDeepCopies adds deep-copy functions to the given scheme. Public
|
||||||
|
// to allow building arbitrary schemes.
|
||||||
|
//
|
||||||
|
// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented.
|
||||||
|
func RegisterDeepCopies(scheme *runtime.Scheme) error {
|
||||||
|
return scheme.AddGeneratedDeepCopyFuncs(
|
||||||
|
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
|
||||||
|
in.(*Configuration).DeepCopyInto(out.(*Configuration))
|
||||||
|
return nil
|
||||||
|
}, InType: reflect.TypeOf(&Configuration{})},
|
||||||
|
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
|
||||||
|
in.(*Limit).DeepCopyInto(out.(*Limit))
|
||||||
|
return nil
|
||||||
|
}, InType: reflect.TypeOf(&Limit{})},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Configuration) DeepCopyInto(out *Configuration) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
if in.Limits != nil {
|
||||||
|
in, out := &in.Limits, &out.Limits
|
||||||
|
*out = make([]Limit, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Configuration.
|
||||||
|
func (in *Configuration) DeepCopy() *Configuration {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Configuration)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *Configuration) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Limit) DeepCopyInto(out *Limit) {
|
||||||
|
*out = *in
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Limit.
|
||||||
|
func (in *Limit) DeepCopy() *Limit {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Limit)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2017 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This file was autogenerated by defaulter-gen. Do not edit it manually!
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterDefaults adds defaulters functions to the given scheme.
|
||||||
|
// Public to allow building arbitrary schemes.
|
||||||
|
// All generated defaulters are covering - they call all nested defaulters.
|
||||||
|
func RegisterDefaults(scheme *runtime.Scheme) error {
|
||||||
|
scheme.AddTypeDefaultingFunc(&Configuration{}, func(obj interface{}) { SetObjectDefaults_Configuration(obj.(*Configuration)) })
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetObjectDefaults_Configuration(in *Configuration) {
|
||||||
|
SetDefaults_Configuration(in)
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
load(
|
||||||
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
|
"go_library",
|
||||||
|
"go_test",
|
||||||
|
)
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["validation.go"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = [
|
||||||
|
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["validation_test.go"],
|
||||||
|
library = ":go_default_library",
|
||||||
|
tags = ["automanaged"],
|
||||||
|
deps = ["//plugin/pkg/admission/eventratelimit/apis/eventratelimit:go_default_library"],
|
||||||
|
)
|
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 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 (
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
|
||||||
|
eventratelimitapi "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit"
|
||||||
|
)
|
||||||
|
|
||||||
|
var limitTypes = map[eventratelimitapi.LimitType]bool{
|
||||||
|
eventratelimitapi.ServerLimitType: true,
|
||||||
|
eventratelimitapi.NamespaceLimitType: true,
|
||||||
|
eventratelimitapi.UserLimitType: true,
|
||||||
|
eventratelimitapi.SourceAndObjectLimitType: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateConfiguration validates the configuration.
|
||||||
|
func ValidateConfiguration(config *eventratelimitapi.Configuration) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
limitsPath := field.NewPath("limits")
|
||||||
|
if len(config.Limits) == 0 {
|
||||||
|
allErrs = append(allErrs, field.Invalid(limitsPath, config.Limits, "must not be empty"))
|
||||||
|
}
|
||||||
|
for i, limit := range config.Limits {
|
||||||
|
idxPath := limitsPath.Index(i)
|
||||||
|
if !limitTypes[limit.Type] {
|
||||||
|
allowedValues := make([]string, len(limitTypes))
|
||||||
|
i := 0
|
||||||
|
for limitType := range limitTypes {
|
||||||
|
allowedValues[i] = string(limitType)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
allErrs = append(allErrs, field.NotSupported(idxPath.Child("type"), limit.Type, allowedValues))
|
||||||
|
}
|
||||||
|
if limit.Burst <= 0 {
|
||||||
|
allErrs = append(allErrs, field.Invalid(idxPath.Child("burst"), limit.Burst, "must be positive"))
|
||||||
|
}
|
||||||
|
if limit.QPS <= 0 {
|
||||||
|
allErrs = append(allErrs, field.Invalid(idxPath.Child("qps"), limit.QPS, "must be positive"))
|
||||||
|
}
|
||||||
|
if limit.Type != eventratelimitapi.ServerLimitType {
|
||||||
|
if limit.CacheSize < 0 {
|
||||||
|
allErrs = append(allErrs, field.Invalid(idxPath.Child("cacheSize"), limit.CacheSize, "must not be negative"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
@ -0,0 +1,192 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 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 (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
eventratelimitapi "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidateConfiguration(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
config eventratelimitapi.Configuration
|
||||||
|
expectedResult bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid server",
|
||||||
|
config: eventratelimitapi.Configuration{
|
||||||
|
Limits: []eventratelimitapi.Limit{
|
||||||
|
{
|
||||||
|
Type: "Server",
|
||||||
|
Burst: 5,
|
||||||
|
QPS: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid namespace",
|
||||||
|
config: eventratelimitapi.Configuration{
|
||||||
|
Limits: []eventratelimitapi.Limit{
|
||||||
|
{
|
||||||
|
Type: "Namespace",
|
||||||
|
Burst: 10,
|
||||||
|
QPS: 2,
|
||||||
|
CacheSize: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid user",
|
||||||
|
config: eventratelimitapi.Configuration{
|
||||||
|
Limits: []eventratelimitapi.Limit{
|
||||||
|
{
|
||||||
|
Type: "User",
|
||||||
|
Burst: 10,
|
||||||
|
QPS: 2,
|
||||||
|
CacheSize: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid source+object",
|
||||||
|
config: eventratelimitapi.Configuration{
|
||||||
|
Limits: []eventratelimitapi.Limit{
|
||||||
|
{
|
||||||
|
Type: "SourceAndObject",
|
||||||
|
Burst: 5,
|
||||||
|
QPS: 1,
|
||||||
|
CacheSize: 1000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid multiple",
|
||||||
|
config: eventratelimitapi.Configuration{
|
||||||
|
Limits: []eventratelimitapi.Limit{
|
||||||
|
{
|
||||||
|
Type: "Server",
|
||||||
|
Burst: 5,
|
||||||
|
QPS: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "Namespace",
|
||||||
|
Burst: 10,
|
||||||
|
QPS: 2,
|
||||||
|
CacheSize: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "SourceAndObject",
|
||||||
|
Burst: 25,
|
||||||
|
QPS: 10,
|
||||||
|
CacheSize: 1000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing limits",
|
||||||
|
config: eventratelimitapi.Configuration{},
|
||||||
|
expectedResult: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing type",
|
||||||
|
config: eventratelimitapi.Configuration{
|
||||||
|
Limits: []eventratelimitapi.Limit{
|
||||||
|
{
|
||||||
|
Burst: 25,
|
||||||
|
QPS: 10,
|
||||||
|
CacheSize: 1000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedResult: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid type",
|
||||||
|
config: eventratelimitapi.Configuration{
|
||||||
|
Limits: []eventratelimitapi.Limit{
|
||||||
|
{
|
||||||
|
Type: "unknown-type",
|
||||||
|
Burst: 25,
|
||||||
|
QPS: 10,
|
||||||
|
CacheSize: 1000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedResult: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing burst",
|
||||||
|
config: eventratelimitapi.Configuration{
|
||||||
|
Limits: []eventratelimitapi.Limit{
|
||||||
|
{
|
||||||
|
Type: "Server",
|
||||||
|
QPS: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedResult: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing qps",
|
||||||
|
config: eventratelimitapi.Configuration{
|
||||||
|
Limits: []eventratelimitapi.Limit{
|
||||||
|
{
|
||||||
|
Type: "Server",
|
||||||
|
Burst: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedResult: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negative cache size",
|
||||||
|
config: eventratelimitapi.Configuration{
|
||||||
|
Limits: []eventratelimitapi.Limit{
|
||||||
|
{
|
||||||
|
Type: "Namespace",
|
||||||
|
Burst: 10,
|
||||||
|
QPS: 2,
|
||||||
|
CacheSize: -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedResult: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
errs := ValidateConfiguration(&tc.config)
|
||||||
|
if e, a := tc.expectedResult, len(errs) == 0; e != a {
|
||||||
|
if e {
|
||||||
|
t.Errorf("%v: expected success: %v", tc.name, errs)
|
||||||
|
} else {
|
||||||
|
t.Errorf("%v: expected failure", tc.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2017 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This file was autogenerated by deepcopy-gen. Do not edit it manually!
|
||||||
|
|
||||||
|
package eventratelimit
|
||||||
|
|
||||||
|
import (
|
||||||
|
conversion "k8s.io/apimachinery/pkg/conversion"
|
||||||
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
reflect "reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
SchemeBuilder.Register(RegisterDeepCopies)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterDeepCopies adds deep-copy functions to the given scheme. Public
|
||||||
|
// to allow building arbitrary schemes.
|
||||||
|
//
|
||||||
|
// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented.
|
||||||
|
func RegisterDeepCopies(scheme *runtime.Scheme) error {
|
||||||
|
return scheme.AddGeneratedDeepCopyFuncs(
|
||||||
|
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
|
||||||
|
in.(*Configuration).DeepCopyInto(out.(*Configuration))
|
||||||
|
return nil
|
||||||
|
}, InType: reflect.TypeOf(&Configuration{})},
|
||||||
|
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
|
||||||
|
in.(*Limit).DeepCopyInto(out.(*Limit))
|
||||||
|
return nil
|
||||||
|
}, InType: reflect.TypeOf(&Limit{})},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Configuration) DeepCopyInto(out *Configuration) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
if in.Limits != nil {
|
||||||
|
in, out := &in.Limits, &out.Limits
|
||||||
|
*out = make([]Limit, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Configuration.
|
||||||
|
func (in *Configuration) DeepCopy() *Configuration {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Configuration)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *Configuration) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Limit) DeepCopyInto(out *Limit) {
|
||||||
|
*out = *in
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Limit.
|
||||||
|
func (in *Limit) DeepCopy() *Limit {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Limit)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
57
plugin/pkg/admission/eventratelimit/cache.go
Normal file
57
plugin/pkg/admission/eventratelimit/cache.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 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 eventratelimit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/golang-lru"
|
||||||
|
|
||||||
|
"k8s.io/client-go/util/flowcontrol"
|
||||||
|
)
|
||||||
|
|
||||||
|
// cache is an interface for caching the limits of a particular type
|
||||||
|
type cache interface {
|
||||||
|
// get the rate limiter associated with the specified key
|
||||||
|
get(key interface{}) flowcontrol.RateLimiter
|
||||||
|
}
|
||||||
|
|
||||||
|
// singleCache is a cache that only stores a single, constant item
|
||||||
|
type singleCache struct {
|
||||||
|
// the single rate limiter held by the cache
|
||||||
|
rateLimiter flowcontrol.RateLimiter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *singleCache) get(key interface{}) flowcontrol.RateLimiter {
|
||||||
|
return c.rateLimiter
|
||||||
|
}
|
||||||
|
|
||||||
|
// lruCache is a least-recently-used cache
|
||||||
|
type lruCache struct {
|
||||||
|
// factory to use to create new rate limiters
|
||||||
|
rateLimiterFactory func() flowcontrol.RateLimiter
|
||||||
|
// the actual LRU cache
|
||||||
|
cache *lru.Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *lruCache) get(key interface{}) flowcontrol.RateLimiter {
|
||||||
|
value, found := c.cache.Get(key)
|
||||||
|
if !found {
|
||||||
|
rateLimter := c.rateLimiterFactory()
|
||||||
|
c.cache.Add(key, rateLimter)
|
||||||
|
return rateLimter
|
||||||
|
}
|
||||||
|
return value.(flowcontrol.RateLimiter)
|
||||||
|
}
|
119
plugin/pkg/admission/eventratelimit/cache_test.go
Normal file
119
plugin/pkg/admission/eventratelimit/cache_test.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 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 eventratelimit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/golang-lru"
|
||||||
|
|
||||||
|
"k8s.io/client-go/util/flowcontrol"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSingleCache(t *testing.T) {
|
||||||
|
rateLimiter := flowcontrol.NewTokenBucketRateLimiter(1., 1)
|
||||||
|
cache := singleCache{
|
||||||
|
rateLimiter: rateLimiter,
|
||||||
|
}
|
||||||
|
cases := []interface{}{nil, "key1", "key2"}
|
||||||
|
for _, tc := range cases {
|
||||||
|
actual := cache.get(tc)
|
||||||
|
if e, a := rateLimiter, actual; e != a {
|
||||||
|
t.Errorf("unexpected entry in cache for key %v: expected %v, got %v", tc, e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLRUCache(t *testing.T) {
|
||||||
|
rateLimiters := []flowcontrol.RateLimiter{
|
||||||
|
flowcontrol.NewTokenBucketRateLimiter(1., 1),
|
||||||
|
flowcontrol.NewTokenBucketRateLimiter(2., 2),
|
||||||
|
flowcontrol.NewTokenBucketRateLimiter(3., 3),
|
||||||
|
flowcontrol.NewTokenBucketRateLimiter(4., 4),
|
||||||
|
}
|
||||||
|
nextRateLimiter := 0
|
||||||
|
rateLimiterFactory := func() flowcontrol.RateLimiter {
|
||||||
|
rateLimiter := rateLimiters[nextRateLimiter]
|
||||||
|
nextRateLimiter++
|
||||||
|
return rateLimiter
|
||||||
|
}
|
||||||
|
underlyingCache, err := lru.New(2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create LRU cache: %v", err)
|
||||||
|
}
|
||||||
|
cache := lruCache{
|
||||||
|
rateLimiterFactory: rateLimiterFactory,
|
||||||
|
cache: underlyingCache,
|
||||||
|
}
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
key int
|
||||||
|
expected flowcontrol.RateLimiter
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "first added",
|
||||||
|
key: 0,
|
||||||
|
expected: rateLimiters[0],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "first obtained",
|
||||||
|
key: 0,
|
||||||
|
expected: rateLimiters[0],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "second added",
|
||||||
|
key: 1,
|
||||||
|
expected: rateLimiters[1],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "second obtained",
|
||||||
|
key: 1,
|
||||||
|
expected: rateLimiters[1],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "first obtained second time",
|
||||||
|
key: 0,
|
||||||
|
expected: rateLimiters[0],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "third added",
|
||||||
|
key: 2,
|
||||||
|
expected: rateLimiters[2],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "third obtained",
|
||||||
|
key: 2,
|
||||||
|
expected: rateLimiters[2],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "first obtained third time",
|
||||||
|
key: 0,
|
||||||
|
expected: rateLimiters[0],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "second re-added after eviction",
|
||||||
|
key: 1,
|
||||||
|
expected: rateLimiters[3],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
actual := cache.get(tc.key)
|
||||||
|
if e, a := tc.expected, actual; e != a {
|
||||||
|
t.Errorf("%v: unexpected entry in cache for key %v: expected %v, got %v", tc.name, tc.key, e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
plugin/pkg/admission/eventratelimit/clock.go
Normal file
34
plugin/pkg/admission/eventratelimit/clock.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 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 eventratelimit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// realClock implements flowcontrol.Clock in terms of standard time functions.
|
||||||
|
type realClock struct{}
|
||||||
|
|
||||||
|
// Now is identical to time.Now.
|
||||||
|
func (realClock) Now() time.Time {
|
||||||
|
return time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sleep is identical to time.Sleep.
|
||||||
|
func (realClock) Sleep(d time.Duration) {
|
||||||
|
time.Sleep(d)
|
||||||
|
}
|
72
plugin/pkg/admission/eventratelimit/config.go
Normal file
72
plugin/pkg/admission/eventratelimit/config.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 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 eventratelimit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/apimachinery/announced"
|
||||||
|
"k8s.io/apimachinery/pkg/apimachinery/registered"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
|
eventratelimitapi "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit"
|
||||||
|
"k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/install"
|
||||||
|
eventratelimitv1alpha1 "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
groupFactoryRegistry = make(announced.APIGroupFactoryRegistry)
|
||||||
|
registry = registered.NewOrDie(os.Getenv("KUBE_API_VERSIONS"))
|
||||||
|
scheme = runtime.NewScheme()
|
||||||
|
codecs = serializer.NewCodecFactory(scheme)
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
install.Install(groupFactoryRegistry, registry, scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadConfiguration loads the provided configuration.
|
||||||
|
func LoadConfiguration(config io.Reader) (*eventratelimitapi.Configuration, error) {
|
||||||
|
// if no config is provided, return a default configuration
|
||||||
|
if config == nil {
|
||||||
|
externalConfig := &eventratelimitv1alpha1.Configuration{}
|
||||||
|
scheme.Default(externalConfig)
|
||||||
|
internalConfig := &eventratelimitapi.Configuration{}
|
||||||
|
if err := scheme.Convert(externalConfig, internalConfig, nil); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return internalConfig, nil
|
||||||
|
}
|
||||||
|
// we have a config so parse it.
|
||||||
|
data, err := ioutil.ReadAll(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
decoder := codecs.UniversalDecoder()
|
||||||
|
decodedObj, err := runtime.Decode(decoder, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resourceQuotaConfiguration, ok := decodedObj.(*eventratelimitapi.Configuration)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected type: %T", decodedObj)
|
||||||
|
}
|
||||||
|
return resourceQuotaConfiguration, nil
|
||||||
|
}
|
18
plugin/pkg/admission/eventratelimit/doc.go
Normal file
18
plugin/pkg/admission/eventratelimit/doc.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 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 eventratelimit contains an admission controller that enforces a rate limit on events
|
||||||
|
package eventratelimit // import "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit"
|
145
plugin/pkg/admission/eventratelimit/limitenforcer.go
Normal file
145
plugin/pkg/admission/eventratelimit/limitenforcer.go
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 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 eventratelimit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/golang-lru"
|
||||||
|
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
"k8s.io/apiserver/pkg/admission"
|
||||||
|
"k8s.io/client-go/util/flowcontrol"
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
eventratelimitapi "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// cache size to use if the user did not specify a cache size
|
||||||
|
defaultCacheSize = 4096
|
||||||
|
)
|
||||||
|
|
||||||
|
// limitEnforcer enforces a single type of event rate limit, such as server, namespace, or source+object
|
||||||
|
type limitEnforcer struct {
|
||||||
|
// type of this limit
|
||||||
|
limitType eventratelimitapi.LimitType
|
||||||
|
// cache for holding the rate limiters
|
||||||
|
cache cache
|
||||||
|
// a keyFunc which is responsible for computing a single key based on input
|
||||||
|
keyFunc func(admission.Attributes) string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLimitEnforcer(config eventratelimitapi.Limit, clock flowcontrol.Clock) (*limitEnforcer, error) {
|
||||||
|
rateLimiterFactory := func() flowcontrol.RateLimiter {
|
||||||
|
return flowcontrol.NewTokenBucketRateLimiterWithClock(float32(config.QPS), int(config.Burst), clock)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Type == eventratelimitapi.ServerLimitType {
|
||||||
|
return &limitEnforcer{
|
||||||
|
limitType: config.Type,
|
||||||
|
cache: &singleCache{
|
||||||
|
rateLimiter: rateLimiterFactory(),
|
||||||
|
},
|
||||||
|
keyFunc: getServerKey,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheSize := int(config.CacheSize)
|
||||||
|
if cacheSize == 0 {
|
||||||
|
cacheSize = defaultCacheSize
|
||||||
|
}
|
||||||
|
underlyingCache, err := lru.New(cacheSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not create lru cache: %v", err)
|
||||||
|
}
|
||||||
|
cache := &lruCache{
|
||||||
|
rateLimiterFactory: rateLimiterFactory,
|
||||||
|
cache: underlyingCache,
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyFunc func(admission.Attributes) string
|
||||||
|
switch t := config.Type; t {
|
||||||
|
case eventratelimitapi.NamespaceLimitType:
|
||||||
|
keyFunc = getNamespaceKey
|
||||||
|
case eventratelimitapi.UserLimitType:
|
||||||
|
keyFunc = getUserKey
|
||||||
|
case eventratelimitapi.SourceAndObjectLimitType:
|
||||||
|
keyFunc = getSourceAndObjectKey
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown event rate limit type: %v", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &limitEnforcer{
|
||||||
|
limitType: config.Type,
|
||||||
|
cache: cache,
|
||||||
|
keyFunc: keyFunc,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enforcer *limitEnforcer) accept(attr admission.Attributes) error {
|
||||||
|
key := enforcer.keyFunc(attr)
|
||||||
|
rateLimiter := enforcer.cache.get(key)
|
||||||
|
|
||||||
|
// ensure we have available rate
|
||||||
|
allow := rateLimiter.TryAccept()
|
||||||
|
|
||||||
|
if !allow {
|
||||||
|
return apierrors.NewTooManyRequestsError(fmt.Sprintf("limit reached on type %v for key %v", enforcer.limitType, key))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getServerKey(attr admission.Attributes) string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// getNamespaceKey returns a cache key that is based on the namespace of the event request
|
||||||
|
func getNamespaceKey(attr admission.Attributes) string {
|
||||||
|
return attr.GetNamespace()
|
||||||
|
}
|
||||||
|
|
||||||
|
// getUserKey returns a cache key that is based on the user of the event request
|
||||||
|
func getUserKey(attr admission.Attributes) string {
|
||||||
|
userInfo := attr.GetUserInfo()
|
||||||
|
if userInfo == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return userInfo.GetName()
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSourceAndObjectKey returns a cache key that is based on the source+object of the event
|
||||||
|
func getSourceAndObjectKey(attr admission.Attributes) string {
|
||||||
|
object := attr.GetObject()
|
||||||
|
if object == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
event, ok := object.(*api.Event)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.Join([]string{
|
||||||
|
event.Source.Component,
|
||||||
|
event.Source.Host,
|
||||||
|
event.InvolvedObject.Kind,
|
||||||
|
event.InvolvedObject.Namespace,
|
||||||
|
event.InvolvedObject.Name,
|
||||||
|
string(event.InvolvedObject.UID),
|
||||||
|
event.InvolvedObject.APIVersion,
|
||||||
|
}, "")
|
||||||
|
}
|
@ -28,6 +28,12 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// StatusTooManyRequests means the server experienced too many requests within a
|
||||||
|
// given window and that the client must wait to perform the action again.
|
||||||
|
StatusTooManyRequests = 429
|
||||||
|
)
|
||||||
|
|
||||||
// StatusError is an error intended for consumption by a REST API server; it can also be
|
// StatusError is an error intended for consumption by a REST API server; it can also be
|
||||||
// reconstructed by clients from a REST response. Public to allow easy type switches.
|
// reconstructed by clients from a REST response. Public to allow easy type switches.
|
||||||
type StatusError struct {
|
type StatusError struct {
|
||||||
@ -309,6 +315,18 @@ func NewTimeoutError(message string, retryAfterSeconds int) *StatusError {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewTooManyRequestsError returns an error indicating that the request was rejected because
|
||||||
|
// the server has received too many requests. Client should wait and retry. But if the request
|
||||||
|
// is perishable, then the client should not retry the request.
|
||||||
|
func NewTooManyRequestsError(message string) *StatusError {
|
||||||
|
return &StatusError{metav1.Status{
|
||||||
|
Status: metav1.StatusFailure,
|
||||||
|
Code: StatusTooManyRequests,
|
||||||
|
Reason: metav1.StatusReasonTooManyRequests,
|
||||||
|
Message: fmt.Sprintf("Too many requests: %s", message),
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
// NewGenericServerResponse returns a new error for server responses that are not in a recognizable form.
|
// NewGenericServerResponse returns a new error for server responses that are not in a recognizable form.
|
||||||
func NewGenericServerResponse(code int, verb string, qualifiedResource schema.GroupResource, name, serverMessage string, retryAfterSeconds int, isUnexpectedResponse bool) *StatusError {
|
func NewGenericServerResponse(code int, verb string, qualifiedResource schema.GroupResource, name, serverMessage string, retryAfterSeconds int, isUnexpectedResponse bool) *StatusError {
|
||||||
reason := metav1.StatusReasonUnknown
|
reason := metav1.StatusReasonUnknown
|
||||||
|
Loading…
Reference in New Issue
Block a user