Skip to content

Commit

Permalink
feat: Reference AnalysisTemplates inside an AnalysisTemplate (#3353)
Browse files Browse the repository at this point in the history
Signed-off-by: Guillaume Doussin <[email protected]>
  • Loading branch information
OpenGuidou authored Mar 29, 2024
1 parent ea398fc commit f5793e4
Show file tree
Hide file tree
Showing 28 changed files with 2,324 additions and 1,021 deletions.
178 changes: 178 additions & 0 deletions docs/features/analysis.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,184 @@ templates together. The controller combines the `metrics` and `args` fields of a
* Multiple metrics in the templates have the same name
* Two arguments with the same name have different default values no matter the argument value in Rollout

## Analysis Template referencing other Analysis Templates

AnalysisTemplates and ClusterAnalysisTemplates may reference other templates.

They can be combined with other metrics:

=== "AnalysisTemplate"

```yaml
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: error-rate
spec:
args:
- name: service-name
metrics:
- name: error-rate
interval: 5m
successCondition: result[0] <= 0.95
failureLimit: 3
provider:
prometheus:
address: http://prometheus.example.com:9090
query: |
sum(irate(
istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}",response_code=~"5.*"}[5m]
)) /
sum(irate(
istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}"}[5m]
))
---
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: rates
spec:
args:
- name: service-name
metrics:
- name: success-rate
interval: 5m
successCondition: result[0] >= 0.95
failureLimit: 3
provider:
prometheus:
address: http://prometheus.example.com:9090
query: |
sum(irate(
istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}",response_code!~"5.*"}[5m]
)) /
sum(irate(
istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}"}[5m]
))
templates:
- templateName: error-rate
clusterScope: false
```

Or without additional metrics:

=== "AnalysisTemplate"

```yaml
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: success-rate
spec:
args:
- name: service-name
metrics:
- name: success-rate
interval: 5m
successCondition: result[0] >= 0.95
failureLimit: 3
provider:
prometheus:
address: http://prometheus.example.com:9090
query: |
sum(irate(
istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}",response_code!~"5.*"}[5m]
)) /
sum(irate(
istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}"}[5m]
))
---
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: error-rate
spec:
args:
- name: service-name
metrics:
- name: error-rate
interval: 5m
successCondition: result[0] <= 0.95
failureLimit: 3
provider:
prometheus:
address: http://prometheus.example.com:9090
query: |
sum(irate(
istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}",response_code=~"5.*"}[5m]
)) /
sum(irate(
istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}"}[5m]
))
---
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: rates
spec:
args:
- name: service-name
templates:
- templateName: success-rate
clusterScope: false
- templateName: error-rate
clusterScope: false
```

The result in the AnalysisRun will have the aggregation of metrics of each template:

=== "AnalysisRun"

```yaml
# NOTE: Generated AnalysisRun from a single template referencing several templates
apiVersion: argoproj.io/v1alpha1
kind: AnalysisRun
metadata:
name: guestbook-CurrentPodHash-templates-in-template
spec:
args:
- name: service-name
value: guestbook-svc.default.svc.cluster.local
metrics:
- name: success-rate
interval: 5m
successCondition: result[0] >= 0.95
failureLimit: 3
provider:
prometheus:
address: http://prometheus.example.com:9090
query: |
sum(irate(
istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}",response_code!~"5.*"}[5m]
)) /
sum(irate(
istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}"}[5m]
))
- name: error-rate
interval: 5m
successCondition: result[0] <= 0.95
failureLimit: 3
provider:
prometheus:
address: http://prometheus.example.com:9090
query: |
sum(irate(
istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}",response_code=~"5.*"}[5m]
)) /
sum(irate(
istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}"}[5m]
))
```

!!! note
The same limitations as for the multiple templates feature apply.
The controller will error when merging the templates if:

* Multiple metrics in the templates have the same name
* Two arguments with the same name have different default values no matter the argument value in Rollout

However, if the same AnalysisTemplate is referenced several times along the chain of references, the controller will only keep it once and discard the other references.

## Analysis Template Arguments

AnalysisTemplates may declare a set of arguments that can be passed by Rollouts. The args can then be used as in metrics configuration and are resolved at the time the AnalysisRun is created. Argument placeholders are defined as
Expand Down
38 changes: 32 additions & 6 deletions docs/features/kustomize/rollout_cr_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -9250,11 +9250,24 @@
"type": "array",
"x-kubernetes-patch-merge-key": "name",
"x-kubernetes-patch-strategy": "merge"
},
"templates": {
"items": {
"properties": {
"clusterScope": {
"type": "boolean"
},
"templateName": {
"type": "string"
}
},
"type": "object"
},
"type": "array",
"x-kubernetes-patch-merge-key": "templateName",
"x-kubernetes-patch-strategy": "merge"
}
},
"required": [
"metrics"
],
"type": "object"
}
},
Expand Down Expand Up @@ -13883,11 +13896,24 @@
"type": "array",
"x-kubernetes-patch-merge-key": "name",
"x-kubernetes-patch-strategy": "merge"
},
"templates": {
"items": {
"properties": {
"clusterScope": {
"type": "boolean"
},
"templateName": {
"type": "string"
}
},
"type": "object"
},
"type": "array",
"x-kubernetes-patch-merge-key": "templateName",
"x-kubernetes-patch-strategy": "merge"
}
},
"required": [
"metrics"
],
"type": "object"
}
},
Expand Down
16 changes: 12 additions & 4 deletions experiments/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ func generateRSName(ex *v1alpha1.Experiment, template v1alpha1.TemplateSpec) str
return fmt.Sprintf("%s-%s", ex.Name, template.Name)
}

func calculatePatch(ex *v1alpha1.Experiment, patch string, templates []v1alpha1.TemplateStatus, condition *v1alpha1.ExperimentCondition) string {
func calculatePatch(ex *v1alpha1.Experiment, patch string, templates []v1alpha1.TemplateStatus, condition *v1alpha1.ExperimentCondition, analysisRuns []*v1alpha1.ExperimentAnalysisRunStatus, message string) string {
patchMap := make(map[string]any)
err := json.Unmarshal([]byte(patch), &patchMap)
if err != nil {
Expand All @@ -318,6 +318,14 @@ func calculatePatch(ex *v1alpha1.Experiment, patch string, templates []v1alpha1.
newStatus["conditions"] = []v1alpha1.ExperimentCondition{*condition}
patchMap["status"] = newStatus
}
if analysisRuns != nil {
newStatus["analysisRuns"] = analysisRuns
patchMap["status"] = newStatus
}
if message != "" {
newStatus["message"] = message
patchMap["status"] = newStatus
}

patchBytes, err := json.Marshal(patchMap)
if err != nil {
Expand Down Expand Up @@ -806,7 +814,7 @@ func TestAddInvalidSpec(t *testing.T) {
expectedPatch := calculatePatch(e, `{
"status":{
}
}`, nil, cond)
}`, nil, cond, nil, "")
assert.JSONEq(t, expectedPatch, patch)
}

Expand Down Expand Up @@ -853,7 +861,7 @@ func TestUpdateInvalidSpec(t *testing.T) {
expectedPatch := calculatePatch(e, `{
"status":{
}
}`, nil, cond)
}`, nil, cond, nil, "")
assert.JSONEq(t, expectedPatch, patch)

}
Expand Down Expand Up @@ -893,7 +901,7 @@ func TestRemoveInvalidSpec(t *testing.T) {
expectedPatch := calculatePatch(e, `{
"status":{
}
}`, templateStatus, cond)
}`, templateStatus, cond, nil, "")
assert.JSONEq(t, expectedPatch, patch)
}

Expand Down
Loading

0 comments on commit f5793e4

Please sign in to comment.