Skip to content

Commit

Permalink
feat: allow running versioned experiments (#451)
Browse files Browse the repository at this point in the history
This PR allows us to filter the experiments we want to run in a
declarative way. Experiments must now be explicitly defined in the [new
manifest file
embedded](go-chaos/internal/chaos-experiments/camunda-cloud/manifest.yml)
in the chaos worker.

The manifest looks like this:

```yaml
experiments:
  - path: broker-dataloss/experiment.json
    clusterPlans:
      - production-s
  - path: deployment-distribution/experiment.json
    clusterPlans:
      - production-s
  # only for testing
  - path: test/experiment.json
    clusterPlans:
      - test
  - path: test/version-experiment.json
    clusterPlans:
      - test
    minVersion: 8.3
    maxVersion: 8.4
```

> [!Note]
> For version bounds, we only check the major and minor version. So 8.3,
8.4, etc. If the target cluster is 8.4.0-SNAPSHOT and the bound is 8.4,
then it will match.

The logic when reading the manifest is as follows:

- Given the current target cluster plan X, only experiments who have `X`
in their `clusterPlans` list will be picked.
- Given the current target cluster version X, only experiments whose
`minVersion >= X` `maxVersion <= X` are picked.
- The `minVersion` and `maxVersion` parameters are completely optional.
If you specify none, then the experiment runs on any version.
- If we somehow fail to get the target cluster version, any experiment
with version bound is omitted.

The target version is obtained by getting the topology of the target
cluster, either from a given namespace, or from the current namespace of
the kubeconfig if no namespace was provided.
  • Loading branch information
npepinpe authored Dec 11, 2023
2 parents 795ed56 + 7da36c9 commit c9b90f8
Show file tree
Hide file tree
Showing 19 changed files with 317 additions and 43 deletions.
14 changes: 7 additions & 7 deletions go-chaos/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ require (
github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.8.4
github.com/testcontainers/testcontainers-go v0.26.0
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb
golang.org/x/mod v0.14.0
google.golang.org/grpc v1.59.0
k8s.io/api v0.28.4
k8s.io/apimachinery v0.28.4
Expand Down Expand Up @@ -75,15 +77,13 @@ require (
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect
golang.org/x/mod v0.10.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/oauth2 v0.12.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/term v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.8.0 // indirect
golang.org/x/tools v0.16.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
google.golang.org/protobuf v1.31.0 // indirect
Expand Down
30 changes: 15 additions & 15 deletions go-chaos/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -201,26 +201,26 @@ github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQ
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4=
golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8=
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand All @@ -241,24 +241,24 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
50 changes: 50 additions & 0 deletions go-chaos/internal/chaos-experiments/camunda-cloud/manifest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Lists all the experiments that the chaos-worker can run.
# Each experiment must use the following structure:
#
# - path: path to the JSON file relative to the manifest
# clusterPlans: a list of cluster plans the experiment can run on
# minVersion: the minimum (inclusive) version of the target cluster the experiment can run against
# maxVersion: the maximum (inclusive) version of the target cluster the experiment can run against
#
# Both minVersion and maxVersion should only be the major and minor versions, e.g. 8.3, 8.4, etc.
# minVersion and maxVersion are both optional.
experiments:
- path: broker-dataloss/experiment.json
clusterPlans:
- production-s
- path: deployment-distribution/experiment.json
clusterPlans:
- production-s
- path: follower-restart/experiment.json
clusterPlans:
- production-s
- path: follower-terminate/experiment.json
clusterPlans:
- production-s
- path: leader-restart/experiment.json
clusterPlans:
- production-s
- path: leader-terminate/experiment.json
clusterPlans:
- production-s
- path: msg-correlation/experiment.json
clusterPlans:
- production-s
- path: multiple-leader-restart/experiment.json
clusterPlans:
- production-s
- path: stress-cpu-on-broker/experiment.json
clusterPlans:
- production-s
- path: worker-restart/experiment.json
clusterPlans:
- production-s
# only for testing
- path: test/experiment.json
clusterPlans:
- test
- path: test/version-experiment.json
clusterPlans:
- test
minVersion: 8.3
maxVersion: 8.4
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"version": "0.1.0",
"title": "Versioned Test Experiment",
"description": "This fake experiment is just to test the integration with Zeebe and zbchaos workers",
"contributions": {
"reliability": "high",
"availability": "high"
},
"steady-state-hypothesis": {
"title": "Zeebe is alive",
"probes": [
{
"name": "Show version",
"type": "probe",
"tolerance": 0,
"provider": {
"type": "process",
"path": "zbchaos",
"arguments": ["version"],
"timeout": 900
}
}
]
},
"method": [
{
"type": "action",
"name": "Show again the version",
"tolerance": 0,
"provider": {
"type": "process",
"path": "zbchaos",
"arguments": ["version"],
"timeout": 900
}
}
],
"rollbacks": []
}
81 changes: 65 additions & 16 deletions go-chaos/internal/chaos-experiments/chaos_experiments.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package chaos_experiments

import (
"bytes"
"embed"
"encoding/json"
"fmt"
"strings"

"github.com/zeebe-io/zeebe-chaos/go-chaos/internal"
"golang.org/x/exp/slices"
"golang.org/x/mod/semver"
"k8s.io/apimachinery/pkg/util/yaml"
)

// chaosContent holds our static camunda cloud chaos experiments, which are copied with the go:embed directive
Expand All @@ -13,34 +18,78 @@ import (
var chaosContent embed.FS

const experimentFileName = "experiment.json"
const contentFolder = "camunda-cloud"
const manifestFileName = contentFolder + "/manifest.yml"

type Experiments struct {
Experiments []map[string]interface{} `json:"experiments"`
}

func ReadExperimentsForClusterPlan(clusterPlan string) (Experiments, error) {
func ReadExperimentsForClusterPlan(clusterPlan string, targetClusterVersion string) (Experiments, error) {
// semver package requires versions to start with "v"
normalizedClusterPlan := strings.ToLower(strings.Replace(clusterPlan, " ", "", -1))
rootPath := fmt.Sprintf("camunda-cloud/%s", normalizedClusterPlan)
dirEntries, err := chaosContent.ReadDir(rootPath)
manifest, err := readManifest()
if err != nil {
return Experiments{}, err
}
paths := manifest.filterExperiments(normalizedClusterPlan, targetClusterVersion)
internal.LogVerbose("Given cluster plan '%s' and target cluster version '%s', selected the following experiments: [%#v]",
normalizedClusterPlan, targetClusterVersion, paths)

experiments := Experiments{}
for _, dir := range dirEntries {
if dir.IsDir() {
experimentBytes, err := chaosContent.ReadFile(fmt.Sprintf("%s/%s/%s", rootPath, dir.Name(), experimentFileName))
if err != nil {
return experiments, err
}
var jsonObj map[string]interface{}
err = json.Unmarshal(experimentBytes, &jsonObj)
if err != nil {
return experiments, err
}
experiments.Experiments = append(experiments.Experiments, jsonObj)
for _, path := range paths {
experimentBytes, err := chaosContent.ReadFile(contentFolder + "/" + path)
if err != nil {
return experiments, err
}
var jsonObj map[string]interface{}
err = json.Unmarshal(experimentBytes, &jsonObj)
if err != nil {
return experiments, err
}
experiments.Experiments = append(experiments.Experiments, jsonObj)
}

return experiments, err
}

type manifest struct {
Experiments []experiment `yaml:"experiments"`
}

type experiment struct {
Path string `yaml:"path"`
ClusterPlans []string `yaml:"clusterPlans"`
MinVersion string `yaml:"minVersion"`
MaxVersion string `yaml:"maxVersion"`
}

func (m manifest) filterExperiments(clusterPlan string, targetVersion string) (experiments []string) {
for _, entry := range m.Experiments {
isValidClusterPlan := slices.Contains(entry.ClusterPlans, clusterPlan)
matchesMinVersion := entry.MinVersion == "" || (targetVersion != "" && semver.Compare(semver.MajorMinor("v"+targetVersion), semver.MajorMinor("v"+entry.MinVersion)) >= 0)
matchesMaxVersion := entry.MaxVersion == "" || (targetVersion != "" && semver.Compare(semver.MajorMinor("v"+targetVersion), semver.MajorMinor("v"+entry.MaxVersion)) <= 0)
matchesVersion := matchesMinVersion && matchesMaxVersion

if isValidClusterPlan && matchesVersion {
experiments = append(experiments, entry.Path)
} else {
internal.LogInfo(
"Skipping experiment at '%#v' for cluster plan '%s' and target version '%s' because: [isValidClusterPlan=%t, matchesMinVersion=%t, matchesMaxVersion=%t]",
entry, clusterPlan, targetVersion, isValidClusterPlan, matchesMinVersion, matchesMaxVersion)
}
}

return
}

func readManifest() (m manifest, err error) {
manifestBytes, err := chaosContent.ReadFile(manifestFileName)
if err != nil {
return
}

decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(manifestBytes), 0)
err = decoder.Decode(&m)
return
}
Loading

0 comments on commit c9b90f8

Please sign in to comment.