...

Source file src/k8s.io/kubernetes/plugin/pkg/admission/runtimeclass/admission.go

Documentation: k8s.io/kubernetes/plugin/pkg/admission/runtimeclass

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package runtimeclass contains an admission controller for modifying and validating new Pods to
    18  // take RuntimeClass into account. For RuntimeClass definitions which describe an overhead associated
    19  // with running a pod, this admission controller will set the pod.Spec.Overhead field accordingly. This
    20  // field should only be set through this controller, so validation will be carried out to ensure the pod's
    21  // value matches what is defined in the coresponding RuntimeClass.
    22  package runtimeclass
    23  
    24  import (
    25  	"context"
    26  	"fmt"
    27  	"io"
    28  
    29  	nodev1 "k8s.io/api/node/v1"
    30  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    31  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apiserver/pkg/admission"
    34  	genericadmissioninitailizer "k8s.io/apiserver/pkg/admission/initializer"
    35  	"k8s.io/client-go/informers"
    36  	"k8s.io/client-go/kubernetes"
    37  	nodev1client "k8s.io/client-go/kubernetes/typed/node/v1"
    38  	nodev1listers "k8s.io/client-go/listers/node/v1"
    39  	api "k8s.io/kubernetes/pkg/apis/core"
    40  	node "k8s.io/kubernetes/pkg/apis/node"
    41  	apinodev1 "k8s.io/kubernetes/pkg/apis/node/v1"
    42  	"k8s.io/kubernetes/pkg/util/tolerations"
    43  )
    44  
    45  // PluginName indicates name of admission plugin.
    46  const PluginName = "RuntimeClass"
    47  
    48  // Register registers a plugin
    49  func Register(plugins *admission.Plugins) {
    50  	plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
    51  		return NewRuntimeClass(), nil
    52  	})
    53  }
    54  
    55  // RuntimeClass is an implementation of admission.Interface.
    56  // It looks at all new pods and sets pod.Spec.Overhead if a RuntimeClass is specified which
    57  // defines an Overhead. If pod.Spec.Overhead is set but a RuntimeClass with matching overhead is
    58  // not specified, the pod is rejected.
    59  type RuntimeClass struct {
    60  	*admission.Handler
    61  	runtimeClassLister nodev1listers.RuntimeClassLister
    62  	runtimeClassClient nodev1client.RuntimeClassInterface
    63  }
    64  
    65  var _ admission.MutationInterface = &RuntimeClass{}
    66  var _ admission.ValidationInterface = &RuntimeClass{}
    67  
    68  var _ genericadmissioninitailizer.WantsExternalKubeInformerFactory = &RuntimeClass{}
    69  var _ genericadmissioninitailizer.WantsExternalKubeClientSet = &RuntimeClass{}
    70  
    71  // SetExternalKubeClientSet sets the client for the plugin
    72  func (r *RuntimeClass) SetExternalKubeClientSet(client kubernetes.Interface) {
    73  	r.runtimeClassClient = client.NodeV1().RuntimeClasses()
    74  }
    75  
    76  // SetExternalKubeInformerFactory implements the WantsExternalKubeInformerFactory interface.
    77  func (r *RuntimeClass) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
    78  	runtimeClassInformer := f.Node().V1().RuntimeClasses()
    79  	r.SetReadyFunc(runtimeClassInformer.Informer().HasSynced)
    80  	r.runtimeClassLister = runtimeClassInformer.Lister()
    81  }
    82  
    83  // ValidateInitialization implements the WantsExternalKubeInformerFactory interface.
    84  func (r *RuntimeClass) ValidateInitialization() error {
    85  	if r.runtimeClassLister == nil {
    86  		return fmt.Errorf("missing RuntimeClass lister")
    87  	}
    88  	if r.runtimeClassClient == nil {
    89  		return fmt.Errorf("missing RuntimeClass client")
    90  	}
    91  	return nil
    92  }
    93  
    94  // Admit makes an admission decision based on the request attributes
    95  func (r *RuntimeClass) Admit(ctx context.Context, attributes admission.Attributes, o admission.ObjectInterfaces) error {
    96  	// Ignore all calls to subresources or resources other than pods.
    97  	if shouldIgnore(attributes) {
    98  		return nil
    99  	}
   100  
   101  	pod, runtimeClass, err := r.prepareObjects(ctx, attributes)
   102  	if err != nil {
   103  		return err
   104  	}
   105  	if err := setOverhead(attributes, pod, runtimeClass); err != nil {
   106  		return err
   107  	}
   108  
   109  	if err := setScheduling(attributes, pod, runtimeClass); err != nil {
   110  		return err
   111  	}
   112  
   113  	return nil
   114  }
   115  
   116  // Validate makes sure that pod adhere's to RuntimeClass's definition
   117  func (r *RuntimeClass) Validate(ctx context.Context, attributes admission.Attributes, o admission.ObjectInterfaces) error {
   118  	// Ignore all calls to subresources or resources other than pods.
   119  	if shouldIgnore(attributes) {
   120  		return nil
   121  	}
   122  
   123  	pod, runtimeClass, err := r.prepareObjects(ctx, attributes)
   124  	if err != nil {
   125  		return err
   126  	}
   127  	if err := validateOverhead(attributes, pod, runtimeClass); err != nil {
   128  		return err
   129  	}
   130  
   131  	return nil
   132  }
   133  
   134  // NewRuntimeClass creates a new RuntimeClass admission control handler
   135  func NewRuntimeClass() *RuntimeClass {
   136  	return &RuntimeClass{
   137  		Handler: admission.NewHandler(admission.Create),
   138  	}
   139  }
   140  
   141  // prepareObjects returns pod and runtimeClass types from the given admission attributes
   142  func (r *RuntimeClass) prepareObjects(ctx context.Context, attributes admission.Attributes) (pod *api.Pod, runtimeClass *nodev1.RuntimeClass, err error) {
   143  	pod, ok := attributes.GetObject().(*api.Pod)
   144  	if !ok {
   145  		return nil, nil, apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
   146  	}
   147  
   148  	if pod.Spec.RuntimeClassName == nil {
   149  		return pod, nil, nil
   150  	}
   151  
   152  	// get RuntimeClass object
   153  	runtimeClass, err = r.runtimeClassLister.Get(*pod.Spec.RuntimeClassName)
   154  	if apierrors.IsNotFound(err) {
   155  		// if not found, our informer cache could be lagging, do a live lookup
   156  		runtimeClass, err = r.runtimeClassClient.Get(ctx, *pod.Spec.RuntimeClassName, metav1.GetOptions{})
   157  		if apierrors.IsNotFound(err) {
   158  			return pod, nil, admission.NewForbidden(attributes, fmt.Errorf("pod rejected: RuntimeClass %q not found", *pod.Spec.RuntimeClassName))
   159  		}
   160  	}
   161  
   162  	// return the pod and runtimeClass.
   163  	return pod, runtimeClass, err
   164  }
   165  
   166  func setOverhead(a admission.Attributes, pod *api.Pod, runtimeClass *nodev1.RuntimeClass) (err error) {
   167  	if runtimeClass == nil || runtimeClass.Overhead == nil {
   168  		return nil
   169  	}
   170  
   171  	// convert to internal type and assign to pod's Overhead
   172  	nodeOverhead := &node.Overhead{}
   173  	if err = apinodev1.Convert_v1_Overhead_To_node_Overhead(runtimeClass.Overhead, nodeOverhead, nil); err != nil {
   174  		return err
   175  	}
   176  
   177  	// reject pod if Overhead is already set that differs from what is defined in RuntimeClass
   178  	if pod.Spec.Overhead != nil && !apiequality.Semantic.DeepEqual(nodeOverhead.PodFixed, pod.Spec.Overhead) {
   179  		return admission.NewForbidden(a, fmt.Errorf("pod rejected: Pod's Overhead doesn't match RuntimeClass's defined Overhead"))
   180  	}
   181  
   182  	pod.Spec.Overhead = nodeOverhead.PodFixed
   183  
   184  	return nil
   185  }
   186  
   187  func setScheduling(a admission.Attributes, pod *api.Pod, runtimeClass *nodev1.RuntimeClass) (err error) {
   188  	if runtimeClass == nil || runtimeClass.Scheduling == nil {
   189  		return nil
   190  	}
   191  
   192  	// convert to internal type and assign to pod's Scheduling
   193  	nodeScheduling := &node.Scheduling{}
   194  	if err = apinodev1.Convert_v1_Scheduling_To_node_Scheduling(runtimeClass.Scheduling, nodeScheduling, nil); err != nil {
   195  		return err
   196  	}
   197  
   198  	runtimeNodeSelector := nodeScheduling.NodeSelector
   199  	newNodeSelector := pod.Spec.NodeSelector
   200  	if newNodeSelector == nil {
   201  		newNodeSelector = runtimeNodeSelector
   202  	} else {
   203  		for key, runtimeClassValue := range runtimeNodeSelector {
   204  			if podValue, ok := newNodeSelector[key]; ok && podValue != runtimeClassValue {
   205  				return admission.NewForbidden(a, fmt.Errorf("conflict: runtimeClass.scheduling.nodeSelector[%s] = %s; pod.spec.nodeSelector[%s] = %s", key, runtimeClassValue, key, podValue))
   206  			}
   207  			newNodeSelector[key] = runtimeClassValue
   208  		}
   209  	}
   210  
   211  	newTolerations := tolerations.MergeTolerations(pod.Spec.Tolerations, nodeScheduling.Tolerations)
   212  
   213  	pod.Spec.NodeSelector = newNodeSelector
   214  	pod.Spec.Tolerations = newTolerations
   215  
   216  	return nil
   217  }
   218  
   219  func validateOverhead(a admission.Attributes, pod *api.Pod, runtimeClass *nodev1.RuntimeClass) (err error) {
   220  	if runtimeClass != nil && runtimeClass.Overhead != nil {
   221  		// If the Overhead set doesn't match what is provided in the RuntimeClass definition, reject the pod
   222  		nodeOverhead := &node.Overhead{}
   223  		if err := apinodev1.Convert_v1_Overhead_To_node_Overhead(runtimeClass.Overhead, nodeOverhead, nil); err != nil {
   224  			return err
   225  		}
   226  		if !apiequality.Semantic.DeepEqual(nodeOverhead.PodFixed, pod.Spec.Overhead) {
   227  			return admission.NewForbidden(a, fmt.Errorf("pod rejected: Pod's Overhead doesn't match RuntimeClass's defined Overhead"))
   228  		}
   229  	} else {
   230  		// If RuntimeClass with Overhead is not defined but an Overhead is set for pod, reject the pod
   231  		if pod.Spec.Overhead != nil {
   232  			return admission.NewForbidden(a, fmt.Errorf("pod rejected: Pod Overhead set without corresponding RuntimeClass defined Overhead"))
   233  		}
   234  	}
   235  
   236  	return nil
   237  }
   238  
   239  func shouldIgnore(attributes admission.Attributes) bool {
   240  	// Ignore all calls to subresources or resources other than pods.
   241  	if len(attributes.GetSubresource()) != 0 || attributes.GetResource().GroupResource() != api.Resource("pods") {
   242  		return true
   243  	}
   244  
   245  	return false
   246  }
   247  

View as plain text