Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
262 lines (226 sloc) 8.07 KB
/*
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 (
"context"
"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(ctx context.Context, 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(ctx, a, o)
}
// Validate ensures that the pod node selector is allowed
func (p *Plugin) Validate(ctx context.Context, 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{}
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
}
You can’t perform that action at this time.