blob: 8404467c82cf66d924bae1c8c2f797a36af6b751 [file] [log] [blame]
/*
Copyright 2017 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 priority
import (
"testing"
"k8s.io/klog"
schedulingv1beta1 "k8s.io/api/scheduling/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/authentication/user"
utilfeature "k8s.io/apiserver/pkg/util/feature"
utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing"
"k8s.io/client-go/informers"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/scheduling"
"k8s.io/kubernetes/pkg/apis/scheduling/v1beta1"
"k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/features"
)
func addPriorityClasses(ctrl *priorityPlugin, priorityClasses []*scheduling.PriorityClass) error {
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
ctrl.SetExternalKubeInformerFactory(informerFactory)
// First add the existing classes to the cache.
for _, c := range priorityClasses {
s := &schedulingv1beta1.PriorityClass{}
if err := v1beta1.Convert_scheduling_PriorityClass_To_v1beta1_PriorityClass(c, s, nil); err != nil {
return err
}
informerFactory.Scheduling().V1beta1().PriorityClasses().Informer().GetStore().Add(s)
}
return nil
}
var defaultClass1 = &scheduling.PriorityClass{
TypeMeta: metav1.TypeMeta{
Kind: "PriorityClass",
},
ObjectMeta: metav1.ObjectMeta{
Name: "default1",
},
Value: 1000,
GlobalDefault: true,
}
var defaultClass2 = &scheduling.PriorityClass{
TypeMeta: metav1.TypeMeta{
Kind: "PriorityClass",
},
ObjectMeta: metav1.ObjectMeta{
Name: "default2",
},
Value: 2000,
GlobalDefault: true,
}
var nondefaultClass1 = &scheduling.PriorityClass{
TypeMeta: metav1.TypeMeta{
Kind: "PriorityClass",
},
ObjectMeta: metav1.ObjectMeta{
Name: "nondefault1",
},
Value: 2000,
Description: "Just a test priority class",
}
var systemClusterCritical = &scheduling.PriorityClass{
TypeMeta: metav1.TypeMeta{
Kind: "PriorityClass",
},
ObjectMeta: metav1.ObjectMeta{
Name: scheduling.SystemClusterCritical,
},
Value: scheduling.SystemCriticalPriority,
GlobalDefault: true,
}
func TestPriorityClassAdmission(t *testing.T) {
var systemClass = &scheduling.PriorityClass{
TypeMeta: metav1.TypeMeta{
Kind: "PriorityClass",
},
ObjectMeta: metav1.ObjectMeta{
Name: scheduling.SystemPriorityClassPrefix + "test",
},
Value: scheduling.HighestUserDefinablePriority + 1,
Description: "Name has system critical prefix",
}
tests := []struct {
name string
existingClasses []*scheduling.PriorityClass
newClass *scheduling.PriorityClass
userInfo user.Info
expectError bool
}{
{
"one default class",
[]*scheduling.PriorityClass{},
defaultClass1,
nil,
false,
},
{
"more than one default classes",
[]*scheduling.PriorityClass{defaultClass1},
defaultClass2,
nil,
true,
},
{
"system name and value are allowed by admission controller",
[]*scheduling.PriorityClass{},
systemClass,
&user.DefaultInfo{
Name: user.APIServerUser,
},
false,
},
}
for _, test := range tests {
klog.V(4).Infof("starting test %q", test.name)
ctrl := newPlugin()
// Add existing priority classes.
if err := addPriorityClasses(ctrl, test.existingClasses); err != nil {
t.Errorf("Test %q: unable to add object to informer: %v", test.name, err)
}
// Now add the new class.
attrs := admission.NewAttributesRecord(
test.newClass,
nil,
scheduling.Kind("PriorityClass").WithVersion("version"),
"",
"",
scheduling.Resource("priorityclasses").WithVersion("version"),
"",
admission.Create,
false,
test.userInfo,
)
err := ctrl.Validate(attrs)
klog.Infof("Got %v", err)
if err != nil && !test.expectError {
t.Errorf("Test %q: unexpected error received: %v", test.name, err)
}
if err == nil && test.expectError {
t.Errorf("Test %q: expected error and no error recevied", test.name)
}
}
}
// TestDefaultPriority tests that default priority is resolved correctly.
func TestDefaultPriority(t *testing.T) {
pcResource := scheduling.Resource("priorityclasses").WithVersion("version")
pcKind := scheduling.Kind("PriorityClass").WithVersion("version")
updatedDefaultClass1 := *defaultClass1
updatedDefaultClass1.GlobalDefault = false
tests := []struct {
name string
classesBefore []*scheduling.PriorityClass
classesAfter []*scheduling.PriorityClass
attributes admission.Attributes
expectedDefaultBefore int32
expectedDefaultNameBefore string
expectedDefaultAfter int32
expectedDefaultNameAfter string
}{
{
name: "simple resolution with a default class",
classesBefore: []*scheduling.PriorityClass{defaultClass1},
classesAfter: []*scheduling.PriorityClass{defaultClass1},
attributes: nil,
expectedDefaultBefore: defaultClass1.Value,
expectedDefaultNameBefore: defaultClass1.Name,
expectedDefaultAfter: defaultClass1.Value,
expectedDefaultNameAfter: defaultClass1.Name,
},
{
name: "add a default class",
classesBefore: []*scheduling.PriorityClass{nondefaultClass1},
classesAfter: []*scheduling.PriorityClass{nondefaultClass1, defaultClass1},
attributes: admission.NewAttributesRecord(defaultClass1, nil, pcKind, "", defaultClass1.Name, pcResource, "", admission.Create, false, nil),
expectedDefaultBefore: scheduling.DefaultPriorityWhenNoDefaultClassExists,
expectedDefaultNameBefore: "",
expectedDefaultAfter: defaultClass1.Value,
expectedDefaultNameAfter: defaultClass1.Name,
},
{
name: "multiple default classes resolves to the minimum value among them",
classesBefore: []*scheduling.PriorityClass{defaultClass1, defaultClass2},
classesAfter: []*scheduling.PriorityClass{defaultClass2},
attributes: admission.NewAttributesRecord(nil, nil, pcKind, "", defaultClass1.Name, pcResource, "", admission.Delete, false, nil),
expectedDefaultBefore: defaultClass1.Value,
expectedDefaultNameBefore: defaultClass1.Name,
expectedDefaultAfter: defaultClass2.Value,
expectedDefaultNameAfter: defaultClass2.Name,
},
{
name: "delete default priority class",
classesBefore: []*scheduling.PriorityClass{defaultClass1},
classesAfter: []*scheduling.PriorityClass{},
attributes: admission.NewAttributesRecord(nil, nil, pcKind, "", defaultClass1.Name, pcResource, "", admission.Delete, false, nil),
expectedDefaultBefore: defaultClass1.Value,
expectedDefaultNameBefore: defaultClass1.Name,
expectedDefaultAfter: scheduling.DefaultPriorityWhenNoDefaultClassExists,
expectedDefaultNameAfter: "",
},
{
name: "update default class and remove its global default",
classesBefore: []*scheduling.PriorityClass{defaultClass1},
classesAfter: []*scheduling.PriorityClass{&updatedDefaultClass1},
attributes: admission.NewAttributesRecord(&updatedDefaultClass1, defaultClass1, pcKind, "", defaultClass1.Name, pcResource, "", admission.Update, false, nil),
expectedDefaultBefore: defaultClass1.Value,
expectedDefaultNameBefore: defaultClass1.Name,
expectedDefaultAfter: scheduling.DefaultPriorityWhenNoDefaultClassExists,
expectedDefaultNameAfter: "",
},
}
for _, test := range tests {
klog.V(4).Infof("starting test %q", test.name)
ctrl := newPlugin()
if err := addPriorityClasses(ctrl, test.classesBefore); err != nil {
t.Errorf("Test %q: unable to add object to informer: %v", test.name, err)
}
pcName, defaultPriority, err := ctrl.getDefaultPriority()
if err != nil {
t.Errorf("Test %q: unexpected error while getting default priority: %v", test.name, err)
}
if err == nil &&
(defaultPriority != test.expectedDefaultBefore || pcName != test.expectedDefaultNameBefore) {
t.Errorf("Test %q: expected default priority %s(%d), but got %s(%d)",
test.name, test.expectedDefaultNameBefore, test.expectedDefaultBefore, pcName, defaultPriority)
}
if test.attributes != nil {
err := ctrl.Validate(test.attributes)
if err != nil {
t.Errorf("Test %q: unexpected error received: %v", test.name, err)
}
}
if err := addPriorityClasses(ctrl, test.classesAfter); err != nil {
t.Errorf("Test %q: unable to add object to informer: %v", test.name, err)
}
pcName, defaultPriority, err = ctrl.getDefaultPriority()
if err != nil {
t.Errorf("Test %q: unexpected error while getting default priority: %v", test.name, err)
}
if err == nil &&
(defaultPriority != test.expectedDefaultAfter || pcName != test.expectedDefaultNameAfter) {
t.Errorf("Test %q: expected default priority %s(%d), but got %s(%d)",
test.name, test.expectedDefaultNameAfter, test.expectedDefaultAfter, pcName, defaultPriority)
}
}
}
var zeroPriority = int32(0)
var intPriority = int32(1000)
func TestPodAdmission(t *testing.T) {
containerName := "container"
pods := []*api.Pod{
// pod[0]: Pod with a proper priority class.
{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-w-priorityclass",
Namespace: "namespace",
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: containerName,
},
},
PriorityClassName: "default1",
},
},
// pod[1]: Pod with no priority class
{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-wo-priorityclass",
Namespace: "namespace",
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: containerName,
},
},
},
},
// pod[2]: Pod with non-existing priority class
{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-w-non-existing-priorityclass",
Namespace: "namespace",
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: containerName,
},
},
PriorityClassName: "non-existing",
},
},
// pod[3]: Pod with integer value of priority
{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-w-integer-priority",
Namespace: "namespace",
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: containerName,
},
},
PriorityClassName: "default1",
Priority: &intPriority,
},
},
// pod[4]: Pod with a system priority class name
{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-w-system-priority",
Namespace: metav1.NamespaceSystem,
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: containerName,
},
},
PriorityClassName: scheduling.SystemClusterCritical,
},
},
// pod[5]: mirror Pod with a system priority class name
{
ObjectMeta: metav1.ObjectMeta{
Name: "mirror-pod-w-system-priority",
Namespace: metav1.NamespaceSystem,
Annotations: map[string]string{api.MirrorPodAnnotationKey: ""},
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: containerName,
},
},
PriorityClassName: "system-cluster-critical",
},
},
// pod[6]: mirror Pod with integer value of priority
{
ObjectMeta: metav1.ObjectMeta{
Name: "mirror-pod-w-integer-priority",
Namespace: "namespace",
Annotations: map[string]string{api.MirrorPodAnnotationKey: ""},
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: containerName,
},
},
PriorityClassName: "default1",
Priority: &intPriority,
},
},
// pod[7]: Pod with a critical priority annotation. This needs to be automatically assigned
// system-cluster-critical
{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-w-system-priority",
Namespace: "kube-system",
Annotations: map[string]string{"scheduler.alpha.kubernetes.io/critical-pod": ""},
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: containerName,
},
},
},
},
// pod[8]: Pod with a system priority class name in non-system namespace
{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-w-system-priority-in-nonsystem-namespace",
Namespace: "non-system-namespace",
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: containerName,
},
},
PriorityClassName: scheduling.SystemClusterCritical,
},
},
// pod[9]: Pod with a priority value that matches the resolved priority
{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-w-zero-priority-in-nonsystem-namespace",
Namespace: "non-system-namespace",
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: containerName,
},
},
Priority: &zeroPriority,
},
},
// pod[10]: Pod with a priority value that matches the resolved default priority
{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-w-priority-matching-default-priority",
Namespace: "non-system-namespace",
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: containerName,
},
},
Priority: &defaultClass2.Value,
},
},
// pod[11]: Pod with a priority value that matches the resolved priority
{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-w-priority-matching-resolved-default-priority",
Namespace: metav1.NamespaceSystem,
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: containerName,
},
},
PriorityClassName: systemClusterCritical.Name,
Priority: &systemClusterCritical.Value,
},
},
}
// Enable PodPriority feature gate.
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodPriority, true)()
// Enable ExperimentalCriticalPodAnnotation feature gate.
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExperimentalCriticalPodAnnotation, true)()
tests := []struct {
name string
existingClasses []*scheduling.PriorityClass
// Admission controller changes pod spec. So, we take an api.Pod instead of
// *api.Pod to avoid interfering with other tests.
pod api.Pod
expectedPriority int32
expectError bool
}{
{
"Pod with priority class",
[]*scheduling.PriorityClass{defaultClass1, nondefaultClass1},
*pods[0],
1000,
false,
},
{
"Pod without priority class",
[]*scheduling.PriorityClass{defaultClass1},
*pods[1],
1000,
false,
},
{
"pod without priority class and no existing priority class",
[]*scheduling.PriorityClass{},
*pods[1],
scheduling.DefaultPriorityWhenNoDefaultClassExists,
false,
},
{
"pod without priority class and no default class",
[]*scheduling.PriorityClass{nondefaultClass1},
*pods[1],
scheduling.DefaultPriorityWhenNoDefaultClassExists,
false,
},
{
"pod with a system priority class",
[]*scheduling.PriorityClass{systemClusterCritical},
*pods[4],
scheduling.SystemCriticalPriority,
false,
},
{
"Pod with non-existing priority class",
[]*scheduling.PriorityClass{defaultClass1, nondefaultClass1},
*pods[2],
0,
true,
},
{
"pod with integer priority",
[]*scheduling.PriorityClass{},
*pods[3],
0,
true,
},
{
"mirror pod with system priority class",
[]*scheduling.PriorityClass{systemClusterCritical},
*pods[5],
scheduling.SystemCriticalPriority,
false,
},
{
"mirror pod with integer priority",
[]*scheduling.PriorityClass{},
*pods[6],
0,
true,
},
{
"pod with critical pod annotation",
[]*scheduling.PriorityClass{systemClusterCritical},
*pods[7],
scheduling.SystemCriticalPriority,
false,
},
{
"pod with system critical priority in non-system namespace",
[]*scheduling.PriorityClass{systemClusterCritical},
*pods[8],
scheduling.SystemCriticalPriority,
true,
},
{
"pod with priority that matches computed priority",
[]*scheduling.PriorityClass{nondefaultClass1},
*pods[9],
0,
false,
},
{
"pod with priority that matches default priority",
[]*scheduling.PriorityClass{defaultClass2},
*pods[10],
defaultClass2.Value,
false,
},
{
"pod with priority that matches resolved priority",
[]*scheduling.PriorityClass{systemClusterCritical},
*pods[11],
systemClusterCritical.Value,
false,
},
}
for _, test := range tests {
klog.V(4).Infof("starting test %q", test.name)
ctrl := newPlugin()
// Add existing priority classes.
if err := addPriorityClasses(ctrl, test.existingClasses); err != nil {
t.Errorf("Test %q: unable to add object to informer: %v", test.name, err)
}
// Create pod.
attrs := admission.NewAttributesRecord(
&test.pod,
nil,
api.Kind("Pod").WithVersion("version"),
test.pod.ObjectMeta.Namespace,
"",
api.Resource("pods").WithVersion("version"),
"",
admission.Create,
false,
nil,
)
err := ctrl.Admit(attrs)
klog.Infof("Got %v", err)
if !test.expectError {
if err != nil {
t.Errorf("Test %q: unexpected error received: %v", test.name, err)
} else if *test.pod.Spec.Priority != test.expectedPriority {
t.Errorf("Test %q: expected priority is %d, but got %d.", test.name, test.expectedPriority, *test.pod.Spec.Priority)
}
}
if err == nil && test.expectError {
t.Errorf("Test %q: expected error and no error recevied", test.name)
}
}
}