package runonceduration import ( "bytes" "testing" "k8s.io/kubernetes/pkg/admission" kapi "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/client/cache" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" oadmission "github.com/openshift/origin/pkg/cmd/server/admission" projectcache "github.com/openshift/origin/pkg/project/cache" "github.com/openshift/origin/pkg/quota/admission/runonceduration/api" _ "github.com/openshift/origin/pkg/api/install" ) func testCache(projectAnnotations map[string]string) *projectcache.ProjectCache { kclient := &fake.Clientset{} pCache := projectcache.NewFake(kclient.Core().Namespaces(), projectcache.NewCacheStore(cache.MetaNamespaceKeyFunc), "") ns := &kapi.Namespace{} ns.Name = "default" ns.Annotations = projectAnnotations pCache.Store.Add(ns) return pCache } func testConfig(n *int64) *api.RunOnceDurationConfig { return &api.RunOnceDurationConfig{ ActiveDeadlineSecondsLimit: n, } } func testRunOncePod() *kapi.Pod { pod := &kapi.Pod{} pod.Spec.RestartPolicy = kapi.RestartPolicyNever return pod } func testRestartOnFailurePod() *kapi.Pod { pod := &kapi.Pod{} pod.Spec.RestartPolicy = kapi.RestartPolicyOnFailure return pod } func testRunOncePodWithDuration(n int64) *kapi.Pod { pod := testRunOncePod() pod.Spec.ActiveDeadlineSeconds = &n return pod } func testRestartAlwaysPod() *kapi.Pod { pod := &kapi.Pod{} pod.Spec.RestartPolicy = kapi.RestartPolicyAlways return pod } func int64p(n int64) *int64 { return &n } func TestRunOnceDurationAdmit(t *testing.T) { tests := []struct { name string config *api.RunOnceDurationConfig pod *kapi.Pod projectAnnotations map[string]string expectedActiveDeadlineSeconds *int64 }{ { name: "expect globally configured duration to be set", config: testConfig(int64p(10)), pod: testRunOncePod(), expectedActiveDeadlineSeconds: int64p(10), }, { name: "empty config, no duration to be set", config: testConfig(nil), pod: testRunOncePod(), expectedActiveDeadlineSeconds: nil, }, { name: "expect configured duration to not limit lower existing duration", config: testConfig(int64p(10)), pod: testRunOncePodWithDuration(5), expectedActiveDeadlineSeconds: int64p(5), }, { name: "expect empty config to not limit existing duration", config: testConfig(nil), pod: testRunOncePodWithDuration(5), expectedActiveDeadlineSeconds: int64p(5), }, { name: "expect project limit to be used with nil global value", config: testConfig(nil), pod: testRunOncePodWithDuration(2000), projectAnnotations: map[string]string{ api.ActiveDeadlineSecondsLimitAnnotation: "1000", }, expectedActiveDeadlineSeconds: int64p(1000), }, { name: "expect project limit to not limit a smaller set value", config: testConfig(nil), pod: testRunOncePodWithDuration(10), projectAnnotations: map[string]string{ api.ActiveDeadlineSecondsLimitAnnotation: "1000", }, expectedActiveDeadlineSeconds: int64p(10), }, { name: "expect project limit to have priority over global config value", config: testConfig(int64p(10)), pod: testRunOncePodWithDuration(2000), projectAnnotations: map[string]string{ api.ActiveDeadlineSecondsLimitAnnotation: "1000", }, expectedActiveDeadlineSeconds: int64p(1000), }, { name: "make no change to a pod that is not a run-once pod", config: testConfig(int64p(10)), pod: testRestartAlwaysPod(), expectedActiveDeadlineSeconds: nil, }, { name: "update a pod that has a RestartOnFailure policy", config: testConfig(int64p(10)), pod: testRestartOnFailurePod(), expectedActiveDeadlineSeconds: int64p(10), }, } for _, tc := range tests { runOnceDuration := NewRunOnceDuration(tc.config) runOnceDuration.(oadmission.WantsProjectCache).SetProjectCache(testCache(tc.projectAnnotations)) pod := tc.pod attrs := admission.NewAttributesRecord(pod, nil, kapi.Kind("Pod").WithVersion("version"), "default", "test", kapi.Resource("pods").WithVersion("version"), "", admission.Create, nil) err := runOnceDuration.Admit(attrs) if err != nil { t.Errorf("%s: unexpected admission error: %v", tc.name, err) continue } switch { case tc.expectedActiveDeadlineSeconds == nil && pod.Spec.ActiveDeadlineSeconds == nil: // continue case tc.expectedActiveDeadlineSeconds == nil && pod.Spec.ActiveDeadlineSeconds != nil: t.Errorf("%s: expected nil ActiveDeadlineSeconds. Got: %d", tc.name, *pod.Spec.ActiveDeadlineSeconds) case tc.expectedActiveDeadlineSeconds != nil && pod.Spec.ActiveDeadlineSeconds == nil: t.Errorf("%s: unexpected nil ActiveDeadlineSeconds.", tc.name) case *pod.Spec.ActiveDeadlineSeconds != *tc.expectedActiveDeadlineSeconds: t.Errorf("%s: unexpected active deadline seconds: %d", tc.name, *pod.Spec.ActiveDeadlineSeconds) } } } func TestReadConfig(t *testing.T) { configStr := `apiVersion: v1 kind: RunOnceDurationConfig activeDeadlineSecondsOverride: 3600 ` buf := bytes.NewBufferString(configStr) config, err := readConfig(buf) if err != nil { t.Fatalf("unexpected error reading config: %v", err) } if config.ActiveDeadlineSecondsLimit == nil { t.Fatalf("nil value for ActiveDeadlineSecondsLimit") } if *config.ActiveDeadlineSecondsLimit != 3600 { t.Errorf("unexpected value for ActiveDeadlineSecondsLimit: %d", config.ActiveDeadlineSecondsLimit) } } func TestInt64MinP(t *testing.T) { ten := int64(10) twenty := int64(20) tests := []struct { a, b, expected *int64 }{ { a: &ten, b: nil, expected: &ten, }, { a: nil, b: &ten, expected: &ten, }, { a: &ten, b: &twenty, expected: &ten, }, { a: nil, b: nil, expected: nil, }, } for _, test := range tests { actual := int64MinP(test.a, test.b) switch { case actual == nil && test.expected != nil, test.expected == nil && actual != nil: t.Errorf("unexpected %v for %#v", actual, test) continue case actual == nil: continue case *actual != *test.expected: t.Errorf("unexpected: %v for %#v", actual, test) } } }