├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── PULL_REQUEST_TEMPLATE
│ └── pull_request_template.md
├── release-config.yaml
├── spellcheck.yml
├── wordlist.txt
└── workflows
│ ├── assets
│ └── grpcurl.yaml
│ ├── draftrelease.yaml
│ ├── golangci-lint.yml
│ ├── linkcheck.yaml
│ ├── lintcharts.yaml
│ ├── lintcharts2.yaml
│ ├── releaseassets.yaml
│ ├── releasecharts.yaml
│ ├── spellcheck.yaml
│ ├── testcharts.yaml
│ ├── testkustomize.yaml
│ ├── unittest.yaml
│ ├── verifyuserexperience.yaml
│ └── versionbump.yaml
├── .gitignore
├── .golangci.yml
├── .lycheeignore
├── ADOPTERS.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── MAINTAINERS.md
├── Makefile
├── README.md
├── abn
├── grpc
│ ├── abn.pb.go
│ ├── abn.proto
│ └── abn_grpc.pb.go
├── service.go
├── service_impl.go
├── service_impl_test.go
├── service_test.go
└── test_helpers.go
├── action
├── doc.go
├── run.go
└── run_test.go
├── base
├── collect_grpc.go
├── collect_grpc_test.go
├── collect_http.go
├── collect_http_test.go
├── doc.go
├── experiment.go
├── experiment_test.go
├── insights_test.go
├── internal
│ ├── common.go
│ ├── doc.go
│ └── helloworld
│ │ └── helloworld
│ │ ├── doc.go
│ │ ├── greeter.pb.go
│ │ ├── greeter.proto
│ │ ├── greeter_grpc.pb.go
│ │ └── greeter_server.go
├── kubedriver.go
├── kubedriver_test.go
├── log
│ ├── doc.go
│ ├── log.go
│ └── log_test.go
├── metrics.go
├── notify.go
├── notify_test.go
├── readiness.go
├── readiness_test.go
├── run.go
├── run_test.go
├── sprigutil.go
├── test_helpers.go
├── test_helpers_driver.go
├── util.go
└── util_test.go
├── bump-version-hints.md
├── charts
├── controller
│ ├── .helmignore
│ ├── Chart.yaml
│ ├── templates
│ │ ├── _helpers.tpl
│ │ ├── configmap.yaml
│ │ ├── persistentvolumeclaim.yaml
│ │ ├── roles.yaml
│ │ ├── service.yaml
│ │ ├── serviceaccount.yaml
│ │ └── statefulset.yaml
│ └── values.yaml
├── iter8
│ ├── .helmignore
│ ├── Chart.yaml
│ ├── README.md
│ ├── templates
│ │ ├── _experiment.tpl
│ │ ├── _k-job.tpl
│ │ ├── _k-role.tpl
│ │ ├── _k-rolebinding.tpl
│ │ ├── _k-secret.tpl
│ │ ├── _k-serviceacccount.tpl
│ │ ├── _task-github.tpl
│ │ ├── _task-grpc.tpl
│ │ ├── _task-http.tpl
│ │ ├── _task-ready.tpl
│ │ ├── _task-slack.tpl
│ │ └── k8s.yaml
│ └── values.yaml
└── release
│ ├── .helmignore
│ ├── Chart.yaml
│ ├── templates
│ ├── _configmap.weight-config.tpl
│ ├── _deployment-gtw.blue-green.routemap.tpl
│ ├── _deployment-gtw.blue-green.tpl
│ ├── _deployment-gtw.canary.routemap.tpl
│ ├── _deployment-gtw.canary.tpl
│ ├── _deployment-gtw.none.routemap.tpl
│ ├── _deployment-gtw.none.tpl
│ ├── _deployment-gtw.service.tpl
│ ├── _deployment-gtw.tpl
│ ├── _deployment-istio.blue-green.routemap.tpl
│ ├── _deployment-istio.blue-green.tpl
│ ├── _deployment-istio.canary.routemap.tpl
│ ├── _deployment-istio.canary.tpl
│ ├── _deployment-istio.mirror.routemap.tpl
│ ├── _deployment-istio.mirror.tpl
│ ├── _deployment-istio.none.routemap.tpl
│ ├── _deployment-istio.none.tpl
│ ├── _deployment-istio.service.tpl
│ ├── _deployment-istio.tpl
│ ├── _deployment.tpl
│ ├── _deployment.version.deployment.tpl
│ ├── _deployment.version.service.tpl
│ ├── _helpers.tpl
│ ├── _kserve.blue-green.routemap.tpl
│ ├── _kserve.blue-green.tpl
│ ├── _kserve.canary.routemap.tpl
│ ├── _kserve.canary.tpl
│ ├── _kserve.none.routemap.tpl
│ ├── _kserve.none.tpl
│ ├── _kserve.service.tpl
│ ├── _kserve.tpl
│ ├── _kserve.version.isvc.tpl
│ ├── _mm-istio.blue-green.routemap.tpl
│ ├── _mm-istio.blue-green.tpl
│ ├── _mm-istio.canary.routemap.tpl
│ ├── _mm-istio.canary.tpl
│ ├── _mm-istio.none.routemap.tpl
│ ├── _mm-istio.none.tpl
│ ├── _mm-istio.service.tpl
│ ├── _mm-istio.tpl
│ ├── _mm-istio.version.isvc.tpl
│ └── release.yaml
│ └── values.yaml
├── cmd
├── controllers.go
├── controllers_test.go
├── doc.go
├── docs.go
├── docs_test.go
├── k.go
├── krun.go
├── krun_test.go
├── root.go
├── test_helpers.go
├── version.go
└── version_test.go
├── config.yaml
├── controllers
├── allcontrollers.go
├── allcontrollers_test.go
├── config.go
├── config_test.go
├── events.go
├── finalizer.go
├── finalizer_test.go
├── interface.go
├── interface_test.go
├── k8sclient
│ ├── fake
│ │ ├── simple.go
│ │ └── simple_test.go
│ ├── interface.go
│ └── simple.go
├── podname.go
├── podname_test.go
├── routemap.go
├── routemap_test.go
├── routemaps.go
└── routemaps_test.go
├── doc.go
├── docker
└── Dockerfile
├── driver
├── common.go
├── common_test.go
├── doc.go
├── kubedriver.go
├── kubedriver_test.go
└── test_helpers.go
├── go.mod
├── go.sum
├── grafana
├── abn.json
├── grpc.json
└── http.json
├── kustomize
└── controller
│ ├── clusterScoped
│ └── kustomization.yaml
│ └── namespaceScoped
│ ├── configmap.yaml
│ ├── kustomization.yaml
│ ├── pvc.yaml
│ ├── role.yaml
│ ├── rolebinding.yaml
│ ├── service.yaml
│ ├── serviceaccount.yaml
│ └── statefulset.yaml
├── main.go
├── metrics
├── doc.go
├── server.go
├── server_test.go
└── test_helpers.go
├── storage
├── badgerdb
│ ├── badgerdb.go
│ └── badgerdb_test.go
├── client
│ ├── client.go
│ └── client_test.go
├── interface.go
├── redis
│ ├── redis.go
│ └── redis_test.go
├── util.go
└── util_test.go
├── templates
└── notify
│ ├── _payload-github.tpl
│ └── _payload-slack.tpl
└── testdata
├── .gitignore
├── abninputs
├── application.yaml
└── config.yaml
├── assertinputs
├── .gitignore
└── experiment.yaml
├── controllers
├── config.yaml
└── garb.age
├── drivertests
├── .gitignore
└── experiment.tpl
├── experiment.tpl
├── experiment.yaml
├── experiment_grpc.yaml
├── output
├── .gitignore
├── gen-cli-values.txt
├── gen-values-file.txt
├── hub-with-destdir.txt
├── hub.txt
├── kassert.txt
├── kdelete.txt
├── klaunch.txt
├── klog.txt
├── krun.txt
├── launch-with-destdir.txt
└── launch.txt
└── payload
└── ukpolice.json
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: kind/bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior
15 |
16 | **Expected behavior**
17 | A clear and concise description of what you expected to happen.
18 |
19 | **Screenshots**
20 | If applicable, add screenshots to help explain your problem.
21 |
22 | **Desktop (please complete the following information):**
23 | - OS: [e.g. MacOS]
24 | - Output of the `iter8 version` command
25 |
26 | **Additional context**
27 | Add any other context about the problem here.
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Pull request
3 | about: Create a pull request
4 | title: ''
5 | labels: kind/bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | # Pull Request Template
11 |
12 | ## Description
13 |
14 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.
15 |
16 | Fixes # (issue)
17 |
18 | ## Type of change
19 |
20 | Please delete options that are not relevant.
21 |
22 | - [ ] Bug fix (non-breaking change which fixes an issue)
23 | - [ ] New feature (non-breaking change which adds functionality)
24 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
25 | - [ ] This change requires a documentation update
26 |
27 | ## How Has This Been Tested?
28 |
29 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
30 |
31 | - [ ] Test A
32 | - [ ] Test B
33 |
34 | **Test Configuration**:
35 | * OS (if applicable)
36 | * Kubernetes version (if applicable)
37 |
38 | ## Checklist:
39 |
40 | - [ ] My code follows the style guidelines of this project
41 | - [ ] I have performed a self-review of my own code
42 | - [ ] I have commented my code, particularly in hard-to-understand areas
43 | - [ ] I have made corresponding changes to the documentation
44 | - [ ] My changes generate no new warnings
45 | - [ ] I have added tests that prove my fix is effective or that my feature works
46 | - [ ] New and existing unit tests pass locally with my changes
47 | - [ ] Any dependent changes have been merged and published in downstream modules
48 | - [ ] I have checked my code and corrected any misspellings
49 |
--------------------------------------------------------------------------------
/.github/release-config.yaml:
--------------------------------------------------------------------------------
1 | name-template: 'Version $NEXT_PATCH_VERSION of Iter8'
2 | tag-template: 'v$NEXT_PATCH_VERSION'
3 | tag-prefix: 'v'
4 | categories:
5 | - title: '🚀 Features'
6 | labels:
7 | - 'kind/enhancement'
8 | - title: '🧹 Cleaned or removed features'
9 | label: 'remove'
10 | - title: '🐛 Bug Fixes'
11 | labels:
12 | - 'kind/bug'
13 | - title: '🧰 Maintenance'
14 | label: 'chore'
15 | - title: '📝 Documentation'
16 | labels:
17 | - 'kind/docs'
18 | - title: '🚦 CI'
19 | labels:
20 | - 'area/CI'
21 | change-template: '- #$NUMBER: $TITLE'
22 | template: |
23 | ## What’s Changed
24 | $CHANGES
25 |
--------------------------------------------------------------------------------
/.github/spellcheck.yml:
--------------------------------------------------------------------------------
1 | matrix:
2 | - name: Markdown
3 | aspell:
4 | lang: en
5 | ignore-case: true
6 | dictionary:
7 | wordlists:
8 | - .github/wordlist.txt # <-- put path to custom dictionary file here
9 | encoding: utf-8
10 | pipeline:
11 | - pyspelling.filters.markdown:
12 | - pyspelling.filters.html:
13 | comments: false
14 | ignores:
15 | - code
16 | - pre
17 | sources:
18 | - 'docs/**/*.md'
19 | - '*.md'
20 | default_encoding: utf-8
--------------------------------------------------------------------------------
/.github/wordlist.txt:
--------------------------------------------------------------------------------
1 | abn
2 | acm
3 | API
4 | apis
5 | ArgoCD
6 | AutoX
7 | backend
8 | backends
9 | benchmarking
10 | Cha
11 | chaosengine
12 | CLI
13 | composable
14 | CRD
15 | CRDs
16 | cronjob
17 | crontab
18 | DCO
19 | default_encoding
20 | declaratively
21 | DevOps
22 | DevSecOps
23 | Dockerfile
24 | DZone
25 | Fortio
26 | frontend
27 | GitOps
28 | gRPC
29 | GitHub
30 | Grafana
31 | GVR
32 | Homebrew
33 | http
34 | httpbin
35 | https
36 | iCalendar
37 | InferenceService
38 | integrations
39 | io
40 | Istio
41 | Istio's
42 | Iter
43 | ITnext
44 | js
45 | JSON
46 | jq
47 | Kalantar
48 | Knative
49 | kubectl
50 | Kubernetes
51 | Kubecon
52 | KServe
53 | Linkerd
54 | LitmusChaos
55 | localhost
56 | minikube
57 | MLOps
58 | modelmesh
59 | namespace
60 | namespaces
61 | NewRelic
62 | Parthasarathy
63 | plotly
64 | png
65 | PRs
66 | protobuf
67 | protoc
68 | quickstart
69 | roadmap
70 | scikit
71 | SDK
72 | sed
73 | Seldon
74 | sexualized
75 | SHA
76 | sklearn
77 | SLO
78 | SLOs
79 | SRE
80 | Srinivasan
81 | subdirectory
82 | Sysdig
83 | tada
84 | Tekton
85 | toc
86 | unary
87 | warmup
88 | warmupDuration
89 | warmupNumRequests
90 | webhook
91 | webhooks
92 | yaml
93 | abnmetrics
94 | auth
95 | argoproj
96 | custommetrics
97 | ctx
98 | deleteiter
99 | dev
100 | encodedmetric
101 | execintosleep
102 | expreport
103 | failured
104 | getRecommendation
105 | GetTrack
106 | GitCommit
107 | githubusercontent
108 | gmail
109 | golang
110 | GoVersion
111 | GOBIN
112 | installbrewbins
113 | installghaction
114 | installiter
115 | ksvc
116 | kustomize
117 | lastupdatetime
118 | lifecycle
119 | linenums
120 | lut
121 | metricname
122 | nofailure
123 | repo
124 | repos
125 | req
126 | rollout
127 | rollouts
128 | setName
129 | setUser
130 | trackToRoute
131 | toJson
132 | verifyUserExperience
133 | versionname
134 | WriteMetric
135 | contentType
136 | numRequests
137 | payloadStr
138 | payloadURL
139 | proto
140 | qps
141 | bool
142 | payloadTemplateURL
143 | tpl
144 | usr
145 | softFailure
146 | struct
147 | versionValues
148 | ProviderSpec
149 | jqExpression
150 | binaryDataURL
151 | binaryDataURL
152 | dataURL
153 | metadataURL
154 | protoURL
155 | irisv
156 | EOF
157 | HOSTNAME
158 | jsonpath
159 | cronjobSchedule
160 | logLevel
161 | serviceAccountName
162 | wget
163 | gz
164 | xvf
165 | IMG
166 | mv
167 | Atin
168 | ChaosNative
169 | Chaudhary
170 | Datagrate
171 | Mert
172 | Shubham
173 | Sood
174 | Toolchains
175 | jetic
176 | Öztürk
177 | reconfigures
178 | spartha
179 | sriumcp
180 |
--------------------------------------------------------------------------------
/.github/workflows/draftrelease.yaml:
--------------------------------------------------------------------------------
1 | name: Release drafter
2 |
3 | # Runs when changes are pushed
4 |
5 | on:
6 | push:
7 | branches:
8 | - master
9 |
10 | jobs:
11 | update_release_draft:
12 | runs-on: ubuntu-latest
13 | steps:
14 | # Drafts your next Release notes as Pull Requests are merged into any tracked branch
15 | - uses: release-drafter/release-drafter@v5
16 | with:
17 | config-name: release-config.yaml
18 | env:
19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/.github/workflows/golangci-lint.yml:
--------------------------------------------------------------------------------
1 | name: golangci-lint
2 |
3 | # Only runs when there are golang code changes
4 |
5 | # Lint golang files
6 |
7 | on:
8 | pull_request:
9 | branches:
10 | - master
11 | paths:
12 | - '**.go'
13 |
14 | permissions:
15 | contents: read
16 | # Optional: allow read access to pull request. Use with `only-new-issues` option.
17 | # pull-requests: read
18 |
19 | jobs:
20 | golangci:
21 | name: lint
22 | runs-on: ubuntu-latest
23 | steps:
24 | - uses: actions/setup-go@v5
25 | with:
26 | go-version: 1.21
27 | - uses: actions/checkout@v4
28 | - name: golangci-lint
29 | uses: golangci/golangci-lint-action@v3
30 | with:
31 | # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
32 | version: v1.55.2
33 |
34 | # Optional: working directory, useful for monorepos
35 | # working-directory: somedir
36 |
37 | # Optional: golangci-lint command line arguments.
38 | # args: --issues-exit-code=0
39 |
40 | # Optional: show only new issues if it's a pull request. The default value is `false`.
41 | # only-new-issues: true
42 |
43 | # Optional: if set to true then the all caching functionality will be complete disabled,
44 | # takes precedence over all other caching options.
45 | # skip-cache: true
46 |
47 | # Optional: if set to true then the action don't cache or restore ~/go/pkg.
48 | # skip-pkg-cache: true
49 |
50 | # Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
51 | # skip-build-cache: true
--------------------------------------------------------------------------------
/.github/workflows/linkcheck.yaml:
--------------------------------------------------------------------------------
1 | name: Link checker
2 |
3 | # Only runs when there are markdown changes and intermittently
4 |
5 | # Check links across markdown files
6 |
7 | on:
8 | pull_request:
9 | branches:
10 | - master
11 | paths:
12 | - '**.md'
13 | schedule:
14 | - cron: "0 0 1 * *"
15 |
16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
17 | jobs:
18 | # This workflow contains a single job called "build"
19 | build:
20 | # The type of runner that the job will run on
21 | runs-on: ubuntu-latest
22 |
23 | # Steps represent a sequence of tasks that will be executed as part of the job
24 | steps:
25 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
26 | - uses: actions/checkout@v4
27 |
28 | - name: Link checker
29 | id: lychee
30 | uses: lycheeverse/lychee-action@v1.8.0
31 | env:
32 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
33 | with:
34 | fail: true
35 | args: -v '**/*.md'
--------------------------------------------------------------------------------
/.github/workflows/lintcharts.yaml:
--------------------------------------------------------------------------------
1 | name: Lint Helm charts
2 |
3 | # Only runs when charts have changed
4 |
5 | # Lint Helm charts
6 |
7 | on:
8 | pull_request:
9 | branches:
10 | - master
11 | paths:
12 | - charts/**
13 |
14 | jobs:
15 | # Get the paths for the Helm charts to lint
16 | get_paths:
17 | runs-on: ubuntu-latest
18 |
19 | steps:
20 | - uses: actions/checkout@v4
21 | with:
22 | fetch-depth: 0
23 |
24 | - name: Get the paths for Helm charts to lint
25 | id: set-matrix
26 | run: |
27 | # Get paths (in string form)
28 | stringPaths=$(find -maxdepth 2 -path './charts/*')
29 |
30 | # Check paths (length greater than 0)
31 | stringPathsLength=$(echo ${#stringPaths})
32 | if (( stringPathsLength == 0 ));
33 | then
34 | echo "No paths to check"
35 | exit 1
36 | fi
37 |
38 | # Serialize paths into JSON array
39 | paths=$(jq -ncR '[inputs]' <<< "$stringPaths")
40 |
41 | # Output serialized paths
42 | echo "matrix=$paths" >> $GITHUB_OUTPUT
43 | echo $paths
44 |
45 | outputs:
46 | matrix: ${{ steps.set-matrix.outputs.matrix }}
47 |
48 | # Lint Helm charts based on paths provided by previous job
49 | lint:
50 | name: Test changed-files
51 | needs: get_paths
52 | runs-on: ubuntu-latest
53 | strategy:
54 | matrix:
55 | version: ${{ fromJson(needs.get_paths.outputs.matrix) }}
56 | steps:
57 | - uses: actions/checkout@v4
58 | with:
59 | fetch-depth: 0
60 |
61 | - name: Get modified files in the ${{ matrix.version }} folder
62 | id: modified-files
63 | uses: tj-actions/changed-files@v41
64 | with:
65 | files: ${{ matrix.version }}
66 |
67 | - name: Lint Helm charts in the ${{ matrix.version }} folder
68 | uses: stackrox/kube-linter-action@v1
69 | if: steps.modified-files.outputs.any_modified == 'true'
70 | with:
71 | directory: ${{ matrix.version }}
--------------------------------------------------------------------------------
/.github/workflows/lintcharts2.yaml:
--------------------------------------------------------------------------------
1 | name: Additional Helm chart linting
2 | # Like lintcharts.yaml, the other lint Helm chart workflow, this workflow uses kube-linter
3 | # kube-linter checks Helm templates but it does not check what is contained in {{ define ... }} blocks
4 | # This workflow builds on the other workflow by producing Kubernetes YAML files from the templates and running kube-linter on those files
5 | # See iter8-tools/iter8#1452
6 |
7 | # Only runs when charts have changed
8 |
9 | # Lint Helm charts
10 | # Use templates to create Kubernetes YAML files and lint them
11 |
12 | on:
13 | pull_request:
14 | branches:
15 | - master
16 | paths:
17 | - charts/**
18 |
19 | jobs:
20 | http:
21 | name: Lint HTTP performance test
22 | runs-on: ubuntu-latest
23 |
24 | steps:
25 | - name: Check out code
26 | uses: actions/checkout@v4
27 |
28 | - name: Get modified files in the charts/iter8 folder
29 | id: modified-files
30 | uses: tj-actions/changed-files@v41
31 | with:
32 | files: charts/iter8
33 |
34 | - uses: azure/setup-helm@v3
35 | if: steps.modified-files.outputs.any_modified == 'true'
36 | with:
37 | token: ${{ secrets.GITHUB_TOKEN }}
38 |
39 | - name: Create Kubernetes YAML file
40 | if: steps.modified-files.outputs.any_modified == 'true'
41 | run: |
42 | helm template charts/iter8 \
43 | --set tasks={http} \
44 | --set http.url=http://httpbin.default/get >> iter8.yaml
45 |
46 | - name: Lint Kubernetes YAML file
47 | if: steps.modified-files.outputs.any_modified == 'true'
48 | uses: stackrox/kube-linter-action@v1
49 | with:
50 | directory: iter8.yaml
51 |
52 | grpc:
53 | name: Lint gRPC performance test
54 | runs-on: ubuntu-latest
55 |
56 | steps:
57 | - name: Check out code
58 | uses: actions/checkout@v4
59 |
60 | - name: Get modified files in the charts/iter8 folder
61 | id: modified-files
62 | uses: tj-actions/changed-files@v41
63 | with:
64 | files: charts/iter8
65 |
66 | - uses: azure/setup-helm@v3
67 | if: steps.modified-files.outputs.any_modified == 'true'
68 | with:
69 | token: ${{ secrets.GITHUB_TOKEN }}
70 |
71 | - name: Create Kubernetes YAML file
72 | if: steps.modified-files.outputs.any_modified == 'true'
73 | run: |
74 | helm template charts/iter8 \
75 | --set tasks={grpc} \
76 | --set grpc.host="hello.default:50051" \
77 | --set grpc.call="helloworld.Greeter.SayHello" \
78 | --set grpc.protoURL="https://raw.githubusercontent.com/grpc/grpc-go/master/examples/helloworld/helloworld/helloworld.proto" >> iter8.yaml
79 |
80 | - name: Lint Kubernetes YAML file
81 | if: steps.modified-files.outputs.any_modified == 'true'
82 | uses: stackrox/kube-linter-action@v1
83 | with:
84 | directory: iter8.yaml
85 |
--------------------------------------------------------------------------------
/.github/workflows/releaseassets.yaml:
--------------------------------------------------------------------------------
1 | name: Release binaries and Docker image
2 |
3 | # Runs when a release is published
4 |
5 | # Build and publish binaries and release Docker image
6 | #
7 | # NOTE: completion of this task will trigger verifyuserexperience.yaml
8 | # which will test the released image (with released charts)
9 |
10 | on:
11 | release:
12 | types: [published]
13 |
14 | jobs:
15 | build-and-push:
16 | name: Push Iter8 image to Docker Hub
17 | runs-on: ubuntu-latest
18 | steps:
19 | - uses: actions/checkout@v4
20 | with:
21 | fetch-depth: 0
22 | - name: Get version
23 | run: |
24 | tagref=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
25 | # Strip "v" prefix from tagref
26 | echo "VERSION=$(echo $tagref | sed -e 's/^v//')" >> $GITHUB_ENV
27 | echo "MAJOR_MINOR_VERSION=$(echo $tagref | sed -e 's/^v//' -e 's,\([0-9]*\.[0-9]*\)\.\([0-9]*\),\1,')" >> $GITHUB_ENV
28 | - name: Get owner
29 | run: |
30 | ownerrepo=${{ github.repository }}
31 | owner=$(echo $ownerrepo | cut -f1 -d/)
32 | if [[ "$owner" == "iter8-tools" ]]; then
33 | owner=iter8
34 | fi
35 | echo "OWNER=$owner" >> $GITHUB_ENV
36 | - uses: docker/setup-buildx-action@v3
37 | - uses: docker/login-action@v3
38 | with:
39 | username: ${{ secrets.DOCKERHUB_USERNAME }}
40 | password: ${{ secrets.DOCKERHUB_SECRET }}
41 | - uses: docker/build-push-action@v5
42 | with:
43 | file: docker/Dockerfile
44 | platforms: linux/amd64,linux/arm64
45 | tags: ${{ env.OWNER }}/iter8:${{ env.VERSION }},${{ env.OWNER }}/iter8:${{ env.MAJOR_MINOR_VERSION }},${{ env.OWNER }}/iter8:latest
46 | push: true
47 | build-args: |
48 | TAG=v${{ env.VERSION }}
49 |
--------------------------------------------------------------------------------
/.github/workflows/releasecharts.yaml:
--------------------------------------------------------------------------------
1 | name: Release charts
2 |
3 | # Only runs when charts are pushed
4 |
5 | # Release charts
6 | #
7 | # NOTE: completion of this task will trigger verifyuserexperience.yaml
8 | # which will test the released charts (with released image)
9 |
10 | on:
11 | push:
12 | branches:
13 | - master
14 | paths:
15 | - charts/**
16 |
17 | jobs:
18 | release-charts:
19 | permissions:
20 | contents: write
21 | runs-on: ubuntu-latest
22 | steps:
23 | - name: Checkout
24 | uses: actions/checkout@v4
25 | with:
26 | fetch-depth: 0
27 |
28 | - name: Configure Git
29 | run: |
30 | git config user.name "$GITHUB_ACTOR"
31 | git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
32 |
33 | - name: Install Helm
34 | uses: azure/setup-helm@v3
35 | with:
36 | token: ${{ secrets.GITHUB_TOKEN }}
37 |
38 | - name: Run chart-releaser
39 | uses: helm/chart-releaser-action@v1.5.0
40 | with:
41 | config: config.yaml
42 | env:
43 | CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
44 |
--------------------------------------------------------------------------------
/.github/workflows/spellcheck.yaml:
--------------------------------------------------------------------------------
1 | name: Spell check markdown
2 |
3 | # Runs during pull request
4 |
5 | # Spell check markdown
6 |
7 | on:
8 | pull_request:
9 | branches:
10 | - master
11 |
12 | jobs:
13 | spell-check:
14 | runs-on: ubuntu-latest
15 | steps:
16 | - uses: actions/checkout@v4
17 | with:
18 | fetch-depth: 0
19 | - run: |
20 | pwd
21 | ls -l
22 | - uses: rojopolis/spellcheck-github-actions@0.35.0
23 | with:
24 | config_path: .github/spellcheck.yml
25 |
--------------------------------------------------------------------------------
/.github/workflows/unittest.yaml:
--------------------------------------------------------------------------------
1 | name: Unit test
2 |
3 | # Runs during pull request
4 |
5 | # Always needs to pass in order for PR to be accepted
6 |
7 | on:
8 | pull_request:
9 | branches:
10 | - master
11 |
12 | jobs:
13 | unit-test:
14 | name: unit-test
15 | runs-on: ubuntu-latest
16 | steps:
17 | - name: Install Go
18 | uses: actions/setup-go@v5
19 | with:
20 | go-version: 1.21
21 |
22 | - name: Check out code into the Go module directory
23 | uses: actions/checkout@v4
24 |
25 | - name: Test and compute coverage
26 | run: make coverage # includes vet and lint
27 |
28 | - name: Enforce coverage
29 | run: |
30 | export COVERAGE=$(go tool cover -func coverage.out | grep total | awk '{print substr($3, 1, length($3)-1)}')
31 | echo "code coverage is at ${COVERAGE}"
32 | if [ 1 -eq "$(echo "${COVERAGE} > 76.0" | bc)" ]; then \
33 | echo "all good... coverage is above 76.0%";
34 | else \
35 | echo "not good... coverage is not above 76.0%";
36 | exit 1
37 | fi
38 |
--------------------------------------------------------------------------------
/.github/workflows/versionbump.yaml:
--------------------------------------------------------------------------------
1 | name: Version bump check
2 |
3 | # Only runs when charts have changed
4 |
5 | # Check if the version number of changed charts have been bumped
6 |
7 | on:
8 | pull_request:
9 | branches:
10 | - master
11 | paths:
12 | - charts/**
13 |
14 | jobs:
15 | # Get the paths for the Helm charts to version check
16 | get_paths:
17 | runs-on: ubuntu-latest
18 |
19 | steps:
20 | - uses: actions/checkout@v4
21 | with:
22 | fetch-depth: 0
23 |
24 | - name: Get the paths for Helm charts to version check
25 | id: set-matrix
26 | run: |
27 | # Get paths (in string form)
28 | stringPaths=$(find -maxdepth 2 -path './charts/*')
29 |
30 | # Check paths (length greater than 0)
31 | stringPathsLength=$(echo ${#stringPaths})
32 | if (( stringPathsLength == 0 ));
33 | then
34 | echo "No paths to check"
35 | exit 1
36 | fi
37 |
38 | # Serialize paths into JSON array
39 | paths=$(jq -ncR '[inputs]' <<< "$stringPaths")
40 | echo $paths
41 |
42 | # Output serialized paths
43 | echo "matrix=$paths" >> $GITHUB_OUTPUT
44 |
45 | outputs:
46 | matrix: ${{ steps.set-matrix.outputs.matrix }}
47 |
48 | # Version check Helm charts based on paths provided by previous job
49 | version_check:
50 | name: Version check
51 | needs: get_paths
52 | runs-on: ubuntu-latest
53 | strategy:
54 | matrix:
55 | version: ${{ fromJson(needs.get_paths.outputs.matrix) }}
56 | steps:
57 | - uses: actions/checkout@v4
58 | with:
59 | fetch-depth: 0
60 |
61 | - name: Get modified files in the ${{ matrix.version }} folder
62 | id: modified-files
63 | uses: tj-actions/changed-files@v41
64 | with:
65 | files: ${{ matrix.version }}
66 |
67 | - name: Run step if any file(s) in the ${{ matrix.version }} folder was modified
68 | if: steps.modified-files.outputs.any_modified == 'true'
69 | run: |
70 | # Remove ./ prefix from raw matrix version (i.e. ./charts/iter8 -> charts/iter8)
71 | version=$(echo ${{ matrix.version }} | sed s/".\/"//)
72 |
73 | # Get chart file
74 | chartFile="$version/Chart.yaml"
75 |
76 | # Get git diff of the Chart.yaml between the master branch and PR branch
77 | gitDiff=$(git diff origin/master..HEAD -- $chartFile)
78 | echo $gitDiff
79 |
80 | # Addition in Chart.yaml
81 | addChart="+++ b/$add$chartFile"
82 | echo $addChart
83 |
84 | # Addition of version in Chart.yaml
85 | addVersion="+version:"
86 | echo $addVersion
87 |
88 | if [[ "$gitDiff" == *"$addChart"* ]] && [[ "$gitDiff" == *$addVersion* ]];
89 | then
90 | echo "version in $chartFile has been modified"
91 | else
92 | echo "version in $chartFile needs to be modified"
93 | exit 1
94 | fi
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | # Logs
4 | logs
5 | *.log
6 | npm-debug.log*
7 | yarn-debug.log*
8 | yarn-error.log*
9 | lerna-debug.log*
10 |
11 | # Diagnostic reports (https://nodejs.org/api/report.html)
12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
13 |
14 | # Runtime data
15 | pids
16 | *.pid
17 | *.seed
18 | *.pid.lock
19 |
20 | # Directory for instrumented libs generated by jscoverage/JSCover
21 | lib-cov
22 |
23 | # Coverage directory used by tools like istanbul
24 | coverage
25 | coverage.out
26 | *.lcov
27 |
28 | # nyc test coverage
29 | .nyc_output
30 |
31 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
32 | .grunt
33 |
34 | # Bower dependency directory (https://bower.io/)
35 | bower_components
36 |
37 | # node-waf configuration
38 | .lock-wscript
39 |
40 | # Compiled binary addons (https://nodejs.org/api/addons.html)
41 | build/Release
42 |
43 | # Dependency directories
44 | node_modules/
45 | jspm_packages/
46 |
47 | # TypeScript v1 declaration files
48 | typings/
49 |
50 | # TypeScript cache
51 | *.tsbuildinfo
52 |
53 | # Optional npm cache directory
54 | .npm
55 |
56 | # Optional eslint cache
57 | .eslintcache
58 |
59 | # Microbundle cache
60 | .rpt2_cache/
61 | .rts2_cache_cjs/
62 | .rts2_cache_es/
63 | .rts2_cache_umd/
64 |
65 | # Optional REPL history
66 | .node_repl_history
67 |
68 | # Output of 'npm pack'
69 | *.tgz
70 |
71 | # Yarn Integrity file
72 | .yarn-integrity
73 |
74 | # dotenv environment variables file
75 | .env
76 | .env.test
77 |
78 | # parcel-bundler cache (https://parceljs.org/)
79 | .cache
80 |
81 | # Next.js build output
82 | .next
83 |
84 | # Nuxt.js build/generate output
85 | .nuxt
86 | dist
87 |
88 | # Gatsby files
89 | .cache/
90 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
91 | # https://nextjs.org/blog/next-9-1#public-directory-support
92 | # public
93 |
94 | # vuepress build output
95 | .vuepress/dist
96 |
97 | # Serverless directories
98 | .serverless/
99 |
100 | # FuseBox cache
101 | .fusebox/
102 |
103 | # DynamoDB Local files
104 | .dynamodb/
105 |
106 | # TernJS port file
107 | .tern-port
108 |
109 | # Python
110 | *.py[ocd]
111 | *__pycache__/
112 | *venv*
113 | *.venv*
114 |
115 | # material (built from npm)
116 | material
117 |
118 | # site (built from mkdocs)
119 | site
120 |
121 | # backup pptx
122 | ~$*.pptx
123 |
124 | # vscode
125 | .vscode
126 |
127 | # Helm chart.lock
128 | Chart.lock
129 |
130 | # Iter8 binary and yamls and reports
131 | **/experiment.yaml
132 | !testdata/experiment.yaml
133 | **/result.yaml
134 | **/report.html
135 | **/*.metrics.yaml
136 | !testdata/metrics/*.metrics.yaml
137 |
138 | bin/
139 | _dist/
140 | docker/iter8
141 | **/ghz.proto
142 | **/ghz-call-data.json
143 | **/ghz-call-data.bin
144 | **/ghz-call-metadata.json
145 |
146 | !testdata/charts
147 | !testdata/charts/iter8
148 |
149 | # iter8lib tgz
150 | iter8lib*.tgz
151 |
152 | # data
153 | payload.dat
154 |
155 |
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | # Refer to golangci-lint's example config file for more options and information:
2 | # https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml
3 |
4 | run:
5 | timeout: 10m
6 | modules-download-mode: readonly
7 |
8 | linters:
9 | enable:
10 | - errcheck
11 | - gosimple
12 | - ineffassign
13 | - typecheck
14 | - unused
15 | - goimports
16 | - revive
17 | - govet
18 | - staticcheck
19 | - gosec
20 | - misspell
21 |
22 | issues:
23 | exclude-use-default: false
24 | max-issues-per-linter: 0
25 | max-same-issues: 0
--------------------------------------------------------------------------------
/.lycheeignore:
--------------------------------------------------------------------------------
1 | http://localhost:8000/
--------------------------------------------------------------------------------
/ADOPTERS.md:
--------------------------------------------------------------------------------
1 | # Adopters
2 |
3 | If you are starting to use Iter8, we would love to see you in the list below. Please raise a PR to add yourself to this list.
4 |
5 | | Organization / Project / Company | Contact(s) |
6 | | --- | --- |
7 | | IBM Cloud (DevOps Toolchains) | [Michael Kalantar](https://github.com/kalantar), [Srinivasan Parthasarathy](https://github.com/sriumcp) |
8 | | IBM Research Cloud Innovation Lab | [Atin Sood](https://github.com/atinsood)|
9 | | IBM Cloud (Code Engine) | [Doug Davis](https://github.com/duglin) |
10 | | ChaosNative (LitmusChaos) | [Shubham Chaudhary](https://github.com/ispeakc0de) |
11 | | Seldon Core | [Clive Cox](https://github.com/cliveseldon) |
12 | | Datagrate, Inc. (jetic.io) | [Mert Öztürk](https://github.com/mertdotcc) |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing or otherwise unacceptable behavior may be reported by contacting the project team at iter8tools@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it seems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
--------------------------------------------------------------------------------
/MAINTAINERS.md:
--------------------------------------------------------------------------------
1 | # Overview
2 |
3 | This document contains a list of maintainers for Iter8.
4 |
5 | | Maintainer | GitHub ID | Email |
6 | |---------------------------| --------------------------------------- | ------------------- |
7 | | Michael Kalantar | [kalantar](https://github.com/kalantar) | kalantar@us.ibm.com |
8 | | Alan Cha | [Ala-Cha](https://github.com/Alan-Cha) | Alan.Cha1@ibm.com |
9 | | Srinivasan Parthasarathy | [sriumcp](https://github.com/sriumcp) | spartha@us.ibm.com |
10 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | BINDIR := $(CURDIR)/bin
2 | INSTALL_PATH ?= /usr/local/bin
3 | BINNAME ?= iter8
4 |
5 | GOBIN = $(shell go env GOBIN)
6 | ifeq ($(GOBIN),)
7 | GOBIN = $(shell go env GOPATH)/bin
8 | endif
9 |
10 | # go options
11 | TAGS :=
12 | LDFLAGS := -w -s
13 | GOFLAGS :=
14 |
15 | # Rebuild the binary if any of these files change
16 | SRC := $(shell find . -type f -name '*.(go|proto|tpl)' -print) go.mod go.sum
17 |
18 | # Required for globs to work correctly
19 | SHELL = /usr/bin/env bash
20 |
21 | GIT_COMMIT = $(shell git rev-parse HEAD)
22 | GIT_TAG = $(shell git describe --tags --dirty)
23 |
24 | ifdef VERSION
25 | BINARY_VERSION = $(VERSION)
26 | endif
27 | BINARY_VERSION ?= ${GIT_TAG}
28 |
29 | # Only set Version if GIT_TAG or VERSION is set
30 | ifneq ($(BINARY_VERSION),)
31 | LDFLAGS += -X github.com/iter8-tools/iter8/base.Version=${BINARY_VERSION}
32 | endif
33 |
34 |
35 | LDFLAGS += -X github.com/iter8-tools/iter8/cmd.gitCommit=${GIT_COMMIT}
36 |
37 | .PHONY: all
38 | all: build
39 |
40 | # ------------------------------------------------------------------------------
41 | # build
42 |
43 | .PHONY: build
44 | build: $(BINDIR)/$(BINNAME)
45 |
46 | $(BINDIR)/$(BINNAME): $(SRC)
47 | GO111MODULE=on go build $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o '$(BINDIR)'/$(BINNAME) ./
48 |
49 | # ------------------------------------------------------------------------------
50 | # install
51 |
52 | .PHONY: install
53 | install: build
54 | @install "$(BINDIR)/$(BINNAME)" "$(INSTALL_PATH)/$(BINNAME)"
55 |
56 | # ------------------------------------------------------------------------------
57 | # dependencies
58 |
59 | .PHONY: clean
60 | clean:
61 | @rm -rf '$(BINDIR)'
62 |
63 | # ------------------------------------------------------------------------------
64 | # test
65 |
66 | .PHONY: fmt
67 | fmt: ## Run go fmt against code.
68 | go fmt ./...
69 |
70 | .PHONY: vet
71 | vet: ## Run go vet against code
72 | go vet ./...
73 |
74 | .PHONY: golangci-lint
75 | golangci-lint:
76 | golangci-lint run ./...
77 |
78 | .PHONY: lint
79 | lint: vet golangci-lint
80 |
81 | .PHONY: test
82 | test: fmt vet ## Run tests.
83 | go test -v ./... -coverprofile=coverage.out
84 |
85 | .PHONY: coverage
86 | coverage: test
87 | @echo "test coverage: $(shell go tool cover -func coverage.out | grep total | awk '{print substr($$3, 1, length($$3)-1)}')"
88 |
89 | .PHONY: htmlcov
90 | htmlcov: coverage
91 | go tool cover -html=coverage.out
92 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Iter8: Kubernetes Release Optimizer
2 |
3 | [](https://github.com/iter8-tools/iter8/releases)
4 | [](https://pkg.go.dev/github.com/iter8-tools/iter8)
5 |
6 | Iter8 is the Kubernetes release optimizer built for DevOps, MLOps, SRE and data science teams. Iter8 makes it easy to ensure that Kubernetes apps and ML models perform well and maximize business value.
7 |
8 | Iter8 supports the following use-cases:
9 |
10 | 1. Progressive release with automated traffic management
11 | 2. A/B/n testing with a client SDK and business metrics
12 | 3. Performance testing for HTTP and gRPC endpoints
13 |
14 | Any Kubernetes resource type, including CRDs can be used with Iter8.
15 |
16 | ## :rocket: Features
17 |
18 | Iter8 introduces a set of tasks which can be composed in order to conduct tests.
19 |
20 |
21 |
22 |
23 |
24 | Iter8 packs a number of powerful features that facilitate Kubernetes application and ML model testing. They include the following:
25 |
26 | 1. **Use any resource types.** Iter8 is easily extensible so that an application being tested can be composed of any resource types including CRDs.
27 | 2. **Client SDK.** A client SDK enables application frontend components to reliably associate business metrics with the contributing version of the backend thereby enabling A/B/n testing of backends.
28 | 3. **Composable test tasks.** Performance test tasks include load generation and metrics storage simplifying setup.
29 |
30 | Please see [https://iter8.tools](https://iter8.tools) for the complete documentation.
31 |
32 | ## :maple_leaf: Issues
33 | Iter8 issues are tracked [here](https://github.com/iter8-tools/iter8/issues).
34 |
35 | ## :tada: Contributing
36 | We welcome PRs!
37 |
38 | See [here](CONTRIBUTING.md) for information about ways to contribute, finding an issue, asking for help, pull-request lifecycle, and more.
39 |
40 | ## :hibiscus: Credits
41 | Iter8 is primarily written in `Go` and builds on a few awesome open source projects including:
42 |
43 | - [Helm](https://helm.sh)
44 | - [Istio](https://istio.io)
45 | - [Kubernetes Gateway API](https://gateway-api.sigs.k8s.io/)
46 | - [Fortio](https://github.com/fortio/fortio)
47 | - [ghz](https://ghz.sh)
48 | - [Grafana](https://grafana.com/)
49 |
--------------------------------------------------------------------------------
/abn/grpc/abn.proto:
--------------------------------------------------------------------------------
1 | // protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative abn/grpc/abn.proto
2 | // python -m grpc_tools.protoc -I../../../iter8-tools/iter8/abn/grpc --python_out=. --grpc_python_out=. ../../../iter8-tools/iter8/abn/grpc/abn.proto
3 |
4 | syntax = "proto3";
5 |
6 | option go_package = "github.com/iter8-tools/iter8/abn/grpc";
7 |
8 | import "google/protobuf/empty.proto";
9 | package main;
10 |
11 | // for more information, see https://github.com/iter8-tools/iter8/issues/1257
12 |
13 | service ABN {
14 | // Identify a version (index) the caller should send a request to.
15 | // Should be called for each request (transaction).
16 | rpc Lookup(Application) returns(VersionRecommendation) {}
17 |
18 | // Write a metric value to metrics database.
19 | // The metric value is explicitly associated with a list of transactions that contributed to its computation.
20 | // The user is expected to identify these transactions.
21 | rpc WriteMetric(MetricValue) returns (google.protobuf.Empty) {}
22 | }
23 |
24 | message Application {
25 | // name of (backend) application or service
26 | // This value is used to identify the Kubernetes objects that make up the service
27 | // Kubernetes objects that comprise the service should have the label app.kubernetes.io/name set to name
28 | string name = 1;
29 | // User or user session identifier
30 | string user = 2;
31 | }
32 |
33 | message VersionRecommendation {
34 | // versionNumber index of an application version
35 | int32 versionNumber = 1;
36 | }
37 |
38 | message MetricValue {
39 | // Metric name
40 | string name = 1;
41 | // Metric value
42 | string value = 2;
43 | // name of application
44 | string application = 3;
45 | // User or user session identifier
46 | string user = 4;
47 | }
48 |
49 | // https://developers.google.com/protocol-buffers/docs/proto3
--------------------------------------------------------------------------------
/abn/service_impl_test.go:
--------------------------------------------------------------------------------
1 | package abn
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/dgraph-io/badger/v4"
7 | "github.com/google/uuid"
8 | util "github.com/iter8-tools/iter8/base"
9 | "github.com/iter8-tools/iter8/storage/badgerdb"
10 | storageclient "github.com/iter8-tools/iter8/storage/client"
11 | "github.com/stretchr/testify/assert"
12 | )
13 |
14 | // tests that we get the same result for the same inputs
15 | func TestLookupInternal(t *testing.T) {
16 | var err error
17 | // set up test metrics db for recording users
18 | tempDirPath := t.TempDir()
19 | storageclient.MetricsClient, err = badgerdb.GetClient(badger.DefaultOptions(tempDirPath), badgerdb.AdditionalOptions{})
20 | assert.NoError(t, err)
21 |
22 | // setup: add desired routemaps to allRoutemaps
23 | testRM := testRoutemaps{
24 | allroutemaps: setupRoutemaps(t, *getTestRM("default", "test")),
25 | }
26 | allRoutemaps = &testRM
27 |
28 | tries := 20 // needs to be big enough to find at least one problem; this is probably overkill
29 | // do lookup tries times
30 | versionNumbers := make([]int, tries)
31 | for i := 0; i < tries; i++ {
32 | _, v, err := lookupInternal("default/test", "user")
33 | assert.NoError(t, err)
34 | versionNumbers[i] = v
35 | }
36 |
37 | tr := versionNumbers[0]
38 | for i := 1; i < tries; i++ {
39 | assert.Equal(t, tr, versionNumbers[i])
40 | }
41 | }
42 |
43 | func TestWeights(t *testing.T) {
44 | var err error
45 |
46 | // set up test metrics db for recording users
47 | tempDirPath := t.TempDir()
48 | storageclient.MetricsClient, err = badgerdb.GetClient(badger.DefaultOptions(tempDirPath), badgerdb.AdditionalOptions{})
49 | assert.NoError(t, err)
50 |
51 | // setup: add desired routemaps to allRoutemaps
52 | testRM := testRoutemaps{
53 | allroutemaps: setupRoutemaps(t, *getWeightedTestRM("default", "test", []uint32{3, 1})),
54 | }
55 | allRoutemaps = &testRM
56 |
57 | tries := 100
58 | versionNumbers := make([]int, tries)
59 | for i := 0; i < tries; i++ {
60 | _, v, err := lookupInternal("default/test", uuid.NewString())
61 | assert.NoError(t, err)
62 | versionNumbers[i] = v
63 | }
64 |
65 | // expect 3/4 will be for version 0 (weight 3); ie, 75
66 | // expect 1/4 will be for version 1 (weight 1); ie, 25
67 | // compute number for version 1 by summing versionNumbers
68 | // assert less than 30 (bigger than 25)
69 | // there is a slight possibility of test failure
70 |
71 | sum := 0
72 | for i := 1; i < tries; i++ {
73 | sum += versionNumbers[i]
74 | }
75 | assert.Less(t, sum, 30)
76 | }
77 |
78 | func getWeightedTestRM(namespace, name string, weights []uint32) *testroutemap {
79 | copyWeights := make([]uint32, len(weights))
80 | versions := make([]testversion, len(weights))
81 | for i := range weights {
82 | copyWeights[i] = weights[i]
83 | versions[i] = testversion{signature: util.StringPointer(uuid.NewString())}
84 | }
85 |
86 | return &testroutemap{
87 | namespace: namespace,
88 | name: name,
89 | versions: versions,
90 | normalizedWeights: copyWeights,
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/abn/test_helpers.go:
--------------------------------------------------------------------------------
1 | package abn
2 |
3 | import "github.com/iter8-tools/iter8/controllers"
4 |
5 | type testroutemapsByName map[string]*testroutemap
6 | type testroutemaps struct {
7 | nsRoutemap map[string]testroutemapsByName
8 | }
9 |
10 | func (s *testroutemaps) GetRoutemapFromNamespaceName(namespace string, name string) controllers.RoutemapInterface {
11 | rmByName, ok := s.nsRoutemap[namespace]
12 | if ok {
13 | return rmByName[name]
14 | }
15 | return nil
16 | }
17 |
18 | type testversion struct {
19 | signature *string
20 | }
21 |
22 | func (v *testversion) GetSignature() *string {
23 | return v.signature
24 | }
25 |
26 | type testroutemap struct {
27 | name string
28 | namespace string
29 | versions []testversion
30 | normalizedWeights []uint32
31 | }
32 |
33 | func (s *testroutemap) RLock() {}
34 |
35 | func (s *testroutemap) RUnlock() {}
36 |
37 | func (s *testroutemap) GetNamespace() string {
38 | return s.namespace
39 | }
40 |
41 | func (s *testroutemap) GetName() string {
42 | return s.name
43 | }
44 |
45 | func (s *testroutemap) Weights() []uint32 {
46 | return s.normalizedWeights
47 | }
48 |
49 | func (s *testroutemap) GetVersions() []controllers.VersionInterface {
50 | result := make([]controllers.VersionInterface, len(s.versions))
51 | for i := range s.versions {
52 | v := s.versions[i]
53 | result[i] = controllers.VersionInterface(&v)
54 | }
55 | return result
56 | }
57 |
58 | type testRoutemaps struct {
59 | allroutemaps testroutemaps
60 | }
61 |
62 | func (cm *testRoutemaps) GetAllRoutemaps() controllers.RoutemapsInterface {
63 | return &cm.allroutemaps
64 | }
65 |
--------------------------------------------------------------------------------
/action/doc.go:
--------------------------------------------------------------------------------
1 | // Package action contains the logic for each action that Iter8 can perform.
2 | //
3 | // This is a library for calling top-level Iter8 actions like 'launch' and 'assert'.
4 | // Actions approximately match the command line invocations that the Iter8 CLI uses.
5 | package action
6 |
--------------------------------------------------------------------------------
/action/run.go:
--------------------------------------------------------------------------------
1 | package action
2 |
3 | import (
4 | "github.com/iter8-tools/iter8/base"
5 | "github.com/iter8-tools/iter8/driver"
6 | )
7 |
8 | // RunOpts are the options used for running an experiment
9 | type RunOpts struct {
10 | // Rundir is the directory of the local experiment.yaml file
11 | RunDir string
12 |
13 | // KubeDriver enables Kubernetes experiment run
14 | *driver.KubeDriver
15 | }
16 |
17 | // NewRunOpts initializes and returns run opts
18 | func NewRunOpts(kd *driver.KubeDriver) *RunOpts {
19 | return &RunOpts{
20 | RunDir: ".",
21 | KubeDriver: kd,
22 | }
23 | }
24 |
25 | // KubeRun runs a Kubernetes experiment
26 | func (rOpts *RunOpts) KubeRun() error {
27 | // initialize kube driver
28 | if err := rOpts.KubeDriver.InitKube(); err != nil {
29 | return err
30 | }
31 |
32 | return base.RunExperiment(rOpts.KubeDriver)
33 | }
34 |
--------------------------------------------------------------------------------
/action/run_test.go:
--------------------------------------------------------------------------------
1 | package action
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "io"
8 | "net/http"
9 | "os"
10 | "testing"
11 |
12 | "fortio.org/fortio/fhttp"
13 | "github.com/iter8-tools/iter8/base"
14 | "github.com/iter8-tools/iter8/driver"
15 | "github.com/stretchr/testify/assert"
16 | "helm.sh/helm/v3/pkg/cli"
17 | corev1 "k8s.io/api/core/v1"
18 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19 | )
20 |
21 | const (
22 | myName = "myName"
23 | myNamespace = "myNamespace"
24 | )
25 |
26 | func TestKubeRun(t *testing.T) {
27 | // define METRICS_SERVER_URL
28 | metricsServerURL := "http://iter8.default:8080"
29 | err := os.Setenv(base.MetricsServerURL, metricsServerURL)
30 | assert.NoError(t, err)
31 |
32 | // create and configure HTTP endpoint for testing
33 | mux, addr := fhttp.DynamicHTTPServer(false)
34 | url := fmt.Sprintf("http://127.0.0.1:%d/get", addr.Port)
35 | var verifyHandlerCalled bool
36 | mux.HandleFunc("/get", base.GetTrackingHandler(&verifyHandlerCalled))
37 |
38 | // mock metrics server
39 | base.StartHTTPMock(t)
40 | metricsServerCalled := false
41 | base.MockMetricsServer(base.MockMetricsServerInput{
42 | MetricsServerURL: metricsServerURL,
43 | ExperimentResultCallback: func(req *http.Request) {
44 | metricsServerCalled = true
45 |
46 | // check query parameters
47 | assert.Equal(t, myName, req.URL.Query().Get("test"))
48 | assert.Equal(t, myNamespace, req.URL.Query().Get("namespace"))
49 |
50 | // check payload
51 | body, err := io.ReadAll(req.Body)
52 | assert.NoError(t, err)
53 | assert.NotNil(t, body)
54 |
55 | // check payload content
56 | bodyExperimentResult := base.ExperimentResult{}
57 | err = json.Unmarshal(body, &bodyExperimentResult)
58 | assert.NoError(t, err)
59 | assert.NotNil(t, body)
60 |
61 | // no experiment failure
62 | assert.False(t, bodyExperimentResult.Failure)
63 | },
64 | })
65 |
66 | _ = os.Chdir(t.TempDir())
67 |
68 | // create experiment.yaml
69 | base.CreateExperimentYaml(t, base.CompletePath("../testdata", base.ExperimentTemplateFile), url, base.ExperimentFile)
70 |
71 | // fix rOpts
72 | rOpts := NewRunOpts(driver.NewFakeKubeDriver(cli.New()))
73 |
74 | // read experiment from file created above
75 | byteArray, _ := os.ReadFile(base.ExperimentFile)
76 | _, _ = rOpts.Clientset.CoreV1().Secrets("default").Create(context.TODO(), &corev1.Secret{
77 | ObjectMeta: metav1.ObjectMeta{
78 | Name: "default",
79 | Namespace: "default",
80 | },
81 | StringData: map[string]string{base.ExperimentFile: string(byteArray)},
82 | }, metav1.CreateOptions{})
83 |
84 | err = rOpts.KubeRun()
85 | assert.NoError(t, err)
86 | // sanity check -- handler was called
87 | assert.True(t, verifyHandlerCalled)
88 | assert.True(t, metricsServerCalled)
89 | }
90 |
--------------------------------------------------------------------------------
/base/doc.go:
--------------------------------------------------------------------------------
1 | // Package base defines Iter8's experiment, task and metric data structures.
2 | // It contains the core logic for running an experiment.
3 | package base
4 |
--------------------------------------------------------------------------------
/base/insights_test.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestTrackVersionStr(t *testing.T) {
10 | scenarios := map[string]struct {
11 | in Insights
12 | expectedStr string
13 | }{
14 | "VersionNames is nil": {in: Insights{}, expectedStr: "version 0"},
15 | "Version and Track empty": {in: Insights{VersionNames: []VersionInfo{}}, expectedStr: "version 0"},
16 | "Track is empty": {in: Insights{VersionNames: []VersionInfo{{Version: "version"}}}, expectedStr: "version"},
17 | "Version is empty": {in: Insights{VersionNames: []VersionInfo{{Track: "track"}}}, expectedStr: "track"},
18 | "Version and Track not empty": {in: Insights{VersionNames: []VersionInfo{{Track: "track", Version: "version"}}}, expectedStr: "track (version)"},
19 | }
20 |
21 | for l, s := range scenarios {
22 | t.Run(l, func(t *testing.T) {
23 | assert.Equal(t, s.expectedStr, s.in.TrackVersionStr(0))
24 | })
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/base/internal/common.go:
--------------------------------------------------------------------------------
1 | package internal
2 |
3 | /*
4 | Credit: This file is sourced from https://github.com/bojand/ghz and modified for reuse in Iter8
5 | */
6 |
7 | import (
8 | "net"
9 |
10 | "google.golang.org/grpc"
11 | "google.golang.org/grpc/reflection"
12 |
13 | "github.com/iter8-tools/iter8/base/internal/helloworld/helloworld"
14 | )
15 |
16 | // LocalHostPort is the localhost:12345 combo used for testing
17 | const LocalHostPort = "localhost:12345"
18 |
19 | // StartServer starts the server.
20 | //
21 | // For testing only.
22 | func StartServer(_ bool) (*helloworld.Greeter, *grpc.Server, error) {
23 | lis, err := net.Listen("tcp", LocalHostPort)
24 | if err != nil {
25 | return nil, nil, err
26 | }
27 |
28 | var opts []grpc.ServerOption
29 |
30 | stats := helloworld.NewHWStats()
31 |
32 | opts = append(opts, grpc.StatsHandler(stats))
33 |
34 | s := grpc.NewServer(opts...)
35 |
36 | gs := helloworld.NewGreeter()
37 | helloworld.RegisterGreeterServer(s, gs)
38 | reflection.Register(s)
39 |
40 | gs.Stats = stats
41 |
42 | go func() {
43 | _ = s.Serve(lis)
44 | }()
45 |
46 | return gs, s, err
47 | }
48 |
--------------------------------------------------------------------------------
/base/internal/doc.go:
--------------------------------------------------------------------------------
1 | // Package internal provides gRPC code used for testing load-test-grpc
2 | package internal
3 |
--------------------------------------------------------------------------------
/base/internal/helloworld/helloworld/doc.go:
--------------------------------------------------------------------------------
1 | // Package helloworld implements the helloworld grpc service.
2 | // This package is used for testing the load-test-grpc task
3 | package helloworld
4 |
--------------------------------------------------------------------------------
/base/internal/helloworld/helloworld/greeter.proto:
--------------------------------------------------------------------------------
1 | // Credit: This file is from https://github.com/bojand/ghz/
2 | // this file is used for test purposes
3 | syntax = "proto3";
4 |
5 | option go_package = "github.com/iter8-tools/iter8/base/internal/helloworld/helloworld";
6 |
7 | package helloworld;
8 |
9 | service Greeter {
10 | rpc SayHello (HelloRequest) returns (HelloReply) {}
11 | rpc SayHelloCS (stream HelloRequest) returns (HelloReply) {}
12 | rpc SayHellos (HelloRequest) returns (stream HelloReply) {}
13 | rpc SayHelloBidi (stream HelloRequest) returns (stream HelloReply) {}
14 | }
15 |
16 | // The request message containing the user's name.
17 | message HelloRequest {
18 | string name = 1;
19 | }
20 |
21 | // The response message containing the greetings
22 | message HelloReply {
23 | string message = 1;
24 | }
--------------------------------------------------------------------------------
/base/kubedriver.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | import (
4 | "errors"
5 |
6 | // Import to initialize client auth plugins.
7 | _ "k8s.io/client-go/plugin/pkg/client/auth"
8 |
9 | "github.com/iter8-tools/iter8/base/log"
10 |
11 | "helm.sh/helm/v3/pkg/cli"
12 |
13 | "k8s.io/client-go/dynamic"
14 | )
15 |
16 | var (
17 | kd = NewKubeDriver(cli.New())
18 | )
19 |
20 | // KubeDriver embeds Kube configuration, and
21 | // enables interaction with a Kubernetes cluster through Kube APIs
22 | type KubeDriver struct {
23 | // EnvSettings provides generic Kubernetes options
24 | *cli.EnvSettings
25 | // dynamicClient enables unstructured interaction with a Kubernetes cluster
26 | dynamicClient dynamic.Interface
27 | }
28 |
29 | // NewKubeDriver creates and returns a new KubeDriver
30 | func NewKubeDriver(s *cli.EnvSettings) *KubeDriver {
31 | kd := &KubeDriver{
32 | EnvSettings: s,
33 | dynamicClient: nil,
34 | }
35 | return kd
36 | }
37 |
38 | // initKube initializes the Kubernetes clientset
39 | func (kd *KubeDriver) initKube() (err error) {
40 | if kd.dynamicClient == nil {
41 | // get REST config
42 | restConfig, err := kd.EnvSettings.RESTClientGetter().ToRESTConfig()
43 | if err != nil {
44 | e := errors.New("unable to get Kubernetes REST config")
45 | log.Logger.WithStackTrace(err.Error()).Error(e)
46 | return e
47 | }
48 | kd.dynamicClient, err = dynamic.NewForConfig(restConfig)
49 | if err != nil {
50 | e := errors.New("unable to get Kubernetes dynamic client")
51 | log.Logger.WithStackTrace(err.Error()).Error(e)
52 | return e
53 | }
54 | }
55 |
56 | return nil
57 | }
58 |
--------------------------------------------------------------------------------
/base/kubedriver_test.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | "helm.sh/helm/v3/pkg/cli"
8 | )
9 |
10 | func TestInitKube(t *testing.T) {
11 | kubeDriver := NewKubeDriver(cli.New())
12 | err := kubeDriver.initKube()
13 |
14 | assert.NoError(t, err)
15 | }
16 |
--------------------------------------------------------------------------------
/base/log/doc.go:
--------------------------------------------------------------------------------
1 | // Package log enables logging for Iter8.
2 | package log
3 |
--------------------------------------------------------------------------------
/base/log/log.go:
--------------------------------------------------------------------------------
1 | // Package log provides primitives for logging.
2 | package log
3 |
4 | import (
5 | "bufio"
6 | "strings"
7 |
8 | "github.com/sirupsen/logrus"
9 | )
10 |
11 | // Level is the log level for Iter8 CLI
12 | var Level = logrus.InfoLevel
13 |
14 | // Iter8Logger inherits all methods from logrus logger.
15 | // Provides additional methods for standardized Iter8 logging.
16 | type Iter8Logger struct {
17 | *logrus.Logger
18 | }
19 |
20 | // StackTrace is the trace from external components like a shell scripts run by an Iter8 task.
21 | type StackTrace struct {
22 | // prefix is the string with which external traces are prefixed
23 | prefix string
24 | // Trace is the raw trace
25 | Trace string
26 | }
27 |
28 | // Logger to be used in all of Iter8.
29 | var Logger *Iter8Logger
30 |
31 | // init initializes the logger.
32 | func init() {
33 | Logger = &Iter8Logger{logrus.New()}
34 | Logger.SetFormatter(&logrus.TextFormatter{
35 | TimestampFormat: "2006-01-02 15:04:05",
36 | FullTimestamp: true,
37 | DisableQuote: true,
38 | DisableSorting: true,
39 | })
40 |
41 | Logger.SetLevel(Level)
42 | }
43 |
44 | // WithStackTrace yields a log entry with a formatted stack trace field embedded in it.
45 | func (l *Iter8Logger) WithStackTrace(t string) *logrus.Entry {
46 | return l.WithField("stack-trace", &StackTrace{
47 | prefix: "::Trace:: ",
48 | Trace: t,
49 | })
50 | }
51 |
52 | // WithIndentedTrace yields a log entry with a formatted indent embedded in it.
53 | func (l *Iter8Logger) WithIndentedTrace(t string) *logrus.Entry {
54 | return l.WithField("indented-trace", &StackTrace{
55 | prefix: " ",
56 | Trace: t,
57 | })
58 | }
59 |
60 | // String processes stack traces by prefixing each line of the trace with prefix.
61 | // This enables other tools like grep to easily filter out these traces if needed.
62 | func (st *StackTrace) String() string {
63 | out := "below ... \n"
64 | scanner := bufio.NewScanner(strings.NewReader(st.Trace))
65 | for scanner.Scan() {
66 | out += st.prefix + scanner.Text() + "\n"
67 | }
68 | out = strings.TrimSuffix(out, "\n")
69 | return out
70 | }
71 |
--------------------------------------------------------------------------------
/base/log/log_test.go:
--------------------------------------------------------------------------------
1 | package log
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestStackTrace(t *testing.T) {
11 | Logger.WithIndentedTrace("hello there")
12 | Logger.WithStackTrace("hello there")
13 |
14 | st := StackTrace{
15 | prefix: "::Trace:: ",
16 | Trace: fmt.Sprintln("a") + fmt.Sprintln("b"),
17 | }
18 | assert.Contains(t, st.String(), "::Trace:: a")
19 | assert.Contains(t, st.String(), "::Trace:: b")
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/base/metrics.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "net/http"
8 | "net/url"
9 |
10 | log "github.com/iter8-tools/iter8/base/log"
11 | )
12 |
13 | const (
14 | // MetricsServerURL is the URL of the metrics server
15 | MetricsServerURL = "METRICS_SERVER_URL"
16 |
17 | // TestResultPath is the path to the PUT /testResult endpoint
18 | TestResultPath = "/testResult"
19 |
20 | // AbnDashboard is the path to the GET /abnDashboard endpoint
21 | AbnDashboard = "/abnDashboard"
22 | // HTTPDashboardPath is the path to the GET /httpDashboard endpoint
23 | HTTPDashboardPath = "/httpDashboard"
24 | // GRPCDashboardPath is the path to the GET /grpcDashboard endpoint
25 | GRPCDashboardPath = "/grpcDashboard"
26 | )
27 |
28 | // callMetricsService is a general function that can be used to send data to the metrics service
29 | func callMetricsService(method, metricsServerURL, path string, queryParams map[string]string, payload interface{}) error {
30 | // handle URL and URL parameters
31 | u, err := url.ParseRequestURI(metricsServerURL + path)
32 | if err != nil {
33 | return err
34 | }
35 |
36 | params := url.Values{}
37 | for paramKey, paramValue := range queryParams {
38 | params.Add(paramKey, paramValue)
39 | }
40 | u.RawQuery = params.Encode()
41 | urlStr := fmt.Sprintf("%v", u)
42 |
43 | log.Logger.Trace(fmt.Sprintf("call metrics service URL: %s", urlStr))
44 |
45 | // handle payload
46 | dataBytes, err := json.Marshal(payload)
47 | if err != nil {
48 | log.Logger.Error("cannot JSON marshal data for metrics server request: ", err)
49 | return err
50 | }
51 |
52 | // create request
53 | req, err := http.NewRequest(method, urlStr, bytes.NewBuffer(dataBytes))
54 | if err != nil {
55 | log.Logger.Error("cannot create new HTTP request metrics server: ", err)
56 | return err
57 | }
58 |
59 | req.Header.Set("Content-Type", "application/json")
60 |
61 | log.Logger.Trace("sending request")
62 |
63 | // send request
64 | client := &http.Client{}
65 | resp, err := client.Do(req)
66 | if err != nil {
67 | log.Logger.Error("could not send request to metrics server: ", err)
68 | return err
69 | }
70 | defer func() {
71 | err = resp.Body.Close()
72 | if err != nil {
73 | log.Logger.Error("could not close response body: ", err)
74 | }
75 | }()
76 |
77 | log.Logger.Trace("sent request")
78 |
79 | return nil
80 | }
81 |
82 | // PutExperimentResultToMetricsService sends the test result to the metrics service
83 | func PutExperimentResultToMetricsService(metricsServerURL, namespace, experiment string, experimentResult *ExperimentResult) error {
84 | return callMetricsService(http.MethodPut, metricsServerURL, TestResultPath, map[string]string{
85 | "namespace": namespace,
86 | "test": experiment,
87 | }, experimentResult)
88 | }
89 |
--------------------------------------------------------------------------------
/base/run.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "os/exec"
7 |
8 | log "github.com/iter8-tools/iter8/base/log"
9 | )
10 |
11 | const (
12 | // RunTaskName is the name of the run task which performs running of a shell script
13 | RunTaskName = "run"
14 | )
15 |
16 | var (
17 | // tempDirEnv is a temporary directory
18 | tempDirEnv = fmt.Sprintf("TEMP_DIR=%v", os.TempDir())
19 | )
20 |
21 | // runTask enables running a shell script
22 | type runTask struct {
23 | // TaskMeta has fields common to all tasks
24 | TaskMeta
25 | }
26 |
27 | // initializeDefaults sets default values for task inputs
28 | func (t *runTask) initializeDefaults() {}
29 |
30 | // validateInputs for this task
31 | func (t *runTask) validateInputs() error {
32 | return nil
33 | }
34 |
35 | // getCommand gets the executable command
36 | func (t *runTask) getCommand() *exec.Cmd {
37 | cmdStr := *t.TaskMeta.Run
38 | // create command to be executed
39 | // #nosec
40 | cmd := exec.Command("/bin/bash", "-c", cmdStr)
41 | // append the environment variable for temp dir
42 | cmd.Env = append(os.Environ(), tempDirEnv)
43 | return cmd
44 | }
45 |
46 | // run the command
47 | func (t *runTask) run(_ *Experiment) error {
48 | err := t.validateInputs()
49 | if err != nil {
50 | return err
51 | }
52 |
53 | t.initializeDefaults()
54 |
55 | cmd := t.getCommand()
56 | out, err := cmd.CombinedOutput()
57 | if err != nil {
58 | log.Logger.WithStackTrace(err.Error()).Error("combined execution failed")
59 | log.Logger.WithStackTrace(string(out)).Error("combined output from command")
60 | return err
61 | }
62 | log.Logger.WithStackTrace(string(out)).Trace("combined output from command")
63 | return nil
64 | }
65 |
--------------------------------------------------------------------------------
/base/run_test.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | import (
4 | "os"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestRunRun(t *testing.T) {
11 | _ = os.Chdir(t.TempDir())
12 | // valid run task... should succeed
13 | rt := &runTask{
14 | TaskMeta: TaskMeta{
15 | Run: StringPointer("echo hello"),
16 | },
17 | }
18 |
19 | exp := &Experiment{
20 | Spec: []Task{rt},
21 | Result: &ExperimentResult{},
22 | }
23 | exp.initResults(1)
24 | err := rt.run(exp)
25 | assert.NoError(t, err)
26 | }
27 |
--------------------------------------------------------------------------------
/base/sprigutil.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | )
7 |
8 | // Note: the following code snippets are from sprig library
9 | // https://github.com/Masterminds/sprig
10 |
11 | // The following copyright notice is from the sprig library.
12 | // This copyright applies to the code in this file.
13 | // It is included as required by the MIT License under which sprig is released.
14 |
15 | /*
16 | Copyright (C) 2013-2020 Masterminds
17 |
18 | Permission is hereby granted, free of charge, to any person obtaining a copy
19 | of this software and associated documentation files (the "Software"), to deal
20 | in the Software without restriction, including without limitation the rights
21 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
22 | copies of the Software, and to permit persons to whom the Software is
23 | furnished to do so, subject to the following conditions:
24 |
25 | The above copyright notice and this permission notice shall be included in
26 | all copies or substantial portions of the Software.
27 |
28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
29 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
30 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
31 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
32 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
33 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
34 | THE SOFTWARE.
35 | */
36 |
37 | // Uniq deduplicates a list
38 | // We have switched from uniq to Uniq, since we want to use it in other packages
39 | func Uniq(list interface{}) []interface{} {
40 | l, err := mustUniq(list)
41 | if err != nil {
42 | panic(err)
43 | }
44 |
45 | return l
46 | }
47 |
48 | // mustUniq deduplicates a list and returns an error if the type doesn't permit equality checks
49 | // this function has been modified from the sprig implementation, in order to use the
50 | // the two valued inList function
51 | func mustUniq(list interface{}) ([]interface{}, error) {
52 | tp := reflect.TypeOf(list).Kind()
53 | switch tp {
54 | case reflect.Slice, reflect.Array:
55 | l2 := reflect.ValueOf(list)
56 |
57 | l := l2.Len()
58 | dest := []interface{}{}
59 | var item interface{}
60 | for i := 0; i < l; i++ {
61 | item = l2.Index(i).Interface()
62 | if ok, _ := inList(dest, item); !ok {
63 | dest = append(dest, item)
64 | }
65 | }
66 |
67 | return dest, nil
68 | default:
69 | return nil, fmt.Errorf("cannot find uniq on type %s", tp)
70 | }
71 | }
72 |
73 | // inList checks if needle is present in haystack
74 | // this function has been modified from the sprig implementation, in order to return index also
75 | func inList(haystack []interface{}, needle interface{}) (bool, int) {
76 | for i, h := range haystack {
77 | if reflect.DeepEqual(needle, h) {
78 | return true, i
79 | }
80 | }
81 | return false, -1
82 | }
83 |
--------------------------------------------------------------------------------
/base/test_helpers_driver.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | import (
4 | "helm.sh/helm/v3/pkg/cli"
5 |
6 | "k8s.io/apimachinery/pkg/runtime"
7 | dynamicfake "k8s.io/client-go/dynamic/fake"
8 | )
9 |
10 | // initKubeFake initialize the Kube clientset with a fake
11 | func initKubeFake(kd *KubeDriver, _ ...runtime.Object) {
12 | kd.dynamicClient = dynamicfake.NewSimpleDynamicClient(runtime.NewScheme())
13 | }
14 |
15 | // NewFakeKubeDriver creates and returns a new KubeDriver with fake clients
16 | func NewFakeKubeDriver(s *cli.EnvSettings, objects ...runtime.Object) *KubeDriver {
17 | kd := &KubeDriver{
18 | EnvSettings: s,
19 | }
20 | initKubeFake(kd, objects...)
21 | return kd
22 | }
23 |
--------------------------------------------------------------------------------
/base/util_test.go:
--------------------------------------------------------------------------------
1 | package base
2 |
3 | import (
4 | "os"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | type config struct {
11 | Property *int `json:"property,omitempty"`
12 | }
13 |
14 | func TestReadConfigDefaultProperty(t *testing.T) {
15 | configEnvironnentVariable := "CONFIG"
16 | defaultPropertyValue := 8888
17 |
18 | file, err := os.CreateTemp("/tmp", "test")
19 | assert.NoError(t, err)
20 | assert.NotNil(t, file)
21 | defer func() {
22 | err := os.Remove(file.Name())
23 | assert.NoError(t, err)
24 | }()
25 |
26 | err = os.Setenv(configEnvironnentVariable, file.Name())
27 | assert.NoError(t, err)
28 | conf := &config{}
29 | err = ReadConfig(configEnvironnentVariable, conf, func() {
30 | if nil == conf.Property {
31 | conf.Property = IntPointer(defaultPropertyValue)
32 | }
33 | })
34 | assert.NoError(t, err)
35 |
36 | assert.Equal(t, defaultPropertyValue, *conf.Property)
37 | }
38 |
39 | func TestReadConfigNoEnvVar(t *testing.T) {
40 | configEnvironnentVariable := "CONFIG"
41 | defaultPropertyValue := 8888
42 |
43 | // don't set environment variable
44 | conf := &config{}
45 | err := ReadConfig(configEnvironnentVariable, conf, func() {
46 | if nil == conf.Property {
47 | conf.Property = IntPointer(defaultPropertyValue)
48 | }
49 | })
50 | assert.Error(t, err)
51 | }
52 |
53 | func TestReadConfigNoFile(t *testing.T) {
54 | configEnvironnentVariable := "CONFIG"
55 | defaultPropertyValue := 8888
56 |
57 | err := os.Setenv(configEnvironnentVariable, "/tmp/noexistant")
58 | assert.NoError(t, err)
59 | conf := &config{}
60 | err = ReadConfig(configEnvironnentVariable, conf, func() {
61 | if nil == conf.Property {
62 | conf.Property = IntPointer(defaultPropertyValue)
63 | }
64 | })
65 | assert.Error(t, err)
66 | }
67 |
68 | func TestSplitApplication(t *testing.T) {
69 | ns, n := SplitApplication("namespace/name")
70 | assert.Equal(t, "namespace", ns)
71 | assert.Equal(t, "name", n)
72 |
73 | ns, n = SplitApplication("namespace/name/ignored")
74 | assert.Equal(t, "namespace", ns)
75 | assert.Equal(t, "name", n)
76 |
77 | ns, n = SplitApplication("name")
78 | assert.Equal(t, "default", ns)
79 | assert.Equal(t, "name", n)
80 | }
81 |
82 | type testType struct {
83 | S string
84 | I int
85 | Nested struct {
86 | S string
87 | I int
88 | }
89 | }
90 |
91 | func TestToYAML(t *testing.T) {
92 | obj := testType{
93 | S: "hello world",
94 | I: 3,
95 | Nested: struct {
96 | S string
97 | I int
98 | }{
99 | S: "nested",
100 | },
101 | }
102 |
103 | objString := ToYAML(obj)
104 | assert.Equal(t, `I: 3
105 | Nested:
106 | I: 0
107 | S: nested
108 | S: hello world`, string(objString))
109 | }
110 |
--------------------------------------------------------------------------------
/bump-version-hints.md:
--------------------------------------------------------------------------------
1 | These instructions are a guide to making a new major or minor (not patch) release. The challenge is that charts refer to the image version. But this version is only created when the release is published. Consequently the following sequence of steps is needed:
2 |
3 | 1. Modify only golang code (no version bump)
4 | 1. Do not change `MajorMinor` or `Version` in `base/util.go`
5 |
6 | Make new major/minor release
7 |
8 | 2. Bump version references in `/charts` changes and bump `/charts/iter8` chart version (no changes to `/testdata`) and bump Kustomize files and bump verifyUserExperience workflow
9 | 1. The charts are modified to use the new image
10 | 2. The chart versions should be bumped to match the major/minor version (this is required for the `iter8` chart) but is desirable for all
11 |
12 | Merging the chart changes triggers a automatic chart releases
13 |
14 | 3. Version bump golang and `/testdata` and other workflows
15 | 1. Bump `MajorMinor` or `Version` in `base/util.go`
16 | 2. Bump explicit version references in remaining workflows
17 | 3. Bump Dockerfile
18 | 5. Changes to `/testdata` is only a version bump in charts (`iter8.tools/version`)
19 |
20 | Make a release (new patch version)
21 |
22 | ***
23 |
24 | At this point the documentation can be updated to refer to the new version. This usually takes 2 commits (or one with failed link checking)
25 |
26 | Some things to change in the docs:
27 |
28 | * `iter8.tools/version` in Kubernetes manifests samples
29 | * `--version` for any `helm upgrade` and `helm template` commands
30 | * `getting-started/delete.md.md` and `getting-started/install.md`
31 | * Reference to `values.yaml`
32 |
--------------------------------------------------------------------------------
/charts/controller/.helmignore:
--------------------------------------------------------------------------------
1 | # Patterns to ignore when building packages.
2 | # This supports shell glob matching, relative path matching, and
3 | # negation (prefixed with !). Only one pattern per line.
4 | .DS_Store
5 | # Common VCS dirs
6 | .git/
7 | .gitignore
8 | .bzr/
9 | .bzrignore
10 | .hg/
11 | .hgignore
12 | .svn/
13 | # Common backup files
14 | *.swp
15 | *.bak
16 | *.tmp
17 | *.orig
18 | *~
19 | # Various IDEs
20 | .project
21 | .idea/
22 | *.tmproj
23 | .vscode/
24 |
25 | # generated files need to be ignored
26 | experiment.yaml
--------------------------------------------------------------------------------
/charts/controller/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v2
2 | name: controller
3 | version: 1.1.0
4 | description: Iter8 controller controller
5 | type: application
6 | keywords:
7 | - Iter8
8 | - controller
9 | - experiment
10 | home: https://iter8.tools
11 | sources:
12 | - https://github.com/iter8-tools/iter8
13 | maintainers:
14 | - name: Alan Cha
15 | email: alan.cha1@ibm.com
16 | - name: Iter8
17 | email: iter8-tools@gmail.com
18 | icon: https://github.com/iter8-tools/iter8/raw/master/mkdocs/docs/images/favicon.png
19 | appVersion: v1.1
20 |
--------------------------------------------------------------------------------
/charts/controller/templates/_helpers.tpl:
--------------------------------------------------------------------------------
1 | {{- define "iter8-controller.name" -}}
2 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
3 | {{- end -}}
4 |
5 | {{- define "iter8-controller.labels" -}}
6 | labels:
7 | app.kubernetes.io/name: {{ template "iter8-controller.name" . }}
8 | helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
9 | app.kubernetes.io/managed-by: {{ .Release.Service }}
10 | app.kubernetes.io/instance: {{ .Release.Name }}
11 | app.kubernetes.io/version: {{ .Chart.AppVersion }}
12 | {{- end -}}
13 |
--------------------------------------------------------------------------------
/charts/controller/templates/configmap.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: {{ .Release.Name }}
5 | data:
6 | config.yaml: |
7 | {{ omit .Values "metrics" "abn" | toYaml | indent 4 | trim }}
8 | metrics.yaml: |
9 | {{ toYaml .Values.metrics | indent 4 | trim }}
10 | abn.yaml: |
11 | {{ toYaml .Values.abn | indent 4 | trim }}
--------------------------------------------------------------------------------
/charts/controller/templates/persistentvolumeclaim.yaml:
--------------------------------------------------------------------------------
1 |
2 | {{- if or (not .Values.metrics) (not .Values.metrics.implementation) (eq "badgerdb" .Values.metrics.implementation) }}
3 | apiVersion: v1
4 | kind: PersistentVolumeClaim
5 | metadata:
6 | name: {{ .Release.Name }}
7 | {{ template "iter8-controller.labels" . }}
8 | spec:
9 | accessModes:
10 | - ReadWriteOnce
11 | resources:
12 | requests:
13 | storage: {{ default "50Mi" .Values.metrics.badgerdb.storage }}
14 | storageClassName: {{ default "standard" .Values.metrics.badgerdb.storageClassName }}
15 | {{- end }}
16 |
--------------------------------------------------------------------------------
/charts/controller/templates/roles.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | {{- if .Values.clusterScoped }}
3 | kind: ClusterRole
4 | {{- else }}
5 | kind: Role
6 | {{- end }}
7 | metadata:
8 | name: {{ $.Release.Name }}
9 | {{ template "iter8-controller.labels" $ }}
10 | rules:
11 | {{- range $typeName, $type := .Values.resourceTypes }}
12 | {{- if not $type.Resource }}
13 | {{- fail (print "resourceType \"" (print $typeName "\" does not have a resource parameter")) }}
14 | {{- end }}
15 | - apiGroups: ["{{- $type.Group -}}"]
16 | resources: ["{{- $type.Resource -}}"]
17 | verbs: ["get", "list", "watch", "patch", "update", "create"]
18 | {{- end }}
19 | - apiGroups: [""]
20 | resources: ["events"]
21 | verbs: ["get", "create"]
22 | - apiGroups: [""]
23 | resources: ["pods"]
24 | verbs: ["get"]
25 | ---
26 | apiVersion: rbac.authorization.k8s.io/v1
27 | {{- if .Values.clusterScoped }}
28 | kind: ClusterRoleBinding
29 | {{- else }}
30 | kind: RoleBinding
31 | {{- end }}
32 | metadata:
33 | name: {{ $.Release.Name }}
34 | {{ template "iter8-controller.labels" $ }}
35 | subjects:
36 | - kind: ServiceAccount
37 | name: {{ $.Release.Name }}
38 | namespace: {{ $.Release.Namespace }}
39 | roleRef:
40 | {{- if .Values.clusterScoped }}
41 | kind: ClusterRole
42 | {{- else }}
43 | kind: Role
44 | {{- end }}
45 | name: {{ $.Release.Name }}
46 | apiGroup: rbac.authorization.k8s.io
--------------------------------------------------------------------------------
/charts/controller/templates/service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: {{ .Release.Name }}
5 | spec:
6 | selector:
7 | app.kubernetes.io/name: {{ template "iter8-controller.name" . }}
8 | ports:
9 | - name: grpc
10 | port: {{ .Values.abn.port }}
11 | targetPort: {{ .Values.abn.port }}
12 | - name: http
13 | port: {{ .Values.metrics.port }}
14 | targetPort: {{ .Values.metrics.port }}
--------------------------------------------------------------------------------
/charts/controller/templates/serviceaccount.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ServiceAccount
3 | metadata:
4 | name: {{ .Release.Name }}
5 | {{ template "iter8-controller.labels" . }}
--------------------------------------------------------------------------------
/charts/controller/templates/statefulset.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: StatefulSet
3 | metadata:
4 | name: {{ .Release.Name }}
5 | {{ template "iter8-controller.labels" . }}
6 | spec:
7 | serviceName: {{ .Release.Name }}
8 | replicas: {{ default 1 .Values.replicas | int }}
9 | selector:
10 | matchLabels:
11 | app.kubernetes.io/name: {{ template "iter8-controller.name" . }}
12 | template:
13 | metadata:
14 | labels:
15 | app.kubernetes.io/name: {{ template "iter8-controller.name" . }}
16 | spec:
17 | terminationGracePeriodSeconds: 10
18 | serviceAccountName: {{ .Release.Name }}
19 | containers:
20 | - name: iter8-controller
21 | image: {{ .Values.image }}
22 | imagePullPolicy: Always
23 | command: ["/bin/iter8"]
24 | args: ["controllers", "-l", "{{ .Values.logLevel }}"]
25 | env:
26 | - name: CONFIG_FILE
27 | value: /config/config.yaml
28 | - name: METRICS_CONFIG_FILE
29 | value: /config/metrics.yaml
30 | - name: ABN_CONFIG_FILE
31 | value: /config/abn.yaml
32 | - name: METRICS_DIR
33 | value: /metrics
34 | - name: POD_NAME
35 | valueFrom:
36 | fieldRef:
37 | fieldPath: metadata.name
38 | - name: POD_NAMESPACE
39 | valueFrom:
40 | fieldRef:
41 | fieldPath: metadata.namespace
42 | volumeMounts:
43 | - name: config
44 | mountPath: "/config"
45 | readOnly: true
46 | {{- if or (not .Values.metrics) (not .Values.metrics.implementation) (eq "badgerdb" .Values.metrics.implementation) }}
47 | - name: metrics
48 | mountPath: {{ default "/metrics" .Values.metrics.badgerdb.dir }}
49 | {{- end }}
50 | resources:
51 | {{ toYaml .Values.resources | indent 10 | trim }}
52 | securityContext:
53 | readOnlyRootFilesystem: true
54 | allowPrivilegeEscalation: false
55 | capabilities:
56 | drop:
57 | - ALL
58 | runAsNonRoot: true
59 | runAsUser: 1001040000
60 | volumes:
61 | - name: config
62 | configMap:
63 | name: {{ .Release.Name }}
64 | {{- if or (not .Values.metrics) (not .Values.metrics.implementation) (eq "badgerdb" .Values.metrics.implementation) }}
65 | - name: metrics
66 | persistentVolumeClaim:
67 | claimName: {{ .Release.Name }}
68 | {{- end }}
--------------------------------------------------------------------------------
/charts/controller/values.yaml:
--------------------------------------------------------------------------------
1 | ### Controller image
2 | image: iter8/iter8:1.1
3 | replicas: 1
4 |
5 | ### default resync time for controller
6 | defaultResync: 15m
7 |
8 | ### flag indicating whether installed as cluster scoped or (default) namespace scoped
9 | # clusterScoped: true
10 |
11 | ### list of resource types to watch. For each resource type, an Iter8 shortname is mapped to a group, version, and resource.
12 | ### to add types to watch, any shortname can be used
13 | ### Where a condition is identified, it identifies the status condition on an object that should be inspected to determine
14 | ### if the resource is "ready".
15 | resourceTypes:
16 | svc:
17 | Group: ""
18 | Version: v1
19 | Resource: services
20 | service:
21 | Group: ""
22 | Version: v1
23 | Resource: services
24 | cm:
25 | Group: ""
26 | Version: v1
27 | Resource: configmaps
28 | deploy:
29 | Group: apps
30 | Version: v1
31 | Resource: deployments
32 | conditions:
33 | - Available
34 | isvc:
35 | Group: serving.kserve.io
36 | Version: v1beta1
37 | Resource: inferenceservices
38 | conditions:
39 | - Ready
40 | vs:
41 | Group: networking.istio.io
42 | Version: v1beta1
43 | Resource: virtualservices
44 | httproute:
45 | Group: gateway.networking.k8s.io
46 | Version: v1beta1
47 | Resource: httproutes
48 |
49 | ### log level. Must be one of trace, debug, info, warning, error
50 | logLevel: info
51 |
52 | ### Resource limits
53 | resources:
54 | requests:
55 | memory: "64Mi"
56 | cpu: "250m"
57 | limits:
58 | memory: "128Mi"
59 | cpu: "500m"
60 |
61 | ### A/B/n
62 | abn:
63 | # port for Iter8 gRPC service
64 | port: 50051
65 |
66 | ### Metrics
67 | metrics:
68 | # port on which HTTP service (for Grafana) should be exposed
69 | port: 8080
70 | # implementation technology for metrics storage
71 | # Valid values are badgerdb (default) and redis
72 | # The set of properties used to configure the metrics store depend on the
73 | # implementation selected.
74 | implementation: badgerdb
75 | # default properties specific to BadgerDB
76 | badgerdb:
77 | # storage that should be created to support badger db
78 | storage: 50Mi
79 | storageClassName: standard
80 | # location to mount storage
81 | dir: /metrics
82 | # default properties specific to Redis
83 | redis:
84 | address: redis:6379
85 | # password: (default - none)
86 | # username: (default - none)
87 | # db: (default 0)
88 |
89 |
90 |
--------------------------------------------------------------------------------
/charts/iter8/.helmignore:
--------------------------------------------------------------------------------
1 | # Patterns to ignore when building packages.
2 | # This supports shell glob matching, relative path matching, and
3 | # negation (prefixed with !). Only one pattern per line.
4 | .DS_Store
5 | # Common VCS dirs
6 | .git/
7 | .gitignore
8 | .bzr/
9 | .bzrignore
10 | .hg/
11 | .hgignore
12 | .svn/
13 | # Common backup files
14 | *.swp
15 | *.bak
16 | *.tmp
17 | *.orig
18 | *~
19 | # Various IDEs
20 | .project
21 | .idea/
22 | *.tmproj
23 | .vscode/
24 |
--------------------------------------------------------------------------------
/charts/iter8/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v2
2 | name: iter8
3 | version: 1.1.0
4 | description: Iter8 experiment chart
5 | type: application
6 | home: https://iter8.tools
7 | sources:
8 | - https://github.com/iter8-tools/hub
9 | maintainers:
10 | - name: Srinivasan Parthasarathy
11 | email: spartha@us.ibm.com
12 | url: https://researcher.watson.ibm.com/researcher/view.php?person=us-spartha
13 | - name: Michael Kalantar
14 | email: kalantar@us.ibm.com
15 | icon: https://github.com/iter8-tools/iter8/raw/master/mkdocs/docs/images/favicon.png
16 |
--------------------------------------------------------------------------------
/charts/iter8/README.md:
--------------------------------------------------------------------------------
1 | # Iter8 test runner chart
2 |
3 | This chart enables Iter8 tests.
--------------------------------------------------------------------------------
/charts/iter8/templates/_experiment.tpl:
--------------------------------------------------------------------------------
1 | {{- define "experiment" -}}
2 | {{- if not .Values.tasks }}
3 | {{- fail ".Values.tasks is empty" }}
4 | {{- end }}
5 | metadata:
6 | name: {{ .Release.Name }}
7 | namespace: {{ .Release.Namespace }}
8 | spec:
9 | {{- range .Values.tasks }}
10 | {{- if eq "grpc" . }}
11 | {{- include "task.grpc" $.Values.grpc -}}
12 | {{- else if eq "http" . }}
13 | {{- include "task.http" $.Values.http -}}
14 | {{- else if eq "ready" . }}
15 | {{- include "task.ready" $ -}}
16 | {{- else if eq "slack" . }}
17 | {{- include "task.slack" $.Values.slack -}}
18 | {{- else if eq "github" . }}
19 | {{- include "task.github" $.Values.github -}}
20 | {{- else }}
21 | {{- fail "task name must be one of grpc, http, ready, github, or slack" -}}
22 | {{- end }}
23 | {{- end }}
24 | result:
25 | startTime: {{ now | toJson }}
26 | numCompletedTasks: 0
27 | failure: false
28 | iter8Version: {{ .Values.majorMinor }}
29 | {{- end }}
--------------------------------------------------------------------------------
/charts/iter8/templates/_k-job.tpl:
--------------------------------------------------------------------------------
1 | {{- define "k.job" -}}
2 | apiVersion: batch/v1
3 | kind: Job
4 | metadata:
5 | name: {{ .Release.Name }}-{{ .Release.Revision }}-job
6 | annotations:
7 | iter8.tools/test: {{ .Release.Name }}
8 | iter8.tools/revision: {{ .Release.Revision | quote }}
9 | spec:
10 | template:
11 | metadata:
12 | labels:
13 | iter8.tools/test: {{ .Release.Name }}
14 | annotations:
15 | sidecar.istio.io/inject: "false"
16 | spec:
17 | serviceAccountName: {{ default (printf "%s-iter8-sa" .Release.Name) .Values.serviceAccountName }}
18 | containers:
19 | - name: iter8
20 | image: {{ .Values.iter8Image }}
21 | imagePullPolicy: Always
22 | command:
23 | - "/bin/sh"
24 | - "-c"
25 | - |
26 | iter8 k run --namespace {{ .Release.Namespace }} --test {{ .Release.Name }} -l {{ .Values.logLevel }}
27 | resources:
28 | {{ toYaml .Values.resources | indent 10 | trim }}
29 | securityContext:
30 | allowPrivilegeEscalation: false
31 | capabilities:
32 | drop:
33 | - ALL
34 | runAsNonRoot: true
35 | runAsUser: 1001040000
36 | env:
37 | - name: METRICS_SERVER_URL
38 | value: "{{ .Values.metricsServerURL }}"
39 | restartPolicy: Never
40 | backoffLimit: 0
41 | {{- end }}
42 |
--------------------------------------------------------------------------------
/charts/iter8/templates/_k-role.tpl:
--------------------------------------------------------------------------------
1 | {{- define "k.role" -}}
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: Role
4 | metadata:
5 | name: {{ .Release.Name }}
6 | annotations:
7 | iter8.tools/test: {{ .Release.Name }}
8 | rules:
9 | - apiGroups: [""]
10 | resourceNames: [{{ .Release.Name | quote }}]
11 | resources: ["secrets"]
12 | verbs: ["get", "update"]
13 | {{- if .Values.ready }}
14 | ---
15 | {{- $namespace := coalesce $.Values.ready.namespace $.Release.Namespace }}
16 | apiVersion: rbac.authorization.k8s.io/v1
17 | kind: Role
18 | metadata:
19 | name: {{ .Release.Name }}-ready
20 | {{- if $namespace }}
21 | namespace: {{ $namespace }}
22 | {{- end }} {{- /* if $namespace */}}
23 | annotations:
24 | iter8.tools/test: {{ .Release.Name }}
25 | rules:
26 | {{- $typesToCheck := omit .Values.ready "timeout" "namespace" }}
27 | {{- range $type, $name := $typesToCheck }}
28 | {{- $definition := get $.Values.resourceTypes $type }}
29 | {{- if not $definition }}
30 | {{- cat "no type definition for: " $type | fail }}
31 | {{- else }}
32 | - apiGroups: [ {{ get $definition "Group" | quote }} ]
33 | resourceNames: [ {{ $name | quote }} ]
34 | resources: [ {{ get $definition "Resource" | quote }} ]
35 | verbs: [ "get" ]
36 | {{- end }} {{- /* if not $definition */}}
37 | {{- end }} {{- /* range $type, $name */}}
38 | {{- end }} {{- /* {{- if .Values.ready */}}
39 | {{- end }} {{- /* {{- if .Values.ready */}}
40 |
--------------------------------------------------------------------------------
/charts/iter8/templates/_k-rolebinding.tpl:
--------------------------------------------------------------------------------
1 | {{- define "k.rolebinding" -}}
2 | apiVersion: rbac.authorization.k8s.io/v1
3 | kind: RoleBinding
4 | metadata:
5 | name: {{ .Release.Name }}
6 | annotations:
7 | iter8.tools/test: {{ .Release.Name }}
8 | subjects:
9 | - kind: ServiceAccount
10 | name: {{ .Release.Name }}-iter8-sa
11 | namespace: {{ .Release.Namespace }}
12 | roleRef:
13 | kind: Role
14 | name: {{ .Release.Name }}
15 | apiGroup: rbac.authorization.k8s.io
16 | {{- if .Values.ready }}
17 | ---
18 | {{- $namespace := coalesce .Values.ready.namespace .Release.Namespace }}
19 | {{- if $namespace }}
20 | apiVersion: rbac.authorization.k8s.io/v1
21 | kind: RoleBinding
22 | metadata:
23 | name: {{ .Release.Name }}-ready
24 | namespace: {{ $namespace }}
25 | annotations:
26 | iter8.tools/test: {{ .Release.Name }}
27 | subjects:
28 | - kind: ServiceAccount
29 | name: {{ .Release.Name }}-iter8-sa
30 | namespace: {{ .Release.Namespace }}
31 | roleRef:
32 | kind: Role
33 | name: {{ .Release.Name }}-ready
34 | apiGroup: rbac.authorization.k8s.io
35 | {{- end }}
36 | {{- end }}
37 | {{- end }}
38 |
--------------------------------------------------------------------------------
/charts/iter8/templates/_k-secret.tpl:
--------------------------------------------------------------------------------
1 | {{- define "k.secret" -}}
2 | apiVersion: v1
3 | kind: Secret
4 | metadata:
5 | name: {{ .Release.Name }}
6 | annotations:
7 | iter8.tools/test: {{ .Release.Name }}
8 | stringData:
9 | experiment.yaml: |
10 | {{ include "experiment" . | indent 4 }}
11 | {{- end }}
--------------------------------------------------------------------------------
/charts/iter8/templates/_k-serviceacccount.tpl:
--------------------------------------------------------------------------------
1 | {{- define "k.serviceaccount" -}}
2 | apiVersion: v1
3 | kind: ServiceAccount
4 | metadata:
5 | name: {{ .Release.Name }}-iter8-sa
6 | {{- end }}
7 |
--------------------------------------------------------------------------------
/charts/iter8/templates/_task-github.tpl:
--------------------------------------------------------------------------------
1 | {{- define "task.github" -}}
2 | {{- /* Validate values */ -}}
3 | {{- if not . }}
4 | {{- fail "github notify values object is nil" }}
5 | {{- end }}
6 | {{- if not .owner }}
7 | {{- fail "please set a value for the owner parameter" }}
8 | {{- end }}
9 | {{- if not .repo }}
10 | {{- fail "please set a value for the repo parameter" }}
11 | {{- end }}
12 | {{- if not .token }}
13 | {{- fail "please set a value for the token parameter" }}
14 | {{- end }}
15 | # task: send a GitHub notification
16 | - task: notify
17 | with:
18 | url: https://api.github.com/repos/{{ .owner }}/{{ .repo }}/dispatches
19 | method: POST
20 | headers:
21 | Authorization: token {{ .token }}
22 | Accept: application/vnd.github+json
23 | payloadTemplateURL: {{ default "https://raw.githubusercontent.com/iter8-tools/iter8/v0.16.5/templates/notify/_payload-github.tpl" .payloadTemplateURL }}
24 | softFailure: {{ default true .softFailure }}
25 | {{ end }}
--------------------------------------------------------------------------------
/charts/iter8/templates/_task-http.tpl:
--------------------------------------------------------------------------------
1 | {{- define "task.http" -}}
2 | {{- /* Validate values */ -}}
3 | {{- if not . }}
4 | {{- fail "http values object is nil" }}
5 | {{- end }}
6 | {{/* url must be defined or a url must be defined for each endpoint */}}
7 | {{- if not .url }}
8 | {{- if .endpoints }}
9 | {{- range $endpointID, $endpoint := .endpoints }}
10 | {{- if not $endpoint.url }}
11 | {{- fail (print "endpoint \"" (print $endpointID "\" does not have a url parameter")) }}
12 | {{- end }}
13 | {{- end }}
14 | {{- else }}
15 | {{- fail "please set the url parameter or the endpoints parameter" }}
16 | {{- end }}
17 | {{- end }}
18 | {{- /**************************/ -}}
19 | {{- /* Perform the various setup steps before the main task */ -}}
20 | {{- $vals := mustDeepCopy . }}
21 | {{- if $vals.payloadURL }}
22 | # task: download payload from payload URL
23 | - run: |
24 | curl -o /tmp/payload.dat {{ $vals.payloadURL }}
25 | {{- $_ := set $vals "payloadFile" "/tmp/payload.dat" }}
26 | {{- end }}
27 | {{- /**************************/ -}}
28 | {{- /* Repeat above for each endpoint */ -}}
29 | {{- range $endpointID, $endpoint := $vals.endpoints }}
30 | {{- if $endpoint.payloadURL }}
31 | {{- $payloadFile := print "/tmp/" $endpointID "_payload.dat" }}
32 | # task: download payload from payload URL for endpoint
33 | - run: |
34 | curl -o {{ $payloadFile }} {{ $endpoint.payloadURL }}
35 | {{- $_ := set $endpoint "payloadFile" $payloadFile }}
36 | {{- end }}
37 | {{- end }}
38 | {{- /**************************/ -}}
39 | {{- /* Warmup task if requested */ -}}
40 | {{- if or .warmupNumRequests .warmupDuration }}
41 | {{- $warmupVals := mustDeepCopy $vals }}
42 | {{- if .warmupNumRequests }}
43 | {{- $_ := set $warmupVals "numRequests" .warmupNumRequests }}
44 | {{- else }}
45 | {{- $_ := set $warmupVals "duration" .warmupDuration}}
46 | {{- end }}
47 | {{- /* replace warmup options a boolean */ -}}
48 | {{- $_ := unset $warmupVals "warmupDuration" }}
49 | {{- $_ := unset $warmupVals "warmupNumRequests" }}
50 | {{- $_ := set $warmupVals "warmup" true }}
51 | # task: generate warmup HTTP requests
52 | # collect Iter8's built-in HTTP latency and error-related metrics
53 | - task: http
54 | with:
55 | {{ toYaml $warmupVals | indent 4 }}
56 | {{- end }}
57 | {{- /* warmup done */ -}}
58 | {{- /**************************/ -}}
59 | {{- /* Main task */ -}}
60 | {{- /* remove warmup options if present */ -}}
61 | {{- $_ := unset . "warmupDuration" }}
62 | {{- $_ := unset . "warmupNumRequests" }}
63 | # task: generate HTTP requests for app
64 | # collect Iter8's built-in HTTP latency and error-related metrics
65 | - task: http
66 | with:
67 | {{ toYaml $vals | indent 4 }}
68 | {{- end }}
--------------------------------------------------------------------------------
/charts/iter8/templates/_task-ready.tpl:
--------------------------------------------------------------------------------
1 | {{- define "task.ready" }}
2 | {{- if .Values.ready }}
3 | {{- $typesToCheck := omit .Values.ready "timeout" "namespace" }}
4 | {{- range $type, $name := $typesToCheck }}
5 | {{- $definition := get $.Values.resourceTypes $type }}
6 | {{- if not $definition }}
7 | {{- cat "no type definition for: " $type | fail }}
8 | {{- else }}
9 | # task: test for existence and readiness of a resource
10 | - task: ready
11 | with:
12 | name: {{ $name | quote }}
13 | group: {{ get $definition "Group" | quote }}
14 | version: {{ get $definition "Version" | quote }}
15 | resource: {{ get $definition "Resource" | quote }}
16 | {{- if (hasKey $definition "conditions") }}
17 | conditions:
18 | {{ toYaml (get $definition "conditions") | indent 4 }}
19 | {{- end }} {{- /* if (hasKey $definition "conditions") */}}
20 | {{- $namespace := coalesce $.Values.ready.namespace $.Release.Namespace }}
21 | {{- if $namespace }}
22 | namespace: {{ $namespace }}
23 | {{- end }} {{- /* if $namespace */}}
24 | {{- if $.Values.ready.timeout }}
25 | timeout: {{ $.Values.ready.timeout }}
26 | {{- end }} {{- /* if $.Values.ready.timeout */}}
27 | {{- end }} {{- /* if not $definition */}}
28 | {{- end }} {{- /* range $type, $name */}}
29 | {{- end }} {{- /* {{- if .Values.ready */}}
30 | {{- end }} {{- /* define "task.ready" */}}
31 |
--------------------------------------------------------------------------------
/charts/iter8/templates/_task-slack.tpl:
--------------------------------------------------------------------------------
1 | {{- define "task.slack" -}}
2 | {{- /* Validate values */ -}}
3 | {{- if not . }}
4 | {{- fail "slack notify values object is nil" }}
5 | {{- end }}
6 | {{- if not .url }}
7 | {{- fail "please set a value for the url parameter" }}
8 | {{- end }}
9 | # task: send a Slack notification
10 | - task: notify
11 | with:
12 | url: {{ .url }}
13 | method: POST
14 | payloadTemplateURL: {{ default "https://raw.githubusercontent.com/iter8-tools/iter8/v0.16.5/templates/notify/_payload-slack.tpl" .payloadTemplateURL }}
15 | softFailure: {{ default true .softFailure }}
16 | {{ end }}
--------------------------------------------------------------------------------
/charts/iter8/templates/k8s.yaml:
--------------------------------------------------------------------------------
1 | {{ include "k.secret" . }}
2 | {{- if not .Values.serviceAccountName }}
3 | ---
4 | {{ include "k.role" . }}
5 | ---
6 | {{ include "k.serviceaccount" . }}
7 | ---
8 | {{ include "k.rolebinding" . }}
9 | {{- end}}
10 | ---
11 | {{ include "k.job" . }}
12 |
--------------------------------------------------------------------------------
/charts/iter8/values.yaml:
--------------------------------------------------------------------------------
1 | ### iter8Image default iter8 image used for running Kubernetes experiments
2 | iter8Image: iter8/iter8:1.1
3 |
4 | ### majorMinor is the minor version of Iter8
5 | majorMinor: v1.1
6 |
7 | logLevel: info
8 |
9 | ### resources are the resource limits for the pods
10 | resources:
11 | requests:
12 | memory: "64Mi"
13 | cpu: "250m"
14 | limits:
15 | memory: "128Mi"
16 | cpu: "500m"
17 |
18 | ### metricsServerURL is the URL to the Metrics server
19 | metricsServerURL: http://iter8.default:8080
20 |
21 | ### list of resource types and conditions used to evalutate object readiness.
22 | resourceTypes:
23 | svc:
24 | Group: ""
25 | Version: v1
26 | Resource: services
27 | service:
28 | Group: ""
29 | Version: v1
30 | Resource: services
31 | cm:
32 | Group: ""
33 | Version: v1
34 | Resource: configmaps
35 | deploy:
36 | Group: apps
37 | Version: v1
38 | Resource: deployments
39 | conditions:
40 | - Available
41 | isvc:
42 | Group: serving.kserve.io
43 | Version: v1beta1
44 | Resource: inferenceservices
45 | conditions:
46 | - Ready
47 | vs:
48 | Group: networking.istio.io
49 | Version: v1beta1
50 | Resource: virtualservices
--------------------------------------------------------------------------------
/charts/release/.helmignore:
--------------------------------------------------------------------------------
1 | # Patterns to ignore when building packages.
2 | # This supports shell glob matching, relative path matching, and
3 | # negation (prefixed with !). Only one pattern per line.
4 | .DS_Store
5 | # Common VCS dirs
6 | .git/
7 | .gitignore
8 | .bzr/
9 | .bzrignore
10 | .hg/
11 | .hgignore
12 | .svn/
13 | # Common backup files
14 | *.swp
15 | *.bak
16 | *.tmp
17 | *.orig
18 | *~
19 | # Various IDEs
20 | .project
21 | .idea/
22 | *.tmproj
23 | .vscode/
24 |
25 | # generated files need to be ignored
26 | experiment.yaml
--------------------------------------------------------------------------------
/charts/release/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v2
2 | name: release
3 | version: 1.1.0
4 | description: Iter8 supported application release
5 | type: application
6 | keywords:
7 | - Iter8
8 | - traffic
9 | - blue-green
10 | - canary
11 | - mirroring
12 | home: https://iter8.tools
13 | sources:
14 | - https://github.com/iter8-tools/iter8
15 | maintainers:
16 | - name: Michael Kalantar
17 | email: kalantar@us.ibm.com
18 | - name: Iter8
19 | email: iter8-tools@gmail.com
20 | icon: https://github.com/iter8-tools/iter8/raw/master/mkdocs/docs/images/favicon.png
21 | appVersion: v1.1
22 |
--------------------------------------------------------------------------------
/charts/release/templates/_configmap.weight-config.tpl:
--------------------------------------------------------------------------------
1 | {{- define "configmap.weight-config" }}
2 | apiVersion: v1
3 | kind: ConfigMap
4 | metadata:
5 | name: {{ .VERSION_NAME }}-weight-config
6 | labels:
7 | iter8.tools/watch: "true"
8 | {{- if .weight }}
9 | annotations:
10 | iter8.tools/weight: "{{ .weight }}"
11 | {{- end }} {{- /* if .weight */}}
12 | {{- end }} {{- /* define "configmap.weight-config" */}}
13 |
--------------------------------------------------------------------------------
/charts/release/templates/_deployment-gtw.blue-green.routemap.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.deployment-gtw.blue-green.routemap" }}
2 |
3 | {{- $APP_NAME := (include "application.name" .) }}
4 | {{- $APP_NAMESPACE := (include "application.namespace" .) }}
5 | {{- $versions := include "normalize.versions.deployment" . | mustFromJson }}
6 | {{- $APP_PORT := pluck "port" (dict "port" 80) $.Values.application | first }}
7 |
8 | apiVersion: v1
9 | kind: ConfigMap
10 | {{- template "routemap.metadata" . }}
11 | data:
12 | strSpec: |
13 | versions:
14 | {{- range $i, $v := $versions }}
15 | - resources:
16 | - gvrShort: svc
17 | name: {{ template "svc.name" $v }}
18 | namespace: {{ template "svc.namespace" $v }}
19 | - gvrShort: deploy
20 | name: {{ template "deploy.name" $v }}
21 | namespace: {{ template "deploy.namespace" $v }}
22 | - gvrShort: cm
23 | name: {{ $v.VERSION_NAME }}-weight-config
24 | namespace: {{ $v.VERSION_NAMESPACE }}
25 | weight: {{ $v.weight }}
26 | {{- end }} {{- /* range $i, $v := $versions */}}
27 | routingTemplates:
28 | {{ .Values.application.strategy }}:
29 | gvrShort: httproute
30 | template: |
31 | apiVersion: gateway.networking.k8s.io/v1beta1
32 | kind: HTTPRoute
33 | metadata:
34 | name: {{ $APP_NAME }}
35 | namespace: {{ $APP_NAMESPACE }}
36 | spec:
37 | hostnames:
38 | - {{ $APP_NAME }}
39 | - {{ $APP_NAME }}.{{ $APP_NAMESPACE }}
40 | - {{ $APP_NAME }}.{{ $APP_NAMESPACE }}.svc.cluster.local
41 | parentRefs:
42 | - group: ""
43 | kind: Service
44 | name: {{ $APP_NAME }}
45 | port: {{ $APP_PORT }}
46 | {{- if .Values.gateway }}
47 | - name: {{ .Values.gateway }}
48 | {{- end }}
49 | rules:
50 | - backendRefs:
51 | {{- range $i, $v := $versions }}
52 | - group: ""
53 | kind: Service
54 | name: {{ template "svc.name" $v }}
55 | port: {{ $v.port }}
56 | {{- if gt (len $versions) 1 }}
57 | {{ `{{- if gt (index .Weights 1) 0 }}` }}
58 | weight: {{ `{{ index .Weights ` }}{{ print $i }}{{ ` }}` }}
59 | {{ `{{- end }}` }}
60 | {{- end }}
61 | filters:
62 | - type: ResponseHeaderModifier
63 | responseHeaderModifier:
64 | add:
65 | - name: app-version
66 | value: {{ template "svc.name" $v }}
67 | {{- end }} {{- /* range $i, $v := $versions */}}
68 | {{- end }} {{- /* define "env.deployment-gtw.blue-green.routemap" */}}
69 |
--------------------------------------------------------------------------------
/charts/release/templates/_deployment-gtw.blue-green.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.deployment-gtw.blue-green" }}
2 |
3 | {{- /* prepare versions for simpler processing */}}
4 | {{- $versions := include "normalize.versions.deployment" . | mustFromJson }}
5 |
6 | {{- /* weight-config ConfigMaps */}}
7 | {{- range $i, $v := $versions }}
8 | {{ include "configmap.weight-config" $v }}
9 | ---
10 | {{- end }} {{- /* range $i, $v := $versions */}}
11 |
12 | {{- /* routemap */}}
13 | {{ include "env.deployment-gtw.blue-green.routemap" . }}
14 |
15 | {{- end }} {{- /* define "env.deployment-gtw.blue-green" */}}
--------------------------------------------------------------------------------
/charts/release/templates/_deployment-gtw.canary.routemap.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.deployment-gtw.canary.routemap" }}
2 |
3 | {{- $APP_NAME := (include "application.name" .) }}
4 | {{- $APP_NAMESPACE := (include "application.namespace" .) }}
5 | {{- $versions := include "normalize.versions.deployment" . | mustFromJson }}
6 | {{- $APP_PORT := pluck "port" (dict "port" 80) $.Values.application | first }}
7 |
8 | apiVersion: v1
9 | kind: ConfigMap
10 | {{- template "routemap.metadata" . }}
11 | data:
12 | strSpec: |
13 | versions:
14 | {{- range $i, $v := $versions }}
15 | - resources:
16 | - gvrShort: svc
17 | name: {{ template "svc.name" $v }}
18 | namespace: {{ template "svc.namespace" $v }}
19 | - gvrShort: deploy
20 | name: {{ template "deploy.name" $v }}
21 | namespace: {{ template "deploy.namespace" $v }}
22 | {{- end }} {{- /* range $i, $v := $versions */}}
23 | routingTemplates:
24 | {{ .Values.application.strategy }}:
25 | gvrShort: httproute
26 | template: |
27 | apiVersion: gateway.networking.k8s.io/v1beta1
28 | kind: HTTPRoute
29 | metadata:
30 | name: {{ $APP_NAME }}
31 | namespace: {{ $APP_NAMESPACE }}
32 | spec:
33 | hostnames:
34 | - {{ $APP_NAME }}.{{ $APP_NAMESPACE }}
35 | - {{ $APP_NAME }}.{{ $APP_NAMESPACE }}.svc
36 | - {{ $APP_NAME }}.{{ $APP_NAMESPACE }}.svc.cluster.local
37 | parentRefs:
38 | - group: ""
39 | kind: Service
40 | name: {{ $APP_NAME }}
41 | port: {{ $APP_PORT }}
42 | {{- if .Values.gateway }}
43 | - name: {{ .Values.gateway }}
44 | {{- end }}
45 | rules:
46 | # non-primary versions
47 | {{- range $i, $v := (rest $versions) }}
48 | - matches:
49 | {{- toYaml $v.matches | nindent 14 }}
50 | backendRefs:
51 | - group: ""
52 | kind: Service
53 | name: {{ template "svc.name" $v }}
54 | port: {{ $v.port }}
55 | filters:
56 | - type: ResponseHeaderModifier
57 | responseHeaderModifier:
58 | add:
59 | - name: app-version
60 | value: {{ template "svc.name" $v }}
61 | {{- end }} {{- /* range $i, $v := (rest $versions) */}}
62 | # primary version (default)
63 | {{- $v := (index $versions 0) }}
64 | - backendRefs:
65 | - group: ""
66 | kind: Service
67 | name: {{ template "svc.name" $v }}
68 | port: {{ $v.port }}
69 | filters:
70 | - type: ResponseHeaderModifier
71 | responseHeaderModifier:
72 | add:
73 | - name: app-version
74 | value: {{ template "svc.name" $v }}
75 | {{- end }} {{- /* define "env.deployment-gtw.canary.routemap" */}}
76 |
--------------------------------------------------------------------------------
/charts/release/templates/_deployment-gtw.canary.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.deployment-gtw.canary" }}
2 |
3 | {{- /* routemap */}}
4 | {{ include "env.deployment-gtw.canary.routemap" . }}
5 |
6 | {{- end }} {{- /* define "env.deployment-gtw.canary" */}}
--------------------------------------------------------------------------------
/charts/release/templates/_deployment-gtw.none.routemap.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.deployment-gtw.none.routemap" }}
2 |
3 | {{- $versions := include "normalize.versions.deployment" . | mustFromJson }}
4 |
5 | apiVersion: v1
6 | kind: ConfigMap
7 | {{- template "routemap.metadata" . }}
8 | data:
9 | strSpec: |
10 | versions:
11 | {{- range $i, $v := $versions }}
12 | - resources:
13 | - gvrShort: svc
14 | name: {{ template "svc.name" $v }}
15 | namespace: {{ template "svc.namespace" $v }}
16 | - gvrShort: deploy
17 | name: {{ template "deploy.name" $v }}
18 | namespace: {{ template "deploy.namespace" $v }}
19 | {{- end }}
20 |
21 | {{- end }} {{- /* define "env.deployment-gtw.none.routemap" */}}
22 |
--------------------------------------------------------------------------------
/charts/release/templates/_deployment-gtw.none.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.deployment-gtw.none" }}
2 |
3 | {{- /* routemap */}}
4 | {{ include "env.deployment-gtw.none.routemap" . }}
5 |
6 | {{- end }} {{- /* define "env.deployment-gtw.none" */}}
--------------------------------------------------------------------------------
/charts/release/templates/_deployment-gtw.service.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.deployment-gtw.service" }}
2 |
3 | {{- $APP_NAME := (include "application.name" .) }}
4 | {{- $APP_NAMESPACE := (include "application.namespace" .) }}
5 |
6 | apiVersion: v1
7 | kind: Service
8 | metadata:
9 | name: {{ $APP_NAME }}
10 | namespace: {{ $APP_NAMESPACE }}
11 | spec:
12 | selector:
13 | app: {{ $APP_NAME }}
14 | ports:
15 | - port: 80
16 | {{- end }} {{- /* define "env.deployment-gtw.service" */}}
--------------------------------------------------------------------------------
/charts/release/templates/_deployment-gtw.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.deployment-gtw" }}
2 |
3 | {{- /* Prepare versions for simpler processing */}}
4 | {{- $versions := include "normalize.versions.deployment" . | mustFromJson }}
5 |
6 | {{- range $i, $v := $versions }}
7 | {{- /* Deployment */}}
8 | {{ include "env.deployment.version.deployment" $v }}
9 | ---
10 | {{- /* Service */}}
11 | {{ include "env.deployment.version.service" $v }}
12 | ---
13 | {{- end }} {{- /* range $i, $v := $versions */}}
14 |
15 | {{- /* Service */}}
16 | {{ include "env.deployment-gtw.service" . }}
17 | ---
18 |
19 | {{- /* routemap (and other strategy specific objects) */}}
20 | {{- if not .Values.application.strategy }}
21 | {{ include "env.deployment-gtw.none" . }}
22 | {{- else if eq "none" .Values.application.strategy }}
23 | {{ include "env.deployment-gtw.none" . }}
24 | {{- else if eq "blue-green" .Values.application.strategy }}
25 | {{ include "env.deployment-gtw.blue-green" . }}
26 | {{- else if eq "canary" .Values.application.strategy }}
27 | {{ include "env.deployment-gtw.canary" . }}
28 | {{- end }} {{- /* if eq ... .Values.application.strategy */}}
29 |
30 | {{- end }} {{- /* define "env.deployment-gtw" */}}
--------------------------------------------------------------------------------
/charts/release/templates/_deployment-istio.blue-green.routemap.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.deployment-istio.blue-green.routemap" }}
2 |
3 | {{- $APP_NAME := (include "application.name" .) }}
4 | {{- $APP_NAMESPACE := (include "application.namespace" .) }}
5 | {{- $versions := include "normalize.versions.deployment" . | mustFromJson }}
6 |
7 | apiVersion: v1
8 | kind: ConfigMap
9 | {{- template "routemap.metadata" . }}
10 | data:
11 | strSpec: |
12 | versions:
13 | {{- range $i, $v := $versions }}
14 | - resources:
15 | - gvrShort: svc
16 | name: {{ template "svc.name" $v }}
17 | namespace: {{ template "svc.namespace" $v }}
18 | - gvrShort: deploy
19 | name: {{ template "deploy.name" $v }}
20 | namespace: {{ template "deploy.namespace" $v }}
21 | - gvrShort: cm
22 | name: {{ $v.VERSION_NAME }}-weight-config
23 | namespace: {{ $v.VERSION_NAMESPACE }}
24 | weight: {{ $v.weight }}
25 | {{- end }} {{- /* range $i, $v := $versions */}}
26 | routingTemplates:
27 | {{ .Values.application.strategy }}:
28 | gvrShort: vs
29 | template: |
30 | apiVersion: networking.istio.io/v1beta1
31 | kind: VirtualService
32 | metadata:
33 | name: {{ $APP_NAME }}
34 | namespace: {{ $APP_NAMESPACE }}
35 | spec:
36 | gateways:
37 | {{- if .Values.gateway }}
38 | - {{ .Values.gateway }}
39 | {{- end }}
40 | - mesh
41 | hosts:
42 | - {{ $APP_NAME }}.{{ $APP_NAMESPACE }}
43 | - {{ $APP_NAME }}.{{ $APP_NAMESPACE }}.svc
44 | - {{ $APP_NAME }}.{{ $APP_NAMESPACE }}.svc.cluster.local
45 | http:
46 | - name: {{ $APP_NAME }}
47 | route:
48 | # primary version
49 | {{- $v := (index $versions 0) }}
50 | - destination:
51 | host: {{ template "svc.name" $v }}.{{ $APP_NAMESPACE }}.svc.cluster.local
52 | port:
53 | number: {{ $v.port }}
54 | {{- if gt (len $versions) 1 }}
55 | {{ `{{- if gt (index .Weights 1) 0 }}` }}
56 | weight: {{ `{{ index .Weights 0 }}` }}
57 | {{ `{{- end }}` }}
58 | {{- end }}
59 | headers:
60 | response:
61 | add:
62 | app-version: {{ template "svc.name" $v }}
63 | # other versions
64 | {{- range $i, $v := (rest $versions) }}
65 | {{ `{{- if gt (index .Weights ` }}{{ print (add1 $i) }}{{ `) 0 }}` }}
66 | - destination:
67 | host: {{ template "svc.name" $v }}.{{ $APP_NAMESPACE }}.svc.cluster.local
68 | port:
69 | number: {{ $v.port }}
70 | weight: {{ `{{ index .Weights ` }}{{ print (add1 $i) }}{{ ` }}` }}
71 | headers:
72 | response:
73 | add:
74 | app-version: {{ template "svc.name" $v }}
75 | {{ `{{- end }}` }}
76 | {{- end }}
77 | {{- end }} {{- /* define "env.deployment-istio.blue-green.routemap" */}}
78 |
--------------------------------------------------------------------------------
/charts/release/templates/_deployment-istio.blue-green.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.deployment-istio.blue-green" }}
2 |
3 | {{- /* prepare versions for simpler processing */}}
4 | {{- $versions := include "normalize.versions.deployment" . | mustFromJson }}
5 |
6 | {{- /* weight-config ConfigMaps */}}
7 | {{- range $i, $v := $versions }}
8 | {{ include "configmap.weight-config" $v }}
9 | ---
10 | {{- end }} {{- /* range $i, $v := $versions */}}
11 |
12 | {{- /* routemap */}}
13 | {{ include "env.deployment-istio.blue-green.routemap" . }}
14 |
15 | {{- end }} {{- /* define "env.deployment-istio.blue-green" */}}
--------------------------------------------------------------------------------
/charts/release/templates/_deployment-istio.canary.routemap.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.deployment-istio.canary.routemap" }}
2 |
3 | {{- $APP_NAME := (include "application.name" .) }}
4 | {{- $APP_NAMESPACE := (include "application.namespace" .) }}
5 | {{- $versions := include "normalize.versions.deployment" . | mustFromJson }}
6 |
7 | apiVersion: v1
8 | kind: ConfigMap
9 | {{- template "routemap.metadata" . }}
10 | data:
11 | strSpec: |
12 | versions:
13 | {{- range $i, $v := $versions }}
14 | - resources:
15 | - gvrShort: svc
16 | name: {{ template "svc.name" $v }}
17 | namespace: {{ template "svc.namespace" $v }}
18 | - gvrShort: deploy
19 | name: {{ template "deploy.name" $v }}
20 | namespace: {{ template "deploy.namespace" $v }}
21 | {{- end }}
22 | routingTemplates:
23 | {{ .Values.application.strategy }}:
24 | gvrShort: vs
25 | template: |
26 | apiVersion: networking.istio.io/v1beta1
27 | kind: VirtualService
28 | metadata:
29 | name: {{ $APP_NAME }}
30 | namespace: {{ $APP_NAMESPACE }}
31 | spec:
32 | gateways:
33 | {{- if .Values.gateway }}
34 | - {{ .Values.gateway }}
35 | {{- end }}
36 | - mesh
37 | hosts:
38 | - {{ $APP_NAME }}.{{ $APP_NAMESPACE }}
39 | - {{ $APP_NAME }}.{{ $APP_NAMESPACE }}.svc
40 | - {{ $APP_NAME }}.{{ $APP_NAMESPACE }}.svc.cluster.local
41 | http:
42 | # non-primary versions
43 | {{- range $i, $v := (rest $versions) }}
44 | {{- /* continue only if candidate is ready (weight > 0) */}}
45 | {{ `{{- if gt (index .Weights ` }}{{ print (add1 $i) }}{{ `) 0 }}` }}
46 | - name: {{ template "svc.name" $v }}
47 | match:
48 | {{- /* A match may have several ORed clauses */}}
49 | {{- range $j, $m := $v.match }}
50 | {{- /* include any other header requirements */}}
51 | {{- if (hasKey $m "headers") }}
52 | - headers:
53 | {{ toYaml (pick $m "headers").headers | indent 18 }}
54 | {{- end }} {{- /* if (hasKey $m "headers") */}}
55 | {{- /* include any other (non-header) requirements */}}
56 | {{- if gt (omit $m "headers" | keys | len) 0 }}
57 | {{ toYaml (omit $m "headers") | indent 16 }}
58 | {{- end }} {{- /* if gt (omit $m "headers" | keys | len) 0 */}}
59 | {{- end }} {{- /* range $j, $m := $v.match */}}
60 | route:
61 | - destination:
62 | host: {{ template "svc.name" $v }}.{{ $APP_NAMESPACE }}.svc.cluster.local
63 | port:
64 | number: {{ $v.port }}
65 | headers:
66 | response:
67 | add:
68 | app-version: {{ template "svc.name" $v }}
69 | {{ `{{- end }}` }}
70 | {{- end }} {{- /* range $i, $v := (rest $versions) */}}
71 | # primary version (default)
72 | {{- $v := (index $versions 0) }}
73 | - name: {{ template "svc.name" $v }}
74 | route:
75 | - destination:
76 | host: {{ template "svc.name" $v }}.{{ $APP_NAMESPACE }}.svc.cluster.local
77 | port:
78 | number: {{ $v.port }}
79 | headers:
80 | response:
81 | add:
82 | app-version: {{ template "svc.name" $v }}
83 |
84 | {{- end }} {{- /* define "env.deployment-istio.canary.routemap" */}}
85 |
--------------------------------------------------------------------------------
/charts/release/templates/_deployment-istio.canary.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.deployment-istio.canary" }}
2 |
3 | {{- /* routemap */}}
4 | {{ include "env.deployment-istio.canary.routemap" . }}
5 |
6 | {{- end }} {{- /* define "env.deployment-istio.canary" */}}
--------------------------------------------------------------------------------
/charts/release/templates/_deployment-istio.mirror.routemap.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.deployment-istio.mirror.routemap" }}
2 |
3 | {{- $APP_NAME := (include "application.name" .) }}
4 | {{- $APP_NAMESPACE := (include "application.namespace" .) }}
5 | {{- $versions := include "normalize.versions.deployment" . | mustFromJson }}
6 |
7 | apiVersion: v1
8 | kind: ConfigMap
9 | {{- template "routemap.metadata" . }}
10 | data:
11 | strSpec: |
12 | versions:
13 | {{- range $i, $v := $versions }}
14 | - resources:
15 | - gvrShort: svc
16 | name: {{ template "svc.name" $v }}
17 | namespace: {{ template "svc.namespace" $v }}
18 | - gvrShort: deploy
19 | name: {{ template "deploy.name" $v }}
20 | namespace: {{ template "deploy.namespace" $v }}
21 | - gvrShort: cm
22 | name: {{ $v.VERSION_NAME }}-weight-config
23 | namespace: {{ $v.VERSION_NAMESPACE }}
24 | weight: {{ $v.weight }}
25 | {{- end }} {{- /* range $i, $v := $versions */}}
26 | routingTemplates:
27 | {{ .Values.application.strategy }}:
28 | gvrShort: vs
29 | template: |
30 | apiVersion: networking.istio.io/v1beta1
31 | kind: VirtualService
32 | metadata:
33 | name: {{ $APP_NAME }}
34 | namespace: {{ $APP_NAMESPACE }}
35 | spec:
36 | gateways:
37 | {{- if .Values.gateway }}
38 | - {{ .Values.gateway }}
39 | {{- end }}
40 | - mesh
41 | hosts:
42 | - {{ $APP_NAME }}.{{ $APP_NAMESPACE }}
43 | - {{ $APP_NAME }}.{{ $APP_NAMESPACE }}.svc
44 | - {{ $APP_NAME }}.{{ $APP_NAMESPACE }}.svc.cluster.local
45 | http:
46 | - name: {{ $APP_NAME }}
47 | route:
48 | # primary version
49 | {{- $v := (index $versions 0) }}
50 | - destination:
51 | host: {{ template "svc.name" $v }}.{{ $APP_NAMESPACE }}.svc.cluster.local
52 | port:
53 | number: {{ $v.port }}
54 | {{- if gt (len $versions) 1 }}
55 | {{ `{{- if gt (index .Weights 1) 0 }}` }}
56 | weight: {{ `{{ index .Weights 0 }}` }}
57 | {{ `{{- end }}` }}
58 | {{- end }}
59 | headers:
60 | response:
61 | add:
62 | app-version: {{ template "svc.name" $v }}
63 | # other versions
64 | {{- range $i, $v := (rest $versions) }}
65 | {{ `{{- if gt (index .Weights ` }}{{ print (add1 $i) }}{{ `) 0 }}` }}
66 | mirror:
67 | host: {{ template "svc.name" $v }}.{{ $APP_NAMESPACE }}.svc.cluster.local
68 | port:
69 | number: {{ $v.port }}
70 | mirrorPercentage:
71 | value: {{ `{{ index .Weights ` }}{{ print (add1 $i) }}{{ ` }}` }}
72 | {{ `{{- end }}` }}
73 | {{- end }}
74 | {{- end }} {{- /* define "env.deployment-istio.mirror.routemap" */}}
75 |
--------------------------------------------------------------------------------
/charts/release/templates/_deployment-istio.mirror.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.deployment-istio.mirror" }}
2 |
3 | {{- /* prepare versions for simpler processing */}}
4 | {{- $versions := include "normalize.versions.deployment" . | mustFromJson }}
5 |
6 | {{- /* weight-config ConfigMaps except for primary */}}
7 | {{- range $i, $v := (rest $versions) }}
8 | {{ include "configmap.weight-config" $v }}
9 | ---
10 | {{- end }} {{- /* range $i, $v := $versions */}}
11 |
12 | {{- /* routemap */}}
13 | {{ include "env.deployment-istio.mirror.routemap" . }}
14 |
15 | {{- end }} {{- /* define "env.deployment-istio.mirror" */}}
--------------------------------------------------------------------------------
/charts/release/templates/_deployment-istio.none.routemap.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.deployment-istio.none.routemap" }}
2 |
3 | {{- $versions := include "normalize.versions.deployment" . | mustFromJson }}
4 |
5 | apiVersion: v1
6 | kind: ConfigMap
7 | {{- template "routemap.metadata" . }}
8 | data:
9 | strSpec: |
10 | versions:
11 | {{- range $i, $v := $versions }}
12 | - resources:
13 | - gvrShort: svc
14 | name: {{ template "svc.name" $v }}
15 | namespace: {{ template "svc.namespace" $v }}
16 | - gvrShort: deploy
17 | name: {{ template "deploy.name" $v }}
18 | namespace: {{ template "deploy.namespace" $v }}
19 | {{- end }}
20 |
21 | {{- end }} {{- /* define "env.deployment-istio.none.routemap" */}}
22 |
--------------------------------------------------------------------------------
/charts/release/templates/_deployment-istio.none.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.deployment-istio.none" }}
2 |
3 | {{- /* routemap */}}
4 | {{ include "env.deployment-istio.none.routemap" . }}
5 |
6 | {{- end }} {{- /* define "env.deployment-istio.none" */}}
--------------------------------------------------------------------------------
/charts/release/templates/_deployment-istio.service.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.deployment-istio.service" }}
2 |
3 | {{- $APP_NAME := (include "application.name" .) }}
4 | {{- $APP_NAMESPACE := (include "application.namespace" .) }}
5 |
6 | apiVersion: v1
7 | kind: Service
8 | metadata:
9 | name: {{ $APP_NAME }}
10 | namespace: {{ $APP_NAMESPACE }}
11 | spec:
12 | externalName: istio-ingressgateway.istio-system.svc.cluster.local
13 | sessionAffinity: None
14 | type: ExternalName
15 | {{- end }} {{- /* define "env.deployment-istio.service" */}}
--------------------------------------------------------------------------------
/charts/release/templates/_deployment-istio.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.deployment-istio" }}
2 |
3 | {{- /* Prepare versions for simpler processing */}}
4 | {{- $versions := include "normalize.versions.deployment" . | mustFromJson }}
5 |
6 | {{- range $i, $v := $versions }}
7 | {{- /* Deployment */}}
8 | {{ include "env.deployment.version.deployment" $v }}
9 | ---
10 | {{- /* Service */}}
11 | {{ include "env.deployment.version.service" $v }}
12 | ---
13 | {{- end }} {{- /* range $i, $v := $versions */}}
14 |
15 | {{- /* Service */}}
16 | {{ include "env.deployment-istio.service" . }}
17 | ---
18 |
19 | {{- /* routemap (and other strategy specific objects) */}}
20 | {{- if not .Values.application.strategy }}
21 | {{ include "env.deployment-istio.none" . }}
22 | {{- else if eq "none" .Values.application.strategy }}
23 | {{ include "env.deployment-istio.none" . }}
24 | {{- else if eq "blue-green" .Values.application.strategy }}
25 | {{ include "env.deployment-istio.blue-green" . }}
26 | {{- else if eq "canary" .Values.application.strategy }}
27 | {{ include "env.deployment-istio.canary" . }}
28 | {{- else if eq "mirror" .Values.application.strategy }}
29 | {{ include "env.deployment-istio.mirror" . }}
30 | {{- end }} {{- /* if eq ... .Values.application.strategy */}}
31 |
32 | {{- end }} {{- /* define "env.deployment-istio" */}}
--------------------------------------------------------------------------------
/charts/release/templates/_deployment.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.deployment" }}
2 |
3 | {{- /* Prepare versions for simpler processing */}}
4 | {{- $versions := include "normalize.versions.deployment" . | mustFromJson }}
5 |
6 | {{- range $i, $v := $versions }}
7 |
8 | {{- /* Deployment */}}
9 | {{ include "env.deployment.version.deployment" $v }}
10 | ---
11 | {{- /* Service */}}
12 | {{ include "env.deployment.version.service" $v }}
13 | ---
14 | {{- end }} {{- /* range $i, $v := $versions */}}
15 |
16 | {{- /* routemap (and other strategy specific objects) */}}
17 | {{- if not .Values.application.strategy }}
18 | {{ include "env.deployment-istio.none" . }}
19 | {{- else if eq "none" .Values.application.strategy }}
20 | {{ include "env.deployment-istio.none" . }}
21 | {{- else }}
22 | {{- printf "unknown or invalid application strategy (%s) for environment (%s)" .Values.application.strategy .Values.environment | fail }}
23 | {{- end }} {{- /* if eq ... .Values.application.strategy */}}
24 |
25 | {{- end }} {{- /* define "env.deployment" */}}
--------------------------------------------------------------------------------
/charts/release/templates/_deployment.version.deployment.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.deployment.version.deployment" }}
2 |
3 | {{- /* compute basic metadata */}}
4 | {{- $metadata := include "application.version.metadata" . | mustFromJson }}
5 |
6 | apiVersion: apps/v1
7 | kind: Deployment
8 | {{- if .deploymentSpecification }}
9 | metadata:
10 | {{- if .deploymentSpecification.metadata }}
11 | {{ toYaml (merge .deploymentSpecification.metadata $metadata) | nindent 2 | trim }}
12 | {{- else }}
13 | {{ toYaml $metadata | nindent 2 | trim }}
14 | {{- end }} {{- /* if .deploymentSpecification.metadata */}}
15 | spec:
16 | {{ toYaml .deploymentSpecification.spec | nindent 2 | trim }}
17 | {{- else }}
18 | {{- if not .image }} {{- /* require .image */}}
19 | {{- print "missing field: image required when deploymentSpecification absent" | fail }}
20 | {{- end }} {{- /* if not .image */}}
21 | {{- if not .port }} {{- /* require .port */}}
22 | {{- print "missing field: port required when deploymentSpecification absent" | fail }}
23 | {{- end }} {{- /* if not .port */}}
24 | metadata:
25 | {{ toYaml $metadata | nindent 2 | trim }}
26 | spec:
27 | selector:
28 | matchLabels:
29 | app: {{ .VERSION_NAME }}
30 | template:
31 | metadata:
32 | labels:
33 | app: {{ .VERSION_NAME }}
34 | spec:
35 | containers:
36 | - name: {{ .VERSION_NAME }}
37 | image: {{ .image }}
38 | ports:
39 | - containerPort: {{ .port }}
40 | {{- end }} {{- /* if .deploymentSpecification */}}
41 |
42 | {{- end }} {{- /* define "env.deployment.version.deployment" */}}
43 |
--------------------------------------------------------------------------------
/charts/release/templates/_deployment.version.service.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.deployment.version.service" }}
2 |
3 | {{- /* compute basic metadata */}}
4 | {{- $metadata := include "application.version.metadata" . | mustFromJson }}
5 |
6 | apiVersion: v1
7 | kind: Service
8 | {{- if .serviceSpecification }}
9 | metadata:
10 | {{- if .serviceSpecification.metadata }}
11 | {{ toYaml (merge .serviceSpecification.metadata $metadata) | nindent 2 | trim }}
12 | {{- else }}
13 | {{ toYaml $metadata | nindent 2 | trim }}
14 | {{- end }} {{- /* if .serviceSpecification.metadata */}}
15 | spec:
16 | {{ toYaml .serviceSpecification.spec | nindent 2 | trim }}
17 | {{- else }}
18 | {{- if not .port }} {{- /* require .port */}}
19 | {{- print "missing field: port required when serviceSpecification absent" | fail }}
20 | {{- end }} {{- /* if not .port */}}
21 | metadata:
22 | {{ toYaml $metadata | nindent 2 | trim }}
23 | spec:
24 | selector:
25 | app: {{ .VERSION_NAME }}
26 | ports:
27 | - port: {{ .port }}
28 | {{- end }} {{- /* if .serviceSpecification */}}
29 | {{- end }} {{- /* define "env.deployment.version.service" */}}
30 |
--------------------------------------------------------------------------------
/charts/release/templates/_kserve.blue-green.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.kserve.blue-green" }}
2 |
3 | {{- /* prepare versions for simpler processing */}}
4 | {{- $versions := include "normalize.versions.kserve" . | mustFromJson }}
5 |
6 | {{- /* weight-config ConfigMaps */}}
7 | {{- range $i, $v := $versions }}
8 | {{ include "configmap.weight-config" $v }}
9 | ---
10 | {{- end }} {{- /* range $i, $v := $versions */}}
11 |
12 | {{- /* routemap */}}
13 | {{ include "env.kserve.blue-green.routemap" . }}
14 |
15 | {{- end }} {{- /* define "env.kserve.blue-green" */}}
--------------------------------------------------------------------------------
/charts/release/templates/_kserve.canary.routemap.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.kserve.canary.routemap" }}
2 |
3 | {{- $APP_NAME := (include "application.name" .) }}
4 | {{- $APP_NAMESPACE := (include "application.namespace" .) }}
5 | {{- $versions := include "normalize.versions.kserve" . | mustFromJson }}
6 |
7 | apiVersion: v1
8 | kind: ConfigMap
9 | {{- template "routemap.metadata" . }}
10 | data:
11 | strSpec: |
12 | versions:
13 | {{- range $i, $v := $versions }}
14 | - resources:
15 | - gvrShort: isvc
16 | name: {{ template "isvc.name" $v }}
17 | namespace: {{ template "isvc.namespace" $v }}
18 | weight: {{ $v.weight }}
19 | {{- end }} {{- /* range $i, $v := $versions */}}
20 | routingTemplates:
21 | {{ .Values.application.strategy }}:
22 | gvrShort: vs
23 | template: |
24 | apiVersion: networking.istio.io/v1beta1
25 | kind: VirtualService
26 | metadata:
27 | name: {{ $APP_NAME }}
28 | namespace: {{ $APP_NAMESPACE }}
29 | spec:
30 | gateways:
31 | - knative-serving/knative-ingress-gateway
32 | - knative-serving/knative-local-gateway
33 | - mesh
34 | hosts:
35 | - {{ $APP_NAME }}.{{ $APP_NAMESPACE }}
36 | - {{ $APP_NAME }}.{{ $APP_NAMESPACE }}.svc
37 | - {{ $APP_NAME }}.{{ $APP_NAMESPACE }}.svc.cluster.local
38 | http:
39 | # non-primary versions
40 | {{- /* For candidate versions, ensure mm-model header is required in all matches */}}
41 | {{- range $i, $v := (rest $versions) }}
42 | {{- /* continue only if candidate is ready (weight > 0) */}}
43 | {{ `{{- if gt (index .Weights ` }}{{ print (add1 $i) }}{{ `) 0 }}` }}
44 | - name: {{ template "isvc.name" $v }}
45 | match:
46 | {{- /* A match may have several ORd clauses */}}
47 | {{- range $j, $m := $v.match }}
48 | {{- /* include any other header requirements */}}
49 | {{- if (hasKey $m "headers") }}
50 | - headers:
51 | {{ toYaml (pick $m "headers").headers | indent 18 }}
52 | {{- end }}
53 | {{- /* include any other (non-header) requirements */}}
54 | {{- if gt (omit $m "headers" | keys | len) 0 }}
55 | {{ toYaml (omit $m "headers") | indent 16 }}
56 | {{- end }}
57 | {{- end }}
58 | rewrite:
59 | uri: /v2/models/{{ template "isvc.name" $v }}/infer
60 | route:
61 | - destination:
62 | host: knative-local-gateway.istio-system.svc.cluster.local
63 | headers:
64 | request:
65 | set:
66 | Host: {{ template "isvc.name" $v }}-{{ template "kserve.host" $ }}
67 | response:
68 | add:
69 | app-version: {{ template "isvc.name" $v }}
70 | {{ `{{- end }}` }}
71 | {{- end }}
72 | # primary version (default)
73 | {{- $v := (index $versions 0) }}
74 | - name: {{ template "isvc.name" $v }}
75 | rewrite:
76 | uri: /v2/models/{{ template "isvc.name" $v }}/infer
77 | route:
78 | - destination:
79 | host: knative-local-gateway.istio-system.svc.cluster.local
80 | headers:
81 | request:
82 | set:
83 | Host: {{ template "isvc.name" $v }}-{{ template "kserve.host" $ }}
84 | response:
85 | add:
86 | app-version: {{ template "isvc.name" $v }}
87 |
88 | {{- end }} {{- /* define "env.kserve.canary.routemap" */}}
89 |
--------------------------------------------------------------------------------
/charts/release/templates/_kserve.canary.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.kserve.canary" }}
2 |
3 | {{- /* routemap */}}
4 | {{ include "env.kserve.canary.routemap" . }}
5 |
6 | {{- end }} {{- /* define "env.kserve.canary" */}}
--------------------------------------------------------------------------------
/charts/release/templates/_kserve.none.routemap.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.kserve.none.routemap" }}
2 |
3 | {{- $versions := include "normalize.versions.kserve" . | mustFromJson }}
4 |
5 | apiVersion: v1
6 | kind: ConfigMap
7 | {{- template "routemap.metadata" . }}
8 | data:
9 | strSpec: |
10 | versions:
11 | {{- range $i, $v := $versions }}
12 | - resources:
13 | - gvrShort: isvc
14 | name: {{ template "isvc.name" $v }}
15 | namespace: {{ template "isvc.namespace" $v }}
16 | {{- end }} {{- /* range $i, $v := $versions */}}
17 |
18 | {{- end }} {{- /* define "env.kserve.none.routemap" */}}
19 |
--------------------------------------------------------------------------------
/charts/release/templates/_kserve.none.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.kserve.none" }}
2 |
3 | {{- /* routemap */}}
4 | {{ include "env.kserve.none.routemap" . }}
5 |
6 | {{- end }} {{- /* define "env.kserve.none" */}}
--------------------------------------------------------------------------------
/charts/release/templates/_kserve.service.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.kserve.service" }}
2 |
3 | {{- $APP_NAME := (include "application.name" .) }}
4 | {{- $APP_NAMESPACE := (include "application.namespace" .) }}
5 |
6 | apiVersion: v1
7 | kind: Service
8 | metadata:
9 | name: {{ $APP_NAME }}
10 | namespace: {{ $APP_NAMESPACE }}
11 | spec:
12 | externalName: knative-local-gateway.istio-system.svc.cluster.local
13 | sessionAffinity: None
14 | type: ExternalName
15 | {{- end }} {{- /* define "env.kserve.service" */}}
--------------------------------------------------------------------------------
/charts/release/templates/_kserve.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.kserve" }}
2 |
3 | {{- /* Prepare versions for simpler processing */}}
4 | {{- $versions := include "normalize.versions.kserve" . | mustFromJson }}
5 |
6 | {{- range $i, $v := $versions }}
7 | {{- /* InferenceService */}}
8 | {{ include "env.kserve.version.isvc" $v }}
9 | ---
10 | {{- end }} {{- /* range $i, $v := $versions */}}
11 |
12 | {{- /* Service */}}
13 | {{ include "env.kserve.service" . }}
14 | ---
15 |
16 | {{- /* routemap (and other strategy specific objects) */}}
17 | {{- if not .Values.application.strategy }}
18 | {{ include "env.kserve.none" . }}
19 | {{- else if eq "none" .Values.application.strategy }}
20 | {{ include "env.kserve.none" . }}
21 | {{- else if eq "blue-green" .Values.application.strategy }}
22 | {{ include "env.kserve.blue-green" . }}
23 | {{- else if eq "canary" .Values.application.strategy }}
24 | {{ include "env.kserve.canary" . }}
25 | {{- end }} {{- /* if eq ... .Values.application.strategy */}}
26 |
27 | {{- end }} {{- /* define "env.kserve" */}}
--------------------------------------------------------------------------------
/charts/release/templates/_kserve.version.isvc.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.kserve.version.isvc" }}
2 |
3 | {{- /* compute basic metadata */}}
4 | {{- $metadata := include "application.version.metadata" . | mustFromJson }}
5 | {{- /* add annotation serving.kserve.io/deploymentMode */}}
6 |
7 | {{- /* define InferenceServcie */}}
8 | apiVersion: serving.kserve.io/v1beta1
9 | kind: InferenceService
10 | {{- if .inferenceServiceSpecification }}
11 | metadata:
12 | {{- if .inferenceServiceSpecification.metadata }}
13 | {{ toYaml (merge .inferenceServiceSpecification.metadata $metadata) | nindent 2 | trim }}
14 | {{- else }}
15 | {{ toYaml $metadata | nindent 2 | trim }}
16 | {{- end }} {{- /* if .inferenceServiceSpecification.metadata */}}
17 | spec:
18 | {{ toYaml .inferenceServiceSpecification.spec | nindent 2 | trim }}
19 | {{- else }}
20 | {{- if not .storageUri }} {{- /* require .storageUri */}}
21 | {{- print "missing field: storageUri required when inferenceServiceSpecification absent" | fail }}
22 | {{- end }} {{- /* if not .storageUri */}}
23 | metadata:
24 | {{ toYaml $metadata | nindent 2 | trim }}
25 | spec:
26 | predictor:
27 | minReplicas: 1
28 | model:
29 | modelFormat:
30 | name: {{ .modelFormat }}
31 | runtime: {{ .runtime }}
32 | storageUri: {{ .storageUri }}
33 | {{- if .protocolVersion }}
34 | protocolVersion: {{ .protocolVersion }}
35 | {{- end }} {{- /* if .protocolVersion */}}
36 | {{- if .ports }}
37 | ports:
38 | {{ toYaml .ports | nindent 6 | trim }}
39 | {{- end }} {{- /* if .ports */}}
40 | {{- end }} {{- /* if .inferenceServiceSpecification */}}
41 |
42 | {{- end }} {{- /* define "env.kserve.version.isvc" */}}
43 |
--------------------------------------------------------------------------------
/charts/release/templates/_mm-istio.blue-green.routemap.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.mm-istio.blue-green.routemap" }}
2 |
3 | {{- $APP_NAME := (include "application.name" .) }}
4 | {{- $APP_NAMESPACE := (include "application.namespace" .) }}
5 | {{- $versions := include "normalize.versions.kserve-mm" . | mustFromJson }}
6 |
7 | apiVersion: v1
8 | kind: ConfigMap
9 | {{- template "routemap.metadata" . }}
10 | data:
11 | strSpec: |
12 | versions:
13 | {{- range $i, $v := $versions }}
14 | - resources:
15 | - gvrShort: isvc
16 | name: {{ template "isvc.name" $v }}
17 | namespace: {{ template "isvc.namespace" $v }}
18 | - gvrShort: cm
19 | name: {{ $v.VERSION_NAME }}-weight-config
20 | namespace: {{ $v.VERSION_NAMESPACE }}
21 | weight: {{ $v.weight }}
22 | {{- end }} {{- /* range $i, $v := $versions */}}
23 | routingTemplates:
24 | {{ .Values.application.strategy }}:
25 | gvrShort: vs
26 | template: |
27 | apiVersion: networking.istio.io/v1beta1
28 | kind: VirtualService
29 | metadata:
30 | name: {{ $APP_NAME }}
31 | namespace: {{ $APP_NAMESPACE }}
32 | spec:
33 | gateways:
34 | {{- if .Values.gateway }}
35 | - {{ .Values.gateway }}
36 | {{- end }}
37 | - mesh
38 | hosts:
39 | - {{ $APP_NAME }}.{{ $APP_NAMESPACE }}
40 | - {{ $APP_NAME }}.{{ $APP_NAMESPACE }}.svc
41 | - {{ $APP_NAME }}.{{ $APP_NAMESPACE }}.svc.cluster.local
42 | http:
43 | - route:
44 | # primary model version
45 | {{- $v := (index $versions 0) }}
46 | - destination:
47 | host: {{ template "mm.serviceHost" }}
48 | port:
49 | number: {{ template "mm.servicePort" . }}
50 | {{- if gt (len $versions) 1 }}
51 | {{ `{{- if gt (index .Weights 1) 0 }}` }}
52 | weight: {{ `{{ index .Weights 0 }}` }}
53 | {{ `{{- end }}` }}
54 | {{- end }} {{- /* if gt (len $versions) 1 */}}
55 | headers:
56 | request:
57 | set:
58 | mm-vmodel-id: {{ template "isvc.name" $v }}
59 | remove:
60 | - branch
61 | response:
62 | add:
63 | app-version: {{ template "isvc.name" $v }}
64 | # non-primary model versions
65 | {{- range $i, $v := (rest $versions) }}
66 | - destination:
67 | host: {{ template "mm.serviceHost" $ }}
68 | port:
69 | number: {{ template "mm.servicePort" $ }}
70 | weight: {{ `{{ index .Weights ` }}{{ print (add1 $i) }}{{ ` }}` }}
71 | headers:
72 | request:
73 | set:
74 | mm-vmodel-id: {{ template "isvc.name" $v }}
75 | response:
76 | add:
77 | app-version: {{ template "isvc.name" $v }}
78 | {{- end }} {{- /* {{- range $i, $v := (rest $versions) }} */}}
79 |
80 | {{- end }} {{- /* define "env.mm-istio.blue-green.routemap" */}}
81 |
--------------------------------------------------------------------------------
/charts/release/templates/_mm-istio.blue-green.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.mm-istio.blue-green" }}
2 |
3 | {{- /* ServiceEntry */}}
4 | {{ include "env.mm-istio.service" . }}
5 | ---
6 |
7 | {{- /* prepare versions for simpler processing */}}
8 | {{- $versions := include "normalize.versions.kserve-mm" . | mustFromJson }}
9 |
10 | {{- /* weight-config ConfigMaps */}}
11 | {{- range $i, $v := $versions }}
12 | {{ include "configmap.weight-config" $v }}
13 | ---
14 | {{- end }} {{- /* range $i, $v := $versions */}}
15 |
16 | {{- /* routemap */}}
17 | {{ include "env.mm-istio.blue-green.routemap" . }}
18 |
19 | {{- end }} {{- /* define "env.mm-istio.blue-green" */}}
--------------------------------------------------------------------------------
/charts/release/templates/_mm-istio.canary.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.mm-istio.canary" }}
2 |
3 | {{- /* ServiceEntry */}}
4 | {{ include "env.mm-istio.service" . }}
5 | ---
6 |
7 | {{- /* routemap */}}
8 | {{ include "env.mm-istio.canary.routemap" . }}
9 |
10 | {{- end }} {{- /* define "env.mm-istio.canary" */}}
--------------------------------------------------------------------------------
/charts/release/templates/_mm-istio.none.routemap.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.mm-istio.none.routemap" }}
2 |
3 | {{- $versions := include "normalize.versions.kserve-mm" . | mustFromJson }}
4 |
5 | apiVersion: v1
6 | kind: ConfigMap
7 | {{- template "routemap.metadata" . }}
8 | data:
9 | strSpec: |
10 | versions:
11 | {{- range $i, $v := $versions }}
12 | - resources:
13 | - gvrShort: isvc
14 | name: {{ template "isvc.name" $v }}
15 | namespace: {{ template "isvc.namespace" $v }}
16 | {{- end }}
17 |
18 | {{- end }} {{- /* define "env.mm-istio.none.routemap" */}}
19 |
--------------------------------------------------------------------------------
/charts/release/templates/_mm-istio.none.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.mm-istio.none" }}
2 |
3 | {{- /* routemap */}}
4 | {{ include "env.mm-istio.none.routemap" . }}
5 |
6 | {{- end }} {{- /* define "env.mm-istio.none" */}}
--------------------------------------------------------------------------------
/charts/release/templates/_mm-istio.service.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.mm-istio.service" }}
2 |
3 | {{- $APP_NAME := (include "application.name" .) }}
4 | {{- $APP_NAMESPACE := (include "application.namespace" .) }}
5 |
6 | apiVersion: networking.istio.io/v1beta1
7 | kind: ServiceEntry
8 | metadata:
9 | name: {{ $APP_NAME }}
10 | namespace: {{ $APP_NAMESPACE }}
11 | spec:
12 | hosts:
13 | - {{ $APP_NAME }}.{{ $APP_NAMESPACE }}
14 | - {{ $APP_NAME }}.{{ $APP_NAMESPACE }}.svc
15 | - {{ $APP_NAME }}.{{ $APP_NAMESPACE }}.svc.cluster.local
16 | location: MESH_INTERNAL
17 | ports:
18 | - number: {{ template "mm.servicePort" . }}
19 | name: http
20 | protocol: HTTP
21 | resolution: DNS
22 | workloadSelector:
23 | labels:
24 | modelmesh-service: modelmesh-serving
25 | {{- end }} {{- /* define "env.mm-istio.service" */}}
--------------------------------------------------------------------------------
/charts/release/templates/_mm-istio.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.mm-istio" }}
2 |
3 | {{- /* Prepare versions for simpler processing */}}
4 | {{- $versions := include "normalize.versions.kserve-mm" . | mustFromJson }}
5 |
6 | {{- /* InferenceServices */}}
7 | {{- range $i, $v := $versions }}
8 | {{ include "env.mm-istio.version.isvc" $v }}
9 | ---
10 | {{- end }} {{- /* range $i, $v := $versions */}}
11 |
12 | {{- /* routemap (and other strategy specific objects) */}}
13 | {{- if not .Values.application.strategy }}
14 | {{ include "env.mm-istio.none" . }}
15 | {{- else if eq "none" .Values.application.strategy }}
16 | {{ include "env.mm-istio.none" . }}
17 | {{- else if eq "blue-green" .Values.application.strategy }}
18 | {{ include "env.mm-istio.blue-green" . }}
19 | {{- else if eq "canary" .Values.application.strategy }}
20 | {{ include "env.mm-istio.canary" . }}
21 | {{- end }} {{- /* if eq ... .Values.application.strategy */}}
22 |
23 | {{- end }} {{- /* define "env.mm-istio" */}}
--------------------------------------------------------------------------------
/charts/release/templates/_mm-istio.version.isvc.tpl:
--------------------------------------------------------------------------------
1 | {{- define "env.mm-istio.version.isvc" }}
2 |
3 | {{- /* compute basic metadata */}}
4 | {{- $metadata := include "application.version.metadata" . | mustFromJson }}
5 | {{- /* add annotation serving.kserve.io/deploymentMode */}}
6 | {{- $metadata := set $metadata "annotations" (merge $metadata.annotations (dict "serving.kserve.io/deploymentMode" "ModelMesh")) }}
7 |
8 | {{- /* define InferenceServcie */}}
9 | apiVersion: serving.kserve.io/v1beta1
10 | kind: InferenceService
11 | {{- if .inferenceServiceSpecification }}
12 | metadata:
13 | {{- if .inferenceServiceSpecification.metadata }}
14 | {{ toYaml (merge .inferenceServiceSpecification.metadata $metadata) | nindent 2 | trim }}
15 | {{- else }}
16 | {{ toYaml $metadata | nindent 2 | trim }}
17 | {{- end }} {{- /* if .inferenceServiceSpecification.metadata */}}
18 | spec:
19 | {{ toYaml .inferenceServiceSpecification.spec | nindent 2 | trim }}
20 | {{- else }}
21 | {{- if not .modelFormat }} {{- /* require .modelFormat */}}
22 | {{- print "missing field: modelFormat required when inferenceServiceSpecification absent" | fail }}
23 | {{- end }} {{- /* if not .modelFormat */}}
24 | {{- if not .storageUri }} {{- /* require .storageUri */}}
25 | {{- print "missing field: storageUri required when inferenceServiceSpecification absent" | fail }}
26 | {{- end }} {{- /* if not .storageUri */}}
27 | metadata:
28 | {{ toYaml $metadata | nindent 2 | trim }}
29 | spec:
30 | predictor:
31 | model:
32 | modelFormat:
33 | name: {{ .modelFormat }}
34 | storageUri: {{ .storageUri }}
35 | {{- end }} {{- /* if .inferenceServiceSpecification */}}
36 |
37 | {{- end }} {{- /* define "env.mm-istio.version.isvc" */}}
38 |
--------------------------------------------------------------------------------
/charts/release/templates/release.yaml:
--------------------------------------------------------------------------------
1 | {{- /* Verify that .Values.environment is valid */}}
2 | {{- if not .Values.environment }}
3 | {{- printf "environment is required" | fail }}
4 | {{- end }} {{- /* if not .Values.environment */}}
5 |
6 | {{- /* Verify that .Values.application is valid */}}
7 | {{- if not .Values.application }}
8 | {{- printf "application is required" | fail }}
9 | {{- end }} {{- /* if not .Values.application */}}
10 |
11 | {{- /* Different processing based on .Values.environment */}}
12 | {{- if eq "deployment" .Values.environment }}
13 | {{- include "env.deployment" . }}
14 | {{- else if eq "deployment-gtw" .Values.environment }}
15 | {{- include "env.deployment-gtw" . }}
16 | {{- else if eq "deployment-istio" .Values.environment }}
17 | {{- include "env.deployment-istio" . }}
18 | {{- else if eq "kserve-modelmesh-istio" .Values.environment }}
19 | {{- include "env.mm-istio" . }}
20 | {{- else if has .Values.environment (list "kserve" "kserve-0.11") }}
21 | {{- include "env.kserve" . }}
22 | {{- else if eq "kserve-0.10" .Values.environment }}
23 | {{- include "env.kserve-10" . }}
24 | {{- else }}
25 | {{- printf "Unknown environment: '%s'" .Values.environment | fail }}
26 | {{- end }} {{- /* if eq ,,, .Values.environment */}}
27 |
--------------------------------------------------------------------------------
/charts/release/values.yaml:
--------------------------------------------------------------------------------
1 | # iter8Version is the minor version of Iter8
2 | # should be specified as the value of the iter8.tools/version label on all routemaps
3 | iter8Version: v1.1
4 |
5 | # default Istio Gateway name
6 | istioGateway: my-gateway
7 |
--------------------------------------------------------------------------------
/cmd/controllers.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "context"
5 | "os"
6 | "os/signal"
7 | "syscall"
8 |
9 | "github.com/iter8-tools/iter8/abn"
10 | "github.com/iter8-tools/iter8/base/log"
11 | "github.com/iter8-tools/iter8/controllers"
12 | "github.com/iter8-tools/iter8/controllers/k8sclient"
13 | "github.com/iter8-tools/iter8/metrics"
14 | "github.com/spf13/cobra"
15 | "google.golang.org/grpc"
16 | )
17 |
18 | // controllersDesc is the description of controllers cmd
19 | const controllersDesc = `
20 | Start Iter8 controllers.
21 |
22 | $ iter8 controllers
23 | `
24 |
25 | // newControllersCmd creates the Iter8 controllers
26 | // when invoking this function for real, set stopCh to nil
27 | // this will block the controller from exiting until an os.Interrupt;
28 | // when invoking this function in a unit test, set stopCh to ctx.Done()
29 | // this will exit the controller when cancel() is called by the parent function;
30 | func newControllersCmd(stopCh <-chan struct{}, client k8sclient.Interface) *cobra.Command {
31 | cmd := &cobra.Command{
32 | Use: "controllers",
33 | Short: "Start Iter8 controllers",
34 | Long: controllersDesc,
35 | Args: cobra.NoArgs,
36 | SilenceUsage: true,
37 | RunE: func(_ *cobra.Command, _ []string) error {
38 | // createSigCh indicates if we should create sigCh (channel) that fires on interrupt
39 | createSigCh := false
40 |
41 | ctx, cancel := context.WithCancel(context.Background())
42 | defer cancel()
43 | // if stopCh is nil, create sigCh to exit this func on interrupt,
44 | // and use ctx.Done() to clean up controllers when exiting;
45 | // otherwise, simply use stopCh for both
46 | if stopCh == nil {
47 | stopCh = ctx.Done()
48 | createSigCh = true
49 | }
50 |
51 | if client == nil {
52 | var err error
53 | client, err = k8sclient.New(settings)
54 | if err != nil {
55 | log.Logger.Error("could not obtain Kube client... ")
56 | return err
57 | }
58 | }
59 |
60 | if err := controllers.Start(stopCh, client); err != nil {
61 | log.Logger.Error("controllers did not start... ")
62 | return err
63 | }
64 | log.Logger.Debug("started controllers... ")
65 |
66 | // launch gRPC server to respond to frontend requests
67 | go func() {
68 | err := abn.LaunchGRPCServer([]grpc.ServerOption{}, stopCh)
69 | if err != nil {
70 | log.Logger.Error("cound not start A/B/n service")
71 | }
72 | }()
73 |
74 | // launch metrics HTTP server to respond to support Grafana visualization
75 | go func() {
76 | err := metrics.Start(stopCh)
77 | if err != nil {
78 | log.Logger.Error("count not start A/B/n metrics service")
79 | }
80 | }()
81 |
82 | // if createSigCh, then block until there is an os.Interrupt
83 | if createSigCh {
84 | sigCh := make(chan os.Signal, 1)
85 | signal.Notify(sigCh, syscall.SIGTERM, os.Interrupt)
86 | <-sigCh
87 | log.Logger.Warn("SIGTERM... ")
88 | }
89 |
90 | return nil
91 | },
92 | }
93 | return cmd
94 | }
95 |
--------------------------------------------------------------------------------
/cmd/controllers_test.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "context"
5 | "os"
6 | "testing"
7 | "time"
8 |
9 | "github.com/iter8-tools/iter8/base"
10 | "github.com/iter8-tools/iter8/controllers/k8sclient/fake"
11 | "github.com/stretchr/testify/assert"
12 | )
13 |
14 | func TestControllers(t *testing.T) {
15 | // set pod name
16 | _ = os.Setenv("POD_NAME", "pod-0")
17 | // set pod namespace
18 | _ = os.Setenv("POD_NAMESPACE", "default")
19 | // set config file
20 | _ = os.Setenv("CONFIG_FILE", base.CompletePath("../", "testdata/controllers/config.yaml"))
21 |
22 | kubeClient = fake.New(nil, nil)
23 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
24 | defer cancel()
25 | cmd := newControllersCmd(ctx.Done(), kubeClient)
26 | err := cmd.RunE(cmd, nil)
27 | assert.NoError(t, err)
28 | }
29 |
--------------------------------------------------------------------------------
/cmd/doc.go:
--------------------------------------------------------------------------------
1 | // Package cmd defines the Iter8 CLI commands and their flags.
2 | package cmd
3 |
--------------------------------------------------------------------------------
/cmd/docs.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 | "path"
6 | "path/filepath"
7 | "strings"
8 |
9 | "github.com/spf13/cobra/doc"
10 | "golang.org/x/text/cases"
11 | "golang.org/x/text/language"
12 |
13 | "github.com/spf13/cobra"
14 | )
15 |
16 | // docsDesc is the description for the docs command
17 | const docsDesc = `
18 | Generate markdown documentation for Iter8 CLI commands. Documentation will be generated for all commands that are not hidden.
19 | `
20 |
21 | // newDocsCmd creates the docs command
22 | func newDocsCmd() *cobra.Command {
23 | docsDir := ""
24 | cmd := &cobra.Command{
25 | Use: "docs",
26 | Short: "Generate markdown documentation for Iter8 CLI",
27 | Long: docsDesc,
28 | Hidden: true,
29 | SilenceUsage: true,
30 | RunE: func(cmd *cobra.Command, args []string) error {
31 | standardLinks := func(s string) string { return s }
32 |
33 | hdrFunc := func(filename string) string {
34 | base := filepath.Base(filename)
35 | name := strings.TrimSuffix(base, path.Ext(base))
36 | caser := cases.Title(language.English)
37 | title := caser.String(strings.Replace(name, "_", " ", -1))
38 | tpl := `---
39 | template: main.html
40 | title: "%s"
41 | hide:
42 | - toc
43 | ---
44 | `
45 | return fmt.Sprintf(tpl, title)
46 | }
47 |
48 | // automatically generate markdown documentation for all Iter8 commands
49 | return doc.GenMarkdownTreeCustom(rootCmd, docsDir, hdrFunc, standardLinks)
50 | },
51 | }
52 | addDocsFlags(cmd, &docsDir)
53 | return cmd
54 | }
55 |
56 | // addDocsFlags defines the flags for the docs command
57 | func addDocsFlags(cmd *cobra.Command, docsDirPtr *string) {
58 | cmd.Flags().StringVar(docsDirPtr, "commandDocsDir", "", "directory where Iter8 CLI documentation will be created")
59 | _ = cmd.MarkFlagRequired("commandDocsDir")
60 | }
61 |
--------------------------------------------------------------------------------
/cmd/docs_test.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "testing"
7 | )
8 |
9 | func TestDocs(t *testing.T) {
10 | _ = os.Chdir(t.TempDir())
11 | tests := []cmdTestCase{
12 | // assert
13 | {
14 | name: "create docs",
15 | cmd: fmt.Sprintf("docs --commandDocsDir %v", t.TempDir()),
16 | },
17 | }
18 |
19 | runTestActionCmd(t, tests)
20 | }
21 |
--------------------------------------------------------------------------------
/cmd/k.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/iter8-tools/iter8/base/log"
7 | "github.com/iter8-tools/iter8/driver"
8 | "github.com/spf13/cobra"
9 | )
10 |
11 | // kcmd is the root command that enables Kubernetes experiments
12 | var kcmd = &cobra.Command{
13 | Use: "k",
14 | Short: "Work with Kubernetes experiments",
15 | Long: "Work with Kubernetes experiments",
16 | }
17 |
18 | // addTestFlag adds the test flag
19 | func addTestFlag(cmd *cobra.Command, testP *string) {
20 | cmd.Flags().StringVarP(testP, "test", "t", driver.DefaultTestName, "name of the test")
21 | }
22 |
23 | func init() {
24 | settings.AddFlags(kcmd.PersistentFlags())
25 | // hiding these Helm flags for now
26 | if err := kcmd.PersistentFlags().MarkHidden("debug"); err != nil {
27 | log.Logger.Fatal(err)
28 | os.Exit(1)
29 | }
30 | if err := kcmd.PersistentFlags().MarkHidden("registry-config"); err != nil {
31 | log.Logger.Fatal(err)
32 | os.Exit(1)
33 | }
34 | if err := kcmd.PersistentFlags().MarkHidden("repository-config"); err != nil {
35 | log.Logger.Fatal(err)
36 | os.Exit(1)
37 | }
38 | if err := kcmd.PersistentFlags().MarkHidden("repository-cache"); err != nil {
39 | log.Logger.Fatal(err)
40 | os.Exit(1)
41 | }
42 |
43 | // add k run
44 | kcmd.AddCommand(newKRunCmd(kd, os.Stdout))
45 | }
46 |
--------------------------------------------------------------------------------
/cmd/krun.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "io"
5 |
6 | ia "github.com/iter8-tools/iter8/action"
7 | "github.com/iter8-tools/iter8/driver"
8 | "github.com/spf13/cobra"
9 | )
10 |
11 | // krunDesc is the description of the k run command
12 | const krunDesc = `
13 | Run a performance test on Kubernetes. This command reads a test specified in a secret and writes the result back to the secret.
14 |
15 | $ iter8 k run --namespace {{ namespace }} --test {{ test name }}
16 |
17 | This command is intended for use within the Iter8 Docker image that is used to execute Kubernetes tests.
18 | `
19 |
20 | // newKRunCmd creates the Kubernetes run command
21 | func newKRunCmd(kd *driver.KubeDriver, _ io.Writer) *cobra.Command {
22 | actor := ia.NewRunOpts(kd)
23 | actor.EnvSettings = settings
24 | cmd := &cobra.Command{
25 | Use: "run",
26 | Short: "Run a performance test on Kubernetes",
27 | Long: krunDesc,
28 | SilenceUsage: true,
29 | Hidden: true,
30 | RunE: func(_ *cobra.Command, _ []string) error {
31 | return actor.KubeRun()
32 | },
33 | }
34 | addTestFlag(cmd, &actor.Test)
35 | return cmd
36 | }
37 |
--------------------------------------------------------------------------------
/cmd/krun_test.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "io"
8 | "net/http"
9 | "os"
10 | "testing"
11 |
12 | "fortio.org/fortio/fhttp"
13 | "github.com/iter8-tools/iter8/base"
14 | id "github.com/iter8-tools/iter8/driver"
15 | "github.com/stretchr/testify/assert"
16 | corev1 "k8s.io/api/core/v1"
17 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
18 | )
19 |
20 | const (
21 | myName = "myName"
22 | myNamespace = "myNamespace"
23 | )
24 |
25 | func TestKRun(t *testing.T) {
26 | // define METRICS_SERVER_URL
27 | metricsServerURL := "http://iter8.default:8080"
28 | err := os.Setenv(base.MetricsServerURL, metricsServerURL)
29 | assert.NoError(t, err)
30 |
31 | // create and configure HTTP endpoint for testing
32 | mux, addr := fhttp.DynamicHTTPServer(false)
33 | url := fmt.Sprintf("http://127.0.0.1:%d/get", addr.Port)
34 | var verifyHandlerCalled bool
35 | mux.HandleFunc("/get", base.GetTrackingHandler(&verifyHandlerCalled))
36 |
37 | // mock metrics server
38 | base.StartHTTPMock(t)
39 | metricsServerCalled := false
40 | base.MockMetricsServer(base.MockMetricsServerInput{
41 | MetricsServerURL: metricsServerURL,
42 | ExperimentResultCallback: func(req *http.Request) {
43 | metricsServerCalled = true
44 |
45 | // check query parameters
46 | assert.Equal(t, myName, req.URL.Query().Get("test"))
47 | assert.Equal(t, myNamespace, req.URL.Query().Get("namespace"))
48 |
49 | // check payload
50 | body, err := io.ReadAll(req.Body)
51 | assert.NoError(t, err)
52 | assert.NotNil(t, body)
53 |
54 | // check payload content
55 | bodyExperimentResult := base.ExperimentResult{}
56 |
57 | err = json.Unmarshal(body, &bodyExperimentResult)
58 | assert.NoError(t, err)
59 | assert.NotNil(t, body)
60 | assert.Equal(t, myName, bodyExperimentResult.Name)
61 | assert.Equal(t, myNamespace, bodyExperimentResult.Namespace)
62 | },
63 | })
64 |
65 | _ = os.Chdir(t.TempDir())
66 |
67 | // create experiment.yaml
68 | base.CreateExperimentYaml(t, base.CompletePath("../testdata", base.ExperimentTemplateFile), url, base.ExperimentFile)
69 |
70 | tests := []cmdTestCase{
71 | // k report
72 | {
73 | name: "k run",
74 | cmd: "k run -t default --namespace default",
75 | golden: base.CompletePath("../testdata", "output/krun.txt"),
76 | },
77 | }
78 |
79 | // fake kube cluster
80 | *kd = *id.NewFakeKubeDriver(settings)
81 |
82 | // and read it...
83 | byteArray, _ := os.ReadFile(base.ExperimentFile)
84 | _, _ = kd.Clientset.CoreV1().Secrets("default").Create(context.TODO(), &corev1.Secret{
85 | ObjectMeta: metav1.ObjectMeta{
86 | Name: "default",
87 | Namespace: "default",
88 | },
89 | StringData: map[string]string{base.ExperimentFile: string(byteArray)},
90 | }, metav1.CreateOptions{})
91 |
92 | runTestActionCmd(t, tests)
93 | // sanity check -- handler was called
94 | assert.True(t, verifyHandlerCalled)
95 | assert.True(t, metricsServerCalled)
96 | }
97 |
--------------------------------------------------------------------------------
/cmd/root.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "github.com/iter8-tools/iter8/controllers/k8sclient"
5 | "github.com/iter8-tools/iter8/driver"
6 |
7 | "github.com/iter8-tools/iter8/base/log"
8 | "github.com/sirupsen/logrus"
9 | "github.com/spf13/cobra"
10 | "helm.sh/helm/v3/pkg/cli"
11 | )
12 |
13 | var (
14 | // default log level for Iter8 CLI
15 | logLevel = "info"
16 | // Default Helm and Kubernetes settings
17 | settings = cli.New()
18 | // KubeDriver used by actions package
19 | kd = driver.NewKubeDriver(settings)
20 | // kubeclient is the client used for controllers package
21 | kubeClient k8sclient.Interface
22 | )
23 |
24 | // rootCmd represents the base command when called without any subcommands
25 | var rootCmd = &cobra.Command{
26 | Use: "iter8",
27 | Short: "Kubernetes release optimizer",
28 | Long: `
29 | Iter8 is the Kubernetes release optimizer built for DevOps, MLOps, SRE and data science teams. Iter8 makes it easy to ensure that Kubernetes apps and ML models perform well and maximize business value.
30 | `,
31 | PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
32 | ll, err := logrus.ParseLevel(logLevel)
33 | if err != nil {
34 | log.Logger.Error(err)
35 | return err
36 | }
37 | log.Logger.Level = ll
38 | return nil
39 | },
40 | }
41 |
42 | // Execute adds all child commands to the root command and sets flags appropriately.
43 | // This is called by main.main(). It only needs to happen once to the rootCmd.
44 | func Execute() {
45 | cobra.CheckErr(rootCmd.Execute())
46 | }
47 |
48 | // initialize Iter8 CLI root command and add all subcommands
49 | func init() {
50 | // disable completion command for now
51 | rootCmd.CompletionOptions.DisableDefaultCmd = true
52 | rootCmd.PersistentFlags().StringVarP(&logLevel, "loglevel", "l", "info", "trace, debug, info, warning, error, fatal, panic")
53 | rootCmd.SilenceErrors = true // will get printed in Execute() (by cobra.CheckErr())
54 |
55 | // add docs
56 | rootCmd.AddCommand(newDocsCmd())
57 |
58 | // add k
59 | rootCmd.AddCommand(kcmd)
60 |
61 | // add version
62 | rootCmd.AddCommand(newVersionCmd())
63 |
64 | // add controllers
65 | rootCmd.AddCommand(newControllersCmd(nil, kubeClient))
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/cmd/version.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 | "runtime"
6 |
7 | "github.com/iter8-tools/iter8/base"
8 | "github.com/spf13/cobra"
9 | )
10 |
11 | // versionDesc is the description of the version command
12 | var versionDesc = `
13 | Print the version of Iter8 CLI.
14 |
15 | $ iter8 version
16 |
17 | The output may look as follows:
18 |
19 | $ cmd.BuildInfo{Version:"v0.13.0", GitCommit:"f24e86f3d3eceb02eabbba54b40af2c940f55ad5", GoVersion:"go1.19.3"}
20 |
21 | In the sample output shown above:
22 |
23 | - Version is the semantic version of the Iter8 CLI.
24 | - GitCommit is the SHA hash for the commit that this version was built from.
25 | - GoVersion is the version of Go that was used to compile Iter8 CLI.
26 | `
27 |
28 | var (
29 | // gitCommit is the git sha1
30 | gitCommit = ""
31 | )
32 |
33 | // BuildInfo describes the compile time information.
34 | type BuildInfo struct {
35 | // Version is the semantic version
36 | Version string `json:"version,omitempty"`
37 | // GitCommit is the git sha1.
38 | GitCommit string `json:"git_commit,omitempty"`
39 | // GoVersion is the version of the Go compiler used to compile Iter8.
40 | GoVersion string `json:"go_version,omitempty"`
41 | }
42 |
43 | // newVersionCmd creates the version command
44 | func newVersionCmd() *cobra.Command {
45 | var short bool
46 | // versionCmd represents the version command
47 | cmd := &cobra.Command{
48 | Use: "version",
49 | Short: "Print Iter8 CLI version",
50 | Long: versionDesc,
51 | SilenceErrors: true,
52 | RunE: func(_ *cobra.Command, _ []string) error {
53 | v := getBuildInfo()
54 | if short {
55 | if len(v.GitCommit) >= 7 {
56 | fmt.Printf("%s+g%s", base.Version, v.GitCommit[:7])
57 | fmt.Println()
58 | return nil
59 | }
60 | fmt.Println(base.Version)
61 | return nil
62 | }
63 | fmt.Printf("%#v", v)
64 | fmt.Println()
65 | return nil
66 | },
67 | }
68 | addShortFlag(cmd, &short)
69 | return cmd
70 | }
71 |
72 | // get returns build info
73 | func getBuildInfo() BuildInfo {
74 | v := BuildInfo{
75 | Version: base.Version,
76 | GitCommit: gitCommit,
77 | GoVersion: runtime.Version(),
78 | }
79 | return v
80 | }
81 |
82 | // addShortFlag adds the short flag to the version command
83 | func addShortFlag(cmd *cobra.Command, shortPtr *bool) {
84 | cmd.Flags().BoolVar(shortPtr, "short", false, "print abbreviated version info")
85 | cmd.Flags().Lookup("short").NoOptDefVal = "true"
86 | }
87 |
--------------------------------------------------------------------------------
/cmd/version_test.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestVersion(t *testing.T) {
8 | tests := []cmdTestCase{
9 | // version
10 | {
11 | name: "version",
12 | cmd: "version",
13 | },
14 | // version
15 | {
16 | name: "version short",
17 | cmd: "version --short",
18 | },
19 | }
20 |
21 | runTestActionCmd(t, tests)
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/config.yaml:
--------------------------------------------------------------------------------
1 | # Used by the helm/chart-releaser-action action in the releasecharts.yaml workflow
2 | skip-existing: true
--------------------------------------------------------------------------------
/controllers/config.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | util "github.com/iter8-tools/iter8/base"
5 | "k8s.io/apimachinery/pkg/runtime/schema"
6 | )
7 |
8 | const (
9 | // configEnv is the name of environment variable with config file path
10 | configEnv = "CONFIG_FILE"
11 | )
12 |
13 | // GroupVersionResourceConditions is a Kubernetes resource type along with a list of conditions
14 | type GroupVersionResourceConditions struct {
15 | schema.GroupVersionResource
16 | Conditions []string `json:"conditions,omitempty"`
17 | }
18 |
19 | // Config defines the configuration of the controllers
20 | type Config struct {
21 | // ResourceTypes map from shortnames of Kubernetes API resources to their GVRs with conditions
22 | ResourceTypes map[string]GroupVersionResourceConditions `json:"resourceTypes,omitempty"`
23 | // DefaultResync period for controller watch functions
24 | DefaultResync string `json:"defaultResync,omitempty"`
25 | // ClusterScoped is true if Iter8 controller is cluster-scoped
26 | ClusterScoped bool `json:"clusterScoped,omitempty"`
27 | }
28 |
29 | // readConfig reads configuration information from file
30 | func readConfig() (*Config, error) {
31 | conf := &Config{}
32 | err := util.ReadConfig(configEnv, conf, func() {})
33 | return conf, err
34 | }
35 |
36 | // validate the config
37 | // no-op for now
38 | func (c *Config) validate() error {
39 | return nil
40 | }
41 |
--------------------------------------------------------------------------------
/controllers/config_test.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "os"
5 | "testing"
6 |
7 | "github.com/iter8-tools/iter8/base"
8 | "github.com/stretchr/testify/assert"
9 | "k8s.io/apimachinery/pkg/runtime/schema"
10 | )
11 |
12 | func TestReadConfig(t *testing.T) {
13 | var tests = []struct {
14 | confEnv bool
15 | confFile string
16 | valid bool
17 | }{
18 | {true, base.CompletePath("../", "testdata/controllers/config.yaml"), true},
19 | {false, base.CompletePath("../", "testdata/controllers/config.yaml"), false},
20 | {true, base.CompletePath("../", "testdata/controllers/garb.age"), false},
21 | {true, base.CompletePath("../", "this/file/does/not/exist"), false},
22 | }
23 |
24 | for _, tt := range tests {
25 | _ = os.Unsetenv(configEnv)
26 | if tt.confEnv {
27 | _ = os.Setenv(configEnv, tt.confFile)
28 | }
29 |
30 | c, err := readConfig()
31 | if tt.valid {
32 | assert.NoError(t, err)
33 | assert.Equal(t, "15m", c.DefaultResync)
34 | assert.Equal(t, 5, len(c.ResourceTypes))
35 | isvc := c.ResourceTypes["isvc"]
36 | assert.Equal(t, isvc, GroupVersionResourceConditions{
37 | GroupVersionResource: schema.GroupVersionResource{
38 | Group: "serving.kserve.io",
39 | Version: "v1beta1",
40 | Resource: "inferenceservices",
41 | },
42 | Conditions: []string{
43 | "Ready",
44 | },
45 | })
46 | } else {
47 | assert.Error(t, err)
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/controllers/events.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "github.com/iter8-tools/iter8/controllers/k8sclient"
5 | corev1 "k8s.io/api/core/v1"
6 | "k8s.io/apimachinery/pkg/runtime"
7 | typedv1core "k8s.io/client-go/kubernetes/typed/core/v1"
8 | "k8s.io/client-go/tools/record"
9 | )
10 |
11 | // broadcastEvent broadcasts an event to the controller
12 | func broadcastEvent(object runtime.Object, eventtype, reason, message string, client k8sclient.Interface) {
13 | if object != nil {
14 | scheme := runtime.NewScheme()
15 | _ = corev1.AddToScheme(scheme)
16 |
17 | // TODO: Do we want to reuse the event broadcaster?
18 | eventBroadcaster := record.NewBroadcaster()
19 | eventBroadcaster.StartStructuredLogging(4)
20 | eventBroadcaster.StartRecordingToSink(&typedv1core.EventSinkImpl{Interface: client.CoreV1().Events("")})
21 | eventRecorder := eventBroadcaster.NewRecorder(scheme, corev1.EventSource{})
22 |
23 | eventRecorder.Event(object, eventtype, reason, message)
24 | eventBroadcaster.Shutdown()
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/controllers/interface.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | // RoutemapsInterface defines behavior for a set of routemaps
4 | type RoutemapsInterface interface {
5 | // GetRoutemapFromNamespaceName returns a route map with the given namespace and name
6 | GetRoutemapFromNamespaceName(string, string) RoutemapInterface
7 | }
8 |
9 | // RoutemapInterface defines behavior of a routemap
10 | type RoutemapInterface interface {
11 | // RLock locks the object for reading
12 | RLock()
13 | // RUnlock unlocks an object locked for reading
14 | RUnlock()
15 | // GetName returns the name of the object
16 | GetName() string
17 | // GetNamespace returns the namespace of the object
18 | GetNamespace() string
19 | // Weights provides the relative weights from traffic routing between versions
20 | Weights() []uint32
21 | // GetVersions returns a list of versions
22 | GetVersions() []VersionInterface
23 | }
24 |
25 | // VersionInterface defines behavior for a version
26 | type VersionInterface interface {
27 | // GetSignature returns a signature of a version
28 | GetSignature() *string
29 | }
30 |
31 | // AllRouteMapsInterface is interface defines way to get all routemaps
32 | type AllRouteMapsInterface interface {
33 | GetAllRoutemaps() RoutemapsInterface
34 | }
35 |
36 | // DefaultRoutemaps is default implementation
37 | type DefaultRoutemaps struct{}
38 |
39 | // GetAllRoutemaps is default implementation that returns package local map
40 | func (cm *DefaultRoutemaps) GetAllRoutemaps() RoutemapsInterface {
41 | return &AllRoutemaps
42 | }
43 |
--------------------------------------------------------------------------------
/controllers/interface_test.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestGetAllRoutemaps(t *testing.T) {
10 | rm := DefaultRoutemaps{}
11 | assert.NotNil(t, rm.GetAllRoutemaps())
12 | }
13 |
--------------------------------------------------------------------------------
/controllers/k8sclient/fake/simple.go:
--------------------------------------------------------------------------------
1 | // Package fake provides fake Kuberntes clients for testing
2 | package fake
3 |
4 | import (
5 | "context"
6 |
7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
9 | "k8s.io/apimachinery/pkg/runtime"
10 | "k8s.io/apimachinery/pkg/runtime/schema"
11 | "k8s.io/apimachinery/pkg/types"
12 | fakedynamic "k8s.io/client-go/dynamic/fake"
13 | fakek8s "k8s.io/client-go/kubernetes/fake"
14 | )
15 |
16 | // Client provides structured and dynamic fake clients
17 | type Client struct {
18 | *fakek8s.Clientset
19 | *fakedynamic.FakeDynamicClient
20 | }
21 |
22 | /*
23 | Patch applies a patch for a resource.
24 | Important: fake clients should not be used for server-side apply or strategic-merge patches.
25 | https://github.com/kubernetes/kubernetes/pull/78630#issuecomment-500424163
26 |
27 | Hence, we are mocking the Patch call in this fake client so that,
28 | instead of server-side apply as in the real client, we perform of merge patch instead.
29 | */
30 | func (cl *Client) Patch(gvr schema.GroupVersionResource, objNamespace string, objName string, jsonBytes []byte) (*unstructured.Unstructured, error) {
31 | return cl.FakeDynamicClient.Resource(gvr).Namespace(objNamespace).Patch(context.TODO(), objName, types.MergePatchType, jsonBytes, metav1.PatchOptions{})
32 | }
33 |
34 | // New returns a new fake Kubernetes client populated with runtime objects
35 | func New(sObjs []runtime.Object, unsObjs []runtime.Object) *Client {
36 | s := runtime.NewScheme()
37 | return &Client{
38 | fakek8s.NewSimpleClientset(sObjs...),
39 | fakedynamic.NewSimpleDynamicClientWithCustomListKinds(s, map[schema.GroupVersionResource]string{
40 | {
41 | Group: "apps",
42 | Version: "v1",
43 | Resource: "deployments",
44 | }: "DeploymentList",
45 | {
46 | Group: "",
47 | Version: "v1",
48 | Resource: "configmaps",
49 | }: "ConfigMapList",
50 | {
51 | Group: "networking.istio.io",
52 | Version: "v1beta1",
53 | Resource: "virtualservices",
54 | }: "VirtualServiceList",
55 | {
56 | Group: "",
57 | Version: "v1",
58 | Resource: "services",
59 | }: "ServiceList",
60 | {
61 | Group: "serving.kserve.io",
62 | Version: "v1beta1",
63 | Resource: "inferenceservices",
64 | }: "InferenceServiceList",
65 | {
66 | Group: "",
67 | Version: "v1",
68 | Resource: "secrets",
69 | }: "SecretList",
70 | }, unsObjs...),
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/controllers/k8sclient/fake/simple_test.go:
--------------------------------------------------------------------------------
1 | package fake
2 |
3 | import (
4 | "context"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
10 | "k8s.io/apimachinery/pkg/runtime"
11 | "k8s.io/apimachinery/pkg/runtime/schema"
12 | )
13 |
14 | const (
15 | myns = "myns"
16 | myname = "myname"
17 | myname2 = "myname2"
18 | hello = "hello"
19 | world = "world"
20 | )
21 |
22 | func TestNew(t *testing.T) {
23 | var tests = []struct {
24 | a []runtime.Object
25 | b bool
26 | }{
27 | {nil, true},
28 | {[]runtime.Object{
29 | &unstructured.Unstructured{
30 | Object: map[string]interface{}{
31 | "apiVersion": "iter8.tools",
32 | "kind": "v1",
33 | "metadata": map[string]interface{}{
34 | "namespace": myns,
35 | "name": myname,
36 | },
37 | },
38 | },
39 | }, true},
40 | }
41 |
42 | for _, e := range tests {
43 | client := New(nil, e.a)
44 | assert.NotNil(t, client)
45 | }
46 | }
47 |
48 | func TestPatch(t *testing.T) {
49 | gvr := schema.GroupVersionResource{
50 | Group: "apps",
51 | Version: "v1",
52 | Resource: "deployments",
53 | }
54 |
55 | client := New(nil, []runtime.Object{
56 | &unstructured.Unstructured{
57 | Object: map[string]interface{}{
58 | "apiVersion": "apps/v1",
59 | "kind": "Deployment",
60 | "metadata": map[string]interface{}{
61 | "namespace": myns,
62 | "name": myname,
63 | },
64 | },
65 | },
66 | })
67 |
68 | myDeployment, err := client.FakeDynamicClient.Resource(gvr).Namespace(myns).Get(context.TODO(), myname, v1.GetOptions{})
69 | assert.NoError(t, err)
70 | assert.NotNil(t, myDeployment)
71 | // myDeployment should not have the hello: world label yet
72 | assert.Equal(t, "", myDeployment.GetLabels()[hello])
73 |
74 | // Create a copy of myDeployment and add the hello: world label
75 | copiedDeployment := myDeployment.DeepCopy()
76 | copiedDeployment.SetLabels(map[string]string{
77 | hello: world,
78 | })
79 | newDeploymentBytes, err := copiedDeployment.MarshalJSON()
80 | assert.NoError(t, err)
81 | assert.NotNil(t, newDeploymentBytes)
82 |
83 | // Patch myDeployment
84 | patchedDeployment, err := client.Patch(gvr, myns, myname, newDeploymentBytes)
85 | assert.NoError(t, err)
86 | assert.NotNil(t, patchedDeployment)
87 | // Patched myDeployment should now have the hello: world label
88 | assert.Equal(t, world, patchedDeployment.GetLabels()[hello])
89 | }
90 |
--------------------------------------------------------------------------------
/controllers/k8sclient/interface.go:
--------------------------------------------------------------------------------
1 | // Package k8sclient provides the Kubernetes client for the controllers package
2 | package k8sclient
3 |
4 | import (
5 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
6 | "k8s.io/apimachinery/pkg/runtime/schema"
7 | "k8s.io/client-go/dynamic"
8 | "k8s.io/client-go/kubernetes"
9 | )
10 |
11 | // Interface enables interaction with a Kubernetes cluster
12 | // Can be mocked in unit tests with fake implementation
13 | type Interface interface {
14 | kubernetes.Interface
15 | dynamic.Interface
16 | Patch(gvr schema.GroupVersionResource, objNamespace string, objName string, by []byte) (*unstructured.Unstructured, error)
17 | }
18 |
--------------------------------------------------------------------------------
/controllers/k8sclient/simple.go:
--------------------------------------------------------------------------------
1 | package k8sclient
2 |
3 | import (
4 | "context"
5 | "errors"
6 |
7 | "github.com/iter8-tools/iter8/base"
8 | "github.com/iter8-tools/iter8/base/log"
9 | "helm.sh/helm/v3/pkg/cli"
10 |
11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
13 | "k8s.io/apimachinery/pkg/runtime/schema"
14 | "k8s.io/apimachinery/pkg/types"
15 |
16 | // Import to initialize client auth plugins.
17 | _ "k8s.io/client-go/plugin/pkg/client/auth"
18 |
19 | "k8s.io/client-go/dynamic"
20 | "k8s.io/client-go/kubernetes"
21 | )
22 |
23 | // Client provides typed and dynamic Kubernetes clients
24 | type Client struct {
25 | // typed Kubernetes client
26 | *kubernetes.Clientset
27 | // dynamic Kubernetes client
28 | *dynamic.DynamicClient
29 | }
30 |
31 | const iter8ControllerFieldManager = "iter8-controller"
32 |
33 | // Patch performs a server-side apply of GVR
34 | func (cl *Client) Patch(gvr schema.GroupVersionResource, objNamespace string, objName string, jsonBytes []byte) (*unstructured.Unstructured, error) {
35 | return cl.DynamicClient.Resource(gvr).Namespace(objNamespace).Patch(context.TODO(), objName, types.ApplyPatchType, jsonBytes, metav1.PatchOptions{
36 | FieldManager: iter8ControllerFieldManager,
37 | Force: base.BoolPointer(true),
38 | })
39 | }
40 |
41 | // New creates a new kubernetes client
42 | func New(settings *cli.EnvSettings) (*Client, error) {
43 | log.Logger.Trace("kubernetes client creation invoked...")
44 |
45 | // get rest config
46 | restConfig, err := settings.RESTClientGetter().ToRESTConfig()
47 | if err != nil {
48 | e := errors.New("unable to get Kubernetes REST config")
49 | log.Logger.WithStackTrace(err.Error()).Error(e)
50 | return nil, e
51 | }
52 |
53 | // get clientset
54 | clientset, err := kubernetes.NewForConfig(restConfig)
55 | if err != nil {
56 | e := errors.New("unable to get Kubernetes clientset")
57 | log.Logger.WithStackTrace(err.Error()).Error(e)
58 | return nil, e
59 | }
60 |
61 | // get dynamic client
62 | dynamicClient, err := dynamic.NewForConfig(restConfig)
63 | if err != nil {
64 | e := errors.New("unable to get Kubernetes dynamic client")
65 | log.Logger.WithStackTrace(err.Error()).Error(e)
66 | return nil, e
67 | }
68 |
69 | log.Logger.Trace("returning kubernetes client... ")
70 |
71 | return &Client{
72 | Clientset: clientset,
73 | DynamicClient: dynamicClient,
74 | }, nil
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/controllers/podname.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "errors"
5 | "os"
6 | "strings"
7 |
8 | "github.com/iter8-tools/iter8/base/log"
9 | )
10 |
11 | const (
12 | // podNameEnvVariable is the name of the environment variable with pod name
13 | podNameEnvVariable = "POD_NAME"
14 | // podNamespaceEnvVariable is the name of the environment variable with pod namespace
15 | podNamespaceEnvVariable = "POD_NAMESPACE"
16 | // leaderSuffix is used to determine the leader pod
17 | leaderSuffix = "-0"
18 | )
19 |
20 | // getPodName returns the name of this pod
21 | func getPodName() (string, bool) {
22 | podName, ok := os.LookupEnv(podNameEnvVariable)
23 | // missing env variable is unacceptable
24 | if !ok {
25 | return "", false
26 | }
27 | // empty podName is unacceptable
28 | if len(podName) == 0 {
29 | return "", false
30 | }
31 | return podName, true
32 | }
33 |
34 | // leaderIsMe is true if this pod has the leaderSuffix ("-0")
35 | func leaderIsMe() (bool, error) {
36 | podName, ok := getPodName()
37 | if !ok {
38 | e := errors.New("unable to retrieve pod name")
39 | log.Logger.Error(e)
40 | return false, e
41 | }
42 | return strings.HasSuffix(podName, leaderSuffix), nil
43 | }
44 |
--------------------------------------------------------------------------------
/controllers/podname_test.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "os"
5 | "testing"
6 |
7 | util "github.com/iter8-tools/iter8/base"
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestGetPodName(t *testing.T) {
12 | var tests = []struct {
13 | a *string
14 | b string
15 | c bool
16 | }{
17 | {util.StringPointer("x-0"), "x-0", true},
18 | {util.StringPointer("x-y-0"), "x-y-0", true},
19 | {util.StringPointer("x-1"), "x-1", true},
20 | {util.StringPointer("x-y-1"), "x-y-1", true},
21 | {util.StringPointer("x"), "x", true},
22 | {util.StringPointer(""), "", false},
23 | {nil, "", false},
24 | }
25 |
26 | for _, e := range tests {
27 | if e.a == nil {
28 | _ = os.Unsetenv(podNameEnvVariable)
29 | } else {
30 | _ = os.Setenv(podNameEnvVariable, *e.a)
31 | }
32 | podName, ok := getPodName()
33 | assert.Equal(t, e.b, podName)
34 | assert.Equal(t, e.c, ok)
35 | }
36 |
37 | }
38 |
39 | func TestLeaderIsMe(t *testing.T) {
40 | var tests = []struct {
41 | a string
42 | b bool
43 | c bool
44 | }{
45 | {"x-0", true, false},
46 | {"x-y-0", true, false},
47 | {"x-1", false, false},
48 | {"x-y-1", false, false},
49 | {"x", false, false},
50 | {"", false, true},
51 | }
52 |
53 | for _, e := range tests {
54 | _ = os.Setenv(podNameEnvVariable, e.a)
55 | leaderStatus, err := leaderIsMe()
56 | assert.Equal(t, e.b, leaderStatus)
57 | if e.c {
58 | assert.Error(t, err)
59 | }
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/controllers/routemaps_test.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "sync"
5 | "testing"
6 |
7 | "github.com/iter8-tools/iter8/base"
8 | "github.com/stretchr/testify/assert"
9 | corev1 "k8s.io/api/core/v1"
10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11 | )
12 |
13 | func TestRouteMaps_Delete(t *testing.T) {
14 | s := routemaps{
15 | mutex: sync.RWMutex{},
16 | nsRoutemap: map[string]routemapsByName{
17 | "default": {
18 | "test": {
19 | mutex: sync.RWMutex{},
20 | ObjectMeta: metav1.ObjectMeta{},
21 | Versions: []version{},
22 | RoutingTemplates: map[string]routingTemplate{},
23 | normalizedWeights: []uint32{},
24 | },
25 | },
26 | },
27 | }
28 | s.delete(&corev1.ConfigMap{
29 | TypeMeta: metav1.TypeMeta{},
30 | ObjectMeta: metav1.ObjectMeta{
31 | Name: "test",
32 | Namespace: "default",
33 | },
34 | Immutable: base.BoolPointer(true),
35 | })
36 | obj, ok := s.nsRoutemap["default"]
37 | assert.False(t, ok)
38 | assert.Nil(t, obj)
39 | }
40 |
--------------------------------------------------------------------------------
/doc.go:
--------------------------------------------------------------------------------
1 | // Package main is the entry point for the Iter8 CLI.
2 | // Iter8 is the Kubernetes release optimizer built for DevOps, MLOps, SRE and data science teams. Iter8 makes it easy to ensure that Kubernetes apps and ML models perform well and maximize business value.
3 | package main
4 |
--------------------------------------------------------------------------------
/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.22.1-bookworm AS build-stage
2 |
3 | WORKDIR /app
4 | COPY . ./
5 |
6 | RUN go mod download
7 |
8 | RUN mkdir -p bin \
9 | && make clean \
10 | && make build
11 |
12 |
13 | FROM debian:bookworm-slim
14 |
15 | WORKDIR /
16 |
17 | # Install curl
18 | RUN apt-get update && apt-get install -y curl
19 |
20 | # Install /bin/iter8
21 | COPY --from=build-stage /app/bin/iter8 /bin/iter8
22 |
23 | # Set Iter8 version from build args
24 | ARG TAG
25 | ENV TAG=${TAG:-v1.1.0}
26 |
27 |
--------------------------------------------------------------------------------
/driver/common.go:
--------------------------------------------------------------------------------
1 | package driver
2 |
3 | import (
4 | "github.com/iter8-tools/iter8/base"
5 | "github.com/iter8-tools/iter8/base/log"
6 | "sigs.k8s.io/yaml"
7 | )
8 |
9 | const (
10 | // DefaultTestName is the default name of the performance test
11 | DefaultTestName = "default"
12 | )
13 |
14 | // ExperimentFromBytes reads experiment from bytes
15 | func ExperimentFromBytes(b []byte) (*base.Experiment, error) {
16 | e := base.Experiment{}
17 | err := yaml.Unmarshal(b, &e)
18 | if err != nil {
19 | log.Logger.WithStackTrace(err.Error()).Error("unable to unmarshal experiment: ", string(b))
20 | return nil, err
21 | }
22 | return &e, err
23 | }
24 |
--------------------------------------------------------------------------------
/driver/common_test.go:
--------------------------------------------------------------------------------
1 | package driver
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/iter8-tools/iter8/base"
7 | "github.com/stretchr/testify/assert"
8 | "sigs.k8s.io/yaml"
9 | )
10 |
11 | func TestExperimentFromBytes(t *testing.T) {
12 | experiment := base.Experiment{}
13 | experimentBytes, err := yaml.Marshal(experiment)
14 | assert.NoError(t, err)
15 | assert.NotNil(t, experimentBytes)
16 |
17 | // Experiment from marshalled experiment
18 | experiment2, err := ExperimentFromBytes(experimentBytes)
19 | assert.NoError(t, err)
20 | assert.NotNil(t, experiment2)
21 |
22 | // Experiment from random bytes
23 | experiment3, err := ExperimentFromBytes([]byte{1, 2, 3})
24 | assert.Error(t, err)
25 | assert.Nil(t, experiment3)
26 | }
27 |
--------------------------------------------------------------------------------
/driver/doc.go:
--------------------------------------------------------------------------------
1 | // Package driver enables interaction with experiment resources.
2 | // It provides drivers for local and Kubernetes experiments.
3 | package driver
4 |
--------------------------------------------------------------------------------
/driver/kubedriver_test.go:
--------------------------------------------------------------------------------
1 | package driver
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "io"
8 | "net/http"
9 | "os"
10 | "testing"
11 |
12 | "fortio.org/fortio/fhttp"
13 | "github.com/iter8-tools/iter8/base"
14 | "github.com/stretchr/testify/assert"
15 | "helm.sh/helm/v3/pkg/cli"
16 | corev1 "k8s.io/api/core/v1"
17 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
18 | )
19 |
20 | const (
21 | myName = "myName"
22 | myNamespace = "myNamespace"
23 | )
24 |
25 | func TestKubeRun(t *testing.T) {
26 | // define METRICS_SERVER_URL
27 | metricsServerURL := "http://iter8.default:8080"
28 | err := os.Setenv(base.MetricsServerURL, metricsServerURL)
29 | assert.NoError(t, err)
30 |
31 | // create and configure HTTP endpoint for testing
32 | mux, addr := fhttp.DynamicHTTPServer(false)
33 | url := fmt.Sprintf("http://127.0.0.1:%d/get", addr.Port)
34 | var verifyHandlerCalled bool
35 | mux.HandleFunc("/get", base.GetTrackingHandler(&verifyHandlerCalled))
36 |
37 | // mock metrics server
38 | base.StartHTTPMock(t)
39 | metricsServerCalled := false
40 | base.MockMetricsServer(base.MockMetricsServerInput{
41 | MetricsServerURL: metricsServerURL,
42 | ExperimentResultCallback: func(req *http.Request) {
43 | metricsServerCalled = true
44 |
45 | // check query parameters
46 | assert.Equal(t, myName, req.URL.Query().Get("test"))
47 | assert.Equal(t, myNamespace, req.URL.Query().Get("namespace"))
48 |
49 | // check payload
50 | body, err := io.ReadAll(req.Body)
51 | assert.NoError(t, err)
52 | assert.NotNil(t, body)
53 |
54 | // check payload content
55 | bodyExperimentResult := base.ExperimentResult{}
56 | err = json.Unmarshal(body, &bodyExperimentResult)
57 | assert.NoError(t, err)
58 | assert.NotNil(t, body)
59 |
60 | // no experiment failure
61 | assert.False(t, bodyExperimentResult.Failure)
62 | },
63 | })
64 |
65 | _ = os.Chdir(t.TempDir())
66 |
67 | // create experiment.yaml
68 | base.CreateExperimentYaml(t, base.CompletePath("../testdata/drivertests", "experiment.tpl"), url, base.ExperimentFile)
69 |
70 | kd := NewFakeKubeDriver(cli.New())
71 | kd.revision = 1
72 |
73 | byteArray, _ := os.ReadFile(base.ExperimentFile)
74 | _, _ = kd.Clientset.CoreV1().Secrets("default").Create(context.TODO(), &corev1.Secret{
75 | ObjectMeta: metav1.ObjectMeta{
76 | Name: "default",
77 | Namespace: "default",
78 | },
79 | StringData: map[string]string{base.ExperimentFile: string(byteArray)},
80 | }, metav1.CreateOptions{})
81 |
82 | err = base.RunExperiment(kd)
83 | assert.NoError(t, err)
84 | // sanity check -- handler was called
85 | assert.True(t, verifyHandlerCalled)
86 | assert.True(t, metricsServerCalled)
87 | }
88 |
--------------------------------------------------------------------------------
/driver/test_helpers.go:
--------------------------------------------------------------------------------
1 | package driver
2 |
3 | import (
4 | "io"
5 |
6 | "github.com/iter8-tools/iter8/base/log"
7 | "helm.sh/helm/v3/pkg/action"
8 | "helm.sh/helm/v3/pkg/chartutil"
9 | "helm.sh/helm/v3/pkg/cli"
10 | helmfake "helm.sh/helm/v3/pkg/kube/fake"
11 | "helm.sh/helm/v3/pkg/registry"
12 | "helm.sh/helm/v3/pkg/storage"
13 | helmdriver "helm.sh/helm/v3/pkg/storage/driver"
14 | corev1 "k8s.io/api/core/v1"
15 | "k8s.io/apimachinery/pkg/runtime"
16 | "k8s.io/client-go/kubernetes/fake"
17 | ktesting "k8s.io/client-go/testing"
18 | )
19 |
20 | // initKubeFake initialize the Kube clientset with a fake
21 | func initKubeFake(kd *KubeDriver, objects ...runtime.Object) {
22 | // secretDataReactor sets the secret.Data field based on the values from secret.StringData
23 | // Credit: this function is adapted from https://github.com/creydr/go-k8s-utils
24 | var secretDataReactor = func(action ktesting.Action) (bool, runtime.Object, error) {
25 | secret, _ := action.(ktesting.CreateAction).GetObject().(*corev1.Secret)
26 |
27 | if secret.Data == nil {
28 | secret.Data = make(map[string][]byte)
29 | }
30 |
31 | for k, v := range secret.StringData {
32 | secret.Data[k] = []byte(v)
33 | }
34 |
35 | return false, nil, nil
36 | }
37 |
38 | fc := fake.NewSimpleClientset(objects...)
39 | fc.PrependReactor("create", "secrets", secretDataReactor)
40 | fc.PrependReactor("update", "secrets", secretDataReactor)
41 | kd.Clientset = fc
42 | }
43 |
44 | // initHelmFake initializes the Helm config with a fake
45 | // Credit: this function is adapted from helm
46 | // https://github.com/helm/helm/blob/e9abdc5efe11cdc23576c20c97011d452201cd92/pkg/action/action_test.go#L37
47 | func initHelmFake(kd *KubeDriver) {
48 | registryClient, err := registry.NewClient()
49 | if err != nil {
50 | log.Logger.Error(err)
51 | return
52 | }
53 |
54 | kd.Configuration = &action.Configuration{
55 | Releases: storage.Init(helmdriver.NewMemory()),
56 | KubeClient: &helmfake.FailingKubeClient{PrintingKubeClient: helmfake.PrintingKubeClient{Out: io.Discard}},
57 | Capabilities: chartutil.DefaultCapabilities,
58 | RegistryClient: registryClient,
59 | Log: log.Logger.Debugf,
60 | }
61 | }
62 |
63 | // NewFakeKubeDriver creates and returns a new KubeDriver with fake clients
64 | func NewFakeKubeDriver(s *cli.EnvSettings, objects ...runtime.Object) *KubeDriver {
65 | kd := &KubeDriver{
66 | EnvSettings: s,
67 | Test: DefaultTestName,
68 | }
69 | initKubeFake(kd, objects...)
70 | initHelmFake(kd)
71 | return kd
72 | }
73 |
--------------------------------------------------------------------------------
/kustomize/controller/clusterScoped/kustomization.yaml:
--------------------------------------------------------------------------------
1 | bases:
2 | - ../namespaceScoped
3 |
4 | namespace: default
5 |
6 | patches:
7 | - patch: |-
8 | - op: replace
9 | path: /kind
10 | value: ClusterRole
11 | target:
12 | kind: Role
13 |
14 | # Order matters
15 | # /roleRef/kind patch should happen before /kind patch
16 | - patch: |-
17 | - op: replace
18 | path: /roleRef/kind
19 | value: ClusterRole
20 | target:
21 | kind: RoleBinding
22 | - patch: |-
23 | - op: replace
24 | path: /kind
25 | value: ClusterRoleBinding
26 | target:
27 | kind: RoleBinding
28 |
29 | - patch: |-
30 | - op: replace
31 | path: /data/config.yaml
32 | value: |
33 | clusterScoped: true
34 | defaultResync: 15m
35 | image: iter8/iter8:1.1
36 | logLevel: info
37 | resourceTypes:
38 | cm:
39 | Group: ""
40 | Resource: configmaps
41 | Version: v1
42 | deploy:
43 | Group: apps
44 | Resource: deployments
45 | Version: v1
46 | conditions:
47 | - Available
48 | isvc:
49 | Group: serving.kserve.io
50 | Resource: inferenceservices
51 | Version: v1beta1
52 | conditions:
53 | - Ready
54 | svc:
55 | Group: ""
56 | Resource: services
57 | Version: v1
58 | service:
59 | Group: ""
60 | Resource: services
61 | Version: v1
62 | vs:
63 | Group: networking.istio.io
64 | Resource: virtualservices
65 | Version: v1beta1
66 | resources:
67 | limits:
68 | cpu: 500m
69 | memory: 128Mi
70 | requests:
71 | cpu: 250m
72 | memory: 64Mi
73 | storage: 50Mi
74 | storageClassName: standard
75 | metrics.yaml: |
76 | port: 8080
77 | abn.yaml: |
78 | port: 50051
79 | target:
80 | kind: ConfigMap
81 | name: iter8
--------------------------------------------------------------------------------
/kustomize/controller/namespaceScoped/configmap.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: iter8
5 | data:
6 | config.yaml: |
7 | defaultResync: 15m
8 | image: iter8/iter8:1.1
9 | logLevel: info
10 | resourceTypes:
11 | cm:
12 | Group: ""
13 | Resource: configmaps
14 | Version: v1
15 | deploy:
16 | Group: apps
17 | Resource: deployments
18 | Version: v1
19 | conditions:
20 | - Available
21 | isvc:
22 | Group: serving.kserve.io
23 | Resource: inferenceservices
24 | Version: v1beta1
25 | conditions:
26 | - Ready
27 | svc:
28 | Group: ""
29 | Resource: services
30 | Version: v1
31 | service:
32 | Group: ""
33 | Resource: services
34 | Version: v1
35 | vs:
36 | Group: networking.istio.io
37 | Resource: virtualservices
38 | Version: v1beta1
39 | resources:
40 | limits:
41 | cpu: 500m
42 | memory: 128Mi
43 | requests:
44 | cpu: 250m
45 | memory: 64Mi
46 | storage: 50Mi
47 | storageClassName: standard
48 | metrics.yaml: |
49 | port: 8080
50 | abn.yaml: |
51 | port: 50051
--------------------------------------------------------------------------------
/kustomize/controller/namespaceScoped/kustomization.yaml:
--------------------------------------------------------------------------------
1 | resources:
2 | - configmap.yaml
3 | - service.yaml
4 | - pvc.yaml
5 | - role.yaml
6 | - rolebinding.yaml
7 | - serviceaccount.yaml
8 | - statefulset.yaml
9 |
10 | commonLabels:
11 | app.kubernetes.io/name: controller
12 | app.kubernetes.io/version: v1.1
--------------------------------------------------------------------------------
/kustomize/controller/namespaceScoped/pvc.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: PersistentVolumeClaim
3 | metadata:
4 | name: iter8
5 | spec:
6 | accessModes:
7 | - ReadWriteOnce
8 | resources:
9 | requests:
10 | storage: 50Mi
11 | storageClassName: standard
12 |
--------------------------------------------------------------------------------
/kustomize/controller/namespaceScoped/role.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: Role
3 | metadata:
4 | name: iter8
5 | rules:
6 | - apiGroups: [""]
7 | resources: ["configmaps"]
8 | verbs: ["get", "list", "watch", "patch", "update"]
9 | - apiGroups: ["apps"]
10 | resources: ["deployments"]
11 | verbs: ["get", "list", "watch", "patch", "update"]
12 | - apiGroups: ["serving.kserve.io"]
13 | resources: ["inferenceservices"]
14 | verbs: ["get", "list", "watch", "patch", "update"]
15 | - apiGroups: [""]
16 | resources: ["services"]
17 | verbs: ["get", "list", "watch", "patch", "update"]
18 | - apiGroups: ["networking.istio.io"]
19 | resources: ["virtualservices"]
20 | verbs: ["get", "list", "watch", "patch", "update"]
21 | - apiGroups: [""]
22 | resources: ["events"]
23 | verbs: ["get", "create"]
24 | - apiGroups: [""]
25 | resources: ["pods"]
26 | verbs: ["get"]
--------------------------------------------------------------------------------
/kustomize/controller/namespaceScoped/rolebinding.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: RoleBinding
3 | metadata:
4 | name: iter8
5 | subjects:
6 | - kind: ServiceAccount
7 | name: iter8
8 | roleRef:
9 | kind: Role
10 | name: iter8
11 | apiGroup: rbac.authorization.k8s.io
--------------------------------------------------------------------------------
/kustomize/controller/namespaceScoped/service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: iter8
5 | spec:
6 | clusterIP: None
7 | selector:
8 | app.kubernetes.io/name: controller
9 | ports:
10 | - name: grpc
11 | port: 50051
12 | - name: http
13 | port: 8080
14 | targetPort: 8080
--------------------------------------------------------------------------------
/kustomize/controller/namespaceScoped/serviceaccount.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ServiceAccount
3 | metadata:
4 | name: iter8
--------------------------------------------------------------------------------
/kustomize/controller/namespaceScoped/statefulset.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: StatefulSet
3 | metadata:
4 | name: iter8
5 | spec:
6 | serviceName: iter8
7 | selector:
8 | matchLabels:
9 | app.kubernetes.io/name: controller
10 | template:
11 | metadata:
12 | labels:
13 | app.kubernetes.io/name: controller
14 | spec:
15 | terminationGracePeriodSeconds: 10
16 | serviceAccountName: iter8
17 | containers:
18 | - name: iter8-controller
19 | image: iter8/iter8:1.1
20 | imagePullPolicy: Always
21 | command: ["/bin/iter8"]
22 | args: ["controllers", "-l", "info"]
23 | env:
24 | - name: CONFIG_FILE
25 | value: /config/config.yaml
26 | - name: METRICS_CONFIG_FILE
27 | value: /config/metrics.yaml
28 | - name: ABN_CONFIG_FILE
29 | value: /config/abn.yaml
30 | - name: METRICS_DIR
31 | value: /metrics
32 | - name: POD_NAME
33 | valueFrom:
34 | fieldRef:
35 | fieldPath: metadata.name
36 | - name: POD_NAMESPACE
37 | valueFrom:
38 | fieldRef:
39 | fieldPath: metadata.namespace
40 | volumeMounts:
41 | - name: config
42 | mountPath: "/config"
43 | readOnly: true
44 | - name: metrics
45 | mountPath: "/metrics"
46 | resources:
47 | limits:
48 | cpu: 500m
49 | memory: 128Mi
50 | requests:
51 | cpu: 250m
52 | memory: 64Mi
53 | securityContext:
54 | readOnlyRootFilesystem: true
55 | allowPrivilegeEscalation: false
56 | capabilities:
57 | drop:
58 | - ALL
59 | runAsNonRoot: true
60 | runAsUser: 1001040000
61 | volumes:
62 | - name: config
63 | configMap:
64 | name: iter8
65 | - name: metrics
66 | persistentVolumeClaim:
67 | claimName: iter8
68 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/iter8-tools/iter8/cmd"
5 | _ "k8s.io/client-go/plugin/pkg/client/auth"
6 | )
7 |
8 | func main() {
9 | cmd.Execute()
10 | }
11 |
--------------------------------------------------------------------------------
/metrics/doc.go:
--------------------------------------------------------------------------------
1 | // Package metrics implements an HTTP service that exposes A/B/n SDK metrics
2 | package metrics
3 |
--------------------------------------------------------------------------------
/metrics/test_helpers.go:
--------------------------------------------------------------------------------
1 | package metrics
2 |
3 | import "github.com/iter8-tools/iter8/controllers"
4 |
5 | type testroutemapsByName map[string]*testroutemap
6 | type testroutemaps struct {
7 | nsRoutemap map[string]testroutemapsByName
8 | }
9 |
10 | func (s *testroutemaps) GetRoutemapFromNamespaceName(namespace string, name string) controllers.RoutemapInterface {
11 | rmByName, ok := s.nsRoutemap[namespace]
12 | if ok {
13 | return rmByName[name]
14 | }
15 | return nil
16 | }
17 |
18 | type testversion struct {
19 | signature *string
20 | }
21 |
22 | func (v *testversion) GetSignature() *string {
23 | return v.signature
24 | }
25 |
26 | type testroutemap struct {
27 | name string
28 | namespace string
29 | versions []testversion
30 | normalizedWeights []uint32
31 | }
32 |
33 | func (s *testroutemap) RLock() {}
34 |
35 | func (s *testroutemap) RUnlock() {}
36 |
37 | func (s *testroutemap) GetNamespace() string {
38 | return s.namespace
39 | }
40 |
41 | func (s *testroutemap) GetName() string {
42 | return s.name
43 | }
44 |
45 | func (s *testroutemap) Weights() []uint32 {
46 | return s.normalizedWeights
47 | }
48 |
49 | func (s *testroutemap) GetVersions() []controllers.VersionInterface {
50 | result := make([]controllers.VersionInterface, len(s.versions))
51 | for i := range s.versions {
52 | v := s.versions[i]
53 | result[i] = controllers.VersionInterface(&v)
54 | }
55 | return result
56 | }
57 |
--------------------------------------------------------------------------------
/storage/client/client.go:
--------------------------------------------------------------------------------
1 | // Package client implements an implementation independent storage client
2 | package client
3 |
4 | import (
5 | "fmt"
6 | "strings"
7 |
8 | "github.com/dgraph-io/badger/v4"
9 | util "github.com/iter8-tools/iter8/base"
10 | "github.com/iter8-tools/iter8/storage"
11 | "github.com/iter8-tools/iter8/storage/badgerdb"
12 | "github.com/iter8-tools/iter8/storage/redis"
13 | )
14 |
15 | const (
16 | metricsConfigFileEnv = "METRICS_CONFIG_FILE"
17 | defaultImplementation = "badgerdb"
18 | )
19 |
20 | var (
21 | // MetricsClient is storage client
22 | MetricsClient storage.Interface
23 | )
24 |
25 | // metricsStorageConfig is configuration of metrics service
26 | type metricsStorageConfig struct {
27 | // Implementation method for metrics service
28 | Implementation *string `json:"implementation,omitempty"`
29 | }
30 |
31 | // GetClient creates a metric service client based on configuration
32 | func GetClient() (storage.Interface, error) {
33 | conf := &metricsStorageConfig{}
34 | err := util.ReadConfig(metricsConfigFileEnv, conf, func() {
35 | if conf.Implementation == nil {
36 | conf.Implementation = util.StringPointer(defaultImplementation)
37 | }
38 | })
39 | if err != nil {
40 | return nil, err
41 | }
42 |
43 | switch strings.ToLower(*conf.Implementation) {
44 | case "badgerdb":
45 | // badgerConfig defines the configuration of a badgerDB based metrics service
46 | type mConfig struct {
47 | badgerdb.ClientConfig `json:"badgerdb,omitempty"`
48 | }
49 |
50 | conf := &mConfig{}
51 | err := util.ReadConfig(metricsConfigFileEnv, conf, func() {
52 | if conf.ClientConfig.Storage == nil {
53 | conf.ClientConfig.Storage = util.StringPointer("50Mi")
54 | }
55 | if conf.ClientConfig.StorageClassName == nil {
56 | conf.ClientConfig.StorageClassName = util.StringPointer("standard")
57 | }
58 | if conf.ClientConfig.Dir == nil {
59 | conf.ClientConfig.Dir = util.StringPointer("/metrics")
60 | }
61 | })
62 | if err != nil {
63 | return nil, err
64 | }
65 |
66 | cl, err := badgerdb.GetClient(badger.DefaultOptions(*conf.ClientConfig.Dir), badgerdb.AdditionalOptions{})
67 | if err != nil {
68 | return nil, err
69 | }
70 | return cl, nil
71 |
72 | case "redis":
73 | // redisConfig defines the configuration of a redis based metrics service
74 | type mConfig struct {
75 | redis.ClientConfig `json:"redis,omitempty"`
76 | }
77 |
78 | conf := &mConfig{}
79 | err := util.ReadConfig(metricsConfigFileEnv, conf, func() {
80 | if conf.ClientConfig.Address == nil {
81 | conf.ClientConfig.Address = util.StringPointer("redis:6379")
82 | }
83 | })
84 | if err != nil {
85 | return nil, err
86 | }
87 |
88 | cl, err := redis.GetClient(conf.ClientConfig)
89 | if err != nil {
90 | return nil, err
91 | }
92 | return cl, nil
93 |
94 | default:
95 | return nil, fmt.Errorf("no metrics store implementation for %s", *conf.Implementation)
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/storage/client/client_test.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "os"
5 | "testing"
6 |
7 | "github.com/alicebob/miniredis"
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestGetClientRedis(t *testing.T) {
12 |
13 | server, _ := miniredis.Run()
14 | assert.NotNil(t, server)
15 |
16 | metricsConfig := `port: 8080
17 | implementation: redis
18 | redis:
19 | address: ` + server.Addr()
20 |
21 | mf, err := os.CreateTemp("", "metrics*.yaml")
22 | assert.NoError(t, err)
23 |
24 | err = os.Setenv(metricsConfigFileEnv, mf.Name())
25 | assert.NoError(t, err)
26 |
27 | _, err = mf.WriteString(metricsConfig)
28 | assert.NoError(t, err)
29 |
30 | client, err := GetClient()
31 | assert.NoError(t, err)
32 | assert.NotNil(t, client)
33 | }
34 |
35 | func TestGetClientBadger(t *testing.T) {
36 |
37 | tempDirPath := os.TempDir()
38 |
39 | metricsConfig := `port: 8080
40 | implementation: badgerdb
41 | badgerdb:
42 | dir: ` + tempDirPath
43 |
44 | mf, err := os.CreateTemp("", "metrics*.yaml")
45 | assert.NoError(t, err)
46 |
47 | err = os.Setenv(metricsConfigFileEnv, mf.Name())
48 | assert.NoError(t, err)
49 |
50 | _, err = mf.WriteString(metricsConfig)
51 | assert.NoError(t, err)
52 |
53 | client, err := GetClient()
54 | assert.NoError(t, err)
55 | assert.NotNil(t, client)
56 | }
57 |
--------------------------------------------------------------------------------
/storage/interface.go:
--------------------------------------------------------------------------------
1 | // Package storage provides the storage client for the controllers package
2 | package storage
3 |
4 | import "github.com/iter8-tools/iter8/base"
5 |
6 | // SummarizedMetric is a metric summary
7 | type SummarizedMetric struct {
8 | Count uint64
9 | Mean float64
10 | StdDev float64
11 | Min float64
12 | Max float64
13 | }
14 |
15 | // MetricSummary contains metric summary for all metrics as well as cumulative metrics per user
16 | type MetricSummary struct {
17 | // all transactions
18 | SummaryOverTransactions SummarizedMetric
19 |
20 | // cumulative metrics per user
21 | SummaryOverUsers SummarizedMetric
22 | }
23 |
24 | // VersionMetricSummary is a metric summary for a given app version
25 | type VersionMetricSummary struct {
26 | NumUsers uint64
27 |
28 | // key = metric name; value is the metric summary
29 | MetricSummaries map[string]MetricSummary
30 | }
31 |
32 | // VersionMetrics contains all the metrics over transactions and over users
33 | // key = metric name
34 | type VersionMetrics map[string]struct {
35 | MetricsOverTransactions []float64
36 | MetricsOverUsers []float64
37 | }
38 |
39 | // Interface enables interaction with a storage entity
40 | // Can be mocked in unit tests with fake implementation
41 | type Interface interface {
42 | // GetMerics returns all metrics for an app/version
43 | // Returned result is a nested map of the metrics data
44 | // Example:
45 | // {
46 | // "my-metric": {
47 | // "MetricsOverTransactions": [1, 1, 3, 4, 5]
48 | // "MetricsOverUsers": [2, 7, 5]
49 | // }
50 | // }
51 | //
52 | // NOTE: for users that have not produced any metrics (for example, via lookup()), GetMetrics() will add 0s for the extra users in metricsOverUsers
53 | // Example, given 5 total users:
54 | //
55 | // {
56 | // "my-metric": {
57 | // "MetricsOverTransactions": [1, 1, 3, 4, 5]
58 | // "MetricsOverUsers": [2, 7, 5, 0, 0]
59 | // }
60 | // }
61 | GetMetrics(applicationName string, version int, signature string) (*VersionMetrics, error)
62 |
63 | // SetMetric records a metric value
64 | // Called by the A/B/n SDK gRPC API implementation (SDK for application clients)
65 | // Example key: kt-metric::my-app::0::my-signature::my-metric::my-user::my-transaction-id -> my-metric-value (get the metric value with all the provided information)
66 | SetMetric(applicationName string, version int, signature, metric, user, transaction string, metricValue float64) error
67 |
68 | // SetUser records the name of user
69 | // Example key: kt-users::my-app::0::my-signature::my-user -> true
70 | SetUser(applicationName string, version int, signature, user string) error
71 |
72 | // GetExperimentResult returns the experiment result for a particular namespace and experiment
73 | GetExperimentResult(namespace, experiment string) (*base.ExperimentResult, error)
74 |
75 | // SetExperimentResult records an expeirment result
76 | // called by the A/B/n SDK gRPC API implementation (SDK for application clients)
77 | // Example key: kt-metric::my-app::0::my-signature::my-metric::my-user::my-transaction-id -> my-metric-value (get the metric value with all the provided information)
78 | SetExperimentResult(namespace, experiment string, data *base.ExperimentResult) error
79 | }
80 |
--------------------------------------------------------------------------------
/storage/util.go:
--------------------------------------------------------------------------------
1 | package storage
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "strings"
8 |
9 | "github.com/iter8-tools/iter8/base"
10 | "golang.org/x/sys/unix"
11 | )
12 |
13 | // GetVolumeUsage gets the available and total capacity of a volume, in that order
14 | func GetVolumeUsage(path string) (uint64, uint64, error) {
15 | var stat unix.Statfs_t
16 | err := unix.Statfs(path, &stat)
17 | if err != nil {
18 | return 0, 0, err
19 | }
20 |
21 | // Available blocks * size per block = available space in bytes
22 | availableBytes := stat.Bavail * uint64(stat.Bsize)
23 | // Total blocks * size per block = available space in bytes
24 | totalBytes := stat.Blocks * uint64(stat.Bsize)
25 |
26 | return availableBytes, totalBytes, nil
27 | }
28 |
29 | func validateKeyToken(s string) error {
30 | if strings.Contains(s, ":") {
31 | return errors.New("key token contains \":\"")
32 | }
33 |
34 | return nil
35 | }
36 |
37 | // GetMetricKeyPrefix returns the prefix of a metric key
38 | func GetMetricKeyPrefix(applicationName string, version int, signature string) string {
39 | return fmt.Sprintf("kt-metric::%s::%d::%s::", applicationName, version, signature)
40 | }
41 |
42 | // GetMetricKey returns a metric key from the inputs
43 | func GetMetricKey(applicationName string, version int, signature, metric, user, transaction string) (string, error) {
44 | if err := validateKeyToken(applicationName); err != nil {
45 | return "", errors.New("application name cannot have \":\"")
46 | }
47 | if err := validateKeyToken(signature); err != nil {
48 | return "", errors.New("signature cannot have \":\"")
49 | }
50 | if err := validateKeyToken(metric); err != nil {
51 | return "", errors.New("metric name cannot have \":\"")
52 | }
53 | if err := validateKeyToken(user); err != nil {
54 | return "", errors.New("user name cannot have \":\"")
55 | }
56 | if err := validateKeyToken(transaction); err != nil {
57 | return "", errors.New("transaction ID cannot have \":\"")
58 | }
59 |
60 | return fmt.Sprintf("%s%s::%s::%s", GetMetricKeyPrefix(applicationName, version, signature), metric, user, transaction), nil
61 | }
62 |
63 | // GetUserKeyPrefix returns the prefix of a user key
64 | func GetUserKeyPrefix(applicationName string, version int, signature string) string {
65 | prefix := fmt.Sprintf("kt-users::%s::%d::%s::", applicationName, version, signature)
66 | return prefix
67 | }
68 |
69 | // GetUserKey returns a user key from the inputs
70 | func GetUserKey(applicationName string, version int, signature, user string) string {
71 | key := fmt.Sprintf("%s%s", GetUserKeyPrefix(applicationName, version, signature), user)
72 | return key
73 | }
74 |
75 | // GetExperimentResultKey returns a performance experiment key from the inputs
76 | func GetExperimentResultKey(namespace, experiment string) string {
77 | // getExperimentResultKey() is just getUserPrefix() with the user appended at the end
78 | return fmt.Sprintf("kt-result::%s::%s", namespace, experiment)
79 | }
80 |
81 | // GetExperimentResult returns an experiment result retrieved from a key value store
82 | func GetExperimentResult(fetch func() ([]byte, error)) (*base.ExperimentResult, error) {
83 | value, err := fetch()
84 | if err != nil {
85 | return nil, err
86 | }
87 |
88 | experimentResult := base.ExperimentResult{}
89 | err = json.Unmarshal(value, &experimentResult)
90 | if err != nil {
91 | return nil, fmt.Errorf("cannot unmarshal ExperimentResult: \"%s\": %e", string(value), err)
92 | }
93 |
94 | return &experimentResult, err
95 | }
96 |
--------------------------------------------------------------------------------
/templates/notify/_payload-github.tpl:
--------------------------------------------------------------------------------
1 | {
2 | "event_type": "iter8",
3 | "client_payload": {{ .Summary | toPrettyJson }}
4 | }
--------------------------------------------------------------------------------
/templates/notify/_payload-slack.tpl:
--------------------------------------------------------------------------------
1 | {
2 | "text": "Your Iter8 report is ready: {{ regexReplaceAll "\"" (regexReplaceAll "\n" (.Summary | toPrettyJson) "\\n") "\\\""}}"
3 | }
--------------------------------------------------------------------------------
/testdata/.gitignore:
--------------------------------------------------------------------------------
1 | !./experiment.yaml
2 | !iter8.tpl
--------------------------------------------------------------------------------
/testdata/abninputs/application.yaml:
--------------------------------------------------------------------------------
1 | name: default/backend
2 | tracks:
3 | candidate: v2
4 | default: v1
5 | versions:
6 | v1:
7 | metrics:
8 | sample_metric:
9 | - 223
10 | - 10309
11 | - 0
12 | - 100
13 | - 652049
14 | v2:
15 | metrics:
16 | sample_metric:
17 | - 252
18 | - 12228
19 | - 0
20 | - 100
21 | - 782208
22 |
--------------------------------------------------------------------------------
/testdata/abninputs/config.yaml:
--------------------------------------------------------------------------------
1 | abn:
2 | port: 50051
3 |
--------------------------------------------------------------------------------
/testdata/assertinputs/.gitignore:
--------------------------------------------------------------------------------
1 | !experiment.yaml
2 | !result.yaml
--------------------------------------------------------------------------------
/testdata/assertinputs/experiment.yaml:
--------------------------------------------------------------------------------
1 | metadata:
2 | name: myName
3 | namespace: myNamespace
4 | spec:
5 | # task 1: generate HTTP requests for application URL
6 | # collect Iter8's built-in HTTP latency and error-related metrics
7 | - task: http
8 | with:
9 | duration: 2s
10 | errorRanges:
11 | - lower: 500
12 | url: https://httpbin.org/get
13 | result:
14 | failure: false
15 | insights:
16 | numVersions: 1
17 | iter8Version: v0.13
18 | numCompletedTasks: 1
19 | startTime: "2022-03-16T10:22:58.540897-04:00"
--------------------------------------------------------------------------------
/testdata/controllers/config.yaml:
--------------------------------------------------------------------------------
1 | defaultResync: 15m
2 | # by default, Iter8 controller is namespace scoped
3 | # clusterScoped: true
4 | resourceTypes:
5 | svc:
6 | Group: ""
7 | Version: v1
8 | Resource: services
9 | cm:
10 | Group: ""
11 | Version: v1
12 | Resource: configmaps
13 | deploy:
14 | Group: apps
15 | Version: v1
16 | Resource: deployments
17 | isvc:
18 | Group: serving.kserve.io
19 | Version: v1beta1
20 | Resource: inferenceservices
21 | conditions:
22 | - Ready
23 | vs:
24 | Group: networking.istio.io
25 | Version: v1beta1
26 | Resource: virtualservices
27 |
--------------------------------------------------------------------------------
/testdata/controllers/garb.age:
--------------------------------------------------------------------------------
1 | this is not a
2 | . real yaml .
3 | if you try : this or
4 | some thing equally bad
5 | ( yaml parsing breaks)
6 | { real bad }
7 | [ in fact ]
--------------------------------------------------------------------------------
/testdata/drivertests/.gitignore:
--------------------------------------------------------------------------------
1 | !experiment.yaml
2 |
--------------------------------------------------------------------------------
/testdata/drivertests/experiment.tpl:
--------------------------------------------------------------------------------
1 | metadata:
2 | name: myName
3 | namespace: myNamespace
4 | spec:
5 | # task 1: generate HTTP requests for application URL
6 | # collect Iter8's built-in HTTP latency and error-related metrics
7 | - task: http
8 | with:
9 | duration: 2s
10 | errorRanges:
11 | - lower: 500
12 | url: {{ .URL }}
13 |
--------------------------------------------------------------------------------
/testdata/experiment.tpl:
--------------------------------------------------------------------------------
1 | metadata:
2 | name: myName
3 | namespace: myNamespace
4 | spec:
5 | # task 1: generate HTTP requests for application URL
6 | # collect Iter8's built-in HTTP latency and error-related metrics
7 | - task: http
8 | with:
9 | duration: 2s
10 | errorRanges:
11 | - lower: 500
12 | url: {{ .URL }}
--------------------------------------------------------------------------------
/testdata/experiment.yaml:
--------------------------------------------------------------------------------
1 | spec:
2 | # task 1: generate HTTP requests for application URL
3 | # collect Iter8's built-in HTTP latency and error-related metrics
4 | - task: http
5 | with:
6 | duration: 2s
7 | errorRanges:
8 | - lower: 500
9 | url: https://httpbin.org/get
10 |
--------------------------------------------------------------------------------
/testdata/experiment_grpc.yaml:
--------------------------------------------------------------------------------
1 | spec:
2 | # task 1: generate gRPC requests for application
3 | # collect Iter8's built-in gRPC latency and error-related metrics
4 | - task: grpc
5 | with:
6 | total: 200
7 | concurrency: 5
8 | data:
9 | name: bob
10 | timeout: 10s
11 | connect-timeeout: 5s
12 | protoURL: "https://raw.githubusercontent.com/bojand/ghz/v0.105.0/testdata/greeter.proto"
13 | call: "helloworld.Greeter.SayHello"
14 | host: "127.0.0.1"
--------------------------------------------------------------------------------
/testdata/output/.gitignore:
--------------------------------------------------------------------------------
1 | !report.html
--------------------------------------------------------------------------------
/testdata/output/gen-cli-values.txt:
--------------------------------------------------------------------------------
1 | time=1977-09-02 22:04:05 level=info msg=created experiment.yaml file
2 |
--------------------------------------------------------------------------------
/testdata/output/gen-values-file.txt:
--------------------------------------------------------------------------------
1 | time=1977-09-02 22:04:05 level=info msg=created experiment.yaml file
2 |
--------------------------------------------------------------------------------
/testdata/output/hub-with-destdir.txt:
--------------------------------------------------------------------------------
1 | time=1977-09-02 22:04:05 level=info msg=pulling load-test-http
2 |
--------------------------------------------------------------------------------
/testdata/output/hub.txt:
--------------------------------------------------------------------------------
1 | time=1977-09-02 22:04:05 level=info msg=downloading github.com/iter8-tools/iter8.git//charts into charts
2 |
--------------------------------------------------------------------------------
/testdata/output/kassert.txt:
--------------------------------------------------------------------------------
1 | time=1977-09-02 22:04:05 level=info msg=experiment completed
2 | time=1977-09-02 22:04:05 level=info msg=experiment has no failure
3 | time=1977-09-02 22:04:05 level=info msg=all conditions were satisfied
4 |
--------------------------------------------------------------------------------
/testdata/output/kdelete.txt:
--------------------------------------------------------------------------------
1 | time=1977-09-02 22:04:05 level=info msg=experiment group default deleted
2 |
--------------------------------------------------------------------------------
/testdata/output/klaunch.txt:
--------------------------------------------------------------------------------
1 | time=1977-09-02 22:04:05 level=info msg=experiment launched. Happy Iter8ing!
2 |
--------------------------------------------------------------------------------
/testdata/output/klog.txt:
--------------------------------------------------------------------------------
1 | time=1977-09-02 22:04:05 level=info msg=experiment logs from Kubernetes cluster indented-trace=below ...
2 | fake logs
3 |
--------------------------------------------------------------------------------
/testdata/output/krun.txt:
--------------------------------------------------------------------------------
1 | time=1977-09-02 22:04:05 level=info msg=task 1: http: started
2 | time=1977-09-02 22:04:05 level=info msg=task 1: http: completed
3 |
--------------------------------------------------------------------------------
/testdata/output/launch-with-destdir.txt:
--------------------------------------------------------------------------------
1 | time=1977-09-02 22:04:05 level=info msg=pulling load-test-http
2 | time=1977-09-02 22:04:05 level=info msg=created experiment.yaml file
3 | time=1977-09-02 22:04:05 level=info msg=starting local experiment
4 | time=1977-09-02 22:04:05 level=info msg=task 1: gen-load-and-collect-metrics-http: started
5 | time=1977-09-02 22:04:05 level=info msg=task 1: gen-load-and-collect-metrics-http: completed
6 |
--------------------------------------------------------------------------------
/testdata/output/launch.txt:
--------------------------------------------------------------------------------
1 | time=1977-09-02 22:04:05 level=info msg=created experiment.yaml file
2 | time=1977-09-02 22:04:05 level=info msg=starting local experiment
3 | time=1977-09-02 22:04:05 level=info msg=task 1: http: started
4 | time=1977-09-02 22:04:05 level=info msg=task 1: http: completed
5 |
--------------------------------------------------------------------------------