Skip to content

Commit

Permalink
Change RunOnce duration plugin to act as a limit instead of override
Browse files Browse the repository at this point in the history
  • Loading branch information
csrwng committed Mar 30, 2016
1 parent 9fcbe9e commit 2d2601a
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 60 deletions.
33 changes: 23 additions & 10 deletions pkg/quota/admission/runonceduration/admission.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func readConfig(reader io.Reader) (*api.RunOnceDurationConfig, error) {
// NewRunOnceDuration creates a new RunOnceDuration admission plugin
func NewRunOnceDuration(config *api.RunOnceDurationConfig) admission.Interface {
return &runOnceDuration{
Handler: admission.NewHandler(admission.Create, admission.Update),
Handler: admission.NewHandler(admission.Create),
config: config,
}
}
Expand Down Expand Up @@ -84,13 +84,13 @@ func (a *runOnceDuration) Admit(attributes admission.Attributes) error {
return nil
}

appliedProjectOverride, err := a.applyProjectAnnotationOverride(attributes.GetNamespace(), pod)
appliedProjectLimit, err := a.applyProjectAnnotationLimit(attributes.GetNamespace(), pod)
if err != nil {
return admission.NewForbidden(attributes, err)
}

if !appliedProjectOverride && a.config.ActiveDeadlineSecondsOverride != nil {
pod.Spec.ActiveDeadlineSeconds = a.config.ActiveDeadlineSecondsOverride
if !appliedProjectLimit && a.config.ActiveDeadlineSecondsLimit != nil {
pod.Spec.ActiveDeadlineSeconds = minInt64(a.config.ActiveDeadlineSecondsLimit, pod.Spec.ActiveDeadlineSeconds)
}
return nil
}
Expand All @@ -106,22 +106,35 @@ func (a *runOnceDuration) Validate() error {
return nil
}

func (a *runOnceDuration) applyProjectAnnotationOverride(namespace string, pod *kapi.Pod) (bool, error) {
func (a *runOnceDuration) applyProjectAnnotationLimit(namespace string, pod *kapi.Pod) (bool, error) {
ns, err := a.cache.GetNamespace(namespace)
if err != nil {
return false, fmt.Errorf("error looking up pod namespace: %v", err)
}
if ns.Annotations == nil {
return false, nil
}
override, hasOverride := ns.Annotations[api.ActiveDeadlineSecondsOverrideAnnotation]
if !hasOverride {
limit, hasLimit := ns.Annotations[api.ActiveDeadlineSecondsLimitAnnotation]
if !hasLimit {
return false, nil
}
overrideInt64, err := strconv.ParseInt(override, 10, 64)
limitInt64, err := strconv.ParseInt(limit, 10, 64)
if err != nil {
return false, fmt.Errorf("cannot parse the ActiveDeadlineSeconds override (%s) for project %s: %v", override, ns.Name, err)
return false, fmt.Errorf("cannot parse the ActiveDeadlineSeconds limit (%s) for project %s: %v", limit, ns.Name, err)
}
pod.Spec.ActiveDeadlineSeconds = &overrideInt64
pod.Spec.ActiveDeadlineSeconds = minInt64(&limitInt64, pod.Spec.ActiveDeadlineSeconds)
return true, nil
}

func minInt64(a, b *int64) *int64 {
switch {
case a == nil:
return b
case b == nil:
return a
case *a < *b:
return a
default:
return b
}
}
75 changes: 60 additions & 15 deletions pkg/quota/admission/runonceduration/admission_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func testCache(projectAnnotations map[string]string) *projectcache.ProjectCache

func testConfig(n *int64) *api.RunOnceDurationConfig {
return &api.RunOnceDurationConfig{
ActiveDeadlineSecondsOverride: n,
ActiveDeadlineSecondsLimit: n,
}
}

Expand Down Expand Up @@ -81,32 +81,41 @@ func TestRunOnceDurationAdmit(t *testing.T) {
expectedActiveDeadlineSeconds: nil,
},
{
name: "expect configured duration to override existing duration",
name: "expect configured duration to not limit lower existing duration",
config: testConfig(int64p(10)),
pod: testRunOncePodWithDuration(5),
expectedActiveDeadlineSeconds: int64p(10),
expectedActiveDeadlineSeconds: int64p(5),
},
{
name: "expect empty config to not override existing duration",
name: "expect empty config to not limit existing duration",
config: testConfig(nil),
pod: testRunOncePodWithDuration(5),
expectedActiveDeadlineSeconds: int64p(5),
},
{
name: "expect project override to be used with nil global value",
name: "expect project limit to be used with nil global value",
config: testConfig(nil),
pod: testRunOncePodWithDuration(5),
pod: testRunOncePodWithDuration(2000),
projectAnnotations: map[string]string{
api.ActiveDeadlineSecondsOverrideAnnotation: "1000",
api.ActiveDeadlineSecondsLimitAnnotation: "1000",
},
expectedActiveDeadlineSeconds: int64p(1000),
},
{
name: "expect project override to have priority over global config value",
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(5),
pod: testRunOncePodWithDuration(2000),
projectAnnotations: map[string]string{
api.ActiveDeadlineSecondsOverrideAnnotation: "1000",
api.ActiveDeadlineSecondsLimitAnnotation: "1000",
},
expectedActiveDeadlineSeconds: int64p(1000),
},
Expand Down Expand Up @@ -150,17 +159,53 @@ func TestRunOnceDurationAdmit(t *testing.T) {
func TestReadConfig(t *testing.T) {
configStr := `apiVersion: v1
kind: RunOnceDurationConfig
activeDeadlineSecondsOverride: 3600
activeDeadlineSecondsLimit: 3600
`
buf := bytes.NewBufferString(configStr)
config, err := readConfig(buf)
if err != nil {
t.Fatalf("unexpected error reading config: %v", err)
}
if config.ActiveDeadlineSecondsOverride == nil {
t.Fatalf("nil value for ActiveDeadlineSecondsOverride")
if config.ActiveDeadlineSecondsLimit == nil {
t.Fatalf("nil value for ActiveDeadlineSecondsLimit")
}
if *config.ActiveDeadlineSecondsLimit != 3600 {
t.Errorf("unexpected value for ActiveDeadlineSecondsLimit: %d", config.ActiveDeadlineSecondsLimit)
}
}

func TestMinInt64(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,
},
}
if *config.ActiveDeadlineSecondsOverride != 3600 {
t.Errorf("unexpected value for ActiveDeadlineSecondsOverride: %d", config.ActiveDeadlineSecondsOverride)

for _, test := range tests {
actual := minInt64(test.a, test.b)
if actual != test.expected {
t.Errorf("unexpected: %v for %#v", actual, test)
}
}
}
10 changes: 5 additions & 5 deletions pkg/quota/admission/runonceduration/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ import (
)

// RunOnceDurationConfig is the configuration for the RunOnceDuration plugin.
// It specifies a default override value for ActiveDeadlineSeconds for a run-once pod.
// It specifies a maximum value for ActiveDeadlineSeconds for a run-once pod.
// The project that contains the pod may specify a different setting. That setting will
// take precedence over the one configured for the plugin here.
type RunOnceDurationConfig struct {
unversioned.TypeMeta

// ActiveDeadlineSecondsOverride is the value to set on containers of run-once pods
// ActiveDeadlineSecondsLimit is the maximum value to set on containers of run-once pods
// Only a positive value is valid. Absence of a value means that the plugin
// won't make any changes to the pod
ActiveDeadlineSecondsOverride *int64
ActiveDeadlineSecondsLimit *int64
}

// ActiveDeadlineSecondsOverrideAnnotation can be set on a project to override the number of
// ActiveDeadlineSecondsLimitAnnotation can be set on a project to limit the number of
// seconds that a run-once pod can be active in that project
const ActiveDeadlineSecondsOverrideAnnotation = "openshift.io/active-deadline-seconds-override"
const ActiveDeadlineSecondsLimitAnnotation = "openshift.io/active-deadline-seconds-limit"
4 changes: 2 additions & 2 deletions pkg/quota/admission/runonceduration/api/v1/swagger_doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ package v1
// ==== DO NOT EDIT THIS FILE MANUALLY ====

var map_RunOnceDurationConfig = map[string]string{
"": "RunOnceDurationConfig is the configuration for the RunOnceDuration plugin. It specifies a default override value for ActiveDeadlineSeconds for a run-once pod. The project that contains the pod may specify a different setting. That setting will take precedence over the one configured for the plugin here.",
"activeDeadlineSecondsOverride": "ActiveDeadlineSecondsOverride is the value to set on containers of run-once pods Only a positive value is valid. Absence of a value means that the plugin won't make any changes to the pod",
"": "RunOnceDurationConfig is the configuration for the RunOnceDuration plugin. It specifies a maximum value for ActiveDeadlineSeconds for a run-once pod. The project that contains the pod may specify a different setting. That setting will take precedence over the one configured for the plugin here.",
"activeDeadlineSecondsLimit": "ActiveDeadlineSecondsLimit is the maximum value to set on containers of run-once pods Only a positive value is valid. Absence of a value means that the plugin won't make any changes to the pod",
}

func (RunOnceDurationConfig) SwaggerDoc() map[string]string {
Expand Down
6 changes: 3 additions & 3 deletions pkg/quota/admission/runonceduration/api/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import (
)

// RunOnceDurationConfig is the configuration for the RunOnceDuration plugin.
// It specifies a default override value for ActiveDeadlineSeconds for a run-once pod.
// It specifies a maximum value for ActiveDeadlineSeconds for a run-once pod.
// The project that contains the pod may specify a different setting. That setting will
// take precedence over the one configured for the plugin here.
type RunOnceDurationConfig struct {
unversioned.TypeMeta `json:",inline"`

// ActiveDeadlineSecondsOverride is the value to set on containers of run-once pods
// ActiveDeadlineSecondsLimit is the maximum value to set on containers of run-once pods
// Only a positive value is valid. Absence of a value means that the plugin
// won't make any changes to the pod
ActiveDeadlineSecondsOverride *int64 `json:"activeDeadlineSecondsOverride,omitempty",description:"value to override activeDeadlineSeconds in run-once pods"`
ActiveDeadlineSecondsLimit *int64 `json:"activeDeadlineSecondsLimit,omitempty",description:"maximum value for activeDeadlineSeconds in run-once pods"`
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import (
// ValidateRunOnceDurationConfig validates the RunOnceDuration plugin configuration
func ValidateRunOnceDurationConfig(config *api.RunOnceDurationConfig) field.ErrorList {
allErrs := field.ErrorList{}
if config == nil || config.ActiveDeadlineSecondsOverride == nil {
if config == nil || config.ActiveDeadlineSecondsLimit == nil {
return allErrs
}
if *config.ActiveDeadlineSecondsOverride <= 0 {
allErrs = append(allErrs, field.Invalid(field.NewPath("activeDeadlineSecondsOverride"), config.ActiveDeadlineSecondsOverride, "must be greater than 0"))
if *config.ActiveDeadlineSecondsLimit <= 0 {
allErrs = append(allErrs, field.Invalid(field.NewPath("activeDeadlineSecondsLimit"), config.ActiveDeadlineSecondsLimit, "must be greater than 0"))
}
return allErrs
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ func TestRunOnceDurationConfigValidation(t *testing.T) {
// Check invalid duration returns an error
var invalidSecs int64 = -1
invalidConfig := &api.RunOnceDurationConfig{
ActiveDeadlineSecondsOverride: &invalidSecs,
ActiveDeadlineSecondsLimit: &invalidSecs,
}
errs := ValidateRunOnceDurationConfig(invalidConfig)
if len(errs) == 0 {
Expand All @@ -20,7 +20,7 @@ func TestRunOnceDurationConfigValidation(t *testing.T) {
// Check that valid duration returns no error
var validSecs int64 = 5
validConfig := &api.RunOnceDurationConfig{
ActiveDeadlineSecondsOverride: &validSecs,
ActiveDeadlineSecondsLimit: &validSecs,
}
errs = ValidateRunOnceDurationConfig(validConfig)
if len(errs) > 0 {
Expand Down
51 changes: 31 additions & 20 deletions test/integration/runonce_duration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,50 +15,61 @@ import (
testserver "github.com/openshift/origin/test/util/server"
)

func testRunOnceDurationPod() *kapi.Pod {
func testRunOnceDurationPod(activeDeadlineSeconds int64) *kapi.Pod {
pod := &kapi.Pod{}
pod.Name = "testpod"
pod.GenerateName = "testpod"
pod.Spec.RestartPolicy = kapi.RestartPolicyNever
pod.Spec.Containers = []kapi.Container{
{
Name: "container",
Image: "test/image",
},
}
if activeDeadlineSeconds > 0 {
pod.Spec.ActiveDeadlineSeconds = &activeDeadlineSeconds
}
return pod
}

func testPodDuration(t *testing.T, name string, kclient kclient.Interface, pod *kapi.Pod, expected int64) {
// Pod with no duration set
pod, err := kclient.Pods(testutil.Namespace()).Create(pod)
if err != nil {
t.Fatalf("%s: unexpected: %v", name, err)
}
if pod.Spec.ActiveDeadlineSeconds == nil {
t.Errorf("%s: unexpected nil value for pod.Spec.ActiveDeadlineSeconds", name)
return
}
if *pod.Spec.ActiveDeadlineSeconds != expected {
t.Errorf("%s: unexpected value for pod.Spec.ActiveDeadlineSeconds: %d. Expected: %d", name, *pod.Spec.ActiveDeadlineSeconds, expected)
}
}

func TestRunOnceDurationAdmissionPlugin(t *testing.T) {
var secs int64 = 3600
config := &pluginapi.RunOnceDurationConfig{
ActiveDeadlineSecondsOverride: &secs,
ActiveDeadlineSecondsLimit: &secs,
}
kclient := setupRunOnceDurationTest(t, config, nil)
pod, err := kclient.Pods(testutil.Namespace()).Create(testRunOnceDurationPod())
if err != nil {
t.Fatalf("Unexpected: %v", err)
}
if pod.Spec.ActiveDeadlineSeconds == nil || *pod.Spec.ActiveDeadlineSeconds != 3600 {
t.Errorf("Unexpected value for pod.ActiveDeadlineSeconds %v", pod.Spec.ActiveDeadlineSeconds)
}

testPodDuration(t, "global, no duration", kclient, testRunOnceDurationPod(0), 3600)
testPodDuration(t, "global, larger duration", kclient, testRunOnceDurationPod(7200), 3600)
testPodDuration(t, "global, smaller duration", kclient, testRunOnceDurationPod(100), 100)
}

func TestRunOnceDurationAdmissionPluginProjectOverride(t *testing.T) {
func TestRunOnceDurationAdmissionPluginProjectLimit(t *testing.T) {
var secs int64 = 3600
config := &pluginapi.RunOnceDurationConfig{
ActiveDeadlineSecondsOverride: &secs,
ActiveDeadlineSecondsLimit: &secs,
}
nsAnnotations := map[string]string{
pluginapi.ActiveDeadlineSecondsOverrideAnnotation: "100",
pluginapi.ActiveDeadlineSecondsLimitAnnotation: "100",
}
kclient := setupRunOnceDurationTest(t, config, nsAnnotations)
pod, err := kclient.Pods(testutil.Namespace()).Create(testRunOnceDurationPod())
if err != nil {
t.Fatalf("Unexpected: %v", err)
}
if pod.Spec.ActiveDeadlineSeconds == nil || *pod.Spec.ActiveDeadlineSeconds != 100 {
t.Errorf("Unexpected value for pod.ActiveDeadlineSeconds %v", pod.Spec.ActiveDeadlineSeconds)
}
testPodDuration(t, "project, no duration", kclient, testRunOnceDurationPod(0), 100)
testPodDuration(t, "project, larger duration", kclient, testRunOnceDurationPod(7200), 100)
testPodDuration(t, "project, smaller duration", kclient, testRunOnceDurationPod(50), 50)
}

func setupRunOnceDurationTest(t *testing.T, pluginConfig *pluginapi.RunOnceDurationConfig, nsAnnotations map[string]string) kclient.Interface {
Expand Down

0 comments on commit 2d2601a

Please sign in to comment.