admission.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. /*
  2. Copyright 2016 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 podnodeselector
  14. import (
  15. "fmt"
  16. "io"
  17. "reflect"
  18. "k8s.io/klog"
  19. corev1 "k8s.io/api/core/v1"
  20. "k8s.io/apimachinery/pkg/api/errors"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. "k8s.io/apimachinery/pkg/labels"
  23. "k8s.io/apimachinery/pkg/util/yaml"
  24. "k8s.io/apiserver/pkg/admission"
  25. genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer"
  26. "k8s.io/client-go/informers"
  27. "k8s.io/client-go/kubernetes"
  28. corev1listers "k8s.io/client-go/listers/core/v1"
  29. api "k8s.io/kubernetes/pkg/apis/core"
  30. )
  31. // NamespaceNodeSelectors is for assigning node selectors labels to
  32. // namespaces. Default value is the annotation key
  33. // scheduler.alpha.kubernetes.io/node-selector
  34. var NamespaceNodeSelectors = []string{"scheduler.alpha.kubernetes.io/node-selector"}
  35. // PluginName is a string with the name of the plugin
  36. const PluginName = "PodNodeSelector"
  37. // Register registers a plugin
  38. func Register(plugins *admission.Plugins) {
  39. plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
  40. // TODO move this to a versioned configuration file format.
  41. pluginConfig := readConfig(config)
  42. plugin := NewPodNodeSelector(pluginConfig.PodNodeSelectorPluginConfig)
  43. return plugin, nil
  44. })
  45. }
  46. // Plugin is an implementation of admission.Interface.
  47. type Plugin struct {
  48. *admission.Handler
  49. client kubernetes.Interface
  50. namespaceLister corev1listers.NamespaceLister
  51. // global default node selector and namespace whitelists in a cluster.
  52. clusterNodeSelectors map[string]string
  53. }
  54. var _ = genericadmissioninitializer.WantsExternalKubeClientSet(&Plugin{})
  55. var _ = genericadmissioninitializer.WantsExternalKubeInformerFactory(&Plugin{})
  56. type pluginConfig struct {
  57. PodNodeSelectorPluginConfig map[string]string
  58. }
  59. // readConfig reads default value of clusterDefaultNodeSelector
  60. // from the file provided with --admission-control-config-file
  61. // If the file is not supplied, it defaults to ""
  62. // The format in a file:
  63. // podNodeSelectorPluginConfig:
  64. // clusterDefaultNodeSelector: <node-selectors-labels>
  65. // namespace1: <node-selectors-labels>
  66. // namespace2: <node-selectors-labels>
  67. func readConfig(config io.Reader) *pluginConfig {
  68. defaultConfig := &pluginConfig{}
  69. if config == nil || reflect.ValueOf(config).IsNil() {
  70. return defaultConfig
  71. }
  72. d := yaml.NewYAMLOrJSONDecoder(config, 4096)
  73. for {
  74. if err := d.Decode(defaultConfig); err != nil {
  75. if err != io.EOF {
  76. continue
  77. }
  78. }
  79. break
  80. }
  81. return defaultConfig
  82. }
  83. // Admit enforces that pod and its namespace node label selectors matches at least a node in the cluster.
  84. func (p *Plugin) Admit(a admission.Attributes, o admission.ObjectInterfaces) error {
  85. if shouldIgnore(a) {
  86. return nil
  87. }
  88. if !p.WaitForReady() {
  89. return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
  90. }
  91. resource := a.GetResource().GroupResource()
  92. pod := a.GetObject().(*api.Pod)
  93. namespaceNodeSelector, err := p.getNamespaceNodeSelectorMap(a.GetNamespace())
  94. if err != nil {
  95. return err
  96. }
  97. if labels.Conflicts(namespaceNodeSelector, labels.Set(pod.Spec.NodeSelector)) {
  98. return errors.NewForbidden(resource, pod.Name, fmt.Errorf("pod node label selector conflicts with its namespace node label selector"))
  99. }
  100. // Merge pod node selector = namespace node selector + current pod node selector
  101. // second selector wins
  102. podNodeSelectorLabels := labels.Merge(namespaceNodeSelector, pod.Spec.NodeSelector)
  103. pod.Spec.NodeSelector = map[string]string(podNodeSelectorLabels)
  104. return p.Validate(a, o)
  105. }
  106. // Validate ensures that the pod node selector is allowed
  107. func (p *Plugin) Validate(a admission.Attributes, o admission.ObjectInterfaces) error {
  108. if shouldIgnore(a) {
  109. return nil
  110. }
  111. if !p.WaitForReady() {
  112. return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
  113. }
  114. resource := a.GetResource().GroupResource()
  115. pod := a.GetObject().(*api.Pod)
  116. namespaceNodeSelector, err := p.getNamespaceNodeSelectorMap(a.GetNamespace())
  117. if err != nil {
  118. return err
  119. }
  120. if labels.Conflicts(namespaceNodeSelector, labels.Set(pod.Spec.NodeSelector)) {
  121. return errors.NewForbidden(resource, pod.Name, fmt.Errorf("pod node label selector conflicts with its namespace node label selector"))
  122. }
  123. // whitelist verification
  124. whitelist, err := labels.ConvertSelectorToLabelsMap(p.clusterNodeSelectors[a.GetNamespace()])
  125. if err != nil {
  126. return err
  127. }
  128. if !labels.AreLabelsInWhiteList(pod.Spec.NodeSelector, whitelist) {
  129. return errors.NewForbidden(resource, pod.Name, fmt.Errorf("pod node label selector labels conflict with its namespace whitelist"))
  130. }
  131. return nil
  132. }
  133. func (p *Plugin) getNamespaceNodeSelectorMap(namespaceName string) (labels.Set, error) {
  134. namespace, err := p.namespaceLister.Get(namespaceName)
  135. if errors.IsNotFound(err) {
  136. namespace, err = p.defaultGetNamespace(namespaceName)
  137. if err != nil {
  138. if errors.IsNotFound(err) {
  139. return nil, err
  140. }
  141. return nil, errors.NewInternalError(err)
  142. }
  143. } else if err != nil {
  144. return nil, errors.NewInternalError(err)
  145. }
  146. return p.getNodeSelectorMap(namespace)
  147. }
  148. func shouldIgnore(a admission.Attributes) bool {
  149. resource := a.GetResource().GroupResource()
  150. if resource != api.Resource("pods") {
  151. return true
  152. }
  153. if a.GetSubresource() != "" {
  154. // only run the checks below on pods proper and not subresources
  155. return true
  156. }
  157. _, ok := a.GetObject().(*api.Pod)
  158. if !ok {
  159. klog.Errorf("expected pod but got %s", a.GetKind().Kind)
  160. return true
  161. }
  162. return false
  163. }
  164. // NewPodNodeSelector initializes a podNodeSelector
  165. func NewPodNodeSelector(clusterNodeSelectors map[string]string) *Plugin {
  166. return &Plugin{
  167. Handler: admission.NewHandler(admission.Create),
  168. clusterNodeSelectors: clusterNodeSelectors,
  169. }
  170. }
  171. // SetExternalKubeClientSet sets the plugin's client
  172. func (p *Plugin) SetExternalKubeClientSet(client kubernetes.Interface) {
  173. p.client = client
  174. }
  175. // SetExternalKubeInformerFactory configures the plugin's informer factory
  176. func (p *Plugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
  177. namespaceInformer := f.Core().V1().Namespaces()
  178. p.namespaceLister = namespaceInformer.Lister()
  179. p.SetReadyFunc(namespaceInformer.Informer().HasSynced)
  180. }
  181. // ValidateInitialization verifies the object has been properly initialized
  182. func (p *Plugin) ValidateInitialization() error {
  183. if p.namespaceLister == nil {
  184. return fmt.Errorf("missing namespaceLister")
  185. }
  186. if p.client == nil {
  187. return fmt.Errorf("missing client")
  188. }
  189. return nil
  190. }
  191. func (p *Plugin) defaultGetNamespace(name string) (*corev1.Namespace, error) {
  192. namespace, err := p.client.CoreV1().Namespaces().Get(name, metav1.GetOptions{})
  193. if err != nil {
  194. return nil, fmt.Errorf("namespace %s does not exist", name)
  195. }
  196. return namespace, nil
  197. }
  198. func (p *Plugin) getNodeSelectorMap(namespace *corev1.Namespace) (labels.Set, error) {
  199. selector := labels.Set{}
  200. labelsMap := labels.Set{}
  201. var err error
  202. found := false
  203. if len(namespace.ObjectMeta.Annotations) > 0 {
  204. for _, annotation := range NamespaceNodeSelectors {
  205. if ns, ok := namespace.ObjectMeta.Annotations[annotation]; ok {
  206. labelsMap, err = labels.ConvertSelectorToLabelsMap(ns)
  207. if err != nil {
  208. return labels.Set{}, err
  209. }
  210. if labels.Conflicts(selector, labelsMap) {
  211. nsName := namespace.ObjectMeta.Name
  212. return labels.Set{}, fmt.Errorf("%s annotations' node label selectors conflict", nsName)
  213. }
  214. selector = labels.Merge(selector, labelsMap)
  215. found = true
  216. }
  217. }
  218. }
  219. if !found {
  220. selector, err = labels.ConvertSelectorToLabelsMap(p.clusterNodeSelectors["clusterDefaultNodeSelector"])
  221. if err != nil {
  222. return labels.Set{}, err
  223. }
  224. }
  225. return selector, nil
  226. }