mirror of https://github.com/k3s-io/k3s
Pod toleration restriction plugin with taints and tolerations.
parent
ef075a441f
commit
af53794854
|
@ -40,6 +40,7 @@ import (
|
|||
_ "k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label"
|
||||
_ "k8s.io/kubernetes/plugin/pkg/admission/podnodeselector"
|
||||
_ "k8s.io/kubernetes/plugin/pkg/admission/podpreset"
|
||||
_ "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction"
|
||||
_ "k8s.io/kubernetes/plugin/pkg/admission/resourcequota"
|
||||
_ "k8s.io/kubernetes/plugin/pkg/admission/security/podsecuritypolicy"
|
||||
_ "k8s.io/kubernetes/plugin/pkg/admission/securitycontext/scdeny"
|
||||
|
|
|
@ -155,7 +155,7 @@ func ValidateTolerationsInPodAnnotations(annotations map[string]string, fldPath
|
|||
}
|
||||
|
||||
if len(tolerations) > 0 {
|
||||
allErrs = append(allErrs, validateTolerations(tolerations, fldPath.Child(api.TolerationsAnnotationKey))...)
|
||||
allErrs = append(allErrs, ValidateTolerations(tolerations, fldPath.Child(api.TolerationsAnnotationKey))...)
|
||||
}
|
||||
|
||||
return allErrs
|
||||
|
@ -2000,12 +2000,12 @@ func validateOnlyAddedTolerations(newTolerations []api.Toleration, oldToleration
|
|||
}
|
||||
}
|
||||
|
||||
allErrs = append(allErrs, validateTolerations(newTolerations, fldPath)...)
|
||||
allErrs = append(allErrs, ValidateTolerations(newTolerations, fldPath)...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// validateTolerations tests if given tolerations have valid data.
|
||||
func validateTolerations(tolerations []api.Toleration, fldPath *field.Path) field.ErrorList {
|
||||
// ValidateTolerations tests if given tolerations have valid data.
|
||||
func ValidateTolerations(tolerations []api.Toleration, fldPath *field.Path) field.ErrorList {
|
||||
allErrors := field.ErrorList{}
|
||||
for i, toleration := range tolerations {
|
||||
idxPath := fldPath.Index(i)
|
||||
|
@ -2102,7 +2102,7 @@ func ValidatePodSpec(spec *api.PodSpec, fldPath *field.Path) field.ErrorList {
|
|||
}
|
||||
|
||||
if len(spec.Tolerations) > 0 {
|
||||
allErrs = append(allErrs, validateTolerations(spec.Tolerations, fldPath.Child("tolerations"))...)
|
||||
allErrs = append(allErrs, ValidateTolerations(spec.Tolerations, fldPath.Child("tolerations"))...)
|
||||
}
|
||||
|
||||
return allErrs
|
||||
|
|
|
@ -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 tolerations provides utilities to work with pod spec tolerations.
|
||||
package tolerations // import "k8s.io/kubernetes/pkg/util/tolerations"
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
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 tolerations
|
||||
|
||||
import (
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
type key struct {
|
||||
tolerationKey string
|
||||
effect api.TaintEffect
|
||||
}
|
||||
|
||||
// VerifyAgainstWhitelist checks if the provided tolerations
|
||||
// satisfy the provided whitelist and returns true, otherwise returns false
|
||||
func VerifyAgainstWhitelist(tolerations []api.Toleration, whitelist []api.Toleration) bool {
|
||||
if len(whitelist) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
t := ConvertTolerationToAMap(tolerations)
|
||||
w := ConvertTolerationToAMap(whitelist)
|
||||
|
||||
for k1, v1 := range t {
|
||||
if v2, ok := w[k1]; !ok || !AreEqual(v1, v2) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsConflict returns true if the key of two tolerations match
|
||||
// but one or more other fields differ, otherwise returns false
|
||||
func IsConflict(first []api.Toleration, second []api.Toleration) bool {
|
||||
firstMap := ConvertTolerationToAMap(first)
|
||||
secondMap := ConvertTolerationToAMap(second)
|
||||
|
||||
for k1, v1 := range firstMap {
|
||||
if v2, ok := secondMap[k1]; ok && !AreEqual(v1, v2) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MergeTolerations merges two sets of tolerations into one
|
||||
// it does not check for conflicts
|
||||
func MergeTolerations(first []api.Toleration, second []api.Toleration) []api.Toleration {
|
||||
var mergedTolerations []api.Toleration
|
||||
mergedTolerations = append(mergedTolerations, second...)
|
||||
|
||||
firstMap := ConvertTolerationToAMap(first)
|
||||
secondMap := ConvertTolerationToAMap(second)
|
||||
|
||||
for k1, v1 := range firstMap {
|
||||
if _, ok := secondMap[k1]; !ok {
|
||||
mergedTolerations = append(mergedTolerations, v1)
|
||||
}
|
||||
}
|
||||
return mergedTolerations
|
||||
}
|
||||
|
||||
// EqualTolerations returns true if two sets of tolerations are equal, otherwise false
|
||||
// it assumes no duplicates in individual set of tolerations
|
||||
func EqualTolerations(first []api.Toleration, second []api.Toleration) bool {
|
||||
if len(first) != len(second) {
|
||||
return false
|
||||
}
|
||||
|
||||
firstMap := ConvertTolerationToAMap(first)
|
||||
secondMap := ConvertTolerationToAMap(second)
|
||||
|
||||
for k1, v1 := range firstMap {
|
||||
if v2, ok := secondMap[k1]; !ok || !AreEqual(v1, v2) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ConvertTolerationToAMap converts toleration list into a map[string]api.Toleration
|
||||
func ConvertTolerationToAMap(in []api.Toleration) map[key]api.Toleration {
|
||||
out := map[key]api.Toleration{}
|
||||
for i := range in {
|
||||
out[key{in[i].Key, in[i].Effect}] = in[i]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// AreEqual checks if two provided tolerations are equal or not.
|
||||
func AreEqual(first, second api.Toleration) bool {
|
||||
if first.Key == second.Key &&
|
||||
first.Operator == second.Operator &&
|
||||
first.Value == second.Value &&
|
||||
first.Effect == second.Effect &&
|
||||
AreTolerationSecondsEqual(first.TolerationSeconds, second.TolerationSeconds) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AreTolerationSecondsEqual checks if two provided TolerationSeconds are equal or not.
|
||||
func AreTolerationSecondsEqual(ts1, ts2 *int64) bool {
|
||||
if ts1 == ts2 {
|
||||
return true
|
||||
}
|
||||
if ts1 != nil && ts2 != nil && *ts1 == *ts2 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
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 tolerations
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
func TestVerifyAgainstWhitelist(t *testing.T) {
|
||||
tests := []struct {
|
||||
input []api.Toleration
|
||||
whitelist []api.Toleration
|
||||
testName string
|
||||
testStatus bool
|
||||
}{
|
||||
{
|
||||
input: []api.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}},
|
||||
whitelist: []api.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}},
|
||||
testName: "equal input and whitelist",
|
||||
testStatus: true,
|
||||
},
|
||||
{
|
||||
input: []api.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}},
|
||||
whitelist: []api.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoExecute"}},
|
||||
testName: "input does not exist in whitelist",
|
||||
testStatus: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range tests {
|
||||
status := VerifyAgainstWhitelist(c.input, c.whitelist)
|
||||
if status != c.testStatus {
|
||||
t.Errorf("Test: %s, expected %v", c.testName, status)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestIsConflict(t *testing.T) {
|
||||
tests := []struct {
|
||||
input1 []api.Toleration
|
||||
input2 []api.Toleration
|
||||
testName string
|
||||
testStatus bool
|
||||
}{
|
||||
{
|
||||
input1: []api.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}},
|
||||
input2: []api.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}},
|
||||
testName: "equal inputs",
|
||||
testStatus: true,
|
||||
},
|
||||
{
|
||||
input1: []api.Toleration{{Key: "foo", Operator: "Equal", Value: "foo", Effect: "NoExecute"}},
|
||||
input2: []api.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoExecute"}},
|
||||
testName: "mismatch values in inputs",
|
||||
testStatus: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range tests {
|
||||
status := IsConflict(c.input1, c.input2)
|
||||
if status == c.testStatus {
|
||||
t.Errorf("Test: %s, expected %v", c.testName, status)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
/*
|
||||
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/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/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/util/tolerations"
|
||||
pluginapi "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction"
|
||||
)
|
||||
|
||||
func init() {
|
||||
admission.RegisterPlugin("PodTolerationRestriction", 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 _ = 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 {
|
||||
resource := a.GetResource().GroupResource()
|
||||
if resource != api.Resource("pods") {
|
||||
return nil
|
||||
}
|
||||
if a.GetSubresource() != "" {
|
||||
// only run the checks below on pods proper and not subresources
|
||||
return nil
|
||||
}
|
||||
|
||||
obj := a.GetObject()
|
||||
pod, ok := obj.(*api.Pod)
|
||||
if !ok {
|
||||
glog.Errorf("expected pod but got %s", a.GetKind().Kind)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !p.WaitForReady() {
|
||||
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
|
||||
}
|
||||
|
||||
nsName := a.GetNamespace()
|
||||
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 err
|
||||
}
|
||||
return errors.NewInternalError(err)
|
||||
}
|
||||
} else if err != nil {
|
||||
return errors.NewInternalError(err)
|
||||
}
|
||||
|
||||
var finalTolerations []api.Toleration
|
||||
if a.GetOperation() == admission.Create {
|
||||
ts, err := p.getNamespaceDefaultTolerations(namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the namespace has not specified its default tolerations,
|
||||
// fall back to cluster's default tolerations.
|
||||
if len(ts) == 0 {
|
||||
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
|
||||
}
|
||||
|
||||
// whitelist verification.
|
||||
if len(finalTolerations) > 0 {
|
||||
whitelist, err := p.getNamespaceTolerationsWhitelist(namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the namespace has not specified its tolerations whitelist,
|
||||
// fall back to cluster's whitelist of tolerations.
|
||||
if len(whitelist) == 0 {
|
||||
whitelist = p.pluginConfig.Whitelist
|
||||
}
|
||||
|
||||
if len(whitelist) > 0 {
|
||||
// check if the merged pod tolerations satisfy its namespace whitelist
|
||||
if !tolerations.VerifyAgainstWhitelist(finalTolerations, whitelist) {
|
||||
return fmt.Errorf("pod tolerations (possibly merged with namespace default tolerations) conflict with its namespace whitelist")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pod.Spec.Tolerations = finalTolerations
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
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) Validate() error {
|
||||
if p.namespaceLister == nil {
|
||||
return fmt.Errorf("missing namespaceLister")
|
||||
}
|
||||
if p.client == nil {
|
||||
return fmt.Errorf("missing client")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *podTolerationsPlugin) getNamespaceDefaultTolerations(ns *api.Namespace) ([]api.Toleration, error) {
|
||||
return extractNSTolerations(ns, NSDefaultTolerations)
|
||||
}
|
||||
|
||||
func (p *podTolerationsPlugin) getNamespaceTolerationsWhitelist(ns *api.Namespace) ([]api.Toleration, error) {
|
||||
return extractNSTolerations(ns, NSWLTolerations)
|
||||
}
|
||||
|
||||
func extractNSTolerations(ns *api.Namespace, key string) ([]api.Toleration, error) {
|
||||
var v1Tolerations []v1.Toleration
|
||||
if len(ns.Annotations) > 0 && ns.Annotations[key] != "" {
|
||||
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 := v1.Convert_v1_Toleration_To_api_Toleration(&v1Tolerations[i], &ts[i], nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return ts, nil
|
||||
}
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
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"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
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/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) {
|
||||
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"},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
defaultClusterTolerations []api.Toleration
|
||||
namespaceTolerations []api.Toleration
|
||||
whitelist []api.Toleration
|
||||
clusterWhitelist []api.Toleration
|
||||
podTolerations []api.Toleration
|
||||
mergedTolerations []api.Toleration
|
||||
admit bool
|
||||
testName string
|
||||
}{
|
||||
{
|
||||
defaultClusterTolerations: []api.Toleration{{Key: "testKey", Operator: "Equal", Value: "testValue", Effect: "NoSchedule", TolerationSeconds: nil}},
|
||||
namespaceTolerations: []api.Toleration{},
|
||||
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",
|
||||
},
|
||||
{
|
||||
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",
|
||||
},
|
||||
{
|
||||
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",
|
||||
},
|
||||
{
|
||||
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",
|
||||
},
|
||||
{
|
||||
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",
|
||||
},
|
||||
{
|
||||
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}},
|
||||
admit: false,
|
||||
testName: "conflicting pod and default cluster tolerations",
|
||||
},
|
||||
{
|
||||
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",
|
||||
},
|
||||
{
|
||||
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",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
if len(test.namespaceTolerations) > 0 {
|
||||
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 len(test.whitelist) > 0 {
|
||||
tolerationStr, err := json.Marshal(test.whitelist)
|
||||
if err != nil {
|
||||
t.Errorf("error in marshalling namespace whitelist %v", test.whitelist)
|
||||
}
|
||||
namespace.Annotations[NSWLTolerations] = string(tolerationStr)
|
||||
}
|
||||
|
||||
informerFactory.Core().InternalVersion().Namespaces().Informer().GetStore().Update(namespace)
|
||||
|
||||
handler.pluginConfig = &pluginapi.Configuration{Default: test.defaultClusterTolerations, Whitelist: test.clusterWhitelist}
|
||||
pod.Spec.Tolerations = test.podTolerations
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
pluginInitializer.Initialize(handler)
|
||||
err = admission.Validate(handler)
|
||||
return handler, f, err
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
reviewers:
|
||||
approvers:
|
|
@ -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,register
|
||||
|
||||
package podtolerationrestriction // import "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction"
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package install installs the experimental API group, making it available as
|
||||
// an option to all of the API encoding/decoding machinery.
|
||||
package install
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/apimachinery/announced"
|
||||
"k8s.io/apimachinery/pkg/apimachinery/registered"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
internalapi "k8s.io/kubernetes/plugin/pkg/admission/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(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *registered.APIRegistrationManager, scheme *runtime.Scheme) {
|
||||
if err := announced.NewGroupMetaFactory(
|
||||
&announced.GroupMetaFactoryArgs{
|
||||
GroupName: internalapi.GroupName,
|
||||
VersionPreferenceOrder: []string{versionedapi.SchemeGroupVersion.Version},
|
||||
ImportPrefix: "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction",
|
||||
AddInternalObjectsToScheme: internalapi.AddToScheme,
|
||||
},
|
||||
announced.VersionToSchemeFunc{
|
||||
versionedapi.SchemeGroupVersion.Version: versionedapi.AddToScheme,
|
||||
},
|
||||
).Announce(groupFactoryRegistry).RegisterAndEnable(registry, scheme); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
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
|
||||
}
|
||||
|
||||
func (obj *Configuration) GetObjectKind() schema.ObjectKind { return &obj.TypeMeta }
|
|
@ -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 podtolerationrestriction
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
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 {
|
||||
RegisterDefaults(scheme)
|
||||
return scheme.AddDefaultingFuncs(
|
||||
SetDefaults_Configuration,
|
||||
)
|
||||
}
|
||||
|
||||
func SetDefaults_Configuration(obj *Configuration) {}
|
|
@ -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,register
|
||||
// +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"
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
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 (
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes, addDefaultingFuncs)
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&Configuration{},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (obj *Configuration) GetObjectKind() schema.ObjectKind { return &obj.TypeMeta }
|
|
@ -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 v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
)
|
||||
|
||||
// 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"`
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/kubernetes/pkg/api/validation"
|
||||
internalapi "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction"
|
||||
)
|
||||
|
||||
// ValidateConfiguration validates the configuration.
|
||||
func ValidateConfiguration(config *internalapi.Configuration) error {
|
||||
allErrs := field.ErrorList{}
|
||||
fldpath := field.NewPath("podtolerationrestriction")
|
||||
allErrs = append(allErrs, validation.ValidateTolerations(config.Default, fldpath.Child("default"))...)
|
||||
allErrs = append(allErrs, validation.ValidateTolerations(config.Whitelist, fldpath.Child("whitelist"))...)
|
||||
if len(allErrs) > 0 {
|
||||
return fmt.Errorf("invalid config: %v", allErrs)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
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"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
internalapi "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction"
|
||||
)
|
||||
|
||||
func TestValidateConfiguration(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
config internalapi.Configuration
|
||||
testName string
|
||||
testStatus bool
|
||||
}{
|
||||
{
|
||||
config: internalapi.Configuration{
|
||||
Default: []api.Toleration{
|
||||
{Key: "foo", Operator: "Exists", Value: "", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]},
|
||||
{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]},
|
||||
{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoSchedule"},
|
||||
{Operator: "Exists", Effect: "NoSchedule"},
|
||||
},
|
||||
Whitelist: []api.Toleration{
|
||||
{Key: "foo", Value: "bar", Effect: "NoSchedule"},
|
||||
{Key: "foo", Operator: "Equal", Value: "bar"},
|
||||
},
|
||||
},
|
||||
testName: "Valid cases",
|
||||
testStatus: true,
|
||||
},
|
||||
{
|
||||
config: internalapi.Configuration{
|
||||
Whitelist: []api.Toleration{{Key: "foo", Operator: "Exists", Value: "bar", Effect: "NoSchedule"}},
|
||||
},
|
||||
testName: "Invalid case",
|
||||
testStatus: false,
|
||||
},
|
||||
{
|
||||
config: internalapi.Configuration{
|
||||
Default: []api.Toleration{{Operator: "Equal", Value: "bar", Effect: "NoSchedule"}},
|
||||
},
|
||||
testName: "Invalid case",
|
||||
testStatus: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i := range tests {
|
||||
errs := ValidateConfiguration(&tests[i].config)
|
||||
if tests[i].testStatus && errs != nil {
|
||||
t.Errorf("Test: %s, expected success: %v", tests[i].testName, errs)
|
||||
}
|
||||
if !tests[i].testStatus && errs == nil {
|
||||
t.Errorf("Test: %s, expected errors: %v", tests[i].testName, errs)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apimachinery/announced"
|
||||
"k8s.io/apimachinery/pkg/apimachinery/registered"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
internalapi "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction"
|
||||
"k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/install"
|
||||
versionedapi "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1"
|
||||
"k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/validation"
|
||||
)
|
||||
|
||||
var (
|
||||
groupFactoryRegistry = make(announced.APIGroupFactoryRegistry)
|
||||
registry = registered.NewOrDie(os.Getenv("KUBE_API_VERSIONS"))
|
||||
scheme = runtime.NewScheme()
|
||||
codecs = serializer.NewCodecFactory(scheme)
|
||||
)
|
||||
|
||||
func init() {
|
||||
install.Install(groupFactoryRegistry, registry, scheme)
|
||||
}
|
||||
|
||||
// LoadConfiguration loads the provided configuration.
|
||||
func loadConfiguration(config io.Reader) (*internalapi.Configuration, error) {
|
||||
// if no config is provided, return a default configuration
|
||||
if config == nil {
|
||||
externalConfig := &versionedapi.Configuration{}
|
||||
scheme.Default(externalConfig)
|
||||
internalConfig := &internalapi.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
|
||||
}
|
||||
externalConfig, ok := decodedObj.(*internalapi.Configuration)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected type: %T", decodedObj)
|
||||
}
|
||||
|
||||
if err := validation.ValidateConfiguration(externalConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return externalConfig, nil
|
||||
}
|
Loading…
Reference in New Issue