123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- /*
- Copyright 2016 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package podnodeselector
- import (
- "fmt"
- "io"
- "reflect"
- "k8s.io/klog"
- corev1 "k8s.io/api/core/v1"
- "k8s.io/apimachinery/pkg/api/errors"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/labels"
- "k8s.io/apimachinery/pkg/util/yaml"
- "k8s.io/apiserver/pkg/admission"
- genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer"
- "k8s.io/client-go/informers"
- "k8s.io/client-go/kubernetes"
- corev1listers "k8s.io/client-go/listers/core/v1"
- api "k8s.io/kubernetes/pkg/apis/core"
- )
- // NamespaceNodeSelectors is for assigning node selectors labels to
- // namespaces. Default value is the annotation key
- // scheduler.alpha.kubernetes.io/node-selector
- var NamespaceNodeSelectors = []string{"scheduler.alpha.kubernetes.io/node-selector"}
- // PluginName is a string with the name of the plugin
- const PluginName = "PodNodeSelector"
- // Register registers a plugin
- func Register(plugins *admission.Plugins) {
- plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
- // TODO move this to a versioned configuration file format.
- pluginConfig := readConfig(config)
- plugin := NewPodNodeSelector(pluginConfig.PodNodeSelectorPluginConfig)
- return plugin, nil
- })
- }
- // Plugin is an implementation of admission.Interface.
- type Plugin struct {
- *admission.Handler
- client kubernetes.Interface
- namespaceLister corev1listers.NamespaceLister
- // global default node selector and namespace whitelists in a cluster.
- clusterNodeSelectors map[string]string
- }
- var _ = genericadmissioninitializer.WantsExternalKubeClientSet(&Plugin{})
- var _ = genericadmissioninitializer.WantsExternalKubeInformerFactory(&Plugin{})
- type pluginConfig struct {
- PodNodeSelectorPluginConfig map[string]string
- }
- // readConfig reads default value of clusterDefaultNodeSelector
- // from the file provided with --admission-control-config-file
- // If the file is not supplied, it defaults to ""
- // The format in a file:
- // podNodeSelectorPluginConfig:
- // clusterDefaultNodeSelector: <node-selectors-labels>
- // namespace1: <node-selectors-labels>
- // namespace2: <node-selectors-labels>
- func readConfig(config io.Reader) *pluginConfig {
- defaultConfig := &pluginConfig{}
- if config == nil || reflect.ValueOf(config).IsNil() {
- return defaultConfig
- }
- d := yaml.NewYAMLOrJSONDecoder(config, 4096)
- for {
- if err := d.Decode(defaultConfig); err != nil {
- if err != io.EOF {
- continue
- }
- }
- break
- }
- return defaultConfig
- }
- // Admit enforces that pod and its namespace node label selectors matches at least a node in the cluster.
- func (p *Plugin) Admit(a admission.Attributes, o admission.ObjectInterfaces) error {
- if shouldIgnore(a) {
- return nil
- }
- if !p.WaitForReady() {
- return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
- }
- resource := a.GetResource().GroupResource()
- pod := a.GetObject().(*api.Pod)
- namespaceNodeSelector, err := p.getNamespaceNodeSelectorMap(a.GetNamespace())
- if err != nil {
- return err
- }
- if labels.Conflicts(namespaceNodeSelector, labels.Set(pod.Spec.NodeSelector)) {
- return errors.NewForbidden(resource, pod.Name, fmt.Errorf("pod node label selector conflicts with its namespace node label selector"))
- }
- // Merge pod node selector = namespace node selector + current pod node selector
- // second selector wins
- podNodeSelectorLabels := labels.Merge(namespaceNodeSelector, pod.Spec.NodeSelector)
- pod.Spec.NodeSelector = map[string]string(podNodeSelectorLabels)
- return p.Validate(a, o)
- }
- // Validate ensures that the pod node selector is allowed
- func (p *Plugin) Validate(a admission.Attributes, o admission.ObjectInterfaces) error {
- if shouldIgnore(a) {
- return nil
- }
- if !p.WaitForReady() {
- return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
- }
- resource := a.GetResource().GroupResource()
- pod := a.GetObject().(*api.Pod)
- namespaceNodeSelector, err := p.getNamespaceNodeSelectorMap(a.GetNamespace())
- if err != nil {
- return err
- }
- if labels.Conflicts(namespaceNodeSelector, labels.Set(pod.Spec.NodeSelector)) {
- return errors.NewForbidden(resource, pod.Name, fmt.Errorf("pod node label selector conflicts with its namespace node label selector"))
- }
- // whitelist verification
- whitelist, err := labels.ConvertSelectorToLabelsMap(p.clusterNodeSelectors[a.GetNamespace()])
- if err != nil {
- return err
- }
- if !labels.AreLabelsInWhiteList(pod.Spec.NodeSelector, whitelist) {
- return errors.NewForbidden(resource, pod.Name, fmt.Errorf("pod node label selector labels conflict with its namespace whitelist"))
- }
- return nil
- }
- func (p *Plugin) getNamespaceNodeSelectorMap(namespaceName string) (labels.Set, error) {
- namespace, err := p.namespaceLister.Get(namespaceName)
- if errors.IsNotFound(err) {
- namespace, err = p.defaultGetNamespace(namespaceName)
- if err != nil {
- if errors.IsNotFound(err) {
- return nil, err
- }
- return nil, errors.NewInternalError(err)
- }
- } else if err != nil {
- return nil, errors.NewInternalError(err)
- }
- return p.getNodeSelectorMap(namespace)
- }
- func shouldIgnore(a admission.Attributes) bool {
- resource := a.GetResource().GroupResource()
- if resource != api.Resource("pods") {
- return true
- }
- if a.GetSubresource() != "" {
- // only run the checks below on pods proper and not subresources
- return true
- }
- _, ok := a.GetObject().(*api.Pod)
- if !ok {
- klog.Errorf("expected pod but got %s", a.GetKind().Kind)
- return true
- }
- return false
- }
- // NewPodNodeSelector initializes a podNodeSelector
- func NewPodNodeSelector(clusterNodeSelectors map[string]string) *Plugin {
- return &Plugin{
- Handler: admission.NewHandler(admission.Create),
- clusterNodeSelectors: clusterNodeSelectors,
- }
- }
- // SetExternalKubeClientSet sets the plugin's client
- func (p *Plugin) SetExternalKubeClientSet(client kubernetes.Interface) {
- p.client = client
- }
- // SetExternalKubeInformerFactory configures the plugin's informer factory
- func (p *Plugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
- namespaceInformer := f.Core().V1().Namespaces()
- p.namespaceLister = namespaceInformer.Lister()
- p.SetReadyFunc(namespaceInformer.Informer().HasSynced)
- }
- // ValidateInitialization verifies the object has been properly initialized
- func (p *Plugin) ValidateInitialization() error {
- if p.namespaceLister == nil {
- return fmt.Errorf("missing namespaceLister")
- }
- if p.client == nil {
- return fmt.Errorf("missing client")
- }
- return nil
- }
- func (p *Plugin) defaultGetNamespace(name string) (*corev1.Namespace, error) {
- namespace, err := p.client.CoreV1().Namespaces().Get(name, metav1.GetOptions{})
- if err != nil {
- return nil, fmt.Errorf("namespace %s does not exist", name)
- }
- return namespace, nil
- }
- func (p *Plugin) getNodeSelectorMap(namespace *corev1.Namespace) (labels.Set, error) {
- selector := labels.Set{}
- labelsMap := labels.Set{}
- var err error
- found := false
- if len(namespace.ObjectMeta.Annotations) > 0 {
- for _, annotation := range NamespaceNodeSelectors {
- if ns, ok := namespace.ObjectMeta.Annotations[annotation]; ok {
- labelsMap, err = labels.ConvertSelectorToLabelsMap(ns)
- if err != nil {
- return labels.Set{}, err
- }
- if labels.Conflicts(selector, labelsMap) {
- nsName := namespace.ObjectMeta.Name
- return labels.Set{}, fmt.Errorf("%s annotations' node label selectors conflict", nsName)
- }
- selector = labels.Merge(selector, labelsMap)
- found = true
- }
- }
- }
- if !found {
- selector, err = labels.ConvertSelectorToLabelsMap(p.clusterNodeSelectors["clusterDefaultNodeSelector"])
- if err != nil {
- return labels.Set{}, err
- }
- }
- return selector, nil
- }
|