Merge pull request #55058 from deads2k/admission-11-split-doubles

Automatic merge from submit-queue (batch tested with PRs 53273, 55058, 55237, 50140). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

split some admission plugins into mutation and validation halves

Splits the podnodeselector, serviceaccount, and priority admission plugins into validating and mutating admission plugins.

@kubernetes/sig-api-machinery-pr-reviews
This commit is contained in:
Kubernetes Submit Queue 2017-11-07 09:39:39 -08:00 committed by GitHub
commit 26986e8407
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 111 additions and 54 deletions

View File

@ -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,26 +95,9 @@ 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() != "" {
// only run the checks below on pods proper and not subresources
return nil
}
obj := a.GetObject()
pod, ok := obj.(*api.Pod)
if !ok {
glog.Errorf("expected pod but got %s", a.GetKind().Kind)
return nil
}
if !p.WaitForReady() {
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
}
updateInitialized, err := util.IsUpdatingInitializedObject(a) updateInitialized, err := util.IsUpdatingInitializedObject(a)
if err != nil { if err != nil {
return err return err
@ -121,51 +106,96 @@ func (p *podNodeSelector) Admit(a admission.Attributes) error {
// node selector of an initialized pod is immutable // node selector of an initialized pod is immutable
return nil return nil
} }
if !p.WaitForReady() {
name := pod.Name return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
nsName := a.GetNamespace()
var namespace *api.Namespace
namespace, err = p.namespaceLister.Get(nsName)
if errors.IsNotFound(err) {
namespace, err = p.defaultGetNamespace(nsName)
if err != nil {
if errors.IsNotFound(err) {
return err
}
return errors.NewInternalError(err)
}
} else if err != nil {
return errors.NewInternalError(err)
} }
namespaceNodeSelector, err := p.getNodeSelectorMap(namespace) 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 labels.Conflicts(namespaceNodeSelector, labels.Set(pod.Spec.NodeSelector)) { if labels.Conflicts(namespaceNodeSelector, labels.Set(pod.Spec.NodeSelector)) {
return errors.NewForbidden(resource, name, fmt.Errorf("pod node label selector conflicts with its namespace node label selector")) return errors.NewForbidden(resource, pod.Name, fmt.Errorf("pod node label selector conflicts with its namespace node label selector"))
}
whitelist, err := labels.ConvertSelectorToLabelsMap(p.clusterNodeSelectors[namespace.Name])
if err != nil {
return err
} }
// Merge pod node selector = namespace node selector + current pod node selector // Merge pod node selector = namespace node selector + current pod node selector
// second selector wins
podNodeSelectorLabels := labels.Merge(namespaceNodeSelector, pod.Spec.NodeSelector) podNodeSelectorLabels := labels.Merge(namespaceNodeSelector, pod.Spec.NodeSelector)
pod.Spec.NodeSelector = map[string]string(podNodeSelectorLabels)
return p.Validate(a)
}
// whitelist verification // Validate ensures that the pod node selector is allowed
if !labels.AreLabelsInWhiteList(podNodeSelectorLabels, whitelist) { func (p *podNodeSelector) Validate(a admission.Attributes) error {
return errors.NewForbidden(resource, name, fmt.Errorf("pod node label selector labels conflict with its namespace whitelist")) 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"))
} }
// Updated pod node selector = namespace node selector + current pod node selector
pod.Spec.NodeSelector = map[string]string(podNodeSelectorLabels)
return nil return nil
} }
func (p *podNodeSelector) 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 {
glog.Errorf("expected pod but got %s", a.GetKind().Kind)
return true
}
return false
}
func NewPodNodeSelector(clusterNodeSelectors map[string]string) *podNodeSelector { func NewPodNodeSelector(clusterNodeSelectors map[string]string) *podNodeSelector {
return &podNodeSelector{ return &podNodeSelector{
Handler: admission.NewHandler(admission.Create, admission.Update), Handler: admission.NewHandler(admission.Create, admission.Update),

View File

@ -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) {
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)
}
} }
} }

View File

@ -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 (
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 {
} }
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 {
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 {

View File

@ -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) {
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)
} }