k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/plugin/pkg/admission/runtimeclass/admission.go (about) 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 len(pod.Spec.Overhead) > 0 && !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 }