...

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

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

     1  /*
     2  Copyright 2017 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 priority
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io"
    23  
    24  	apiv1 "k8s.io/api/core/v1"
    25  	schedulingv1 "k8s.io/api/scheduling/v1"
    26  	"k8s.io/apimachinery/pkg/api/errors"
    27  	"k8s.io/apimachinery/pkg/labels"
    28  	"k8s.io/apiserver/pkg/admission"
    29  	genericadmissioninitializers "k8s.io/apiserver/pkg/admission/initializer"
    30  	"k8s.io/client-go/informers"
    31  	"k8s.io/client-go/kubernetes"
    32  	schedulingv1listers "k8s.io/client-go/listers/scheduling/v1"
    33  	"k8s.io/kubernetes/pkg/apis/core"
    34  	"k8s.io/kubernetes/pkg/apis/scheduling"
    35  )
    36  
    37  const (
    38  	// PluginName indicates name of admission plugin.
    39  	PluginName = "Priority"
    40  )
    41  
    42  // Register registers a plugin
    43  func Register(plugins *admission.Plugins) {
    44  	plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
    45  		return NewPlugin(), nil
    46  	})
    47  }
    48  
    49  // Plugin is an implementation of admission.Interface.
    50  type Plugin struct {
    51  	*admission.Handler
    52  	client kubernetes.Interface
    53  	lister schedulingv1listers.PriorityClassLister
    54  }
    55  
    56  var _ admission.MutationInterface = &Plugin{}
    57  var _ admission.ValidationInterface = &Plugin{}
    58  var _ = genericadmissioninitializers.WantsExternalKubeInformerFactory(&Plugin{})
    59  var _ = genericadmissioninitializers.WantsExternalKubeClientSet(&Plugin{})
    60  
    61  // NewPlugin creates a new priority admission plugin.
    62  func NewPlugin() *Plugin {
    63  	return &Plugin{
    64  		Handler: admission.NewHandler(admission.Create, admission.Update, admission.Delete),
    65  	}
    66  }
    67  
    68  // ValidateInitialization implements the InitializationValidator interface.
    69  func (p *Plugin) ValidateInitialization() error {
    70  	if p.client == nil {
    71  		return fmt.Errorf("%s requires a client", PluginName)
    72  	}
    73  	if p.lister == nil {
    74  		return fmt.Errorf("%s requires a lister", PluginName)
    75  	}
    76  	return nil
    77  }
    78  
    79  // SetExternalKubeClientSet implements the WantsInternalKubeClientSet interface.
    80  func (p *Plugin) SetExternalKubeClientSet(client kubernetes.Interface) {
    81  	p.client = client
    82  }
    83  
    84  // SetExternalKubeInformerFactory implements the WantsInternalKubeInformerFactory interface.
    85  func (p *Plugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
    86  	priorityInformer := f.Scheduling().V1().PriorityClasses()
    87  	p.lister = priorityInformer.Lister()
    88  	p.SetReadyFunc(priorityInformer.Informer().HasSynced)
    89  }
    90  
    91  var (
    92  	podResource           = core.Resource("pods")
    93  	priorityClassResource = scheduling.Resource("priorityclasses")
    94  )
    95  
    96  // Admit checks Pods and admits or rejects them. It also resolves the priority of pods based on their PriorityClass.
    97  // Note that pod validation mechanism prevents update of a pod priority.
    98  func (p *Plugin) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
    99  	operation := a.GetOperation()
   100  	// Ignore all calls to subresources
   101  	if len(a.GetSubresource()) != 0 {
   102  		return nil
   103  	}
   104  	switch a.GetResource().GroupResource() {
   105  	case podResource:
   106  		if operation == admission.Create || operation == admission.Update {
   107  			return p.admitPod(a)
   108  		}
   109  		return nil
   110  
   111  	default:
   112  		return nil
   113  	}
   114  }
   115  
   116  // Validate checks PriorityClasses and admits or rejects them.
   117  func (p *Plugin) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
   118  	operation := a.GetOperation()
   119  	// Ignore all calls to subresources
   120  	if len(a.GetSubresource()) != 0 {
   121  		return nil
   122  	}
   123  
   124  	switch a.GetResource().GroupResource() {
   125  	case priorityClassResource:
   126  		if operation == admission.Create || operation == admission.Update {
   127  			return p.validatePriorityClass(a)
   128  		}
   129  		return nil
   130  
   131  	default:
   132  		return nil
   133  	}
   134  }
   135  
   136  // admitPod makes sure a new pod does not set spec.Priority field. It also makes sure that the PriorityClassName exists if it is provided and resolves the pod priority from the PriorityClassName.
   137  func (p *Plugin) admitPod(a admission.Attributes) error {
   138  	operation := a.GetOperation()
   139  	pod, ok := a.GetObject().(*core.Pod)
   140  	if !ok {
   141  		return errors.NewBadRequest("resource was marked with kind Pod but was unable to be converted")
   142  	}
   143  
   144  	if operation == admission.Update {
   145  		oldPod, ok := a.GetOldObject().(*core.Pod)
   146  		if !ok {
   147  			return errors.NewBadRequest("resource was marked with kind Pod but was unable to be converted")
   148  		}
   149  
   150  		// This admission plugin set pod.Spec.Priority on create.
   151  		// Ensure the existing priority is preserved on update.
   152  		// API validation prevents mutations to Priority and PriorityClassName, so any other changes will fail update validation and not be persisted.
   153  		if pod.Spec.Priority == nil && oldPod.Spec.Priority != nil {
   154  			pod.Spec.Priority = oldPod.Spec.Priority
   155  		}
   156  		if pod.Spec.PreemptionPolicy == nil && oldPod.Spec.PreemptionPolicy != nil {
   157  			pod.Spec.PreemptionPolicy = oldPod.Spec.PreemptionPolicy
   158  		}
   159  		return nil
   160  	}
   161  
   162  	if operation == admission.Create {
   163  		var priority int32
   164  		var preemptionPolicy *apiv1.PreemptionPolicy
   165  		if len(pod.Spec.PriorityClassName) == 0 {
   166  			var err error
   167  			var pcName string
   168  			pcName, priority, preemptionPolicy, err = p.getDefaultPriority()
   169  			if err != nil {
   170  				return fmt.Errorf("failed to get default priority class: %v", err)
   171  			}
   172  			pod.Spec.PriorityClassName = pcName
   173  		} else {
   174  			// Try resolving the priority class name.
   175  			pc, err := p.lister.Get(pod.Spec.PriorityClassName)
   176  			if err != nil {
   177  				if errors.IsNotFound(err) {
   178  					return admission.NewForbidden(a, fmt.Errorf("no PriorityClass with name %v was found", pod.Spec.PriorityClassName))
   179  				}
   180  
   181  				return fmt.Errorf("failed to get PriorityClass with name %s: %v", pod.Spec.PriorityClassName, err)
   182  			}
   183  
   184  			priority = pc.Value
   185  			preemptionPolicy = pc.PreemptionPolicy
   186  		}
   187  		// if the pod contained a priority that differs from the one computed from the priority class, error
   188  		if pod.Spec.Priority != nil && *pod.Spec.Priority != priority {
   189  			return admission.NewForbidden(a, fmt.Errorf("the integer value of priority (%d) must not be provided in pod spec; priority admission controller computed %d from the given PriorityClass name", *pod.Spec.Priority, priority))
   190  		}
   191  		pod.Spec.Priority = &priority
   192  
   193  		var corePolicy core.PreemptionPolicy
   194  		if preemptionPolicy != nil {
   195  			corePolicy = core.PreemptionPolicy(*preemptionPolicy)
   196  			if pod.Spec.PreemptionPolicy != nil && *pod.Spec.PreemptionPolicy != corePolicy {
   197  				return admission.NewForbidden(a, fmt.Errorf("the string value of PreemptionPolicy (%s) must not be provided in pod spec; priority admission controller computed %s from the given PriorityClass name", *pod.Spec.PreemptionPolicy, corePolicy))
   198  			}
   199  			pod.Spec.PreemptionPolicy = &corePolicy
   200  		}
   201  	}
   202  	return nil
   203  }
   204  
   205  // validatePriorityClass ensures that the value field is not larger than the highest user definable priority. If the GlobalDefault is set, it ensures that there is no other PriorityClass whose GlobalDefault is set.
   206  func (p *Plugin) validatePriorityClass(a admission.Attributes) error {
   207  	operation := a.GetOperation()
   208  	pc, ok := a.GetObject().(*scheduling.PriorityClass)
   209  	if !ok {
   210  		return errors.NewBadRequest("resource was marked with kind PriorityClass but was unable to be converted")
   211  	}
   212  	// If the new PriorityClass tries to be the default priority, make sure that no other priority class is marked as default.
   213  	if pc.GlobalDefault {
   214  		dpc, err := p.getDefaultPriorityClass()
   215  		if err != nil {
   216  			return fmt.Errorf("failed to get default priority class: %v", err)
   217  		}
   218  		if dpc != nil {
   219  			// Throw an error if a second default priority class is being created, or an existing priority class is being marked as default, while another default already exists.
   220  			if operation == admission.Create || (operation == admission.Update && dpc.GetName() != pc.GetName()) {
   221  				return admission.NewForbidden(a, fmt.Errorf("PriorityClass %v is already marked as default. Only one default can exist", dpc.GetName()))
   222  			}
   223  		}
   224  	}
   225  	return nil
   226  }
   227  
   228  func (p *Plugin) getDefaultPriorityClass() (*schedulingv1.PriorityClass, error) {
   229  	list, err := p.lister.List(labels.Everything())
   230  	if err != nil {
   231  		return nil, err
   232  	}
   233  	// In case more than one global default priority class is added as a result of a race condition,
   234  	// we pick the one with the lowest priority value.
   235  	var defaultPC *schedulingv1.PriorityClass
   236  	for _, pci := range list {
   237  		if pci.GlobalDefault {
   238  			if defaultPC == nil || defaultPC.Value > pci.Value {
   239  				defaultPC = pci
   240  			}
   241  		}
   242  	}
   243  	return defaultPC, nil
   244  }
   245  
   246  func (p *Plugin) getDefaultPriority() (string, int32, *apiv1.PreemptionPolicy, error) {
   247  	dpc, err := p.getDefaultPriorityClass()
   248  	if err != nil {
   249  		return "", 0, nil, err
   250  	}
   251  	if dpc != nil {
   252  		return dpc.Name, dpc.Value, dpc.PreemptionPolicy, nil
   253  	}
   254  	preemptLowerPriority := apiv1.PreemptLowerPriority
   255  	return "", int32(scheduling.DefaultPriorityWhenNoDefaultClassExists), &preemptLowerPriority, nil
   256  }
   257  

View as plain text