Skip to content

Commit

Permalink
TEP-0090: Matrix - Concurrency Control
Browse files Browse the repository at this point in the history
[TEP-0090: Matrix][tep-0090] proposed executing a `PipelineTask` in
parallel `TaskRuns` and `Runs` with substitutions from combinations
of `Parameters` in a `Matrix`.

In this change, we implement concurrency controls for the `Matrix`
as discussed in [TEP-0090: Matrix - Concurrency Control][cc]. The
default value is 256, and users can configure the value for their
own installations.

[tep-0090]: https://github.com/tektoncd/community/blob/main/teps/0090-matrix.md
[cc]: https://github.com/tektoncd/community/blob/main/teps/0090-matrix.md#concurrency-control
  • Loading branch information
jerop authored and tekton-robot committed Jun 8, 2022
1 parent 4c5a505 commit 88e8c8c
Show file tree
Hide file tree
Showing 10 changed files with 251 additions and 15 deletions.
4 changes: 4 additions & 0 deletions config/config-defaults.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,7 @@ data:
# but that a TaskRun does not explicitly provide.
# default-task-run-workspace-binding: |
# emptyDir: {}
# default-max-matrix-combinations-count contains the default maximum number
# of combinations from a Matrix, if none is specified.
default-max-matrix-combinations-count: "256"
3 changes: 3 additions & 0 deletions docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,8 @@ The example below customizes the following:
- the default Pod template to include a node selector to select the node where the Pod will be scheduled by default. A list of supported fields is available [here](https://github.com/tektoncd/pipeline/blob/main/docs/podtemplates.md#supported-fields).
For more information, see [`PodTemplate` in `TaskRuns`](./taskruns.md#specifying-a-pod-template) or [`PodTemplate` in `PipelineRuns`](./pipelineruns.md#specifying-a-pod-template).
- the default `Workspace` configuration can be set for any `Workspaces` that a Task declares but that a TaskRun does not explicitly provide
- the default maximum combinations of `Parameters` in a `Matrix` that can be used to fan out a `PipelineTask`. For
more information, see [`Matrix`](matrix.md).

```yaml
apiVersion: v1
Expand All @@ -333,6 +335,7 @@ data:
default-managed-by-label-value: "my-tekton-installation"
default-task-run-workspace-binding: |
emptyDir: {}
default-max-matrix-combinations-count: "1024"
```

**Note:** The `_example` key in the provided [config-defaults.yaml](./../config/config-defaults.yaml)
Expand Down
29 changes: 29 additions & 0 deletions docs/matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ weight: 11

- [Overview](#overview)
- [Configuring a Matrix](#configuring-a-matrix)
- [Concurrency Control](#concurrency-control)
- [Parameters](#parameters)
- [Context Variables](#context-variables)
- [Results](#results)
Expand All @@ -20,6 +21,11 @@ weight: 11
`Matrix` is used to fan out `Tasks` in a `Pipeline`. This doc will explain the details of `matrix` support in
Tekton.

Documentation for specifying `Matrix` in a `Pipeline`:
- [Specifying `Matrix` in `Tasks`](pipelines.md#specifying-matrix-in-pipelinetasks)
- [Specifying `Matrix` in `Finally Tasks`](pipelines.md#specifying-matrix-in-finally-tasks)
- [Specifying `Matrix` in `Custom Tasks`](pipelines.md#specifying-matrix)

> :seedling: **`Matrix` is an [alpha](install.md#alpha-features) feature.**
> The `enable-api-fields` feature flag must be set to `"alpha"` to specify `Matrix` in a `PipelineTask`.
>
Expand All @@ -29,8 +35,31 @@ Tekton.
## Configuring a Matrix

A `Matrix` supports the following features:
* [Concurrency Control](#concurrency-control)
* [Parameters](#parameters)
* [Context Variables](#context-variables)
* [Results](#results)

### Concurrency Control

The default maximum count of `TaskRuns` or `Runs` from a given `Matrix` is **256**. To customize the maximum count of
`TaskRuns` or `Runs` generated from a given `Matrix`, configure the `default-max-matrix-combinations-count` in
[config defaults](/config/config-defaults.yaml). When a `Matrix` in `PipelineTask` would generate more than the maximum
`TaskRuns` or `Runs`, the `Pipeline` validation would fail.

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: config-defaults
data:
default-service-account: "tekton"
default-timeout-minutes: "20"
default-max-matrix-combinations-count: "1024"
...
```

For more information, see [installation customizations](/docs/install.md#customizing-basic-execution-parameters).

### Parameters

Expand Down
39 changes: 25 additions & 14 deletions pkg/apis/config/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,25 +40,27 @@ const (
// DefaultCloudEventSinkValue is the default value for cloud event sinks.
DefaultCloudEventSinkValue = ""

defaultTimeoutMinutesKey = "default-timeout-minutes"
defaultServiceAccountKey = "default-service-account"
defaultManagedByLabelValueKey = "default-managed-by-label-value"
defaultPodTemplateKey = "default-pod-template"
defaultAAPodTemplateKey = "default-affinity-assistant-pod-template"
defaultCloudEventsSinkKey = "default-cloud-events-sink"
defaultTaskRunWorkspaceBinding = "default-task-run-workspace-binding"
defaultTimeoutMinutesKey = "default-timeout-minutes"
defaultServiceAccountKey = "default-service-account"
defaultManagedByLabelValueKey = "default-managed-by-label-value"
defaultPodTemplateKey = "default-pod-template"
defaultAAPodTemplateKey = "default-affinity-assistant-pod-template"
defaultCloudEventsSinkKey = "default-cloud-events-sink"
defaultTaskRunWorkspaceBinding = "default-task-run-workspace-binding"
defaultMaxMatrixCombinationsCountKey = "default-max-matrix-combinations-count"
)

// Defaults holds the default configurations
// +k8s:deepcopy-gen=true
type Defaults struct {
DefaultTimeoutMinutes int
DefaultServiceAccount string
DefaultManagedByLabelValue string
DefaultPodTemplate *pod.Template
DefaultAAPodTemplate *pod.AffinityAssistantTemplate
DefaultCloudEventsSink string
DefaultTaskRunWorkspaceBinding string
DefaultTimeoutMinutes int
DefaultServiceAccount string
DefaultManagedByLabelValue string
DefaultPodTemplate *pod.Template
DefaultAAPodTemplate *pod.AffinityAssistantTemplate
DefaultCloudEventsSink string
DefaultTaskRunWorkspaceBinding string
DefaultMaxMatrixCombinationsCount int
}

// GetDefaultsConfigName returns the name of the configmap containing all
Expand Down Expand Up @@ -137,6 +139,15 @@ func NewDefaultsFromMap(cfgMap map[string]string) (*Defaults, error) {
if bindingYAML, ok := cfgMap[defaultTaskRunWorkspaceBinding]; ok {
tc.DefaultTaskRunWorkspaceBinding = bindingYAML
}

if defaultMaxMatrixCombinationsCount, ok := cfgMap[defaultMaxMatrixCombinationsCountKey]; ok {
matrixCombinationsCount, err := strconv.ParseInt(defaultMaxMatrixCombinationsCount, 10, 0)
if err != nil {
return nil, fmt.Errorf("failed parsing tracing config %q", defaultMaxMatrixCombinationsCountKey)
}
tc.DefaultMaxMatrixCombinationsCount = int(matrixCombinationsCount)
}

return &tc, nil
}

Expand Down
16 changes: 15 additions & 1 deletion pkg/apis/config/default_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,20 @@ func TestNewDefaultsFromConfigMap(t *testing.T) {
DefaultAAPodTemplate: &pod.AffinityAssistantTemplate{},
},
},
{
expectedError: true,
fileName: "config-defaults-matrix-err",
},
{
expectedError: false,
fileName: "config-defaults-matrix",
expectedConfig: &config.Defaults{
DefaultMaxMatrixCombinationsCount: 1024,
DefaultTimeoutMinutes: 60,
DefaultServiceAccount: "default",
DefaultManagedByLabelValue: config.DefaultManagedByLabelValue,
},
},
}

for _, tc := range testCases {
Expand Down Expand Up @@ -251,7 +265,7 @@ func verifyConfigFileWithExpectedConfig(t *testing.T, fileName string, expectedC
t.Helper()
cm := test.ConfigMapFromTestFile(t, fileName)
if Defaults, err := config.NewDefaultsFromConfigMap(cm); err == nil {
if d := cmp.Diff(Defaults, expectedConfig); d != "" {
if d := cmp.Diff(expectedConfig, Defaults); d != "" {
t.Errorf("Diff:\n%s", diff.PrintWantGot(d))
}
} else {
Expand Down
21 changes: 21 additions & 0 deletions pkg/apis/config/testdata/config-defaults-matrix-err.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2019 The Tekton 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
#
# https://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.

apiVersion: v1
kind: ConfigMap
metadata:
name: config-defaults
namespace: tekton-pipelines
data:
default-max-matrix-combinations-count: "abc"
21 changes: 21 additions & 0 deletions pkg/apis/config/testdata/config-defaults-matrix.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2019 The Tekton 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
#
# https://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.

apiVersion: v1
kind: ConfigMap
metadata:
name: config-defaults
namespace: tekton-pipelines
data:
default-max-matrix-combinations-count: "1024"
21 changes: 21 additions & 0 deletions pkg/apis/pipeline/v1beta1/pipeline_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,12 +302,33 @@ func (pt *PipelineTask) validateMatrix(ctx context.Context) (errs *apis.FieldErr
// This is an alpha feature and will fail validation if it's used in a pipeline spec
// when the enable-api-fields feature gate is anything but "alpha".
errs = errs.Also(ValidateEnabledAPIFields(ctx, "matrix", config.AlphaAPIFields))
errs = errs.Also(pt.validateMatrixCombinationsCount(ctx))
}
errs = errs.Also(validateParameterInOneOfMatrixOrParams(pt.Matrix, pt.Params))
errs = errs.Also(validateParametersInTaskMatrix(pt.Matrix))
return errs
}

func (pt *PipelineTask) validateMatrixCombinationsCount(ctx context.Context) (errs *apis.FieldError) {
matrixCombinationsCount := pt.getMatrixCombinationsCount()
maxMatrixCombinationsCount := config.FromContextOrDefaults(ctx).Defaults.DefaultMaxMatrixCombinationsCount
if matrixCombinationsCount > maxMatrixCombinationsCount {
errs = errs.Also(apis.ErrOutOfBoundsValue(matrixCombinationsCount, 0, maxMatrixCombinationsCount, "matrix"))
}
return errs
}

func (pt *PipelineTask) getMatrixCombinationsCount() int {
if len(pt.Matrix) == 0 {
return 0
}
count := 1
for _, param := range pt.Matrix {
count *= len(param.Value.ArrayVal)
}
return count
}

func (pt *PipelineTask) validateResultsFromMatrixedPipelineTasksNotConsumed(matrixedPipelineTasks sets.String) (errs *apis.FieldError) {
for _, ref := range PipelineTaskResultRefs(pt) {
if matrixedPipelineTasks.Has(ref.PipelineTask) {
Expand Down
104 changes: 104 additions & 0 deletions pkg/apis/pipeline/v1beta1/pipeline_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -746,14 +746,42 @@ func TestPipelineTask_validateMatrix(t *testing.T) {
Message: "invalid value: result references are not allowed in parameters in a matrix",
Paths: []string{"matrix[a-param].value"},
},
}, {
name: "count of combinations of parameters in the matrix exceeds the maximum",
pt: &PipelineTask{
Name: "task",
Matrix: []Param{{
Name: "platform", Value: ArrayOrString{Type: ParamTypeArray, ArrayVal: []string{"linux", "mac", "windows"}},
}, {
Name: "browser", Value: ArrayOrString{Type: ParamTypeArray, ArrayVal: []string{"chrome", "firefox", "safari"}},
}},
},
wantErrs: &apis.FieldError{
Message: "expected 0 <= 9 <= 4",
Paths: []string{"matrix"},
},
}, {
name: "count of combinations of parameters in the matrix equals the maximum",
pt: &PipelineTask{
Name: "task",
Matrix: []Param{{
Name: "platform", Value: ArrayOrString{Type: ParamTypeArray, ArrayVal: []string{"linux", "mac"}},
}, {
Name: "browser", Value: ArrayOrString{Type: ParamTypeArray, ArrayVal: []string{"chrome", "firefox"}},
}},
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
featureFlags, _ := config.NewFeatureFlagsFromMap(map[string]string{
"enable-api-fields": "alpha",
})
defaults := &config.Defaults{
DefaultMaxMatrixCombinationsCount: 4,
}
cfg := &config.Config{
FeatureFlags: featureFlags,
Defaults: defaults,
}
ctx := config.ToContext(context.Background(), cfg)
if d := cmp.Diff(tt.wantErrs.Error(), tt.pt.validateMatrix(ctx).Error()); d != "" {
Expand All @@ -762,3 +790,79 @@ func TestPipelineTask_validateMatrix(t *testing.T) {
})
}
}

func TestPipelineTask_getMatrixCombinationsCount(t *testing.T) {
tests := []struct {
name string
pt *PipelineTask
matrixCombinationsCount int
}{{
name: "combinations count is zero",
pt: &PipelineTask{
Name: "task",
},
matrixCombinationsCount: 0,
}, {
name: "combinations count is one from one parameter",
pt: &PipelineTask{
Name: "task",
Matrix: []Param{{
Name: "foo", Value: ArrayOrString{Type: ParamTypeArray, ArrayVal: []string{"foo"}},
}},
},
matrixCombinationsCount: 1,
}, {
name: "combinations count is one from two parameters",
pt: &PipelineTask{
Name: "task",
Matrix: []Param{{
Name: "foo", Value: ArrayOrString{Type: ParamTypeArray, ArrayVal: []string{"foo"}},
}, {
Name: "bar", Value: ArrayOrString{Type: ParamTypeArray, ArrayVal: []string{"bar"}},
}},
},
matrixCombinationsCount: 1,
}, {
name: "combinations count is two from one parameter",
pt: &PipelineTask{
Name: "task",
Matrix: []Param{{
Name: "foo", Value: ArrayOrString{Type: ParamTypeArray, ArrayVal: []string{"foo", "bar"}},
}},
},
matrixCombinationsCount: 2,
}, {
name: "combinations count is nine",
pt: &PipelineTask{
Name: "task",
Matrix: []Param{{
Name: "foo", Value: ArrayOrString{Type: ParamTypeArray, ArrayVal: []string{"f", "o", "o"}},
}, {
Name: "bar", Value: ArrayOrString{Type: ParamTypeArray, ArrayVal: []string{"b", "a", "r"}},
}},
},
matrixCombinationsCount: 9,
}, {
name: "combinations count is large",
pt: &PipelineTask{
Name: "task",
Matrix: []Param{{
Name: "foo", Value: ArrayOrString{Type: ParamTypeArray, ArrayVal: []string{"f", "o", "o"}},
}, {
Name: "bar", Value: ArrayOrString{Type: ParamTypeArray, ArrayVal: []string{"b", "a", "r"}},
}, {
Name: "quz", Value: ArrayOrString{Type: ParamTypeArray, ArrayVal: []string{"q", "u", "x"}},
}, {
Name: "xyzzy", Value: ArrayOrString{Type: ParamTypeArray, ArrayVal: []string{"x", "y", "z", "z", "y"}},
}},
},
matrixCombinationsCount: 135,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if d := cmp.Diff(tt.matrixCombinationsCount, tt.pt.getMatrixCombinationsCount()); d != "" {
t.Errorf("PipelineTask.getMatrixCombinationsCount() errors diff %s", diff.PrintWantGot(d))
}
})
}
}
8 changes: 8 additions & 0 deletions pkg/apis/pipeline/v1beta1/pipeline_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2716,8 +2716,12 @@ func TestMatrixIncompatibleAPIVersions(t *testing.T) {
featureFlags, _ := config.NewFeatureFlagsFromMap(map[string]string{
"enable-api-fields": version,
})
defaults := &config.Defaults{
DefaultMaxMatrixCombinationsCount: 4,
}
cfg := &config.Config{
FeatureFlags: featureFlags,
Defaults: defaults,
}

ctx := config.ToContext(context.Background(), cfg)
Expand Down Expand Up @@ -2824,8 +2828,12 @@ func Test_validateMatrix(t *testing.T) {
featureFlags, _ := config.NewFeatureFlagsFromMap(map[string]string{
"enable-api-fields": "alpha",
})
defaults := &config.Defaults{
DefaultMaxMatrixCombinationsCount: 4,
}
cfg := &config.Config{
FeatureFlags: featureFlags,
Defaults: defaults,
}

ctx := config.ToContext(context.Background(), cfg)
Expand Down

0 comments on commit 88e8c8c

Please sign in to comment.