Skip to content

Commit

Permalink
Support Task-level Resources Requirements
Browse files Browse the repository at this point in the history
Support Task-level Resources Requirements
  • Loading branch information
austinzhao-go committed May 19, 2022
1 parent 6f633b2 commit 6847b3f
Show file tree
Hide file tree
Showing 9 changed files with 282 additions and 2 deletions.
41 changes: 39 additions & 2 deletions pkg/apis/pipeline/v1beta1/openapi_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions pkg/apis/pipeline/v1beta1/pipelinerun_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"time"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"

"github.com/tektoncd/pipeline/pkg/apis/config"
Expand Down Expand Up @@ -600,6 +601,11 @@ type PipelineTaskRunSpec struct {
StepOverrides []TaskRunStepOverride `json:"stepOverrides,omitempty"`
// +listType=atomic
SidecarOverrides []TaskRunSidecarOverride `json:"sidecarOverrides,omitempty"`
// Compute Resources required by this container.
// Cannot be updated.
// More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
// +optional
Resources corev1.ResourceRequirements `json:"resources,omitempty" protobuf:"bytes,8,opt,name=resources"`
}

// GetTaskRunSpec returns the task specific spec for a given
Expand Down
10 changes: 10 additions & 0 deletions pkg/apis/pipeline/v1beta1/resource_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ type TaskResources struct {
// DeclaredPipelineResources to the input PipelineResources required by the Task.
// +listType=atomic
Outputs []TaskResource `json:"outputs,omitempty"`
// Limits describes the maximum amount of compute resources allowed.
// More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
// +optional
Limits v1.ResourceList `json:"limits,omitempty"`
// Requests describes the minimum amount of compute resources required.
// If Requests is omitted for a container, it defaults to Limits if that is explicitly specified,
// otherwise to an implementation-defined value.
// More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
// +optional
Requests v1.ResourceList `json:"requests,omitempty"`
}

// TaskResource defines an input or output Resource declared as a requirement
Expand Down
16 changes: 16 additions & 0 deletions pkg/apis/pipeline/v1beta1/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -2424,6 +2424,14 @@
},
"x-kubernetes-list-type": "atomic"
},
"limits": {
"description": "Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/",
"type": "object",
"additionalProperties": {
"default": {},
"$ref": "#/definitions/k8s.io.apimachinery.pkg.api.resource.Quantity"
}
},
"outputs": {
"description": "Outputs holds the mapping from the PipelineResources declared in DeclaredPipelineResources to the input PipelineResources required by the Task.",
"type": "array",
Expand All @@ -2432,6 +2440,14 @@
"$ref": "#/definitions/v1beta1.TaskResource"
},
"x-kubernetes-list-type": "atomic"
},
"requests": {
"description": "Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/",
"type": "object",
"additionalProperties": {
"default": {},
"$ref": "#/definitions/k8s.io.apimachinery.pkg.api.resource.Quantity"
}
}
}
},
Expand Down
37 changes: 37 additions & 0 deletions pkg/apis/pipeline/v1beta1/task_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ func (ts *TaskSpec) Validate(ctx context.Context) (errs *apis.FieldError) {
errs = errs.Also(ValidateParameterTypes(ctx, ts.Params).ViaField("params"))
errs = errs.Also(ValidateParameterVariables(ts.Steps, ts.Params))
errs = errs.Also(ValidateResourcesVariables(ts.Steps, ts.Resources))
errs = errs.Also(ValidateResourcesRequirements(ctx, ts))
errs = errs.Also(validateTaskContextVariables(ts.Steps))
errs = errs.Also(validateResults(ctx, ts.Results).ViaField("results"))
return errs
Expand Down Expand Up @@ -370,6 +371,42 @@ func ValidateResourcesVariables(steps []Step, resources *TaskResources) *apis.Fi
return validateVariables(steps, "resources.(?:inputs|outputs)", resourceNames)
}

// ValidateResourcesRequirements() validates if only step-level or task-level resources requirements are configured
func ValidateResourcesRequirements(ctx context.Context, taskSpec *TaskSpec) *apis.FieldError {
if isResourcesRequirementsInTaskLevel(taskSpec) {
if err := ValidateEnabledAPIFields(ctx, "task-level resources requirements", config.AlphaAPIFields); err != nil {
return err
}
}

if isResourcesRequirementsInStepLevel(taskSpec) && isResourcesRequirementsInTaskLevel(taskSpec) {
return &apis.FieldError{
Message: "TaskSpec can't be configured with both step-level(Task.spec.step.resources or Task.spec.stepTemplate.resources) and task-level(Task.spec.resources) resources requirements",
Paths: []string{"resources"},
}
}

return nil
}

// isResourcesRequirementsInStepLevel() checks if any resources requirements specified under step-level
func isResourcesRequirementsInStepLevel(taskSpec *TaskSpec) bool {
for _, step := range taskSpec.Steps {
if step.Resources.Size() > 0 {
return true
}
}
return taskSpec.StepTemplate != nil && taskSpec.StepTemplate.Resources.Size() > 0
}

// isResourcesRequirementsInTaskLevel() checks if any resources requirements specified under task-level
func isResourcesRequirementsInTaskLevel(taskSpec *TaskSpec) bool {
if taskSpec == nil || taskSpec.Resources == nil {
return false
}
return taskSpec.Resources.Limits != nil || taskSpec.Resources.Requests != nil
}

// TODO (@chuangw6): Make sure an object param is not used as a whole when providing values for strings.
// https://github.com/tektoncd/community/blob/main/teps/0075-object-param-and-result-types.md#variable-replacement-with-object-params
// "When providing values for strings, Task and Pipeline authors can access
Expand Down
116 changes: 116 additions & 0 deletions pkg/apis/pipeline/v1beta1/task_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
"github.com/tektoncd/pipeline/test/diff"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"knative.dev/pkg/apis"
)
Expand Down Expand Up @@ -372,7 +373,72 @@ func TestTaskSpecValidate(t *testing.T) {
hello "$(context.taskRun.namespace)"`,
}},
},
}, {
name: "valid step-level(step.resources) resources requirements",
fields: fields{
Steps: []v1beta1.Step{{
Name: "step-1",
Image: "my-image",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("2"),
},
},
}},
},
}, {
name: "valid step-level(stepTemplate.resources) resources requirements",
fields: fields{
Steps: []v1beta1.Step{{
Name: "step-1",
Image: "my-image",
}},
StepTemplate: &v1beta1.StepTemplate{
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("2"),
},
},
},
},
}, {
name: "valid step-level(step.resources and stepTemplate.resources) resources requirements",
fields: fields{
Steps: []v1beta1.Step{{
Name: "step-1",
Image: "my-image",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("1"),
},
},
}},
StepTemplate: &v1beta1.StepTemplate{
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("1"),
},
},
},
},
}, {
name: "valid task-level(spec.resources) resources requirements",
fields: fields{
Steps: []v1beta1.Step{{
Name: "step-1",
Image: "my-image",
}},
Resources: &v1beta1.TaskResources{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("4"),
},
Limits: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("8"),
},
},
},
}}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ts := &v1beta1.TaskSpec{
Expand Down Expand Up @@ -1115,6 +1181,56 @@ func TestTaskSpecValidateError(t *testing.T) {
Message: "invalid value: -10s",
Paths: []string{"steps[0].negative timeout"},
},
}, {
name: "invalid both step-level(step.resources) and task-level resources requirements",
fields: fields{
Steps: []v1beta1.Step{{
Name: "step-1",
Image: "my-image",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("2"),
},
},
}},
Resources: &v1beta1.TaskResources{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("4"),
},
Limits: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("8"),
},
},
}, expectedError: apis.FieldError{
Message: "TaskSpec can't be configured with both step-level(Task.spec.step.resources or Task.spec.stepTemplate.resources) and task-level(Task.spec.resources) resources requirements",
Paths: []string{"resources"},
},
}, {
name: "invalid both step-level(stepTemplate.resources) and task-level resources requirements",
fields: fields{
Steps: []v1beta1.Step{{
Name: "step-1",
Image: "my-image",
}},
StepTemplate: &v1beta1.StepTemplate{
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("1"),
},
},
},
Resources: &v1beta1.TaskResources{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("4"),
},
Limits: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("8"),
},
},
}, expectedError: apis.FieldError{
Message: "TaskSpec can't be configured with both step-level(Task.spec.step.resources or Task.spec.stepTemplate.resources) and task-level(Task.spec.resources) resources requirements",
Paths: []string{"resources"},
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
14 changes: 14 additions & 0 deletions pkg/apis/pipeline/v1beta1/taskrun_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ func (ts *TaskRunSpec) Validate(ctx context.Context) (errs *apis.FieldError) {
if ts.StepOverrides != nil {
errs = errs.Also(ValidateEnabledAPIFields(ctx, "stepOverrides", config.AlphaAPIFields).ViaField("stepOverrides"))
errs = errs.Also(validateStepOverrides(ts.StepOverrides).ViaField("stepOverrides"))
errs = errs.Also(validateStepOverridesResourcesRequirements(ts.TaskSpec, ts.StepOverrides).ViaField("stepOverrides"))
}
if ts.SidecarOverrides != nil {
errs = errs.Also(ValidateEnabledAPIFields(ctx, "sidecarOverrides", config.AlphaAPIFields).ViaField("sidecarOverrides"))
Expand Down Expand Up @@ -138,6 +139,19 @@ func validateStepOverrides(overrides []TaskRunStepOverride) (errs *apis.FieldErr
return errs
}

// validateStepOverridesResourcesRequirements() validates if both step-level and task-level resources requirements are configured
func validateStepOverridesResourcesRequirements(taskSpec *TaskSpec, overrides []TaskRunStepOverride) (errs *apis.FieldError) {
for _, override := range overrides {
if override.Resources.Size() > 0 && isResourcesRequirementsInTaskLevel(taskSpec) {
return &apis.FieldError{
Message: "TaskRun can't be configured with both step-level(stepOverrides.resources) and task-level(taskSpec.resources) resources requirements",
Paths: []string{"resources"},
}
}
}
return nil
}

func validateSidecarOverrides(overrides []TaskRunSidecarOverride) (errs *apis.FieldError) {
var names []string
for i, o := range overrides {
Expand Down
29 changes: 29 additions & 0 deletions pkg/apis/pipeline/v1beta1/taskrun_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,36 @@ func TestTaskRunSpec_Invalidate(t *testing.T) {
},
wantErr: apis.ErrMissingField("sidecarOverrides[0].name"),
wc: enableAlphaAPIFields,
}, {
name: "invalid both step-level(stepOverrides.resources) and task-level(taskSpec.resources) resources requirements",
spec: v1beta1.TaskRunSpec{
TaskSpec: &v1beta1.TaskSpec{
Steps: []v1beta1.Step{{
Name: "step-1",
Image: "my-image",
}},
Resources: &v1beta1.TaskResources{
Requests: corev1.ResourceList{
corev1.ResourceMemory: corev1resources.MustParse("2Gi"),
},
},
},
StepOverrides: []v1beta1.TaskRunStepOverride{{
Name: "foo",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceMemory: corev1resources.MustParse("1Gi"),
},
},
}},
},
wantErr: &apis.FieldError{
Message: "TaskRun can't be configured with both step-level(stepOverrides.resources) and task-level(taskSpec.resources) resources requirements",
Paths: []string{"stepOverrides.resources"},
},
wc: enableAlphaAPIFields,
}}

for _, ts := range tests {
t.Run(ts.name, func(t *testing.T) {
ctx := context.Background()
Expand Down
Loading

0 comments on commit 6847b3f

Please sign in to comment.