Add generated file
This PR adds generated files under pkg/client and vendor folder.
This commit is contained in:
43
vendor/k8s.io/kubernetes/plugin/BUILD
generated
vendored
Normal file
43
vendor/k8s.io/kubernetes/plugin/BUILD
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//plugin/pkg/admission/admit:all-srcs",
|
||||
"//plugin/pkg/admission/alwayspullimages:all-srcs",
|
||||
"//plugin/pkg/admission/antiaffinity:all-srcs",
|
||||
"//plugin/pkg/admission/defaulttolerationseconds:all-srcs",
|
||||
"//plugin/pkg/admission/deny:all-srcs",
|
||||
"//plugin/pkg/admission/eventratelimit:all-srcs",
|
||||
"//plugin/pkg/admission/exec:all-srcs",
|
||||
"//plugin/pkg/admission/extendedresourcetoleration:all-srcs",
|
||||
"//plugin/pkg/admission/gc:all-srcs",
|
||||
"//plugin/pkg/admission/imagepolicy:all-srcs",
|
||||
"//plugin/pkg/admission/limitranger:all-srcs",
|
||||
"//plugin/pkg/admission/namespace/autoprovision:all-srcs",
|
||||
"//plugin/pkg/admission/namespace/exists:all-srcs",
|
||||
"//plugin/pkg/admission/noderestriction:all-srcs",
|
||||
"//plugin/pkg/admission/podnodeselector:all-srcs",
|
||||
"//plugin/pkg/admission/podpreset:all-srcs",
|
||||
"//plugin/pkg/admission/podtolerationrestriction:all-srcs",
|
||||
"//plugin/pkg/admission/priority:all-srcs",
|
||||
"//plugin/pkg/admission/resourcequota:all-srcs",
|
||||
"//plugin/pkg/admission/security:all-srcs",
|
||||
"//plugin/pkg/admission/securitycontext/scdeny:all-srcs",
|
||||
"//plugin/pkg/admission/serviceaccount:all-srcs",
|
||||
"//plugin/pkg/admission/storage/persistentvolume/label:all-srcs",
|
||||
"//plugin/pkg/admission/storage/persistentvolume/resize:all-srcs",
|
||||
"//plugin/pkg/admission/storage/storageclass/setdefault:all-srcs",
|
||||
"//plugin/pkg/admission/storage/storageobjectinuseprotection:all-srcs",
|
||||
"//plugin/pkg/auth:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
12
vendor/k8s.io/kubernetes/plugin/OWNERS
generated
vendored
Normal file
12
vendor/k8s.io/kubernetes/plugin/OWNERS
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
reviewers:
|
||||
- brendandburns
|
||||
- davidopp
|
||||
- dchen1107
|
||||
- lavalamp
|
||||
- thockin
|
||||
approvers:
|
||||
- brendandburns
|
||||
- davidopp
|
||||
- dchen1107
|
||||
- lavalamp
|
||||
- thockin
|
||||
6
vendor/k8s.io/kubernetes/plugin/pkg/admission/OWNERS
generated
vendored
Normal file
6
vendor/k8s.io/kubernetes/plugin/pkg/admission/OWNERS
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
approvers:
|
||||
- derekwaynecarr
|
||||
- deads2k
|
||||
reviewers:
|
||||
- derekwaynecarr
|
||||
- deads2k
|
||||
40
vendor/k8s.io/kubernetes/plugin/pkg/admission/admit/BUILD
generated
vendored
Normal file
40
vendor/k8s.io/kubernetes/plugin/pkg/admission/admit/BUILD
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["admission.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/admit",
|
||||
deps = [
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
64
vendor/k8s.io/kubernetes/plugin/pkg/admission/admit/admission.go
generated
vendored
Normal file
64
vendor/k8s.io/kubernetes/plugin/pkg/admission/admit/admission.go
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
Copyright 2014 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 admit
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
)
|
||||
|
||||
// PluginName indicates name of admission plugin.
|
||||
const PluginName = "AlwaysAdmit"
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
|
||||
return NewAlwaysAdmit(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// alwaysAdmit is an implementation of admission.Interface which always says yes to an admit request.
|
||||
type alwaysAdmit struct{}
|
||||
|
||||
var _ admission.MutationInterface = alwaysAdmit{}
|
||||
var _ admission.ValidationInterface = alwaysAdmit{}
|
||||
|
||||
// Admit makes an admission decision based on the request attributes
|
||||
func (alwaysAdmit) Admit(a admission.Attributes) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate makes an admission decision based on the request attributes. It is NOT allowed to mutate.
|
||||
func (alwaysAdmit) Validate(a admission.Attributes) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handles returns true if this admission controller can handle the given operation
|
||||
// where operation can be one of CREATE, UPDATE, DELETE, or CONNECT
|
||||
func (alwaysAdmit) Handles(operation admission.Operation) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// NewAlwaysAdmit creates a new always admit admission handler
|
||||
func NewAlwaysAdmit() admission.Interface {
|
||||
// DEPRECATED: AlwaysAdmit admit all admission request, it is no use.
|
||||
glog.Warningf("%s admission controller is deprecated. "+
|
||||
"Please remove this controller from your configuration files and scripts.", PluginName)
|
||||
return new(alwaysAdmit)
|
||||
}
|
||||
51
vendor/k8s.io/kubernetes/plugin/pkg/admission/admit/admission_test.go
generated
vendored
Normal file
51
vendor/k8s.io/kubernetes/plugin/pkg/admission/admit/admission_test.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright 2014 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 admit
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
)
|
||||
|
||||
func TestAdmissionNonNilAttribute(t *testing.T) {
|
||||
handler := NewAlwaysAdmit()
|
||||
err := handler.(*alwaysAdmit).Admit(admission.NewAttributesRecord(nil, nil, api.Kind("kind").WithVersion("version"), "namespace", "name", api.Resource("resource").WithVersion("version"), "subresource", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error returned from admission handler")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdmissionNilAttribute(t *testing.T) {
|
||||
handler := NewAlwaysAdmit()
|
||||
err := handler.(*alwaysAdmit).Admit(nil)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error returned from admission handler")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandles(t *testing.T) {
|
||||
handler := NewAlwaysAdmit()
|
||||
tests := []admission.Operation{admission.Create, admission.Connect, admission.Update, admission.Delete}
|
||||
|
||||
for _, test := range tests {
|
||||
if !handler.Handles(test) {
|
||||
t.Errorf("Expected handling all operations, including: %v", test)
|
||||
}
|
||||
}
|
||||
}
|
||||
44
vendor/k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages/BUILD
generated
vendored
Normal file
44
vendor/k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages/BUILD
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["admission.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
124
vendor/k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages/admission.go
generated
vendored
Normal file
124
vendor/k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages/admission.go
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
Copyright 2015 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 alwayspullimages contains an admission controller that modifies every new Pod to force
|
||||
// the image pull policy to Always. This is useful in a multitenant cluster so that users can be
|
||||
// assured that their private images can only be used by those who have the credentials to pull
|
||||
// them. Without this admission controller, once an image has been pulled to a node, any pod from
|
||||
// any user can use it simply by knowing the image's name (assuming the Pod is scheduled onto the
|
||||
// right node), without any authorization check against the image. With this admission controller
|
||||
// enabled, images are always pulled prior to starting containers, which means valid credentials are
|
||||
// required.
|
||||
package alwayspullimages
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
)
|
||||
|
||||
// PluginName indicates name of admission plugin.
|
||||
const PluginName = "AlwaysPullImages"
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
|
||||
return NewAlwaysPullImages(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// AlwaysPullImages is an implementation of admission.Interface.
|
||||
// It looks at all new pods and overrides each container's image pull policy to Always.
|
||||
type AlwaysPullImages struct {
|
||||
*admission.Handler
|
||||
}
|
||||
|
||||
var _ admission.MutationInterface = &AlwaysPullImages{}
|
||||
var _ admission.ValidationInterface = &AlwaysPullImages{}
|
||||
|
||||
// Admit makes an admission decision based on the request attributes
|
||||
func (a *AlwaysPullImages) Admit(attributes admission.Attributes) (err error) {
|
||||
// Ignore all calls to subresources or resources other than pods.
|
||||
if shouldIgnore(attributes) {
|
||||
return nil
|
||||
}
|
||||
pod, ok := attributes.GetObject().(*api.Pod)
|
||||
if !ok {
|
||||
return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
|
||||
}
|
||||
|
||||
for i := range pod.Spec.InitContainers {
|
||||
pod.Spec.InitContainers[i].ImagePullPolicy = api.PullAlways
|
||||
}
|
||||
|
||||
for i := range pod.Spec.Containers {
|
||||
pod.Spec.Containers[i].ImagePullPolicy = api.PullAlways
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate makes sure that all containers are set to always pull images
|
||||
func (*AlwaysPullImages) Validate(attributes admission.Attributes) (err error) {
|
||||
if shouldIgnore(attributes) {
|
||||
return nil
|
||||
}
|
||||
|
||||
pod, ok := attributes.GetObject().(*api.Pod)
|
||||
if !ok {
|
||||
return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
|
||||
}
|
||||
|
||||
for i := range pod.Spec.InitContainers {
|
||||
if pod.Spec.InitContainers[i].ImagePullPolicy != api.PullAlways {
|
||||
return admission.NewForbidden(attributes,
|
||||
field.NotSupported(field.NewPath("spec", "initContainers").Index(i).Child("imagePullPolicy"),
|
||||
pod.Spec.InitContainers[i].ImagePullPolicy, []string{string(api.PullAlways)},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
for i := range pod.Spec.Containers {
|
||||
if pod.Spec.Containers[i].ImagePullPolicy != api.PullAlways {
|
||||
return admission.NewForbidden(attributes,
|
||||
field.NotSupported(field.NewPath("spec", "containers").Index(i).Child("imagePullPolicy"),
|
||||
pod.Spec.Containers[i].ImagePullPolicy, []string{string(api.PullAlways)},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func shouldIgnore(attributes admission.Attributes) bool {
|
||||
// Ignore all calls to subresources or resources other than pods.
|
||||
if len(attributes.GetSubresource()) != 0 || attributes.GetResource().GroupResource() != api.Resource("pods") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// NewAlwaysPullImages creates a new always pull images admission control handler
|
||||
func NewAlwaysPullImages() *AlwaysPullImages {
|
||||
return &AlwaysPullImages{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
}
|
||||
}
|
||||
160
vendor/k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages/admission_test.go
generated
vendored
Normal file
160
vendor/k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages/admission_test.go
generated
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
Copyright 2015 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 alwayspullimages
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
)
|
||||
|
||||
// TestAdmission verifies all create requests for pods result in every container's image pull policy
|
||||
// set to Always
|
||||
func TestAdmission(t *testing.T) {
|
||||
namespace := "test"
|
||||
handler := &AlwaysPullImages{}
|
||||
pod := api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: namespace},
|
||||
Spec: api.PodSpec{
|
||||
InitContainers: []api.Container{
|
||||
{Name: "init1", Image: "image"},
|
||||
{Name: "init2", Image: "image", ImagePullPolicy: api.PullNever},
|
||||
{Name: "init3", Image: "image", ImagePullPolicy: api.PullIfNotPresent},
|
||||
{Name: "init4", Image: "image", ImagePullPolicy: api.PullAlways},
|
||||
},
|
||||
Containers: []api.Container{
|
||||
{Name: "ctr1", Image: "image"},
|
||||
{Name: "ctr2", Image: "image", ImagePullPolicy: api.PullNever},
|
||||
{Name: "ctr3", Image: "image", ImagePullPolicy: api.PullIfNotPresent},
|
||||
{Name: "ctr4", Image: "image", ImagePullPolicy: api.PullAlways},
|
||||
},
|
||||
},
|
||||
}
|
||||
err := handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error returned from admission handler")
|
||||
}
|
||||
for _, c := range pod.Spec.InitContainers {
|
||||
if c.ImagePullPolicy != api.PullAlways {
|
||||
t.Errorf("Container %v: expected pull always, got %v", c, c.ImagePullPolicy)
|
||||
}
|
||||
}
|
||||
for _, c := range pod.Spec.Containers {
|
||||
if c.ImagePullPolicy != api.PullAlways {
|
||||
t.Errorf("Container %v: expected pull always, got %v", c, c.ImagePullPolicy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
namespace := "test"
|
||||
handler := &AlwaysPullImages{}
|
||||
pod := api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: namespace},
|
||||
Spec: api.PodSpec{
|
||||
InitContainers: []api.Container{
|
||||
{Name: "init1", Image: "image"},
|
||||
{Name: "init2", Image: "image", ImagePullPolicy: api.PullNever},
|
||||
{Name: "init3", Image: "image", ImagePullPolicy: api.PullIfNotPresent},
|
||||
{Name: "init4", Image: "image", ImagePullPolicy: api.PullAlways},
|
||||
},
|
||||
Containers: []api.Container{
|
||||
{Name: "ctr1", Image: "image"},
|
||||
{Name: "ctr2", Image: "image", ImagePullPolicy: api.PullNever},
|
||||
{Name: "ctr3", Image: "image", ImagePullPolicy: api.PullIfNotPresent},
|
||||
{Name: "ctr4", Image: "image", ImagePullPolicy: api.PullAlways},
|
||||
},
|
||||
},
|
||||
}
|
||||
expectedError := `pods "123" is forbidden: spec.initContainers[0].imagePullPolicy: Unsupported value: "": supported values: "Always"`
|
||||
err := handler.Validate(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err == nil {
|
||||
t.Fatal("missing expected error")
|
||||
}
|
||||
if err.Error() != expectedError {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOtherResources ensures that this admission controller is a no-op for other resources,
|
||||
// subresources, and non-pods.
|
||||
func TestOtherResources(t *testing.T) {
|
||||
namespace := "testnamespace"
|
||||
name := "testname"
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{Name: "ctr2", Image: "image", ImagePullPolicy: api.PullNever},
|
||||
},
|
||||
},
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
kind string
|
||||
resource string
|
||||
subresource string
|
||||
object runtime.Object
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "non-pod resource",
|
||||
kind: "Foo",
|
||||
resource: "foos",
|
||||
object: pod,
|
||||
},
|
||||
{
|
||||
name: "pod subresource",
|
||||
kind: "Pod",
|
||||
resource: "pods",
|
||||
subresource: "exec",
|
||||
object: pod,
|
||||
},
|
||||
{
|
||||
name: "non-pod object",
|
||||
kind: "Pod",
|
||||
resource: "pods",
|
||||
object: &api.Service{},
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
handler := &AlwaysPullImages{}
|
||||
|
||||
err := handler.Admit(admission.NewAttributesRecord(tc.object, nil, api.Kind(tc.kind).WithVersion("version"), namespace, name, api.Resource(tc.resource).WithVersion("version"), tc.subresource, admission.Create, nil))
|
||||
|
||||
if tc.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("%s: unexpected nil error", tc.name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", tc.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if e, a := api.PullNever, pod.Spec.Containers[0].ImagePullPolicy; e != a {
|
||||
t.Errorf("%s: image pull policy was changed to %s", tc.name, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
48
vendor/k8s.io/kubernetes/plugin/pkg/admission/antiaffinity/BUILD
generated
vendored
Normal file
48
vendor/k8s.io/kubernetes/plugin/pkg/admission/antiaffinity/BUILD
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"admission.go",
|
||||
"doc.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/antiaffinity",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/kubelet/apis:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/kubelet/apis:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
80
vendor/k8s.io/kubernetes/plugin/pkg/admission/antiaffinity/admission.go
generated
vendored
Normal file
80
vendor/k8s.io/kubernetes/plugin/pkg/admission/antiaffinity/admission.go
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package antiaffinity
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis"
|
||||
)
|
||||
|
||||
const PluginName = "LimitPodHardAntiAffinityTopology"
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
|
||||
return NewInterPodAntiAffinity(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// Plugin contains the client used by the admission controller
|
||||
type Plugin struct {
|
||||
*admission.Handler
|
||||
}
|
||||
|
||||
var _ admission.ValidationInterface = &Plugin{}
|
||||
|
||||
// NewInterPodAntiAffinity creates a new instance of the LimitPodHardAntiAffinityTopology admission controller
|
||||
func NewInterPodAntiAffinity() *Plugin {
|
||||
return &Plugin{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
}
|
||||
}
|
||||
|
||||
// Validate will deny any pod that defines AntiAffinity topology key other than kubeletapis.LabelHostname i.e. "kubernetes.io/hostname"
|
||||
// in requiredDuringSchedulingRequiredDuringExecution and requiredDuringSchedulingIgnoredDuringExecution.
|
||||
func (p *Plugin) Validate(attributes admission.Attributes) (err error) {
|
||||
// Ignore all calls to subresources or resources other than pods.
|
||||
if len(attributes.GetSubresource()) != 0 || attributes.GetResource().GroupResource() != api.Resource("pods") {
|
||||
return nil
|
||||
}
|
||||
pod, ok := attributes.GetObject().(*api.Pod)
|
||||
if !ok {
|
||||
return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
|
||||
}
|
||||
affinity := pod.Spec.Affinity
|
||||
if affinity != nil && affinity.PodAntiAffinity != nil {
|
||||
var podAntiAffinityTerms []api.PodAffinityTerm
|
||||
if len(affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution) != 0 {
|
||||
podAntiAffinityTerms = affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution
|
||||
}
|
||||
// TODO: Uncomment this block when implement RequiredDuringSchedulingRequiredDuringExecution.
|
||||
//if len(affinity.PodAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution) != 0 {
|
||||
// podAntiAffinityTerms = append(podAntiAffinityTerms, affinity.PodAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution...)
|
||||
//}
|
||||
for _, v := range podAntiAffinityTerms {
|
||||
if v.TopologyKey != kubeletapis.LabelHostname {
|
||||
return apierrors.NewForbidden(attributes.GetResource().GroupResource(), pod.Name, fmt.Errorf("affinity.PodAntiAffinity.RequiredDuringScheduling has TopologyKey %v but only key %v is allowed", v.TopologyKey, kubeletapis.LabelHostname))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
284
vendor/k8s.io/kubernetes/plugin/pkg/admission/antiaffinity/admission_test.go
generated
vendored
Normal file
284
vendor/k8s.io/kubernetes/plugin/pkg/admission/antiaffinity/admission_test.go
generated
vendored
Normal file
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package antiaffinity
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis"
|
||||
)
|
||||
|
||||
// ensures the hard PodAntiAffinity is denied if it defines TopologyKey other than kubernetes.io/hostname.
|
||||
// TODO: Add test case "invalid topologyKey in requiredDuringSchedulingRequiredDuringExecution then admission fails"
|
||||
// after RequiredDuringSchedulingRequiredDuringExecution is implemented.
|
||||
func TestInterPodAffinityAdmission(t *testing.T) {
|
||||
handler := NewInterPodAntiAffinity()
|
||||
pod := api.Pod{
|
||||
Spec: api.PodSpec{},
|
||||
}
|
||||
tests := []struct {
|
||||
affinity *api.Affinity
|
||||
errorExpected bool
|
||||
}{
|
||||
// empty affinity its success.
|
||||
{
|
||||
affinity: &api.Affinity{},
|
||||
errorExpected: false,
|
||||
},
|
||||
// what ever topologyKey in preferredDuringSchedulingIgnoredDuringExecution, the admission should success.
|
||||
{
|
||||
affinity: &api.Affinity{
|
||||
PodAntiAffinity: &api.PodAntiAffinity{
|
||||
PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
|
||||
{
|
||||
Weight: 5,
|
||||
PodAffinityTerm: api.PodAffinityTerm{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
TopologyKey: "az",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
errorExpected: false,
|
||||
},
|
||||
// valid topologyKey in requiredDuringSchedulingIgnoredDuringExecution,
|
||||
// plus any topologyKey in preferredDuringSchedulingIgnoredDuringExecution, then admission success.
|
||||
{
|
||||
affinity: &api.Affinity{
|
||||
PodAntiAffinity: &api.PodAntiAffinity{
|
||||
PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
|
||||
{
|
||||
Weight: 5,
|
||||
PodAffinityTerm: api.PodAffinityTerm{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
TopologyKey: "az",
|
||||
},
|
||||
},
|
||||
},
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
|
||||
{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
TopologyKey: kubeletapis.LabelHostname,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
errorExpected: false,
|
||||
},
|
||||
// valid topologyKey in requiredDuringSchedulingIgnoredDuringExecution then admission success.
|
||||
{
|
||||
affinity: &api.Affinity{
|
||||
PodAntiAffinity: &api.PodAntiAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
|
||||
{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
TopologyKey: kubeletapis.LabelHostname,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
errorExpected: false,
|
||||
},
|
||||
// invalid topologyKey in requiredDuringSchedulingIgnoredDuringExecution then admission fails.
|
||||
{
|
||||
affinity: &api.Affinity{
|
||||
PodAntiAffinity: &api.PodAntiAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
|
||||
{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
TopologyKey: " zone ",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
errorExpected: true,
|
||||
},
|
||||
// list of requiredDuringSchedulingIgnoredDuringExecution middle element topologyKey is not valid.
|
||||
{
|
||||
affinity: &api.Affinity{
|
||||
PodAntiAffinity: &api.PodAntiAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
|
||||
{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
TopologyKey: kubeletapis.LabelHostname,
|
||||
}, {
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
TopologyKey: " zone ",
|
||||
}, {
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
TopologyKey: kubeletapis.LabelHostname,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
errorExpected: true,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
pod.Spec.Affinity = test.affinity
|
||||
err := handler.Validate(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), "foo", "name", api.Resource("pods").WithVersion("version"), "", "ignored", nil))
|
||||
|
||||
if test.errorExpected && err == nil {
|
||||
t.Errorf("Expected error for Anti Affinity %+v but did not get an error", test.affinity)
|
||||
}
|
||||
|
||||
if !test.errorExpected && err != nil {
|
||||
t.Errorf("Unexpected error %v for AntiAffinity %+v", err, test.affinity)
|
||||
}
|
||||
}
|
||||
}
|
||||
func TestHandles(t *testing.T) {
|
||||
handler := NewInterPodAntiAffinity()
|
||||
tests := map[admission.Operation]bool{
|
||||
admission.Update: true,
|
||||
admission.Create: true,
|
||||
admission.Delete: false,
|
||||
admission.Connect: false,
|
||||
}
|
||||
for op, expected := range tests {
|
||||
result := handler.Handles(op)
|
||||
if result != expected {
|
||||
t.Errorf("Unexpected result for operation %s: %v\n", op, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestOtherResources ensures that this admission controller is a no-op for other resources,
|
||||
// subresources, and non-pods.
|
||||
func TestOtherResources(t *testing.T) {
|
||||
namespace := "testnamespace"
|
||||
name := "testname"
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
kind string
|
||||
resource string
|
||||
subresource string
|
||||
object runtime.Object
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "non-pod resource",
|
||||
kind: "Foo",
|
||||
resource: "foos",
|
||||
object: pod,
|
||||
},
|
||||
{
|
||||
name: "pod subresource",
|
||||
kind: "Pod",
|
||||
resource: "pods",
|
||||
subresource: "eviction",
|
||||
object: pod,
|
||||
},
|
||||
{
|
||||
name: "non-pod object",
|
||||
kind: "Pod",
|
||||
resource: "pods",
|
||||
object: &api.Service{},
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
handler := &Plugin{}
|
||||
|
||||
err := handler.Validate(admission.NewAttributesRecord(tc.object, nil, api.Kind(tc.kind).WithVersion("version"), namespace, name, api.Resource(tc.resource).WithVersion("version"), tc.subresource, admission.Create, nil))
|
||||
|
||||
if tc.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("%s: unexpected nil error", tc.name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", tc.name, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
28
vendor/k8s.io/kubernetes/plugin/pkg/admission/antiaffinity/doc.go
generated
vendored
Normal file
28
vendor/k8s.io/kubernetes/plugin/pkg/admission/antiaffinity/doc.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// LimitPodHardAntiAffinityTopology admission controller rejects any pod
|
||||
// that specifies "hard" (RequiredDuringScheduling) anti-affinity
|
||||
// with a TopologyKey other than kubeletapis.LabelHostname.
|
||||
// Because anti-affinity is symmetric, without this admission controller,
|
||||
// a user could maliciously or accidentally specify that their pod (once it has scheduled)
|
||||
// should block other pods from scheduling into the same zone or some other large topology,
|
||||
// essentially DoSing the cluster.
|
||||
// In the future we will address this problem more fully by using quota and priority,
|
||||
// but for now this admission controller provides a simple protection,
|
||||
// on the assumption that the only legitimate use of hard pod anti-affinity
|
||||
// is to exclude other pods from the same node.
|
||||
package antiaffinity // import "k8s.io/kubernetes/plugin/pkg/admission/antiaffinity"
|
||||
44
vendor/k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds/BUILD
generated
vendored
Normal file
44
vendor/k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds/BUILD
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/core/helper:go_default_library",
|
||||
"//pkg/scheduler/algorithm:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["admission.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/scheduler/algorithm:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
124
vendor/k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds/admission.go
generated
vendored
Normal file
124
vendor/k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds/admission.go
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
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 defaulttolerationseconds
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/scheduler/algorithm"
|
||||
)
|
||||
|
||||
// PluginName indicates name of admission plugin.
|
||||
const PluginName = "DefaultTolerationSeconds"
|
||||
|
||||
var (
|
||||
defaultNotReadyTolerationSeconds = flag.Int64("default-not-ready-toleration-seconds", 300,
|
||||
"Indicates the tolerationSeconds of the toleration for notReady:NoExecute"+
|
||||
" that is added by default to every pod that does not already have such a toleration.")
|
||||
|
||||
defaultUnreachableTolerationSeconds = flag.Int64("default-unreachable-toleration-seconds", 300,
|
||||
"Indicates the tolerationSeconds of the toleration for unreachable:NoExecute"+
|
||||
" that is added by default to every pod that does not already have such a toleration.")
|
||||
|
||||
notReadyToleration = api.Toleration{
|
||||
Key: algorithm.TaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: defaultNotReadyTolerationSeconds,
|
||||
}
|
||||
|
||||
unreachableToleration = api.Toleration{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: defaultUnreachableTolerationSeconds,
|
||||
}
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
|
||||
return NewDefaultTolerationSeconds(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// Plugin contains the client used by the admission controller
|
||||
// It will add default tolerations for every pod
|
||||
// that tolerate taints `notReady:NoExecute` and `unreachable:NoExecute`,
|
||||
// with tolerationSeconds of 300s.
|
||||
// If the pod already specifies a toleration for taint `notReady:NoExecute`
|
||||
// or `unreachable:NoExecute`, the plugin won't touch it.
|
||||
type Plugin struct {
|
||||
*admission.Handler
|
||||
}
|
||||
|
||||
var _ admission.MutationInterface = &Plugin{}
|
||||
|
||||
// NewDefaultTolerationSeconds creates a new instance of the DefaultTolerationSeconds admission controller
|
||||
func NewDefaultTolerationSeconds() *Plugin {
|
||||
return &Plugin{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
}
|
||||
}
|
||||
|
||||
// Admit makes an admission decision based on the request attributes
|
||||
func (p *Plugin) Admit(attributes admission.Attributes) (err error) {
|
||||
if attributes.GetResource().GroupResource() != api.Resource("pods") {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(attributes.GetSubresource()) > 0 {
|
||||
// only run the checks below on pods proper and not subresources
|
||||
return nil
|
||||
}
|
||||
|
||||
pod, ok := attributes.GetObject().(*api.Pod)
|
||||
if !ok {
|
||||
return errors.NewBadRequest(fmt.Sprintf("expected *api.Pod but got %T", attributes.GetObject()))
|
||||
}
|
||||
|
||||
tolerations := pod.Spec.Tolerations
|
||||
|
||||
toleratesNodeNotReady := false
|
||||
toleratesNodeUnreachable := false
|
||||
for _, toleration := range tolerations {
|
||||
if (toleration.Key == algorithm.TaintNodeNotReady || len(toleration.Key) == 0) &&
|
||||
(toleration.Effect == api.TaintEffectNoExecute || len(toleration.Effect) == 0) {
|
||||
toleratesNodeNotReady = true
|
||||
}
|
||||
|
||||
if (toleration.Key == algorithm.TaintNodeUnreachable || len(toleration.Key) == 0) &&
|
||||
(toleration.Effect == api.TaintEffectNoExecute || len(toleration.Effect) == 0) {
|
||||
toleratesNodeUnreachable = true
|
||||
}
|
||||
}
|
||||
|
||||
if !toleratesNodeNotReady {
|
||||
pod.Spec.Tolerations = append(pod.Spec.Tolerations, notReadyToleration)
|
||||
}
|
||||
|
||||
if !toleratesNodeUnreachable {
|
||||
pod.Spec.Tolerations = append(pod.Spec.Tolerations, unreachableToleration)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
423
vendor/k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds/admission_test.go
generated
vendored
Normal file
423
vendor/k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds/admission_test.go
generated
vendored
Normal file
@@ -0,0 +1,423 @@
|
||||
/*
|
||||
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 defaulttolerationseconds
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/core/helper"
|
||||
"k8s.io/kubernetes/pkg/scheduler/algorithm"
|
||||
)
|
||||
|
||||
func TestForgivenessAdmission(t *testing.T) {
|
||||
var defaultTolerationSeconds int64 = 300
|
||||
|
||||
genTolerationSeconds := func(s int64) *int64 {
|
||||
return &s
|
||||
}
|
||||
|
||||
handler := NewDefaultTolerationSeconds()
|
||||
// NOTE: for anyone who want to modify this test, the order of tolerations matters!
|
||||
tests := []struct {
|
||||
description string
|
||||
requestedPod api.Pod
|
||||
expectedPod api.Pod
|
||||
}{
|
||||
{
|
||||
description: "pod has no tolerations, expect add tolerations for `not-ready:NoExecute` and `unreachable:NoExecute`",
|
||||
requestedPod: api.Pod{
|
||||
Spec: api.PodSpec{},
|
||||
},
|
||||
expectedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.TaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod has alpha tolerations, expect add tolerations for `not-ready:NoExecute` and `unreachable:NoExecute`" +
|
||||
", the alpha tolerations will not be touched",
|
||||
requestedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.DeprecatedTaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
{
|
||||
Key: algorithm.DeprecatedTaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.DeprecatedTaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
{
|
||||
Key: algorithm.DeprecatedTaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod has alpha not-ready toleration, expect add tolerations for `not-ready:NoExecute` and `unreachable:NoExecute`" +
|
||||
", the alpha tolerations will not be touched",
|
||||
requestedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.DeprecatedTaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.DeprecatedTaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod has alpha unreachable toleration, expect add tolerations for `not-ready:NoExecute` and `unreachable:NoExecute`" +
|
||||
", the alpha tolerations will not be touched",
|
||||
requestedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.DeprecatedTaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.DeprecatedTaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod has tolerations, but none is for taint `not-ready:NoExecute` or `unreachable:NoExecute`, expect add tolerations for `not-ready:NoExecute` and `unreachable:NoExecute`",
|
||||
requestedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: "foo",
|
||||
Operator: api.TolerationOpEqual,
|
||||
Value: "bar",
|
||||
Effect: api.TaintEffectNoSchedule,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: "foo",
|
||||
Operator: api.TolerationOpEqual,
|
||||
Value: "bar",
|
||||
Effect: api.TaintEffectNoSchedule,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod specified a toleration for taint `not-ready:NoExecute`, expect add toleration for `unreachable:NoExecute`",
|
||||
requestedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.TaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.TaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod specified a toleration for taint `unreachable:NoExecute`, expect add toleration for `not-ready:NoExecute`",
|
||||
requestedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod specified tolerations for both `not-ready:NoExecute` and `unreachable:NoExecute`, expect no change",
|
||||
requestedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.TaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.TaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod specified toleration for taint `unreachable`, expect add toleration for `not-ready:NoExecute`",
|
||||
requestedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: genTolerationSeconds(300),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod has wildcard toleration for all kind of taints, expect no change",
|
||||
requestedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{Operator: api.TolerationOpExists, TolerationSeconds: genTolerationSeconds(700)},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Operator: api.TolerationOpExists,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
err := handler.Admit(admission.NewAttributesRecord(&test.requestedPod, nil, api.Kind("Pod").WithVersion("version"), "foo", "name", api.Resource("pods").WithVersion("version"), "", "ignored", nil))
|
||||
if err != nil {
|
||||
t.Errorf("[%s]: unexpected error %v for pod %+v", test.description, err, test.requestedPod)
|
||||
}
|
||||
|
||||
if !helper.Semantic.DeepEqual(test.expectedPod.Spec.Tolerations, test.requestedPod.Spec.Tolerations) {
|
||||
t.Errorf("[%s]: expected %#v got %#v", test.description, test.expectedPod.Spec.Tolerations, test.requestedPod.Spec.Tolerations)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandles(t *testing.T) {
|
||||
handler := NewDefaultTolerationSeconds()
|
||||
tests := map[admission.Operation]bool{
|
||||
admission.Update: true,
|
||||
admission.Create: true,
|
||||
admission.Delete: false,
|
||||
admission.Connect: false,
|
||||
}
|
||||
for op, expected := range tests {
|
||||
result := handler.Handles(op)
|
||||
if result != expected {
|
||||
t.Errorf("Unexpected result for operation %s: %v\n", op, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
40
vendor/k8s.io/kubernetes/plugin/pkg/admission/deny/BUILD
generated
vendored
Normal file
40
vendor/k8s.io/kubernetes/plugin/pkg/admission/deny/BUILD
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["admission.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/deny",
|
||||
deps = [
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
66
vendor/k8s.io/kubernetes/plugin/pkg/admission/deny/admission.go
generated
vendored
Normal file
66
vendor/k8s.io/kubernetes/plugin/pkg/admission/deny/admission.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
Copyright 2014 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 deny
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
)
|
||||
|
||||
// PluginName indicates name of admission plugin.
|
||||
const PluginName = "AlwaysDeny"
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
|
||||
return NewAlwaysDeny(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// alwaysDeny is an implementation of admission.Interface which always says no to an admission request.
|
||||
type alwaysDeny struct{}
|
||||
|
||||
var _ admission.MutationInterface = alwaysDeny{}
|
||||
var _ admission.ValidationInterface = alwaysDeny{}
|
||||
|
||||
// Admit makes an admission decision based on the request attributes.
|
||||
func (alwaysDeny) Admit(a admission.Attributes) (err error) {
|
||||
return admission.NewForbidden(a, errors.New("admission control is denying all modifications"))
|
||||
}
|
||||
|
||||
// Validate makes an admission decision based on the request attributes. It is NOT allowed to mutate.
|
||||
func (alwaysDeny) Validate(a admission.Attributes) (err error) {
|
||||
return admission.NewForbidden(a, errors.New("admission control is denying all modifications"))
|
||||
}
|
||||
|
||||
// Handles returns true if this admission controller can handle the given operation
|
||||
// where operation can be one of CREATE, UPDATE, DELETE, or CONNECT
|
||||
func (alwaysDeny) Handles(operation admission.Operation) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// NewAlwaysDeny creates an always deny admission handler
|
||||
func NewAlwaysDeny() admission.Interface {
|
||||
// DEPRECATED: AlwaysDeny denys all admission request, it is no use.
|
||||
glog.Warningf("%s admission controller is deprecated. "+
|
||||
"Please remove this controller from your configuration files and scripts.", PluginName)
|
||||
return new(alwaysDeny)
|
||||
}
|
||||
43
vendor/k8s.io/kubernetes/plugin/pkg/admission/deny/admission_test.go
generated
vendored
Normal file
43
vendor/k8s.io/kubernetes/plugin/pkg/admission/deny/admission_test.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
Copyright 2014 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 deny
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
)
|
||||
|
||||
func TestAdmission(t *testing.T) {
|
||||
handler := NewAlwaysDeny()
|
||||
err := handler.(*alwaysDeny).Admit(admission.NewAttributesRecord(nil, nil, api.Kind("kind").WithVersion("version"), "namespace", "name", api.Resource("resource").WithVersion("version"), "subresource", admission.Create, nil))
|
||||
if err == nil {
|
||||
t.Error("Expected error returned from admission handler")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandles(t *testing.T) {
|
||||
handler := NewAlwaysDeny()
|
||||
tests := []admission.Operation{admission.Create, admission.Connect, admission.Update, admission.Delete}
|
||||
|
||||
for _, test := range tests {
|
||||
if !handler.Handles(test) {
|
||||
t.Errorf("Expected handling all operations, including: %v", test)
|
||||
}
|
||||
}
|
||||
}
|
||||
69
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/BUILD
generated
vendored
Normal file
69
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/BUILD
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
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",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/core: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",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit",
|
||||
deps = [
|
||||
"//pkg/apis/core: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/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"],
|
||||
)
|
||||
97
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/admission.go
generated
vendored
Normal file
97
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/admission.go
generated
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
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"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
eventratelimitapi "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit"
|
||||
"k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation"
|
||||
)
|
||||
|
||||
// PluginName indicates name of admission plugin.
|
||||
const PluginName = "EventRateLimit"
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName,
|
||||
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{})
|
||||
})
|
||||
}
|
||||
|
||||
// Plugin implements an admission controller that can enforce event rate limits
|
||||
type Plugin 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
|
||||
}
|
||||
|
||||
var _ admission.ValidationInterface = &Plugin{}
|
||||
|
||||
// newEventRateLimit configures an admission controller that can enforce event rate limits
|
||||
func newEventRateLimit(config *eventratelimitapi.Configuration, clock flowcontrol.Clock) (*Plugin, 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 := &Plugin{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
limitEnforcers: limitEnforcers,
|
||||
}
|
||||
|
||||
return eventRateLimitAdmission, nil
|
||||
}
|
||||
|
||||
// Validate makes admission decisions while enforcing event rate limits
|
||||
func (a *Plugin) Validate(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
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/admission_test.go
generated
vendored
Normal file
502
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/admission_test.go
generated
vendored
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"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
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.Validate(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
40
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/BUILD
generated
vendored
Normal file
40
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/BUILD
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
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",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit",
|
||||
deps = [
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1: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
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/OWNERS
generated
vendored
Executable file
7
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/OWNERS
generated
vendored
Executable file
@@ -0,0 +1,7 @@
|
||||
reviewers:
|
||||
- deads2k
|
||||
- derekwaynecarr
|
||||
approvers:
|
||||
- deads2k
|
||||
- derekwaynecarr
|
||||
- smarterclayton
|
||||
19
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/doc.go
generated
vendored
Normal file
@@ -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
|
||||
|
||||
package eventratelimit // import "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit"
|
||||
31
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/install/BUILD
generated
vendored
Normal file
31
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/install/BUILD
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["install.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/install",
|
||||
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/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
33
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/install/install.go
generated
vendored
Normal file
33
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/install/install.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
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/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/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(scheme *runtime.Scheme) {
|
||||
utilruntime.Must(internalapi.AddToScheme(scheme))
|
||||
utilruntime.Must(versionedapi.AddToScheme(scheme))
|
||||
utilruntime.Must(scheme.SetVersionPriority(versionedapi.SchemeGroupVersion))
|
||||
}
|
||||
51
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/register.go
generated
vendored
Normal file
51
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/register.go
generated
vendored
Normal file
@@ -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
|
||||
}
|
||||
85
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/types.go
generated
vendored
Normal file
85
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/types.go
generated
vendored
Normal file
@@ -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"`
|
||||
}
|
||||
40
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/BUILD
generated
vendored
Normal file
40
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/BUILD
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
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",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1",
|
||||
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"],
|
||||
)
|
||||
25
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/defaults.go
generated
vendored
Normal file
25
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/defaults.go
generated
vendored
Normal file
@@ -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) {}
|
||||
23
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/doc.go
generated
vendored
Normal file
23
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/doc.go
generated
vendored
Normal file
@@ -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
|
||||
// +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"
|
||||
50
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/register.go
generated
vendored
Normal file
50
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/register.go
generated
vendored
Normal file
@@ -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
|
||||
}
|
||||
85
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/types.go
generated
vendored
Normal file
85
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/types.go
generated
vendored
Normal file
@@ -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"`
|
||||
}
|
||||
90
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/zz_generated.conversion.go
generated
vendored
Normal file
90
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/zz_generated.conversion.go
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 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.
|
||||
*/
|
||||
|
||||
// Code generated by conversion-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
unsafe "unsafe"
|
||||
|
||||
conversion "k8s.io/apimachinery/pkg/conversion"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
eventratelimit "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
71
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/zz_generated.deepcopy.go
generated
vendored
Normal file
71
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/zz_generated.deepcopy.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 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.
|
||||
*/
|
||||
|
||||
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
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
|
||||
}
|
||||
37
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/zz_generated.defaults.go
generated
vendored
Normal file
37
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/zz_generated.defaults.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 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.
|
||||
*/
|
||||
|
||||
// Code generated by defaulter-gen. DO NOT EDIT.
|
||||
|
||||
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)
|
||||
}
|
||||
37
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation/BUILD
generated
vendored
Normal file
37
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation/BUILD
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["validation.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation",
|
||||
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"],
|
||||
embed = [":go_default_library"],
|
||||
deps = ["//plugin/pkg/admission/eventratelimit/apis/eventratelimit:go_default_library"],
|
||||
)
|
||||
63
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation/validation.go
generated
vendored
Normal file
63
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation/validation.go
generated
vendored
Normal file
@@ -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
|
||||
}
|
||||
192
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation/validation_test.go
generated
vendored
Normal file
192
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation/validation_test.go
generated
vendored
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
71
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/zz_generated.deepcopy.go
generated
vendored
Normal file
71
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/zz_generated.deepcopy.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 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.
|
||||
*/
|
||||
|
||||
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
package eventratelimit
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
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
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/cache.go
generated
vendored
Normal file
57
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/cache.go
generated
vendored
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
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/cache_test.go
generated
vendored
Normal file
119
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/cache_test.go
generated
vendored
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
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/clock.go
generated
vendored
Normal file
34
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/clock.go
generated
vendored
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)
|
||||
}
|
||||
67
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/config.go
generated
vendored
Normal file
67
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/config.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
"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 (
|
||||
scheme = runtime.NewScheme()
|
||||
codecs = serializer.NewCodecFactory(scheme)
|
||||
)
|
||||
|
||||
func init() {
|
||||
install.Install(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
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/doc.go
generated
vendored
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
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/limitenforcer.go
generated
vendored
Normal file
145
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/limitenforcer.go
generated
vendored
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"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
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,
|
||||
}, "")
|
||||
}
|
||||
50
vendor/k8s.io/kubernetes/plugin/pkg/admission/exec/BUILD
generated
vendored
Normal file
50
vendor/k8s.io/kubernetes/plugin/pkg/admission/exec/BUILD
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["admission.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/exec",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
||||
"//vendor/k8s.io/client-go/testing:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
164
vendor/k8s.io/kubernetes/plugin/pkg/admission/exec/admission.go
generated
vendored
Normal file
164
vendor/k8s.io/kubernetes/plugin/pkg/admission/exec/admission.go
generated
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
Copyright 2015 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 exec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
)
|
||||
|
||||
const (
|
||||
// DenyEscalatingExec indicates name of admission plugin.
|
||||
DenyEscalatingExec = "DenyEscalatingExec"
|
||||
// DenyExecOnPrivileged indicates name of admission plugin.
|
||||
// Deprecated, should use DenyEscalatingExec instead.
|
||||
DenyExecOnPrivileged = "DenyExecOnPrivileged"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(DenyEscalatingExec, func(config io.Reader) (admission.Interface, error) {
|
||||
return NewDenyEscalatingExec(), nil
|
||||
})
|
||||
|
||||
// This is for legacy support of the DenyExecOnPrivileged admission controller. Most
|
||||
// of the time DenyEscalatingExec should be preferred.
|
||||
plugins.Register(DenyExecOnPrivileged, func(config io.Reader) (admission.Interface, error) {
|
||||
return NewDenyExecOnPrivileged(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// DenyExec is an implementation of admission.Interface which says no to a pod/exec on
|
||||
// a pod using host based configurations.
|
||||
type DenyExec struct {
|
||||
*admission.Handler
|
||||
client internalclientset.Interface
|
||||
|
||||
// these flags control which items will be checked to deny exec/attach
|
||||
hostNetwork bool
|
||||
hostIPC bool
|
||||
hostPID bool
|
||||
privileged bool
|
||||
}
|
||||
|
||||
var _ admission.ValidationInterface = &DenyExec{}
|
||||
|
||||
var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&DenyExec{})
|
||||
|
||||
// NewDenyEscalatingExec creates a new admission controller that denies an exec operation on a pod
|
||||
// using host based configurations.
|
||||
func NewDenyEscalatingExec() *DenyExec {
|
||||
return &DenyExec{
|
||||
Handler: admission.NewHandler(admission.Connect),
|
||||
hostNetwork: true,
|
||||
hostIPC: true,
|
||||
hostPID: true,
|
||||
privileged: true,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDenyExecOnPrivileged creates a new admission controller that is only checking the privileged
|
||||
// option. This is for legacy support of the DenyExecOnPrivileged admission controller.
|
||||
// Most of the time NewDenyEscalatingExec should be preferred.
|
||||
func NewDenyExecOnPrivileged() *DenyExec {
|
||||
return &DenyExec{
|
||||
Handler: admission.NewHandler(admission.Connect),
|
||||
hostNetwork: false,
|
||||
hostIPC: false,
|
||||
hostPID: false,
|
||||
privileged: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Validate makes an admission decision based on the request attributes
|
||||
func (d *DenyExec) Validate(a admission.Attributes) (err error) {
|
||||
connectRequest, ok := a.GetObject().(*rest.ConnectRequest)
|
||||
if !ok {
|
||||
return errors.NewBadRequest("a connect request was received, but could not convert the request object.")
|
||||
}
|
||||
// Only handle exec or attach requests on pods
|
||||
if connectRequest.ResourcePath != "pods/exec" && connectRequest.ResourcePath != "pods/attach" {
|
||||
return nil
|
||||
}
|
||||
pod, err := d.client.Core().Pods(a.GetNamespace()).Get(connectRequest.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return admission.NewForbidden(a, err)
|
||||
}
|
||||
|
||||
if pod.Spec.SecurityContext != nil {
|
||||
securityContext := pod.Spec.SecurityContext
|
||||
if d.hostNetwork && securityContext.HostNetwork {
|
||||
return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a container using host network"))
|
||||
}
|
||||
|
||||
if d.hostPID && securityContext.HostPID {
|
||||
return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a container using host pid"))
|
||||
}
|
||||
|
||||
if d.hostIPC && securityContext.HostIPC {
|
||||
return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a container using host ipc"))
|
||||
}
|
||||
}
|
||||
|
||||
if d.privileged && isPrivileged(pod) {
|
||||
return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a privileged container"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isPrivileged will return true a pod has any privileged containers
|
||||
func isPrivileged(pod *api.Pod) bool {
|
||||
for _, c := range pod.Spec.InitContainers {
|
||||
if c.SecurityContext == nil || c.SecurityContext.Privileged == nil {
|
||||
continue
|
||||
}
|
||||
if *c.SecurityContext.Privileged {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, c := range pod.Spec.Containers {
|
||||
if c.SecurityContext == nil || c.SecurityContext.Privileged == nil {
|
||||
continue
|
||||
}
|
||||
if *c.SecurityContext.Privileged {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetInternalKubeClientSet implements the WantsInternalKubeClientSet interface.
|
||||
func (d *DenyExec) SetInternalKubeClientSet(client internalclientset.Interface) {
|
||||
d.client = client
|
||||
}
|
||||
|
||||
// ValidateInitialization implements the InitializationValidator interface.
|
||||
func (d *DenyExec) ValidateInitialization() error {
|
||||
if d.client == nil {
|
||||
return fmt.Errorf("missing client")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
211
vendor/k8s.io/kubernetes/plugin/pkg/admission/exec/admission_test.go
generated
vendored
Normal file
211
vendor/k8s.io/kubernetes/plugin/pkg/admission/exec/admission_test.go
generated
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
Copyright 2015 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 exec
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
core "k8s.io/client-go/testing"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
)
|
||||
|
||||
// newAllowEscalatingExec returns `admission.Interface` that allows execution on
|
||||
// "hostIPC", "hostPID" and "privileged".
|
||||
func newAllowEscalatingExec() *DenyExec {
|
||||
return &DenyExec{
|
||||
Handler: admission.NewHandler(admission.Connect),
|
||||
hostIPC: false,
|
||||
hostPID: false,
|
||||
privileged: false,
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdmission(t *testing.T) {
|
||||
privPod := validPod("privileged")
|
||||
priv := true
|
||||
privPod.Spec.Containers[0].SecurityContext = &api.SecurityContext{
|
||||
Privileged: &priv,
|
||||
}
|
||||
|
||||
hostPIDPod := validPod("hostPID")
|
||||
hostPIDPod.Spec.SecurityContext = &api.PodSecurityContext{}
|
||||
hostPIDPod.Spec.SecurityContext.HostPID = true
|
||||
|
||||
hostIPCPod := validPod("hostIPC")
|
||||
hostIPCPod.Spec.SecurityContext = &api.PodSecurityContext{}
|
||||
hostIPCPod.Spec.SecurityContext.HostIPC = true
|
||||
|
||||
testCases := map[string]struct {
|
||||
pod *api.Pod
|
||||
shouldAccept bool
|
||||
}{
|
||||
"priv": {
|
||||
shouldAccept: false,
|
||||
pod: privPod,
|
||||
},
|
||||
"hostPID": {
|
||||
shouldAccept: false,
|
||||
pod: hostPIDPod,
|
||||
},
|
||||
"hostIPC": {
|
||||
shouldAccept: false,
|
||||
pod: hostIPCPod,
|
||||
},
|
||||
"non privileged": {
|
||||
shouldAccept: true,
|
||||
pod: validPod("nonPrivileged"),
|
||||
},
|
||||
}
|
||||
|
||||
// Get the direct object though to allow testAdmission to inject the client
|
||||
handler := NewDenyEscalatingExec()
|
||||
|
||||
for _, tc := range testCases {
|
||||
testAdmission(t, tc.pod, handler, tc.shouldAccept)
|
||||
}
|
||||
|
||||
// run with a permissive config and all cases should pass
|
||||
handler = newAllowEscalatingExec()
|
||||
|
||||
for _, tc := range testCases {
|
||||
testAdmission(t, tc.pod, handler, true)
|
||||
}
|
||||
|
||||
// run against an init container
|
||||
handler = NewDenyEscalatingExec()
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc.pod.Spec.InitContainers = tc.pod.Spec.Containers
|
||||
tc.pod.Spec.Containers = nil
|
||||
testAdmission(t, tc.pod, handler, tc.shouldAccept)
|
||||
}
|
||||
|
||||
// run with a permissive config and all cases should pass
|
||||
handler = newAllowEscalatingExec()
|
||||
|
||||
for _, tc := range testCases {
|
||||
testAdmission(t, tc.pod, handler, true)
|
||||
}
|
||||
}
|
||||
|
||||
func testAdmission(t *testing.T, pod *api.Pod, handler *DenyExec, shouldAccept bool) {
|
||||
mockClient := &fake.Clientset{}
|
||||
mockClient.AddReactor("get", "pods", func(action core.Action) (bool, runtime.Object, error) {
|
||||
if action.(core.GetAction).GetName() == pod.Name {
|
||||
return true, pod, nil
|
||||
}
|
||||
t.Errorf("Unexpected API call: %#v", action)
|
||||
return true, nil, nil
|
||||
})
|
||||
|
||||
handler.SetInternalKubeClientSet(mockClient)
|
||||
admission.ValidateInitialization(handler)
|
||||
|
||||
// pods/exec
|
||||
{
|
||||
req := &rest.ConnectRequest{Name: pod.Name, ResourcePath: "pods/exec"}
|
||||
err := handler.Validate(admission.NewAttributesRecord(req, nil, api.Kind("Pod").WithVersion("version"), "test", "name", api.Resource("pods").WithVersion("version"), "exec", admission.Connect, nil))
|
||||
if shouldAccept && err != nil {
|
||||
t.Errorf("Unexpected error returned from admission handler: %v", err)
|
||||
}
|
||||
if !shouldAccept && err == nil {
|
||||
t.Errorf("An error was expected from the admission handler. Received nil")
|
||||
}
|
||||
}
|
||||
|
||||
// pods/attach
|
||||
{
|
||||
req := &rest.ConnectRequest{Name: pod.Name, ResourcePath: "pods/attach"}
|
||||
err := handler.Validate(admission.NewAttributesRecord(req, nil, api.Kind("Pod").WithVersion("version"), "test", "name", api.Resource("pods").WithVersion("version"), "attach", admission.Connect, nil))
|
||||
if shouldAccept && err != nil {
|
||||
t.Errorf("Unexpected error returned from admission handler: %v", err)
|
||||
}
|
||||
if !shouldAccept && err == nil {
|
||||
t.Errorf("An error was expected from the admission handler. Received nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test to ensure legacy admission controller works as expected.
|
||||
func TestDenyExecOnPrivileged(t *testing.T) {
|
||||
privPod := validPod("privileged")
|
||||
priv := true
|
||||
privPod.Spec.Containers[0].SecurityContext = &api.SecurityContext{
|
||||
Privileged: &priv,
|
||||
}
|
||||
|
||||
hostPIDPod := validPod("hostPID")
|
||||
hostPIDPod.Spec.SecurityContext = &api.PodSecurityContext{}
|
||||
hostPIDPod.Spec.SecurityContext.HostPID = true
|
||||
|
||||
hostIPCPod := validPod("hostIPC")
|
||||
hostIPCPod.Spec.SecurityContext = &api.PodSecurityContext{}
|
||||
hostIPCPod.Spec.SecurityContext.HostIPC = true
|
||||
|
||||
testCases := map[string]struct {
|
||||
pod *api.Pod
|
||||
shouldAccept bool
|
||||
}{
|
||||
"priv": {
|
||||
shouldAccept: false,
|
||||
pod: privPod,
|
||||
},
|
||||
"hostPID": {
|
||||
shouldAccept: true,
|
||||
pod: hostPIDPod,
|
||||
},
|
||||
"hostIPC": {
|
||||
shouldAccept: true,
|
||||
pod: hostIPCPod,
|
||||
},
|
||||
"non privileged": {
|
||||
shouldAccept: true,
|
||||
pod: validPod("nonPrivileged"),
|
||||
},
|
||||
}
|
||||
|
||||
// Get the direct object though to allow testAdmission to inject the client
|
||||
handler := NewDenyExecOnPrivileged()
|
||||
|
||||
for _, tc := range testCases {
|
||||
testAdmission(t, tc.pod, handler, tc.shouldAccept)
|
||||
}
|
||||
|
||||
// test init containers
|
||||
for _, tc := range testCases {
|
||||
tc.pod.Spec.InitContainers = tc.pod.Spec.Containers
|
||||
tc.pod.Spec.Containers = nil
|
||||
testAdmission(t, tc.pod, handler, tc.shouldAccept)
|
||||
}
|
||||
}
|
||||
|
||||
func validPod(name string) *api.Pod {
|
||||
return &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "test"},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{Name: "ctr1", Image: "image"},
|
||||
{Name: "ctr2", Image: "image2"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
41
vendor/k8s.io/kubernetes/plugin/pkg/admission/extendedresourcetoleration/BUILD
generated
vendored
Normal file
41
vendor/k8s.io/kubernetes/plugin/pkg/admission/extendedresourcetoleration/BUILD
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["admission.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/extendedresourcetoleration",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/core/helper:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/core/helper:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
97
vendor/k8s.io/kubernetes/plugin/pkg/admission/extendedresourcetoleration/admission.go
generated
vendored
Normal file
97
vendor/k8s.io/kubernetes/plugin/pkg/admission/extendedresourcetoleration/admission.go
generated
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
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 extendedresourcetoleration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/core/helper"
|
||||
)
|
||||
|
||||
// PluginName indicates name of admission plugin.
|
||||
const PluginName = "ExtendedResourceToleration"
|
||||
|
||||
// Register is called by the apiserver to register the plugin factory.
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
|
||||
return newExtendedResourceToleration(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// newExtendedResourceToleration creates a new instance of the ExtendedResourceToleration admission controller.
|
||||
func newExtendedResourceToleration() *plugin {
|
||||
return &plugin{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we are implementing the interface.
|
||||
var _ admission.MutationInterface = &plugin{}
|
||||
|
||||
type plugin struct {
|
||||
*admission.Handler
|
||||
}
|
||||
|
||||
// Admit updates the toleration of a pod based on the resources requested by it.
|
||||
// If an extended resource of name "example.com/device" is requested, it adds
|
||||
// a toleration with key "example.com/device", operator "Exists" and effect "NoSchedule".
|
||||
// The rationale for this is described in:
|
||||
// https://github.com/kubernetes/kubernetes/issues/55080
|
||||
func (p *plugin) Admit(attributes admission.Attributes) error {
|
||||
// Ignore all calls to subresources or resources other than pods.
|
||||
if len(attributes.GetSubresource()) != 0 || attributes.GetResource().GroupResource() != core.Resource("pods") {
|
||||
return nil
|
||||
}
|
||||
|
||||
pod, ok := attributes.GetObject().(*core.Pod)
|
||||
if !ok {
|
||||
return errors.NewBadRequest(fmt.Sprintf("expected *core.Pod but got %T", attributes.GetObject()))
|
||||
}
|
||||
|
||||
resources := sets.String{}
|
||||
for _, container := range pod.Spec.Containers {
|
||||
for resourceName := range container.Resources.Requests {
|
||||
if helper.IsExtendedResourceName(resourceName) {
|
||||
resources.Insert(string(resourceName))
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, container := range pod.Spec.InitContainers {
|
||||
for resourceName := range container.Resources.Requests {
|
||||
if helper.IsExtendedResourceName(resourceName) {
|
||||
resources.Insert(string(resourceName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Doing .List() so that we get a stable sorted list.
|
||||
// This allows us to test adding tolerations for multiple extended resources.
|
||||
for _, resource := range resources.List() {
|
||||
helper.AddOrUpdateTolerationInPod(pod, &core.Toleration{
|
||||
Key: resource,
|
||||
Operator: core.TolerationOpExists,
|
||||
Effect: core.TaintEffectNoSchedule,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
382
vendor/k8s.io/kubernetes/plugin/pkg/admission/extendedresourcetoleration/admission_test.go
generated
vendored
Normal file
382
vendor/k8s.io/kubernetes/plugin/pkg/admission/extendedresourcetoleration/admission_test.go
generated
vendored
Normal file
@@ -0,0 +1,382 @@
|
||||
/*
|
||||
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 extendedresourcetoleration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/core/helper"
|
||||
)
|
||||
|
||||
func TestAdmit(t *testing.T) {
|
||||
|
||||
plugin := newExtendedResourceToleration()
|
||||
|
||||
containerRequestingCPU := core.Container{
|
||||
Resources: core.ResourceRequirements{
|
||||
Requests: core.ResourceList{
|
||||
core.ResourceCPU: *resource.NewQuantity(2, resource.DecimalSI),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
containerRequestingMemory := core.Container{
|
||||
Resources: core.ResourceRequirements{
|
||||
Requests: core.ResourceList{
|
||||
core.ResourceMemory: *resource.NewQuantity(2048, resource.DecimalSI),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
extendedResource1 := "example.com/device-ek"
|
||||
extendedResource2 := "example.com/device-do"
|
||||
|
||||
containerRequestingExtendedResource1 := core.Container{
|
||||
Resources: core.ResourceRequirements{
|
||||
Requests: core.ResourceList{
|
||||
core.ResourceName(extendedResource1): *resource.NewQuantity(1, resource.DecimalSI),
|
||||
},
|
||||
},
|
||||
}
|
||||
containerRequestingExtendedResource2 := core.Container{
|
||||
Resources: core.ResourceRequirements{
|
||||
Requests: core.ResourceList{
|
||||
core.ResourceName(extendedResource2): *resource.NewQuantity(2, resource.DecimalSI),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
description string
|
||||
requestedPod core.Pod
|
||||
expectedPod core.Pod
|
||||
}{
|
||||
{
|
||||
description: "empty pod without any extended resources, expect no change in tolerations",
|
||||
requestedPod: core.Pod{
|
||||
Spec: core.PodSpec{},
|
||||
},
|
||||
expectedPod: core.Pod{
|
||||
Spec: core.PodSpec{},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod with container without any extended resources, expect no change in tolerations",
|
||||
requestedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingCPU,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingCPU,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod with init container without any extended resources, expect no change in tolerations",
|
||||
requestedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
InitContainers: []core.Container{
|
||||
containerRequestingMemory,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
InitContainers: []core.Container{
|
||||
containerRequestingMemory,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod with container with extended resource, expect toleration to be added",
|
||||
requestedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingExtendedResource1,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingExtendedResource1,
|
||||
},
|
||||
Tolerations: []core.Toleration{
|
||||
{
|
||||
Key: extendedResource1,
|
||||
Operator: core.TolerationOpExists,
|
||||
Effect: core.TaintEffectNoSchedule,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod with init container with extended resource, expect toleration to be added",
|
||||
requestedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
InitContainers: []core.Container{
|
||||
containerRequestingExtendedResource2,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
InitContainers: []core.Container{
|
||||
containerRequestingExtendedResource2,
|
||||
},
|
||||
Tolerations: []core.Toleration{
|
||||
{
|
||||
Key: extendedResource2,
|
||||
Operator: core.TolerationOpExists,
|
||||
Effect: core.TaintEffectNoSchedule,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod with existing tolerations and container with extended resource, expect existing tolerations to be preserved and new toleration to be added",
|
||||
requestedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingCPU,
|
||||
containerRequestingExtendedResource1,
|
||||
},
|
||||
Tolerations: []core.Toleration{
|
||||
{
|
||||
Key: "foo",
|
||||
Operator: core.TolerationOpEqual,
|
||||
Value: "bar",
|
||||
Effect: core.TaintEffectNoSchedule,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingCPU,
|
||||
containerRequestingExtendedResource1,
|
||||
},
|
||||
Tolerations: []core.Toleration{
|
||||
{
|
||||
Key: "foo",
|
||||
Operator: core.TolerationOpEqual,
|
||||
Value: "bar",
|
||||
Effect: core.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: extendedResource1,
|
||||
Operator: core.TolerationOpExists,
|
||||
Effect: core.TaintEffectNoSchedule,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod with multiple extended resources, expect multiple tolerations to be added",
|
||||
requestedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingMemory,
|
||||
containerRequestingExtendedResource1,
|
||||
},
|
||||
InitContainers: []core.Container{
|
||||
containerRequestingCPU,
|
||||
containerRequestingExtendedResource2,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingMemory,
|
||||
containerRequestingExtendedResource1,
|
||||
},
|
||||
InitContainers: []core.Container{
|
||||
containerRequestingCPU,
|
||||
containerRequestingExtendedResource2,
|
||||
},
|
||||
Tolerations: []core.Toleration{
|
||||
// Note the order, it's sorted by the Key
|
||||
{
|
||||
Key: extendedResource2,
|
||||
Operator: core.TolerationOpExists,
|
||||
Effect: core.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: extendedResource1,
|
||||
Operator: core.TolerationOpExists,
|
||||
Effect: core.TaintEffectNoSchedule,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod with container requesting extended resource and existing correct toleration, expect no change in tolerations",
|
||||
requestedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingCPU,
|
||||
containerRequestingMemory,
|
||||
containerRequestingExtendedResource1,
|
||||
},
|
||||
Tolerations: []core.Toleration{
|
||||
{
|
||||
Key: extendedResource1,
|
||||
Operator: core.TolerationOpExists,
|
||||
Effect: core.TaintEffectNoSchedule,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingCPU,
|
||||
containerRequestingMemory,
|
||||
containerRequestingExtendedResource1,
|
||||
},
|
||||
Tolerations: []core.Toleration{
|
||||
{
|
||||
Key: extendedResource1,
|
||||
Operator: core.TolerationOpExists,
|
||||
Effect: core.TaintEffectNoSchedule,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod with container requesting extended resource and existing toleration with the same key but different effect and value, expect existing tolerations to be preserved and new toleration to be added",
|
||||
requestedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingCPU,
|
||||
containerRequestingMemory,
|
||||
containerRequestingExtendedResource1,
|
||||
},
|
||||
Tolerations: []core.Toleration{
|
||||
{
|
||||
Key: extendedResource1,
|
||||
Operator: core.TolerationOpEqual,
|
||||
Value: "foo",
|
||||
Effect: core.TaintEffectNoExecute,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingCPU,
|
||||
containerRequestingMemory,
|
||||
containerRequestingExtendedResource1,
|
||||
},
|
||||
Tolerations: []core.Toleration{
|
||||
{
|
||||
Key: extendedResource1,
|
||||
Operator: core.TolerationOpEqual,
|
||||
Value: "foo",
|
||||
Effect: core.TaintEffectNoExecute,
|
||||
},
|
||||
{
|
||||
Key: extendedResource1,
|
||||
Operator: core.TolerationOpExists,
|
||||
Effect: core.TaintEffectNoSchedule,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod with wildcard toleration and container requesting extended resource, expect existing tolerations to be preserved and new toleration to be added",
|
||||
requestedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingCPU,
|
||||
containerRequestingMemory,
|
||||
containerRequestingExtendedResource1,
|
||||
},
|
||||
Tolerations: []core.Toleration{
|
||||
{
|
||||
Operator: core.TolerationOpExists,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingCPU,
|
||||
containerRequestingMemory,
|
||||
containerRequestingExtendedResource1,
|
||||
},
|
||||
Tolerations: []core.Toleration{
|
||||
{
|
||||
Operator: core.TolerationOpExists,
|
||||
},
|
||||
{
|
||||
Key: extendedResource1,
|
||||
Operator: core.TolerationOpExists,
|
||||
Effect: core.TaintEffectNoSchedule,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for i, test := range tests {
|
||||
err := plugin.Admit(admission.NewAttributesRecord(&test.requestedPod, nil, core.Kind("Pod").WithVersion("version"), "foo", "name", core.Resource("pods").WithVersion("version"), "", "ignored", nil))
|
||||
if err != nil {
|
||||
t.Errorf("[%d: %s] unexpected error %v for pod %+v", i, test.description, err, test.requestedPod)
|
||||
}
|
||||
|
||||
if !helper.Semantic.DeepEqual(test.expectedPod.Spec.Tolerations, test.requestedPod.Spec.Tolerations) {
|
||||
t.Errorf("[%d: %s] expected %#v got %#v", i, test.description, test.expectedPod.Spec.Tolerations, test.requestedPod.Spec.Tolerations)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandles(t *testing.T) {
|
||||
plugin := newExtendedResourceToleration()
|
||||
tests := map[admission.Operation]bool{
|
||||
admission.Create: true,
|
||||
admission.Update: true,
|
||||
admission.Delete: false,
|
||||
admission.Connect: false,
|
||||
}
|
||||
for op, expected := range tests {
|
||||
result := plugin.Handles(op)
|
||||
if result != expected {
|
||||
t.Errorf("Unexpected result for operation %s: %v\n", op, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
55
vendor/k8s.io/kubernetes/plugin/pkg/admission/gc/BUILD
generated
vendored
Normal file
55
vendor/k8s.io/kubernetes/plugin/pkg/admission/gc/BUILD
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["gc_admission.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/gc",
|
||||
deps = [
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["gc_admission_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/api/legacyscheme:go_default_library",
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta/testrestmapper:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission/initializer:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
281
vendor/k8s.io/kubernetes/plugin/pkg/admission/gc/gc_admission.go
generated
vendored
Normal file
281
vendor/k8s.io/kubernetes/plugin/pkg/admission/gc/gc_admission.go
generated
vendored
Normal file
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package gc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
// PluginName indicates name of admission plugin.
|
||||
const PluginName = "OwnerReferencesPermissionEnforcement"
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
|
||||
// the pods/status endpoint is ignored by this plugin since old kubelets
|
||||
// corrupt them. the pod status strategy ensures status updates cannot mutate
|
||||
// ownerRef.
|
||||
whiteList := []whiteListItem{
|
||||
{
|
||||
groupResource: schema.GroupResource{Resource: "pods"},
|
||||
subresource: "status",
|
||||
},
|
||||
}
|
||||
return &gcPermissionsEnforcement{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
whiteList: whiteList,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
// gcPermissionsEnforcement is an implementation of admission.Interface.
|
||||
type gcPermissionsEnforcement struct {
|
||||
*admission.Handler
|
||||
|
||||
authorizer authorizer.Authorizer
|
||||
|
||||
restMapper meta.RESTMapper
|
||||
|
||||
// items in this whitelist are ignored upon admission.
|
||||
// any item in this list must protect against ownerRef mutations
|
||||
// via strategy enforcement.
|
||||
whiteList []whiteListItem
|
||||
}
|
||||
|
||||
var _ admission.ValidationInterface = &gcPermissionsEnforcement{}
|
||||
|
||||
// whiteListItem describes an entry in a whitelist ignored by gc permission enforcement.
|
||||
type whiteListItem struct {
|
||||
groupResource schema.GroupResource
|
||||
subresource string
|
||||
}
|
||||
|
||||
// isWhiteListed returns true if the specified item is in the whitelist.
|
||||
func (a *gcPermissionsEnforcement) isWhiteListed(groupResource schema.GroupResource, subresource string) bool {
|
||||
for _, item := range a.whiteList {
|
||||
if item.groupResource == groupResource && item.subresource == subresource {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *gcPermissionsEnforcement) Validate(attributes admission.Attributes) (err error) {
|
||||
// // if the request is in the whitelist, we skip mutation checks for this resource.
|
||||
if a.isWhiteListed(attributes.GetResource().GroupResource(), attributes.GetSubresource()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// if we aren't changing owner references, then the edit is always allowed
|
||||
if !isChangingOwnerReference(attributes.GetObject(), attributes.GetOldObject()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// if you are creating a thing, you should always be allowed to set an owner ref since you logically had the power
|
||||
// to never create it. We still need to check block owner deletion below, because the power to delete does not
|
||||
// imply the power to prevent deletion on other resources.
|
||||
if attributes.GetOperation() != admission.Create {
|
||||
deleteAttributes := authorizer.AttributesRecord{
|
||||
User: attributes.GetUserInfo(),
|
||||
Verb: "delete",
|
||||
Namespace: attributes.GetNamespace(),
|
||||
APIGroup: attributes.GetResource().Group,
|
||||
APIVersion: attributes.GetResource().Version,
|
||||
Resource: attributes.GetResource().Resource,
|
||||
Subresource: attributes.GetSubresource(),
|
||||
Name: attributes.GetName(),
|
||||
ResourceRequest: true,
|
||||
Path: "",
|
||||
}
|
||||
decision, reason, err := a.authorizer.Authorize(deleteAttributes)
|
||||
if decision != authorizer.DecisionAllow {
|
||||
return admission.NewForbidden(attributes, fmt.Errorf("cannot set an ownerRef on a resource you can't delete: %v, %v", reason, err))
|
||||
}
|
||||
}
|
||||
|
||||
// Further check if the user is setting ownerReference.blockOwnerDeletion to
|
||||
// true. If so, only allows the change if the user has delete permission of
|
||||
// the _OWNER_
|
||||
newBlockingRefs := newBlockingOwnerDeletionRefs(attributes.GetObject(), attributes.GetOldObject())
|
||||
for _, ref := range newBlockingRefs {
|
||||
records, err := a.ownerRefToDeleteAttributeRecords(ref, attributes)
|
||||
if err != nil {
|
||||
return admission.NewForbidden(attributes, fmt.Errorf("cannot set blockOwnerDeletion in this case because cannot find RESTMapping for APIVersion %s Kind %s: %v", ref.APIVersion, ref.Kind, err))
|
||||
}
|
||||
// Multiple records are returned if ref.Kind could map to multiple
|
||||
// resources. User needs to have delete permission on all the
|
||||
// matched Resources.
|
||||
for _, record := range records {
|
||||
decision, reason, err := a.authorizer.Authorize(record)
|
||||
if decision != authorizer.DecisionAllow {
|
||||
return admission.NewForbidden(attributes, fmt.Errorf("cannot set blockOwnerDeletion if an ownerReference refers to a resource you can't set finalizers on: %v, %v", reason, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func isChangingOwnerReference(newObj, oldObj runtime.Object) bool {
|
||||
newMeta, err := meta.Accessor(newObj)
|
||||
if err != nil {
|
||||
// if we don't have objectmeta, we don't have the object reference
|
||||
return false
|
||||
}
|
||||
|
||||
if oldObj == nil {
|
||||
return len(newMeta.GetOwnerReferences()) > 0
|
||||
}
|
||||
oldMeta, err := meta.Accessor(oldObj)
|
||||
if err != nil {
|
||||
// if we don't have objectmeta, we don't have the object reference
|
||||
return false
|
||||
}
|
||||
|
||||
// compare the old and new. If they aren't the same, then we're trying to change an ownerRef
|
||||
oldOwners := oldMeta.GetOwnerReferences()
|
||||
newOwners := newMeta.GetOwnerReferences()
|
||||
if len(oldOwners) != len(newOwners) {
|
||||
return true
|
||||
}
|
||||
for i := range oldOwners {
|
||||
if !apiequality.Semantic.DeepEqual(oldOwners[i], newOwners[i]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Translates ref to a DeleteAttribute deleting the object referred by the ref.
|
||||
// OwnerReference only records the object kind, which might map to multiple
|
||||
// resources, so multiple DeleteAttribute might be returned.
|
||||
func (a *gcPermissionsEnforcement) ownerRefToDeleteAttributeRecords(ref metav1.OwnerReference, attributes admission.Attributes) ([]authorizer.AttributesRecord, error) {
|
||||
var ret []authorizer.AttributesRecord
|
||||
groupVersion, err := schema.ParseGroupVersion(ref.APIVersion)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
mappings, err := a.restMapper.RESTMappings(schema.GroupKind{Group: groupVersion.Group, Kind: ref.Kind}, groupVersion.Version)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
for _, mapping := range mappings {
|
||||
ret = append(ret, authorizer.AttributesRecord{
|
||||
User: attributes.GetUserInfo(),
|
||||
Verb: "update",
|
||||
// ownerReference can only refer to an object in the same namespace, so attributes.GetNamespace() equals to the owner's namespace
|
||||
Namespace: attributes.GetNamespace(),
|
||||
APIGroup: mapping.Resource.Group,
|
||||
APIVersion: mapping.Resource.Version,
|
||||
Resource: mapping.Resource.Resource,
|
||||
Subresource: "finalizers",
|
||||
Name: ref.Name,
|
||||
ResourceRequest: true,
|
||||
Path: "",
|
||||
})
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// only keeps the blocking refs
|
||||
func blockingOwnerRefs(refs []metav1.OwnerReference) []metav1.OwnerReference {
|
||||
var ret []metav1.OwnerReference
|
||||
for _, ref := range refs {
|
||||
if ref.BlockOwnerDeletion != nil && *ref.BlockOwnerDeletion == true {
|
||||
ret = append(ret, ref)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func indexByUID(refs []metav1.OwnerReference) map[types.UID]metav1.OwnerReference {
|
||||
ret := make(map[types.UID]metav1.OwnerReference)
|
||||
for _, ref := range refs {
|
||||
ret[ref.UID] = ref
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Returns new blocking ownerReferences, and references whose blockOwnerDeletion
|
||||
// field is changed from nil or false to true.
|
||||
func newBlockingOwnerDeletionRefs(newObj, oldObj runtime.Object) []metav1.OwnerReference {
|
||||
newMeta, err := meta.Accessor(newObj)
|
||||
if err != nil {
|
||||
// if we don't have objectmeta, we don't have the object reference
|
||||
return nil
|
||||
}
|
||||
newRefs := newMeta.GetOwnerReferences()
|
||||
blockingNewRefs := blockingOwnerRefs(newRefs)
|
||||
if len(blockingNewRefs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if oldObj == nil {
|
||||
return blockingNewRefs
|
||||
}
|
||||
oldMeta, err := meta.Accessor(oldObj)
|
||||
if err != nil {
|
||||
// if we don't have objectmeta, treat it as if all the ownerReference are newly created
|
||||
return blockingNewRefs
|
||||
}
|
||||
|
||||
var ret []metav1.OwnerReference
|
||||
indexedOldRefs := indexByUID(oldMeta.GetOwnerReferences())
|
||||
for _, ref := range blockingNewRefs {
|
||||
oldRef, ok := indexedOldRefs[ref.UID]
|
||||
if !ok {
|
||||
// if ref is newly added, and it's blocking, then returns it.
|
||||
ret = append(ret, ref)
|
||||
continue
|
||||
}
|
||||
wasNotBlocking := oldRef.BlockOwnerDeletion == nil || *oldRef.BlockOwnerDeletion == false
|
||||
if wasNotBlocking {
|
||||
ret = append(ret, ref)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (a *gcPermissionsEnforcement) SetAuthorizer(authorizer authorizer.Authorizer) {
|
||||
a.authorizer = authorizer
|
||||
}
|
||||
|
||||
func (a *gcPermissionsEnforcement) SetRESTMapper(restMapper meta.RESTMapper) {
|
||||
a.restMapper = restMapper
|
||||
}
|
||||
|
||||
func (a *gcPermissionsEnforcement) ValidateInitialization() error {
|
||||
if a.authorizer == nil {
|
||||
return fmt.Errorf("missing authorizer")
|
||||
}
|
||||
if a.restMapper == nil {
|
||||
return fmt.Errorf("missing restMapper")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
528
vendor/k8s.io/kubernetes/plugin/pkg/admission/gc/gc_admission_test.go
generated
vendored
Normal file
528
vendor/k8s.io/kubernetes/plugin/pkg/admission/gc/gc_admission_test.go
generated
vendored
Normal file
@@ -0,0 +1,528 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package gc
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta/testrestmapper"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/initializer"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
kubeadmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
)
|
||||
|
||||
type fakeAuthorizer struct{}
|
||||
|
||||
func (fakeAuthorizer) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||
username := a.GetUser().GetName()
|
||||
|
||||
if username == "non-deleter" {
|
||||
if a.GetVerb() == "delete" {
|
||||
return authorizer.DecisionNoOpinion, "", nil
|
||||
}
|
||||
if a.GetVerb() == "update" && a.GetSubresource() == "finalizers" {
|
||||
return authorizer.DecisionNoOpinion, "", nil
|
||||
}
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
||||
|
||||
if username == "non-pod-deleter" {
|
||||
if a.GetVerb() == "delete" && a.GetResource() == "pods" {
|
||||
return authorizer.DecisionNoOpinion, "", nil
|
||||
}
|
||||
if a.GetVerb() == "update" && a.GetResource() == "pods" && a.GetSubresource() == "finalizers" {
|
||||
return authorizer.DecisionNoOpinion, "", nil
|
||||
}
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
||||
|
||||
if username == "non-rc-deleter" {
|
||||
if a.GetVerb() == "delete" && a.GetResource() == "replicationcontrollers" {
|
||||
return authorizer.DecisionNoOpinion, "", nil
|
||||
}
|
||||
if a.GetVerb() == "update" && a.GetResource() == "replicationcontrollers" && a.GetSubresource() == "finalizers" {
|
||||
return authorizer.DecisionNoOpinion, "", nil
|
||||
}
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
||||
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
||||
|
||||
// newGCPermissionsEnforcement returns the admission controller configured for testing.
|
||||
func newGCPermissionsEnforcement() (*gcPermissionsEnforcement, error) {
|
||||
// the pods/status endpoint is ignored by this plugin since old kubelets
|
||||
// corrupt them. the pod status strategy ensures status updates cannot mutate
|
||||
// ownerRef.
|
||||
whiteList := []whiteListItem{
|
||||
{
|
||||
groupResource: schema.GroupResource{Resource: "pods"},
|
||||
subresource: "status",
|
||||
},
|
||||
}
|
||||
gcAdmit := &gcPermissionsEnforcement{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
whiteList: whiteList,
|
||||
}
|
||||
|
||||
genericPluginInitializer := initializer.New(nil, nil, fakeAuthorizer{}, nil)
|
||||
pluginInitializer := kubeadmission.NewPluginInitializer(nil, nil, nil, testrestmapper.TestOnlyStaticRESTMapper(legacyscheme.Scheme), nil)
|
||||
initializersChain := admission.PluginInitializers{}
|
||||
initializersChain = append(initializersChain, genericPluginInitializer)
|
||||
initializersChain = append(initializersChain, pluginInitializer)
|
||||
|
||||
initializersChain.Initialize(gcAdmit)
|
||||
return gcAdmit, nil
|
||||
}
|
||||
|
||||
func TestGCAdmission(t *testing.T) {
|
||||
expectNoError := func(err error) bool {
|
||||
return err == nil
|
||||
}
|
||||
expectCantSetOwnerRefError := func(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(err.Error(), "cannot set an ownerRef on a resource you can't delete")
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
username string
|
||||
resource schema.GroupVersionResource
|
||||
subresource string
|
||||
oldObj runtime.Object
|
||||
newObj runtime.Object
|
||||
|
||||
checkError func(error) bool
|
||||
}{
|
||||
{
|
||||
name: "super-user, create, no objectref change",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: &api.Pod{},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "super-user, create, objectref change",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-deleter, create, no objectref change",
|
||||
username: "non-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: &api.Pod{},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-deleter, create, objectref change",
|
||||
username: "non-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-pod-deleter, create, no objectref change",
|
||||
username: "non-pod-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: &api.Pod{},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-pod-deleter, create, objectref change",
|
||||
username: "non-pod-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-pod-deleter, create, objectref change, but not a pod",
|
||||
username: "non-pod-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("not-pods"),
|
||||
newObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
|
||||
{
|
||||
name: "super-user, update, no objectref change",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{},
|
||||
newObj: &api.Pod{},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "super-user, update, no objectref change two",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
newObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "super-user, update, objectref change",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{},
|
||||
newObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-deleter, update, no objectref change",
|
||||
username: "non-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{},
|
||||
newObj: &api.Pod{},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-deleter, update, no objectref change two",
|
||||
username: "non-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
newObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-deleter, update, objectref change",
|
||||
username: "non-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{},
|
||||
newObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
checkError: expectCantSetOwnerRefError,
|
||||
},
|
||||
{
|
||||
name: "non-deleter, update, objectref change two",
|
||||
username: "non-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
newObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}, {Name: "second"}}}},
|
||||
checkError: expectCantSetOwnerRefError,
|
||||
},
|
||||
{
|
||||
name: "non-pod-deleter, update, no objectref change",
|
||||
username: "non-pod-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{},
|
||||
newObj: &api.Pod{},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-pod-deleter, update status, objectref change",
|
||||
username: "non-pod-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
subresource: "status",
|
||||
oldObj: &api.Pod{},
|
||||
newObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-pod-deleter, update, objectref change",
|
||||
username: "non-pod-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{},
|
||||
newObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
checkError: expectCantSetOwnerRefError,
|
||||
},
|
||||
{
|
||||
name: "non-pod-deleter, update, objectref change, but not a pod",
|
||||
username: "non-pod-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("not-pods"),
|
||||
oldObj: &api.Pod{},
|
||||
newObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
gcAdmit, err := newGCPermissionsEnforcement()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
operation := admission.Create
|
||||
if tc.oldObj != nil {
|
||||
operation = admission.Update
|
||||
}
|
||||
user := &user.DefaultInfo{Name: tc.username}
|
||||
attributes := admission.NewAttributesRecord(tc.newObj, tc.oldObj, schema.GroupVersionKind{}, metav1.NamespaceDefault, "foo", tc.resource, tc.subresource, operation, user)
|
||||
|
||||
err = gcAdmit.Validate(attributes)
|
||||
if !tc.checkError(err) {
|
||||
t.Errorf("unexpected err: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockOwnerDeletionAdmission(t *testing.T) {
|
||||
podWithOwnerRefs := func(refs ...metav1.OwnerReference) *api.Pod {
|
||||
var refSlice []metav1.OwnerReference
|
||||
refSlice = append(refSlice, refs...)
|
||||
|
||||
return &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
OwnerReferences: refSlice,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
getTrueVar := func() *bool {
|
||||
ret := true
|
||||
return &ret
|
||||
}
|
||||
|
||||
getFalseVar := func() *bool {
|
||||
ret := false
|
||||
return &ret
|
||||
}
|
||||
blockRC1 := metav1.OwnerReference{
|
||||
APIVersion: "v1",
|
||||
Kind: "ReplicationController",
|
||||
Name: "rc1",
|
||||
BlockOwnerDeletion: getTrueVar(),
|
||||
}
|
||||
blockRC2 := metav1.OwnerReference{
|
||||
APIVersion: "v1",
|
||||
Kind: "ReplicationController",
|
||||
Name: "rc2",
|
||||
BlockOwnerDeletion: getTrueVar(),
|
||||
}
|
||||
notBlockRC1 := metav1.OwnerReference{
|
||||
APIVersion: "v1",
|
||||
Kind: "ReplicationController",
|
||||
Name: "rc1",
|
||||
BlockOwnerDeletion: getFalseVar(),
|
||||
}
|
||||
notBlockRC2 := metav1.OwnerReference{
|
||||
APIVersion: "v1",
|
||||
Kind: "ReplicationController",
|
||||
Name: "rc2",
|
||||
BlockOwnerDeletion: getFalseVar(),
|
||||
}
|
||||
nilBlockRC1 := metav1.OwnerReference{
|
||||
APIVersion: "v1",
|
||||
Kind: "ReplicationController",
|
||||
Name: "rc1",
|
||||
}
|
||||
nilBlockRC2 := metav1.OwnerReference{
|
||||
APIVersion: "v1",
|
||||
Kind: "ReplicationController",
|
||||
Name: "rc2",
|
||||
}
|
||||
blockDS1 := metav1.OwnerReference{
|
||||
APIVersion: "extensions/v1beta1",
|
||||
Kind: "DaemonSet",
|
||||
Name: "ds1",
|
||||
BlockOwnerDeletion: getTrueVar(),
|
||||
}
|
||||
notBlockDS1 := metav1.OwnerReference{
|
||||
APIVersion: "extensions/v1beta1",
|
||||
Kind: "DaemonSet",
|
||||
Name: "ds1",
|
||||
BlockOwnerDeletion: getFalseVar(),
|
||||
}
|
||||
|
||||
expectNoError := func(err error) bool {
|
||||
return err == nil
|
||||
}
|
||||
expectCantSetBlockOwnerDeletionError := func(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(err.Error(), "cannot set blockOwnerDeletion if an ownerReference refers to a resource you can't set finalizers on")
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
username string
|
||||
resource schema.GroupVersionResource
|
||||
subresource string
|
||||
oldObj runtime.Object
|
||||
newObj runtime.Object
|
||||
|
||||
checkError func(error) bool
|
||||
}{
|
||||
// cases for create
|
||||
{
|
||||
name: "super-user, create, no ownerReferences",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: podWithOwnerRefs(),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "super-user, create, all ownerReferences have blockOwnerDeletion=false",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: podWithOwnerRefs(notBlockRC1, notBlockRC2),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "super-user, create, some ownerReferences have blockOwnerDeletion=true",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: podWithOwnerRefs(blockRC1, blockRC2),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-rc-deleter, create, no ownerReferences",
|
||||
username: "non-rc-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: podWithOwnerRefs(),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-rc-deleter, create, all ownerReferences have blockOwnerDeletion=false or nil",
|
||||
username: "non-rc-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: podWithOwnerRefs(notBlockRC1, nilBlockRC2),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-rc-deleter, create, some ownerReferences have blockOwnerDeletion=true",
|
||||
username: "non-rc-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: podWithOwnerRefs(blockRC1, notBlockRC2),
|
||||
checkError: expectCantSetBlockOwnerDeletionError,
|
||||
},
|
||||
{
|
||||
name: "non-rc-deleter, create, some ownerReferences have blockOwnerDeletion=true, but are pointing to daemonset",
|
||||
username: "non-rc-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: podWithOwnerRefs(blockDS1),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
// cases are for update
|
||||
{
|
||||
name: "super-user, update, no ownerReferences change blockOwnerDeletion",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: podWithOwnerRefs(nilBlockRC1),
|
||||
newObj: podWithOwnerRefs(notBlockRC1),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "super-user, update, some ownerReferences change to blockOwnerDeletion=true",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: podWithOwnerRefs(notBlockRC1),
|
||||
newObj: podWithOwnerRefs(blockRC1),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "super-user, update, add new ownerReferences with blockOwnerDeletion=true",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: podWithOwnerRefs(),
|
||||
newObj: podWithOwnerRefs(blockRC1),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-rc-deleter, update, no ownerReferences change blockOwnerDeletion",
|
||||
username: "non-rc-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: podWithOwnerRefs(nilBlockRC1),
|
||||
newObj: podWithOwnerRefs(notBlockRC1),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-rc-deleter, update, some ownerReferences change from blockOwnerDeletion=false to true",
|
||||
username: "non-rc-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: podWithOwnerRefs(notBlockRC1),
|
||||
newObj: podWithOwnerRefs(blockRC1),
|
||||
checkError: expectCantSetBlockOwnerDeletionError,
|
||||
},
|
||||
{
|
||||
name: "non-rc-deleter, update, some ownerReferences change from blockOwnerDeletion=nil to true",
|
||||
username: "non-rc-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: podWithOwnerRefs(nilBlockRC1),
|
||||
newObj: podWithOwnerRefs(blockRC1),
|
||||
checkError: expectCantSetBlockOwnerDeletionError,
|
||||
},
|
||||
{
|
||||
name: "non-rc-deleter, update, some ownerReferences change from blockOwnerDeletion=true to false",
|
||||
username: "non-rc-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: podWithOwnerRefs(blockRC1),
|
||||
newObj: podWithOwnerRefs(notBlockRC1),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-rc-deleter, update, some ownerReferences change blockOwnerDeletion, but all such references are to daemonset",
|
||||
username: "non-rc-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: podWithOwnerRefs(notBlockDS1),
|
||||
newObj: podWithOwnerRefs(blockDS1),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-rc-deleter, update, add new ownerReferences with blockOwnerDeletion=nil or false",
|
||||
username: "non-rc-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: podWithOwnerRefs(),
|
||||
newObj: podWithOwnerRefs(notBlockRC1, nilBlockRC2),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-rc-deleter, update, add new ownerReferences with blockOwnerDeletion=true",
|
||||
username: "non-rc-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: podWithOwnerRefs(),
|
||||
newObj: podWithOwnerRefs(blockRC1),
|
||||
checkError: expectCantSetBlockOwnerDeletionError,
|
||||
},
|
||||
{
|
||||
name: "non-rc-deleter, update, add new ownerReferences with blockOwnerDeletion=true, but the references are to daemonset",
|
||||
username: "non-rc-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: podWithOwnerRefs(),
|
||||
newObj: podWithOwnerRefs(blockDS1),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
}
|
||||
gcAdmit, err := newGCPermissionsEnforcement()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
operation := admission.Create
|
||||
if tc.oldObj != nil {
|
||||
operation = admission.Update
|
||||
}
|
||||
user := &user.DefaultInfo{Name: tc.username}
|
||||
attributes := admission.NewAttributesRecord(tc.newObj, tc.oldObj, schema.GroupVersionKind{}, metav1.NamespaceDefault, "foo", tc.resource, tc.subresource, operation, user)
|
||||
|
||||
err := gcAdmit.Validate(attributes)
|
||||
if !tc.checkError(err) {
|
||||
t.Errorf("%v: unexpected err: %v", tc.name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
62
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/BUILD
generated
vendored
Normal file
62
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/BUILD
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"admission.go",
|
||||
"config.go",
|
||||
"doc.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/imagepolicy",
|
||||
deps = [
|
||||
"//pkg/api/legacyscheme:go_default_library",
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/imagepolicy/install:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/api/imagepolicy/v1alpha1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/cache:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/webhook:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"admission_test.go",
|
||||
"certs_test.go",
|
||||
"config_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/imagepolicy/install:go_default_library",
|
||||
"//vendor/k8s.io/api/imagepolicy/v1alpha1: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/tools/clientcmd/api/v1:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
254
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/admission.go
generated
vendored
Normal file
254
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/admission.go
generated
vendored
Normal file
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package imagepolicy contains an admission controller that configures a webhook to which policy
|
||||
// decisions are delegated.
|
||||
package imagepolicy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/api/imagepolicy/v1alpha1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/cache"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
|
||||
// install the clientgo image policy API for use with api registry
|
||||
_ "k8s.io/kubernetes/pkg/apis/imagepolicy/install"
|
||||
)
|
||||
|
||||
// PluginName indicates name of admission plugin.
|
||||
const PluginName = "ImagePolicyWebhook"
|
||||
|
||||
var (
|
||||
groupVersions = []schema.GroupVersion{v1alpha1.SchemeGroupVersion}
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
|
||||
newImagePolicyWebhook, err := NewImagePolicyWebhook(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newImagePolicyWebhook, nil
|
||||
})
|
||||
}
|
||||
|
||||
// Plugin is an implementation of admission.Interface.
|
||||
type Plugin struct {
|
||||
*admission.Handler
|
||||
webhook *webhook.GenericWebhook
|
||||
responseCache *cache.LRUExpireCache
|
||||
allowTTL time.Duration
|
||||
denyTTL time.Duration
|
||||
retryBackoff time.Duration
|
||||
defaultAllow bool
|
||||
}
|
||||
|
||||
var _ admission.ValidationInterface = &Plugin{}
|
||||
|
||||
func (a *Plugin) statusTTL(status v1alpha1.ImageReviewStatus) time.Duration {
|
||||
if status.Allowed {
|
||||
return a.allowTTL
|
||||
}
|
||||
return a.denyTTL
|
||||
}
|
||||
|
||||
// Filter out annotations that don't match *.image-policy.k8s.io/*
|
||||
func (a *Plugin) filterAnnotations(allAnnotations map[string]string) map[string]string {
|
||||
annotations := make(map[string]string)
|
||||
for k, v := range allAnnotations {
|
||||
if strings.Contains(k, ".image-policy.k8s.io/") {
|
||||
annotations[k] = v
|
||||
}
|
||||
}
|
||||
return annotations
|
||||
}
|
||||
|
||||
// Function to call on webhook failure; behavior determined by defaultAllow flag
|
||||
func (a *Plugin) webhookError(pod *api.Pod, attributes admission.Attributes, err error) error {
|
||||
if err != nil {
|
||||
glog.V(2).Infof("error contacting webhook backend: %s", err)
|
||||
if a.defaultAllow {
|
||||
annotations := pod.GetAnnotations()
|
||||
if annotations == nil {
|
||||
annotations = make(map[string]string)
|
||||
}
|
||||
annotations[api.ImagePolicyFailedOpenKey] = "true"
|
||||
pod.ObjectMeta.SetAnnotations(annotations)
|
||||
glog.V(2).Infof("resource allowed in spite of webhook backend failure")
|
||||
return nil
|
||||
}
|
||||
glog.V(2).Infof("resource not allowed due to webhook backend failure ")
|
||||
return admission.NewForbidden(attributes, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate makes an admission decision based on the request attributes
|
||||
func (a *Plugin) Validate(attributes admission.Attributes) (err error) {
|
||||
// Ignore all calls to subresources or resources other than pods.
|
||||
if attributes.GetSubresource() != "" || attributes.GetResource().GroupResource() != api.Resource("pods") {
|
||||
return nil
|
||||
}
|
||||
|
||||
pod, ok := attributes.GetObject().(*api.Pod)
|
||||
if !ok {
|
||||
return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
|
||||
}
|
||||
|
||||
// Build list of ImageReviewContainerSpec
|
||||
var imageReviewContainerSpecs []v1alpha1.ImageReviewContainerSpec
|
||||
containers := make([]api.Container, 0, len(pod.Spec.Containers)+len(pod.Spec.InitContainers))
|
||||
containers = append(containers, pod.Spec.Containers...)
|
||||
containers = append(containers, pod.Spec.InitContainers...)
|
||||
for _, c := range containers {
|
||||
imageReviewContainerSpecs = append(imageReviewContainerSpecs, v1alpha1.ImageReviewContainerSpec{
|
||||
Image: c.Image,
|
||||
})
|
||||
}
|
||||
imageReview := v1alpha1.ImageReview{
|
||||
Spec: v1alpha1.ImageReviewSpec{
|
||||
Containers: imageReviewContainerSpecs,
|
||||
Annotations: a.filterAnnotations(pod.Annotations),
|
||||
Namespace: attributes.GetNamespace(),
|
||||
},
|
||||
}
|
||||
if err := a.admitPod(pod, attributes, &imageReview); err != nil {
|
||||
return admission.NewForbidden(attributes, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Plugin) admitPod(pod *api.Pod, attributes admission.Attributes, review *v1alpha1.ImageReview) error {
|
||||
cacheKey, err := json.Marshal(review.Spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if entry, ok := a.responseCache.Get(string(cacheKey)); ok {
|
||||
review.Status = entry.(v1alpha1.ImageReviewStatus)
|
||||
} else {
|
||||
result := a.webhook.WithExponentialBackoff(func() rest.Result {
|
||||
return a.webhook.RestClient.Post().Body(review).Do()
|
||||
})
|
||||
|
||||
if err := result.Error(); err != nil {
|
||||
return a.webhookError(pod, attributes, err)
|
||||
}
|
||||
var statusCode int
|
||||
if result.StatusCode(&statusCode); statusCode < 200 || statusCode >= 300 {
|
||||
return a.webhookError(pod, attributes, fmt.Errorf("Error contacting webhook: %d", statusCode))
|
||||
}
|
||||
|
||||
if err := result.Into(review); err != nil {
|
||||
return a.webhookError(pod, attributes, err)
|
||||
}
|
||||
|
||||
a.responseCache.Add(string(cacheKey), review.Status, a.statusTTL(review.Status))
|
||||
}
|
||||
|
||||
if !review.Status.Allowed {
|
||||
if len(review.Status.Reason) > 0 {
|
||||
return fmt.Errorf("image policy webhook backend denied one or more images: %s", review.Status.Reason)
|
||||
}
|
||||
return errors.New("one or more images rejected by webhook backend")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewImagePolicyWebhook a new ImagePolicyWebhook plugin from the provided config file.
|
||||
// The config file is specified by --admission-control-config-file and has the
|
||||
// following format for a webhook:
|
||||
//
|
||||
// {
|
||||
// "imagePolicy": {
|
||||
// "kubeConfigFile": "path/to/kubeconfig/for/backend",
|
||||
// "allowTTL": 30, # time in s to cache approval
|
||||
// "denyTTL": 30, # time in s to cache denial
|
||||
// "retryBackoff": 500, # time in ms to wait between retries
|
||||
// "defaultAllow": true # determines behavior if the webhook backend fails
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// The config file may be json or yaml.
|
||||
//
|
||||
// The kubeconfig property refers to another file in the kubeconfig format which
|
||||
// specifies how to connect to the webhook backend.
|
||||
//
|
||||
// The kubeconfig's cluster field is used to refer to the remote service, user refers to the returned authorizer.
|
||||
//
|
||||
// # clusters refers to the remote service.
|
||||
// clusters:
|
||||
// - name: name-of-remote-imagepolicy-service
|
||||
// cluster:
|
||||
// certificate-authority: /path/to/ca.pem # CA for verifying the remote service.
|
||||
// server: https://images.example.com/policy # URL of remote service to query. Must use 'https'.
|
||||
//
|
||||
// # users refers to the API server's webhook configuration.
|
||||
// users:
|
||||
// - name: name-of-api-server
|
||||
// user:
|
||||
// client-certificate: /path/to/cert.pem # cert for the webhook plugin to use
|
||||
// client-key: /path/to/key.pem # key matching the cert
|
||||
//
|
||||
// For additional HTTP configuration, refer to the kubeconfig documentation
|
||||
// http://kubernetes.io/v1.1/docs/user-guide/kubeconfig-file.html.
|
||||
func NewImagePolicyWebhook(configFile io.Reader) (*Plugin, error) {
|
||||
if configFile == nil {
|
||||
return nil, fmt.Errorf("no config specified")
|
||||
}
|
||||
|
||||
// TODO: move this to a versioned configuration file format
|
||||
var config AdmissionConfig
|
||||
d := yaml.NewYAMLOrJSONDecoder(configFile, 4096)
|
||||
err := d.Decode(&config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
whConfig := config.ImagePolicyWebhook
|
||||
if err := normalizeWebhookConfig(&whConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gw, err := webhook.NewGenericWebhook(legacyscheme.Scheme, legacyscheme.Codecs, whConfig.KubeConfigFile, groupVersions, whConfig.RetryBackoff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Plugin{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
webhook: gw,
|
||||
responseCache: cache.NewLRUExpireCache(1024),
|
||||
allowTTL: whConfig.AllowTTL,
|
||||
denyTTL: whConfig.DenyTTL,
|
||||
defaultAllow: whConfig.DefaultAllow,
|
||||
}, nil
|
||||
}
|
||||
948
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/admission_test.go
generated
vendored
Normal file
948
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/admission_test.go
generated
vendored
Normal file
@@ -0,0 +1,948 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package imagepolicy
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/api/imagepolicy/v1alpha1"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/client-go/tools/clientcmd/api/v1"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
_ "k8s.io/kubernetes/pkg/apis/imagepolicy/install"
|
||||
)
|
||||
|
||||
const defaultConfigTmplJSON = `
|
||||
{
|
||||
"imagePolicy": {
|
||||
"kubeConfigFile": "{{ .KubeConfig }}",
|
||||
"allowTTL": {{ .AllowTTL }},
|
||||
"denyTTL": {{ .DenyTTL }},
|
||||
"retryBackoff": {{ .RetryBackoff }},
|
||||
"defaultAllow": {{ .DefaultAllow }}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const defaultConfigTmplYAML = `
|
||||
imagePolicy:
|
||||
kubeConfigFile: "{{ .KubeConfig }}"
|
||||
allowTTL: {{ .AllowTTL }}
|
||||
denyTTL: {{ .DenyTTL }}
|
||||
retryBackoff: {{ .RetryBackoff }}
|
||||
defaultAllow: {{ .DefaultAllow }}
|
||||
`
|
||||
|
||||
func TestNewFromConfig(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
data := struct {
|
||||
CA string
|
||||
Cert string
|
||||
Key string
|
||||
}{
|
||||
CA: filepath.Join(dir, "ca.pem"),
|
||||
Cert: filepath.Join(dir, "clientcert.pem"),
|
||||
Key: filepath.Join(dir, "clientkey.pem"),
|
||||
}
|
||||
|
||||
files := []struct {
|
||||
name string
|
||||
data []byte
|
||||
}{
|
||||
{data.CA, caCert},
|
||||
{data.Cert, clientCert},
|
||||
{data.Key, clientKey},
|
||||
}
|
||||
for _, file := range files {
|
||||
if err := ioutil.WriteFile(file.name, file.data, 0400); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
msg string
|
||||
kubeConfigTmpl string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
msg: "a single cluster and single user",
|
||||
kubeConfigTmpl: `
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority: {{ .CA }}
|
||||
server: https://admission.example.com
|
||||
name: foobar
|
||||
users:
|
||||
- name: a cluster
|
||||
user:
|
||||
client-certificate: {{ .Cert }}
|
||||
client-key: {{ .Key }}
|
||||
`,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
msg: "multiple clusters with no context",
|
||||
kubeConfigTmpl: `
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority: {{ .CA }}
|
||||
server: https://admission.example.com
|
||||
name: foobar
|
||||
- cluster:
|
||||
certificate-authority: a bad certificate path
|
||||
server: https://admission.example.com
|
||||
name: barfoo
|
||||
users:
|
||||
- name: a name
|
||||
user:
|
||||
client-certificate: {{ .Cert }}
|
||||
client-key: {{ .Key }}
|
||||
`,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
msg: "multiple clusters with a context",
|
||||
kubeConfigTmpl: `
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority: a bad certificate path
|
||||
server: https://admission.example.com
|
||||
name: foobar
|
||||
- cluster:
|
||||
certificate-authority: {{ .CA }}
|
||||
server: https://admission.example.com
|
||||
name: barfoo
|
||||
users:
|
||||
- name: a name
|
||||
user:
|
||||
client-certificate: {{ .Cert }}
|
||||
client-key: {{ .Key }}
|
||||
contexts:
|
||||
- name: default
|
||||
context:
|
||||
cluster: barfoo
|
||||
user: a name
|
||||
current-context: default
|
||||
`,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
msg: "cluster with bad certificate path specified",
|
||||
kubeConfigTmpl: `
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority: a bad certificate path
|
||||
server: https://admission.example.com
|
||||
name: foobar
|
||||
- cluster:
|
||||
certificate-authority: {{ .CA }}
|
||||
server: https://admission.example.com
|
||||
name: barfoo
|
||||
users:
|
||||
- name: a name
|
||||
user:
|
||||
client-certificate: {{ .Cert }}
|
||||
client-key: {{ .Key }}
|
||||
contexts:
|
||||
- name: default
|
||||
context:
|
||||
cluster: foobar
|
||||
user: a name
|
||||
current-context: default
|
||||
`,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
// Use a closure so defer statements trigger between loop iterations.
|
||||
err := func() error {
|
||||
tempfile, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p := tempfile.Name()
|
||||
defer os.Remove(p)
|
||||
|
||||
tmpl, err := template.New("test").Parse(tt.kubeConfigTmpl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse test template: %v", err)
|
||||
}
|
||||
if err := tmpl.Execute(tempfile, data); err != nil {
|
||||
return fmt.Errorf("failed to execute test template: %v", err)
|
||||
}
|
||||
|
||||
tempconfigfile, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pc := tempconfigfile.Name()
|
||||
defer os.Remove(pc)
|
||||
|
||||
configTmpl, err := template.New("testconfig").Parse(defaultConfigTmplJSON)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse test template: %v", err)
|
||||
}
|
||||
dataConfig := struct {
|
||||
KubeConfig string
|
||||
AllowTTL int
|
||||
DenyTTL int
|
||||
RetryBackoff int
|
||||
DefaultAllow bool
|
||||
}{
|
||||
KubeConfig: p,
|
||||
AllowTTL: 500,
|
||||
DenyTTL: 500,
|
||||
RetryBackoff: 500,
|
||||
DefaultAllow: true,
|
||||
}
|
||||
if err := configTmpl.Execute(tempconfigfile, dataConfig); err != nil {
|
||||
return fmt.Errorf("failed to execute test template: %v", err)
|
||||
}
|
||||
|
||||
// Create a new admission controller
|
||||
configFile, err := os.Open(pc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read test config: %v", err)
|
||||
}
|
||||
defer configFile.Close()
|
||||
|
||||
_, err = NewImagePolicyWebhook(configFile)
|
||||
return err
|
||||
}()
|
||||
if err != nil && !tt.wantErr {
|
||||
t.Errorf("failed to load plugin from config %q: %v", tt.msg, err)
|
||||
}
|
||||
if err == nil && tt.wantErr {
|
||||
t.Errorf("wanted an error when loading config, did not get one: %q", tt.msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Service mocks a remote service.
|
||||
type Service interface {
|
||||
Review(*v1alpha1.ImageReview)
|
||||
HTTPStatusCode() int
|
||||
}
|
||||
|
||||
// NewTestServer wraps a Service as an httptest.Server.
|
||||
func NewTestServer(s Service, cert, key, caCert []byte) (*httptest.Server, error) {
|
||||
var tlsConfig *tls.Config
|
||||
if cert != nil {
|
||||
cert, err := tls.X509KeyPair(cert, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig = &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||
}
|
||||
|
||||
if caCert != nil {
|
||||
rootCAs := x509.NewCertPool()
|
||||
rootCAs.AppendCertsFromPEM(caCert)
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = &tls.Config{}
|
||||
}
|
||||
tlsConfig.ClientCAs = rootCAs
|
||||
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
}
|
||||
|
||||
serveHTTP := func(w http.ResponseWriter, r *http.Request) {
|
||||
var review v1alpha1.ImageReview
|
||||
if err := json.NewDecoder(r.Body).Decode(&review); err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to decode body: %v", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if s.HTTPStatusCode() < 200 || s.HTTPStatusCode() >= 300 {
|
||||
http.Error(w, "HTTP Error", s.HTTPStatusCode())
|
||||
return
|
||||
}
|
||||
s.Review(&review)
|
||||
type status struct {
|
||||
Allowed bool `json:"allowed"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
resp := struct {
|
||||
APIVersion string `json:"apiVersion"`
|
||||
Kind string `json:"kind"`
|
||||
Status status `json:"status"`
|
||||
}{
|
||||
APIVersion: v1alpha1.SchemeGroupVersion.String(),
|
||||
Kind: "ImageReview",
|
||||
Status: status{review.Status.Allowed, review.Status.Reason},
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}
|
||||
|
||||
server := httptest.NewUnstartedServer(http.HandlerFunc(serveHTTP))
|
||||
server.TLS = tlsConfig
|
||||
server.StartTLS()
|
||||
return server, nil
|
||||
}
|
||||
|
||||
// A service that can be set to allow all or deny all authorization requests.
|
||||
type mockService struct {
|
||||
allow bool
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func (m *mockService) Review(r *v1alpha1.ImageReview) {
|
||||
r.Status.Allowed = m.allow
|
||||
|
||||
// hardcoded overrides
|
||||
if r.Spec.Containers[0].Image == "good" {
|
||||
r.Status.Allowed = true
|
||||
}
|
||||
|
||||
for _, c := range r.Spec.Containers {
|
||||
if c.Image == "bad" {
|
||||
r.Status.Allowed = false
|
||||
}
|
||||
}
|
||||
|
||||
if !r.Status.Allowed {
|
||||
r.Status.Reason = "not allowed"
|
||||
}
|
||||
}
|
||||
func (m *mockService) Allow() { m.allow = true }
|
||||
func (m *mockService) Deny() { m.allow = false }
|
||||
func (m *mockService) HTTPStatusCode() int { return m.statusCode }
|
||||
|
||||
// newImagePolicyWebhook creates a temporary kubeconfig file from the provided arguments and attempts to load
|
||||
// a new newImagePolicyWebhook from it.
|
||||
func newImagePolicyWebhook(callbackURL string, clientCert, clientKey, ca []byte, cacheTime time.Duration, defaultAllow bool) (*Plugin, error) {
|
||||
tempfile, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := tempfile.Name()
|
||||
defer os.Remove(p)
|
||||
config := v1.Config{
|
||||
Clusters: []v1.NamedCluster{
|
||||
{
|
||||
Cluster: v1.Cluster{Server: callbackURL, CertificateAuthorityData: ca},
|
||||
},
|
||||
},
|
||||
AuthInfos: []v1.NamedAuthInfo{
|
||||
{
|
||||
AuthInfo: v1.AuthInfo{ClientCertificateData: clientCert, ClientKeyData: clientKey},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := json.NewEncoder(tempfile).Encode(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tempconfigfile, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pc := tempconfigfile.Name()
|
||||
defer os.Remove(pc)
|
||||
|
||||
configTmpl, err := template.New("testconfig").Parse(defaultConfigTmplYAML)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse test template: %v", err)
|
||||
}
|
||||
dataConfig := struct {
|
||||
KubeConfig string
|
||||
AllowTTL int64
|
||||
DenyTTL int64
|
||||
RetryBackoff int64
|
||||
DefaultAllow bool
|
||||
}{
|
||||
KubeConfig: p,
|
||||
AllowTTL: cacheTime.Nanoseconds(),
|
||||
DenyTTL: cacheTime.Nanoseconds(),
|
||||
RetryBackoff: 0,
|
||||
DefaultAllow: defaultAllow,
|
||||
}
|
||||
if err := configTmpl.Execute(tempconfigfile, dataConfig); err != nil {
|
||||
return nil, fmt.Errorf("failed to execute test template: %v", err)
|
||||
}
|
||||
|
||||
// Create a new admission controller
|
||||
configFile, err := os.Open(pc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read test config: %v", err)
|
||||
}
|
||||
defer configFile.Close()
|
||||
wh, err := NewImagePolicyWebhook(configFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wh, err
|
||||
}
|
||||
|
||||
func TestTLSConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
test string
|
||||
clientCert, clientKey, clientCA []byte
|
||||
serverCert, serverKey, serverCA []byte
|
||||
wantAllowed, wantErr bool
|
||||
}{
|
||||
{
|
||||
test: "TLS setup between client and server",
|
||||
clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
|
||||
serverCert: serverCert, serverKey: serverKey, serverCA: caCert,
|
||||
wantAllowed: true,
|
||||
},
|
||||
{
|
||||
test: "Server does not require client auth",
|
||||
clientCA: caCert,
|
||||
serverCert: serverCert, serverKey: serverKey,
|
||||
wantAllowed: true,
|
||||
},
|
||||
{
|
||||
test: "Server does not require client auth, client provides it",
|
||||
clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
|
||||
serverCert: serverCert, serverKey: serverKey,
|
||||
wantAllowed: true,
|
||||
},
|
||||
{
|
||||
test: "Client does not trust server",
|
||||
clientCert: clientCert, clientKey: clientKey,
|
||||
serverCert: serverCert, serverKey: serverKey,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "Server does not trust client",
|
||||
clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
|
||||
serverCert: serverCert, serverKey: serverKey, serverCA: badCACert,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
// Plugin does not support insecure configurations.
|
||||
test: "Server is using insecure connection",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
// Use a closure so defer statements trigger between loop iterations.
|
||||
func() {
|
||||
service := new(mockService)
|
||||
service.statusCode = 200
|
||||
|
||||
server, err := NewTestServer(service, tt.serverCert, tt.serverKey, tt.serverCA)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create server: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
defer server.Close()
|
||||
|
||||
wh, err := newImagePolicyWebhook(server.URL, tt.clientCert, tt.clientKey, tt.clientCA, -1, false)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create client: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
pod := goodPod(strconv.Itoa(rand.Intn(1000)))
|
||||
attr := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &user.DefaultInfo{})
|
||||
|
||||
// Allow all and see if we get an error.
|
||||
service.Allow()
|
||||
|
||||
err = wh.Validate(attr)
|
||||
if tt.wantAllowed {
|
||||
if err != nil {
|
||||
t.Errorf("expected successful admission")
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Errorf("expected failed admission")
|
||||
}
|
||||
}
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("expected error making admission request: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to admit with AllowAll policy: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Deny()
|
||||
if err := wh.Validate(attr); err == nil {
|
||||
t.Errorf("%s: incorrectly admitted with DenyAll policy", tt.test)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
type webhookCacheTestCase struct {
|
||||
statusCode int
|
||||
expectedErr bool
|
||||
expectedAuthorized bool
|
||||
expectedCached bool
|
||||
}
|
||||
|
||||
func testWebhookCacheCases(t *testing.T, serv *mockService, wh *Plugin, attr admission.Attributes, tests []webhookCacheTestCase) {
|
||||
for _, test := range tests {
|
||||
serv.statusCode = test.statusCode
|
||||
err := wh.Validate(attr)
|
||||
authorized := err == nil
|
||||
|
||||
if test.expectedErr && err == nil {
|
||||
t.Errorf("Expected error")
|
||||
} else if !test.expectedErr && err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if test.expectedAuthorized && !authorized {
|
||||
if test.expectedCached {
|
||||
t.Errorf("Webhook should have successful response cached, but authorizer reported unauthorized.")
|
||||
} else {
|
||||
t.Errorf("Webhook returned HTTP %d, but authorizer reported unauthorized.", test.statusCode)
|
||||
}
|
||||
} else if !test.expectedAuthorized && authorized {
|
||||
t.Errorf("Webhook returned HTTP %d, but authorizer reported success.", test.statusCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestWebhookCache verifies that error responses from the server are not
|
||||
// cached, but successful responses are.
|
||||
func TestWebhookCache(t *testing.T) {
|
||||
serv := new(mockService)
|
||||
s, err := NewTestServer(serv, serverCert, serverKey, caCert)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
// Create an admission controller that caches successful responses.
|
||||
wh, err := newImagePolicyWebhook(s.URL, clientCert, clientKey, caCert, 200, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tests := []webhookCacheTestCase{
|
||||
{statusCode: 500, expectedErr: true, expectedAuthorized: false, expectedCached: false},
|
||||
{statusCode: 404, expectedErr: true, expectedAuthorized: false, expectedCached: false},
|
||||
{statusCode: 403, expectedErr: true, expectedAuthorized: false, expectedCached: false},
|
||||
{statusCode: 401, expectedErr: true, expectedAuthorized: false, expectedCached: false},
|
||||
{statusCode: 200, expectedErr: false, expectedAuthorized: true, expectedCached: false},
|
||||
{statusCode: 500, expectedErr: false, expectedAuthorized: true, expectedCached: true},
|
||||
}
|
||||
|
||||
attr := admission.NewAttributesRecord(goodPod("test"), nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &user.DefaultInfo{})
|
||||
|
||||
serv.allow = true
|
||||
|
||||
testWebhookCacheCases(t, serv, wh, attr, tests)
|
||||
|
||||
// For a different request, webhook should be called again.
|
||||
tests = []webhookCacheTestCase{
|
||||
{statusCode: 500, expectedErr: true, expectedAuthorized: false, expectedCached: false},
|
||||
{statusCode: 200, expectedErr: false, expectedAuthorized: true, expectedCached: false},
|
||||
{statusCode: 500, expectedErr: false, expectedAuthorized: true, expectedCached: true},
|
||||
}
|
||||
attr = admission.NewAttributesRecord(goodPod("test2"), nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &user.DefaultInfo{})
|
||||
|
||||
testWebhookCacheCases(t, serv, wh, attr, tests)
|
||||
}
|
||||
|
||||
func TestContainerCombinations(t *testing.T) {
|
||||
tests := []struct {
|
||||
test string
|
||||
pod *api.Pod
|
||||
wantAllowed, wantErr bool
|
||||
}{
|
||||
{
|
||||
test: "Single container allowed",
|
||||
pod: goodPod("good"),
|
||||
wantAllowed: true,
|
||||
},
|
||||
{
|
||||
test: "Single container denied",
|
||||
pod: goodPod("bad"),
|
||||
wantAllowed: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "One good container, one bad",
|
||||
pod: &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
ServiceAccountName: "default",
|
||||
SecurityContext: &api.PodSecurityContext{},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "bad",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
{
|
||||
Image: "good",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantAllowed: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "Multiple good containers",
|
||||
pod: &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
ServiceAccountName: "default",
|
||||
SecurityContext: &api.PodSecurityContext{},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "good",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
{
|
||||
Image: "good",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantAllowed: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
test: "Multiple bad containers",
|
||||
pod: &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
ServiceAccountName: "default",
|
||||
SecurityContext: &api.PodSecurityContext{},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "bad",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
{
|
||||
Image: "bad",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantAllowed: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "Good container, bad init container",
|
||||
pod: &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
ServiceAccountName: "default",
|
||||
SecurityContext: &api.PodSecurityContext{},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "good",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
InitContainers: []api.Container{
|
||||
{
|
||||
Image: "bad",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantAllowed: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "Bad container, good init container",
|
||||
pod: &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
ServiceAccountName: "default",
|
||||
SecurityContext: &api.PodSecurityContext{},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "bad",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
InitContainers: []api.Container{
|
||||
{
|
||||
Image: "good",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantAllowed: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "Good container, good init container",
|
||||
pod: &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
ServiceAccountName: "default",
|
||||
SecurityContext: &api.PodSecurityContext{},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "good",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
InitContainers: []api.Container{
|
||||
{
|
||||
Image: "good",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantAllowed: true,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
// Use a closure so defer statements trigger between loop iterations.
|
||||
func() {
|
||||
service := new(mockService)
|
||||
service.statusCode = 200
|
||||
|
||||
server, err := NewTestServer(service, serverCert, serverKey, caCert)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create server: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
defer server.Close()
|
||||
|
||||
wh, err := newImagePolicyWebhook(server.URL, clientCert, clientKey, caCert, 0, false)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create client: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
|
||||
attr := admission.NewAttributesRecord(tt.pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &user.DefaultInfo{})
|
||||
|
||||
err = wh.Validate(attr)
|
||||
if tt.wantAllowed {
|
||||
if err != nil {
|
||||
t.Errorf("expected successful admission: %s", tt.test)
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Errorf("expected failed admission: %s", tt.test)
|
||||
}
|
||||
}
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("expected error making admission request: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to admit: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultAllow(t *testing.T) {
|
||||
tests := []struct {
|
||||
test string
|
||||
pod *api.Pod
|
||||
wantAllowed, wantErr, defaultAllow bool
|
||||
}{
|
||||
{
|
||||
test: "DefaultAllow = true, backend unreachable, bad image",
|
||||
pod: goodPod("bad"),
|
||||
defaultAllow: true,
|
||||
wantAllowed: true,
|
||||
},
|
||||
{
|
||||
test: "DefaultAllow = true, backend unreachable, good image",
|
||||
pod: goodPod("good"),
|
||||
defaultAllow: true,
|
||||
wantAllowed: true,
|
||||
},
|
||||
{
|
||||
test: "DefaultAllow = false, backend unreachable, good image",
|
||||
pod: goodPod("good"),
|
||||
defaultAllow: false,
|
||||
wantAllowed: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "DefaultAllow = false, backend unreachable, bad image",
|
||||
pod: goodPod("bad"),
|
||||
defaultAllow: false,
|
||||
wantAllowed: false,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
// Use a closure so defer statements trigger between loop iterations.
|
||||
func() {
|
||||
service := new(mockService)
|
||||
service.statusCode = 500
|
||||
|
||||
server, err := NewTestServer(service, serverCert, serverKey, caCert)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create server: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
defer server.Close()
|
||||
|
||||
wh, err := newImagePolicyWebhook(server.URL, clientCert, clientKey, caCert, 0, tt.defaultAllow)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create client: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
|
||||
attr := admission.NewAttributesRecord(tt.pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &user.DefaultInfo{})
|
||||
|
||||
err = wh.Validate(attr)
|
||||
if tt.wantAllowed {
|
||||
if err != nil {
|
||||
t.Errorf("expected successful admission")
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Errorf("expected failed admission")
|
||||
}
|
||||
}
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("expected error making admission request: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to admit: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// A service that can record annotations sent to it
|
||||
type annotationService struct {
|
||||
annotations map[string]string
|
||||
}
|
||||
|
||||
func (a *annotationService) Review(r *v1alpha1.ImageReview) {
|
||||
a.annotations = make(map[string]string)
|
||||
for k, v := range r.Spec.Annotations {
|
||||
a.annotations[k] = v
|
||||
}
|
||||
r.Status.Allowed = true
|
||||
}
|
||||
func (a *annotationService) HTTPStatusCode() int { return 200 }
|
||||
func (a *annotationService) Annotations() map[string]string { return a.annotations }
|
||||
|
||||
func TestAnnotationFiltering(t *testing.T) {
|
||||
tests := []struct {
|
||||
test string
|
||||
annotations map[string]string
|
||||
outAnnotations map[string]string
|
||||
}{
|
||||
{
|
||||
test: "all annotations filtered out",
|
||||
annotations: map[string]string{
|
||||
"test": "test",
|
||||
"another": "annotation",
|
||||
"": "",
|
||||
},
|
||||
outAnnotations: map[string]string{},
|
||||
},
|
||||
{
|
||||
test: "image-policy annotations allowed",
|
||||
annotations: map[string]string{
|
||||
"my.image-policy.k8s.io/test": "test",
|
||||
"other.image-policy.k8s.io/test2": "annotation",
|
||||
"test": "test",
|
||||
"another": "another",
|
||||
"": "",
|
||||
},
|
||||
outAnnotations: map[string]string{
|
||||
"my.image-policy.k8s.io/test": "test",
|
||||
"other.image-policy.k8s.io/test2": "annotation",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
// Use a closure so defer statements trigger between loop iterations.
|
||||
func() {
|
||||
service := new(annotationService)
|
||||
|
||||
server, err := NewTestServer(service, serverCert, serverKey, caCert)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create server: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
defer server.Close()
|
||||
|
||||
wh, err := newImagePolicyWebhook(server.URL, clientCert, clientKey, caCert, 0, true)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create client: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
|
||||
pod := goodPod("test")
|
||||
pod.Annotations = tt.annotations
|
||||
|
||||
attr := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &user.DefaultInfo{})
|
||||
|
||||
err = wh.Validate(attr)
|
||||
if err != nil {
|
||||
t.Errorf("expected successful admission")
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tt.outAnnotations, service.Annotations()) {
|
||||
t.Errorf("expected annotations sent to webhook: %v to match expected: %v", service.Annotations(), tt.outAnnotations)
|
||||
}
|
||||
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func goodPod(containerID string) *api.Pod {
|
||||
return &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
ServiceAccountName: "default",
|
||||
SecurityContext: &api.PodSecurityContext{},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: containerID,
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
211
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/certs_test.go
generated
vendored
Normal file
211
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/certs_test.go
generated
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// This file was generated using openssl by the gencerts.sh script
|
||||
// and holds raw certificates for the imagepolicy webhook tests.
|
||||
|
||||
package imagepolicy
|
||||
|
||||
var caKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAoKjaP9PtRAGRNCx8z+0LTGt2eEduqElcPrm8EvlBwn3dnLFo
|
||||
55x+Tejb6ysQsyy1BKI0dRdX4tNSAgFFFaIVcsOo9kGtPq7QsSd4VWViNE3L5zJA
|
||||
+0X2ztHBkPlQXwDrtArsNKxwcpyHP9sXE05BN36XBjAz2XkusTkFrdJ/PzjZhlb4
|
||||
9i9gTZ0bJbexQ1+dfZX2WpY70JypYnKrbV1dLj5ORb65SC8IWZcG/ouqLWAN+lT+
|
||||
eug8P6PjoOQWs3qsl0bSAtAdiYcwXKtPiBEWPJe24ACywyE+8jVzmIJqAm0U1V8k
|
||||
GTHzjmSRwzgX/VN5JMri/nxNIW5UsbhHzYHfjQIDAQABAoIBAQCIeAWz1Bwl+ULT
|
||||
U7rNkChZyKrAbsUDdBVEPtcQMuR2Bh5Z/KUEoHz1RwiP0WwFFsPI5NO0ZpjD1wdB
|
||||
Jrz9LEoVyzfZvl4f8bTZ1pIzz8PEdBTxFVH3Xy3P7oMC15Q6rviIXgLYl2WJJYcJ
|
||||
adxHDOD+96vnmMhiQbq01aAKT9TA6PvXXDusfadMQ+il+mEbeZz4aNYBk9u+34Co
|
||||
aQTNwlLft5anW2820IMJdJR/bFjyX71cPID1rIjw4VOQZExIpIEnuHPiulyE4EvJ
|
||||
hvvVKAm0dRjHg39cz0eAQ6PntX3DUvjNfcLLrj7sQxLco1cnAKZxhpZ8ajtvynr5
|
||||
pF2d5xYBAoGBAM8y/e5+raHTLHEKZUc0vekUey3fc4aRqptyAKTS0ZvOYBXg4Vhl
|
||||
mOK7066IEqwF4UHGmQqW6D5HstqPGx0uN0d9IyImUqDp0JotdFSZMEMQkYLyFD+r
|
||||
J7O2nOO6E4SOxXO9/q9iSB+G/qgl6LS3O9+58uHTYEbUommiDZ6a18qBAoGBAMZ/
|
||||
xSGMa3b6vrU3rUTEh+xBh6YRVNYAxWwpGg2sO0k2brT3SxSMCrx1wvNGY+k7XNx0
|
||||
JJfZQDC/wlR0rcVTnPCi/cE9FTUlh23xXCPRlxwc4vLly+7yU95LhAO+N9XAwsrs
|
||||
OIi4lR57jxoLNO2ofoAVMvllkE5Eo5W6lOPR2xcNAoGAV1Tv0OFV//pJJhAypfOm
|
||||
BCLc1HX1dIfbOA+yE8bEEH7I4w/ZC3AvI4n1a//wls8Xpai2gs8ebnm7+gENdZww
|
||||
MpKdB1zNwQMsKH/2I146CFpoap/sRvW2EzpqIFYiueGPefxf575uFdPJbEgmMF13
|
||||
ABKZO/PjBZfEKO/j+7DaOYECgYBYX+Zqa1QlIrnpgKJZ7Y3+d6ZnH2w/4xQCdcIt
|
||||
uDKlA+ECHN+GhFr7UQq8uOgenNlZJTRtjsHvclCYvWHoarOCx25mrEVW5iCHqF+3
|
||||
asb2Mz4vmnPTLHx+iex6piPBvRJ8ufLpnBR3/9bUZ4znCo9XgxiwxLEcx551OR60
|
||||
12fNuQKBgC1fkqgtDDxQzrabSmmiqXthcPXxFdsYqnSNlFgba0uaAp9LREztSrX8
|
||||
QhwSoSwHVmjBvR6SybLYdsZ9Efj/w7XBejOOcS44MOoHYYFdsP7W47Ao5QFqvDoI
|
||||
oqyQ1R73cF9WX6obRQwH4P3DvcsBebOjvjMX9mljKtpJMc9KqrGc
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var caCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIDFzCCAf+gAwIBAgIJAJlL10mfdZraMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNV
|
||||
BAMMFndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2EwIBcNMTYwODEyMjAyMjUwWhgPMjI5
|
||||
MDA1MjgyMDIyNTBaMCExHzAdBgNVBAMMFndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2Ew
|
||||
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCgqNo/0+1EAZE0LHzP7QtM
|
||||
a3Z4R26oSVw+ubwS+UHCfd2csWjnnH5N6NvrKxCzLLUEojR1F1fi01ICAUUVohVy
|
||||
w6j2Qa0+rtCxJ3hVZWI0TcvnMkD7RfbO0cGQ+VBfAOu0Cuw0rHBynIc/2xcTTkE3
|
||||
fpcGMDPZeS6xOQWt0n8/ONmGVvj2L2BNnRslt7FDX519lfZaljvQnKlicqttXV0u
|
||||
Pk5FvrlILwhZlwb+i6otYA36VP566Dw/o+Og5BazeqyXRtIC0B2JhzBcq0+IERY8
|
||||
l7bgALLDIT7yNXOYgmoCbRTVXyQZMfOOZJHDOBf9U3kkyuL+fE0hblSxuEfNgd+N
|
||||
AgMBAAGjUDBOMB0GA1UdDgQWBBSx2m5pJoFpdGDmOzSVl29jkheQFTAfBgNVHSME
|
||||
GDAWgBSx2m5pJoFpdGDmOzSVl29jkheQFTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3
|
||||
DQEBCwUAA4IBAQBe6tZzmOQKt8fTsnDDKvEjSwK2Pb91R5tkwmIhdpTjmAgC+Zkk
|
||||
kSihR9sZIxdRC4wlbuorRl8BjhX5I8Kr3FWdDhOrIhicp7CIrxPiFh6+ZLSOj3o9
|
||||
pQ6SriIopjXCHvl5XjzKxLg/uQpzui/YUtfqffCRB4EccOsjlyUanK5rjMLBMLCn
|
||||
2LadiRB2Q/cC9fYigczETACDjq5vzp6I9eqwpCTmv/+4bFncW+VBD4touaJc8FKf
|
||||
ljW5xekKRh4uzP85X7rEgrFen/my5Fs/cylkFvYIiZwgn6NLgW3BNi+m31XIfU0S
|
||||
xIbgh4UH0dwc6Zk8WUwFud4GXj6OyGneMGKB
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
var badCAKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAnKpC89q4/H+Xg91xI+GLhkrpJrO4n3nw0+/EQUoF9qwLtEDk
|
||||
mJp6ymUulwJgfvJwHOsUYqQB6jMKfXyqeSR24ssjjF9LTKhaQMZOGcW5Mshi04Ie
|
||||
USX93wDZwbWwqihVSqWaMpmf3JByeldnXNtc29Ik6NwqZcNWW5kEsSszheLhOU4i
|
||||
ZcRUlovwMYhHX37vQCQ1aygMaIMgBOb/vogSNxumqPKS4WdWsjss6LmEPnm350e+
|
||||
+9cb6RAfrDlOaj32VbLEp500SfBpZeCuyc5v81X12HT4V0qmsqIZ79tIgrqAaPxE
|
||||
D/HJXPpH64EAr23bR9dMPLXTh6w2yYWu+NGYywIDAQABAoIBAQCE/CZPN1gVxf0I
|
||||
i12x9o/oVAhruN08Sld6oCm4viwnws1AmmExhNg8m/0bZIIi4Ir4kThBrzSM5/y8
|
||||
nqlaofBk/cjULEQP80yBdZPwXp2hlOYG4on3mkdRGDjALQmktw4HimFFGJDRuq/i
|
||||
V/U+plrBojWAkPtQXKsen9qSxbg7qhI6KZyUQKExIHhsCfmE1ZzGx+/bgLVJEagi
|
||||
7zzZdAj2BzdoCk8yySAAsZG+pNSnd8gs5EzzRJ1RXanwxPSeEG/guX9YhLgLhhFu
|
||||
XzXngJDKVVhz4F2TfxtqIvZYvTMNh0R1OE0OUO2P88M837KKk5BHvW9oqYKZTUFV
|
||||
MC9k5No5AoGBAMtUBp8UcYZy+yetOAK2iGaEYwuWx8vwjY0c1POWun2Hny0nYxTQ
|
||||
WxXXqKaJydxZ+DlD3XuRKmMlKZQsp+bzuL5ukWN/ipO5tgQQfuKOZqVwvL19GkFi
|
||||
+Qr70G/TvYT/rv6A4s6XqbG4xt+7c2gf/XSghyoIyq1uwOcNNtrMdM/tAoGBAMU/
|
||||
tYc4d+vAl7hd8TwhFiZiC3N84C1HwsPVj38uqQI/j8boB21Bhpw6HHzq+VdVPfvp
|
||||
zk5e8AiQdSpitM7pBVmLpoRdTQjdlUDFRUi4TdJwfp5P7dXM8D6swNQ9f9w180na
|
||||
5ewu16PSC+sh19wAl04KwOmiDqZujJrBgWnFcESXAoGBALGofoybAUK3zqlxWcJN
|
||||
GUtyG1Sx72tLiXMmIQ+hwNsUGEoM4y75isy//ZVeSammVxQ6Lxjb00yD2RumFSLg
|
||||
C6kg1Ro6A6xmFRriCuwL/rZJljB/UeSWBQLK2eoL+clu2sl3djWLIPOvft1YXVM6
|
||||
uGwiI1fgDK+TWSvJSQfOo7ZVAoGBAK+A6DvQeqNBUb2xmJsvtU2hnx661Zx0ZU9q
|
||||
DavUEHz3oS4R9cm4q9UFv6NGT2Tta6FhfzcsMdbs8dMs0EPqAeCS6S6M9aYVwl9H
|
||||
J0Z09olvnrmt1KiPGJQrkcdGkSWWu0nTgxCK/UO9+OzVyALwY7AE0XEPyIk9g82O
|
||||
r181VZcxAoGANY2QGYrNtfa++o2B0O4qskKxhYEeCnZPptmjVO0oHOx2YSDQXK3K
|
||||
B0evCQ7ylvMnobNLjp9bqD14a0M86QjRlpSg1vHUhBsETZICc+E0UgV28CdWgYtt
|
||||
urARDE9ZpLVSRfPVAitC1I76pZwevsbQ9TeS2p0cWQpYYKmBtGpkdug=
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var badCACert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIDFzCCAf+gAwIBAgIJANQEJyMW4HFZMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNV
|
||||
BAMMFndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2EwIBcNMTYwODEyMjAyMjUwWhgPMjI5
|
||||
MDA1MjgyMDIyNTBaMCExHzAdBgNVBAMMFndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2Ew
|
||||
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCcqkLz2rj8f5eD3XEj4YuG
|
||||
Sukms7ifefDT78RBSgX2rAu0QOSYmnrKZS6XAmB+8nAc6xRipAHqMwp9fKp5JHbi
|
||||
yyOMX0tMqFpAxk4ZxbkyyGLTgh5RJf3fANnBtbCqKFVKpZoymZ/ckHJ6V2dc21zb
|
||||
0iTo3Cplw1ZbmQSxKzOF4uE5TiJlxFSWi/AxiEdffu9AJDVrKAxogyAE5v++iBI3
|
||||
G6ao8pLhZ1ayOyzouYQ+ebfnR7771xvpEB+sOU5qPfZVssSnnTRJ8Gll4K7Jzm/z
|
||||
VfXYdPhXSqayohnv20iCuoBo/EQP8clc+kfrgQCvbdtH10w8tdOHrDbJha740ZjL
|
||||
AgMBAAGjUDBOMB0GA1UdDgQWBBRjFVG818hHK+HSEhdz+gPwSKa4kzAfBgNVHSME
|
||||
GDAWgBRjFVG818hHK+HSEhdz+gPwSKa4kzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3
|
||||
DQEBCwUAA4IBAQBBCl0UJq0iLy/dvym79mnoPZ1KPhS2WnQB5ZLzJwL26ePkr8j8
|
||||
G/1AOVPu73hovJx51b+T7ZhTgtmAEwqpRHBxRQ0+Yf973YOVJYp4QFGWDnueurzv
|
||||
bCsnZEPkQtccHzZxT3fUsM6Ejy99j0WBNmvfAj1X7yNaN5EZw6kvuaDDda3I7WNM
|
||||
0eGy8aoAcPJZkYfZb39VDq/qJn+bVsAJdUaXt/FkDZBJl6XzoGjC/webjRJOpkgN
|
||||
vgjJDhhQ8LlHFiq+lXIiK4Y55RBWG3iXGTM8W3fjZYTNvH7FlGyuRD4Y4hyaYXTP
|
||||
+PoFWuDZM89EAyICr0yyTc8mkdrAEM/Lj9GO
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
var serverKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAyhmjG7BJCGwuf1FyHJtq9iUXZ3oymtrOHdaSAcsCSxFrUJTH
|
||||
riPOe9d1ahH7bvsZycnzh7/pABdTDUdStiR8/1KUYt8PjjosrmmYyupqNPq+wkBD
|
||||
EmKa+4voR2EBgXbIGghx8e++KmmNnSCNk6B8m2EJR0fn9zPnoY3uHNogKjCICt19
|
||||
g+uipuwZco7yTu3e40LwpIVmA8SsrM0S/CaZqSmtIClSwv7YDvreUd6FuI/GT0cj
|
||||
NMPRuSdfohBxGz6R7Cml7qP4AYKajjl+08mRYv3o+hVclXUltcRmTnJanYGmGS3k
|
||||
C7KiE2sHINzF2qUUAoO+yXpMx7QtK+NS0PhjmQIDAQABAoIBAFOicmZ1+HM82a0k
|
||||
llWSV5xPUzUmU6TT4bJlZnzJd0R7i+6H8250MPH9AwEHOgb+cPiZ02cdGx5HiL4Z
|
||||
AviPdw7uLKwR5U0VdAIlfu6SPat5DNI0Z81G8x4gEtrfIRFjh4GGdykI7qh8j/cz
|
||||
ToOGSaq/aGiQMEWTvEqWArD7742lVHE4/1bM3GuKV8shy31zfw0d9RCCy1GdBR75
|
||||
zZ1w4zKL55DM3PC73Ndy2IcrViVXVAgfqD0xxKwQW1qoENgThueALj3PkU1XaKxI
|
||||
nOdztt1fBFpcSHyFBkJ1sexumnssMRXSVcJ/0D5F2T4QPUnWBM0oSzoyioAab4RP
|
||||
8XrZwAECgYEA/eFjNgCeHztXgS3YRC/RddLOtobrerYKN7vA64ou5VUCqEQ9rfQE
|
||||
MbmKdZdiFVNJI0JrPq8Gx39ME9g2OLTVVqdtlm6JYjy5CHdUXHIHObo9oz7Uueos
|
||||
TdeCf0LFvEUNXvbGIP5KqcdVi+wekauHMqXGQYTNa6bar/FE99MdyAECgYEAy8mU
|
||||
tCjm4QsuKsdku5bDHGv56ZN9DkWd7Lcjie5otElwH9bKfIQ2lUYyoUAIa0rEJ9Ya
|
||||
7vuAZ2bX7od9s8Jkci91ONDWxdy361SRZcbpuqgQKKVRuzGlfamufyW4sStbXY1k
|
||||
+zeQxyWGJHhhLWpapzca89RELGZSkbIMVVIT25kCgYEA7EUYboZuoYQ5cGf476RM
|
||||
28kfRXEUrvPBWJLr/IhyEk1mFrDDciM40AnrWHpU9qG23BCQ/BopRforFADQnT91
|
||||
l5pje29NfdYjIUTkhtA79zZi7IyprofHSX453TOIECl3QxyH0Oa3F4ACFiDdZhXq
|
||||
0XDDq+/quLfkp37y/2xDOAECgYEAmi55g5UumTWMSHFzlToLhIVtH3unMhUZ1u74
|
||||
xHLMZRrq6ivoJy0g3u+tfrKjrAl1P26OEiHWlGULGj0Ireh1dq7RUZsv46OKw1HI
|
||||
b+h/Den5z8bEf4ygWOL4UtqHUgQrrCw+KpNvxjxtsUoiu+mrjLf0fGYs7iq8bd73
|
||||
1dWzkIECgYEAi6P/LzMC6orbyONmwlscqO1Ili8ZBkUjJ/wThkiNMMA3pyKmb68W
|
||||
yt56Yh0rs+WnuVUN90cG87k+CY35dQ7FAOVUJi9LWGA3Oq9fGkoOB7f4dzaUu/rB
|
||||
dtit2KPCxiKpZsxqSf4+S8AXYF48abNPLYK3DCCSqAah09gYOrqYlW4=
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var serverCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIDCzCCAfOgAwIBAgIJAMvo2rkGpEUQMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNV
|
||||
BAMMFndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2EwIBcNMTYwODEyMjAyMjUwWhgPMjI5
|
||||
MDA1MjgyMDIyNTBaMCUxIzAhBgNVBAMUGndlYmhvb2tfaW1hZ2Vwb2xpY3lfc2Vy
|
||||
dmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyhmjG7BJCGwuf1Fy
|
||||
HJtq9iUXZ3oymtrOHdaSAcsCSxFrUJTHriPOe9d1ahH7bvsZycnzh7/pABdTDUdS
|
||||
tiR8/1KUYt8PjjosrmmYyupqNPq+wkBDEmKa+4voR2EBgXbIGghx8e++KmmNnSCN
|
||||
k6B8m2EJR0fn9zPnoY3uHNogKjCICt19g+uipuwZco7yTu3e40LwpIVmA8SsrM0S
|
||||
/CaZqSmtIClSwv7YDvreUd6FuI/GT0cjNMPRuSdfohBxGz6R7Cml7qP4AYKajjl+
|
||||
08mRYv3o+hVclXUltcRmTnJanYGmGS3kC7KiE2sHINzF2qUUAoO+yXpMx7QtK+NS
|
||||
0PhjmQIDAQABo0AwPjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DATBgNVHSUEDDAK
|
||||
BggrBgEFBQcDATAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQCF
|
||||
xaS/KIijKDLbaL/P7AxhnAta8jYSEzL66WTaYV4GeRhLtX/vPUV9gzPWnkNr0TBM
|
||||
lS+Q0KDxh17rJ/MrWwrMSwsgKZahTR+7mSHiXrIlHcnHXXSvhnoXu8VDu8goqOEI
|
||||
5yRHt6plzmFZEwVi/hSmIAuQjmyjOk2dc/ZKI0fMExKhnVms8AoztjAMbt3TFMTK
|
||||
Kk7bVGPblFsXiVPhRlzbLbh5i/PvHHf+12ACrVxoxOOQUmuXy1DPxmkk7jP3FIsE
|
||||
+rnyWnfmGS5sW8oMkj2nFYIh3LehADsMS9s7JVlJk/loNJDA9Yn2fev/vRKck8RZ
|
||||
siw54G4e+6nKpY5BAY1M
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
var clientKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA3IOqCz88jTQpsGIBFTdjbqBg+0NFeym3OEl8zLfzkLQuZieO
|
||||
3AoFMiLaeYgC4m9BBsdJWSXRzWcqgVWIY8KU7c2SPfErlhP86VFoD0RKHJxwRVh0
|
||||
y70WyK8+CzzwrrPpWydgtAwbm9F+0v/zdcCL0TEL2/MYgCc97mSGwtTRaW4bqq6V
|
||||
MWMHBcOu44dHq8+CF8ixxk0WSBl2oocXnF7QdEA15iuOM5hacLB0fyH4T3NM54lO
|
||||
rOSXUMUuysougSrMcCPv3esFlv4TVUkldwu73jWx+Wja0gNXlnmgU2lqFdM+PsVT
|
||||
DPMzoHTEhIGPIWO5anYR5Qv0SmX3nXkNcx9QDQIDAQABAoIBADblRCC2pmFUmghB
|
||||
7ZkVh9hTbrE+Zv6pPOZzTPE93hGo+WAO+v6GNBLuIEte87DhF2QTmovp4VfsFeXK
|
||||
oECNgTvOEFkBP+OFqFGBJZGfY3/J5h0tTy4lLZXaImzzx8sGGNLLc8R+uyTIO3VV
|
||||
qIso2uXB+vzPgMrueflt5yp7hoJjI0c+qEktUg5n+WJFAFteI9LCngN+xwRWVEgp
|
||||
rjKVPcT9zio8tLJOhcSPA7q6lORUkwbPWHyNDpamvldnqjhgp5Ceq5f/qfoWPzvM
|
||||
H5o72Ax2WduxST+P+hCOqZReUmTaGzAKb5rJwdEpmbnDZ3kSR08aT/40m/EG1SvQ
|
||||
pi0b3QECgYEA/mRGIjaYPQr+tw3Sz8g76t3PYfrglro60HdLBn2IUpj2sEpazNId
|
||||
2aPFPb58whL+VPmUfXbpPH+wW/+wWpRw4MraFkJanbOjDiEGXK5ZoUQIDZJWUSwf
|
||||
oCge5uacU69weC67UyPYmK1e+A/gaFw1Dz729jLxtB3rGWKxEGbWEc0CgYEA3eiP
|
||||
hv0GxbdEEbSfQoSPKbBHGI9spaqAIcqL+dSsx3m6Ckqx0El/xi9mQkITgqs2gyqI
|
||||
o2T/3yDli9oF4+3Plz0wrZ11auOWX+nhKfACtF679I1PL0UOavXF0FVgOfwOIqdG
|
||||
jp4QQV7USkbTP9ZOHo90Y8G4rmTEdMZ/VsH490ECgYEA8u/bsiyk8haf7Tx8SAWW
|
||||
gtLUi2NEO20ZYZ+qvEYBe6+sVeqMD/HQo9ksMazKA6ST0Z6O2cpHLolaaGEjjz0X
|
||||
FvVhk8RGOTglzQZoxvWRjtojPqKzX81dXlsyN5ufSqPOKlemeN1QqW1XtlmjGsaD
|
||||
vU2KFs/L1xCDRbjkEx/B6zkCgYBmqeE9InKvpknnpxjHPWy+bL93rWMmgesltv9r
|
||||
ZelJoBdiC4yYQGjM18EHhmpgWbWumU79yQxXvnB0czmmaa9Q2Q5cRCy+duxrE1kI
|
||||
ffHCYNG0ImwwAlLZSTtrVxRdvy8K+Ti7YoVCuQyeEIZLUmpx2QyP2mAGzrfVDsB6
|
||||
8uKsAQKBgQDO+PmADra91NKJP1iVuvOK8iEy/Z14L03uKtF3X9u8vLdzQZa/Q/P9
|
||||
hXOX9ovFwSBQOOfgb+/+QRuPL4xxi1J8CFwrSWCEeFgrDijl9DS6aNY6BWHDA8p6
|
||||
8V7Adb04cnenj8QjYYN8/mqsQlHSoAIxeAlUoJpq+pk7O8PAfbjgMw==
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var clientCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIC+jCCAeKgAwIBAgIJAMvo2rkGpEURMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNV
|
||||
BAMMFndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2EwIBcNMTYwODEyMjAyMjUwWhgPMjI5
|
||||
MDA1MjgyMDIyNTBaMCUxIzAhBgNVBAMUGndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2xp
|
||||
ZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3IOqCz88jTQpsGIB
|
||||
FTdjbqBg+0NFeym3OEl8zLfzkLQuZieO3AoFMiLaeYgC4m9BBsdJWSXRzWcqgVWI
|
||||
Y8KU7c2SPfErlhP86VFoD0RKHJxwRVh0y70WyK8+CzzwrrPpWydgtAwbm9F+0v/z
|
||||
dcCL0TEL2/MYgCc97mSGwtTRaW4bqq6VMWMHBcOu44dHq8+CF8ixxk0WSBl2oocX
|
||||
nF7QdEA15iuOM5hacLB0fyH4T3NM54lOrOSXUMUuysougSrMcCPv3esFlv4TVUkl
|
||||
dwu73jWx+Wja0gNXlnmgU2lqFdM+PsVTDPMzoHTEhIGPIWO5anYR5Qv0SmX3nXkN
|
||||
cx9QDQIDAQABoy8wLTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DATBgNVHSUEDDAK
|
||||
BggrBgEFBQcDAjANBgkqhkiG9w0BAQsFAAOCAQEAkHIhrPfRROhzLg2hRZz5/7Kw
|
||||
3V0/Y0XS91YU3rew+c2k++bLp1INzpWxfB6gbSC6bTOgn/seIDvxwJ2g5DRdOxU/
|
||||
Elcpqg1hTCVfpmra9PCniMzZuP7lsz8sJKj6FgE6ElJ1S74FW/CYz/jA+76LLot4
|
||||
JwGkCJHzyLgFPBEOjJ/mLYSM/SDzHU5E+NHXVaKz4MjM3JwycN/juqi4ikAcZEBW
|
||||
1HmpcHKBedAwlCM90zlvG2SL4sFRp/clMbntRdmh5L+/1F6aP82PO3iuvXtXP48d
|
||||
NtjboxP3IV2eY5iUle8BOQ9CnFQs4wsF1LxTMNACypQyFinMsHrCpwrB3i4VvA==
|
||||
-----END CERTIFICATE-----`)
|
||||
93
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/config.go
generated
vendored
Normal file
93
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/config.go
generated
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package imagepolicy contains an admission controller that configures a webhook to which policy
|
||||
// decisions are delegated.
|
||||
package imagepolicy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultRetryBackoff = time.Duration(500) * time.Millisecond
|
||||
minRetryBackoff = time.Duration(1)
|
||||
maxRetryBackoff = time.Duration(5) * time.Minute
|
||||
defaultAllowTTL = time.Duration(5) * time.Minute
|
||||
defaultDenyTTL = time.Duration(30) * time.Second
|
||||
minAllowTTL = time.Duration(1) * time.Second
|
||||
maxAllowTTL = time.Duration(30) * time.Minute
|
||||
minDenyTTL = time.Duration(1) * time.Second
|
||||
maxDenyTTL = time.Duration(30) * time.Minute
|
||||
useDefault = time.Duration(0) //sentinel for using default TTL
|
||||
disableTTL = time.Duration(-1) //sentinel for disabling a TTL
|
||||
)
|
||||
|
||||
// imagePolicyWebhookConfig holds config data for imagePolicyWebhook
|
||||
type imagePolicyWebhookConfig struct {
|
||||
KubeConfigFile string `json:"kubeConfigFile"`
|
||||
AllowTTL time.Duration `json:"allowTTL"`
|
||||
DenyTTL time.Duration `json:"denyTTL"`
|
||||
RetryBackoff time.Duration `json:"retryBackoff"`
|
||||
DefaultAllow bool `json:"defaultAllow"`
|
||||
}
|
||||
|
||||
// AdmissionConfig holds config data for admission controllers
|
||||
type AdmissionConfig struct {
|
||||
ImagePolicyWebhook imagePolicyWebhookConfig `json:"imagePolicy"`
|
||||
}
|
||||
|
||||
func normalizeWebhookConfig(config *imagePolicyWebhookConfig) (err error) {
|
||||
config.RetryBackoff, err = normalizeConfigDuration("backoff", time.Millisecond, config.RetryBackoff, minRetryBackoff, maxRetryBackoff, defaultRetryBackoff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.AllowTTL, err = normalizeConfigDuration("allow cache", time.Second, config.AllowTTL, minAllowTTL, maxAllowTTL, defaultAllowTTL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.DenyTTL, err = normalizeConfigDuration("deny cache", time.Second, config.DenyTTL, minDenyTTL, maxDenyTTL, defaultDenyTTL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeConfigDuration(name string, scale, value, min, max, defaultValue time.Duration) (time.Duration, error) {
|
||||
// disable with -1 sentinel
|
||||
if value == disableTTL {
|
||||
glog.V(2).Infof("image policy webhook %s disabled", name)
|
||||
return time.Duration(0), nil
|
||||
}
|
||||
|
||||
// use default with 0 sentinel
|
||||
if value == useDefault {
|
||||
glog.V(2).Infof("image policy webhook %s using default value", name)
|
||||
return defaultValue, nil
|
||||
}
|
||||
|
||||
// convert to s; unmarshalling gives ns
|
||||
value *= scale
|
||||
|
||||
// check value is within range
|
||||
if value < min || value > max {
|
||||
return value, fmt.Errorf("valid value is between %v and %v, got %v", min, max, value)
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
133
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/config_test.go
generated
vendored
Normal file
133
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/config_test.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package imagepolicy
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestConfigNormalization(t *testing.T) {
|
||||
tests := []struct {
|
||||
test string
|
||||
config imagePolicyWebhookConfig
|
||||
normalizedConfig imagePolicyWebhookConfig
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
test: "config within normal ranges",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: ((minAllowTTL + maxAllowTTL) / 2) / time.Second,
|
||||
DenyTTL: ((minDenyTTL + maxDenyTTL) / 2) / time.Second,
|
||||
RetryBackoff: ((minRetryBackoff + maxRetryBackoff) / 2) / time.Millisecond,
|
||||
},
|
||||
normalizedConfig: imagePolicyWebhookConfig{
|
||||
AllowTTL: ((minAllowTTL + maxAllowTTL) / 2) / time.Second * time.Second,
|
||||
DenyTTL: ((minDenyTTL + maxDenyTTL) / 2) / time.Second * time.Second,
|
||||
RetryBackoff: (minRetryBackoff + maxRetryBackoff) / 2,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
test: "config below normal ranges, error",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: minAllowTTL - time.Duration(1),
|
||||
DenyTTL: minDenyTTL - time.Duration(1),
|
||||
RetryBackoff: minRetryBackoff - time.Duration(1),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "config above normal ranges, error",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: time.Duration(1) + maxAllowTTL,
|
||||
DenyTTL: time.Duration(1) + maxDenyTTL,
|
||||
RetryBackoff: time.Duration(1) + maxRetryBackoff,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "config wants default values",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: useDefault,
|
||||
DenyTTL: useDefault,
|
||||
RetryBackoff: useDefault,
|
||||
},
|
||||
normalizedConfig: imagePolicyWebhookConfig{
|
||||
AllowTTL: defaultAllowTTL,
|
||||
DenyTTL: defaultDenyTTL,
|
||||
RetryBackoff: defaultRetryBackoff,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
test: "config wants disabled values",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: disableTTL,
|
||||
DenyTTL: disableTTL,
|
||||
RetryBackoff: disableTTL,
|
||||
},
|
||||
normalizedConfig: imagePolicyWebhookConfig{
|
||||
AllowTTL: time.Duration(0),
|
||||
DenyTTL: time.Duration(0),
|
||||
RetryBackoff: time.Duration(0),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
test: "config within normal ranges for min values",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: minAllowTTL / time.Second,
|
||||
DenyTTL: minDenyTTL / time.Second,
|
||||
RetryBackoff: minRetryBackoff,
|
||||
},
|
||||
normalizedConfig: imagePolicyWebhookConfig{
|
||||
AllowTTL: minAllowTTL,
|
||||
DenyTTL: minDenyTTL,
|
||||
RetryBackoff: minRetryBackoff * time.Millisecond,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
test: "config within normal ranges for max values",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: maxAllowTTL / time.Second,
|
||||
DenyTTL: maxDenyTTL / time.Second,
|
||||
RetryBackoff: maxRetryBackoff / time.Millisecond,
|
||||
},
|
||||
normalizedConfig: imagePolicyWebhookConfig{
|
||||
AllowTTL: maxAllowTTL,
|
||||
DenyTTL: maxDenyTTL,
|
||||
RetryBackoff: maxRetryBackoff,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
err := normalizeWebhookConfig(&tt.config)
|
||||
if err == nil && tt.wantErr == true {
|
||||
t.Errorf("%s: expected error from normalization and didn't have one", tt.test)
|
||||
}
|
||||
if err != nil && tt.wantErr == false {
|
||||
t.Errorf("%s: unexpected error from normalization: %v", tt.test, err)
|
||||
}
|
||||
if err == nil && !reflect.DeepEqual(tt.config, tt.normalizedConfig) {
|
||||
t.Errorf("%s: expected config to be normalized. got: %v expected: %v", tt.test, tt.config, tt.normalizedConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
18
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/doc.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package imagepolicy checks a webhook for image admission
|
||||
package imagepolicy // import "k8s.io/kubernetes/plugin/pkg/admission/imagepolicy"
|
||||
102
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/gencerts.sh
generated
vendored
Executable file
102
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/gencerts.sh
generated
vendored
Executable file
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# 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.
|
||||
|
||||
set -e
|
||||
|
||||
# gencerts.sh generates the certificates for the webhook authz plugin tests.
|
||||
#
|
||||
# It is not expected to be run often (there is no go generate rule), and mainly
|
||||
# exists for documentation purposes.
|
||||
|
||||
cat > server.conf << EOF
|
||||
[req]
|
||||
req_extensions = v3_req
|
||||
distinguished_name = req_distinguished_name
|
||||
[req_distinguished_name]
|
||||
[ v3_req ]
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
extendedKeyUsage = serverAuth
|
||||
subjectAltName = @alt_names
|
||||
[alt_names]
|
||||
IP.1 = 127.0.0.1
|
||||
EOF
|
||||
|
||||
cat > client.conf << EOF
|
||||
[req]
|
||||
req_extensions = v3_req
|
||||
distinguished_name = req_distinguished_name
|
||||
[req_distinguished_name]
|
||||
[ v3_req ]
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
extendedKeyUsage = clientAuth
|
||||
EOF
|
||||
|
||||
# Create a certificate authority
|
||||
openssl genrsa -out caKey.pem 2048
|
||||
openssl req -x509 -new -nodes -key caKey.pem -days 100000 -out caCert.pem -subj "/CN=webhook_imagepolicy_ca"
|
||||
|
||||
# Create a second certificate authority
|
||||
openssl genrsa -out badCAKey.pem 2048
|
||||
openssl req -x509 -new -nodes -key badCAKey.pem -days 100000 -out badCACert.pem -subj "/CN=webhook_imagepolicy_ca"
|
||||
|
||||
# Create a server certiticate
|
||||
openssl genrsa -out serverKey.pem 2048
|
||||
openssl req -new -key serverKey.pem -out server.csr -subj "/CN=webhook_imagepolicy_server" -config server.conf
|
||||
openssl x509 -req -in server.csr -CA caCert.pem -CAkey caKey.pem -CAcreateserial -out serverCert.pem -days 100000 -extensions v3_req -extfile server.conf
|
||||
|
||||
# Create a client certiticate
|
||||
openssl genrsa -out clientKey.pem 2048
|
||||
openssl req -new -key clientKey.pem -out client.csr -subj "/CN=webhook_imagepolicy_client" -config client.conf
|
||||
openssl x509 -req -in client.csr -CA caCert.pem -CAkey caKey.pem -CAcreateserial -out clientCert.pem -days 100000 -extensions v3_req -extfile client.conf
|
||||
|
||||
outfile=certs_test.go
|
||||
|
||||
cat > $outfile << EOF
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
EOF
|
||||
|
||||
echo "// This file was generated using openssl by the gencerts.sh script" >> $outfile
|
||||
echo "// and holds raw certificates for the imagepolicy webhook tests." >> $outfile
|
||||
echo "" >> $outfile
|
||||
echo "package imagepolicy" >> $outfile
|
||||
for file in caKey caCert badCAKey badCACert serverKey serverCert clientKey clientCert; do
|
||||
data=$(cat ${file}.pem)
|
||||
echo "" >> $outfile
|
||||
echo "var $file = []byte(\`$data\`)" >> $outfile
|
||||
done
|
||||
|
||||
# Clean up after we're done.
|
||||
rm *.pem
|
||||
rm *.csr
|
||||
rm *.srl
|
||||
rm *.conf
|
||||
64
vendor/k8s.io/kubernetes/plugin/pkg/admission/limitranger/BUILD
generated
vendored
Normal file
64
vendor/k8s.io/kubernetes/plugin/pkg/admission/limitranger/BUILD
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"admission.go",
|
||||
"interfaces.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/limitranger",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
|
||||
"//pkg/client/listers/core/internalversion:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//vendor/github.com/hashicorp/golang-lru:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
|
||||
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/client-go/testing:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
609
vendor/k8s.io/kubernetes/plugin/pkg/admission/limitranger/admission.go
generated
vendored
Normal file
609
vendor/k8s.io/kubernetes/plugin/pkg/admission/limitranger/admission.go
generated
vendored
Normal file
@@ -0,0 +1,609 @@
|
||||
/*
|
||||
Copyright 2014 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 limitranger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||
corelisters "k8s.io/kubernetes/pkg/client/listers/core/internalversion"
|
||||
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
)
|
||||
|
||||
const (
|
||||
limitRangerAnnotation = "kubernetes.io/limit-ranger"
|
||||
// PluginName indicates name of admission plugin.
|
||||
PluginName = "LimitRanger"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
|
||||
return NewLimitRanger(&DefaultLimitRangerActions{})
|
||||
})
|
||||
}
|
||||
|
||||
// LimitRanger enforces usage limits on a per resource basis in the namespace
|
||||
type LimitRanger struct {
|
||||
*admission.Handler
|
||||
client internalclientset.Interface
|
||||
actions LimitRangerActions
|
||||
lister corelisters.LimitRangeLister
|
||||
|
||||
// liveLookups holds the last few live lookups we've done to help ammortize cost on repeated lookup failures.
|
||||
// This let's us handle the case of latent caches, by looking up actual results for a namespace on cache miss/no results.
|
||||
// We track the lookup result here so that for repeated requests, we don't look it up very often.
|
||||
liveLookupCache *lru.Cache
|
||||
liveTTL time.Duration
|
||||
}
|
||||
|
||||
var _ admission.MutationInterface = &LimitRanger{}
|
||||
var _ admission.ValidationInterface = &LimitRanger{}
|
||||
var _ kubeapiserveradmission.WantsInternalKubeInformerFactory = &LimitRanger{}
|
||||
|
||||
type liveLookupEntry struct {
|
||||
expiry time.Time
|
||||
items []*api.LimitRange
|
||||
}
|
||||
|
||||
func (l *LimitRanger) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) {
|
||||
limitRangeInformer := f.Core().InternalVersion().LimitRanges()
|
||||
l.SetReadyFunc(limitRangeInformer.Informer().HasSynced)
|
||||
l.lister = limitRangeInformer.Lister()
|
||||
}
|
||||
|
||||
func (l *LimitRanger) ValidateInitialization() error {
|
||||
if l.lister == nil {
|
||||
return fmt.Errorf("missing limitRange lister")
|
||||
}
|
||||
if l.client == nil {
|
||||
return fmt.Errorf("missing client")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Admit admits resources into cluster that do not violate any defined LimitRange in the namespace
|
||||
func (l *LimitRanger) Admit(a admission.Attributes) (err error) {
|
||||
return l.runLimitFunc(a, l.actions.MutateLimit)
|
||||
}
|
||||
|
||||
// Validate admits resources into cluster that do not violate any defined LimitRange in the namespace
|
||||
func (l *LimitRanger) Validate(a admission.Attributes) (err error) {
|
||||
return l.runLimitFunc(a, l.actions.ValidateLimit)
|
||||
}
|
||||
|
||||
func (l *LimitRanger) runLimitFunc(a admission.Attributes, limitFn func(limitRange *api.LimitRange, kind string, obj runtime.Object) error) (err error) {
|
||||
if !l.actions.SupportsAttributes(a) {
|
||||
return nil
|
||||
}
|
||||
|
||||
obj := a.GetObject()
|
||||
name := "Unknown"
|
||||
if obj != nil {
|
||||
name, _ = meta.NewAccessor().Name(obj)
|
||||
if len(name) == 0 {
|
||||
name, _ = meta.NewAccessor().GenerateName(obj)
|
||||
}
|
||||
}
|
||||
|
||||
// ignore all objects marked for deletion
|
||||
oldObj := a.GetOldObject()
|
||||
if oldObj != nil {
|
||||
oldAccessor, err := meta.Accessor(oldObj)
|
||||
if err != nil {
|
||||
return admission.NewForbidden(a, err)
|
||||
}
|
||||
if oldAccessor.GetDeletionTimestamp() != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
items, err := l.GetLimitRanges(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ensure it meets each prescribed min/max
|
||||
for i := range items {
|
||||
limitRange := items[i]
|
||||
|
||||
if !l.actions.SupportsLimit(limitRange) {
|
||||
continue
|
||||
}
|
||||
|
||||
err = limitFn(limitRange, a.GetResource().Resource, a.GetObject())
|
||||
if err != nil {
|
||||
return admission.NewForbidden(a, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LimitRanger) GetLimitRanges(a admission.Attributes) ([]*api.LimitRange, error) {
|
||||
items, err := l.lister.LimitRanges(a.GetNamespace()).List(labels.Everything())
|
||||
if err != nil {
|
||||
return nil, admission.NewForbidden(a, fmt.Errorf("unable to %s %v at this time because there was an error enforcing limit ranges", a.GetOperation(), a.GetResource()))
|
||||
}
|
||||
|
||||
// if there are no items held in our indexer, check our live-lookup LRU, if that misses, do the live lookup to prime it.
|
||||
if len(items) == 0 {
|
||||
lruItemObj, ok := l.liveLookupCache.Get(a.GetNamespace())
|
||||
if !ok || lruItemObj.(liveLookupEntry).expiry.Before(time.Now()) {
|
||||
// TODO: If there are multiple operations at the same time and cache has just expired,
|
||||
// this may cause multiple List operations being issued at the same time.
|
||||
// If there is already in-flight List() for a given namespace, we should wait until
|
||||
// it is finished and cache is updated instead of doing the same, also to avoid
|
||||
// throttling - see #22422 for details.
|
||||
liveList, err := l.client.Core().LimitRanges(a.GetNamespace()).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, admission.NewForbidden(a, err)
|
||||
}
|
||||
newEntry := liveLookupEntry{expiry: time.Now().Add(l.liveTTL)}
|
||||
for i := range liveList.Items {
|
||||
newEntry.items = append(newEntry.items, &liveList.Items[i])
|
||||
}
|
||||
l.liveLookupCache.Add(a.GetNamespace(), newEntry)
|
||||
lruItemObj = newEntry
|
||||
}
|
||||
lruEntry := lruItemObj.(liveLookupEntry)
|
||||
|
||||
for i := range lruEntry.items {
|
||||
items = append(items, lruEntry.items[i])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// NewLimitRanger returns an object that enforces limits based on the supplied limit function
|
||||
func NewLimitRanger(actions LimitRangerActions) (*LimitRanger, error) {
|
||||
liveLookupCache, err := lru.New(10000)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if actions == nil {
|
||||
actions = &DefaultLimitRangerActions{}
|
||||
}
|
||||
|
||||
return &LimitRanger{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
actions: actions,
|
||||
liveLookupCache: liveLookupCache,
|
||||
liveTTL: time.Duration(30 * time.Second),
|
||||
}, nil
|
||||
}
|
||||
|
||||
var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&LimitRanger{})
|
||||
var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&LimitRanger{})
|
||||
|
||||
func (a *LimitRanger) SetInternalKubeClientSet(client internalclientset.Interface) {
|
||||
a.client = client
|
||||
}
|
||||
|
||||
// defaultContainerResourceRequirements returns the default requirements for a container
|
||||
// the requirement.Limits are taken from the LimitRange defaults (if specified)
|
||||
// the requirement.Requests are taken from the LimitRange default request (if specified)
|
||||
func defaultContainerResourceRequirements(limitRange *api.LimitRange) api.ResourceRequirements {
|
||||
requirements := api.ResourceRequirements{}
|
||||
requirements.Requests = api.ResourceList{}
|
||||
requirements.Limits = api.ResourceList{}
|
||||
|
||||
for i := range limitRange.Spec.Limits {
|
||||
limit := limitRange.Spec.Limits[i]
|
||||
if limit.Type == api.LimitTypeContainer {
|
||||
for k, v := range limit.DefaultRequest {
|
||||
value := v.Copy()
|
||||
requirements.Requests[k] = *value
|
||||
}
|
||||
for k, v := range limit.Default {
|
||||
value := v.Copy()
|
||||
requirements.Limits[k] = *value
|
||||
}
|
||||
}
|
||||
}
|
||||
return requirements
|
||||
}
|
||||
|
||||
// mergeContainerResources handles defaulting all of the resources on a container.
|
||||
func mergeContainerResources(container *api.Container, defaultRequirements *api.ResourceRequirements, annotationPrefix string, annotations []string) []string {
|
||||
setRequests := []string{}
|
||||
setLimits := []string{}
|
||||
if container.Resources.Limits == nil {
|
||||
container.Resources.Limits = api.ResourceList{}
|
||||
}
|
||||
if container.Resources.Requests == nil {
|
||||
container.Resources.Requests = api.ResourceList{}
|
||||
}
|
||||
for k, v := range defaultRequirements.Limits {
|
||||
_, found := container.Resources.Limits[k]
|
||||
if !found {
|
||||
container.Resources.Limits[k] = *v.Copy()
|
||||
setLimits = append(setLimits, string(k))
|
||||
}
|
||||
}
|
||||
for k, v := range defaultRequirements.Requests {
|
||||
_, found := container.Resources.Requests[k]
|
||||
if !found {
|
||||
container.Resources.Requests[k] = *v.Copy()
|
||||
setRequests = append(setRequests, string(k))
|
||||
}
|
||||
}
|
||||
if len(setRequests) > 0 {
|
||||
sort.Strings(setRequests)
|
||||
a := strings.Join(setRequests, ", ") + fmt.Sprintf(" request for %s %s", annotationPrefix, container.Name)
|
||||
annotations = append(annotations, a)
|
||||
}
|
||||
if len(setLimits) > 0 {
|
||||
sort.Strings(setLimits)
|
||||
a := strings.Join(setLimits, ", ") + fmt.Sprintf(" limit for %s %s", annotationPrefix, container.Name)
|
||||
annotations = append(annotations, a)
|
||||
}
|
||||
return annotations
|
||||
}
|
||||
|
||||
// mergePodResourceRequirements merges enumerated requirements with default requirements
|
||||
// it annotates the pod with information about what requirements were modified
|
||||
func mergePodResourceRequirements(pod *api.Pod, defaultRequirements *api.ResourceRequirements) {
|
||||
annotations := []string{}
|
||||
|
||||
for i := range pod.Spec.Containers {
|
||||
annotations = mergeContainerResources(&pod.Spec.Containers[i], defaultRequirements, "container", annotations)
|
||||
}
|
||||
|
||||
for i := range pod.Spec.InitContainers {
|
||||
annotations = mergeContainerResources(&pod.Spec.InitContainers[i], defaultRequirements, "init container", annotations)
|
||||
}
|
||||
|
||||
if len(annotations) > 0 {
|
||||
if pod.ObjectMeta.Annotations == nil {
|
||||
pod.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
val := "LimitRanger plugin set: " + strings.Join(annotations, "; ")
|
||||
pod.ObjectMeta.Annotations[limitRangerAnnotation] = val
|
||||
}
|
||||
}
|
||||
|
||||
// requestLimitEnforcedValues returns the specified values at a common precision to support comparability
|
||||
func requestLimitEnforcedValues(requestQuantity, limitQuantity, enforcedQuantity resource.Quantity) (request, limit, enforced int64) {
|
||||
request = requestQuantity.Value()
|
||||
limit = limitQuantity.Value()
|
||||
enforced = enforcedQuantity.Value()
|
||||
// do a more precise comparison if possible (if the value won't overflow)
|
||||
if request <= resource.MaxMilliValue && limit <= resource.MaxMilliValue && enforced <= resource.MaxMilliValue {
|
||||
request = requestQuantity.MilliValue()
|
||||
limit = limitQuantity.MilliValue()
|
||||
enforced = enforcedQuantity.MilliValue()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// minConstraint enforces the min constraint over the specified resource
|
||||
func minConstraint(limitType api.LimitType, resourceName api.ResourceName, enforced resource.Quantity, request api.ResourceList, limit api.ResourceList) error {
|
||||
req, reqExists := request[resourceName]
|
||||
lim, limExists := limit[resourceName]
|
||||
observedReqValue, observedLimValue, enforcedValue := requestLimitEnforcedValues(req, lim, enforced)
|
||||
|
||||
if !reqExists {
|
||||
return fmt.Errorf("minimum %s usage per %s is %s. No request is specified.", resourceName, limitType, enforced.String())
|
||||
}
|
||||
if observedReqValue < enforcedValue {
|
||||
return fmt.Errorf("minimum %s usage per %s is %s, but request is %s.", resourceName, limitType, enforced.String(), req.String())
|
||||
}
|
||||
if limExists && (observedLimValue < enforcedValue) {
|
||||
return fmt.Errorf("minimum %s usage per %s is %s, but limit is %s.", resourceName, limitType, enforced.String(), lim.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// maxRequestConstraint enforces the max constraint over the specified resource
|
||||
// use when specify LimitType resource doesn't recognize limit values
|
||||
func maxRequestConstraint(limitType api.LimitType, resourceName api.ResourceName, enforced resource.Quantity, request api.ResourceList) error {
|
||||
req, reqExists := request[resourceName]
|
||||
observedReqValue, _, enforcedValue := requestLimitEnforcedValues(req, resource.Quantity{}, enforced)
|
||||
|
||||
if !reqExists {
|
||||
return fmt.Errorf("maximum %s usage per %s is %s. No request is specified.", resourceName, limitType, enforced.String())
|
||||
}
|
||||
if observedReqValue > enforcedValue {
|
||||
return fmt.Errorf("maximum %s usage per %s is %s, but request is %s.", resourceName, limitType, enforced.String(), req.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// maxConstraint enforces the max constraint over the specified resource
|
||||
func maxConstraint(limitType api.LimitType, resourceName api.ResourceName, enforced resource.Quantity, request api.ResourceList, limit api.ResourceList) error {
|
||||
req, reqExists := request[resourceName]
|
||||
lim, limExists := limit[resourceName]
|
||||
observedReqValue, observedLimValue, enforcedValue := requestLimitEnforcedValues(req, lim, enforced)
|
||||
|
||||
if !limExists {
|
||||
return fmt.Errorf("maximum %s usage per %s is %s. No limit is specified.", resourceName, limitType, enforced.String())
|
||||
}
|
||||
if observedLimValue > enforcedValue {
|
||||
return fmt.Errorf("maximum %s usage per %s is %s, but limit is %s.", resourceName, limitType, enforced.String(), lim.String())
|
||||
}
|
||||
if reqExists && (observedReqValue > enforcedValue) {
|
||||
return fmt.Errorf("maximum %s usage per %s is %s, but request is %s.", resourceName, limitType, enforced.String(), req.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// limitRequestRatioConstraint enforces the limit to request ratio over the specified resource
|
||||
func limitRequestRatioConstraint(limitType api.LimitType, resourceName api.ResourceName, enforced resource.Quantity, request api.ResourceList, limit api.ResourceList) error {
|
||||
req, reqExists := request[resourceName]
|
||||
lim, limExists := limit[resourceName]
|
||||
observedReqValue, observedLimValue, _ := requestLimitEnforcedValues(req, lim, enforced)
|
||||
|
||||
if !reqExists || (observedReqValue == int64(0)) {
|
||||
return fmt.Errorf("%s max limit to request ratio per %s is %s, but no request is specified or request is 0.", resourceName, limitType, enforced.String())
|
||||
}
|
||||
if !limExists || (observedLimValue == int64(0)) {
|
||||
return fmt.Errorf("%s max limit to request ratio per %s is %s, but no limit is specified or limit is 0.", resourceName, limitType, enforced.String())
|
||||
}
|
||||
|
||||
observedRatio := float64(observedLimValue) / float64(observedReqValue)
|
||||
displayObservedRatio := observedRatio
|
||||
maxLimitRequestRatio := float64(enforced.Value())
|
||||
if enforced.Value() <= resource.MaxMilliValue {
|
||||
observedRatio = observedRatio * 1000
|
||||
maxLimitRequestRatio = float64(enforced.MilliValue())
|
||||
}
|
||||
|
||||
if observedRatio > maxLimitRequestRatio {
|
||||
return fmt.Errorf("%s max limit to request ratio per %s is %s, but provided ratio is %f.", resourceName, limitType, enforced.String(), displayObservedRatio)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sum takes the total of each named resource across all inputs
|
||||
// if a key is not in each input, then the output resource list will omit the key
|
||||
func sum(inputs []api.ResourceList) api.ResourceList {
|
||||
result := api.ResourceList{}
|
||||
keys := []api.ResourceName{}
|
||||
for i := range inputs {
|
||||
for k := range inputs[i] {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
for _, key := range keys {
|
||||
total, isSet := int64(0), true
|
||||
|
||||
for i := range inputs {
|
||||
input := inputs[i]
|
||||
v, exists := input[key]
|
||||
if exists {
|
||||
if key == api.ResourceCPU {
|
||||
total = total + v.MilliValue()
|
||||
} else {
|
||||
total = total + v.Value()
|
||||
}
|
||||
} else {
|
||||
isSet = false
|
||||
}
|
||||
}
|
||||
|
||||
if isSet {
|
||||
if key == api.ResourceCPU {
|
||||
result[key] = *(resource.NewMilliQuantity(total, resource.DecimalSI))
|
||||
} else {
|
||||
result[key] = *(resource.NewQuantity(total, resource.DecimalSI))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// DefaultLimitRangerActions is the default implementation of LimitRangerActions.
|
||||
type DefaultLimitRangerActions struct{}
|
||||
|
||||
// ensure DefaultLimitRangerActions implements the LimitRangerActions interface.
|
||||
var _ LimitRangerActions = &DefaultLimitRangerActions{}
|
||||
|
||||
// Limit enforces resource requirements of incoming resources against enumerated constraints
|
||||
// on the LimitRange. It may modify the incoming object to apply default resource requirements
|
||||
// if not specified, and enumerated on the LimitRange
|
||||
func (d *DefaultLimitRangerActions) MutateLimit(limitRange *api.LimitRange, resourceName string, obj runtime.Object) error {
|
||||
switch resourceName {
|
||||
case "pods":
|
||||
return PodMutateLimitFunc(limitRange, obj.(*api.Pod))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Limit enforces resource requirements of incoming resources against enumerated constraints
|
||||
// on the LimitRange. It may modify the incoming object to apply default resource requirements
|
||||
// if not specified, and enumerated on the LimitRange
|
||||
func (d *DefaultLimitRangerActions) ValidateLimit(limitRange *api.LimitRange, resourceName string, obj runtime.Object) error {
|
||||
switch resourceName {
|
||||
case "pods":
|
||||
return PodValidateLimitFunc(limitRange, obj.(*api.Pod))
|
||||
case "persistentvolumeclaims":
|
||||
return PersistentVolumeClaimValidateLimitFunc(limitRange, obj.(*api.PersistentVolumeClaim))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SupportsAttributes ignores all calls that do not deal with pod resources or storage requests (PVCs).
|
||||
// Also ignores any call that has a subresource defined.
|
||||
func (d *DefaultLimitRangerActions) SupportsAttributes(a admission.Attributes) bool {
|
||||
if a.GetSubresource() != "" {
|
||||
return false
|
||||
}
|
||||
|
||||
return a.GetKind().GroupKind() == api.Kind("Pod") || a.GetKind().GroupKind() == api.Kind("PersistentVolumeClaim")
|
||||
}
|
||||
|
||||
// SupportsLimit always returns true.
|
||||
func (d *DefaultLimitRangerActions) SupportsLimit(limitRange *api.LimitRange) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// PersistentVolumeClaimValidateLimitFunc enforces storage limits for PVCs.
|
||||
// Users request storage via pvc.Spec.Resources.Requests. Min/Max is enforced by an admin with LimitRange.
|
||||
// Claims will not be modified with default values because storage is a required part of pvc.Spec.
|
||||
// All storage enforced values *only* apply to pvc.Spec.Resources.Requests.
|
||||
func PersistentVolumeClaimValidateLimitFunc(limitRange *api.LimitRange, pvc *api.PersistentVolumeClaim) error {
|
||||
var errs []error
|
||||
for i := range limitRange.Spec.Limits {
|
||||
limit := limitRange.Spec.Limits[i]
|
||||
limitType := limit.Type
|
||||
if limitType == api.LimitTypePersistentVolumeClaim {
|
||||
for k, v := range limit.Min {
|
||||
// normal usage of minConstraint. pvc.Spec.Resources.Limits is not recognized as user input
|
||||
if err := minConstraint(limitType, k, v, pvc.Spec.Resources.Requests, api.ResourceList{}); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
for k, v := range limit.Max {
|
||||
// We want to enforce the max of the LimitRange against what
|
||||
// the user requested.
|
||||
if err := maxRequestConstraint(limitType, k, v, pvc.Spec.Resources.Requests); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
// PodMutateLimitFunc sets resource requirements enumerated by the pod against
|
||||
// the specified LimitRange. The pod may be modified to apply default resource
|
||||
// requirements if not specified, and enumerated on the LimitRange
|
||||
func PodMutateLimitFunc(limitRange *api.LimitRange, pod *api.Pod) error {
|
||||
defaultResources := defaultContainerResourceRequirements(limitRange)
|
||||
mergePodResourceRequirements(pod, &defaultResources)
|
||||
return nil
|
||||
}
|
||||
|
||||
// PodValidateLimitFunc enforces resource requirements enumerated by the pod against
|
||||
// the specified LimitRange.
|
||||
func PodValidateLimitFunc(limitRange *api.LimitRange, pod *api.Pod) error {
|
||||
var errs []error
|
||||
|
||||
for i := range limitRange.Spec.Limits {
|
||||
limit := limitRange.Spec.Limits[i]
|
||||
limitType := limit.Type
|
||||
// enforce container limits
|
||||
if limitType == api.LimitTypeContainer {
|
||||
for j := range pod.Spec.Containers {
|
||||
container := &pod.Spec.Containers[j]
|
||||
for k, v := range limit.Min {
|
||||
if err := minConstraint(limitType, k, v, container.Resources.Requests, container.Resources.Limits); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
for k, v := range limit.Max {
|
||||
if err := maxConstraint(limitType, k, v, container.Resources.Requests, container.Resources.Limits); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
for k, v := range limit.MaxLimitRequestRatio {
|
||||
if err := limitRequestRatioConstraint(limitType, k, v, container.Resources.Requests, container.Resources.Limits); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
for j := range pod.Spec.InitContainers {
|
||||
container := &pod.Spec.InitContainers[j]
|
||||
for k, v := range limit.Min {
|
||||
if err := minConstraint(limitType, k, v, container.Resources.Requests, container.Resources.Limits); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
for k, v := range limit.Max {
|
||||
if err := maxConstraint(limitType, k, v, container.Resources.Requests, container.Resources.Limits); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
for k, v := range limit.MaxLimitRequestRatio {
|
||||
if err := limitRequestRatioConstraint(limitType, k, v, container.Resources.Requests, container.Resources.Limits); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// enforce pod limits on init containers
|
||||
if limitType == api.LimitTypePod {
|
||||
containerRequests, containerLimits := []api.ResourceList{}, []api.ResourceList{}
|
||||
for j := range pod.Spec.Containers {
|
||||
container := &pod.Spec.Containers[j]
|
||||
containerRequests = append(containerRequests, container.Resources.Requests)
|
||||
containerLimits = append(containerLimits, container.Resources.Limits)
|
||||
}
|
||||
podRequests := sum(containerRequests)
|
||||
podLimits := sum(containerLimits)
|
||||
for j := range pod.Spec.InitContainers {
|
||||
container := &pod.Spec.InitContainers[j]
|
||||
// take max(sum_containers, any_init_container)
|
||||
for k, v := range container.Resources.Requests {
|
||||
if v2, ok := podRequests[k]; ok {
|
||||
if v.Cmp(v2) > 0 {
|
||||
podRequests[k] = v
|
||||
}
|
||||
} else {
|
||||
podRequests[k] = v
|
||||
}
|
||||
}
|
||||
for k, v := range container.Resources.Limits {
|
||||
if v2, ok := podLimits[k]; ok {
|
||||
if v.Cmp(v2) > 0 {
|
||||
podLimits[k] = v
|
||||
}
|
||||
} else {
|
||||
podLimits[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
for k, v := range limit.Min {
|
||||
if err := minConstraint(limitType, k, v, podRequests, podLimits); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
for k, v := range limit.Max {
|
||||
if err := maxConstraint(limitType, k, v, podRequests, podLimits); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
for k, v := range limit.MaxLimitRequestRatio {
|
||||
if err := limitRequestRatioConstraint(limitType, k, v, podRequests, podLimits); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
837
vendor/k8s.io/kubernetes/plugin/pkg/admission/limitranger/admission_test.go
generated
vendored
Normal file
837
vendor/k8s.io/kubernetes/plugin/pkg/admission/limitranger/admission_test.go
generated
vendored
Normal file
@@ -0,0 +1,837 @@
|
||||
/*
|
||||
Copyright 2014 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 limitranger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
core "k8s.io/client-go/testing"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||
kubeadmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
)
|
||||
|
||||
func getComputeResourceList(cpu, memory string) api.ResourceList {
|
||||
res := api.ResourceList{}
|
||||
if cpu != "" {
|
||||
res[api.ResourceCPU] = resource.MustParse(cpu)
|
||||
}
|
||||
if memory != "" {
|
||||
res[api.ResourceMemory] = resource.MustParse(memory)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func getStorageResourceList(storage string) api.ResourceList {
|
||||
res := api.ResourceList{}
|
||||
if storage != "" {
|
||||
res[api.ResourceStorage] = resource.MustParse(storage)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func getResourceRequirements(requests, limits api.ResourceList) api.ResourceRequirements {
|
||||
res := api.ResourceRequirements{}
|
||||
res.Requests = requests
|
||||
res.Limits = limits
|
||||
return res
|
||||
}
|
||||
|
||||
// createLimitRange creates a limit range with the specified data
|
||||
func createLimitRange(limitType api.LimitType, min, max, defaultLimit, defaultRequest, maxLimitRequestRatio api.ResourceList) api.LimitRange {
|
||||
return api.LimitRange{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "abc",
|
||||
Namespace: "test",
|
||||
},
|
||||
Spec: api.LimitRangeSpec{
|
||||
Limits: []api.LimitRangeItem{
|
||||
{
|
||||
Type: limitType,
|
||||
Min: min,
|
||||
Max: max,
|
||||
Default: defaultLimit,
|
||||
DefaultRequest: defaultRequest,
|
||||
MaxLimitRequestRatio: maxLimitRequestRatio,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func validLimitRange() api.LimitRange {
|
||||
return api.LimitRange{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "abc",
|
||||
Namespace: "test",
|
||||
},
|
||||
Spec: api.LimitRangeSpec{
|
||||
Limits: []api.LimitRangeItem{
|
||||
{
|
||||
Type: api.LimitTypePod,
|
||||
Max: getComputeResourceList("200m", "4Gi"),
|
||||
Min: getComputeResourceList("50m", "2Mi"),
|
||||
},
|
||||
{
|
||||
Type: api.LimitTypeContainer,
|
||||
Max: getComputeResourceList("100m", "2Gi"),
|
||||
Min: getComputeResourceList("25m", "1Mi"),
|
||||
Default: getComputeResourceList("75m", "10Mi"),
|
||||
DefaultRequest: getComputeResourceList("50m", "5Mi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func validLimitRangeNoDefaults() api.LimitRange {
|
||||
return api.LimitRange{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "abc",
|
||||
Namespace: "test",
|
||||
},
|
||||
Spec: api.LimitRangeSpec{
|
||||
Limits: []api.LimitRangeItem{
|
||||
{
|
||||
Type: api.LimitTypePod,
|
||||
Max: getComputeResourceList("200m", "4Gi"),
|
||||
Min: getComputeResourceList("50m", "2Mi"),
|
||||
},
|
||||
{
|
||||
Type: api.LimitTypeContainer,
|
||||
Max: getComputeResourceList("100m", "2Gi"),
|
||||
Min: getComputeResourceList("25m", "1Mi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func validPod(name string, numContainers int, resources api.ResourceRequirements) api.Pod {
|
||||
pod := api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "test"},
|
||||
Spec: api.PodSpec{},
|
||||
}
|
||||
pod.Spec.Containers = make([]api.Container, 0, numContainers)
|
||||
for i := 0; i < numContainers; i++ {
|
||||
pod.Spec.Containers = append(pod.Spec.Containers, api.Container{
|
||||
Image: "foo:V" + strconv.Itoa(i),
|
||||
Resources: resources,
|
||||
Name: "foo-" + strconv.Itoa(i),
|
||||
})
|
||||
}
|
||||
return pod
|
||||
}
|
||||
|
||||
func validPodInit(pod api.Pod, resources ...api.ResourceRequirements) api.Pod {
|
||||
for i := 0; i < len(resources); i++ {
|
||||
pod.Spec.InitContainers = append(pod.Spec.InitContainers, api.Container{
|
||||
Image: "foo:V" + strconv.Itoa(i),
|
||||
Resources: resources[i],
|
||||
Name: "foo-" + strconv.Itoa(i),
|
||||
})
|
||||
}
|
||||
return pod
|
||||
}
|
||||
|
||||
func TestDefaultContainerResourceRequirements(t *testing.T) {
|
||||
limitRange := validLimitRange()
|
||||
expected := api.ResourceRequirements{
|
||||
Requests: getComputeResourceList("50m", "5Mi"),
|
||||
Limits: getComputeResourceList("75m", "10Mi"),
|
||||
}
|
||||
|
||||
actual := defaultContainerResourceRequirements(&limitRange)
|
||||
if !apiequality.Semantic.DeepEqual(expected, actual) {
|
||||
t.Errorf("actual.Limits != expected.Limits; %v != %v", actual.Limits, expected.Limits)
|
||||
t.Errorf("actual.Requests != expected.Requests; %v != %v", actual.Requests, expected.Requests)
|
||||
t.Errorf("expected != actual; %v != %v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func verifyAnnotation(t *testing.T, pod *api.Pod, expected string) {
|
||||
a, ok := pod.ObjectMeta.Annotations[limitRangerAnnotation]
|
||||
if !ok {
|
||||
t.Errorf("No annotation but expected %v", expected)
|
||||
}
|
||||
if a != expected {
|
||||
t.Errorf("Wrong annotation set by Limit Ranger: got %v, expected %v", a, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func expectNoAnnotation(t *testing.T, pod *api.Pod) {
|
||||
if a, ok := pod.ObjectMeta.Annotations[limitRangerAnnotation]; ok {
|
||||
t.Errorf("Expected no annotation but got %v", a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergePodResourceRequirements(t *testing.T) {
|
||||
limitRange := validLimitRange()
|
||||
|
||||
// pod with no resources enumerated should get each resource from default request
|
||||
expected := getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", ""))
|
||||
pod := validPod("empty-resources", 1, expected)
|
||||
defaultRequirements := defaultContainerResourceRequirements(&limitRange)
|
||||
mergePodResourceRequirements(&pod, &defaultRequirements)
|
||||
for i := range pod.Spec.Containers {
|
||||
actual := pod.Spec.Containers[i].Resources
|
||||
if !apiequality.Semantic.DeepEqual(expected, actual) {
|
||||
t.Errorf("pod %v, expected != actual; %v != %v", pod.Name, expected, actual)
|
||||
}
|
||||
}
|
||||
verifyAnnotation(t, &pod, "LimitRanger plugin set: cpu, memory request for container foo-0; cpu, memory limit for container foo-0")
|
||||
|
||||
// pod with some resources enumerated should only merge empty
|
||||
input := getResourceRequirements(getComputeResourceList("", "512Mi"), getComputeResourceList("", ""))
|
||||
pod = validPodInit(validPod("limit-memory", 1, input), input)
|
||||
expected = api.ResourceRequirements{
|
||||
Requests: api.ResourceList{
|
||||
api.ResourceCPU: defaultRequirements.Requests[api.ResourceCPU],
|
||||
api.ResourceMemory: resource.MustParse("512Mi"),
|
||||
},
|
||||
Limits: defaultRequirements.Limits,
|
||||
}
|
||||
mergePodResourceRequirements(&pod, &defaultRequirements)
|
||||
for i := range pod.Spec.Containers {
|
||||
actual := pod.Spec.Containers[i].Resources
|
||||
if !apiequality.Semantic.DeepEqual(expected, actual) {
|
||||
t.Errorf("pod %v, expected != actual; %v != %v", pod.Name, expected, actual)
|
||||
}
|
||||
}
|
||||
for i := range pod.Spec.InitContainers {
|
||||
actual := pod.Spec.InitContainers[i].Resources
|
||||
if !apiequality.Semantic.DeepEqual(expected, actual) {
|
||||
t.Errorf("pod %v, expected != actual; %v != %v", pod.Name, expected, actual)
|
||||
}
|
||||
}
|
||||
verifyAnnotation(t, &pod, "LimitRanger plugin set: cpu request for container foo-0; cpu, memory limit for container foo-0")
|
||||
|
||||
// pod with all resources enumerated should not merge anything
|
||||
input = getResourceRequirements(getComputeResourceList("100m", "512Mi"), getComputeResourceList("200m", "1G"))
|
||||
initInputs := []api.ResourceRequirements{getResourceRequirements(getComputeResourceList("200m", "1G"), getComputeResourceList("400m", "2G"))}
|
||||
pod = validPodInit(validPod("limit-memory", 1, input), initInputs...)
|
||||
expected = input
|
||||
mergePodResourceRequirements(&pod, &defaultRequirements)
|
||||
for i := range pod.Spec.Containers {
|
||||
actual := pod.Spec.Containers[i].Resources
|
||||
if !apiequality.Semantic.DeepEqual(expected, actual) {
|
||||
t.Errorf("pod %v, expected != actual; %v != %v", pod.Name, expected, actual)
|
||||
}
|
||||
}
|
||||
for i := range pod.Spec.InitContainers {
|
||||
actual := pod.Spec.InitContainers[i].Resources
|
||||
if !apiequality.Semantic.DeepEqual(initInputs[i], actual) {
|
||||
t.Errorf("pod %v, expected != actual; %v != %v", pod.Name, initInputs[i], actual)
|
||||
}
|
||||
}
|
||||
expectNoAnnotation(t, &pod)
|
||||
}
|
||||
|
||||
func TestPodLimitFunc(t *testing.T) {
|
||||
type testCase struct {
|
||||
pod api.Pod
|
||||
limitRange api.LimitRange
|
||||
}
|
||||
|
||||
successCases := []testCase{
|
||||
{
|
||||
pod: validPod("ctr-min-cpu-request", 1, getResourceRequirements(getComputeResourceList("100m", ""), getComputeResourceList("", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("50m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-min-cpu-request-limit", 1, getResourceRequirements(getComputeResourceList("100m", ""), getComputeResourceList("200m", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("50m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-min-memory-request", 1, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("", "50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-min-memory-request-limit", 1, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", "100Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("", "50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-max-cpu-request-limit", 1, getResourceRequirements(getComputeResourceList("500m", ""), getComputeResourceList("1", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-max-cpu-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("1", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-max-mem-request-limit", 1, getResourceRequirements(getComputeResourceList("", "250Mi"), getComputeResourceList("", "500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-max-cpu-ratio", 1, getResourceRequirements(getComputeResourceList("500m", ""), getComputeResourceList("750m", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, getComputeResourceList("1.5", "")),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-max-mem-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", "500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-min-cpu-request", 2, getResourceRequirements(getComputeResourceList("75m", ""), getComputeResourceList("", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("100m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-min-cpu-request-limit", 2, getResourceRequirements(getComputeResourceList("75m", ""), getComputeResourceList("200m", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("100m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-min-memory-request", 2, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-min-memory-request-limit", 2, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", "100Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPodInit(
|
||||
validPod("pod-init-min-memory-request", 2, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", ""))),
|
||||
getResourceRequirements(getComputeResourceList("", "100Mi"), getComputeResourceList("", "")),
|
||||
),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPodInit(
|
||||
validPod("pod-init-min-memory-request-limit", 2, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", "100Mi"))),
|
||||
getResourceRequirements(getComputeResourceList("", "80Mi"), getComputeResourceList("", "100Mi")),
|
||||
),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-cpu-request-limit", 2, getResourceRequirements(getComputeResourceList("500m", ""), getComputeResourceList("1", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-cpu-limit", 2, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("1", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPodInit(
|
||||
validPod("pod-init-max-cpu-request-limit", 2, getResourceRequirements(getComputeResourceList("500m", ""), getComputeResourceList("1", ""))),
|
||||
getResourceRequirements(getComputeResourceList("1", ""), getComputeResourceList("2", "")),
|
||||
getResourceRequirements(getComputeResourceList("1", ""), getComputeResourceList("1", "")),
|
||||
),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPodInit(
|
||||
validPod("pod-init-max-cpu-limit", 2, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("1", ""))),
|
||||
getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("2", "")),
|
||||
getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("2", "")),
|
||||
),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-mem-request-limit", 2, getResourceRequirements(getComputeResourceList("", "250Mi"), getComputeResourceList("", "500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-mem-limit", 2, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", "500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-mem-ratio", 3, getResourceRequirements(getComputeResourceList("", "300Mi"), getComputeResourceList("", "450Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "2Gi"), api.ResourceList{}, api.ResourceList{}, getComputeResourceList("", "1.5")),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-1-min-local-ephemeral-storage-request", 1, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-1-min-local-ephemeral-storage-request-limit", 1, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList("100Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-1-max-local-ephemeral-storage-request-limit", 1, getResourceRequirements(getLocalStorageResourceList("250Mi"), getLocalStorageResourceList("500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-1-max-local-ephemeral-storage-limit", 1, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList("500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-2-min-local-ephemeral-storage-request", 2, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-2-min-local-ephemeral-storage-request-limit", 2, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList("100Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-2-max-local-ephemeral-storage-request-limit", 2, getResourceRequirements(getLocalStorageResourceList("250Mi"), getLocalStorageResourceList("500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("600Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-2-max-local-ephemeral-storage-limit", 2, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList("500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("600Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-min-local-ephemeral-storage-request", 2, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getLocalStorageResourceList("100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-min-local-ephemeral-storage-request-limit", 2, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList("100Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getLocalStorageResourceList("100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPodInit(
|
||||
validPod("pod-init-min-local-ephemeral-storage-request", 2, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList(""))),
|
||||
getResourceRequirements(getLocalStorageResourceList("100Mi"), getLocalStorageResourceList("")),
|
||||
),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getLocalStorageResourceList("100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPodInit(
|
||||
validPod("pod-init-min-local-ephemeral-storage-request-limit", 2, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList("100Mi"))),
|
||||
getResourceRequirements(getLocalStorageResourceList("80Mi"), getLocalStorageResourceList("100Mi")),
|
||||
),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getLocalStorageResourceList("100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-local-ephemeral-storage-request-limit", 2, getResourceRequirements(getLocalStorageResourceList("250Mi"), getLocalStorageResourceList("500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-local-ephemeral-storage-limit", 2, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList("500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-local-ephemeral-storage-ratio", 3, getResourceRequirements(getLocalStorageResourceList("300Mi"), getLocalStorageResourceList("450Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getLocalStorageResourceList("2Gi"), api.ResourceList{}, api.ResourceList{}, getLocalStorageResourceList("1.5")),
|
||||
},
|
||||
}
|
||||
for i := range successCases {
|
||||
test := successCases[i]
|
||||
err := PodMutateLimitFunc(&test.limitRange, &test.pod)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error for pod: %s, %v", test.pod.Name, err)
|
||||
}
|
||||
err = PodValidateLimitFunc(&test.limitRange, &test.pod)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error for pod: %s, %v", test.pod.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
errorCases := []testCase{
|
||||
{
|
||||
pod: validPod("ctr-min-cpu-request", 1, getResourceRequirements(getComputeResourceList("40m", ""), getComputeResourceList("", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("50m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-min-cpu-request-limit", 1, getResourceRequirements(getComputeResourceList("40m", ""), getComputeResourceList("200m", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("50m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-min-cpu-no-request-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("50m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-min-memory-request", 1, getResourceRequirements(getComputeResourceList("", "40Mi"), getComputeResourceList("", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("", "50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-min-memory-request-limit", 1, getResourceRequirements(getComputeResourceList("", "40Mi"), getComputeResourceList("", "100Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("", "50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-min-memory-no-request-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("", "50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-max-cpu-request-limit", 1, getResourceRequirements(getComputeResourceList("500m", ""), getComputeResourceList("2500m", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-max-cpu-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("2500m", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-max-cpu-no-request-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-max-cpu-ratio", 1, getResourceRequirements(getComputeResourceList("1250m", ""), getComputeResourceList("2500m", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, getComputeResourceList("1", "")),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-max-mem-request-limit", 1, getResourceRequirements(getComputeResourceList("", "250Mi"), getComputeResourceList("", "2Gi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-max-mem-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", "2Gi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-max-mem-no-request-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-min-cpu-request", 1, getResourceRequirements(getComputeResourceList("75m", ""), getComputeResourceList("", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("100m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-min-cpu-request-limit", 1, getResourceRequirements(getComputeResourceList("75m", ""), getComputeResourceList("200m", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("100m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-min-memory-request", 1, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-min-memory-request-limit", 1, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", "100Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-cpu-request-limit", 3, getResourceRequirements(getComputeResourceList("500m", ""), getComputeResourceList("1", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-cpu-limit", 3, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("1", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-mem-request-limit", 3, getResourceRequirements(getComputeResourceList("", "250Mi"), getComputeResourceList("", "500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-mem-limit", 3, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", "500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPodInit(
|
||||
validPod("pod-init-max-mem-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", "500Mi"))),
|
||||
getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", "1.5Gi")),
|
||||
),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-mem-ratio", 3, getResourceRequirements(getComputeResourceList("", "250Mi"), getComputeResourceList("", "500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "2Gi"), api.ResourceList{}, api.ResourceList{}, getComputeResourceList("", "1.5")),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-1-min-local-ephemeral-storage-request", 1, getResourceRequirements(getLocalStorageResourceList("40Mi"), getLocalStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-1-min-local-ephemeral-storage-request-limit", 1, getResourceRequirements(getLocalStorageResourceList("40Mi"), getLocalStorageResourceList("100Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-1-min-local-ephemeral-storage-no-request-limit", 1, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-1-max-local-ephemeral-storage-request-limit", 1, getResourceRequirements(getLocalStorageResourceList("250Mi"), getLocalStorageResourceList("2Gi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-1-max-local-ephemeral-storage-limit", 1, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList("2Gi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-1-max-local-ephemeral-storage-no-request-limit", 1, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-2-min-local-ephemeral-storage-request", 2, getResourceRequirements(getLocalStorageResourceList("40Mi"), getLocalStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-2-min-local-ephemeral-storage-request-limit", 2, getResourceRequirements(getLocalStorageResourceList("40Mi"), getLocalStorageResourceList("100Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-2-min-local-ephemeral-storage-no-request-limit", 2, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-2-max-local-ephemeral-storage-request-limit", 2, getResourceRequirements(getLocalStorageResourceList("250Mi"), getLocalStorageResourceList("2Gi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-2-max-local-ephemeral-storage-limit", 2, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList("2Gi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-2-max-local-ephemeral-storage-no-request-limit", 2, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-min-local-ephemeral-storage-request", 1, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getLocalStorageResourceList("100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-min-local-ephemeral-storage-request-limit", 1, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList("100Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getLocalStorageResourceList("100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-local-ephemeral-storage-request-limit", 3, getResourceRequirements(getLocalStorageResourceList("250Mi"), getLocalStorageResourceList("500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-local-ephemeral-storage-limit", 3, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList("500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPodInit(
|
||||
validPod("pod-init-max-local-ephemeral-storage-limit", 1, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList("500Mi"))),
|
||||
getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList("1.5Gi")),
|
||||
),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-local-ephemeral-storage-ratio", 3, getResourceRequirements(getLocalStorageResourceList("250Mi"), getLocalStorageResourceList("500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getLocalStorageResourceList("2Gi"), api.ResourceList{}, api.ResourceList{}, getLocalStorageResourceList("1.5")),
|
||||
},
|
||||
}
|
||||
for i := range errorCases {
|
||||
test := errorCases[i]
|
||||
err := PodMutateLimitFunc(&test.limitRange, &test.pod)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error for pod: %s, %v", test.pod.Name, err)
|
||||
}
|
||||
err = PodValidateLimitFunc(&test.limitRange, &test.pod)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error for pod: %s", test.pod.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getLocalStorageResourceList(ephemeralStorage string) api.ResourceList {
|
||||
res := api.ResourceList{}
|
||||
if ephemeralStorage != "" {
|
||||
res[api.ResourceEphemeralStorage] = resource.MustParse(ephemeralStorage)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func TestPodLimitFuncApplyDefault(t *testing.T) {
|
||||
limitRange := validLimitRange()
|
||||
testPod := validPodInit(validPod("foo", 1, getResourceRequirements(api.ResourceList{}, api.ResourceList{})), getResourceRequirements(api.ResourceList{}, api.ResourceList{}))
|
||||
err := PodMutateLimitFunc(&limitRange, &testPod)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error for valid pod: %s, %v", testPod.Name, err)
|
||||
}
|
||||
|
||||
for i := range testPod.Spec.Containers {
|
||||
container := testPod.Spec.Containers[i]
|
||||
limitMemory := container.Resources.Limits.Memory().String()
|
||||
limitCpu := container.Resources.Limits.Cpu().String()
|
||||
requestMemory := container.Resources.Requests.Memory().String()
|
||||
requestCpu := container.Resources.Requests.Cpu().String()
|
||||
|
||||
if limitMemory != "10Mi" {
|
||||
t.Errorf("Unexpected limit memory value %s", limitMemory)
|
||||
}
|
||||
if limitCpu != "75m" {
|
||||
t.Errorf("Unexpected limit cpu value %s", limitCpu)
|
||||
}
|
||||
if requestMemory != "5Mi" {
|
||||
t.Errorf("Unexpected request memory value %s", requestMemory)
|
||||
}
|
||||
if requestCpu != "50m" {
|
||||
t.Errorf("Unexpected request cpu value %s", requestCpu)
|
||||
}
|
||||
}
|
||||
|
||||
for i := range testPod.Spec.InitContainers {
|
||||
container := testPod.Spec.InitContainers[i]
|
||||
limitMemory := container.Resources.Limits.Memory().String()
|
||||
limitCpu := container.Resources.Limits.Cpu().String()
|
||||
requestMemory := container.Resources.Requests.Memory().String()
|
||||
requestCpu := container.Resources.Requests.Cpu().String()
|
||||
|
||||
if limitMemory != "10Mi" {
|
||||
t.Errorf("Unexpected limit memory value %s", limitMemory)
|
||||
}
|
||||
if limitCpu != "75m" {
|
||||
t.Errorf("Unexpected limit cpu value %s", limitCpu)
|
||||
}
|
||||
if requestMemory != "5Mi" {
|
||||
t.Errorf("Unexpected request memory value %s", requestMemory)
|
||||
}
|
||||
if requestCpu != "50m" {
|
||||
t.Errorf("Unexpected request cpu value %s", requestCpu)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLimitRangerIgnoresSubresource(t *testing.T) {
|
||||
limitRange := validLimitRangeNoDefaults()
|
||||
mockClient := newMockClientForTest([]api.LimitRange{limitRange})
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
testPod := validPod("testPod", 1, api.ResourceRequirements{})
|
||||
err = handler.Admit(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = handler.Validate(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error since the pod did not specify resource limits in its update call")
|
||||
}
|
||||
|
||||
err = handler.Validate(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "status", admission.Update, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Should have ignored calls to any subresource of pod %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestLimitRangerAdmitPod(t *testing.T) {
|
||||
limitRange := validLimitRangeNoDefaults()
|
||||
mockClient := newMockClientForTest([]api.LimitRange{limitRange})
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
testPod := validPod("testPod", 1, api.ResourceRequirements{})
|
||||
err = handler.Admit(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = handler.Validate(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error since the pod did not specify resource limits in its update call")
|
||||
}
|
||||
|
||||
err = handler.Validate(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "status", admission.Update, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Should have ignored calls to any subresource of pod %v", err)
|
||||
}
|
||||
|
||||
// a pod that is undergoing termination should never be blocked
|
||||
terminatingPod := validPod("terminatingPod", 1, api.ResourceRequirements{})
|
||||
now := metav1.Now()
|
||||
terminatingPod.DeletionTimestamp = &now
|
||||
err = handler.Validate(admission.NewAttributesRecord(&terminatingPod, &terminatingPod, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "terminatingPod", api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
|
||||
if err != nil {
|
||||
t.Errorf("LimitRange should ignore a pod marked for termination")
|
||||
}
|
||||
}
|
||||
|
||||
// newMockClientForTest creates a mock client that returns a client configured for the specified list of limit ranges
|
||||
func newMockClientForTest(limitRanges []api.LimitRange) *fake.Clientset {
|
||||
mockClient := &fake.Clientset{}
|
||||
mockClient.AddReactor("list", "limitranges", func(action core.Action) (bool, runtime.Object, error) {
|
||||
limitRangeList := &api.LimitRangeList{
|
||||
ListMeta: metav1.ListMeta{
|
||||
ResourceVersion: fmt.Sprintf("%d", len(limitRanges)),
|
||||
},
|
||||
}
|
||||
for index, value := range limitRanges {
|
||||
value.ResourceVersion = fmt.Sprintf("%d", index)
|
||||
limitRangeList.Items = append(limitRangeList.Items, value)
|
||||
}
|
||||
return true, limitRangeList, nil
|
||||
})
|
||||
return mockClient
|
||||
}
|
||||
|
||||
// newHandlerForTest returns a handler configured for testing.
|
||||
func newHandlerForTest(c clientset.Interface) (*LimitRanger, informers.SharedInformerFactory, error) {
|
||||
f := informers.NewSharedInformerFactory(c, 5*time.Minute)
|
||||
handler, err := NewLimitRanger(&DefaultLimitRangerActions{})
|
||||
if err != nil {
|
||||
return nil, f, err
|
||||
}
|
||||
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil)
|
||||
pluginInitializer.Initialize(handler)
|
||||
err = admission.ValidateInitialization(handler)
|
||||
return handler, f, err
|
||||
}
|
||||
|
||||
func validPersistentVolumeClaim(name string, resources api.ResourceRequirements) api.PersistentVolumeClaim {
|
||||
pvc := api.PersistentVolumeClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "test"},
|
||||
Spec: api.PersistentVolumeClaimSpec{
|
||||
Resources: resources,
|
||||
},
|
||||
}
|
||||
return pvc
|
||||
}
|
||||
|
||||
func TestPersistentVolumeClaimLimitFunc(t *testing.T) {
|
||||
type testCase struct {
|
||||
pvc api.PersistentVolumeClaim
|
||||
limitRange api.LimitRange
|
||||
}
|
||||
|
||||
successCases := []testCase{
|
||||
{
|
||||
pvc: validPersistentVolumeClaim("pvc-is-min-storage-request", getResourceRequirements(getStorageResourceList("1Gi"), getStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypePersistentVolumeClaim, getStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pvc: validPersistentVolumeClaim("pvc-is-max-storage-request", getResourceRequirements(getStorageResourceList("1Gi"), getStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypePersistentVolumeClaim, api.ResourceList{}, getStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pvc: validPersistentVolumeClaim("pvc-no-minmax-storage-request", getResourceRequirements(getStorageResourceList("100Gi"), getStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypePersistentVolumeClaim, getStorageResourceList(""), getStorageResourceList(""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pvc: validPersistentVolumeClaim("pvc-within-minmax-storage-request", getResourceRequirements(getStorageResourceList("5Gi"), getStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypePersistentVolumeClaim, getStorageResourceList("1Gi"), getStorageResourceList("10Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
}
|
||||
for i := range successCases {
|
||||
test := successCases[i]
|
||||
err := PersistentVolumeClaimValidateLimitFunc(&test.limitRange, &test.pvc)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error for pvc: %s, %v", test.pvc.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
errorCases := []testCase{
|
||||
{
|
||||
pvc: validPersistentVolumeClaim("pvc-below-min-storage-request", getResourceRequirements(getStorageResourceList("500Mi"), getStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypePersistentVolumeClaim, getStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pvc: validPersistentVolumeClaim("pvc-exceeds-max-storage-request", getResourceRequirements(getStorageResourceList("100Gi"), getStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypePersistentVolumeClaim, getStorageResourceList("1Gi"), getStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
}
|
||||
for i := range errorCases {
|
||||
test := errorCases[i]
|
||||
err := PersistentVolumeClaimValidateLimitFunc(&test.limitRange, &test.pvc)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error for pvc: %s", test.pvc.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
36
vendor/k8s.io/kubernetes/plugin/pkg/admission/limitranger/interfaces.go
generated
vendored
Normal file
36
vendor/k8s.io/kubernetes/plugin/pkg/admission/limitranger/interfaces.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright 2014 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 limitranger
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
)
|
||||
|
||||
type LimitRangerActions interface {
|
||||
// MutateLimit is a pluggable function to set limits on the object.
|
||||
MutateLimit(limitRange *api.LimitRange, kind string, obj runtime.Object) error
|
||||
// ValidateLimits is a pluggable function to enforce limits on the object.
|
||||
ValidateLimit(limitRange *api.LimitRange, kind string, obj runtime.Object) error
|
||||
// SupportsAttributes is a pluggable function to allow overridding what resources the limitranger
|
||||
// supports.
|
||||
SupportsAttributes(attr admission.Attributes) bool
|
||||
// SupportsLimit is a pluggable function to allow ignoring limits that should not be applied
|
||||
// for any reason.
|
||||
SupportsLimit(limitRange *api.LimitRange) bool
|
||||
}
|
||||
55
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision/BUILD
generated
vendored
Normal file
55
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision/BUILD
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["admission.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
|
||||
"//pkg/client/listers/core/internalversion:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
|
||||
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/client-go/testing:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
122
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision/admission.go
generated
vendored
Normal file
122
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision/admission.go
generated
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
Copyright 2014 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 autoprovision
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||
corelisters "k8s.io/kubernetes/pkg/client/listers/core/internalversion"
|
||||
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
)
|
||||
|
||||
// PluginName indicates name of admission plugin.
|
||||
const PluginName = "NamespaceAutoProvision"
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
|
||||
return NewProvision(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// Provision is an implementation of admission.Interface.
|
||||
// It looks at all incoming requests in a namespace context, and if the namespace does not exist, it creates one.
|
||||
// It is useful in deployments that do not want to restrict creation of a namespace prior to its usage.
|
||||
type Provision struct {
|
||||
*admission.Handler
|
||||
client internalclientset.Interface
|
||||
namespaceLister corelisters.NamespaceLister
|
||||
}
|
||||
|
||||
var _ admission.MutationInterface = &Provision{}
|
||||
var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&Provision{})
|
||||
var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&Provision{})
|
||||
|
||||
// Admit makes an admission decision based on the request attributes
|
||||
func (p *Provision) Admit(a admission.Attributes) error {
|
||||
// if we're here, then we've already passed authentication, so we're allowed to do what we're trying to do
|
||||
// if we're here, then the API server has found a route, which means that if we have a non-empty namespace
|
||||
// its a namespaced resource.
|
||||
if len(a.GetNamespace()) == 0 || a.GetKind().GroupKind() == api.Kind("Namespace") {
|
||||
return nil
|
||||
}
|
||||
// we need to wait for our caches to warm
|
||||
if !p.WaitForReady() {
|
||||
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
|
||||
}
|
||||
|
||||
_, err := p.namespaceLister.Get(a.GetNamespace())
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !errors.IsNotFound(err) {
|
||||
return admission.NewForbidden(a, err)
|
||||
}
|
||||
|
||||
namespace := &api.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: a.GetNamespace(),
|
||||
Namespace: "",
|
||||
},
|
||||
Status: api.NamespaceStatus{},
|
||||
}
|
||||
|
||||
_, err = p.client.Core().Namespaces().Create(namespace)
|
||||
if err != nil && !errors.IsAlreadyExists(err) {
|
||||
return admission.NewForbidden(a, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewProvision creates a new namespace provision admission control handler
|
||||
func NewProvision() *Provision {
|
||||
return &Provision{
|
||||
Handler: admission.NewHandler(admission.Create),
|
||||
}
|
||||
}
|
||||
|
||||
// SetInternalKubeClientSet implements the WantsInternalKubeClientSet interface.
|
||||
func (p *Provision) SetInternalKubeClientSet(client internalclientset.Interface) {
|
||||
p.client = client
|
||||
}
|
||||
|
||||
// SetInternalKubeInformerFactory implements the WantsInternalKubeInformerFactory interface.
|
||||
func (p *Provision) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) {
|
||||
namespaceInformer := f.Core().InternalVersion().Namespaces()
|
||||
p.namespaceLister = namespaceInformer.Lister()
|
||||
p.SetReadyFunc(namespaceInformer.Informer().HasSynced)
|
||||
}
|
||||
|
||||
// ValidateInitialization implements the InitializationValidator interface.
|
||||
func (p *Provision) ValidateInitialization() error {
|
||||
if p.namespaceLister == nil {
|
||||
return fmt.Errorf("missing namespaceLister")
|
||||
}
|
||||
if p.client == nil {
|
||||
return fmt.Errorf("missing client")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
172
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision/admission_test.go
generated
vendored
Normal file
172
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision/admission_test.go
generated
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
Copyright 2014 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 autoprovision
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
core "k8s.io/client-go/testing"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||
kubeadmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
)
|
||||
|
||||
// newHandlerForTest returns the admission controller configured for testing.
|
||||
func newHandlerForTest(c clientset.Interface) (admission.MutationInterface, informers.SharedInformerFactory, error) {
|
||||
f := informers.NewSharedInformerFactory(c, 5*time.Minute)
|
||||
handler := NewProvision()
|
||||
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil)
|
||||
pluginInitializer.Initialize(handler)
|
||||
err := admission.ValidateInitialization(handler)
|
||||
return handler, f, err
|
||||
}
|
||||
|
||||
// newMockClientForTest creates a mock client that returns a client configured for the specified list of namespaces.
|
||||
func newMockClientForTest(namespaces []string) *fake.Clientset {
|
||||
mockClient := &fake.Clientset{}
|
||||
mockClient.AddReactor("list", "namespaces", func(action core.Action) (bool, runtime.Object, error) {
|
||||
namespaceList := &api.NamespaceList{
|
||||
ListMeta: metav1.ListMeta{
|
||||
ResourceVersion: fmt.Sprintf("%d", len(namespaces)),
|
||||
},
|
||||
}
|
||||
for i, ns := range namespaces {
|
||||
namespaceList.Items = append(namespaceList.Items, api.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: ns,
|
||||
ResourceVersion: fmt.Sprintf("%d", i),
|
||||
},
|
||||
})
|
||||
}
|
||||
return true, namespaceList, nil
|
||||
})
|
||||
return mockClient
|
||||
}
|
||||
|
||||
// newPod returns a new pod for the specified namespace
|
||||
func newPod(namespace string) api.Pod {
|
||||
return api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: namespace},
|
||||
Spec: api.PodSpec{
|
||||
Volumes: []api.Volume{{Name: "vol"}},
|
||||
Containers: []api.Container{{Name: "ctr", Image: "image"}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// hasCreateNamespaceAction returns true if it has the create namespace action
|
||||
func hasCreateNamespaceAction(mockClient *fake.Clientset) bool {
|
||||
for _, action := range mockClient.Actions() {
|
||||
if action.GetVerb() == "create" && action.GetResource().Resource == "namespaces" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TestAdmission verifies a namespace is created on create requests for namespace managed resources
|
||||
func TestAdmission(t *testing.T) {
|
||||
namespace := "test"
|
||||
mockClient := newMockClientForTest([]string{})
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
pod := newPod(namespace)
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error returned from admission handler")
|
||||
}
|
||||
if !hasCreateNamespaceAction(mockClient) {
|
||||
t.Errorf("expected create namespace action")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdmissionNamespaceExists verifies that no client call is made when a namespace already exists
|
||||
func TestAdmissionNamespaceExists(t *testing.T) {
|
||||
namespace := "test"
|
||||
mockClient := newMockClientForTest([]string{namespace})
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
pod := newPod(namespace)
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error returned from admission handler")
|
||||
}
|
||||
if hasCreateNamespaceAction(mockClient) {
|
||||
t.Errorf("unexpected create namespace action")
|
||||
}
|
||||
}
|
||||
|
||||
// TestIgnoreAdmission validates that a request is ignored if its not a create
|
||||
func TestIgnoreAdmission(t *testing.T) {
|
||||
namespace := "test"
|
||||
mockClient := newMockClientForTest([]string{})
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
chainHandler := admission.NewChainHandler(handler)
|
||||
|
||||
pod := newPod(namespace)
|
||||
err = chainHandler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error returned from admission handler")
|
||||
}
|
||||
if hasCreateNamespaceAction(mockClient) {
|
||||
t.Errorf("unexpected create namespace action")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdmissionWithLatentCache(t *testing.T) {
|
||||
namespace := "test"
|
||||
mockClient := newMockClientForTest([]string{})
|
||||
mockClient.AddReactor("create", "namespaces", func(action core.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, errors.NewAlreadyExists(api.Resource("namespaces"), namespace)
|
||||
})
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
pod := newPod(namespace)
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error returned from admission handler")
|
||||
}
|
||||
|
||||
if !hasCreateNamespaceAction(mockClient) {
|
||||
t.Errorf("expected create namespace action")
|
||||
}
|
||||
}
|
||||
54
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/exists/BUILD
generated
vendored
Normal file
54
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/exists/BUILD
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["admission.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/namespace/exists",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
|
||||
"//pkg/client/listers/core/internalversion:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
|
||||
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/client-go/testing:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
117
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/exists/admission.go
generated
vendored
Normal file
117
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/exists/admission.go
generated
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
Copyright 2014 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 exists
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||
corelisters "k8s.io/kubernetes/pkg/client/listers/core/internalversion"
|
||||
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
)
|
||||
|
||||
// PluginName indicates name of admission plugin.
|
||||
const PluginName = "NamespaceExists"
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
|
||||
return NewExists(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// Exists is an implementation of admission.Interface.
|
||||
// It rejects all incoming requests in a namespace context if the namespace does not exist.
|
||||
// It is useful in deployments that want to enforce pre-declaration of a Namespace resource.
|
||||
type Exists struct {
|
||||
*admission.Handler
|
||||
client internalclientset.Interface
|
||||
namespaceLister corelisters.NamespaceLister
|
||||
}
|
||||
|
||||
var _ admission.ValidationInterface = &Exists{}
|
||||
var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&Exists{})
|
||||
var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&Exists{})
|
||||
|
||||
// Validate makes an admission decision based on the request attributes
|
||||
func (e *Exists) Validate(a admission.Attributes) error {
|
||||
// if we're here, then we've already passed authentication, so we're allowed to do what we're trying to do
|
||||
// if we're here, then the API server has found a route, which means that if we have a non-empty namespace
|
||||
// its a namespaced resource.
|
||||
if len(a.GetNamespace()) == 0 || a.GetKind().GroupKind() == api.Kind("Namespace") {
|
||||
return nil
|
||||
}
|
||||
|
||||
// we need to wait for our caches to warm
|
||||
if !e.WaitForReady() {
|
||||
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
|
||||
}
|
||||
_, err := e.namespaceLister.Get(a.GetNamespace())
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !errors.IsNotFound(err) {
|
||||
return errors.NewInternalError(err)
|
||||
}
|
||||
|
||||
// in case of latency in our caches, make a call direct to storage to verify that it truly exists or not
|
||||
_, err = e.client.Core().Namespaces().Get(a.GetNamespace(), metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return errors.NewInternalError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewExists creates a new namespace exists admission control handler
|
||||
func NewExists() *Exists {
|
||||
return &Exists{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update, admission.Delete),
|
||||
}
|
||||
}
|
||||
|
||||
// SetInternalKubeClientSet implements the WantsInternalKubeClientSet interface.
|
||||
func (e *Exists) SetInternalKubeClientSet(client internalclientset.Interface) {
|
||||
e.client = client
|
||||
}
|
||||
|
||||
// SetInternalKubeInformerFactory implements the WantsInternalKubeInformerFactory interface.
|
||||
func (e *Exists) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) {
|
||||
namespaceInformer := f.Core().InternalVersion().Namespaces()
|
||||
e.namespaceLister = namespaceInformer.Lister()
|
||||
e.SetReadyFunc(namespaceInformer.Informer().HasSynced)
|
||||
}
|
||||
|
||||
// ValidateInitialization implements the InitializationValidator interface.
|
||||
func (e *Exists) ValidateInitialization() error {
|
||||
if e.namespaceLister == nil {
|
||||
return fmt.Errorf("missing namespaceLister")
|
||||
}
|
||||
if e.client == nil {
|
||||
return fmt.Errorf("missing client")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
118
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/exists/admission_test.go
generated
vendored
Normal file
118
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/exists/admission_test.go
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
Copyright 2014 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 exists
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
core "k8s.io/client-go/testing"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||
kubeadmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
)
|
||||
|
||||
// newHandlerForTest returns the admission controller configured for testing.
|
||||
func newHandlerForTest(c clientset.Interface) (admission.ValidationInterface, informers.SharedInformerFactory, error) {
|
||||
f := informers.NewSharedInformerFactory(c, 5*time.Minute)
|
||||
handler := NewExists()
|
||||
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil)
|
||||
pluginInitializer.Initialize(handler)
|
||||
err := admission.ValidateInitialization(handler)
|
||||
return handler, f, err
|
||||
}
|
||||
|
||||
// newMockClientForTest creates a mock client that returns a client configured for the specified list of namespaces.
|
||||
func newMockClientForTest(namespaces []string) *fake.Clientset {
|
||||
mockClient := &fake.Clientset{}
|
||||
mockClient.AddReactor("list", "namespaces", func(action core.Action) (bool, runtime.Object, error) {
|
||||
namespaceList := &api.NamespaceList{
|
||||
ListMeta: metav1.ListMeta{
|
||||
ResourceVersion: fmt.Sprintf("%d", len(namespaces)),
|
||||
},
|
||||
}
|
||||
for i, ns := range namespaces {
|
||||
namespaceList.Items = append(namespaceList.Items, api.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: ns,
|
||||
ResourceVersion: fmt.Sprintf("%d", i),
|
||||
},
|
||||
})
|
||||
}
|
||||
return true, namespaceList, nil
|
||||
})
|
||||
return mockClient
|
||||
}
|
||||
|
||||
// newPod returns a new pod for the specified namespace
|
||||
func newPod(namespace string) api.Pod {
|
||||
return api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: namespace},
|
||||
Spec: api.PodSpec{
|
||||
Volumes: []api.Volume{{Name: "vol"}},
|
||||
Containers: []api.Container{{Name: "ctr", Image: "image"}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdmissionNamespaceExists verifies pod is admitted only if namespace exists.
|
||||
func TestAdmissionNamespaceExists(t *testing.T) {
|
||||
namespace := "test"
|
||||
mockClient := newMockClientForTest([]string{namespace})
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
pod := newPod(namespace)
|
||||
err = handler.Validate(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error returned from admission handler")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdmissionNamespaceDoesNotExist verifies pod is not admitted if namespace does not exist.
|
||||
func TestAdmissionNamespaceDoesNotExist(t *testing.T) {
|
||||
namespace := "test"
|
||||
mockClient := newMockClientForTest([]string{})
|
||||
mockClient.AddReactor("get", "namespaces", func(action core.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, fmt.Errorf("nope, out of luck")
|
||||
})
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
pod := newPod(namespace)
|
||||
err = handler.Validate(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err == nil {
|
||||
actions := ""
|
||||
for _, action := range mockClient.Actions() {
|
||||
actions = actions + action.GetVerb() + ":" + action.GetResource().Resource + ":" + action.GetSubresource() + ", "
|
||||
}
|
||||
t.Errorf("expected error returned from admission handler: %v", actions)
|
||||
}
|
||||
}
|
||||
62
vendor/k8s.io/kubernetes/plugin/pkg/admission/noderestriction/BUILD
generated
vendored
Normal file
62
vendor/k8s.io/kubernetes/plugin/pkg/admission/noderestriction/BUILD
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["admission.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/noderestriction",
|
||||
deps = [
|
||||
"//pkg/api/pod:go_default_library",
|
||||
"//pkg/apis/authentication:go_default_library",
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/policy:go_default_library",
|
||||
"//pkg/auth/nodeidentifier:go_default_library",
|
||||
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
|
||||
"//pkg/client/listers/core/internalversion:go_default_library",
|
||||
"//pkg/features:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/authentication:go_default_library",
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/policy:go_default_library",
|
||||
"//pkg/auth/nodeidentifier:go_default_library",
|
||||
"//pkg/client/listers/core/internalversion:go_default_library",
|
||||
"//pkg/features:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types: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/apiserver/pkg/util/feature:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
10
vendor/k8s.io/kubernetes/plugin/pkg/admission/noderestriction/OWNERS
generated
vendored
Normal file
10
vendor/k8s.io/kubernetes/plugin/pkg/admission/noderestriction/OWNERS
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
approvers:
|
||||
- deads2k
|
||||
- liggitt
|
||||
- tallclair
|
||||
- mikedanese
|
||||
reviewers:
|
||||
- deads2k
|
||||
- liggitt
|
||||
- tallclair
|
||||
- mikedanese
|
||||
391
vendor/k8s.io/kubernetes/plugin/pkg/admission/noderestriction/admission.go
generated
vendored
Normal file
391
vendor/k8s.io/kubernetes/plugin/pkg/admission/noderestriction/admission.go
generated
vendored
Normal file
@@ -0,0 +1,391 @@
|
||||
/*
|
||||
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 noderestriction
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
podutil "k8s.io/kubernetes/pkg/api/pod"
|
||||
authenticationapi "k8s.io/kubernetes/pkg/apis/authentication"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/policy"
|
||||
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
|
||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||
internalversion "k8s.io/kubernetes/pkg/client/listers/core/internalversion"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
)
|
||||
|
||||
const (
|
||||
PluginName = "NodeRestriction"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
|
||||
return NewPlugin(nodeidentifier.NewDefaultNodeIdentifier()), nil
|
||||
})
|
||||
}
|
||||
|
||||
// NewPlugin creates a new NodeRestriction admission plugin.
|
||||
// This plugin identifies requests from nodes
|
||||
func NewPlugin(nodeIdentifier nodeidentifier.NodeIdentifier) *nodePlugin {
|
||||
return &nodePlugin{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update, admission.Delete),
|
||||
nodeIdentifier: nodeIdentifier,
|
||||
features: utilfeature.DefaultFeatureGate,
|
||||
}
|
||||
}
|
||||
|
||||
// nodePlugin holds state for and implements the admission plugin.
|
||||
type nodePlugin struct {
|
||||
*admission.Handler
|
||||
nodeIdentifier nodeidentifier.NodeIdentifier
|
||||
podsGetter internalversion.PodLister
|
||||
// allows overriding for testing
|
||||
features utilfeature.FeatureGate
|
||||
}
|
||||
|
||||
var (
|
||||
_ = admission.Interface(&nodePlugin{})
|
||||
_ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&nodePlugin{})
|
||||
)
|
||||
|
||||
func (p *nodePlugin) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) {
|
||||
p.podsGetter = f.Core().InternalVersion().Pods().Lister()
|
||||
}
|
||||
|
||||
func (p *nodePlugin) ValidateInitialization() error {
|
||||
if p.nodeIdentifier == nil {
|
||||
return fmt.Errorf("%s requires a node identifier", PluginName)
|
||||
}
|
||||
if p.podsGetter == nil {
|
||||
return fmt.Errorf("%s requires a pod getter", PluginName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
podResource = api.Resource("pods")
|
||||
nodeResource = api.Resource("nodes")
|
||||
pvcResource = api.Resource("persistentvolumeclaims")
|
||||
svcacctResource = api.Resource("serviceaccounts")
|
||||
)
|
||||
|
||||
func (c *nodePlugin) Admit(a admission.Attributes) error {
|
||||
nodeName, isNode := c.nodeIdentifier.NodeIdentity(a.GetUserInfo())
|
||||
|
||||
// Our job is just to restrict nodes
|
||||
if !isNode {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(nodeName) == 0 {
|
||||
// disallow requests we cannot match to a particular node
|
||||
return admission.NewForbidden(a, fmt.Errorf("could not determine node from user %q", a.GetUserInfo().GetName()))
|
||||
}
|
||||
|
||||
switch a.GetResource().GroupResource() {
|
||||
case podResource:
|
||||
switch a.GetSubresource() {
|
||||
case "":
|
||||
return c.admitPod(nodeName, a)
|
||||
case "status":
|
||||
return c.admitPodStatus(nodeName, a)
|
||||
case "eviction":
|
||||
return c.admitPodEviction(nodeName, a)
|
||||
default:
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected pod subresource %q", a.GetSubresource()))
|
||||
}
|
||||
|
||||
case nodeResource:
|
||||
return c.admitNode(nodeName, a)
|
||||
|
||||
case pvcResource:
|
||||
switch a.GetSubresource() {
|
||||
case "status":
|
||||
return c.admitPVCStatus(nodeName, a)
|
||||
default:
|
||||
return admission.NewForbidden(a, fmt.Errorf("may only update PVC status"))
|
||||
}
|
||||
|
||||
case svcacctResource:
|
||||
if c.features.Enabled(features.TokenRequest) {
|
||||
return c.admitServiceAccount(nodeName, a)
|
||||
}
|
||||
return nil
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *nodePlugin) admitPod(nodeName string, a admission.Attributes) error {
|
||||
switch a.GetOperation() {
|
||||
case admission.Create:
|
||||
// require a pod object
|
||||
pod, ok := a.GetObject().(*api.Pod)
|
||||
if !ok {
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
|
||||
}
|
||||
|
||||
// only allow nodes to create mirror pods
|
||||
if _, isMirrorPod := pod.Annotations[api.MirrorPodAnnotationKey]; !isMirrorPod {
|
||||
return admission.NewForbidden(a, fmt.Errorf("pod does not have %q annotation, node %q can only create mirror pods", api.MirrorPodAnnotationKey, nodeName))
|
||||
}
|
||||
|
||||
// only allow nodes to create a pod bound to itself
|
||||
if pod.Spec.NodeName != nodeName {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node %q can only create pods with spec.nodeName set to itself", nodeName))
|
||||
}
|
||||
|
||||
// don't allow a node to create a pod that references any other API objects
|
||||
if pod.Spec.ServiceAccountName != "" {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node %q can not create pods that reference a service account", nodeName))
|
||||
}
|
||||
hasSecrets := false
|
||||
podutil.VisitPodSecretNames(pod, func(name string) (shouldContinue bool) { hasSecrets = true; return false })
|
||||
if hasSecrets {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node %q can not create pods that reference secrets", nodeName))
|
||||
}
|
||||
hasConfigMaps := false
|
||||
podutil.VisitPodConfigmapNames(pod, func(name string) (shouldContinue bool) { hasConfigMaps = true; return false })
|
||||
if hasConfigMaps {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node %q can not create pods that reference configmaps", nodeName))
|
||||
}
|
||||
for _, v := range pod.Spec.Volumes {
|
||||
if v.PersistentVolumeClaim != nil {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node %q can not create pods that reference persistentvolumeclaims", nodeName))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
case admission.Delete:
|
||||
// get the existing pod
|
||||
existingPod, err := c.podsGetter.Pods(a.GetNamespace()).Get(a.GetName())
|
||||
if errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return admission.NewForbidden(a, err)
|
||||
}
|
||||
// only allow a node to delete a pod bound to itself
|
||||
if existingPod.Spec.NodeName != nodeName {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node %q can only delete pods with spec.nodeName set to itself", nodeName))
|
||||
}
|
||||
return nil
|
||||
|
||||
default:
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected operation %q", a.GetOperation()))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *nodePlugin) admitPodStatus(nodeName string, a admission.Attributes) error {
|
||||
switch a.GetOperation() {
|
||||
case admission.Update:
|
||||
// require an existing pod
|
||||
pod, ok := a.GetOldObject().(*api.Pod)
|
||||
if !ok {
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetOldObject()))
|
||||
}
|
||||
// only allow a node to update status of a pod bound to itself
|
||||
if pod.Spec.NodeName != nodeName {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node %q can only update pod status for pods with spec.nodeName set to itself", nodeName))
|
||||
}
|
||||
return nil
|
||||
|
||||
default:
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected operation %q", a.GetOperation()))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *nodePlugin) admitPodEviction(nodeName string, a admission.Attributes) error {
|
||||
switch a.GetOperation() {
|
||||
case admission.Create:
|
||||
// require eviction to an existing pod object
|
||||
eviction, ok := a.GetObject().(*policy.Eviction)
|
||||
if !ok {
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
|
||||
}
|
||||
// use pod name from the admission attributes, if set, rather than from the submitted Eviction object
|
||||
podName := a.GetName()
|
||||
if len(podName) == 0 {
|
||||
if len(eviction.Name) == 0 {
|
||||
return admission.NewForbidden(a, fmt.Errorf("could not determine pod from request data"))
|
||||
}
|
||||
podName = eviction.Name
|
||||
}
|
||||
// get the existing pod
|
||||
existingPod, err := c.podsGetter.Pods(a.GetNamespace()).Get(podName)
|
||||
if errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return admission.NewForbidden(a, err)
|
||||
}
|
||||
// only allow a node to evict a pod bound to itself
|
||||
if existingPod.Spec.NodeName != nodeName {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node %s can only evict pods with spec.nodeName set to itself", nodeName))
|
||||
}
|
||||
return nil
|
||||
|
||||
default:
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected operation %s", a.GetOperation()))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *nodePlugin) admitPVCStatus(nodeName string, a admission.Attributes) error {
|
||||
switch a.GetOperation() {
|
||||
case admission.Update:
|
||||
if !c.features.Enabled(features.ExpandPersistentVolumes) {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node %q may not update persistentvolumeclaim metadata", nodeName))
|
||||
}
|
||||
|
||||
oldPVC, ok := a.GetOldObject().(*api.PersistentVolumeClaim)
|
||||
if !ok {
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetOldObject()))
|
||||
}
|
||||
|
||||
newPVC, ok := a.GetObject().(*api.PersistentVolumeClaim)
|
||||
if !ok {
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
|
||||
}
|
||||
|
||||
// make copies for comparison
|
||||
oldPVC = oldPVC.DeepCopy()
|
||||
newPVC = newPVC.DeepCopy()
|
||||
|
||||
// zero out resourceVersion to avoid comparing differences,
|
||||
// since the new object could leave it empty to indicate an unconditional update
|
||||
oldPVC.ObjectMeta.ResourceVersion = ""
|
||||
newPVC.ObjectMeta.ResourceVersion = ""
|
||||
|
||||
oldPVC.Status.Capacity = nil
|
||||
newPVC.Status.Capacity = nil
|
||||
|
||||
oldPVC.Status.Conditions = nil
|
||||
newPVC.Status.Conditions = nil
|
||||
|
||||
// ensure no metadata changed. nodes should not be able to relabel, add finalizers/owners, etc
|
||||
if !apiequality.Semantic.DeepEqual(oldPVC, newPVC) {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node %q may not update fields other than status.capacity and status.conditions: %v", nodeName, diff.ObjectReflectDiff(oldPVC, newPVC)))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
default:
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected operation %q", a.GetOperation()))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *nodePlugin) admitNode(nodeName string, a admission.Attributes) error {
|
||||
requestedName := a.GetName()
|
||||
if a.GetOperation() == admission.Create {
|
||||
node, ok := a.GetObject().(*api.Node)
|
||||
if !ok {
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
|
||||
}
|
||||
|
||||
// Don't allow a node to create its Node API object with the config source set.
|
||||
// We scope node access to things listed in the Node.Spec, so allowing this would allow a view escalation.
|
||||
if node.Spec.ConfigSource != nil {
|
||||
return admission.NewForbidden(a, fmt.Errorf("cannot create with non-nil configSource"))
|
||||
}
|
||||
|
||||
// On create, get name from new object if unset in admission
|
||||
if len(requestedName) == 0 {
|
||||
requestedName = node.Name
|
||||
}
|
||||
}
|
||||
if requestedName != nodeName {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node %q cannot modify node %q", nodeName, requestedName))
|
||||
}
|
||||
|
||||
if a.GetOperation() == admission.Update {
|
||||
node, ok := a.GetObject().(*api.Node)
|
||||
if !ok {
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
|
||||
}
|
||||
oldNode, ok := a.GetOldObject().(*api.Node)
|
||||
if !ok {
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
|
||||
}
|
||||
|
||||
// Don't allow a node to update the config source on its Node API object.
|
||||
// We scope node access to things listed in the Node.Spec, so allowing this would allow a view escalation.
|
||||
// We only do the check if the new node's configSource is non-nil; old kubelets might drop the field during a status update.
|
||||
if node.Spec.ConfigSource != nil && !apiequality.Semantic.DeepEqual(node.Spec.ConfigSource, oldNode.Spec.ConfigSource) {
|
||||
return admission.NewForbidden(a, fmt.Errorf("cannot update configSource to a new non-nil configSource"))
|
||||
}
|
||||
|
||||
// Don't allow a node to update its own taints. This would allow a node to remove or modify its
|
||||
// taints in a way that would let it steer disallowed workloads to itself.
|
||||
if !apiequality.Semantic.DeepEqual(node.Spec.Taints, oldNode.Spec.Taints) {
|
||||
return admission.NewForbidden(a, fmt.Errorf("cannot modify taints"))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *nodePlugin) admitServiceAccount(nodeName string, a admission.Attributes) error {
|
||||
if a.GetOperation() != admission.Create {
|
||||
return nil
|
||||
}
|
||||
if a.GetSubresource() != "token" {
|
||||
return nil
|
||||
}
|
||||
tr, ok := a.GetObject().(*authenticationapi.TokenRequest)
|
||||
if !ok {
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
|
||||
}
|
||||
|
||||
// TokenRequests from a node must have a pod binding. That pod must be
|
||||
// scheduled on the node.
|
||||
ref := tr.Spec.BoundObjectRef
|
||||
if ref == nil ||
|
||||
ref.APIVersion != "v1" ||
|
||||
ref.Kind != "Pod" ||
|
||||
ref.Name == "" {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node requested token not bound to a pod"))
|
||||
}
|
||||
if ref.UID == "" {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node requested token with a pod binding without a uid"))
|
||||
}
|
||||
pod, err := c.podsGetter.Pods(a.GetNamespace()).Get(ref.Name)
|
||||
if errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return admission.NewForbidden(a, err)
|
||||
}
|
||||
if ref.UID != pod.UID {
|
||||
return admission.NewForbidden(a, fmt.Errorf("the UID in the bound object reference (%s) does not match the UID in record (%s). The object might have been deleted and then recreated", ref.UID, pod.UID))
|
||||
}
|
||||
if pod.Spec.NodeName != nodeName {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node requested token bound to a pod scheduled on a different node"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
860
vendor/k8s.io/kubernetes/plugin/pkg/admission/noderestriction/admission_test.go
generated
vendored
Normal file
860
vendor/k8s.io/kubernetes/plugin/pkg/admission/noderestriction/admission_test.go
generated
vendored
Normal file
@@ -0,0 +1,860 @@
|
||||
/*
|
||||
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 noderestriction
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
authenticationapi "k8s.io/kubernetes/pkg/apis/authentication"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/policy"
|
||||
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
|
||||
"k8s.io/kubernetes/pkg/client/listers/core/internalversion"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
)
|
||||
|
||||
var (
|
||||
trEnabledFeature = utilfeature.NewFeatureGate()
|
||||
trDisabledFeature = utilfeature.NewFeatureGate()
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := trEnabledFeature.Add(map[utilfeature.Feature]utilfeature.FeatureSpec{features.TokenRequest: {Default: true}}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := trDisabledFeature.Add(map[utilfeature.Feature]utilfeature.FeatureSpec{features.TokenRequest: {Default: false}}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func makeTestPod(namespace, name, node string, mirror bool) *api.Pod {
|
||||
pod := &api.Pod{}
|
||||
pod.Namespace = namespace
|
||||
pod.UID = types.UID("pod-uid")
|
||||
pod.Name = name
|
||||
pod.Spec.NodeName = node
|
||||
if mirror {
|
||||
pod.Annotations = map[string]string{api.MirrorPodAnnotationKey: "true"}
|
||||
}
|
||||
return pod
|
||||
}
|
||||
|
||||
func makeTestPodEviction(name string) *policy.Eviction {
|
||||
eviction := &policy.Eviction{}
|
||||
eviction.Name = name
|
||||
eviction.Namespace = "ns"
|
||||
return eviction
|
||||
}
|
||||
|
||||
func makeTokenRequest(podname string, poduid types.UID) *authenticationapi.TokenRequest {
|
||||
tr := &authenticationapi.TokenRequest{
|
||||
Spec: authenticationapi.TokenRequestSpec{
|
||||
Audiences: []string{"foo"},
|
||||
},
|
||||
}
|
||||
if podname != "" {
|
||||
tr.Spec.BoundObjectRef = &authenticationapi.BoundObjectReference{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
Name: podname,
|
||||
UID: poduid,
|
||||
}
|
||||
}
|
||||
return tr
|
||||
}
|
||||
|
||||
func Test_nodePlugin_Admit(t *testing.T) {
|
||||
var (
|
||||
mynode = &user.DefaultInfo{Name: "system:node:mynode", Groups: []string{"system:nodes"}}
|
||||
bob = &user.DefaultInfo{Name: "bob"}
|
||||
|
||||
mynodeObjMeta = metav1.ObjectMeta{Name: "mynode"}
|
||||
mynodeObj = &api.Node{ObjectMeta: mynodeObjMeta}
|
||||
mynodeObjConfigA = &api.Node{ObjectMeta: mynodeObjMeta, Spec: api.NodeSpec{ConfigSource: &api.NodeConfigSource{
|
||||
ConfigMap: &api.ConfigMapNodeConfigSource{
|
||||
Name: "foo",
|
||||
Namespace: "bar",
|
||||
UID: "fooUID",
|
||||
KubeletConfigKey: "kubelet",
|
||||
}}}}
|
||||
mynodeObjConfigB = &api.Node{ObjectMeta: mynodeObjMeta, Spec: api.NodeSpec{ConfigSource: &api.NodeConfigSource{
|
||||
ConfigMap: &api.ConfigMapNodeConfigSource{
|
||||
Name: "qux",
|
||||
Namespace: "bar",
|
||||
UID: "quxUID",
|
||||
KubeletConfigKey: "kubelet",
|
||||
}}}}
|
||||
mynodeObjTaintA = &api.Node{ObjectMeta: mynodeObjMeta, Spec: api.NodeSpec{Taints: []api.Taint{{Key: "mykey", Value: "A"}}}}
|
||||
mynodeObjTaintB = &api.Node{ObjectMeta: mynodeObjMeta, Spec: api.NodeSpec{Taints: []api.Taint{{Key: "mykey", Value: "B"}}}}
|
||||
othernodeObj = &api.Node{ObjectMeta: metav1.ObjectMeta{Name: "othernode"}}
|
||||
|
||||
mymirrorpod = makeTestPod("ns", "mymirrorpod", "mynode", true)
|
||||
othermirrorpod = makeTestPod("ns", "othermirrorpod", "othernode", true)
|
||||
unboundmirrorpod = makeTestPod("ns", "unboundmirrorpod", "", true)
|
||||
mypod = makeTestPod("ns", "mypod", "mynode", false)
|
||||
otherpod = makeTestPod("ns", "otherpod", "othernode", false)
|
||||
unboundpod = makeTestPod("ns", "unboundpod", "", false)
|
||||
unnamedpod = makeTestPod("ns", "", "mynode", false)
|
||||
|
||||
mymirrorpodEviction = makeTestPodEviction("mymirrorpod")
|
||||
othermirrorpodEviction = makeTestPodEviction("othermirrorpod")
|
||||
unboundmirrorpodEviction = makeTestPodEviction("unboundmirrorpod")
|
||||
mypodEviction = makeTestPodEviction("mypod")
|
||||
otherpodEviction = makeTestPodEviction("otherpod")
|
||||
unboundpodEviction = makeTestPodEviction("unboundpod")
|
||||
unnamedEviction = makeTestPodEviction("")
|
||||
|
||||
configmapResource = api.Resource("configmap").WithVersion("v1")
|
||||
configmapKind = api.Kind("ConfigMap").WithVersion("v1")
|
||||
|
||||
podResource = api.Resource("pods").WithVersion("v1")
|
||||
podKind = api.Kind("Pod").WithVersion("v1")
|
||||
evictionKind = policy.Kind("Eviction").WithVersion("v1beta1")
|
||||
|
||||
nodeResource = api.Resource("nodes").WithVersion("v1")
|
||||
nodeKind = api.Kind("Node").WithVersion("v1")
|
||||
|
||||
svcacctResource = api.Resource("serviceaccounts").WithVersion("v1")
|
||||
tokenrequestKind = api.Kind("TokenRequest").WithVersion("v1")
|
||||
|
||||
noExistingPodsIndex = cache.NewIndexer(cache.MetaNamespaceKeyFunc, nil)
|
||||
noExistingPods = internalversion.NewPodLister(noExistingPodsIndex)
|
||||
|
||||
existingPodsIndex = cache.NewIndexer(cache.MetaNamespaceKeyFunc, nil)
|
||||
existingPods = internalversion.NewPodLister(existingPodsIndex)
|
||||
)
|
||||
|
||||
existingPodsIndex.Add(mymirrorpod)
|
||||
existingPodsIndex.Add(othermirrorpod)
|
||||
existingPodsIndex.Add(unboundmirrorpod)
|
||||
existingPodsIndex.Add(mypod)
|
||||
existingPodsIndex.Add(otherpod)
|
||||
existingPodsIndex.Add(unboundpod)
|
||||
|
||||
sapod := makeTestPod("ns", "mysapod", "mynode", true)
|
||||
sapod.Spec.ServiceAccountName = "foo"
|
||||
|
||||
secretpod := makeTestPod("ns", "mysecretpod", "mynode", true)
|
||||
secretpod.Spec.Volumes = []api.Volume{{VolumeSource: api.VolumeSource{Secret: &api.SecretVolumeSource{SecretName: "foo"}}}}
|
||||
|
||||
configmappod := makeTestPod("ns", "myconfigmappod", "mynode", true)
|
||||
configmappod.Spec.Volumes = []api.Volume{{VolumeSource: api.VolumeSource{ConfigMap: &api.ConfigMapVolumeSource{LocalObjectReference: api.LocalObjectReference{Name: "foo"}}}}}
|
||||
|
||||
pvcpod := makeTestPod("ns", "mypvcpod", "mynode", true)
|
||||
pvcpod.Spec.Volumes = []api.Volume{{VolumeSource: api.VolumeSource{PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{ClaimName: "foo"}}}}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
podsGetter internalversion.PodLister
|
||||
attributes admission.Attributes
|
||||
features utilfeature.FeatureGate
|
||||
err string
|
||||
}{
|
||||
// Mirror pods bound to us
|
||||
{
|
||||
name: "allow creating a mirror pod bound to self",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(mymirrorpod, nil, podKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "", admission.Create, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "forbid update of mirror pod bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mymirrorpod, mymirrorpod, podKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "allow delete of mirror pod bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "", admission.Delete, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "forbid create of mirror pod status bound to self",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(mymirrorpod, nil, podKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "status", admission.Create, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "allow update of mirror pod status bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mymirrorpod, mymirrorpod, podKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "status", admission.Update, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of mirror pod status bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "status", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "allow create of eviction for mirror pod bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mymirrorpodEviction, nil, evictionKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "forbid update of eviction for mirror pod bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mymirrorpodEviction, nil, evictionKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "eviction", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of eviction for mirror pod bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mymirrorpodEviction, nil, evictionKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "eviction", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "allow create of unnamed eviction for mirror pod bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "",
|
||||
},
|
||||
|
||||
// Mirror pods bound to another node
|
||||
{
|
||||
name: "forbid creating a mirror pod bound to another",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(othermirrorpod, nil, podKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "", admission.Create, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid update of mirror pod bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(othermirrorpod, othermirrorpod, podKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of mirror pod bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "", admission.Delete, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid create of mirror pod status bound to another",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(othermirrorpod, nil, podKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "status", admission.Create, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid update of mirror pod status bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(othermirrorpod, othermirrorpod, podKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "status", admission.Update, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of mirror pod status bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "status", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid create of eviction for mirror pod bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(othermirrorpodEviction, nil, evictionKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid update of eviction for mirror pod bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(othermirrorpodEviction, nil, evictionKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "eviction", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of eviction for mirror pod bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(othermirrorpodEviction, nil, evictionKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "eviction", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid create of unnamed eviction for mirror pod to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
|
||||
// Mirror pods not bound to any node
|
||||
{
|
||||
name: "forbid creating a mirror pod unbound",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundmirrorpod, nil, podKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "", admission.Create, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid update of mirror pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundmirrorpod, unboundmirrorpod, podKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of mirror pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "", admission.Delete, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid create of mirror pod status unbound",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundmirrorpod, nil, podKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "status", admission.Create, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid update of mirror pod status unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundmirrorpod, unboundmirrorpod, podKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "status", admission.Update, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of mirror pod status unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "status", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid create of eviction for mirror pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundmirrorpodEviction, nil, evictionKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid update of eviction for mirror pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundmirrorpodEviction, nil, evictionKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "eviction", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of eviction for mirror pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundmirrorpodEviction, nil, evictionKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "eviction", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid create of unnamed eviction for mirror pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
|
||||
// Normal pods bound to us
|
||||
{
|
||||
name: "forbid creating a normal pod bound to self",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(mypod, nil, podKind, mypod.Namespace, mypod.Name, podResource, "", admission.Create, mynode),
|
||||
err: "can only create mirror pods",
|
||||
},
|
||||
{
|
||||
name: "forbid update of normal pod bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mypod, mypod, podKind, mypod.Namespace, mypod.Name, podResource, "", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "allow delete of normal pod bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, mypod.Namespace, mypod.Name, podResource, "", admission.Delete, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "forbid create of normal pod status bound to self",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(mypod, nil, podKind, mypod.Namespace, mypod.Name, podResource, "status", admission.Create, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "allow update of normal pod status bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mypod, mypod, podKind, mypod.Namespace, mypod.Name, podResource, "status", admission.Update, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of normal pod status bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, mypod.Namespace, mypod.Name, podResource, "status", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid update of eviction for normal pod bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, mypod.Namespace, mypod.Name, podResource, "eviction", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of eviction for normal pod bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, mypod.Namespace, mypod.Name, podResource, "eviction", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "allow create of unnamed eviction for normal pod bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, mypod.Namespace, mypod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "",
|
||||
},
|
||||
|
||||
// Normal pods bound to another
|
||||
{
|
||||
name: "forbid creating a normal pod bound to another",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(otherpod, nil, podKind, otherpod.Namespace, otherpod.Name, podResource, "", admission.Create, mynode),
|
||||
err: "can only create mirror pods",
|
||||
},
|
||||
{
|
||||
name: "forbid update of normal pod bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(otherpod, otherpod, podKind, otherpod.Namespace, otherpod.Name, podResource, "", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of normal pod bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, otherpod.Namespace, otherpod.Name, podResource, "", admission.Delete, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid create of normal pod status bound to another",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(otherpod, nil, podKind, otherpod.Namespace, otherpod.Name, podResource, "status", admission.Create, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid update of normal pod status bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(otherpod, otherpod, podKind, otherpod.Namespace, otherpod.Name, podResource, "status", admission.Update, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of normal pod status bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, otherpod.Namespace, otherpod.Name, podResource, "status", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid create of eviction for normal pod bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(otherpodEviction, nil, evictionKind, otherpodEviction.Namespace, otherpodEviction.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid update of eviction for normal pod bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(otherpodEviction, nil, evictionKind, otherpodEviction.Namespace, otherpodEviction.Name, podResource, "eviction", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of eviction for normal pod bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(otherpodEviction, nil, evictionKind, otherpodEviction.Namespace, otherpodEviction.Name, podResource, "eviction", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid create of unnamed eviction for normal pod bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, otherpod.Namespace, otherpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
|
||||
// Normal pods not bound to any node
|
||||
{
|
||||
name: "forbid creating a normal pod unbound",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundpod, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "", admission.Create, mynode),
|
||||
err: "can only create mirror pods",
|
||||
},
|
||||
{
|
||||
name: "forbid update of normal pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundpod, unboundpod, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of normal pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "", admission.Delete, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid create of normal pod status unbound",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundpod, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "status", admission.Create, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid update of normal pod status unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundpod, unboundpod, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "status", admission.Update, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of normal pod status unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "status", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid create of eviction for normal pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundpodEviction, nil, evictionKind, unboundpod.Namespace, unboundpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid update of eviction for normal pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundpodEviction, nil, evictionKind, unboundpod.Namespace, unboundpod.Name, podResource, "eviction", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of eviction for normal pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundpodEviction, nil, evictionKind, unboundpod.Namespace, unboundpod.Name, podResource, "eviction", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid create of unnamed eviction for normal unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, unboundpod.Namespace, unboundpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
|
||||
// Missing pod
|
||||
{
|
||||
name: "forbid delete of unknown pod",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "", admission.Delete, mynode),
|
||||
err: "not found",
|
||||
},
|
||||
{
|
||||
name: "forbid create of eviction for unknown pod",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, mypod.Namespace, mypod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "not found",
|
||||
},
|
||||
{
|
||||
name: "forbid update of eviction for unknown pod",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, mypod.Namespace, mypod.Name, podResource, "eviction", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of eviction for unknown pod",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, mypod.Namespace, mypod.Name, podResource, "eviction", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid create of unnamed eviction for unknown pod",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, mypod.Namespace, mypod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "not found",
|
||||
},
|
||||
|
||||
// Eviction for unnamed pod
|
||||
{
|
||||
name: "allow create of eviction for unnamed pod",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, unnamedpod.Namespace, unnamedpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
// use the submitted eviction resource name as the pod name
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "forbid update of eviction for unnamed pod",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, unnamedpod.Namespace, unnamedpod.Name, podResource, "eviction", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of eviction for unnamed pod",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, unnamedpod.Namespace, unnamedpod.Name, podResource, "eviction", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid create of unnamed eviction for unnamed pod",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, unnamedpod.Namespace, unnamedpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "could not determine pod from request data",
|
||||
},
|
||||
|
||||
// Resource pods
|
||||
{
|
||||
name: "forbid create of pod referencing service account",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(sapod, nil, podKind, sapod.Namespace, sapod.Name, podResource, "", admission.Create, mynode),
|
||||
err: "reference a service account",
|
||||
},
|
||||
{
|
||||
name: "forbid create of pod referencing secret",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(secretpod, nil, podKind, secretpod.Namespace, secretpod.Name, podResource, "", admission.Create, mynode),
|
||||
err: "reference secrets",
|
||||
},
|
||||
{
|
||||
name: "forbid create of pod referencing configmap",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(configmappod, nil, podKind, configmappod.Namespace, configmappod.Name, podResource, "", admission.Create, mynode),
|
||||
err: "reference configmaps",
|
||||
},
|
||||
{
|
||||
name: "forbid create of pod referencing persistentvolumeclaim",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(pvcpod, nil, podKind, pvcpod.Namespace, pvcpod.Name, podResource, "", admission.Create, mynode),
|
||||
err: "reference persistentvolumeclaims",
|
||||
},
|
||||
|
||||
// My node object
|
||||
{
|
||||
name: "allow create of my node",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(mynodeObj, nil, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Create, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow create of my node pulling name from object",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(mynodeObj, nil, nodeKind, mynodeObj.Namespace, "", nodeResource, "", admission.Create, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow create of my node with taints",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(mynodeObjTaintA, nil, nodeKind, mynodeObj.Namespace, "", nodeResource, "", admission.Create, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow update of my node",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mynodeObj, mynodeObj, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow delete of my node",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Delete, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow update of my node status",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mynodeObj, mynodeObj, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "status", admission.Update, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "forbid create of my node with non-nil configSource",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(mynodeObjConfigA, nil, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Create, mynode),
|
||||
err: "create with non-nil configSource",
|
||||
},
|
||||
{
|
||||
name: "forbid update of my node: nil configSource to new non-nil configSource",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mynodeObjConfigA, mynodeObj, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, mynode),
|
||||
err: "update configSource to a new non-nil configSource",
|
||||
},
|
||||
{
|
||||
name: "forbid update of my node: non-nil configSource to new non-nil configSource",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mynodeObjConfigB, mynodeObjConfigA, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, mynode),
|
||||
err: "update configSource to a new non-nil configSource",
|
||||
},
|
||||
{
|
||||
name: "allow update of my node: non-nil configSource unchanged",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mynodeObjConfigA, mynodeObjConfigA, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow update of my node: non-nil configSource to nil configSource",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mynodeObj, mynodeObjConfigA, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow update of my node: no change to taints",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mynodeObjTaintA, mynodeObjTaintA, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "forbid update of my node: add taints",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mynodeObjTaintA, mynodeObj, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, mynode),
|
||||
err: "cannot modify taints",
|
||||
},
|
||||
{
|
||||
name: "forbid update of my node: remove taints",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mynodeObj, mynodeObjTaintA, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, mynode),
|
||||
err: "cannot modify taints",
|
||||
},
|
||||
{
|
||||
name: "forbid update of my node: change taints",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mynodeObjTaintA, mynodeObjTaintB, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, mynode),
|
||||
err: "cannot modify taints",
|
||||
},
|
||||
|
||||
// Other node object
|
||||
{
|
||||
name: "forbid create of other node",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(othernodeObj, nil, nodeKind, othernodeObj.Namespace, othernodeObj.Name, nodeResource, "", admission.Create, mynode),
|
||||
err: "cannot modify node",
|
||||
},
|
||||
{
|
||||
name: "forbid create of other node pulling name from object",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(othernodeObj, nil, nodeKind, othernodeObj.Namespace, "", nodeResource, "", admission.Create, mynode),
|
||||
err: "cannot modify node",
|
||||
},
|
||||
{
|
||||
name: "forbid update of other node",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(othernodeObj, othernodeObj, nodeKind, othernodeObj.Namespace, othernodeObj.Name, nodeResource, "", admission.Update, mynode),
|
||||
err: "cannot modify node",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of other node",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, nodeKind, othernodeObj.Namespace, othernodeObj.Name, nodeResource, "", admission.Delete, mynode),
|
||||
err: "cannot modify node",
|
||||
},
|
||||
{
|
||||
name: "forbid update of other node status",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(othernodeObj, othernodeObj, nodeKind, othernodeObj.Namespace, othernodeObj.Name, nodeResource, "status", admission.Update, mynode),
|
||||
err: "cannot modify node",
|
||||
},
|
||||
|
||||
// Service accounts
|
||||
{
|
||||
name: "forbid create of unbound token",
|
||||
podsGetter: noExistingPods,
|
||||
features: trEnabledFeature,
|
||||
attributes: admission.NewAttributesRecord(makeTokenRequest("", ""), nil, tokenrequestKind, "ns", "mysa", svcacctResource, "token", admission.Create, mynode),
|
||||
err: "not bound to a pod",
|
||||
},
|
||||
{
|
||||
name: "forbid create of token bound to nonexistant pod",
|
||||
podsGetter: noExistingPods,
|
||||
features: trEnabledFeature,
|
||||
attributes: admission.NewAttributesRecord(makeTokenRequest("nopod", "someuid"), nil, tokenrequestKind, "ns", "mysa", svcacctResource, "token", admission.Create, mynode),
|
||||
err: "not found",
|
||||
},
|
||||
{
|
||||
name: "forbid create of token bound to pod without uid",
|
||||
podsGetter: existingPods,
|
||||
features: trEnabledFeature,
|
||||
attributes: admission.NewAttributesRecord(makeTokenRequest(mypod.Name, ""), nil, tokenrequestKind, "ns", "mysa", svcacctResource, "token", admission.Create, mynode),
|
||||
err: "pod binding without a uid",
|
||||
},
|
||||
{
|
||||
name: "forbid create of token bound to pod scheduled on another node",
|
||||
podsGetter: existingPods,
|
||||
features: trEnabledFeature,
|
||||
attributes: admission.NewAttributesRecord(makeTokenRequest(otherpod.Name, otherpod.UID), nil, tokenrequestKind, otherpod.Namespace, "mysa", svcacctResource, "token", admission.Create, mynode),
|
||||
err: "pod scheduled on a different node",
|
||||
},
|
||||
{
|
||||
name: "allow create of token bound to pod scheduled this node",
|
||||
podsGetter: existingPods,
|
||||
features: trEnabledFeature,
|
||||
attributes: admission.NewAttributesRecord(makeTokenRequest(mypod.Name, mypod.UID), nil, tokenrequestKind, mypod.Namespace, "mysa", svcacctResource, "token", admission.Create, mynode),
|
||||
},
|
||||
|
||||
// Unrelated objects
|
||||
{
|
||||
name: "allow create of unrelated object",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(&api.ConfigMap{}, nil, configmapKind, "myns", "mycm", configmapResource, "", admission.Create, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow update of unrelated object",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(&api.ConfigMap{}, &api.ConfigMap{}, configmapKind, "myns", "mycm", configmapResource, "", admission.Update, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow delete of unrelated object",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, configmapKind, "myns", "mycm", configmapResource, "", admission.Delete, mynode),
|
||||
err: "",
|
||||
},
|
||||
|
||||
// Unrelated user
|
||||
{
|
||||
name: "allow unrelated user creating a normal pod unbound",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundpod, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "", admission.Create, bob),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow unrelated user update of normal pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundpod, unboundpod, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "", admission.Update, bob),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow unrelated user delete of normal pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "", admission.Delete, bob),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow unrelated user create of normal pod status unbound",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundpod, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "status", admission.Create, bob),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow unrelated user update of normal pod status unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundpod, unboundpod, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "status", admission.Update, bob),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow unrelated user delete of normal pod status unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "status", admission.Delete, bob),
|
||||
err: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := NewPlugin(nodeidentifier.NewDefaultNodeIdentifier())
|
||||
if tt.features != nil {
|
||||
c.features = tt.features
|
||||
}
|
||||
c.podsGetter = tt.podsGetter
|
||||
err := c.Admit(tt.attributes)
|
||||
if (err == nil) != (len(tt.err) == 0) {
|
||||
t.Errorf("nodePlugin.Admit() error = %v, expected %v", err, tt.err)
|
||||
return
|
||||
}
|
||||
if len(tt.err) > 0 && !strings.Contains(err.Error(), tt.err) {
|
||||
t.Errorf("nodePlugin.Admit() error = %v, expected %v", err, tt.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
56
vendor/k8s.io/kubernetes/plugin/pkg/admission/podnodeselector/BUILD
generated
vendored
Normal file
56
vendor/k8s.io/kubernetes/plugin/pkg/admission/podnodeselector/BUILD
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["admission.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/podnodeselector",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
|
||||
"//pkg/client/listers/core/internalversion:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//pkg/kubeapiserver/admission/util:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
|
||||
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
265
vendor/k8s.io/kubernetes/plugin/pkg/admission/podnodeselector/admission.go
generated
vendored
Normal file
265
vendor/k8s.io/kubernetes/plugin/pkg/admission/podnodeselector/admission.go
generated
vendored
Normal file
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package podnodeselector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||
corelisters "k8s.io/kubernetes/pkg/client/listers/core/internalversion"
|
||||
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
"k8s.io/kubernetes/pkg/kubeapiserver/admission/util"
|
||||
)
|
||||
|
||||
// The annotation key scheduler.alpha.kubernetes.io/node-selector is for assigning
|
||||
// node selectors labels to namespaces
|
||||
var NamespaceNodeSelectors = []string{"scheduler.alpha.kubernetes.io/node-selector"}
|
||||
|
||||
const PluginName = "PodNodeSelector"
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
|
||||
// TODO move this to a versioned configuration file format.
|
||||
pluginConfig := readConfig(config)
|
||||
plugin := NewPodNodeSelector(pluginConfig.PodNodeSelectorPluginConfig)
|
||||
return plugin, nil
|
||||
})
|
||||
}
|
||||
|
||||
// podNodeSelector is an implementation of admission.Interface.
|
||||
type podNodeSelector struct {
|
||||
*admission.Handler
|
||||
client internalclientset.Interface
|
||||
namespaceLister corelisters.NamespaceLister
|
||||
// global default node selector and namespace whitelists in a cluster.
|
||||
clusterNodeSelectors map[string]string
|
||||
}
|
||||
|
||||
var _ admission.MutationInterface = &podNodeSelector{}
|
||||
var _ admission.ValidationInterface = &podNodeSelector{}
|
||||
var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&podNodeSelector{})
|
||||
var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&podNodeSelector{})
|
||||
|
||||
type pluginConfig struct {
|
||||
PodNodeSelectorPluginConfig map[string]string
|
||||
}
|
||||
|
||||
// readConfig reads default value of clusterDefaultNodeSelector
|
||||
// from the file provided with --admission-control-config-file
|
||||
// If the file is not supplied, it defaults to ""
|
||||
// The format in a file:
|
||||
// podNodeSelectorPluginConfig:
|
||||
// clusterDefaultNodeSelector: <node-selectors-labels>
|
||||
// namespace1: <node-selectors-labels>
|
||||
// namespace2: <node-selectors-labels>
|
||||
func readConfig(config io.Reader) *pluginConfig {
|
||||
defaultConfig := &pluginConfig{}
|
||||
if config == nil || reflect.ValueOf(config).IsNil() {
|
||||
return defaultConfig
|
||||
}
|
||||
d := yaml.NewYAMLOrJSONDecoder(config, 4096)
|
||||
for {
|
||||
if err := d.Decode(defaultConfig); err != nil {
|
||||
if err != io.EOF {
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
return defaultConfig
|
||||
}
|
||||
|
||||
// Admit enforces that pod and its namespace node label selectors matches at least a node in the cluster.
|
||||
func (p *podNodeSelector) Admit(a admission.Attributes) error {
|
||||
if shouldIgnore(a) {
|
||||
return nil
|
||||
}
|
||||
updateInitialized, err := util.IsUpdatingInitializedObject(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if updateInitialized {
|
||||
// node selector of an initialized pod is immutable
|
||||
return nil
|
||||
}
|
||||
if !p.WaitForReady() {
|
||||
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
|
||||
}
|
||||
|
||||
resource := a.GetResource().GroupResource()
|
||||
pod := a.GetObject().(*api.Pod)
|
||||
namespaceNodeSelector, err := p.getNamespaceNodeSelectorMap(a.GetNamespace())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if labels.Conflicts(namespaceNodeSelector, labels.Set(pod.Spec.NodeSelector)) {
|
||||
return errors.NewForbidden(resource, pod.Name, fmt.Errorf("pod node label selector conflicts with its namespace node label selector"))
|
||||
}
|
||||
|
||||
// Merge pod node selector = namespace node selector + current pod node selector
|
||||
// second selector wins
|
||||
podNodeSelectorLabels := labels.Merge(namespaceNodeSelector, pod.Spec.NodeSelector)
|
||||
pod.Spec.NodeSelector = map[string]string(podNodeSelectorLabels)
|
||||
return p.Validate(a)
|
||||
}
|
||||
|
||||
// Validate ensures that the pod node selector is allowed
|
||||
func (p *podNodeSelector) Validate(a admission.Attributes) error {
|
||||
if shouldIgnore(a) {
|
||||
return nil
|
||||
}
|
||||
if !p.WaitForReady() {
|
||||
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
|
||||
}
|
||||
|
||||
resource := a.GetResource().GroupResource()
|
||||
pod := a.GetObject().(*api.Pod)
|
||||
|
||||
namespaceNodeSelector, err := p.getNamespaceNodeSelectorMap(a.GetNamespace())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if labels.Conflicts(namespaceNodeSelector, labels.Set(pod.Spec.NodeSelector)) {
|
||||
return errors.NewForbidden(resource, pod.Name, fmt.Errorf("pod node label selector conflicts with its namespace node label selector"))
|
||||
}
|
||||
|
||||
// whitelist verification
|
||||
whitelist, err := labels.ConvertSelectorToLabelsMap(p.clusterNodeSelectors[a.GetNamespace()])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !labels.AreLabelsInWhiteList(pod.Spec.NodeSelector, whitelist) {
|
||||
return errors.NewForbidden(resource, pod.Name, fmt.Errorf("pod node label selector labels conflict with its namespace whitelist"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *podNodeSelector) getNamespaceNodeSelectorMap(namespaceName string) (labels.Set, error) {
|
||||
namespace, err := p.namespaceLister.Get(namespaceName)
|
||||
if errors.IsNotFound(err) {
|
||||
namespace, err = p.defaultGetNamespace(namespaceName)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
return nil, errors.NewInternalError(err)
|
||||
}
|
||||
} else if err != nil {
|
||||
return nil, errors.NewInternalError(err)
|
||||
}
|
||||
|
||||
return p.getNodeSelectorMap(namespace)
|
||||
}
|
||||
|
||||
func shouldIgnore(a admission.Attributes) bool {
|
||||
resource := a.GetResource().GroupResource()
|
||||
if resource != api.Resource("pods") {
|
||||
return true
|
||||
}
|
||||
if a.GetSubresource() != "" {
|
||||
// only run the checks below on pods proper and not subresources
|
||||
return true
|
||||
}
|
||||
|
||||
_, ok := a.GetObject().(*api.Pod)
|
||||
if !ok {
|
||||
glog.Errorf("expected pod but got %s", a.GetKind().Kind)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func NewPodNodeSelector(clusterNodeSelectors map[string]string) *podNodeSelector {
|
||||
return &podNodeSelector{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
clusterNodeSelectors: clusterNodeSelectors,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *podNodeSelector) SetInternalKubeClientSet(client internalclientset.Interface) {
|
||||
a.client = client
|
||||
}
|
||||
|
||||
func (p *podNodeSelector) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) {
|
||||
namespaceInformer := f.Core().InternalVersion().Namespaces()
|
||||
p.namespaceLister = namespaceInformer.Lister()
|
||||
p.SetReadyFunc(namespaceInformer.Informer().HasSynced)
|
||||
}
|
||||
|
||||
func (p *podNodeSelector) ValidateInitialization() error {
|
||||
if p.namespaceLister == nil {
|
||||
return fmt.Errorf("missing namespaceLister")
|
||||
}
|
||||
if p.client == nil {
|
||||
return fmt.Errorf("missing client")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *podNodeSelector) defaultGetNamespace(name string) (*api.Namespace, error) {
|
||||
namespace, err := p.client.Core().Namespaces().Get(name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("namespace %s does not exist", name)
|
||||
}
|
||||
return namespace, nil
|
||||
}
|
||||
|
||||
func (p *podNodeSelector) getNodeSelectorMap(namespace *api.Namespace) (labels.Set, error) {
|
||||
selector := labels.Set{}
|
||||
labelsMap := labels.Set{}
|
||||
var err error
|
||||
found := false
|
||||
if len(namespace.ObjectMeta.Annotations) > 0 {
|
||||
for _, annotation := range NamespaceNodeSelectors {
|
||||
if ns, ok := namespace.ObjectMeta.Annotations[annotation]; ok {
|
||||
labelsMap, err = labels.ConvertSelectorToLabelsMap(ns)
|
||||
if err != nil {
|
||||
return labels.Set{}, err
|
||||
}
|
||||
|
||||
if labels.Conflicts(selector, labelsMap) {
|
||||
nsName := namespace.ObjectMeta.Name
|
||||
return labels.Set{}, fmt.Errorf("%s annotations' node label selectors conflict", nsName)
|
||||
}
|
||||
selector = labels.Merge(selector, labelsMap)
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
selector, err = labels.ConvertSelectorToLabelsMap(p.clusterNodeSelectors["clusterDefaultNodeSelector"])
|
||||
if err != nil {
|
||||
return labels.Set{}, err
|
||||
}
|
||||
}
|
||||
return selector, nil
|
||||
}
|
||||
260
vendor/k8s.io/kubernetes/plugin/pkg/admission/podnodeselector/admission_test.go
generated
vendored
Normal file
260
vendor/k8s.io/kubernetes/plugin/pkg/admission/podnodeselector/admission_test.go
generated
vendored
Normal file
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package podnodeselector
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||
kubeadmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
)
|
||||
|
||||
// TestPodAdmission verifies various scenarios involving pod/namespace/global node label selectors
|
||||
func TestPodAdmission(t *testing.T) {
|
||||
namespace := &api.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "testNamespace",
|
||||
Namespace: "",
|
||||
},
|
||||
}
|
||||
|
||||
mockClient := &fake.Clientset{}
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
stopCh := make(chan struct{})
|
||||
defer close(stopCh)
|
||||
informerFactory.Start(stopCh)
|
||||
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "testPod", Namespace: "testNamespace"},
|
||||
}
|
||||
|
||||
oldPod := *pod
|
||||
oldPod.Initializers = &metav1.Initializers{Pending: []metav1.Initializer{{Name: "init"}}}
|
||||
oldPod.Spec.NodeSelector = map[string]string{
|
||||
"old": "true",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
defaultNodeSelector string
|
||||
namespaceNodeSelector string
|
||||
whitelist string
|
||||
podNodeSelector map[string]string
|
||||
mergedNodeSelector labels.Set
|
||||
ignoreTestNamespaceNodeSelector bool
|
||||
admit bool
|
||||
testName string
|
||||
}{
|
||||
{
|
||||
defaultNodeSelector: "",
|
||||
podNodeSelector: map[string]string{},
|
||||
mergedNodeSelector: labels.Set{},
|
||||
ignoreTestNamespaceNodeSelector: true,
|
||||
admit: true,
|
||||
testName: "No node selectors",
|
||||
},
|
||||
{
|
||||
defaultNodeSelector: "infra = false",
|
||||
podNodeSelector: map[string]string{},
|
||||
mergedNodeSelector: labels.Set{"infra": "false"},
|
||||
ignoreTestNamespaceNodeSelector: true,
|
||||
admit: true,
|
||||
testName: "Default node selector and no conflicts",
|
||||
},
|
||||
{
|
||||
defaultNodeSelector: "",
|
||||
namespaceNodeSelector: " infra = false ",
|
||||
podNodeSelector: map[string]string{},
|
||||
mergedNodeSelector: labels.Set{"infra": "false"},
|
||||
admit: true,
|
||||
testName: "TestNamespace node selector with whitespaces and no conflicts",
|
||||
},
|
||||
{
|
||||
defaultNodeSelector: "infra = false",
|
||||
namespaceNodeSelector: "infra=true",
|
||||
podNodeSelector: map[string]string{},
|
||||
mergedNodeSelector: labels.Set{"infra": "true"},
|
||||
admit: true,
|
||||
testName: "Default and namespace node selector, no conflicts",
|
||||
},
|
||||
{
|
||||
defaultNodeSelector: "infra = false",
|
||||
namespaceNodeSelector: "",
|
||||
podNodeSelector: map[string]string{},
|
||||
mergedNodeSelector: labels.Set{},
|
||||
admit: true,
|
||||
testName: "Empty namespace node selector and no conflicts",
|
||||
},
|
||||
{
|
||||
defaultNodeSelector: "infra = false",
|
||||
namespaceNodeSelector: "infra=true",
|
||||
podNodeSelector: map[string]string{"env": "test"},
|
||||
mergedNodeSelector: labels.Set{"infra": "true", "env": "test"},
|
||||
admit: true,
|
||||
testName: "TestNamespace and pod node selector, no conflicts",
|
||||
},
|
||||
{
|
||||
defaultNodeSelector: "env = test",
|
||||
namespaceNodeSelector: "infra=true",
|
||||
podNodeSelector: map[string]string{"infra": "false"},
|
||||
admit: false,
|
||||
testName: "Conflicting pod and namespace node selector, one label",
|
||||
},
|
||||
{
|
||||
defaultNodeSelector: "env=dev",
|
||||
namespaceNodeSelector: "infra=false, env = test",
|
||||
podNodeSelector: map[string]string{"env": "dev", "color": "blue"},
|
||||
admit: false,
|
||||
testName: "Conflicting pod and namespace node selector, multiple labels",
|
||||
},
|
||||
{
|
||||
defaultNodeSelector: "env=dev",
|
||||
namespaceNodeSelector: "infra=false, env = dev",
|
||||
whitelist: "env=dev, infra=false, color=blue",
|
||||
podNodeSelector: map[string]string{"env": "dev", "color": "blue"},
|
||||
mergedNodeSelector: labels.Set{"infra": "false", "env": "dev", "color": "blue"},
|
||||
admit: true,
|
||||
testName: "Merged pod node selectors satisfy the whitelist",
|
||||
},
|
||||
{
|
||||
defaultNodeSelector: "env=dev",
|
||||
namespaceNodeSelector: "infra=false, env = dev",
|
||||
whitelist: "env=dev, infra=true, color=blue",
|
||||
podNodeSelector: map[string]string{"env": "dev", "color": "blue"},
|
||||
admit: false,
|
||||
testName: "Merged pod node selectors conflict with the whitelist",
|
||||
},
|
||||
{
|
||||
defaultNodeSelector: "env=dev",
|
||||
ignoreTestNamespaceNodeSelector: true,
|
||||
whitelist: "env=prd",
|
||||
podNodeSelector: map[string]string{},
|
||||
admit: false,
|
||||
testName: "Default node selector conflict with the whitelist",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
if !test.ignoreTestNamespaceNodeSelector {
|
||||
namespace.ObjectMeta.Annotations = map[string]string{"scheduler.alpha.kubernetes.io/node-selector": test.namespaceNodeSelector}
|
||||
informerFactory.Core().InternalVersion().Namespaces().Informer().GetStore().Update(namespace)
|
||||
}
|
||||
handler.clusterNodeSelectors = make(map[string]string)
|
||||
handler.clusterNodeSelectors["clusterDefaultNodeSelector"] = test.defaultNodeSelector
|
||||
handler.clusterNodeSelectors[namespace.Name] = test.whitelist
|
||||
pod.Spec = api.PodSpec{NodeSelector: test.podNodeSelector}
|
||||
|
||||
err := handler.Admit(admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "testNamespace", namespace.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if test.admit && err != nil {
|
||||
t.Errorf("Test: %s, expected no error but got: %s", test.testName, err)
|
||||
} else if !test.admit && err == nil {
|
||||
t.Errorf("Test: %s, expected an error", test.testName)
|
||||
}
|
||||
if test.admit && !labels.Equals(test.mergedNodeSelector, labels.Set(pod.Spec.NodeSelector)) {
|
||||
t.Errorf("Test: %s, expected: %s but got: %s", test.testName, test.mergedNodeSelector, pod.Spec.NodeSelector)
|
||||
}
|
||||
err = handler.Validate(admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "testNamespace", namespace.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if test.admit && err != nil {
|
||||
t.Errorf("Test: %s, expected no error but got: %s", test.testName, err)
|
||||
} else if !test.admit && err == nil {
|
||||
t.Errorf("Test: %s, expected an error", test.testName)
|
||||
}
|
||||
|
||||
// handles update of uninitialized pod like it's newly created.
|
||||
err = handler.Admit(admission.NewAttributesRecord(pod, &oldPod, api.Kind("Pod").WithVersion("version"), "testNamespace", namespace.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
|
||||
if test.admit && err != nil {
|
||||
t.Errorf("Test: %s, expected no error but got: %s", test.testName, err)
|
||||
} else if !test.admit && err == nil {
|
||||
t.Errorf("Test: %s, expected an error", test.testName)
|
||||
}
|
||||
if test.admit && !labels.Equals(test.mergedNodeSelector, labels.Set(pod.Spec.NodeSelector)) {
|
||||
t.Errorf("Test: %s, expected: %s but got: %s", test.testName, test.mergedNodeSelector, pod.Spec.NodeSelector)
|
||||
}
|
||||
err = handler.Validate(admission.NewAttributesRecord(pod, &oldPod, api.Kind("Pod").WithVersion("version"), "testNamespace", namespace.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
|
||||
if test.admit && err != nil {
|
||||
t.Errorf("Test: %s, expected no error but got: %s", test.testName, err)
|
||||
} else if !test.admit && err == nil {
|
||||
t.Errorf("Test: %s, expected an error", test.testName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandles(t *testing.T) {
|
||||
for op, shouldHandle := range map[admission.Operation]bool{
|
||||
admission.Create: true,
|
||||
admission.Update: true,
|
||||
admission.Connect: false,
|
||||
admission.Delete: false,
|
||||
} {
|
||||
nodeEnvionment := NewPodNodeSelector(nil)
|
||||
if e, a := shouldHandle, nodeEnvionment.Handles(op); e != a {
|
||||
t.Errorf("%v: shouldHandle=%t, handles=%t", op, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIgnoreUpdatingInitializedPod(t *testing.T) {
|
||||
mockClient := &fake.Clientset{}
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
handler.SetReadyFunc(func() bool { return true })
|
||||
|
||||
podNodeSelector := map[string]string{"infra": "false"}
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "testPod", Namespace: "testNamespace"},
|
||||
Spec: api.PodSpec{NodeSelector: podNodeSelector},
|
||||
}
|
||||
// this conflicts with podNodeSelector
|
||||
namespaceNodeSelector := "infra=true"
|
||||
namespace := &api.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "testNamespace",
|
||||
Namespace: "",
|
||||
Annotations: map[string]string{"scheduler.alpha.kubernetes.io/node-selector": namespaceNodeSelector},
|
||||
},
|
||||
}
|
||||
err = informerFactory.Core().InternalVersion().Namespaces().Informer().GetStore().Update(namespace)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// if the update of initialized pod is not ignored, an error will be returned because the pod's nodeSelector conflicts with namespace's nodeSelector.
|
||||
err = handler.Admit(admission.NewAttributesRecord(pod, pod, api.Kind("Pod").WithVersion("version"), "testNamespace", namespace.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// newHandlerForTest returns the admission controller configured for testing.
|
||||
func newHandlerForTest(c clientset.Interface) (*podNodeSelector, informers.SharedInformerFactory, error) {
|
||||
f := informers.NewSharedInformerFactory(c, 5*time.Minute)
|
||||
handler := NewPodNodeSelector(nil)
|
||||
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil)
|
||||
pluginInitializer.Initialize(handler)
|
||||
err := admission.ValidateInitialization(handler)
|
||||
return handler, f, err
|
||||
}
|
||||
60
vendor/k8s.io/kubernetes/plugin/pkg/admission/podpreset/BUILD
generated
vendored
Normal file
60
vendor/k8s.io/kubernetes/plugin/pkg/admission/podpreset/BUILD
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/settings:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
|
||||
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
|
||||
"//pkg/client/listers/settings/internalversion:go_default_library",
|
||||
"//pkg/controller:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["admission.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/podpreset",
|
||||
deps = [
|
||||
"//pkg/api/legacyscheme:go_default_library",
|
||||
"//pkg/api/ref:go_default_library",
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/settings:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
|
||||
"//pkg/client/listers/settings/internalversion:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
410
vendor/k8s.io/kubernetes/plugin/pkg/admission/podpreset/admission.go
generated
vendored
Normal file
410
vendor/k8s.io/kubernetes/plugin/pkg/admission/podpreset/admission.go
generated
vendored
Normal file
@@ -0,0 +1,410 @@
|
||||
/*
|
||||
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 podpreset
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/api/ref"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/settings"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||
settingslisters "k8s.io/kubernetes/pkg/client/listers/settings/internalversion"
|
||||
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
)
|
||||
|
||||
const (
|
||||
annotationPrefix = "podpreset.admission.kubernetes.io"
|
||||
PluginName = "PodPreset"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
|
||||
return NewPlugin(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// podPresetPlugin is an implementation of admission.Interface.
|
||||
type podPresetPlugin struct {
|
||||
*admission.Handler
|
||||
client internalclientset.Interface
|
||||
|
||||
lister settingslisters.PodPresetLister
|
||||
}
|
||||
|
||||
var _ admission.MutationInterface = &podPresetPlugin{}
|
||||
var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&podPresetPlugin{})
|
||||
var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&podPresetPlugin{})
|
||||
|
||||
// NewPlugin creates a new pod preset admission plugin.
|
||||
func NewPlugin() *podPresetPlugin {
|
||||
return &podPresetPlugin{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
}
|
||||
}
|
||||
|
||||
func (plugin *podPresetPlugin) ValidateInitialization() error {
|
||||
if plugin.client == nil {
|
||||
return fmt.Errorf("%s requires a client", PluginName)
|
||||
}
|
||||
if plugin.lister == nil {
|
||||
return fmt.Errorf("%s requires a lister", PluginName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *podPresetPlugin) SetInternalKubeClientSet(client internalclientset.Interface) {
|
||||
a.client = client
|
||||
}
|
||||
|
||||
func (a *podPresetPlugin) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) {
|
||||
podPresetInformer := f.Settings().InternalVersion().PodPresets()
|
||||
a.lister = podPresetInformer.Lister()
|
||||
a.SetReadyFunc(podPresetInformer.Informer().HasSynced)
|
||||
}
|
||||
|
||||
// Admit injects a pod with the specific fields for each pod preset it matches.
|
||||
func (c *podPresetPlugin) Admit(a admission.Attributes) error {
|
||||
// Ignore all calls to subresources or resources other than pods.
|
||||
// Ignore all operations other than CREATE.
|
||||
if len(a.GetSubresource()) != 0 || a.GetResource().GroupResource() != api.Resource("pods") || a.GetOperation() != admission.Create {
|
||||
return nil
|
||||
}
|
||||
|
||||
pod, ok := a.GetObject().(*api.Pod)
|
||||
if !ok {
|
||||
return errors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
|
||||
}
|
||||
|
||||
if _, isMirrorPod := pod.Annotations[api.MirrorPodAnnotationKey]; isMirrorPod {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ignore if exclusion annotation is present
|
||||
if podAnnotations := pod.GetAnnotations(); podAnnotations != nil {
|
||||
glog.V(5).Infof("Looking at pod annotations, found: %v", podAnnotations)
|
||||
if podAnnotations[api.PodPresetOptOutAnnotationKey] == "true" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
list, err := c.lister.PodPresets(a.GetNamespace()).List(labels.Everything())
|
||||
if err != nil {
|
||||
return fmt.Errorf("listing pod presets failed: %v", err)
|
||||
}
|
||||
|
||||
matchingPPs, err := filterPodPresets(list, pod)
|
||||
if err != nil {
|
||||
return fmt.Errorf("filtering pod presets failed: %v", err)
|
||||
}
|
||||
|
||||
if len(matchingPPs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
presetNames := make([]string, len(matchingPPs))
|
||||
for i, pp := range matchingPPs {
|
||||
presetNames[i] = pp.GetName()
|
||||
}
|
||||
|
||||
// detect merge conflict
|
||||
err = safeToApplyPodPresetsOnPod(pod, matchingPPs)
|
||||
if err != nil {
|
||||
// conflict, ignore the error, but raise an event
|
||||
glog.Warningf("conflict occurred while applying podpresets: %s on pod: %v err: %v",
|
||||
strings.Join(presetNames, ","), pod.GetGenerateName(), err)
|
||||
return nil
|
||||
}
|
||||
|
||||
applyPodPresetsOnPod(pod, matchingPPs)
|
||||
|
||||
glog.Infof("applied podpresets: %s successfully on Pod: %+v ", strings.Join(presetNames, ","), pod.GetGenerateName())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// filterPodPresets returns list of PodPresets which match given Pod.
|
||||
func filterPodPresets(list []*settings.PodPreset, pod *api.Pod) ([]*settings.PodPreset, error) {
|
||||
var matchingPPs []*settings.PodPreset
|
||||
|
||||
for _, pp := range list {
|
||||
selector, err := metav1.LabelSelectorAsSelector(&pp.Spec.Selector)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("label selector conversion failed: %v for selector: %v", pp.Spec.Selector, err)
|
||||
}
|
||||
|
||||
// check if the pod labels match the selector
|
||||
if !selector.Matches(labels.Set(pod.Labels)) {
|
||||
continue
|
||||
}
|
||||
glog.V(4).Infof("PodPreset %s matches pod %s labels", pp.GetName(), pod.GetName())
|
||||
matchingPPs = append(matchingPPs, pp)
|
||||
}
|
||||
return matchingPPs, nil
|
||||
}
|
||||
|
||||
// safeToApplyPodPresetsOnPod determines if there is any conflict in information
|
||||
// injected by given PodPresets in the Pod.
|
||||
func safeToApplyPodPresetsOnPod(pod *api.Pod, podPresets []*settings.PodPreset) error {
|
||||
var errs []error
|
||||
|
||||
// volumes attribute is defined at the Pod level, so determine if volumes
|
||||
// injection is causing any conflict.
|
||||
if _, err := mergeVolumes(pod.Spec.Volumes, podPresets); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
for _, ctr := range pod.Spec.Containers {
|
||||
if err := safeToApplyPodPresetsOnContainer(&ctr, podPresets); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
// safeToApplyPodPresetsOnContainer determines if there is any conflict in
|
||||
// information injected by given PodPresets in the given container.
|
||||
func safeToApplyPodPresetsOnContainer(ctr *api.Container, podPresets []*settings.PodPreset) error {
|
||||
var errs []error
|
||||
// check if it is safe to merge env vars and volume mounts from given podpresets and
|
||||
// container's existing env vars.
|
||||
if _, err := mergeEnv(ctr.Env, podPresets); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if _, err := mergeVolumeMounts(ctr.VolumeMounts, podPresets); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
// mergeEnv merges a list of env vars with the env vars injected by given list podPresets.
|
||||
// It returns an error if it detects any conflict during the merge.
|
||||
func mergeEnv(envVars []api.EnvVar, podPresets []*settings.PodPreset) ([]api.EnvVar, error) {
|
||||
origEnv := map[string]api.EnvVar{}
|
||||
for _, v := range envVars {
|
||||
origEnv[v.Name] = v
|
||||
}
|
||||
|
||||
mergedEnv := make([]api.EnvVar, len(envVars))
|
||||
copy(mergedEnv, envVars)
|
||||
|
||||
var errs []error
|
||||
|
||||
for _, pp := range podPresets {
|
||||
for _, v := range pp.Spec.Env {
|
||||
found, ok := origEnv[v.Name]
|
||||
if !ok {
|
||||
// if we don't already have it append it and continue
|
||||
origEnv[v.Name] = v
|
||||
mergedEnv = append(mergedEnv, v)
|
||||
continue
|
||||
}
|
||||
|
||||
// make sure they are identical or throw an error
|
||||
if !reflect.DeepEqual(found, v) {
|
||||
errs = append(errs, fmt.Errorf("merging env for %s has a conflict on %s: \n%#v\ndoes not match\n%#v\n in container", pp.GetName(), v.Name, v, found))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := utilerrors.NewAggregate(errs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mergedEnv, err
|
||||
}
|
||||
|
||||
func mergeEnvFrom(envSources []api.EnvFromSource, podPresets []*settings.PodPreset) ([]api.EnvFromSource, error) {
|
||||
var mergedEnvFrom []api.EnvFromSource
|
||||
|
||||
mergedEnvFrom = append(mergedEnvFrom, envSources...)
|
||||
for _, pp := range podPresets {
|
||||
mergedEnvFrom = append(mergedEnvFrom, pp.Spec.EnvFrom...)
|
||||
}
|
||||
|
||||
return mergedEnvFrom, nil
|
||||
}
|
||||
|
||||
// mergeVolumeMounts merges given list of VolumeMounts with the volumeMounts
|
||||
// injected by given podPresets. It returns an error if it detects any conflict during the merge.
|
||||
func mergeVolumeMounts(volumeMounts []api.VolumeMount, podPresets []*settings.PodPreset) ([]api.VolumeMount, error) {
|
||||
|
||||
origVolumeMounts := map[string]api.VolumeMount{}
|
||||
volumeMountsByPath := map[string]api.VolumeMount{}
|
||||
for _, v := range volumeMounts {
|
||||
origVolumeMounts[v.Name] = v
|
||||
volumeMountsByPath[v.MountPath] = v
|
||||
}
|
||||
|
||||
mergedVolumeMounts := make([]api.VolumeMount, len(volumeMounts))
|
||||
copy(mergedVolumeMounts, volumeMounts)
|
||||
|
||||
var errs []error
|
||||
|
||||
for _, pp := range podPresets {
|
||||
for _, v := range pp.Spec.VolumeMounts {
|
||||
found, ok := origVolumeMounts[v.Name]
|
||||
if !ok {
|
||||
// if we don't already have it append it and continue
|
||||
origVolumeMounts[v.Name] = v
|
||||
mergedVolumeMounts = append(mergedVolumeMounts, v)
|
||||
} else {
|
||||
// make sure they are identical or throw an error
|
||||
// shall we throw an error for identical volumeMounts ?
|
||||
if !reflect.DeepEqual(found, v) {
|
||||
errs = append(errs, fmt.Errorf("merging volume mounts for %s has a conflict on %s: \n%#v\ndoes not match\n%#v\n in container", pp.GetName(), v.Name, v, found))
|
||||
}
|
||||
}
|
||||
|
||||
found, ok = volumeMountsByPath[v.MountPath]
|
||||
if !ok {
|
||||
// if we don't already have it append it and continue
|
||||
volumeMountsByPath[v.MountPath] = v
|
||||
} else {
|
||||
// make sure they are identical or throw an error
|
||||
if !reflect.DeepEqual(found, v) {
|
||||
errs = append(errs, fmt.Errorf("merging volume mounts for %s has a conflict on mount path %s: \n%#v\ndoes not match\n%#v\n in container", pp.GetName(), v.MountPath, v, found))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := utilerrors.NewAggregate(errs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mergedVolumeMounts, err
|
||||
}
|
||||
|
||||
// mergeVolumes merges given list of Volumes with the volumes injected by given
|
||||
// podPresets. It returns an error if it detects any conflict during the merge.
|
||||
func mergeVolumes(volumes []api.Volume, podPresets []*settings.PodPreset) ([]api.Volume, error) {
|
||||
origVolumes := map[string]api.Volume{}
|
||||
for _, v := range volumes {
|
||||
origVolumes[v.Name] = v
|
||||
}
|
||||
|
||||
mergedVolumes := make([]api.Volume, len(volumes))
|
||||
copy(mergedVolumes, volumes)
|
||||
|
||||
var errs []error
|
||||
|
||||
for _, pp := range podPresets {
|
||||
for _, v := range pp.Spec.Volumes {
|
||||
found, ok := origVolumes[v.Name]
|
||||
if !ok {
|
||||
// if we don't already have it append it and continue
|
||||
origVolumes[v.Name] = v
|
||||
mergedVolumes = append(mergedVolumes, v)
|
||||
continue
|
||||
}
|
||||
|
||||
// make sure they are identical or throw an error
|
||||
if !reflect.DeepEqual(found, v) {
|
||||
errs = append(errs, fmt.Errorf("merging volumes for %s has a conflict on %s: \n%#v\ndoes not match\n%#v\n in container", pp.GetName(), v.Name, v, found))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := utilerrors.NewAggregate(errs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(mergedVolumes) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return mergedVolumes, err
|
||||
}
|
||||
|
||||
func (c *podPresetPlugin) addEvent(pod *api.Pod, pip *settings.PodPreset, message string) {
|
||||
ref, err := ref.GetReference(legacyscheme.Scheme, pod)
|
||||
if err != nil {
|
||||
glog.Errorf("pip %s: get reference for pod %s failed: %v", pip.GetName(), pod.GetName(), err)
|
||||
return
|
||||
}
|
||||
|
||||
e := &api.Event{
|
||||
InvolvedObject: *ref,
|
||||
Message: message,
|
||||
Source: api.EventSource{
|
||||
Component: fmt.Sprintf("pip %s", pip.GetName()),
|
||||
},
|
||||
Type: "Warning",
|
||||
}
|
||||
|
||||
if _, err := c.client.Core().Events(pod.GetNamespace()).Create(e); err != nil {
|
||||
glog.Errorf("pip %s: creating pod event failed: %v", pip.GetName(), err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// applyPodPresetsOnPod updates the PodSpec with merged information from all the
|
||||
// applicable PodPresets. It ignores the errors of merge functions because merge
|
||||
// errors have already been checked in safeToApplyPodPresetsOnPod function.
|
||||
func applyPodPresetsOnPod(pod *api.Pod, podPresets []*settings.PodPreset) {
|
||||
if len(podPresets) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
volumes, _ := mergeVolumes(pod.Spec.Volumes, podPresets)
|
||||
pod.Spec.Volumes = volumes
|
||||
|
||||
for i, ctr := range pod.Spec.Containers {
|
||||
applyPodPresetsOnContainer(&ctr, podPresets)
|
||||
pod.Spec.Containers[i] = ctr
|
||||
}
|
||||
|
||||
// add annotation
|
||||
if pod.ObjectMeta.Annotations == nil {
|
||||
pod.ObjectMeta.Annotations = map[string]string{}
|
||||
}
|
||||
|
||||
for _, pp := range podPresets {
|
||||
pod.ObjectMeta.Annotations[fmt.Sprintf("%s/podpreset-%s", annotationPrefix, pp.GetName())] = pp.GetResourceVersion()
|
||||
}
|
||||
}
|
||||
|
||||
// applyPodPresetsOnContainer injects envVars, VolumeMounts and envFrom from
|
||||
// given podPresets in to the given container. It ignores conflict errors
|
||||
// because it assumes those have been checked already by the caller.
|
||||
func applyPodPresetsOnContainer(ctr *api.Container, podPresets []*settings.PodPreset) {
|
||||
envVars, _ := mergeEnv(ctr.Env, podPresets)
|
||||
ctr.Env = envVars
|
||||
|
||||
volumeMounts, _ := mergeVolumeMounts(ctr.VolumeMounts, podPresets)
|
||||
ctr.VolumeMounts = volumeMounts
|
||||
|
||||
envFrom, _ := mergeEnvFrom(ctr.EnvFrom, podPresets)
|
||||
ctr.EnvFrom = envFrom
|
||||
}
|
||||
830
vendor/k8s.io/kubernetes/plugin/pkg/admission/podpreset/admission_test.go
generated
vendored
Normal file
830
vendor/k8s.io/kubernetes/plugin/pkg/admission/podpreset/admission_test.go
generated
vendored
Normal file
@@ -0,0 +1,830 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package podpreset
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
kadmission "k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/settings"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||
settingslisters "k8s.io/kubernetes/pkg/client/listers/settings/internalversion"
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
)
|
||||
|
||||
func TestMergeEnv(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
orig []api.EnvVar
|
||||
mod []api.EnvVar
|
||||
result []api.EnvVar
|
||||
shouldFail bool
|
||||
}{
|
||||
"empty original": {
|
||||
mod: []api.EnvVar{{Name: "abc", Value: "value2"}, {Name: "ABC", Value: "value3"}},
|
||||
result: []api.EnvVar{{Name: "abc", Value: "value2"}, {Name: "ABC", Value: "value3"}},
|
||||
shouldFail: false,
|
||||
},
|
||||
"good merge": {
|
||||
orig: []api.EnvVar{{Name: "abcd", Value: "value2"}, {Name: "hello", Value: "value3"}},
|
||||
mod: []api.EnvVar{{Name: "abc", Value: "value2"}, {Name: "ABC", Value: "value3"}},
|
||||
result: []api.EnvVar{{Name: "abcd", Value: "value2"}, {Name: "hello", Value: "value3"}, {Name: "abc", Value: "value2"}, {Name: "ABC", Value: "value3"}},
|
||||
shouldFail: false,
|
||||
},
|
||||
"conflict": {
|
||||
orig: []api.EnvVar{{Name: "abc", Value: "value3"}},
|
||||
mod: []api.EnvVar{{Name: "abc", Value: "value2"}, {Name: "ABC", Value: "value3"}},
|
||||
shouldFail: true,
|
||||
},
|
||||
"one is exact same": {
|
||||
orig: []api.EnvVar{{Name: "abc", Value: "value2"}, {Name: "hello", Value: "value3"}},
|
||||
mod: []api.EnvVar{{Name: "abc", Value: "value2"}, {Name: "ABC", Value: "value3"}},
|
||||
result: []api.EnvVar{{Name: "abc", Value: "value2"}, {Name: "hello", Value: "value3"}, {Name: "ABC", Value: "value3"}},
|
||||
shouldFail: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
result, err := mergeEnv(
|
||||
test.orig,
|
||||
[]*settings.PodPreset{{Spec: settings.PodPresetSpec{Env: test.mod}}},
|
||||
)
|
||||
if test.shouldFail && err == nil {
|
||||
t.Fatalf("expected test %q to fail but got nil", name)
|
||||
}
|
||||
if !test.shouldFail && err != nil {
|
||||
t.Fatalf("test %q failed: %v", name, err)
|
||||
}
|
||||
if !reflect.DeepEqual(test.result, result) {
|
||||
t.Fatalf("results were not equal for test %q: got %#v; expected: %#v", name, result, test.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeEnvFrom(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
orig []api.EnvFromSource
|
||||
mod []api.EnvFromSource
|
||||
result []api.EnvFromSource
|
||||
shouldFail bool
|
||||
}{
|
||||
"empty original": {
|
||||
mod: []api.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Prefix: "pre_",
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []api.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Prefix: "pre_",
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
},
|
||||
shouldFail: false,
|
||||
},
|
||||
"good merge": {
|
||||
orig: []api.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "thing"},
|
||||
},
|
||||
},
|
||||
},
|
||||
mod: []api.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Prefix: "pre_",
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []api.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "thing"},
|
||||
},
|
||||
},
|
||||
{
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Prefix: "pre_",
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
},
|
||||
shouldFail: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
result, err := mergeEnvFrom(
|
||||
test.orig,
|
||||
[]*settings.PodPreset{{Spec: settings.PodPresetSpec{EnvFrom: test.mod}}},
|
||||
)
|
||||
if test.shouldFail && err == nil {
|
||||
t.Fatalf("expected test %q to fail but got nil", name)
|
||||
}
|
||||
if !test.shouldFail && err != nil {
|
||||
t.Fatalf("test %q failed: %v", name, err)
|
||||
}
|
||||
if !reflect.DeepEqual(test.result, result) {
|
||||
t.Fatalf("results were not equal for test %q: got %#v; expected: %#v", name, result, test.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeVolumeMounts(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
orig []api.VolumeMount
|
||||
mod []api.VolumeMount
|
||||
result []api.VolumeMount
|
||||
shouldFail bool
|
||||
}{
|
||||
"empty original": {
|
||||
mod: []api.VolumeMount{
|
||||
{
|
||||
Name: "simply-mounted-volume",
|
||||
MountPath: "/opt/",
|
||||
},
|
||||
},
|
||||
result: []api.VolumeMount{
|
||||
{
|
||||
Name: "simply-mounted-volume",
|
||||
MountPath: "/opt/",
|
||||
},
|
||||
},
|
||||
shouldFail: false,
|
||||
},
|
||||
"good merge": {
|
||||
mod: []api.VolumeMount{
|
||||
{
|
||||
Name: "simply-mounted-volume",
|
||||
MountPath: "/opt/",
|
||||
},
|
||||
},
|
||||
orig: []api.VolumeMount{
|
||||
{
|
||||
Name: "etc-volume",
|
||||
MountPath: "/etc/",
|
||||
},
|
||||
},
|
||||
result: []api.VolumeMount{
|
||||
{
|
||||
Name: "etc-volume",
|
||||
MountPath: "/etc/",
|
||||
},
|
||||
{
|
||||
Name: "simply-mounted-volume",
|
||||
MountPath: "/opt/",
|
||||
},
|
||||
},
|
||||
shouldFail: false,
|
||||
},
|
||||
"conflict": {
|
||||
mod: []api.VolumeMount{
|
||||
{
|
||||
Name: "simply-mounted-volume",
|
||||
MountPath: "/opt/",
|
||||
},
|
||||
{
|
||||
Name: "etc-volume",
|
||||
MountPath: "/things/",
|
||||
},
|
||||
},
|
||||
orig: []api.VolumeMount{
|
||||
{
|
||||
Name: "etc-volume",
|
||||
MountPath: "/etc/",
|
||||
},
|
||||
},
|
||||
shouldFail: true,
|
||||
},
|
||||
"conflict on mount path": {
|
||||
mod: []api.VolumeMount{
|
||||
{
|
||||
Name: "simply-mounted-volume",
|
||||
MountPath: "/opt/",
|
||||
},
|
||||
{
|
||||
Name: "things-volume",
|
||||
MountPath: "/etc/",
|
||||
},
|
||||
},
|
||||
orig: []api.VolumeMount{
|
||||
{
|
||||
Name: "etc-volume",
|
||||
MountPath: "/etc/",
|
||||
},
|
||||
},
|
||||
shouldFail: true,
|
||||
},
|
||||
"one is exact same": {
|
||||
mod: []api.VolumeMount{
|
||||
{
|
||||
Name: "simply-mounted-volume",
|
||||
MountPath: "/opt/",
|
||||
},
|
||||
{
|
||||
Name: "etc-volume",
|
||||
MountPath: "/etc/",
|
||||
},
|
||||
},
|
||||
orig: []api.VolumeMount{
|
||||
{
|
||||
Name: "etc-volume",
|
||||
MountPath: "/etc/",
|
||||
},
|
||||
},
|
||||
result: []api.VolumeMount{
|
||||
{
|
||||
Name: "etc-volume",
|
||||
MountPath: "/etc/",
|
||||
},
|
||||
{
|
||||
Name: "simply-mounted-volume",
|
||||
MountPath: "/opt/",
|
||||
},
|
||||
},
|
||||
shouldFail: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
result, err := mergeVolumeMounts(
|
||||
test.orig,
|
||||
[]*settings.PodPreset{{Spec: settings.PodPresetSpec{VolumeMounts: test.mod}}},
|
||||
)
|
||||
if test.shouldFail && err == nil {
|
||||
t.Fatalf("expected test %q to fail but got nil", name)
|
||||
}
|
||||
if !test.shouldFail && err != nil {
|
||||
t.Fatalf("test %q failed: %v", name, err)
|
||||
}
|
||||
if !reflect.DeepEqual(test.result, result) {
|
||||
t.Fatalf("results were not equal for test %q: got %#v; expected: %#v", name, result, test.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeVolumes(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
orig []api.Volume
|
||||
mod []api.Volume
|
||||
result []api.Volume
|
||||
shouldFail bool
|
||||
}{
|
||||
"empty original": {
|
||||
mod: []api.Volume{
|
||||
{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol2", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
},
|
||||
result: []api.Volume{
|
||||
{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol2", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
},
|
||||
shouldFail: false,
|
||||
},
|
||||
"good merge": {
|
||||
orig: []api.Volume{
|
||||
{Name: "vol3", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol4", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
},
|
||||
mod: []api.Volume{
|
||||
{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol2", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
},
|
||||
result: []api.Volume{
|
||||
{Name: "vol3", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol4", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol2", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
},
|
||||
shouldFail: false,
|
||||
},
|
||||
"conflict": {
|
||||
orig: []api.Volume{
|
||||
{Name: "vol3", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol4", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
},
|
||||
mod: []api.Volume{
|
||||
{Name: "vol3", VolumeSource: api.VolumeSource{HostPath: &api.HostPathVolumeSource{Path: "/etc/apparmor.d"}}},
|
||||
{Name: "vol2", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
},
|
||||
shouldFail: true,
|
||||
},
|
||||
"one is exact same": {
|
||||
orig: []api.Volume{
|
||||
{Name: "vol3", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol4", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
},
|
||||
mod: []api.Volume{
|
||||
{Name: "vol3", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol2", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
},
|
||||
result: []api.Volume{
|
||||
{Name: "vol3", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol4", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol2", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
},
|
||||
shouldFail: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
result, err := mergeVolumes(
|
||||
test.orig,
|
||||
[]*settings.PodPreset{{Spec: settings.PodPresetSpec{Volumes: test.mod}}},
|
||||
)
|
||||
if test.shouldFail && err == nil {
|
||||
t.Fatalf("expected test %q to fail but got nil", name)
|
||||
}
|
||||
if !test.shouldFail && err != nil {
|
||||
t.Fatalf("test %q failed: %v", name, err)
|
||||
}
|
||||
if !reflect.DeepEqual(test.result, result) {
|
||||
t.Fatalf("results were not equal for test %q: got %#v; expected: %#v", name, result, test.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewTestAdmission provides an admission plugin with test implementations of internal structs. It uses
|
||||
// an authorizer that always returns true.
|
||||
func NewTestAdmission(lister settingslisters.PodPresetLister, objects ...runtime.Object) kadmission.MutationInterface {
|
||||
// Build a test client that the admission plugin can use to look up the service account missing from its cache
|
||||
client := fake.NewSimpleClientset(objects...)
|
||||
|
||||
return &podPresetPlugin{
|
||||
client: client,
|
||||
Handler: kadmission.NewHandler(kadmission.Create),
|
||||
lister: lister,
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdmitConflictWithDifferentNamespaceShouldDoNothing(t *testing.T) {
|
||||
containerName := "container"
|
||||
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mypod",
|
||||
Namespace: "namespace",
|
||||
Labels: map[string]string{
|
||||
"security": "S2",
|
||||
},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: containerName,
|
||||
Env: []api.EnvVar{{Name: "abc", Value: "value2"}, {Name: "ABC", Value: "value3"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pip := &settings.PodPreset{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "hello",
|
||||
Namespace: "othernamespace",
|
||||
},
|
||||
Spec: settings.PodPresetSpec{
|
||||
Selector: metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Env: []api.EnvVar{{Name: "abc", Value: "value"}, {Name: "ABC", Value: "value"}},
|
||||
},
|
||||
}
|
||||
|
||||
err := admitPod(pod, pip)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdmitConflictWithNonMatchingLabelsShouldNotError(t *testing.T) {
|
||||
containerName := "container"
|
||||
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mypod",
|
||||
Namespace: "namespace",
|
||||
Labels: map[string]string{
|
||||
"security": "S2",
|
||||
},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: containerName,
|
||||
Env: []api.EnvVar{{Name: "abc", Value: "value2"}, {Name: "ABC", Value: "value3"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pip := &settings.PodPreset{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "hello",
|
||||
Namespace: "namespace",
|
||||
},
|
||||
Spec: settings.PodPresetSpec{
|
||||
Selector: metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S3"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Env: []api.EnvVar{{Name: "abc", Value: "value"}, {Name: "ABC", Value: "value"}},
|
||||
},
|
||||
}
|
||||
|
||||
err := admitPod(pod, pip)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdmitConflictShouldNotModifyPod(t *testing.T) {
|
||||
containerName := "container"
|
||||
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mypod",
|
||||
Namespace: "namespace",
|
||||
Labels: map[string]string{
|
||||
"security": "S2",
|
||||
},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: containerName,
|
||||
Env: []api.EnvVar{{Name: "abc", Value: "value2"}, {Name: "ABC", Value: "value3"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
origPod := *pod
|
||||
|
||||
pip := &settings.PodPreset{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "hello",
|
||||
Namespace: "namespace",
|
||||
},
|
||||
Spec: settings.PodPresetSpec{
|
||||
Selector: metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Env: []api.EnvVar{{Name: "abc", Value: "value"}, {Name: "ABC", Value: "value"}},
|
||||
},
|
||||
}
|
||||
|
||||
err := admitPod(pod, pip)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(&origPod, pod) {
|
||||
t.Fatalf("pod should not get modified in case of conflict origPod: %+v got: %+v", &origPod, pod)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdmit(t *testing.T) {
|
||||
containerName := "container"
|
||||
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mypod",
|
||||
Namespace: "namespace",
|
||||
Labels: map[string]string{
|
||||
"security": "S2",
|
||||
},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: containerName,
|
||||
Env: []api.EnvVar{{Name: "abc", Value: "value2"}, {Name: "ABCD", Value: "value3"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pip := &settings.PodPreset{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "hello",
|
||||
Namespace: "namespace",
|
||||
},
|
||||
Spec: settings.PodPresetSpec{
|
||||
Selector: metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}},
|
||||
Env: []api.EnvVar{{Name: "abcd", Value: "value"}, {Name: "ABC", Value: "value"}},
|
||||
EnvFrom: []api.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Prefix: "pre_",
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := admitPod(pod, pip)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdmitMirrorPod(t *testing.T) {
|
||||
containerName := "container"
|
||||
|
||||
mirrorPod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mypod",
|
||||
Namespace: "namespace",
|
||||
Labels: map[string]string{
|
||||
"security": "S2",
|
||||
},
|
||||
Annotations: map[string]string{api.MirrorPodAnnotationKey: "mirror"},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: containerName,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pip := &settings.PodPreset{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "hello",
|
||||
Namespace: "namespace",
|
||||
},
|
||||
Spec: settings.PodPresetSpec{
|
||||
Selector: metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}},
|
||||
Env: []api.EnvVar{{Name: "abcd", Value: "value"}, {Name: "ABC", Value: "value"}},
|
||||
EnvFrom: []api.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Prefix: "pre_",
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := admitPod(mirrorPod, pip); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
container := mirrorPod.Spec.Containers[0]
|
||||
if len(mirrorPod.Spec.Volumes) != 0 ||
|
||||
len(container.VolumeMounts) != 0 ||
|
||||
len(container.Env) != 0 ||
|
||||
len(container.EnvFrom) != 0 {
|
||||
t.Fatalf("mirror pod is updated by PodPreset admission:\n\tVolumes got %d, expected 0\n\tVolumeMounts go %d, expected 0\n\tEnv got, %d expected 0\n\tEnvFrom got %d, expected 0", len(mirrorPod.Spec.Volumes), len(container.VolumeMounts), len(container.Env), len(container.EnvFrom))
|
||||
}
|
||||
}
|
||||
|
||||
func TestExclusionNoAdmit(t *testing.T) {
|
||||
containerName := "container"
|
||||
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mypod",
|
||||
Namespace: "namespace",
|
||||
Labels: map[string]string{
|
||||
"security": "S2",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
api.PodPresetOptOutAnnotationKey: "true",
|
||||
},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: containerName,
|
||||
Env: []api.EnvVar{{Name: "abc", Value: "value2"}, {Name: "ABCD", Value: "value3"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pip := &settings.PodPreset{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "hello",
|
||||
Namespace: "namespace",
|
||||
},
|
||||
Spec: settings.PodPresetSpec{
|
||||
Selector: metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}},
|
||||
Env: []api.EnvVar{{Name: "abcd", Value: "value"}, {Name: "ABC", Value: "value"}},
|
||||
EnvFrom: []api.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Prefix: "pre_",
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
originalPod := pod.DeepCopy()
|
||||
err := admitPod(pod, pip)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// verify PodSpec has not been mutated
|
||||
if !reflect.DeepEqual(pod, originalPod) {
|
||||
t.Fatalf("Expected pod spec of '%v' to be unchanged", pod.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdmitEmptyPodNamespace(t *testing.T) {
|
||||
containerName := "container"
|
||||
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mypod",
|
||||
Labels: map[string]string{
|
||||
"security": "S2",
|
||||
},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: containerName,
|
||||
Env: []api.EnvVar{{Name: "abc", Value: "value2"}, {Name: "ABCD", Value: "value3"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pip := &settings.PodPreset{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "hello",
|
||||
Namespace: "different", // (pod will be submitted to namespace 'namespace')
|
||||
},
|
||||
Spec: settings.PodPresetSpec{
|
||||
Selector: metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}},
|
||||
Env: []api.EnvVar{{Name: "abcd", Value: "value"}, {Name: "ABC", Value: "value"}},
|
||||
EnvFrom: []api.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Prefix: "pre_",
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
originalPod := pod.DeepCopy()
|
||||
err := admitPod(pod, pip)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// verify PodSpec has not been mutated
|
||||
if !reflect.DeepEqual(pod, originalPod) {
|
||||
t.Fatalf("pod should not get modified in case of emptyNamespace origPod: %+v got: %+v", originalPod, pod)
|
||||
}
|
||||
}
|
||||
|
||||
func admitPod(pod *api.Pod, pip *settings.PodPreset) error {
|
||||
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
|
||||
store := informerFactory.Settings().InternalVersion().PodPresets().Informer().GetStore()
|
||||
store.Add(pip)
|
||||
plugin := NewTestAdmission(informerFactory.Settings().InternalVersion().PodPresets().Lister())
|
||||
attrs := kadmission.NewAttributesRecord(
|
||||
pod,
|
||||
nil,
|
||||
api.Kind("Pod").WithVersion("version"),
|
||||
"namespace",
|
||||
"",
|
||||
api.Resource("pods").WithVersion("version"),
|
||||
"",
|
||||
kadmission.Create,
|
||||
&user.DefaultInfo{},
|
||||
)
|
||||
|
||||
err := plugin.Admit(attrs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
75
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/BUILD
generated
vendored
Normal file
75
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/BUILD
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
|
||||
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//pkg/scheduler/algorithm:go_default_library",
|
||||
"//pkg/util/tolerations:go_default_library",
|
||||
"//plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"admission.go",
|
||||
"config.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/core/helper/qos:go_default_library",
|
||||
"//pkg/apis/core/v1:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
|
||||
"//pkg/client/listers/core/internalversion:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//pkg/kubeapiserver/admission/util:go_default_library",
|
||||
"//pkg/scheduler/algorithm:go_default_library",
|
||||
"//pkg/util/tolerations:go_default_library",
|
||||
"//plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction:go_default_library",
|
||||
"//plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/install:go_default_library",
|
||||
"//plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1:go_default_library",
|
||||
"//plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/validation:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1: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",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
295
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/admission.go
generated
vendored
Normal file
295
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/admission.go
generated
vendored
Normal file
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
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 podtolerationrestriction
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
qoshelper "k8s.io/kubernetes/pkg/apis/core/helper/qos"
|
||||
k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||
corelisters "k8s.io/kubernetes/pkg/client/listers/core/internalversion"
|
||||
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
"k8s.io/kubernetes/pkg/kubeapiserver/admission/util"
|
||||
"k8s.io/kubernetes/pkg/scheduler/algorithm"
|
||||
"k8s.io/kubernetes/pkg/util/tolerations"
|
||||
pluginapi "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction"
|
||||
)
|
||||
|
||||
const PluginName = "PodTolerationRestriction"
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
|
||||
pluginConfig, err := loadConfiguration(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewPodTolerationsPlugin(pluginConfig), nil
|
||||
})
|
||||
}
|
||||
|
||||
// The annotation keys for default and whitelist of tolerations
|
||||
const (
|
||||
NSDefaultTolerations string = "scheduler.alpha.kubernetes.io/defaultTolerations"
|
||||
NSWLTolerations string = "scheduler.alpha.kubernetes.io/tolerationsWhitelist"
|
||||
)
|
||||
|
||||
var _ admission.MutationInterface = &podTolerationsPlugin{}
|
||||
var _ admission.ValidationInterface = &podTolerationsPlugin{}
|
||||
var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&podTolerationsPlugin{})
|
||||
|
||||
type podTolerationsPlugin struct {
|
||||
*admission.Handler
|
||||
client clientset.Interface
|
||||
namespaceLister corelisters.NamespaceLister
|
||||
pluginConfig *pluginapi.Configuration
|
||||
}
|
||||
|
||||
// This plugin first verifies any conflict between a pod's tolerations and
|
||||
// its namespace's tolerations, and rejects the pod if there's a conflict.
|
||||
// If there's no conflict, the pod's tolerations are merged with its namespace's
|
||||
// toleration. Resulting pod's tolerations are verified against its namespace's
|
||||
// whitelist of tolerations. If the verification is successful, the pod is admitted
|
||||
// otherwise rejected. If a namespace does not have associated default or whitelist
|
||||
// of tolerations, then cluster level default or whitelist of tolerations are used
|
||||
// instead if specified. Tolerations to a namespace are assigned via
|
||||
// scheduler.alpha.kubernetes.io/defaultTolerations and scheduler.alpha.kubernetes.io/tolerationsWhitelist
|
||||
// annotations keys.
|
||||
func (p *podTolerationsPlugin) Admit(a admission.Attributes) error {
|
||||
if shouldIgnore(a) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !p.WaitForReady() {
|
||||
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
|
||||
}
|
||||
|
||||
pod := a.GetObject().(*api.Pod)
|
||||
var finalTolerations []api.Toleration
|
||||
updateUninitialized, err := util.IsUpdatingUninitializedObject(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if a.GetOperation() == admission.Create || updateUninitialized {
|
||||
ts, err := p.getNamespaceDefaultTolerations(a.GetNamespace())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the namespace has not specified its default tolerations,
|
||||
// fall back to cluster's default tolerations.
|
||||
if ts == nil {
|
||||
ts = p.pluginConfig.Default
|
||||
}
|
||||
|
||||
if len(ts) > 0 {
|
||||
if len(pod.Spec.Tolerations) > 0 {
|
||||
if tolerations.IsConflict(ts, pod.Spec.Tolerations) {
|
||||
return fmt.Errorf("namespace tolerations and pod tolerations conflict")
|
||||
}
|
||||
|
||||
// modified pod tolerations = namespace tolerations + current pod tolerations
|
||||
finalTolerations = tolerations.MergeTolerations(ts, pod.Spec.Tolerations)
|
||||
} else {
|
||||
finalTolerations = ts
|
||||
|
||||
}
|
||||
} else {
|
||||
finalTolerations = pod.Spec.Tolerations
|
||||
}
|
||||
} else {
|
||||
finalTolerations = pod.Spec.Tolerations
|
||||
}
|
||||
|
||||
if qoshelper.GetPodQOS(pod) != api.PodQOSBestEffort {
|
||||
finalTolerations = tolerations.MergeTolerations(finalTolerations, []api.Toleration{
|
||||
{
|
||||
Key: algorithm.TaintNodeMemoryPressure,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoSchedule,
|
||||
},
|
||||
})
|
||||
}
|
||||
pod.Spec.Tolerations = finalTolerations
|
||||
|
||||
return p.Validate(a)
|
||||
}
|
||||
func (p *podTolerationsPlugin) Validate(a admission.Attributes) error {
|
||||
if shouldIgnore(a) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !p.WaitForReady() {
|
||||
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
|
||||
}
|
||||
|
||||
// whitelist verification.
|
||||
pod := a.GetObject().(*api.Pod)
|
||||
if len(pod.Spec.Tolerations) > 0 {
|
||||
whitelist, err := p.getNamespaceTolerationsWhitelist(a.GetNamespace())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the namespace has not specified its tolerations whitelist,
|
||||
// fall back to cluster's whitelist of tolerations.
|
||||
if whitelist == nil {
|
||||
whitelist = p.pluginConfig.Whitelist
|
||||
}
|
||||
|
||||
if len(whitelist) > 0 {
|
||||
// check if the merged pod tolerations satisfy its namespace whitelist
|
||||
if !tolerations.VerifyAgainstWhitelist(pod.Spec.Tolerations, whitelist) {
|
||||
return fmt.Errorf("pod tolerations (possibly merged with namespace default tolerations) conflict with its namespace whitelist")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func shouldIgnore(a admission.Attributes) bool {
|
||||
resource := a.GetResource().GroupResource()
|
||||
if resource != api.Resource("pods") {
|
||||
return true
|
||||
}
|
||||
if a.GetSubresource() != "" {
|
||||
// only run the checks below on pods proper and not subresources
|
||||
return true
|
||||
}
|
||||
|
||||
obj := a.GetObject()
|
||||
_, ok := obj.(*api.Pod)
|
||||
if !ok {
|
||||
glog.Errorf("expected pod but got %s", a.GetKind().Kind)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func NewPodTolerationsPlugin(pluginConfig *pluginapi.Configuration) *podTolerationsPlugin {
|
||||
return &podTolerationsPlugin{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
pluginConfig: pluginConfig,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *podTolerationsPlugin) SetInternalKubeClientSet(client clientset.Interface) {
|
||||
a.client = client
|
||||
}
|
||||
|
||||
func (p *podTolerationsPlugin) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) {
|
||||
namespaceInformer := f.Core().InternalVersion().Namespaces()
|
||||
p.namespaceLister = namespaceInformer.Lister()
|
||||
p.SetReadyFunc(namespaceInformer.Informer().HasSynced)
|
||||
|
||||
}
|
||||
|
||||
func (p *podTolerationsPlugin) ValidateInitialization() error {
|
||||
if p.namespaceLister == nil {
|
||||
return fmt.Errorf("missing namespaceLister")
|
||||
}
|
||||
if p.client == nil {
|
||||
return fmt.Errorf("missing client")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// in exceptional cases, this can result in two live calls, but once the cache catches up, that will stop.
|
||||
func (p *podTolerationsPlugin) getNamespace(nsName string) (*api.Namespace, error) {
|
||||
namespace, err := p.namespaceLister.Get(nsName)
|
||||
if errors.IsNotFound(err) {
|
||||
// in case of latency in our caches, make a call direct to storage to verify that it truly exists or not
|
||||
namespace, err = p.client.Core().Namespaces().Get(nsName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
return nil, errors.NewInternalError(err)
|
||||
}
|
||||
} else if err != nil {
|
||||
return nil, errors.NewInternalError(err)
|
||||
}
|
||||
|
||||
return namespace, nil
|
||||
}
|
||||
|
||||
func (p *podTolerationsPlugin) getNamespaceDefaultTolerations(nsName string) ([]api.Toleration, error) {
|
||||
ns, err := p.getNamespace(nsName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return extractNSTolerations(ns, NSDefaultTolerations)
|
||||
}
|
||||
|
||||
func (p *podTolerationsPlugin) getNamespaceTolerationsWhitelist(nsName string) ([]api.Toleration, error) {
|
||||
ns, err := p.getNamespace(nsName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return extractNSTolerations(ns, NSWLTolerations)
|
||||
}
|
||||
|
||||
// extractNSTolerations extracts default or whitelist of tolerations from
|
||||
// following namespace annotations keys: "scheduler.alpha.kubernetes.io/defaultTolerations"
|
||||
// and "scheduler.alpha.kubernetes.io/tolerationsWhitelist". If these keys are
|
||||
// unset (nil), extractNSTolerations returns nil. If the value to these
|
||||
// keys are set to empty, an empty toleration is returned, otherwise
|
||||
// configured tolerations are returned.
|
||||
func extractNSTolerations(ns *api.Namespace, key string) ([]api.Toleration, error) {
|
||||
// if a namespace does not have any annotations
|
||||
if len(ns.Annotations) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// if NSWLTolerations or NSDefaultTolerations does not exist
|
||||
if _, ok := ns.Annotations[key]; !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// if value is set to empty
|
||||
if len(ns.Annotations[key]) == 0 {
|
||||
return []api.Toleration{}, nil
|
||||
}
|
||||
|
||||
var v1Tolerations []v1.Toleration
|
||||
err := json.Unmarshal([]byte(ns.Annotations[key]), &v1Tolerations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ts := make([]api.Toleration, len(v1Tolerations))
|
||||
for i := range v1Tolerations {
|
||||
if err := k8s_api_v1.Convert_v1_Toleration_To_core_Toleration(&v1Tolerations[i], &ts[i], nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return ts, nil
|
||||
}
|
||||
370
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/admission_test.go
generated
vendored
Normal file
370
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/admission_test.go
generated
vendored
Normal file
@@ -0,0 +1,370 @@
|
||||
/*
|
||||
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 podtolerationrestriction
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||
kubeadmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
"k8s.io/kubernetes/pkg/scheduler/algorithm"
|
||||
"k8s.io/kubernetes/pkg/util/tolerations"
|
||||
pluginapi "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction"
|
||||
)
|
||||
|
||||
// TestPodAdmission verifies various scenarios involving pod/namespace tolerations
|
||||
func TestPodAdmission(t *testing.T) {
|
||||
|
||||
CPU1000m := resource.MustParse("1000m")
|
||||
CPU500m := resource.MustParse("500m")
|
||||
|
||||
burstablePod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "testPod", Namespace: "testNamespace"},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: "test",
|
||||
Resources: api.ResourceRequirements{
|
||||
Limits: api.ResourceList{api.ResourceCPU: CPU1000m},
|
||||
Requests: api.ResourceList{api.ResourceCPU: CPU500m},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
guaranteedPod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "testPod", Namespace: "testNamespace"},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: "test",
|
||||
Resources: api.ResourceRequirements{
|
||||
Limits: api.ResourceList{api.ResourceCPU: CPU1000m},
|
||||
Requests: api.ResourceList{api.ResourceCPU: CPU1000m},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
bestEffortPod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "testPod", Namespace: "testNamespace"},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := utilfeature.DefaultFeatureGate.Set("TaintNodesByCondition=true"); err != nil {
|
||||
t.Errorf("Failed to enable TaintByCondition feature: %v.", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
pod *api.Pod
|
||||
defaultClusterTolerations []api.Toleration
|
||||
namespaceTolerations []api.Toleration
|
||||
whitelist []api.Toleration
|
||||
clusterWhitelist []api.Toleration
|
||||
podTolerations []api.Toleration
|
||||
mergedTolerations []api.Toleration
|
||||
admit bool
|
||||
testName string
|
||||
}{
|
||||
{
|
||||
pod: bestEffortPod,
|
||||
defaultClusterTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
namespaceTolerations: nil,
|
||||
podTolerations: []api.Toleration{},
|
||||
mergedTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
admit: true,
|
||||
testName: "default cluster tolerations with empty pod tolerations and nil namespace tolerations",
|
||||
},
|
||||
{
|
||||
pod: bestEffortPod,
|
||||
defaultClusterTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
namespaceTolerations: []api.Toleration{},
|
||||
podTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
mergedTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
admit: true,
|
||||
testName: "default cluster tolerations with pod tolerations specified",
|
||||
},
|
||||
{
|
||||
pod: bestEffortPod,
|
||||
defaultClusterTolerations: []api.Toleration{},
|
||||
namespaceTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
podTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
mergedTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
admit: true,
|
||||
testName: "namespace tolerations",
|
||||
},
|
||||
{
|
||||
pod: bestEffortPod,
|
||||
defaultClusterTolerations: []api.Toleration{},
|
||||
namespaceTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
podTolerations: []api.Toleration{},
|
||||
mergedTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
admit: true,
|
||||
testName: "no pod tolerations",
|
||||
},
|
||||
{
|
||||
pod: bestEffortPod,
|
||||
defaultClusterTolerations: []api.Toleration{},
|
||||
namespaceTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
podTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue1", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
admit: false,
|
||||
testName: "conflicting pod and namespace tolerations",
|
||||
},
|
||||
{
|
||||
pod: bestEffortPod,
|
||||
defaultClusterTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue2", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
namespaceTolerations: []api.Toleration{},
|
||||
podTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue1", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
mergedTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue1", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
admit: true,
|
||||
testName: "conflicting pod and default cluster tolerations but overridden by empty namespace tolerations",
|
||||
},
|
||||
{
|
||||
pod: bestEffortPod,
|
||||
defaultClusterTolerations: []api.Toleration{},
|
||||
namespaceTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
whitelist: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
podTolerations: []api.Toleration{},
|
||||
mergedTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
admit: true,
|
||||
testName: "merged pod tolerations satisfy whitelist",
|
||||
},
|
||||
{
|
||||
pod: bestEffortPod,
|
||||
defaultClusterTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
namespaceTolerations: []api.Toleration{},
|
||||
podTolerations: []api.Toleration{},
|
||||
mergedTolerations: []api.Toleration{},
|
||||
admit: true,
|
||||
testName: "Override default cluster toleration by empty namespace level toleration",
|
||||
},
|
||||
{
|
||||
pod: bestEffortPod,
|
||||
whitelist: []api.Toleration{},
|
||||
clusterWhitelist: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue1", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
podTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
mergedTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
admit: true,
|
||||
testName: "pod toleration conflicts with default cluster white list which is overridden by empty namespace whitelist",
|
||||
},
|
||||
{
|
||||
pod: bestEffortPod,
|
||||
defaultClusterTolerations: []api.Toleration{},
|
||||
namespaceTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
whitelist: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue1", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
podTolerations: []api.Toleration{},
|
||||
admit: false,
|
||||
testName: "merged pod tolerations conflict with the whitelist",
|
||||
},
|
||||
{
|
||||
pod: burstablePod,
|
||||
defaultClusterTolerations: []api.Toleration{},
|
||||
namespaceTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
whitelist: []api.Toleration{},
|
||||
podTolerations: []api.Toleration{},
|
||||
mergedTolerations: []api.Toleration{
|
||||
{Key: algorithm.TaintNodeMemoryPressure, Operator: api.TolerationOpExists, Effect: api.TaintEffectNoSchedule, TolerationSeconds: nil},
|
||||
{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil},
|
||||
},
|
||||
admit: true,
|
||||
testName: "added memoryPressure/DiskPressure for Burstable pod",
|
||||
},
|
||||
{
|
||||
pod: guaranteedPod,
|
||||
defaultClusterTolerations: []api.Toleration{},
|
||||
namespaceTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
whitelist: []api.Toleration{},
|
||||
podTolerations: []api.Toleration{},
|
||||
mergedTolerations: []api.Toleration{
|
||||
{Key: algorithm.TaintNodeMemoryPressure, Operator: api.TolerationOpExists, Effect: api.TaintEffectNoSchedule, TolerationSeconds: nil},
|
||||
{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil},
|
||||
},
|
||||
admit: true,
|
||||
testName: "added memoryPressure/DiskPressure for Guaranteed pod",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.testName, func(t *testing.T) {
|
||||
namespace := &api.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "testNamespace",
|
||||
Namespace: "",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
}
|
||||
|
||||
if test.namespaceTolerations != nil {
|
||||
tolerationStr, err := json.Marshal(test.namespaceTolerations)
|
||||
if err != nil {
|
||||
t.Errorf("error in marshalling namespace tolerations %v", test.namespaceTolerations)
|
||||
}
|
||||
namespace.Annotations = map[string]string{NSDefaultTolerations: string(tolerationStr)}
|
||||
}
|
||||
|
||||
if test.whitelist != nil {
|
||||
tolerationStr, err := json.Marshal(test.whitelist)
|
||||
if err != nil {
|
||||
t.Errorf("error in marshalling namespace whitelist %v", test.whitelist)
|
||||
}
|
||||
namespace.Annotations[NSWLTolerations] = string(tolerationStr)
|
||||
}
|
||||
|
||||
mockClient := fake.NewSimpleClientset(namespace)
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
stopCh := make(chan struct{})
|
||||
defer close(stopCh)
|
||||
informerFactory.Start(stopCh)
|
||||
|
||||
handler.pluginConfig = &pluginapi.Configuration{Default: test.defaultClusterTolerations, Whitelist: test.clusterWhitelist}
|
||||
pod := test.pod
|
||||
pod.Spec.Tolerations = test.podTolerations
|
||||
|
||||
// copy the original pod for tests of uninitialized pod updates.
|
||||
oldPod := *pod
|
||||
oldPod.Initializers = &metav1.Initializers{Pending: []metav1.Initializer{{Name: "init"}}}
|
||||
oldPod.Spec.Tolerations = []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue1", Effect: "NoSchedule", TolerationSeconds: nil}}
|
||||
|
||||
err = handler.Admit(admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "testNamespace", namespace.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if test.admit && err != nil {
|
||||
t.Errorf("Test: %s, expected no error but got: %s", test.testName, err)
|
||||
} else if !test.admit && err == nil {
|
||||
t.Errorf("Test: %s, expected an error", test.testName)
|
||||
}
|
||||
|
||||
updatedPodTolerations := pod.Spec.Tolerations
|
||||
if test.admit && !tolerations.EqualTolerations(updatedPodTolerations, test.mergedTolerations) {
|
||||
t.Errorf("Test: %s, expected: %#v but got: %#v", test.testName, test.mergedTolerations, updatedPodTolerations)
|
||||
}
|
||||
|
||||
// handles update of uninitialized pod like it's newly created.
|
||||
err = handler.Admit(admission.NewAttributesRecord(pod, &oldPod, api.Kind("Pod").WithVersion("version"), "testNamespace", namespace.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
|
||||
if test.admit && err != nil {
|
||||
t.Errorf("Test: %s, expected no error but got: %s", test.testName, err)
|
||||
} else if !test.admit && err == nil {
|
||||
t.Errorf("Test: %s, expected an error", test.testName)
|
||||
}
|
||||
|
||||
updatedPodTolerations = pod.Spec.Tolerations
|
||||
if test.admit && !tolerations.EqualTolerations(updatedPodTolerations, test.mergedTolerations) {
|
||||
t.Errorf("Test: %s, expected: %#v but got: %#v", test.testName, test.mergedTolerations, updatedPodTolerations)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandles(t *testing.T) {
|
||||
for op, shouldHandle := range map[admission.Operation]bool{
|
||||
admission.Create: true,
|
||||
admission.Update: true,
|
||||
admission.Connect: false,
|
||||
admission.Delete: false,
|
||||
} {
|
||||
|
||||
pluginConfig, err := loadConfiguration(nil)
|
||||
// must not fail
|
||||
if err != nil {
|
||||
t.Errorf("%v: error reading default configuration", op)
|
||||
}
|
||||
ptPlugin := NewPodTolerationsPlugin(pluginConfig)
|
||||
if e, a := shouldHandle, ptPlugin.Handles(op); e != a {
|
||||
t.Errorf("%v: shouldHandle=%t, handles=%t", op, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIgnoreUpdatingInitializedPod(t *testing.T) {
|
||||
mockClient := &fake.Clientset{}
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
handler.SetReadyFunc(func() bool { return true })
|
||||
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "testPod", Namespace: "testNamespace"},
|
||||
Spec: api.PodSpec{},
|
||||
}
|
||||
podToleration := api.Toleration{
|
||||
Key: "testKey",
|
||||
Operator: "Equal",
|
||||
Value: "testValue1",
|
||||
Effect: "NoSchedule",
|
||||
TolerationSeconds: nil,
|
||||
}
|
||||
pod.Spec.Tolerations = []api.Toleration{podToleration}
|
||||
|
||||
// this conflicts with pod's Tolerations
|
||||
namespaceToleration := podToleration
|
||||
namespaceToleration.Value = "testValue2"
|
||||
namespaceTolerations := []api.Toleration{namespaceToleration}
|
||||
tolerationsStr, err := json.Marshal(namespaceTolerations)
|
||||
if err != nil {
|
||||
t.Errorf("error in marshalling namespace tolerations %v", namespaceTolerations)
|
||||
}
|
||||
namespace := &api.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "testNamespace",
|
||||
Namespace: "",
|
||||
},
|
||||
}
|
||||
namespace.Annotations = map[string]string{NSDefaultTolerations: string(tolerationsStr)}
|
||||
err = informerFactory.Core().InternalVersion().Namespaces().Informer().GetStore().Update(namespace)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// if the update of initialized pod is not ignored, an error will be returned because the pod's Tolerations conflicts with namespace's Tolerations.
|
||||
err = handler.Admit(admission.NewAttributesRecord(pod, pod, api.Kind("Pod").WithVersion("version"), "testNamespace", pod.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// newHandlerForTest returns the admission controller configured for testing.
|
||||
func newHandlerForTest(c clientset.Interface) (*podTolerationsPlugin, informers.SharedInformerFactory, error) {
|
||||
f := informers.NewSharedInformerFactory(c, 5*time.Minute)
|
||||
pluginConfig, err := loadConfiguration(nil)
|
||||
// must not fail
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
handler := NewPodTolerationsPlugin(pluginConfig)
|
||||
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil)
|
||||
pluginInitializer.Initialize(handler)
|
||||
err = admission.ValidateInitialization(handler)
|
||||
return handler, f, err
|
||||
}
|
||||
41
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/BUILD
generated
vendored
Normal file
41
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/BUILD
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
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",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1: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/podtolerationrestriction/apis/podtolerationrestriction/install:all-srcs",
|
||||
"//plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1:all-srcs",
|
||||
"//plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/validation:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
2
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/OWNERS
generated
vendored
Normal file
2
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/OWNERS
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
reviewers:
|
||||
approvers:
|
||||
19
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/doc.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
|
||||
package podtolerationrestriction // import "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction"
|
||||
31
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/install/BUILD
generated
vendored
Normal file
31
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/install/BUILD
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["install.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/install",
|
||||
deps = [
|
||||
"//plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction:go_default_library",
|
||||
"//plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
33
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/install/install.go
generated
vendored
Normal file
33
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/install/install.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
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/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
internalapi "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction"
|
||||
versionedapi "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1"
|
||||
)
|
||||
|
||||
// Install registers the API group and adds types to a scheme
|
||||
func Install(scheme *runtime.Scheme) {
|
||||
utilruntime.Must(internalapi.AddToScheme(scheme))
|
||||
utilruntime.Must(versionedapi.AddToScheme(scheme))
|
||||
utilruntime.Must(scheme.SetVersionPriority(versionedapi.SchemeGroupVersion))
|
||||
}
|
||||
50
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/register.go
generated
vendored
Normal file
50
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/register.go
generated
vendored
Normal file
@@ -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 podtolerationrestriction
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// GroupName is the group name use in this package
|
||||
const GroupName = "podtolerationrestriction.admission.k8s.io"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
|
||||
|
||||
var (
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
// 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 {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&Configuration{},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
35
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/types.go
generated
vendored
Normal file
35
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/types.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
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 podtolerationrestriction
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
)
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// Configuration provides configuration for the PodTolerationRestriction admission controller.
|
||||
type Configuration struct {
|
||||
metav1.TypeMeta
|
||||
|
||||
// cluster level default tolerations
|
||||
Default []api.Toleration
|
||||
|
||||
// cluster level whitelist of tolerations
|
||||
Whitelist []api.Toleration
|
||||
}
|
||||
42
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/BUILD
generated
vendored
Normal file
42
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/BUILD
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
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",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1: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"],
|
||||
)
|
||||
23
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/defaults.go
generated
vendored
Normal file
23
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/defaults.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
func addDefaultingFuncs(scheme *runtime.Scheme) error {
|
||||
return RegisterDefaults(scheme)
|
||||
}
|
||||
23
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/doc.go
generated
vendored
Normal file
23
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/doc.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +k8s:conversion-gen=k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
|
||||
// Package v1alpha1 is the v1alpha1 version of the API.
|
||||
// +groupName=podtolerationrestriction.admission.k8s.io
|
||||
package v1alpha1 // import "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1"
|
||||
50
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/register.go
generated
vendored
Normal file
50
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/register.go
generated
vendored
Normal file
@@ -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 = "podtolerationrestriction.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
|
||||
}
|
||||
35
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/types.go
generated
vendored
Normal file
35
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/types.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
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/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// Configuration provides configuration for the PodTolerationRestriction admission controller.
|
||||
type Configuration struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
// cluster level default tolerations
|
||||
Default []v1.Toleration `json:"default,omitempty"`
|
||||
|
||||
// cluster level whitelist of tolerations
|
||||
Whitelist []v1.Toleration `json:"whitelist,omitempty"`
|
||||
}
|
||||
66
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/zz_generated.conversion.go
generated
vendored
Normal file
66
vendor/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1/zz_generated.conversion.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 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.
|
||||
*/
|
||||
|
||||
// Code generated by conversion-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
unsafe "unsafe"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
conversion "k8s.io/apimachinery/pkg/conversion"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
core "k8s.io/kubernetes/pkg/apis/core"
|
||||
podtolerationrestriction "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction"
|
||||
)
|
||||
|
||||
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_podtolerationrestriction_Configuration,
|
||||
Convert_podtolerationrestriction_Configuration_To_v1alpha1_Configuration,
|
||||
)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_Configuration_To_podtolerationrestriction_Configuration(in *Configuration, out *podtolerationrestriction.Configuration, s conversion.Scope) error {
|
||||
out.Default = *(*[]core.Toleration)(unsafe.Pointer(&in.Default))
|
||||
out.Whitelist = *(*[]core.Toleration)(unsafe.Pointer(&in.Whitelist))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha1_Configuration_To_podtolerationrestriction_Configuration is an autogenerated conversion function.
|
||||
func Convert_v1alpha1_Configuration_To_podtolerationrestriction_Configuration(in *Configuration, out *podtolerationrestriction.Configuration, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha1_Configuration_To_podtolerationrestriction_Configuration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_podtolerationrestriction_Configuration_To_v1alpha1_Configuration(in *podtolerationrestriction.Configuration, out *Configuration, s conversion.Scope) error {
|
||||
out.Default = *(*[]v1.Toleration)(unsafe.Pointer(&in.Default))
|
||||
out.Whitelist = *(*[]v1.Toleration)(unsafe.Pointer(&in.Whitelist))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_podtolerationrestriction_Configuration_To_v1alpha1_Configuration is an autogenerated conversion function.
|
||||
func Convert_podtolerationrestriction_Configuration_To_v1alpha1_Configuration(in *podtolerationrestriction.Configuration, out *Configuration, s conversion.Scope) error {
|
||||
return autoConvert_podtolerationrestriction_Configuration_To_v1alpha1_Configuration(in, out, s)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user