admission.go 8.7 KB

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