Commit 2c671614 authored by David Eads's avatar David Eads

split some admission plugins into mutation and validation halves

parent 55e216f5
...@@ -59,6 +59,8 @@ type podNodeSelector struct { ...@@ -59,6 +59,8 @@ type podNodeSelector struct {
clusterNodeSelectors map[string]string clusterNodeSelectors map[string]string
} }
var _ admission.MutationInterface = &podNodeSelector{}
var _ admission.ValidationInterface = &podNodeSelector{}
var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&podNodeSelector{}) var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&podNodeSelector{})
var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&podNodeSelector{}) var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&podNodeSelector{})
...@@ -93,77 +95,105 @@ func readConfig(config io.Reader) *pluginConfig { ...@@ -93,77 +95,105 @@ func readConfig(config io.Reader) *pluginConfig {
// Admit enforces that pod and its namespace node label selectors matches at least a node in the cluster. // Admit enforces that pod and its namespace node label selectors matches at least a node in the cluster.
func (p *podNodeSelector) Admit(a admission.Attributes) error { func (p *podNodeSelector) Admit(a admission.Attributes) error {
resource := a.GetResource().GroupResource() if shouldIgnore(a) {
if resource != api.Resource("pods") {
return nil return nil
} }
if a.GetSubresource() != "" { updateInitialized, err := util.IsUpdatingInitializedObject(a)
// only run the checks below on pods proper and not subresources if err != nil {
return err
}
if updateInitialized {
// node selector of an initialized pod is immutable
return nil return nil
} }
if !p.WaitForReady() {
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
}
obj := a.GetObject() resource := a.GetResource().GroupResource()
pod, ok := obj.(*api.Pod) pod := a.GetObject().(*api.Pod)
if !ok { namespaceNodeSelector, err := p.getNamespaceNodeSelectorMap(a.GetNamespace())
glog.Errorf("expected pod but got %s", a.GetKind().Kind) if err != nil {
return 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)
}
// Validate ensures that the pod node selector is allowed
func (p *podNodeSelector) Validate(a admission.Attributes) error {
if shouldIgnore(a) {
return nil
}
if !p.WaitForReady() { if !p.WaitForReady() {
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request")) return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
} }
updateInitialized, err := util.IsUpdatingInitializedObject(a) resource := a.GetResource().GroupResource()
pod := a.GetObject().(*api.Pod)
namespaceNodeSelector, err := p.getNamespaceNodeSelectorMap(a.GetNamespace())
if err != nil { if err != nil {
return err return err
} }
if updateInitialized { if labels.Conflicts(namespaceNodeSelector, labels.Set(pod.Spec.NodeSelector)) {
// node selector of an initialized pod is immutable return errors.NewForbidden(resource, pod.Name, fmt.Errorf("pod node label selector conflicts with its namespace node label selector"))
return nil }
// 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"))
} }
name := pod.Name return nil
nsName := a.GetNamespace() }
var namespace *api.Namespace
namespace, err = p.namespaceLister.Get(nsName) func (p *podNodeSelector) getNamespaceNodeSelectorMap(namespaceName string) (labels.Set, error) {
namespace, err := p.namespaceLister.Get(namespaceName)
if errors.IsNotFound(err) { if errors.IsNotFound(err) {
namespace, err = p.defaultGetNamespace(nsName) namespace, err = p.defaultGetNamespace(namespaceName)
if err != nil { if err != nil {
if errors.IsNotFound(err) { if errors.IsNotFound(err) {
return err return nil, err
} }
return errors.NewInternalError(err) return nil, errors.NewInternalError(err)
} }
} else if err != nil { } else if err != nil {
return errors.NewInternalError(err) return nil, errors.NewInternalError(err)
} }
namespaceNodeSelector, err := p.getNodeSelectorMap(namespace) return p.getNodeSelectorMap(namespace)
if err != nil { }
return err
}
if labels.Conflicts(namespaceNodeSelector, labels.Set(pod.Spec.NodeSelector)) { func shouldIgnore(a admission.Attributes) bool {
return errors.NewForbidden(resource, name, fmt.Errorf("pod node label selector conflicts with its namespace node label selector")) resource := a.GetResource().GroupResource()
if resource != api.Resource("pods") {
return true
} }
if a.GetSubresource() != "" {
whitelist, err := labels.ConvertSelectorToLabelsMap(p.clusterNodeSelectors[namespace.Name]) // only run the checks below on pods proper and not subresources
if err != nil { return true
return err
} }
// Merge pod node selector = namespace node selector + current pod node selector _, ok := a.GetObject().(*api.Pod)
podNodeSelectorLabels := labels.Merge(namespaceNodeSelector, pod.Spec.NodeSelector) if !ok {
glog.Errorf("expected pod but got %s", a.GetKind().Kind)
// whitelist verification return true
if !labels.AreLabelsInWhiteList(podNodeSelectorLabels, whitelist) {
return errors.NewForbidden(resource, name, fmt.Errorf("pod node label selector labels conflict with its namespace whitelist"))
} }
// Updated pod node selector = namespace node selector + current pod node selector return false
pod.Spec.NodeSelector = map[string]string(podNodeSelectorLabels)
return nil
} }
func NewPodNodeSelector(clusterNodeSelectors map[string]string) *podNodeSelector { func NewPodNodeSelector(clusterNodeSelectors map[string]string) *podNodeSelector {
......
...@@ -175,6 +175,12 @@ func TestPodAdmission(t *testing.T) { ...@@ -175,6 +175,12 @@ func TestPodAdmission(t *testing.T) {
if test.admit && !labels.Equals(test.mergedNodeSelector, labels.Set(pod.Spec.NodeSelector)) { if test.admit && !labels.Equals(test.mergedNodeSelector, labels.Set(pod.Spec.NodeSelector)) {
t.Errorf("Test: %s, expected: %s but got: %s", test.testName, test.mergedNodeSelector, pod.Spec.NodeSelector) t.Errorf("Test: %s, expected: %s but got: %s", test.testName, test.mergedNodeSelector, pod.Spec.NodeSelector)
} }
err = handler.Validate(admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "testNamespace", namespace.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
if test.admit && err != nil {
t.Errorf("Test: %s, expected no error but got: %s", test.testName, err)
} else if !test.admit && err == nil {
t.Errorf("Test: %s, expected an error", test.testName)
}
// handles update of uninitialized pod like it's newly created. // handles update of uninitialized pod like it's newly created.
err = handler.Admit(admission.NewAttributesRecord(pod, &oldPod, api.Kind("Pod").WithVersion("version"), "testNamespace", namespace.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Update, nil)) err = handler.Admit(admission.NewAttributesRecord(pod, &oldPod, api.Kind("Pod").WithVersion("version"), "testNamespace", namespace.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
...@@ -186,6 +192,12 @@ func TestPodAdmission(t *testing.T) { ...@@ -186,6 +192,12 @@ func TestPodAdmission(t *testing.T) {
if test.admit && !labels.Equals(test.mergedNodeSelector, labels.Set(pod.Spec.NodeSelector)) { if test.admit && !labels.Equals(test.mergedNodeSelector, labels.Set(pod.Spec.NodeSelector)) {
t.Errorf("Test: %s, expected: %s but got: %s", test.testName, test.mergedNodeSelector, pod.Spec.NodeSelector) t.Errorf("Test: %s, expected: %s but got: %s", test.testName, test.mergedNodeSelector, pod.Spec.NodeSelector)
} }
err = handler.Validate(admission.NewAttributesRecord(pod, &oldPod, api.Kind("Pod").WithVersion("version"), "testNamespace", namespace.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
if test.admit && err != nil {
t.Errorf("Test: %s, expected no error but got: %s", test.testName, err)
} else if !test.admit && err == nil {
t.Errorf("Test: %s, expected an error", test.testName)
}
} }
} }
......
...@@ -64,6 +64,8 @@ type PriorityPlugin struct { ...@@ -64,6 +64,8 @@ type PriorityPlugin struct {
globalDefaultPriority *int32 globalDefaultPriority *int32
} }
var _ admission.MutationInterface = &PriorityPlugin{}
var _ admission.ValidationInterface = &PriorityPlugin{}
var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&PriorityPlugin{}) var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&PriorityPlugin{})
var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&PriorityPlugin{}) var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&PriorityPlugin{})
...@@ -102,11 +104,10 @@ var ( ...@@ -102,11 +104,10 @@ var (
priorityClassResource = scheduling.Resource("priorityclasses") priorityClassResource = scheduling.Resource("priorityclasses")
) )
// Admit checks Pods and PriorityClasses and admits or rejects them. It also resolves the priority of pods based on their PriorityClass. // Admit checks Pods and admits or rejects them. It also resolves the priority of pods based on their PriorityClass.
func (p *PriorityPlugin) Admit(a admission.Attributes) error { func (p *PriorityPlugin) Admit(a admission.Attributes) error {
operation := a.GetOperation() operation := a.GetOperation()
// Ignore all calls to subresources or resources other than pods. // Ignore all calls to subresources
// Ignore all operations other than Create and Update.
if len(a.GetSubresource()) != 0 { if len(a.GetSubresource()) != 0 {
return nil return nil
} }
...@@ -118,9 +119,23 @@ func (p *PriorityPlugin) Admit(a admission.Attributes) error { ...@@ -118,9 +119,23 @@ func (p *PriorityPlugin) Admit(a admission.Attributes) error {
} }
return nil return nil
default:
return nil
}
}
// Validate checks PriorityClasses and admits or rejects them.
func (p *PriorityPlugin) Validate(a admission.Attributes) error {
operation := a.GetOperation()
// Ignore all calls to subresources
if len(a.GetSubresource()) != 0 {
return nil
}
switch a.GetResource().GroupResource() {
case priorityClassResource: case priorityClassResource:
if operation == admission.Create || operation == admission.Update { if operation == admission.Create || operation == admission.Update {
return p.admitPriorityClass(a) return p.validatePriorityClass(a)
} }
if operation == admission.Delete { if operation == admission.Delete {
p.invalidateCachedDefaultPriority() p.invalidateCachedDefaultPriority()
...@@ -176,8 +191,8 @@ func (p *PriorityPlugin) admitPod(a admission.Attributes) error { ...@@ -176,8 +191,8 @@ func (p *PriorityPlugin) admitPod(a admission.Attributes) error {
return nil return nil
} }
// admitPriorityClass ensures that the value field is not larger than the highest user definable priority. If the GlobalDefault is set, it ensures that there is no other PriorityClass whose GlobalDefault is set. // validatePriorityClass ensures that the value field is not larger than the highest user definable priority. If the GlobalDefault is set, it ensures that there is no other PriorityClass whose GlobalDefault is set.
func (p *PriorityPlugin) admitPriorityClass(a admission.Attributes) error { func (p *PriorityPlugin) validatePriorityClass(a admission.Attributes) error {
operation := a.GetOperation() operation := a.GetOperation()
pc, ok := a.GetObject().(*scheduling.PriorityClass) pc, ok := a.GetObject().(*scheduling.PriorityClass)
if !ok { if !ok {
......
...@@ -147,7 +147,7 @@ func TestPriorityClassAdmission(t *testing.T) { ...@@ -147,7 +147,7 @@ func TestPriorityClassAdmission(t *testing.T) {
admission.Create, admission.Create,
nil, nil,
) )
err := ctrl.Admit(attrs) err := ctrl.Validate(attrs)
glog.Infof("Got %v", err) glog.Infof("Got %v", err)
if err != nil && !test.expectError { if err != nil && !test.expectError {
t.Errorf("Test %q: unexpected error received: %v", test.name, err) t.Errorf("Test %q: unexpected error received: %v", test.name, err)
...@@ -219,7 +219,7 @@ func TestDefaultPriority(t *testing.T) { ...@@ -219,7 +219,7 @@ func TestDefaultPriority(t *testing.T) {
t.Errorf("Test %q: expected default priority %d, but got %d", test.name, test.expectedDefaultBefore, defaultPriority) t.Errorf("Test %q: expected default priority %d, but got %d", test.name, test.expectedDefaultBefore, defaultPriority)
} }
if test.attributes != nil { if test.attributes != nil {
err := ctrl.Admit(test.attributes) err := ctrl.Validate(test.attributes)
if err != nil { if err != nil {
t.Errorf("Test %q: unexpected error received: %v", test.name, err) t.Errorf("Test %q: unexpected error received: %v", test.name, err)
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment