From 0b80026473812c30a460cabb8a601faaf83a03c3 Mon Sep 17 00:00:00 2001 From: Raul Sevilla Date: Mon, 18 Dec 2023 16:27:39 +0100 Subject: [PATCH] First commit Signed-off-by: Raul Sevilla --- .github/ISSUE_TEMPLATE/bug_report.md | 36 +++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++ .github/PULL_REQUEST_TEMPLATE.md | 21 ++ .github/workflows/builders.yml | 35 +++ .github/workflows/ci-tests.yml | 19 ++ .github/workflows/docs.yml | 46 +++ .github/workflows/gorelease.yml | 27 ++ .github/workflows/linters.yml | 20 ++ .github/workflows/release.yml | 17 + .github/workflows/stale.yml | 22 ++ .github/workflows/test-ocp.yml | 55 ++++ .gitignore | 10 + .golangci.yml | 23 ++ .pre-commit-config.yaml | 19 ++ Makefile | 50 +++ README.md | 0 cluster-density.go | 13 +- cmd/config/metrics-aggregated.yml | 14 +- cmd/config/metrics.yml | 8 +- .../cluster_density_configmap.yml | 8 + .../cluster_density_dep_served.yml | 52 ++++ .../cluster_density_dep_served_ports.yml | 40 +++ .../cluster_density_pod_served.yml | 45 +++ .../cluster_density_pod_service.yml | 15 + .../cluster_density_pod_service_ports.yml | 71 +++++ .../cluster_density_secret.yml | 8 + .../web-burner-cluster-density.yml | 224 ++++++++++++++ .../adminPolicyBasedExternalRoute.yml | 23 ++ cmd/config/web-burner-init/cm_frr.yml | 55 ++++ .../web-burner-init/macvlan_network.yml | 27 ++ .../permissionsClusterRole.yml | 10 + .../permissionsClusterRoleBinding.yml | 13 + .../permissionsRoleBinding.yml | 13 + .../permissionsServiceAccount.yml | 5 + cmd/config/web-burner-init/pod_served.yml | 33 ++ cmd/config/web-burner-init/pod_serving.yml | 99 ++++++ .../web-burner-init/pod_serving_no_bfd.yml | 54 ++++ .../web-burner-init/pod_serving_single.yml | 49 +++ cmd/config/web-burner-init/sriov_network.yml | 15 + .../web-burner-init/web-burner-init.yml | 194 ++++++++++++ .../node_density_pod_served.yml | 46 +++ .../node_density_pod_service_served.yml | 12 + .../web-burner-node-density.yml | 135 ++++++++ cmd/ocp.go | 73 +++-- common.go | 38 +++ crd-scale.go | 4 +- docs/index.md | 291 ++++++++++++++++++ docs/media/logo/kube-burner-logo-black.png | Bin 0 -> 14418 bytes docs/media/logo/kube-burner-logo-black.svg | 18 ++ docs/media/logo/kube-burner-logo-github.png | Bin 0 -> 44349 bytes docs/media/logo/kube-burner-logo-mini.png | Bin 0 -> 12515 bytes docs/media/logo/kube-burner-logo.png | Bin 0 -> 17008 bytes docs/media/logo/kube-burner-logo.svg | 21 ++ docs/media/logo/openshift-logo.png | Bin 0 -> 22227 bytes go.mod | 39 ++- go.sum | 69 +++-- hack/license.sh | 4 + hack/tag_name.sh | 7 + helpers.go | 285 ----------------- index.go | 24 +- mkdocs.yml | 86 ++++++ networkpolicy.go | 4 +- node-density-cni.go | 6 +- node-density-heavy.go | 6 +- node-density.go | 6 +- pvc-density.go | 22 +- test/helpers.bash | 105 +++---- test/test-ocp.bats | 53 ++-- types.go | 78 ++--- utils.go | 48 --- web-burner.go | 59 ++++ 71 files changed, 2449 insertions(+), 598 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/builders.yml create mode 100644 .github/workflows/ci-tests.yml create mode 100644 .github/workflows/docs.yml create mode 100644 .github/workflows/gorelease.yml create mode 100644 .github/workflows/linters.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/stale.yml create mode 100644 .github/workflows/test-ocp.yml create mode 100644 .gitignore create mode 100644 .golangci.yml create mode 100644 .pre-commit-config.yaml create mode 100644 Makefile create mode 100644 README.md create mode 100644 cmd/config/web-burner-cluster-density/cluster_density_configmap.yml create mode 100644 cmd/config/web-burner-cluster-density/cluster_density_dep_served.yml create mode 100644 cmd/config/web-burner-cluster-density/cluster_density_dep_served_ports.yml create mode 100644 cmd/config/web-burner-cluster-density/cluster_density_pod_served.yml create mode 100644 cmd/config/web-burner-cluster-density/cluster_density_pod_service.yml create mode 100644 cmd/config/web-burner-cluster-density/cluster_density_pod_service_ports.yml create mode 100644 cmd/config/web-burner-cluster-density/cluster_density_secret.yml create mode 100644 cmd/config/web-burner-cluster-density/web-burner-cluster-density.yml create mode 100644 cmd/config/web-burner-init/adminPolicyBasedExternalRoute.yml create mode 100644 cmd/config/web-burner-init/cm_frr.yml create mode 100644 cmd/config/web-burner-init/macvlan_network.yml create mode 100644 cmd/config/web-burner-init/permissionsClusterRole.yml create mode 100644 cmd/config/web-burner-init/permissionsClusterRoleBinding.yml create mode 100644 cmd/config/web-burner-init/permissionsRoleBinding.yml create mode 100644 cmd/config/web-burner-init/permissionsServiceAccount.yml create mode 100644 cmd/config/web-burner-init/pod_served.yml create mode 100644 cmd/config/web-burner-init/pod_serving.yml create mode 100644 cmd/config/web-burner-init/pod_serving_no_bfd.yml create mode 100644 cmd/config/web-burner-init/pod_serving_single.yml create mode 100644 cmd/config/web-burner-init/sriov_network.yml create mode 100644 cmd/config/web-burner-init/web-burner-init.yml create mode 100644 cmd/config/web-burner-node-density/node_density_pod_served.yml create mode 100644 cmd/config/web-burner-node-density/node_density_pod_service_served.yml create mode 100644 cmd/config/web-burner-node-density/web-burner-node-density.yml create mode 100644 common.go create mode 100644 docs/index.md create mode 100644 docs/media/logo/kube-burner-logo-black.png create mode 100644 docs/media/logo/kube-burner-logo-black.svg create mode 100644 docs/media/logo/kube-burner-logo-github.png create mode 100644 docs/media/logo/kube-burner-logo-mini.png create mode 100644 docs/media/logo/kube-burner-logo.png create mode 100644 docs/media/logo/kube-burner-logo.svg create mode 100644 docs/media/logo/openshift-logo.png create mode 100755 hack/license.sh create mode 100755 hack/tag_name.sh delete mode 100644 helpers.go create mode 100644 mkdocs.yml delete mode 100644 utils.go create mode 100644 web-burner.go diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..9a8f2ce3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,36 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG]" +labels: bug +assignees: '' +--- + +# Bug Description + +## **Output of `kube-burner` version** + +## **Describe the bug** + +A clear and concise description of what the bug is. + +## **To Reproduce** + +Steps to reproduce the behavior: + +1. Go to '...' +1. Click on '....' +1. Scroll down to '....' +1. See error + +## **Expected behavior** + +A clear and concise description of what you expected to happen. + +## **Screenshots or output** + +If applicable, add screenshots or kube-burner output to help explain your problem. + +## **Additional context** + +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..818a9314 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '[RFE]' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..a2cf9c54 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,21 @@ +## Type of change + +- [ ] Refactor +- [ ] New feature +- [ ] Bug fix +- [ ] Optimization +- [ ] Documentation Update + +## Description + + + +## Related Tickets & Documents + +- Related Issue # +- Closes # + +## Checklist before requesting a review + +- [ ] I have performed a self-review of my code. +- [ ] If it is a core feature, I have added thorough tests. diff --git a/.github/workflows/builders.yml b/.github/workflows/builders.yml new file mode 100644 index 00000000..a4f3868e --- /dev/null +++ b/.github/workflows/builders.yml @@ -0,0 +1,35 @@ +name: Build kube-burner-ocp +on: + workflow_call: +jobs: + build: + runs-on: ubuntu-latest + steps: + + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false + + - name: Set up Go 1.19 + uses: actions/setup-go@v4 + with: + go-version: 1.19 + + - name: Build code + run: make build + + - name: Install mkdocs dependencies + run: pip install mkdocs-material mkdocs-include-markdown-plugin mike + + - name: Build documentation + run: mkdocs build + + - name: Install + run: sudo make install + + - uses: actions/upload-artifact@v3 + with: + name: kube-burner-ocp + path: /usr/bin/kube-burner-ocp diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml new file mode 100644 index 00000000..da2fe4de --- /dev/null +++ b/.github/workflows/ci-tests.yml @@ -0,0 +1,19 @@ +name: CI tests +on: + workflow_call: + pull_request_target: + branches: + - master + - main + +jobs: + lint: + uses: ./.github/workflows/linters.yml + + build: + uses: ./.github/workflows/builders.yml + + tests: + needs: build + uses: ./.github/workflows/test-ocp.yml + secrets: inherit diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..c235dc1e --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,46 @@ +name: Deploy docs +on: + workflow_call: +jobs: + deploy-docs: + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: 3.x + + - name: Setup doc deploy + run: | + git config --global user.name Docs deploy + git config --global user.email docs@dummy.bot.com + + - name: Install dependencies + run: pip install mkdocs-material mkdocs-include-markdown-plugin mike + + - name: Deploy docs + run: mike deploy --push -m "Update docs to version ${{ github.ref_name }}" --update-aliases ${{ github.ref_name }} latest + + - name: Delete docs for older tags + run: | + echo "Keeping documentation for latest tags" + all_tags=$(git tag --sort=-v:refname) + tags_to_keep=$(echo "$all_tags" | head -n 3) + for tag in $all_tags; do + if [[ $tags_to_keep != *"$tag"* ]]; then + if mike list | grep -q "$tag"; then + echo "Deleting documentation for tag: $tag" + mike delete "$tag" + else + echo "Documentation for tag $tag not found. Skipping deletion." + fi + fi + done + + - name: Set latest as default doc branch + run: mike set-default --push latest \ No newline at end of file diff --git a/.github/workflows/gorelease.yml b/.github/workflows/gorelease.yml new file mode 100644 index 00000000..2eac2605 --- /dev/null +++ b/.github/workflows/gorelease.yml @@ -0,0 +1,27 @@ +# gorelease.yml +name: Create a new release of project +on: + workflow_call: +jobs: + release-go: + name: GoReleaser build + runs-on: ubuntu-latest + steps: + - name: Check out code into the Go module directory + uses: actions/checkout@v4 + with: + fetch-depth: 0 # See: https://goreleaser.com/ci/actions/ + persist-credentials: false + + - name: Set up Go 1.19 + uses: actions/setup-go@v4 + with: + go-version: 1.19 + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@master + with: + version: 1.19.0 + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml new file mode 100644 index 00000000..c6d2bc4f --- /dev/null +++ b/.github/workflows/linters.yml @@ -0,0 +1,20 @@ +name: Linters +on: + workflow_call: +jobs: + linters: + runs-on: ubuntu-latest + steps: + + - name: Check out code + uses: actions/checkout@v4 + with: + fetch-depth: 1 + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false + + - name: Install pre-commit + run: pip install pre-commit + + - name: Run pre-commit hooks + run: pre-commit run --all-files diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..1673b130 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,17 @@ +name: Release Workflow +on: + push: + tags: + - "*" # triggers only if push new tag version +jobs: + ci-tests: + uses: ./.github/workflows/ci-tests.yml + secrets: inherit + + release-build: + uses: ./.github/workflows/gorelease.yml + needs: ci-tests + + deploy-docs: + uses: ./.github/workflows/docs.yml + needs: ci-tests diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000..05abd2f3 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,22 @@ +name: Mark stale issues and pull requests + +on: + schedule: + - cron: "0 12 * * *" + +jobs: + stale: + permissions: + issues: write + pull-requests: write + runs-on: ubuntu-latest + + steps: + - uses: actions/stale@v8 + with: + stale-issue-message: 'This issue has become stale and will be closed automatically within 7 days.' + stale-pr-message: 'This pull request has become stale and will be closed automatically within 7 days.' + stale-issue-label: 'stale' + stale-pr-label: 'stale' + days-before-stale: 90 + days-before-close: 7 diff --git a/.github/workflows/test-ocp.yml b/.github/workflows/test-ocp.yml new file mode 100644 index 00000000..ea9b8cad --- /dev/null +++ b/.github/workflows/test-ocp.yml @@ -0,0 +1,55 @@ +name: Execute tests on OCP +on: + workflow_call: + secrets: + OPENSHIFT_SERVER: + required: true + OPENSHIFT_USER: + required: true + OPENSHIFT_PASSWORD: + required: true +jobs: + ocp-e2e-ci: + runs-on: ubuntu-latest + concurrency: + group: ocp-e2e-ci + steps: + + - name: Check out code + uses: actions/checkout@v4 + with: + fetch-depth: 1 + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false + + - name: Download kube-burner-ocp binary + uses: actions/download-artifact@v3 + with: + name: kube-burner-ocp + path: /tmp/ + + - name: Install bats + uses: mig4/setup-bats@v1 + with: + bats-version: 1.9.0 + + - name: Install oc + uses: redhat-actions/oc-installer@v1 + + - name: Authenticate against OCP cluster + run: oc login -u ${OPENSHIFT_USER} -p ${OPENSHIFT_PASSWORD} ${OPENSHIFT_SERVER} --insecure-skip-tls-verify=true + env: + OPENSHIFT_SERVER: ${{ secrets.OPENSHIFT_SERVER }} + OPENSHIFT_USER: ${{ secrets.OPENSHIFT_USER }} + OPENSHIFT_PASSWORD: ${{ secrets.OPENSHIFT_PASSWORD }} + + - name: Execute Tests + run: | + export PATH=${PATH}:/tmp/ + chmod +x /tmp/kube-burner-ocp + make test-ocp + env: + TERM: linux + OPENSHIFT_SERVER: ${{ secrets.OPENSHIFT_SERVER }} + OPENSHIFT_USER: ${{ secrets.OPENSHIFT_USER }} + OPENSHIFT_PASSWORD: ${{ secrets.OPENSHIFT_PASSWORD }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a058a0fd --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.test +*.out +bin +dist +site/ diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000..67e6065f --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,23 @@ +run: + timeout: 5m +linters-settings: + misspell: + locale: US +linters: + disable-all: true + enable: + - nakedret + - unused + - misspell + - ineffassign + - goconst + - goimports + - dupl + - unparam + - revive + - staticcheck + - gosimple + - unconvert + - gocyclo + - gofmt + - govet diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..71f43ab6 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,19 @@ +repos: + - repo: https://github.com/golangci/golangci-lint + rev: v1.51.2 + hooks: + - id: golangci-lint + args: [--timeout=5m] + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.34.0 + hooks: + - id: markdownlint + args: [--disable, MD013, MD002] + - repo: https://github.com/jumanjihouse/pre-commit-hooks + rev: 3.0.0 + hooks: + - id: shellcheck + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-json diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..c7b8d6c4 --- /dev/null +++ b/Makefile @@ -0,0 +1,50 @@ + +.PHONY: build lint clean test help all + + +ARCH ?= amd64 +BIN_NAME = kube-burner-ocp +BIN_DIR = bin +BIN_PATH = $(BIN_DIR)/$(ARCH)/$(BIN_NAME) +CGO = 0 + +GIT_COMMIT = $(shell git rev-parse HEAD) +VERSION ?= $(shell hack/tag_name.sh) +SOURCES := $(shell find . -type f -name "*.go") +BUILD_DATE = $(shell date '+%Y-%m-%d-%H:%M:%S') +VERSION_PKG=github.com/cloud-bulldozer/go-commons/version + +all: lint build + +help: + @echo "Commands for $(BIN_PATH):" + @echo + @echo 'Usage:' + @echo ' make lint Install and execute pre-commit' + @echo ' make clean Clean the compiled binaries' + @echo ' [ARCH=arch] make build Compile the project for arch, default amd64' + @echo ' [ARCH=arch] make install Installs kube-burner binary in the system, default amd64' + @echo ' make help Show this message' + +build: $(BIN_PATH) + +$(BIN_PATH): $(SOURCES) + @echo -e "\033[2mBuilding $(BIN_PATH)\033[0m" + @echo "GOPATH=$(GOPATH)" + GOARCH=$(ARCH) CGO_ENABLED=$(CGO) go build -v -ldflags "-X $(VERSION_PKG).GitCommit=$(GIT_COMMIT) -X $(VERSION_PKG).BuildDate=$(BUILD_DATE) -X $(VERSION_PKG).Version=$(VERSION)" -o $(BIN_PATH) ./cmd/ + +lint: + @echo "Executing pre-commit for all files" + pre-commit run --all-files + @echo "pre-commit executed." + +clean: + test ! -e $(BIN_DIR) || rm -Rf $(BIN_PATH) + +install: + cp $(BIN_PATH) /usr/bin/$(BIN_NAME) + +test: test-ocp + +test-ocp: + cd test && bats -F pretty -T --print-output-on-failure test-ocp.bats diff --git a/README.md b/README.md new file mode 100644 index 00000000..e69de29b diff --git a/cluster-density.go b/cluster-density.go index a07bb6e8..016bcb23 100644 --- a/cluster-density.go +++ b/cluster-density.go @@ -19,7 +19,9 @@ import ( "os" "time" - "github.com/cloud-bulldozer/kube-burner/pkg/workloads" + "github.com/kube-burner/kube-burner/pkg/burner" + "github.com/kube-burner/kube-burner/pkg/workloads" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -34,7 +36,7 @@ func NewClusterDensity(wh *workloads.WorkloadHelper, variant string) *cobra.Comm Use: variant, Short: fmt.Sprintf("Runs %v workload", variant), PreRun: func(cmd *cobra.Command, args []string) { - if verifyContainerRegistry(wh.RestConfig) { + if !burner.VerifyContainerRegistry(wh.RestConfig) { os.Exit(1) } wh.Metadata.Benchmark = cmd.Name() @@ -45,9 +47,14 @@ func NewClusterDensity(wh *workloads.WorkloadHelper, variant string) *cobra.Comm os.Setenv("CHURN_PERCENT", fmt.Sprint(churnPercent)) os.Setenv("CHURN_DELETION_STRATEGY", churnDeletionStrategy) os.Setenv("POD_READY_THRESHOLD", fmt.Sprintf("%v", podReadyThreshold)) + ingressDomain, err := wh.MetadataAgent.GetDefaultIngressDomain() + if err != nil { + log.Fatal("Error obtaining default ingress domain: ", err.Error()) + } + os.Setenv("INGRESS_DOMAIN", ingressDomain) }, Run: func(cmd *cobra.Command, args []string) { - wh.Run(cmd.Name(), MetricsProfileMap[cmd.Name()]) + wh.Run(cmd.Name(), getMetrics(cmd, "metrics-aggregated.yml"), alertsProfiles) }, } cmd.Flags().DurationVar(&podReadyThreshold, "pod-ready-threshold", 2*time.Minute, "Pod ready timeout threshold") diff --git a/cmd/config/metrics-aggregated.yml b/cmd/config/metrics-aggregated.yml index 639e8e20..4c87be6c 100644 --- a/cmd/config/metrics-aggregated.yml +++ b/cmd/config/metrics-aggregated.yml @@ -22,22 +22,22 @@ # Containers & pod metrics -- query: (sum(irate(container_cpu_usage_seconds_total{name!="",container!="POD",namespace=~"openshift-(etcd|oauth-apiserver|sdn|ovn-kubernetes|network-node-identity|multus|.*apiserver|authentication|.*controller-manager|.*scheduler|image-registry|operator-lifecycle-manager)"}[2m]) * 100) by (container, pod, namespace, node) and on (node) kube_node_role{role="master"}) > 0 +- query: (sum(irate(container_cpu_usage_seconds_total{name!="",container!="POD",namespace=~"openshift-(etcd|oauth-apiserver|sdn|ovn-kubernetes|network-node-identity|multus|.*apiserver|authentication|.*controller-manager|.*scheduler|image-registry|operator-lifecycle-manager)|cilium"}[2m]) * 100) by (container, pod, namespace, node) and on (node) kube_node_role{role="master"}) > 0 metricName: containerCPU-Masters -- query: (avg(irate(container_cpu_usage_seconds_total{name!="",container!="POD",namespace=~"openshift-(sdn|ovn-kubernetes|multus|ingress)"}[2m]) * 100 and on (node) kube_node_role{role="worker"}) by (namespace, pod, container)) > 0 +- query: (avg(irate(container_cpu_usage_seconds_total{name!="",container!="POD",namespace=~"openshift-(sdn|ovn-kubernetes|multus|ingress)|cilium"}[2m]) * 100 and on (node) kube_node_role{role="worker"}) by (namespace, pod, container)) > 0 metricName: containerCPU-AggregatedWorkers - query: (sum(irate(container_cpu_usage_seconds_total{name!="",container!="POD",namespace=~"openshift-(monitoring|sdn|ovn-kubernetes|multus|ingress)"}[2m]) * 100) by (container, pod, namespace, node) and on (node) kube_node_role{role="infra"}) > 0 metricName: containerCPU-Infra -- query: (sum(container_memory_rss{name!="",container!="POD",namespace=~"openshift-(etcd|oauth-apiserver|.*apiserver|ovn-kubernetes|network-node-identity|sdn|multus|ingress|authentication|.*controller-manager|.*scheduler|image-registry|operator-lifecycle-manager)"}) by (container, pod, namespace, node) and on (node) kube_node_role{role="master"}) > 0 +- query: (sum(container_memory_rss{name!="",container!="POD",namespace=~"openshift-(etcd|oauth-apiserver|.*apiserver|ovn-kubernetes|network-node-identity|sdn|multus|ingress|authentication|.*controller-manager|.*scheduler|image-registry|operator-lifecycle-manager)|cilium"}) by (container, pod, namespace, node) and on (node) kube_node_role{role="master"}) > 0 metricName: containerMemory-Masters -- query: avg(container_memory_rss{name!="",container!="POD",namespace=~"openshift-(sdn|ovn-kubernetes|multus|ingress)"} and on (node) kube_node_role{role="worker"}) by (pod, container, namespace) +- query: avg(container_memory_rss{name!="",container!="POD",namespace=~"openshift-(sdn|ovn-kubernetes|multus|ingress)|cilium"} and on (node) kube_node_role{role="worker"}) by (pod, container, namespace) metricName: containerMemory-AggregatedWorkers -- query: (sum(container_memory_rss{name!="",container!="POD",namespace=~"openshift-(sdn|ovn-kubernetes|multus|ingress|monitoring|image-registry)"}) by (container, pod, namespace, node) and on (node) kube_node_role{role="infra"}) > 0 +- query: (sum(container_memory_rss{name!="",container!="POD",namespace=~"openshift-(sdn|ovn-kubernetes|multus|ingress|monitoring|image-registry)|cilium"}) by (container, pod, namespace, node) and on (node) kube_node_role{role="infra"}) > 0 metricName: containerMemory-Infra # Node metrics: CPU & Memory @@ -118,6 +118,10 @@ metricName: serviceCount instant: true +- query: count(openshift_route_created{}) + metricName: routeCount + instant: true + - query: kube_node_role metricName: nodeRoles diff --git a/cmd/config/metrics.yml b/cmd/config/metrics.yml index 2387622e..a2e40680 100644 --- a/cmd/config/metrics.yml +++ b/cmd/config/metrics.yml @@ -22,10 +22,10 @@ # Containers & pod metrics -- query: (sum(irate(container_cpu_usage_seconds_total{name!="",container!~"POD|",namespace=~"openshift-(etcd|.*apiserver|ovn-kubernetes|network-node-identity|multus|sdn|ingress|.*controller-manager|.*scheduler)"}[2m]) * 100) by (container, pod, namespace, node)) > 0 +- query: (sum(irate(container_cpu_usage_seconds_total{name!="",container!~"POD|",namespace=~"openshift-(etcd|.*apiserver|ovn-kubernetes|network-node-identity|multus|sdn|ingress|.*controller-manager|.*scheduler)|cilium"}[2m]) * 100) by (container, pod, namespace, node)) > 0 metricName: containerCPU -- query: sum(container_memory_rss{name!="",container!~"POD|",namespace=~"openshift-(etcd|.*apiserver|ovn-kubernetes|network-node-identity|multus|sdn|ingress|.*controller-manager|.*scheduler)"}) by (container, pod, namespace, node) +- query: sum(container_memory_rss{name!="",container!~"POD|",namespace=~"openshift-(etcd|.*apiserver|ovn-kubernetes|network-node-identity|multus|sdn|ingress|.*controller-manager|.*scheduler)|cilium"}) by (container, pod, namespace, node) metricName: containerMemory # Kubelet & CRI-O runtime metrics @@ -109,6 +109,10 @@ metricName: serviceCount instant: true +- query: count(openshift_route_created{}) + metricName: routeCount + instant: true + - query: kube_node_role metricName: nodeRoles diff --git a/cmd/config/web-burner-cluster-density/cluster_density_configmap.yml b/cmd/config/web-burner-cluster-density/cluster_density_configmap.yml new file mode 100644 index 00000000..e7664530 --- /dev/null +++ b/cmd/config/web-burner-cluster-density/cluster_density_configmap.yml @@ -0,0 +1,8 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: configmap-{{ .Replica }} +data: + key1: "3" + key2: "value" diff --git a/cmd/config/web-burner-cluster-density/cluster_density_dep_served.yml b/cmd/config/web-burner-cluster-density/cluster_density_dep_served.yml new file mode 100644 index 00000000..6ccf5a46 --- /dev/null +++ b/cmd/config/web-burner-cluster-density/cluster_density_dep_served.yml @@ -0,0 +1,52 @@ +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: dep-served-{{ .Iteration }}-{{ .Replica }}-{{.JobName }} +spec: + template: + metadata: + name: dep-pod-served-{{ .Replica }}-{{.JobName }} + labels: + app: dep-served-{{ .Replica }} + spec: + containers: + - args: + - sleep + - infinity + name: app + image: k8s.gcr.io/pause:3.1 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 80 + resources: + requests: + memory: '100Mi' + cpu: 100m + limits: + memory: '100Mi' + cpu: 100m + - name: sleep-1 + image: gcr.io/google_containers/pause-amd64:3.0 + imagePullPolicy: IfNotPresent + resources: + requests: + memory: '100Mi' + cpu: 100m + limits: + memory: '100Mi' + cpu: 100m + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-role.kubernetes.io/worker-spk + operator: DoesNotExist + replicas: 2 + selector: + matchLabels: + app: dep-served-{{ .Replica }} + strategy: + type: RollingUpdate + diff --git a/cmd/config/web-burner-cluster-density/cluster_density_dep_served_ports.yml b/cmd/config/web-burner-cluster-density/cluster_density_dep_served_ports.yml new file mode 100644 index 00000000..b1ec747f --- /dev/null +++ b/cmd/config/web-burner-cluster-density/cluster_density_dep_served_ports.yml @@ -0,0 +1,40 @@ +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: dep-served-{{ .ns }}-{{ .Replica }} +spec: + template: + metadata: + name: pod-served-{{ .ns }}-{{ .Replica }} + labels: + app: app-served-{{ .ns }} + spec: + containers: + - name: sleep-1 + imagePullPolicy: IfNotPresent + image: gcr.io/google_containers/pause-amd64:3.0 + - name: app-served-{{ .ns }} + imagePullPolicy: IfNotPresent + image: rhscl/httpd-24-rhel7:latest + ports: + - containerPort: 8080 + protocol: TCP + resources: + requests: + memory: '100Mi' + cpu: 100m + limits: + memory: '100Mi' + cpu: 100m + env: + - name: service_name + value: app-served-{{ .ns }} + # nodeSelector: + # kubernetes.io/hostname: worker{{if eq .Iteration 81}}{{printf "%03d" (add .Iteration 2)}}{{else if eq .Iteration 82}}{{printf "%03d" (add .Iteration 1)}}{{else if eq .Iteration 98}}{{printf "%03d" (add .Iteration 2)}}{{else if eq .Iteration 110}}{{printf "%03d" (add .Iteration 2)}}{{else}}{{printf "%03d" (add .Iteration 3)}}{{end}}-r640 + replicas: 1 + selector: + matchLabels: + app: app-served-{{ .ns }} + strategy: + type: RollingUpdate diff --git a/cmd/config/web-burner-cluster-density/cluster_density_pod_served.yml b/cmd/config/web-burner-cluster-density/cluster_density_pod_served.yml new file mode 100644 index 00000000..4bf68e56 --- /dev/null +++ b/cmd/config/web-burner-cluster-density/cluster_density_pod_served.yml @@ -0,0 +1,45 @@ +--- +apiVersion: v1 +kind: Pod +metadata: + name: pod-served-{{ .ns }}-{{ .Replica }} + labels: + app: app-served-{{ .ns }} +spec: + containers: + - name: sleep-1 + imagePullPolicy: IfNotPresent + image: gcr.io/google_containers/pause-amd64:3.0 + - name: app-served-{{ .ns }} + imagePullPolicy: IfNotPresent + image: quay.io/centos/centos + {{ if contains .probe "true" }} + readinessProbe: + exec: + command: + - ping + - -c1 + - 172.18.0.10 + {{ end }} + ports: + - containerPort: 8080 + protocol: TCP + resources: + requests: + memory: '100Mi' + cpu: 100m + limits: + memory: '100Mi' + cpu: 100m + env: + - name: service_name + value: app-served-{{ .ns }} + # nodeSelector: + # kubernetes.io/hostname: worker{{if eq .Iteration 81}}{{printf "%03d" (add .Iteration 2)}}{{else if eq .Iteration 82}}{{printf "%03d" (add .Iteration 1)}}{{else if eq .Iteration 98}}{{printf "%03d" (add .Iteration 2)}}{{else if eq .Iteration 110}}{{printf "%03d" (add .Iteration 2)}}{{else}}{{printf "%03d" (add .Iteration 3)}}{{end}}-r640 + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-role.kubernetes.io/worker-spk + operator: DoesNotExist diff --git a/cmd/config/web-burner-cluster-density/cluster_density_pod_service.yml b/cmd/config/web-burner-cluster-density/cluster_density_pod_service.yml new file mode 100644 index 00000000..66208ce8 --- /dev/null +++ b/cmd/config/web-burner-cluster-density/cluster_density_pod_service.yml @@ -0,0 +1,15 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: served-ns-{{ .ns }}-{{ .Replica }} + labels: + app: app-served-{{ .ns }} +spec: + ports: + - name: served-ns-{{ .ns }}-{{ .Replica }} + port: 8080 + protocol: TCP + targetPort: 8080 + selector: + app: app-served-{{ .ns }} diff --git a/cmd/config/web-burner-cluster-density/cluster_density_pod_service_ports.yml b/cmd/config/web-burner-cluster-density/cluster_density_pod_service_ports.yml new file mode 100644 index 00000000..149d04e8 --- /dev/null +++ b/cmd/config/web-burner-cluster-density/cluster_density_pod_service_ports.yml @@ -0,0 +1,71 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: served-ns-{{ .ns }}-{{ .Replica }} + labels: + app: app-served-{{ .ns }} +spec: + ports: + - name: served-ns-{{ .ns }}-{{ .Replica }}-1 + port: 8080 + protocol: TCP + targetPort: 8080 + - name: served-ns-{{ .ns }}-{{ .Replica }}-2 + port: 8081 + protocol: TCP + targetPort: 8081 + - name: served-ns-{{ .ns }}-{{ .Replica }}-3 + port: 8082 + protocol: TCP + targetPort: 8082 + - name: served-ns-{{ .ns }}-{{ .Replica }}-4 + port: 8083 + protocol: TCP + targetPort: 8083 + - name: served-ns-{{ .ns }}-{{ .Replica }}-5 + port: 8084 + protocol: TCP + targetPort: 8084 + - name: served-ns-{{ .ns }}-{{ .Replica }}-6 + port: 8085 + protocol: TCP + targetPort: 8085 + - name: served-ns-{{ .ns }}-{{ .Replica }}-7 + port: 8086 + protocol: TCP + targetPort: 8086 + - name: served-ns-{{ .ns }}-{{ .Replica }}-8 + port: 8087 + protocol: TCP + targetPort: 8087 + - name: served-ns-{{ .ns }}-{{ .Replica }}-9 + port: 8088 + protocol: TCP + targetPort: 8088 + - name: served-ns-{{ .ns }}-{{ .Replica }}-10 + port: 8089 + protocol: TCP + targetPort: 8089 + - name: served-ns-{{ .ns }}-{{ .Replica }}-11 + port: 8090 + protocol: TCP + targetPort: 8090 + - name: served-ns-{{ .ns }}-{{ .Replica }}-12 + port: 8091 + protocol: TCP + targetPort: 8091 + - name: served-ns-{{ .ns }}-{{ .Replica }}-13 + port: 8092 + protocol: TCP + targetPort: 8092 + - name: served-ns-{{ .ns }}-{{ .Replica }}-14 + port: 8093 + protocol: TCP + targetPort: 8093 + - name: served-ns-{{ .ns }}-{{ .Replica }}-15 + port: 8094 + protocol: TCP + targetPort: 8094 + selector: + app: app-served-{{ .ns }} diff --git a/cmd/config/web-burner-cluster-density/cluster_density_secret.yml b/cmd/config/web-burner-cluster-density/cluster_density_secret.yml new file mode 100644 index 00000000..86202510 --- /dev/null +++ b/cmd/config/web-burner-cluster-density/cluster_density_secret.yml @@ -0,0 +1,8 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: secret-{{ .Replica }} +data: + ssh-privatekey: LS0tLS1CRUdJTiBPUEVOU1NIIFBSSVZBVEUgS0VZLS0tLS0KYjNCbGJuTnphQzFyWlhrdGRqRUFBQUFBQkc1dmJtVUFBQUFFYm05dVpRQUFBQUFBQUFBQkFBQUJsd0FBQUFkemMyZ3RjbgpOaEFBQUFBd0VBQVFBQUFZRUEyK1MxR3lvNElzTi9vNU4vRkFEMnRlS0lvZVJ1SzY3L2txclErRzl3MFlpeFVxV28zZjZvCkNWQktzeHk1OVp3ZldteWhEV1ZTN21BNVQzL0VFV2xMa0ZvMFNmSnBubG1LcFpqZ2Y4WVFUUVJLUGZRSGZDVmtROVZQNEMKQVVGTklpZWh3Q3cvTlZvTklab21LSm9UbHcxd0ZYbmoyN1JRY1NEM1FIYjkwVkNsQWdYS1dBSDdJZHZVN0wza2hkQjlSdApwQTd6aVcwd1JwbGlJTVR3Yk1FZUxBTnRRYm9mVUQ5b3VOSlZ5VkEzYnhMTDVlL1hZMjU2OFFwWjlaalN3cEVVQ3htekVKCjArRWIzUWFBQzVWSTFCYlVzc0VZWjZWb0ZJaEEweHp1ZHJiRG1SWkJ3T2Exc3VmUDA2ZU9MQWZJZEJKNmZ5d0ZHQjdDR0IKWkNMcmpZSDM3R0FIS2s0VlFRRGVVcjJUaGI5MFA2R0N4V1dhSkh5clU3dUFxVjQxMEhOOEJWMFRjUmtjZys5djdxdTJzbwpQaVhZYXNCY0RSa2E3VE1hNk1qdU92bDJYRjBVcWJVNE9Qai81RTBzZTMrRGdkK0JzM3BHbzBjUGU4QnFkUEZ0bTNsMmRjClNzUzMvaVRyS2VBcitEMWNobXpZVExiNXREOERPMU5xZzJ1TU1ESmxBQUFGbUFSV2hLa0VWb1NwQUFBQUIzTnphQzF5YzIKRUFBQUdCQU52a3RSc3FPQ0xEZjZPVGZ4UUE5clhpaUtIa2JpdXUvNUtxMFBodmNOR0lzVktscU4zK3FBbFFTck1jdWZXYwpIMXBzb1ExbFV1NWdPVTkveEJGcFM1QmFORW55YVo1WmlxV1k0SC9HRUUwRVNqMzBCM3dsWkVQVlQrQWdGQlRTSW5vY0FzClB6VmFEU0dhSmlpYUU1Y05jQlY1NDl1MFVIRWc5MEIyL2RGUXBRSUZ5bGdCK3lIYjFPeTk1SVhRZlViYVFPODRsdE1FYVoKWWlERThHekJIaXdEYlVHNkgxQS9hTGpTVmNsUU4yOFN5K1h2MTJOdWV2RUtXZldZMHNLUkZBc1pzeENkUGhHOTBHZ0F1VgpTTlFXMUxMQkdHZWxhQlNJUU5NYzduYTJ3NWtXUWNEbXRiTG56OU9uaml3SHlIUVNlbjhzQlJnZXdoZ1dRaTY0MkI5K3hnCkJ5cE9GVUVBM2xLOWs0Vy9kRCtoZ3NWbG1pUjhxMU83Z0tsZU5kQnpmQVZkRTNFWkhJUHZiKzZydHJLRDRsMkdyQVhBMFoKR3Uwekd1akk3anI1ZGx4ZEZLbTFPRGo0LytSTkxIdC9nNEhmZ2JONlJxTkhEM3ZBYW5UeGJadDVkblhFckV0LzRrNnluZwpLL2c5WElaczJFeTIrYlEvQXp0VGFvTnJqREF5WlFBQUFBTUJBQUVBQUFHQkFNTnUvc09ZLzNGdVBkZ2RORm1NRGJsVUVFCm1LbzV3eG1iWTZHU1prcldtZFNyU0F4WmdnU0NxVzhQMUY3OFRxTURlR3lwRUVWL1BERTh5amFwaHE5QnprL3dvbDZGY3oKai9WajdVdkN0aVF0UnVyUEk0UGtHN3ZxTDJhc1BBelJRcEV5cWdhTUU3T3VaOWdhS2VaZ0Rma1ptVXpyNHJBVCs2bytYbwptK2ZYKy8vVE02YUtoQy93bU1xY2J5bTdSa3lkbUE3RlJjOWdZWk5zeVhISFFjSTN2TXcrTWh6OGpid25CWmlLTTJwejRSCi82UnFTUTEzV004NGV3RzhCTDJtZi9vRDJDd0pYU1Izb2UzT1NOWTlzMWs5NlhTbjJSem5Db0dORDRjZ1B6dzZiNS96TmYKKzRGU1RpUXA1OWJJM1A3QjVuMHVSdFVleml6V0xtQ00zemdsQlF2S1lQN0FIWmcra2IrY1NySU5nM0FlTVd0WklDUHdVUQovOXRzZ1ora0g5dnFvT3NGeXV1OVZnYlpBdDNRbXFKM2VxRFR2THlkMHprVTZrcHR3YTdnNWpvMmJpT1ZnN1QzZTFBV3JxCjM1cWRLcmpXMlBGVWpnZVJ6QiszVVdjVkozMS9xWTNENzQwWU4raFBpQTV6L3Jrb3dSdEc5RFVmMmtneXhvZ2FpOEFRQUEKQU1CV3FNbW9ERUhRbnlOdnlBT3lVellHV3NjelpvNm4vVTQvTkNvNlAzTzZCYnRWS0FUV1ZYWEVtNjVnZTBZTVRQYlJqWgpkWnE3d2hPV0h6cjVKRTNGanl4RllJWWEvdThPcVdjOEtzeUlIakZzM1dBT0t0RWI0L0x2WjFzWk5KT1I3OVVBYStud3ZqCm1rQXp6d3FxUTBzSFV0UWVoVHV0OTFNaURFNVB6OW8wdFZnQ25FbmJ1eGNuOEdRSmZONW1JZ0E2YnNzRDdmRWo3Sk03dnIKSE5ETjYzUjd2UTUyNTNNQ3lpUG93NW9iV01PcWZMU3VJVGxKNjg3NWJHQWNLQ2dNSUFBQURCQVA5M0l5MVFXcUptWnhsSQpueUZlU3g4Nk5meEJKV3hHc2l1MndqOE12ZldyWXQ4V2pQNHZjSEtSNjcydlc5UEs0Qk1GcDVNSVl2YkFnbGxhYnp2VVVyClF1VGhITWI3czlUL0JHM09yQUpyemVNZURrK3lybzlJaC9jRmxGS01xUGRoZzNLVEYxU1Jzb2F5L2pRVkh5QVIvY0ZnUmUKNzVaM3NQczNGOFJiSlpzL3VNUUxFbUJHVXAvNG9YbG1kKyt1c1FET2ptUWxsYm44bHh1N3gxbU42MDNzKytTVWVxWVVKZQo4cng5Y2hEU2tFTTJQWEhpdm9zbVUzdUZJaTRteUJBUUFBQU1FQTNGcURSQWliRVB3TGRqOWQ1UjJoTjI0eklmSGtadkEvClJxaDhzOCt5ZFUzakJQRGdGRUQwSGl4YXl0MVhnTzEwZDBGZEEvSFI2cE8xdUdybDdzdm5nYjdnSGJpTGo5SnBvRGd0SWkKalZnSEZvcmFZMWpTeGEyemNNbldFWVFaVFRlYUF6TU5qdWlJb2MwemN3ZHBRTWpkWWdwOUJBK1pVVEgvZTJNVmpmdUtmQQo3SlRDSW1uQUJlUWV1SWRtZ1VTZkltUTdRNGJsL29uUUZUZkVLc0tpZVFCU3hjS1VLa0orZ3R2RTJ5ZWJRRVFMV09kaE5GCjJ0S05TQlk5TUFsRTFsQUFBQUlISnpaWFpwYkd4aFFIZHZibVJsY214aGJtUXVjbk5sZG1sc2JHRXViM0puQVFJPQotLS0tLUVORCBPUEVOU1NIIFBSSVZBVEUgS0VZLS0tLS0K + diff --git a/cmd/config/web-burner-cluster-density/web-burner-cluster-density.yml b/cmd/config/web-burner-cluster-density/web-burner-cluster-density.yml new file mode 100644 index 00000000..7c02fc17 --- /dev/null +++ b/cmd/config/web-burner-cluster-density/web-burner-cluster-density.yml @@ -0,0 +1,224 @@ +--- +global: + gc: {{.GC}} + gcMetrics: {{.GC_METRICS}} + indexerConfig: + esServers: ["{{.ES_SERVER}}"] + insecureSkipVerify: true + defaultIndex: {{.ES_INDEX}} + type: {{.INDEXING_TYPE}} + measurements: + - name: podLatency + thresholds: + - conditionType: Ready + metric: P99 + threshold: {{.POD_READY_THRESHOLD}} + +jobs: + - name: cluster-density + jobType: create + jobIterations: {{ mul .LIMITCOUNT .SCALE }} + qps: {{ $.QPS }} + burst: {{ $.BURST }} + namespacedIterations: true + cleanup: false + namespace: served-ns + podWait: true + verifyObjects: true + errorOnVerify: true + preLoadImages: false + objects: + - objectTemplate: cluster_density_configmap.yml + replicas: 30 + - objectTemplate: cluster_density_secret.yml + replicas: 38 + - objectTemplate: node_density_pod_served.yml + replicas: 25 + inputVars: + probe: "{{ $.PROBE }}" + - objectTemplate: node_density_pod_service.yml + replicas: 25 + - objectTemplate: cluster_density_dep_served.yml + replicas: 5 + + - name: app-job-1 + jobType: create + jobIterations: {{ mul 2 .SCALE }} + qps: {{ .QPS }} + burst: {{ .BURST }} + namespacedIterations: true + cleanup: false + namespace: served-ns + podWait: false + verifyObjects: true + errorOnVerify: true + preLoadImages: false + objects: + - objectTemplate: cluster_density_dep_served_ports.yml + replicas: 84 + inputVars: + ns: group-a-1 + - objectTemplate: cluster_density_pod_service_ports.yml + replicas: 1 + inputVars: + ns: group-a-1 + + - objectTemplate: cluster_density_dep_served_ports.yml + replicas: 56 + inputVars: + ns: group-b-1 + - objectTemplate: cluster_density_pod_service_ports.yml + replicas: 1 + inputVars: + ns: group-b-1 + + - objectTemplate: cluster_density_dep_served_ports.yml + replicas: 25 + inputVars: + ns: group-c-1 + - objectTemplate: cluster_density_pod_service_ports.yml + replicas: 1 + inputVars: + ns: group-c-1 + + - name: app-job-2 + jobType: create + jobIterations: {{ mul 2 .SCALE }} + qps: {{ .QPS }} + burst: {{ .BURST }} + namespacedIterations: true + cleanup: false + namespace: served-ns + waitWhenFinished: true + verifyObjects: true + errorOnVerify: true + preLoadImages: false + objects: +{{ range $index, $val := untilStep 1 4 1 }} + - objectTemplate: cluster_density_dep_served_ports.yml + replicas: 24 + inputVars: + ns: group-d-{{ $val }} + - objectTemplate: cluster_density_pod_service_ports.yml + replicas: 1 + inputVars: + ns: group-d-{{ $val }} + + - objectTemplate: cluster_density_dep_served_ports.yml + replicas: 14 + inputVars: + ns: group-e-{{ $val }} + - objectTemplate: cluster_density_pod_service_ports.yml + replicas: 1 + inputVars: + ns: group-e-{{ $val }} +{{ end }} + + - name: app-job-3 + jobType: create + jobIterations: {{ mul 2 .SCALE }} + qps: {{ .QPS }} + burst: {{ .BURST }} + namespacedIterations: true + cleanup: false + namespace: served-ns + waitWhenFinished: true + verifyObjects: true + errorOnVerify: true + preLoadImages: false + objects: +{{ range $index, $val := untilStep 1 7 1 }} + - objectTemplate: cluster_density_dep_served_ports.yml + replicas: 12 + inputVars: + ns: group-f-{{ $val }} + - objectTemplate: cluster_density_pod_service_ports.yml + replicas: 1 + inputVars: + ns: group-f-{{ $val }} + + - objectTemplate: cluster_density_dep_served_ports.yml + replicas: 10 + inputVars: + ns: group-g-{{ $val }} + - objectTemplate: cluster_density_pod_service_ports.yml + replicas: 1 + inputVars: + ns: group-g-{{ $val }} + + - objectTemplate: cluster_density_dep_served_ports.yml + replicas: 9 + inputVars: + ns: group-h-{{ $val }} + - objectTemplate: cluster_density_pod_service_ports.yml + replicas: 1 + inputVars: + ns: group-h-{{ $val }} +{{ end }} + + - name: app-job-4 + jobType: create + jobIterations: {{ mul 2 .SCALE }} + qps: {{ .QPS }} + burst: {{ .BURST }} + namespacedIterations: true + cleanup: false + namespace: served-ns + waitWhenFinished: true + verifyObjects: true + errorOnVerify: true + preLoadImages: false + objects: +{{ range $index, $val := untilStep 1 13 1 }} + - objectTemplate: cluster_density_dep_served_ports.yml + replicas: 8 + inputVars: + ns: group-i-{{ $val }} + - objectTemplate: cluster_density_pod_service_ports.yml + replicas: 1 + inputVars: + ns: group-i-{{ $val }} + + - objectTemplate: cluster_density_dep_served_ports.yml + replicas: 6 + inputVars: + ns: group-j-{{ $val }} + - objectTemplate: cluster_density_pod_service_ports.yml + replicas: 1 + inputVars: + ns: group-j-{{ $val }} + + - objectTemplate: cluster_density_dep_served_ports.yml + replicas: 5 + inputVars: + ns: group-k-{{ $val }} + - objectTemplate: cluster_density_pod_service_ports.yml + replicas: 1 + inputVars: + ns: group-k-{{ $val }} +{{ end }} + + - name: app-job-5 + jobType: create + jobIterations: {{ mul 2 .SCALE }} + qps: {{ .QPS }} + burst: {{ .BURST }} + namespacedIterations: true + cleanup: false + namespace: served-ns + waitWhenFinished: true + verifyObjects: true + errorOnVerify: true + preLoadImages: false + objects: +{{ range $index, $val := untilStep 1 30 1 }} + - objectTemplate: cluster_density_dep_served_ports.yml + replicas: 4 + inputVars: + ns: group-l-{{ $val }} + - objectTemplate: cluster_density_pod_service_ports.yml + replicas: 1 + inputVars: + ns: group-l-{{ $val }} +{{ end }} + diff --git a/cmd/config/web-burner-init/adminPolicyBasedExternalRoute.yml b/cmd/config/web-burner-init/adminPolicyBasedExternalRoute.yml new file mode 100644 index 00000000..b35a8330 --- /dev/null +++ b/cmd/config/web-burner-init/adminPolicyBasedExternalRoute.yml @@ -0,0 +1,23 @@ +apiVersion: k8s.ovn.org/v1 +kind: AdminPolicyBasedExternalRoute +metadata: + name: honeypotting-{{ .Iteration }}-{{.JobName }} +spec: +## gateway example + from: + namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: served-ns-{{ .Iteration }} + nextHops: + # static: + # - ip: "192.168.{{ add 218 .Replica }}.{{ add 1 .Iteration }}" + # bfdEnabled: true + dynamic: + - podSelector: + matchLabels: + lb: lb-{{ .Iteration }} + bfdEnabled: true + namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: serving-ns-{{ .Iteration }} + networkAttachmentName: serving-ns-{{ .Iteration }}/sriov-net-{{ .Iteration }} \ No newline at end of file diff --git a/cmd/config/web-burner-init/cm_frr.yml b/cmd/config/web-burner-init/cm_frr.yml new file mode 100644 index 00000000..1bac5750 --- /dev/null +++ b/cmd/config/web-burner-init/cm_frr.yml @@ -0,0 +1,55 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: frr +data: + daemons: | + bgpd=no + ospfd=no + ospf6d=no + ripd=no + ripngd=no + isisd=no + pimd=no + ldpd=no + nhrpd=no + eigrpd=no + babeld=no + sharpd=no + pbrd=no + bfdd=yes + fabricd=no + vrrpd=no + vtysh_enable=yes + zebra_options=" -A 127.0.0.1 -s 90000000" + bgpd_options=" -A 127.0.0.1" + ospfd_options=" -A 127.0.0.1" + ospf6d_options=" -A ::1" + ripd_options=" -A 127.0.0.1" + ripngd_options=" -A ::1" + isisd_options=" -A 127.0.0.1" + pimd_options=" -A 127.0.0.1" + ldpd_options=" -A 127.0.0.1" + nhrpd_options=" -A 127.0.0.1" + eigrpd_options=" -A 127.0.0.1" + babeld_options=" -A 127.0.0.1" + sharpd_options=" -A 127.0.0.1" + pbrd_options=" -A 127.0.0.1" + staticd_options="-A 127.0.0.1" + bfdd_options=" -A 127.0.0.1" + fabricd_options="-A 127.0.0.1" + vrrpd_options=" -A 127.0.0.1" + vtysh.conf: | + service integrated-vtysh-config + frr.conf: | + hostname vrouter + service integrated-vtysh-config + password frr + enable password frr + ! + debug bfd peer + debug bfd zebra + debug bfd network + ! + log file /tmp/frr.log debugging + bfd diff --git a/cmd/config/web-burner-init/macvlan_network.yml b/cmd/config/web-burner-init/macvlan_network.yml new file mode 100644 index 00000000..d17ab394 --- /dev/null +++ b/cmd/config/web-burner-init/macvlan_network.yml @@ -0,0 +1,27 @@ +apiVersion: k8s.cni.cncf.io/v1 +kind: NetworkAttachmentDefinition +metadata: + name: sriov-net-{{ .Iteration }} +spec: + config: |- + { + "cniVersion": "0.3.1", + "name": "internal-net", + "plugins": [ + { + "type": "macvlan", + "master": "{{.bridge}}", + "mode": "bridge", + "ipam": { + "type": "static" + } + }, + { + "capabilities": { + "mac": true, + "ips": true + }, + "type": "tuning" + } + ] + } diff --git a/cmd/config/web-burner-init/permissionsClusterRole.yml b/cmd/config/web-burner-init/permissionsClusterRole.yml new file mode 100644 index 00000000..5be47e4f --- /dev/null +++ b/cmd/config/web-burner-init/permissionsClusterRole.yml @@ -0,0 +1,10 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: get-routes +rules: +- apiGroups: [""] + resources: ["nodes"] + verbs: + - list + - get diff --git a/cmd/config/web-burner-init/permissionsClusterRoleBinding.yml b/cmd/config/web-burner-init/permissionsClusterRoleBinding.yml new file mode 100644 index 00000000..83b71be9 --- /dev/null +++ b/cmd/config/web-burner-init/permissionsClusterRoleBinding.yml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: get-routes-to-sa-{{ .Iteration }} +subjects: + - kind: ServiceAccount + name: internal-kubectl + namespace: serving-ns-{{ .Iteration }} +roleRef: + kind: ClusterRole + name: get-routes + apiGroup: rbac.authorization.k8s.io + diff --git a/cmd/config/web-burner-init/permissionsRoleBinding.yml b/cmd/config/web-burner-init/permissionsRoleBinding.yml new file mode 100644 index 00000000..6d39962f --- /dev/null +++ b/cmd/config/web-burner-init/permissionsRoleBinding.yml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: system:openshift:scc:privileged + namespace: serving-ns-{{ .Iteration }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:openshift:scc:privileged +subjects: +- kind: ServiceAccount + name: internal-kubectl + namespace: serving-ns-{{ .Iteration }} diff --git a/cmd/config/web-burner-init/permissionsServiceAccount.yml b/cmd/config/web-burner-init/permissionsServiceAccount.yml new file mode 100644 index 00000000..5edfcec8 --- /dev/null +++ b/cmd/config/web-burner-init/permissionsServiceAccount.yml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: internal-kubectl + namespace: serving-ns-{{ .Iteration }} diff --git a/cmd/config/web-burner-init/pod_served.yml b/cmd/config/web-burner-init/pod_served.yml new file mode 100644 index 00000000..43177cb8 --- /dev/null +++ b/cmd/config/web-burner-init/pod_served.yml @@ -0,0 +1,33 @@ +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: dep-served-init-{{ .Iteration }}-{{ .Replica }}-{{.JobName }} +spec: + template: + metadata: + name: pod-served-{{ .Iteration }}-{{ .Replica }}-{{.JobName }} + labels: + app: served-init-{{ .Iteration }}-{{ .Replica }}-{{.JobName }} + spec: + containers: + - args: + - sleep + - infinity + name: app + image: quay.io/centos/centos + ports: + - containerPort: 80 + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-role.kubernetes.io/worker-spk + operator: DoesNotExist + replicas: 1 + selector: + matchLabels: + app: served-init-{{ .Iteration }}-{{ .Replica }}-{{.JobName }} + strategy: + type: RollingUpdate diff --git a/cmd/config/web-burner-init/pod_serving.yml b/cmd/config/web-burner-init/pod_serving.yml new file mode 100644 index 00000000..10962e9e --- /dev/null +++ b/cmd/config/web-burner-init/pod_serving.yml @@ -0,0 +1,99 @@ +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: dep-serving-{{ .Iteration }}-{{ .Replica }}-{{.JobName }} + namespace: serving-ns-{{ .Iteration }} +spec: + template: + metadata: + name: pod-serving-{{ .Iteration }}-{{ .Replica }}-{{.JobName }} + namespace: serving-ns-{{ .Iteration }} + annotations: + {{ if contains .crd "false" }} + gateway-ip: "192.168.{{ add 218 .Replica }}.{{ add 1 .Iteration }}" + k8s.ovn.org/routing-namespaces: served-ns-{{ .Iteration }} + k8s.ovn.org/routing-network: serving-ns-{{ .Iteration }}/sriov-net-{{ .Iteration }} + k8s.ovn.org/bfd-enabled: "" + {{ end }} + k8s.v1.cni.cncf.io/networks: |- + [{ + "name": "sriov-net-{{ .Iteration }}", + "ips": [ "192.168.{{ add 218 .Replica }}.{{ add 1 .Iteration }}/21" ] + }] + k8s.v1.cni.cncf.io/network-status: |- + [{ + "name": "serving-ns-{{ .Iteration }}/sriov-net-{{ .Iteration }}", + "interface": "net1", + "ips": [ "192.168.{{ add 218 .Replica }}.{{ add 1 .Iteration }}" ], + "dns": {} + }] + labels: + serving: true-{{ .Replica }} + lb: lb-{{ .Iteration }} + spec: + serviceAccountName: internal-kubectl + containers: + - name: bfd + image: quay.io/wcaban/frr + command: + - sh + - -c + - >- + sysctl -w net.ipv4.ip_forward=1 && + cp /config/* /etc/frr/ && + for i in {2..254}; do echo " peer $(kubectl get nodes -o jsonpath='{.items[0].metadata.annotations.k8s\.ovn\.org\/node-primary-ifaddr}' | cut -d '"' -f4- | sed 's/\.[^.]*$//').$i" >> /etc/frr/frr.conf; done && + echo " no shutdown" >> /etc/frr/frr.conf && + echo " !" >> /etc/frr/frr.conf && + echo "! subnets for each node" >> /etc/frr/frr.conf && + kubectl get nodes -o jsonpath='{range .items[*].metadata.annotations}{.k8s\.ovn\.org\/node\-subnets}{.k8s\.ovn\.org\/node\-primary\-ifaddr}{"\n"}{end}' | awk -F'["/]' '{print "ip route " $4"/"$5 " " $9}' >> /etc/frr/frr.conf && + ip a a 172.18.0.10/32 dev lo && + /usr/libexec/frr/frrinit.sh start && + tail -f /tmp/frr.log + ports: + - name: bfd + containerPort: 3784 + protocol: UDP + - name: stats + containerPort: 9000 + protocol: TCP + readinessProbe: + exec: + command: + - sh + - -c + - >- + vtysh -c 'show bfd peers brief' | + grep up + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + privileged: true + volumeMounts: + - name: config-volume + mountPath: /config + - name: pre-install + mountPath: /etc/pre-install + volumes: + - name: pre-install + emptyDir: {} + - name: config-volume + configMap: + name: frr + - name: kubeconfig + secret: + secretName: kubeconfig + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-role.kubernetes.io/worker-spk + operator: Exists + replicas: 1 + selector: + matchLabels: + serving: true-{{ .Replica }} + strategy: + type: RollingUpdate + diff --git a/cmd/config/web-burner-init/pod_serving_no_bfd.yml b/cmd/config/web-burner-init/pod_serving_no_bfd.yml new file mode 100644 index 00000000..8fb4c49a --- /dev/null +++ b/cmd/config/web-burner-init/pod_serving_no_bfd.yml @@ -0,0 +1,54 @@ +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: dep-serving-{{ .Iteration }}-{{ .Replica }}-{{.JobName }} + namespace: serving-ns-{{ .Iteration }} +spec: + template: + metadata: + name: pod-serving-no-frr-{{ .Iteration }}-{{ .Replica }}-{{.JobName }} + namespace: serving-ns-{{ .Iteration }} + annotations: + gateway-ip: "192.168.{{ add 218 .Replica }}.{{ add 1 .Iteration }}" + k8s.ovn.org/routing-namespaces: served-ns-{{ .Iteration }} + k8s.ovn.org/routing-network: serving-ns-{{ .Iteration }}/sriov-net-{{ .Iteration }} + k8s.v1.cni.cncf.io/networks: |- + [{ "name": "sriov-net-{{ .Iteration }}", "ips": [ "192.168.{{ add 218 .Replica }}.{{ add 1 .Iteration }}/21" ]}] + k8s.v1.cni.cncf.io/network-status: |- + [{ + "name": "serving-ns-{{ .Iteration }}/sriov-net-{{ .Iteration }}", + "interface": "net1", + "ips": [ + "192.168.{{ add 218 .Replica }}.{{ add 1 .Iteration }}" + ], + "dns": {} + }] + labels: + serving: true-{{ .Replica }} + spec: + containers: + - name: nobfd + image: quay.io/mukrishn/jetski:latest + command: ["/bin/sh","-c"] + args: ["sleep infinity"] + volumes: + - name: config-volume + configMap: + name: frr + - name: kubeconfig + secret: + secretName: kubeconfig + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-role.kubernetes.io/worker-spk + operator: Exists + replicas: 1 + selector: + matchLabels: + serving: true-{{ .Replica }} + strategy: + type: RollingUpdate diff --git a/cmd/config/web-burner-init/pod_serving_single.yml b/cmd/config/web-burner-init/pod_serving_single.yml new file mode 100644 index 00000000..f10afcf7 --- /dev/null +++ b/cmd/config/web-burner-init/pod_serving_single.yml @@ -0,0 +1,49 @@ +apiVersion: v1 +kind: Pod +metadata: + name: pod-serving-{{ .Iteration }}-{{ .Replica }}-{{.JobName }} + namespace: serving-ns-1 + annotations: + gateway-ip: "192.168.{{ add 217 .Replica }}.{{ .Iteration }}" + k8s.ovn.org/routing-namespaces: served-ns-1 + k8s.ovn.org/routing-network: serving-ns-1/sriov-net-1 + k8s.ovn.org/bfd-enabled: "" + k8s.v1.cni.cncf.io/networks: |- + [{ "name": "sriov-net-1", "ips": [ "192.168.{{ add 217 .Replica }}.{{ .Iteration }}/21" ]}] + k8s.v1.cni.cncf.io/network-status: |- + [{ + "name": "serving-ns-1/sriov-net-1", + "interface": "net1", + "ips": [ + "192.168.{{ add 217 .Replica }}.{{ .Iteration }}" + ], + "dns": {} + }] +spec: + containers: + - name: bfd + image: quay.io/fpaoline/ovnkbfdtest:0.2 + command: ["/bin/sh","-c"] + args: ["/usr/lib/frr/frrinit.sh start && sleep infinity"] + ports: + - containerPort: 3784 + protocol: UDP + securityContext: + privileged: true + volumeMounts: + - name: config-volume + mountPath: /etc/frr + volumes: + - name: config-volume + configMap: + name: frr + - name: kubeconfig + secret: + secretName: kubeconfig + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-role.kubernetes.io/worker-spk + operator: Exists diff --git a/cmd/config/web-burner-init/sriov_network.yml b/cmd/config/web-burner-init/sriov_network.yml new file mode 100644 index 00000000..5aaddbf1 --- /dev/null +++ b/cmd/config/web-burner-init/sriov_network.yml @@ -0,0 +1,15 @@ +apiVersion: sriovnetwork.openshift.io/v1 +kind: SriovNetwork +metadata: + name: sriov-net-{{ .Iteration }} + namespace: openshift-sriov-network-operator +spec: + ipam: | + { + "type": "static" + } + spoofChk: "off" + trust: "on" + resourceName: intelnics2 + networkNamespace: serving-ns-{{ .Iteration }} + diff --git a/cmd/config/web-burner-init/web-burner-init.yml b/cmd/config/web-burner-init/web-burner-init.yml new file mode 100644 index 00000000..13e0c0e3 --- /dev/null +++ b/cmd/config/web-burner-init/web-burner-init.yml @@ -0,0 +1,194 @@ +--- +global: + gc: {{.GC}} + gcMetrics: {{.GC_METRICS}} + indexerConfig: + esServers: ["{{.ES_SERVER}}"] + insecureSkipVerify: true + defaultIndex: {{.ES_INDEX}} + type: {{.INDEXING_TYPE}} + measurements: + - name: podLatency + thresholds: + - conditionType: Ready + metric: P99 + threshold: {{.POD_READY_THRESHOLD}} + +jobs: + + - name: create-networks-job + jobType: create + jobIterations: {{ mul .LIMITCOUNT .SCALE }} + qps: 10 + burst: 10 + namespacedIterations: {{ if contains .SRIOV "true" }} false {{ else }} true {{ end }} + cleanup: false + namespace: {{ if contains .SRIOV "true" }} openshift-sriov-network-operator {{ else }} serving-ns {{ end }} + podWait: false + waitWhenFinished: false + verifyObjects: true + errorOnVerify: false + jobIterationDelay: 1s + jobPause: 0s + preLoadImages: false + objects: + - objectTemplate: {{ if contains .SRIOV "true" }} sriov_network.yml {{ else }} macvlan_network.yml {{ end }} + replicas: 1 + inputVars: + bridge: {{ .BRIDGE }} + + - name: create-serviceaccounts-job + jobType: create + jobIterations: {{ mul .LIMITCOUNT .SCALE }} + qps: 20 + burst: 20 + namespacedIterations: true + cleanup: false + namespace: serving-ns + podWait: false + waitWhenFinished: false + verifyObjects: true + errorOnVerify: false + jobIterationDelay: 1s + jobPause: 0s + preLoadImages: false + objects: + - objectTemplate: permissionsServiceAccount.yml + replicas: 1 + + - name: create-clusterrole-job + jobType: create + jobIterations: 1 + qps: 20 + burst: 20 + namespacedIterations: false + cleanup: false + namespace: default + podWait: false + waitWhenFinished: false + verifyObjects: true + errorOnVerify: false + jobIterationDelay: 1s + jobPause: 0s + preLoadImages: false + objects: + - objectTemplate: permissionsClusterRole.yml + replicas: 1 + + - name: create-clusterbindings-jobs + jobType: create + jobIterations: {{ mul .LIMITCOUNT .SCALE }} + qps: 20 + burst: 20 + namespacedIterations: true + cleanup: false + namespace: serving-ns + podWait: false + waitWhenFinished: false + verifyObjects: true + errorOnVerify: false + jobIterationDelay: 1s + jobPause: 0s + preLoadImages: false + objects: + - objectTemplate: permissionsClusterRoleBinding.yml + replicas: 1 + + - name: create-rolebindings-jobs + jobType: create + jobIterations: {{ mul .LIMITCOUNT .SCALE }} + qps: 20 + burst: 20 + namespacedIterations: true + cleanup: false + namespace: serving-ns + podWait: false + waitWhenFinished: false + verifyObjects: true + errorOnVerify: false + jobIterationDelay: 1s + jobPause: 0s + preLoadImages: false + objects: + - objectTemplate: permissionsRoleBinding.yml + replicas: 1 + + - name: create-cms-job + jobType: create + jobIterations: {{ mul .LIMITCOUNT .SCALE }} + qps: 20 + burst: 20 + namespacedIterations: true + cleanup: false + namespace: serving-ns + podWait: false + waitWhenFinished: false + verifyObjects: true + errorOnVerify: false + jobIterationDelay: 1s + jobPause: 0s + preLoadImages: false + objects: + - objectTemplate: cm_frr.yml + replicas: 1 + + - name: init-served-job + jobType: create + jobIterations: {{ mul .LIMITCOUNT .SCALE }} + qps: 20 + burst: 20 + namespacedIterations: true + cleanup: false + namespace: served-ns + podWait: false + waitWhenFinished: true + verifyObjects: true + errorOnVerify: true + jobIterationDelay: 0s + jobPause: 0s + preLoadImages: false + objects: + - objectTemplate: pod_served.yml + replicas: 1 + +{{ if contains .CRD "true" }} + - name: init-externalroutes-job + jobType: create + jobIterations: {{ mul .LIMITCOUNT .SCALE }} + qps: 20 + burst: 20 + namespacedIterations: false + cleanup: false + namespace: default + podWait: false + waitWhenFinished: true + verifyObjects: true + errorOnVerify: true + jobIterationDelay: 0s + jobPause: 0s + preLoadImages: false + objects: + - objectTemplate: adminPolicyBasedExternalRoute.yml + replicas: 1 +{{ end }} + + - name: serving-job + jobType: create + jobIterations: {{ mul .LIMITCOUNT .SCALE }} + qps: 20 + burst: 20 + namespacedIterations: true + cleanup: false + namespace: serving-ns + podWait: false + waitWhenFinished: true + verifyObjects: true + errorOnVerify: true + jobIterationDelay: 0s + jobPause: 0s + preLoadImages: false + objects: + - objectTemplate: {{ if contains .BFD "true" }} pod_serving.yml {{ else }} pod_serving_no_bfd.yml {{ end }} + replicas: 4 + inputVars: + crd: "{{ $.CRD }}" diff --git a/cmd/config/web-burner-node-density/node_density_pod_served.yml b/cmd/config/web-burner-node-density/node_density_pod_served.yml new file mode 100644 index 00000000..465f99c4 --- /dev/null +++ b/cmd/config/web-burner-node-density/node_density_pod_served.yml @@ -0,0 +1,46 @@ +--- +apiVersion: v1 +kind: Pod +metadata: + name: pod-served-{{ .Replica }} + labels: + app: pod-served-{{ .Replica }} + ns: served-ns-{{ .Replica }} +spec: + containers: + - name: perfapp-1 + image: quay.io/openshift-scale/nginx + imagePullPolicy: IfNotPresent + livenessProbe: + httpGet: + path: / + port: 8080 + periodSeconds: 30 + failureThreshold: 1 + timeoutSeconds: 15 + initialDelaySeconds: 5 + ports: + - containerPort: 8080 + - args: + - sleep + - infinity + name: app + image: quay.io/centos/centos + imagePullPolicy: IfNotPresent + {{ if contains .probe "true" }} + readinessProbe: + exec: + command: + - ping + - -c1 + - 172.18.0.10 + {{ end }} + # nodeSelector: + # kubernetes.io/hostname: worker{{if eq .Iteration 81}}{{printf "%03d" (add .Iteration 2)}}{{else if eq .Iteration 82}}{{printf "%03d" (add .Iteration 1)}}{{else if eq .Iteration 98}}{{printf "%03d" (add .Iteration 2)}}{{else if eq .Iteration 110}}{{printf "%03d" (add .Iteration 2)}}{{else}}{{printf "%03d" (add .Iteration 3)}}{{end}}-r640 + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-role.kubernetes.io/worker-spk + operator: DoesNotExist diff --git a/cmd/config/web-burner-node-density/node_density_pod_service_served.yml b/cmd/config/web-burner-node-density/node_density_pod_service_served.yml new file mode 100644 index 00000000..8c5a2359 --- /dev/null +++ b/cmd/config/web-burner-node-density/node_density_pod_service_served.yml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: service-{{ .Iteration }}-{{ .Replica }} +spec: + ports: + - name: http + port: 8080 + protocol: TCP + targetPort: 8080 + selector: + app: pod-served-{{ .Iteration }} diff --git a/cmd/config/web-burner-node-density/web-burner-node-density.yml b/cmd/config/web-burner-node-density/web-burner-node-density.yml new file mode 100644 index 00000000..60ca8376 --- /dev/null +++ b/cmd/config/web-burner-node-density/web-burner-node-density.yml @@ -0,0 +1,135 @@ +--- +global: + gc: {{.GC}} + gcMetrics: {{.GC_METRICS}} + indexerConfig: + esServers: ["{{.ES_SERVER}}"] + insecureSkipVerify: true + defaultIndex: {{.ES_INDEX}} + type: {{.INDEXING_TYPE}} + measurements: + - name: podLatency + thresholds: + - conditionType: Ready + metric: P99 + threshold: {{.POD_READY_THRESHOLD}} + +jobs: +{{ $normalLimit := mul .LIMITCOUNT .SCALE | int }} +{{ range $val := untilStep 1 (add $normalLimit 1|int) 1 }} + - name: normal-service-job-{{ $val }} + jobType: create + jobIterations: 1 + qps: {{ $.QPS }} + burst: {{ $.BURST }} + namespacedIterations: false + cleanup: false + namespace: served-ns-{{ (sub $val 1|int) }} + podWait: false + waitWhenFinished: false + verifyObjects: true + errorOnVerify: false + jobIterationDelay: 0s + jobPause: 0s + preLoadImages: false + objects: + - objectTemplate: node_density_pod_service_served.yml + replicas: 1 +{{ end }} + +{{ range $index, $val := untilStep 1 (add $normalLimit 1|int) 1 }} + - name: normal-job-{{ $val }} + jobType: create + jobIterations: 1 + qps: {{ $.QPS }} + burst: {{ $.BURST }} + namespacedIterations: false + cleanup: false + namespace: served-ns-{{ (sub $val 1|int) }} + podWait: true + waitWhenFinished: true + verifyObjects: true + errorOnVerify: true + jobIterationDelay: 0s + jobPause: 0s + preLoadImages: false + objects: + - objectTemplate: node_density_pod_served.yml + replicas: 60 + inputVars: + probe: "{{ $.PROBE }}" + waitOptions: + forCondition: "Ready" +{{ end }} + +{{ $servedLimit := mul .LIMITCOUNT .SCALE | int }} +{{ range $index, $val := untilStep 1 (add $servedLimit 1|int) 1 }} + - name: served-service-job-{{ $val }} + jobType: create + jobIterations: 1 + qps: {{ $.QPS }} + burst: {{ $.BURST }} + namespacedIterations: false + cleanup: false + namespace: served-ns-{{ (sub $val 1|int) }} + podWait: false + waitWhenFinished: false + verifyObjects: true + errorOnVerify: false + jobIterationDelay: 0s + jobPause: 0s + preLoadImages: false + objects: + - objectTemplate: node_density_pod_service_served.yml + replicas: 1 +{{ end }} + +{{ $lastLimit := add $servedLimit -1 | int }} +{{ range $index, $val := untilStep 1 (add $lastLimit 1|int) 1 }} + - name: served-job-{{ $val }} + jobType: create + jobIterations: 1 + qps: {{ $.QPS }} + burst: {{ $.BURST }} + namespacedIterations: false + cleanup: false + namespace: served-ns-{{ (sub $val 1|int) }} + podWait: true + waitWhenFinished: true + verifyObjects: true + errorOnVerify: false + jobIterationDelay: 0s + jobPause: 0s + preLoadImages: false + objects: + - objectTemplate: node_density_pod_served.yml + replicas: 3 + waitOptions: + forCondition: Ready + inputVars: + probe: "{{ $.PROBE }}" +{{ end }} + + + - name: served-job-{{ $servedLimit }} + jobType: create + jobIterations: 1 + qps: {{ .QPS }} + burst: {{ .BURST }} + namespacedIterations: false + cleanup: false + namespace: served-ns-{{ (sub $servedLimit 1|int) }} + podWait: true + waitWhenFinished: true + verifyObjects: true + errorOnVerify: false + jobIterationDelay: 0s + jobPause: 0s + preLoadImages: false + objects: + - objectTemplate: node_density_pod_served.yml + replicas: 3 + waitOptions: + forCondition: Ready + inputVars: + probe: "{{ $.PROBE }}" diff --git a/cmd/ocp.go b/cmd/ocp.go index 27d852b5..bb2381ca 100644 --- a/cmd/ocp.go +++ b/cmd/ocp.go @@ -17,13 +17,15 @@ package main import ( "embed" _ "embed" + "fmt" + "log" "os" "time" "github.com/cloud-bulldozer/go-commons/indexers" - "github.com/cloud-bulldozer/kube-burner/pkg/workloads" + "github.com/kube-burner/kube-burner/pkg/util" + "github.com/kube-burner/kube-burner/pkg/workloads" uid "github.com/satori/go.uuid" - log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "kube-burner.io/ocp" ) @@ -32,49 +34,60 @@ import ( var ocpConfig embed.FS func openShiftCmd() *cobra.Command { - ocpCmd := &cobra.Command{ - Use: "ocp", - Short: "OpenShift wrapper", - Long: `This subcommand is meant to be used against OpenShift clusters and serve as a shortcut to trigger well-known workloads`, - } var workloadConfig workloads.Config var wh workloads.WorkloadHelper - ocpCmd.PersistentFlags().StringVar(&workloadConfig.EsServer, "es-server", "", "Elastic Search endpoint") - ocpCmd.PersistentFlags().StringVar(&workloadConfig.Esindex, "es-index", "", "Elastic Search index") + var metricsProfileType string + var esServer, esIndex string + var QPS, burst int + var gc, gcMetrics bool + ocpCmd := &cobra.Command{ + Use: "kube-burner-ocp", + Long: `kube-burner plugin designed to be used with OpenShift clusters as a quick way to run well-known workloads`, + } + ocpCmd.PersistentFlags().StringVar(&esServer, "es-server", "", "Elastic Search endpoint") + ocpCmd.PersistentFlags().StringVar(&esIndex, "es-index", "", "Elastic Search index") localIndexing := ocpCmd.PersistentFlags().Bool("local-indexing", false, "Enable local indexing") ocpCmd.PersistentFlags().StringVar(&workloadConfig.MetricsEndpoint, "metrics-endpoint", "", "YAML file with a list of metric endpoints") ocpCmd.PersistentFlags().BoolVar(&workloadConfig.Alerting, "alerting", true, "Enable alerting") ocpCmd.PersistentFlags().StringVar(&workloadConfig.UUID, "uuid", uid.NewV4().String(), "Benchmark UUID") ocpCmd.PersistentFlags().DurationVar(&workloadConfig.Timeout, "timeout", 4*time.Hour, "Benchmark timeout") - ocpCmd.PersistentFlags().IntVar(&workloadConfig.QPS, "qps", 20, "QPS") - ocpCmd.PersistentFlags().IntVar(&workloadConfig.Burst, "burst", 20, "Burst") - ocpCmd.PersistentFlags().BoolVar(&workloadConfig.Gc, "gc", true, "Garbage collect created namespaces") - ocpCmd.PersistentFlags().BoolVar(&workloadConfig.GcMetrics, "gc-metrics", false, "Collect metrics during garbage collection") - userMetadata := ocpCmd.PersistentFlags().String("user-metadata", "", "User provided metadata file, in YAML format") + ocpCmd.PersistentFlags().IntVar(&QPS, "qps", 20, "QPS") + ocpCmd.PersistentFlags().IntVar(&burst, "burst", 20, "Burst") + ocpCmd.PersistentFlags().BoolVar(&gc, "gc", true, "Garbage collect created namespaces") + ocpCmd.PersistentFlags().BoolVar(&gcMetrics, "gc-metrics", false, "Collect metrics during garbage collection") + ocpCmd.PersistentFlags().StringVar(&workloadConfig.UserMetadata, "user-metadata", "", "User provided metadata file, in YAML format") extract := ocpCmd.PersistentFlags().Bool("extract", false, "Extract workload in the current directory") - ocpCmd.PersistentFlags().StringVar(&workloadConfig.ProfileType, "profile-type", "both", "Metrics profile to use, supported options are: regular, reporting or both") + ocpCmd.PersistentFlags().StringVar(&metricsProfileType, "profile-type", "both", "Metrics profile to use, supported options are: regular, reporting or both") ocpCmd.MarkFlagsRequiredTogether("es-server", "es-index") ocpCmd.MarkFlagsMutuallyExclusive("es-server", "local-indexing") ocpCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) { - if workloadConfig.EsServer != "" || *localIndexing { - if workloadConfig.EsServer != "" { + util.ConfigureLogging(cmd) + if *extract { + if err := workloads.ExtractWorkload(ocpConfig, cmd.Name(), "alerts.yml", "metrics.yml", "metrics-aggregated.yml", "metrics-report.yml"); err != nil { + log.Fatal(err.Error()) + } + os.Exit(0) + } + if esServer != "" || *localIndexing { + if esServer != "" { workloadConfig.Indexer = indexers.ElasticIndexer } else { workloadConfig.Indexer = indexers.LocalIndexer } + wh = workloads.NewWorkloadHelper(workloadConfig, workloads.OCP, ocpConfig) } - wh = workloads.NewWorkloadHelper(workloadConfig, ocpConfig) - if *extract { - if err := wh.ExtractWorkload(cmd.Name(), ocp.MetricsProfileMap[cmd.Name()]); err != nil { - log.Fatal(err) - } - os.Exit(0) + envVars := map[string]string{ + "ES_SERVER": esServer, + "ES_INDEX": esIndex, + "QPS": fmt.Sprintf("%d", QPS), + "BURST": fmt.Sprintf("%d", burst), + "GC": fmt.Sprintf("%v", gc), + "GC_METRICS": fmt.Sprintf("%v", gcMetrics), + "INDEXING_TYPE": string(wh.Indexer), } - err := wh.GatherMetadata(*userMetadata) - if err != nil { - log.Fatal(err.Error()) + for k, v := range envVars { + os.Setenv(k, v) } - wh.SetKubeBurnerFlags() } ocpCmd.AddCommand( ocp.NewClusterDensity(&wh, "cluster-density-v2"), @@ -86,9 +99,13 @@ func openShiftCmd() *cobra.Command { ocp.NewNodeDensity(&wh), ocp.NewNodeDensityHeavy(&wh), ocp.NewNodeDensityCNI(&wh), - ocp.NewIndex(&wh.MetricsEndpoint, &wh.OcpMetaAgent), + ocp.NewIndex(&wh.MetricsEndpoint, &wh.MetadataAgent), ocp.NewPVCDensity(&wh), + ocp.NewWebBurner(&wh, "web-burner-init"), + ocp.NewWebBurner(&wh, "web-burner-node-density"), + ocp.NewWebBurner(&wh, "web-burner-cluster-density"), ) + util.SetupCmd(ocpCmd) return ocpCmd } diff --git a/common.go b/common.go new file mode 100644 index 00000000..de4f7422 --- /dev/null +++ b/common.go @@ -0,0 +1,38 @@ +// Copyright 2023 The Kube-burner Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ocp + +import ( + "os" + + "github.com/kube-burner/kube-burner/pkg/measurements/types" + "github.com/spf13/cobra" +) + +func getMetrics(cmd *cobra.Command, metricsProfile string) []string { + + var metricsProfiles []string + profileType, _ := cmd.Root().PersistentFlags().GetString("profile-type") + switch ProfileType(profileType) { + case Reporting: + metricsProfiles = []string{"metrics-report.yml"} + os.Setenv("POD_LATENCY_METRICS", string(types.Quantiles)) + case Regular: + metricsProfiles = []string{metricsProfile} + case Both: + metricsProfiles = []string{"metrics-report.yml", metricsProfile} + } + return metricsProfiles +} diff --git a/crd-scale.go b/crd-scale.go index 09b9d9f8..ce862f13 100644 --- a/crd-scale.go +++ b/crd-scale.go @@ -18,7 +18,7 @@ import ( "fmt" "os" - "github.com/cloud-bulldozer/kube-burner/pkg/workloads" + "github.com/kube-burner/kube-burner/pkg/workloads" "github.com/spf13/cobra" ) @@ -35,7 +35,7 @@ func NewCrdScale(wh *workloads.WorkloadHelper) *cobra.Command { }, Run: func(cmd *cobra.Command, args []string) { - wh.Run(cmd.Name(), MetricsProfileMap[cmd.Name()]) + wh.Run(cmd.Name(), getMetrics(cmd, "metrics-aggregated.yml"), alertsProfiles) }, } cmd.Flags().IntVar(&iterations, "iterations", 0, "Number of CRDs to create") diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..45ee7f20 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,291 @@ +# OpenShift Wrapper + +This plugin is a very opinionated OpenShift wrapper designed to simplify the execution of different workloads in this Kubernetes distribution. + +Executed with `kube-burner-ocp`, it looks like: + +```console +$ kube-burner-ocp help +kube-burner plugin designed to be used with OpenShift clusters as a quick way to run well-known workloads + +Usage: + kube-burner-ocp [command] + +Available Commands: + cluster-density-ms Runs cluster-density-ms workload + cluster-density-v2 Runs cluster-density-v2 workload + completion Generate the autocompletion script for the specified shell + crd-scale Runs crd-scale workload + help Help about any command + index Runs index sub-command + networkpolicy-matchexpressions Runs networkpolicy-matchexpressions workload + networkpolicy-matchlabels Runs networkpolicy-matchlabels workload + networkpolicy-multitenant Runs networkpolicy-multitenant workload + node-density Runs node-density workload + node-density-cni Runs node-density-cni workload + node-density-heavy Runs node-density-heavy workload + pvc-density Runs pvc-density workload + version Print the version number of kube-burner + web-burner-cluster-density Runs web-burner-cluster-density workload + web-burner-init Runs web-burner-init workload + web-burner-node-density Runs web-burner-node-density workload + +Flags: + --alerting Enable alerting (default true) + --burst int Burst (default 20) + --es-index string Elastic Search index + --es-server string Elastic Search endpoint + --extract Extract workload in the current directory + --gc Garbage collect created namespaces (default true) + --gc-metrics Collect metrics during garbage collection + --local-indexing Enable local indexing + --metrics-endpoint string YAML file with a list of metric endpoints + --profile-type string Metrics profile to use, supported options are: regular, reporting or both (default "both") + --qps int QPS (default 20) + --timeout duration Benchmark timeout (default 4h0m0s) + --user-metadata string User provided metadata file, in YAML format + --uuid string Benchmark UUID (default "0827cb6a-9367-4f0b-b11c-75030c69479e") + --log-level string Allowed values: debug, info, warn, error, fatal (default "info") + -h, --help help for kube-burner-ocp +``` + +## Usage + +Some of the benefits the OCP wrapper provides are: + +- Simplified execution of the supported workloads. (Only some flags are required) +- Indexes OpenShift metadata along with the Benchmark result. This document can be found with the following query: `uuid: AND metricName.keyword: "clusterMetadata"` +- Prevents modifying configuration files to tweak some of the parameters of the workloads. +- Discovers the Prometheus URL and authentication token, so the user does not have to perform those operations before using them. +- Workloads configuration is directly embedded in the binary. + +Running node-density with 100 pods per node + +```console +kube-burner-ocp node-density --pods-per-node=100 +``` + +With the command above, the wrapper will calculate the required number of pods to deploy across all worker nodes of the cluster. + +## Multiple endpoints support + +The flag `--metrics-endpoint` can be used to interact with multiple Prometheus endpoints +For example: + +```console +kube-burner-ocp cluster-density-v2 --iterations=1 --churn-duration=2m0s --es-index kube-burner --es-server https://www.esurl.com:443 --metrics-endpoint metrics-endpoints.yaml +``` + +## Cluster density workloads + +This workload family is a control-plane density focused workload that that creates different objects across the cluster. There are 2 different variants [cluster-density-v2](#cluster-density-v2) and [cluster-density-ms](#cluster-density-ms). + +Each iteration of these create a new namespace, the three support similar configuration flags. Check them out from the subcommand help. + +!!! Info + Workload churning of 1h is enabled by default in the `cluster-density` workloads; you can disable it by passing `--churn=false` to the workload subcommand. + +### cluster-density-v2 + +Each iteration creates the following objects in each of the created namespaces: + +- 1 image stream. +- 1 build. The OCP internal container registry must be set-up previously because the resulting container image will be pushed there. +- 3 deployments with two pod 2 replicas (nginx) mounting 4 secrets, 4 config maps, and 1 downward API volume each. +- 2 deployments with two pod 2 replicas (curl) mounting 4 Secrets, 4 config maps and 1 downward API volume each. These pods have configured a readiness probe that makes a request to one of the services and one of the routes created by this workload every 10 seconds. +- 5 services, each one pointing to the TCP/8080 port of one of the nginx deployments. +- 2 edge routes pointing to the to first and second services respectively. +- 10 secrets containing a 2048-character random string. +- 10 config maps containing a 2048-character random string. +- 3 network policies: + - deny-all traffic + - allow traffic from client/nginx pods to server/nginx pods + - allow traffic from openshift-ingress namespace (where routers are deployed by default) to the namespace + +### cluster-density-ms + +Lightest version of this workload family, each iteration the following objects in each of the created namespaces: + +- 1 image stream. +- 4 deployments with two pod replicas (pause) mounting 4 secrets, 4 config maps, and 1 downward API volume each. +- 2 services, each one pointing to the TCP/8080 and TCP/8443 ports of the first and second deployment respectively. +- 1 edge route pointing to the to first service. +- 20 secrets containing a 2048-character random string. +- 10 config maps containing a 2048-character random string. + +## Node density workloads + +The workloads of this family create a single namespace with a set of pods, deployments, and services depending on the workload. + +### node-density + +This workload is meant to fill with pause pods all the worker nodes from the cluster. It can be customized with the following flags. This workload is usually used to measure the Pod's ready latency KPI. + +### node-density-cni + +It creates two deployments, a client/curl and a server/nxing, and 1 service backed by the previous server pods. The client application has configured an startup probe that makes requests to the previous service every second with a timeout of 600s. + +Note: This workload calculates the number of iterations to create from the number of nodes and desired pods per node. In order to keep the test scalable and performant, chunks of 1000 iterations will by broken into separate namespaces, using the config variable `iterationsPerNamespace`. + +### node-density-heavy + +Creates two deployments, a postgresql database, and a simple client that performs periodic insert queries (configured through liveness and readiness probes) on the previous database and a service that is used by the client to reach the database. + +Note: this workload calculates the number of iterations to create from the number of nodes and desired pods per node. In order to keep the test scalable and performant, chunks of 1000 iterations will by broken into separate namespaces, using the config variable `iterationsPerNamespace`. + +## Network Policy workloads + +With the help of [networkpolicy](https://kubernetes.io/docs/concepts/services-networking/network-policies/) object we can control traffic flow at the IP address or port level in Kubernetes. A networkpolicy can come in various shapes and sizes. Allow traffic from a specific namespace, Deny traffic from a specific pod IP, Deny all traffic, etc. Hence we have come up with a few test cases which try to cover most of them. They are as follows. + +### networkpolicy-multitenant + +- 500 namespaces +- 20 pods in each namespace. Each pod acts as a server and a client +- Default deny networkpolicy is applied first that blocks traffic to any test namespace +- 3 network policies in each namespace that allows traffic from the same namespace and two other namespaces using namespace selectors + +### networkpolicy-matchlabels + +- 5 namespaces +- 100 pods in each namespace. Each pod acts as a server and a client +- Each pod with 2 labels and each label shared is by 5 pods +- Default deny networkpolicy is applied first +- Then for each unique label in a namespace we have a networkpolicy with that label as a podSelector which allows traffic from pods with some other randomly selected label. This translates to 40 networkpolicies/namespace + +### networkpolicy-matchexpressions + +- 5 namespaces +- 25 pods in each namespace. Each pod acts as a server and a client +- Each pod with 2 labels and each label shared is by 5 pods +- Default deny networkpolicy is applied first +- Then for each unique label in a namespace we have a networkpolicy with that label as a podSelector which allows traffic from pods which *don't* have some other randomly-selected label. This translates to 10 networkpolicies/namespace + +## Web-burner workloads +This workload is meant to emulate some telco specific workloads. Before running *web-burner-node-density* or *web-burner-cluster-density* load the environment with *web-burner-init* first (without the garbage collection flag: `--gc=false`). + +Pre-requisites: + - At least two worker nodes + - At least one of the worker nodes must have the `node-role.kubernetes.io/worker-spk` label + +### web-burner-init + +- 35 (macvlan/sriov) networks for 35 lb namespace +- 35 lb-ns + - 1 frr config map, 4 emulated lb pods on each namespace +- 35 app-ns + - 1 emulated lb pod on each namespace for bfd session + +### web-burner-node-density +- 35 app-ns + - 3 app pods and services on each namespace +- 35 normal-ns + - 1 service with 60 normal pod endpoints on each namespace + +### web-burner-cluster-density +- 20 normal-ns + - 30 configmaps, 38 secrets, 38 normal pods and services, 5 deployments with 2 replica pods on each namespace +- 35 served-ns + - 3 app pods on each namespace +- 2 app-served-ns + - 1 service(15 ports) with 84 pod endpoints, 1 service(15 ports) with 56 pod endpoints, 1 service(15 ports) with 25 pod endpoints + - 3 service(15 ports each) with 24 pod endpoints, 3 service(15 ports each) with 14 pod endpoints + - 6 service(15 ports each) with 12 pod endpoints, 6 service(15 ports each) with 10 pod endpoints, 6 service(15 ports each) with 9 pod endpoints + - 12 service(15 ports each) with 8 pod endpoints, 12 service(15 ports each) with 6 pod endpoints, 12 service(15 ports each) with 5 pod endpoints + - 29 service(15 ports each) with 4 pod endpoints, 29 service(15 ports each) with 6 pod endpoints + +## Index + +Just like the regular kube-burner, `kube-burner-ocp` also has an indexing functionality which is exposed as `index` subcommand. + +```console +$ kube-burner-ocp index --help +If no other indexer is specified, local indexer is used by default + +Usage: + kube-burner-ocp index [flags] + +Flags: + -m, --metrics-profile string Metrics profile file (default "metrics.yml") + --metrics-directory string Directory to dump the metrics files in, when using default local indexing (default "collected-metrics") + -s, --step duration Prometheus step size (default 30s) + --start int Epoch start time + --end int Epoch end time + -j, --job-name string Indexing job name (default "kube-burner-ocp-indexing") + --user-metadata string User provided metadata file, in YAML format + -h, --help help for index +``` + +## Metrics-profile type + +By specifying `--profile-type`, kube-burner can use two different metrics profiles when scraping metrics from prometheus. By default is configured with `both`, meaning that it will use the regular metrics profiles bound to the workload in question and the reporting metrics profile. + +When using the regular profiles ([metrics-aggregated](https://github.com/kube-burner/kube-burner-ocp/blob/master/cmd/config/metrics-aggregated.yml) or [metrics](https://github.com/kube-burner/kube-burner-ocp/blob/master/cmd/config/metrics.yml)), kube-burner scrapes and indexes metrics timeseries. + +The reporting profile is very useful to reduce the number of documents sent to the configured indexer. Thanks to the combination of aggregations and instant queries for prometheus metrics, and 4 summaries for latency measurements, only a few documents will be indexed per benchmark. This flag makes possible to specify one or both of these profiles indistinctly. + +## Customizing workloads + +It is possible to customize any of the above workload configurations by extracting, updating, and finally running it: + +```console +$ kube-burner-ocp node-density --extract +$ ls +alerts.yml metrics.yml node-density.yml pod.yml metrics-report.yml +$ vi node-density.yml # Perform modifications accordingly +$ kube-burner-ocp node-density --pods-per-node=100 # Run workload +``` + +## Cluster metadata + +When the benchmark finishes, kube-burner will index the cluster metadata in the configured indexer. Currently. this is based on the following Golang struct: + +```golang +type BenchmarkMetadata struct { + ocpmetadata.ClusterMetadata + UUID string `json:"uuid"` + Benchmark string `json:"benchmark"` + Timestamp time.Time `json:"timestamp"` + EndDate time.Time `json:"endDate"` + Passed bool `json:"passed"` + UserMetadata map[string]interface{} `json:"metadata,omitempty"` +} +``` + +Where `ocpmetadata.ClusterMetadata` is an embed struct inherited from the [go-commons library](https://github.com/cloud-bulldozer/go-commons/blob/main/ocp-metadata/types.go), which has the following fields: + +```golang +// Type to store cluster metadata +type ClusterMetadata struct { + MetricName string `json:"metricName,omitempty"` + Platform string `json:"platform"` + OCPVersion string `json:"ocpVersion"` + OCPMajorVersion string `json:"ocpMajorVersion"` + K8SVersion string `json:"k8sVersion"` + MasterNodesType string `json:"masterNodesType"` + WorkerNodesType string `json:"workerNodesType"` + MasterNodesCount int `json:"masterNodesCount"` + InfraNodesType string `json:"infraNodesType"` + WorkerNodesCount int `json:"workerNodesCount"` + InfraNodesCount int `json:"infraNodesCount"` + TotalNodes int `json:"totalNodes"` + SDNType string `json:"sdnType"` + ClusterName string `json:"clusterName"` + Region string `json:"region"` + ExecutionErrors string `json:"executionErrors"` +} +``` + +MetricName is hardcoded to `clusterMetadata` + + +!!! Info + It's important to note that every document indexed when using an OCP wrapper workload will include an small subset of the previous fields: + ```yaml + platform + ocpVersion + ocpMajorVersion + k8sVersion + totalNodes + sdnType + ``` + diff --git a/docs/media/logo/kube-burner-logo-black.png b/docs/media/logo/kube-burner-logo-black.png new file mode 100644 index 0000000000000000000000000000000000000000..03a2f218713b50e1d5a182ce3ec60efef94a57a5 GIT binary patch literal 14418 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 D1A!Ko literal 0 HcmV?d00001 diff --git a/docs/media/logo/kube-burner-logo-black.svg b/docs/media/logo/kube-burner-logo-black.svg new file mode 100644 index 00000000..7d27c6e5 --- /dev/null +++ b/docs/media/logo/kube-burner-logo-black.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/media/logo/kube-burner-logo-github.png b/docs/media/logo/kube-burner-logo-github.png new file mode 100644 index 0000000000000000000000000000000000000000..4a8884d97e0c7f115f9b05bd5016d78867ce0e25 GIT binary patch literal 44349 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%0A_0 zj4&YRStteff)zMB&&|uTq2J24r5Nr;?@07n3v_m?GSFs+6`51>vj!HlkcTI^%G`sg zHSxwsGw%C;lEF@{iKR#==j)c_u^6dnuNs%TB^F6Wk+>TC{?-5iaWcs1E*^OyRVE%* z9fyZhs6?O=6E+E5^gy_5RTz<A+2o~c>>FKWB#$y^ugu{%2;ZwT_&H^2Y1Uvtocd&#p4Wn@Iu_eR=n%Soip>b zV!3eJK2xHrh#2J&LbFW5Z9C3dnfAh=e_q5dHoN% zWNze2Y%8Bnj!|G`n94c**Ty43AZ>ok4J> z@7pWo5Pz4e0ccAJ>YU+sUSHSwkUh|*RJYz*VE@IIF$2*sZjcYFV0Zlr$2i+BaT=Ma zNxkcHH|pacV2;93y?Z;!h-)Z?jDxg%5h+!A*>v25DEml^o{X_f{QLbFW!to)BWucZVIUE=;*kngP!`|7kjTl#aOHs3IKNOV5HLb~CPPzjx)Wy(vO0anF?rhTkyQkGw6e z;$#i$^^Mfp&o6u*gtrKsX0Qax+qmEkf}8+M;G}^AV7vehg5Q4ySZu%tM>Y7^Z*>X0 zj0Y+VCh>p$`Wcab5dI%T{@Hl{TI64<_)kUt{jB~`^0jRq{RbG` BAL0N2 literal 0 HcmV?d00001 diff --git a/docs/media/logo/kube-burner-logo-mini.png b/docs/media/logo/kube-burner-logo-mini.png new file mode 100644 index 0000000000000000000000000000000000000000..85801e2b00eb7d691286ada198fa3f07e5c099c2 GIT binary patch literal 12515 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 literal 0 HcmV?d00001 diff --git a/docs/media/logo/kube-burner-logo.svg b/docs/media/logo/kube-burner-logo.svg new file mode 100644 index 00000000..fb565fa1 --- /dev/null +++ b/docs/media/logo/kube-burner-logo.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/media/logo/openshift-logo.png b/docs/media/logo/openshift-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3089b93ba578d00f941249c25b97734dc9180c72 GIT binary patch literal 22227 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 /home/rsevilla/labs/kube-burner/ diff --git a/go.sum b/go.sum index fcfbdc02..0ecd2264 100644 --- a/go.sum +++ b/go.sum @@ -93,8 +93,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloud-bulldozer/go-commons v1.0.11 h1:zSaSIh2xUsew9YXeeaOf0hwnvsQ3oDH72zF8aPkikqw= -github.com/cloud-bulldozer/go-commons v1.0.11/go.mod h1:dUXxFH2mosY5OYY+cFPS3XvCekUTZRtMPuK/ni8Azq8= +github.com/cloud-bulldozer/go-commons v1.0.13 h1:w/+Ux9hyZEzOTlttYVT6w7xXPIZdHlt3TSbwStXljpA= +github.com/cloud-bulldozer/go-commons v1.0.13/go.mod h1:dUXxFH2mosY5OYY+cFPS3XvCekUTZRtMPuK/ni8Azq8= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -110,7 +110,7 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -170,8 +170,8 @@ github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaL github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= +github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= @@ -225,8 +225,8 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/cel-go v0.9.0/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w= github.com/google/cel-spec v0.6.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA= -github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= -github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= +github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -309,6 +309,7 @@ github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -345,6 +346,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kube-burner/kube-burner v1.8.1-0.20240104114856-55c1c4ad77a7 h1:dfoy4XCuW4UnLS2+CPeAvCy7sZ4KjkDrP/0lDrM9qzM= +github.com/kube-burner/kube-burner v1.8.1-0.20240104114856-55c1c4ad77a7/go.mod h1:eII/kkP1XAQozIt/GG/C0YnMecZNmFC5vAy3y2GxDJw= +github.com/kube-burner/kube-burner v1.8.1-0.20240104190801-ee5222260254 h1:4iCioeJgJc+WhXSylH2nNKow814uYG1SrstBBGv/CTE= +github.com/kube-burner/kube-burner v1.8.1-0.20240104190801-ee5222260254/go.mod h1:eII/kkP1XAQozIt/GG/C0YnMecZNmFC5vAy3y2GxDJw= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -476,8 +481,8 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -492,8 +497,8 @@ github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= +github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -575,8 +580,8 @@ golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -762,13 +767,13 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -778,15 +783,15 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -852,6 +857,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.13.0 h1:a0T3bh+7fhRyqeNbiC3qVHYmkiQgit3wnNan/2c0HMM= +gonum.org/v1/gonum v0.13.0/go.mod h1:/WPYRckkfWrhWefxyYTfrTtQR0KH4iyHNuzxqXAKyAU= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -962,8 +969,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1007,18 +1014,18 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ= k8s.io/api v0.23.5/go.mod h1:Na4XuKng8PXJ2JsploYYrivXrINeTaycCGcYgF91Xm8= -k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY= -k8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0= +k8s.io/api v0.27.2 h1:+H17AJpUMvl+clT+BPnKf0E3ksMAzoBBg7CntpSuADo= +k8s.io/api v0.27.2/go.mod h1:ENmbocXfBT2ADujUXcBhHV55RIT31IIEvkntP6vZKS4= k8s.io/apiextensions-apiserver v0.23.5 h1:5SKzdXyvIJKu+zbfPc3kCbWpbxi+O+zdmAJBm26UJqI= k8s.io/apiextensions-apiserver v0.23.5/go.mod h1:ntcPWNXS8ZPKN+zTXuzYMeg731CP0heCTl6gYBxLcuQ= k8s.io/apimachinery v0.23.3/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= k8s.io/apimachinery v0.23.5/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= -k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8= -k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg= +k8s.io/apimachinery v0.27.2 h1:vBjGaKKieaIreI+oQwELalVG4d8f3YAMNpWLzDXkxeg= +k8s.io/apimachinery v0.27.2/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= k8s.io/apiserver v0.23.5/go.mod h1:7wvMtGJ42VRxzgVI7jkbKvMbuCbVbgsWFT7RyXiRNTw= k8s.io/client-go v0.23.5/go.mod h1:flkeinTO1CirYgzMPRWxUCnV0G4Fbu2vLhYCObnt/r4= -k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY= -k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4= +k8s.io/client-go v0.27.2 h1:vDLSeuYvCHKeoQRhCXjxXO45nHVv2Ip4Fe0MfioMrhE= +k8s.io/client-go v0.27.2/go.mod h1:tY0gVmUsHrAmjzHX9zs7eCjxcBsf8IiNe7KQ52biTcQ= k8s.io/code-generator v0.23.3/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= k8s.io/code-generator v0.23.5/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk= k8s.io/component-base v0.23.5/go.mod h1:c5Nq44KZyt1aLl0IpHX82fhsn84Sb0jjzwjpcA42bY0= @@ -1032,8 +1039,8 @@ k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= -k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= -k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= +k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= +k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= k8s.io/kubectl v0.27.2 h1:sSBM2j94MHBFRWfHIWtEXWCicViQzZsb177rNsKBhZg= k8s.io/kubectl v0.27.2/go.mod h1:GCOODtxPcrjh+EC611MqREkU8RjYBh10ldQCQ6zpFKw= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= diff --git a/hack/license.sh b/hack/license.sh new file mode 100755 index 00000000..8dd58250 --- /dev/null +++ b/hack/license.sh @@ -0,0 +1,4 @@ +#!/bin/bash + + +addlicense -l apache -c "The Kube-burner Authors." ./*.go diff --git a/hack/tag_name.sh b/hack/tag_name.sh new file mode 100755 index 00000000..0ff164f4 --- /dev/null +++ b/hack/tag_name.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +if [[ -z $(git branch --show-current) ]]; then + git describe --tags --abbrev=0 +else + git branch --show-current | sed 's/master/latest/g' +fi diff --git a/helpers.go b/helpers.go deleted file mode 100644 index 40121586..00000000 --- a/helpers.go +++ /dev/null @@ -1,285 +0,0 @@ -// Copyright 2022 The Kube-burner Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package ocp - -import ( - "embed" - "fmt" - "io" - "os" - "path" - "path/filepath" - "time" - - "github.com/cloud-bulldozer/go-commons/indexers" - ocpmetadata "github.com/cloud-bulldozer/go-commons/ocp-metadata" - "github.com/cloud-bulldozer/kube-burner/pkg/alerting" - "github.com/cloud-bulldozer/kube-burner/pkg/burner" - "github.com/cloud-bulldozer/kube-burner/pkg/config" - "github.com/cloud-bulldozer/kube-burner/pkg/measurements/types" - "github.com/cloud-bulldozer/kube-burner/pkg/prometheus" - "github.com/cloud-bulldozer/kube-burner/pkg/util" - "github.com/cloud-bulldozer/kube-burner/pkg/util/metrics" - log "github.com/sirupsen/logrus" - "k8s.io/client-go/tools/clientcmd" -) - -const ( - alertsProfile = "alerts.yml" - ocpCfgDir = "ocp-config" - stepSize = 30 * time.Second - clusterMetadataMetric = "clusterMetadata" - reportProfile = "metrics-report.yml" -) - -var configSpec config.Spec - -// NewWorkloadHelper initializes workloadHelper -func NewWorkloadHelper(config Config, ocpConfig embed.FS) WorkloadHelper { - var kubeconfig string - if os.Getenv("KUBECONFIG") != "" { - kubeconfig = os.Getenv("KUBECONFIG") - } else if _, err := os.Stat(filepath.Join(os.Getenv("HOME"), ".kube", "config")); kubeconfig == "" && !os.IsNotExist(err) { - kubeconfig = filepath.Join(os.Getenv("HOME"), ".kube", "config") - } - restConfig, err := clientcmd.BuildConfigFromFlags("", kubeconfig) - if err != nil { - log.Fatal(err) - } - ocpMetadata, err := ocpmetadata.NewMetadata(restConfig) - if err != nil { - log.Fatal(err.Error()) - } - return WorkloadHelper{ - Config: config, - ocpConfig: ocpConfig, - OcpMetaAgent: ocpMetadata, - restConfig: restConfig, - } -} - -var indexer *indexers.Indexer - -// SetKubeBurnerFlags configures the required environment variables and flags for kube-burner -func (wh *WorkloadHelper) SetKubeBurnerFlags() { - var err error - if wh.MetricsEndpoint == "" { - wh.prometheusURL, wh.prometheusToken, err = wh.OcpMetaAgent.GetPrometheus() - if err != nil { - log.Fatal("Error obtaining Prometheus information: ", err.Error()) - } - } - ingressDomain, err := wh.OcpMetaAgent.GetDefaultIngressDomain() - if err != nil { - log.Fatal("Error obtaining default ingress domain: ", err.Error()) - } - envVars := map[string]string{ - "ES_SERVER": wh.EsServer, - "ES_INDEX": wh.Esindex, - "QPS": fmt.Sprintf("%d", wh.QPS), - "BURST": fmt.Sprintf("%d", wh.Burst), - "INGRESS_DOMAIN": ingressDomain, - "GC": fmt.Sprintf("%v", wh.Gc), - "GC_METRICS": fmt.Sprintf("%v", wh.GcMetrics), - "INDEXING_TYPE": string(wh.Indexer), - } - for k, v := range envVars { - os.Setenv(k, v) - } -} - -func (wh *WorkloadHelper) GatherMetadata(userMetadata string) error { - var err error - wh.Metadata.ClusterMetadata, err = wh.OcpMetaAgent.GetClusterMetadata() - if err != nil { - return err - } - wh.Metadata.MetricName = clusterMetadataMetric - if userMetadata != "" { - userMetadataContent, err := util.ReadUserMetadata(userMetadata) - if err != nil { - log.Fatalf("Error reading provided user metadata: %v", err) - } - wh.Metadata.UserMetadata = userMetadataContent - } - wh.Metadata.UUID = wh.UUID - wh.Metadata.Timestamp = time.Now().UTC() - return nil -} - -func (wh *WorkloadHelper) run(workload, metricsProfile string) { - metadata := map[string]interface{}{ - "platform": wh.Metadata.Platform, - "ocpVersion": wh.Metadata.OCPVersion, - "ocpMajorVersion": wh.Metadata.OCPMajorVersion, - "k8sVersion": wh.Metadata.K8SVersion, - "totalNodes": wh.Metadata.TotalNodes, - "sdnType": wh.Metadata.SDNType, - } - // Combine provided userMetadata with the regular OCP metadata - for k, v := range wh.Metadata.UserMetadata { - metadata[k] = v - } - var f io.Reader - var rc int - var err error - var alertM *alerting.AlertManager - var prometheusClients []*prometheus.Prometheus - var alertMs []*alerting.AlertManager - var metricsEndpoints []prometheus.MetricEndpoint - var embedConfig bool - configFile := fmt.Sprintf("%s.yml", workload) - if _, err := os.Stat(configFile); err != nil { - f, err = util.ReadEmbedConfig(wh.ocpConfig, path.Join(ocpCfgDir, workload, configFile)) - embedConfig = true - if err != nil { - log.Fatalf("Error reading configuration file: %v", err.Error()) - } - } else { - log.Infof("File %v available in the current directory, using it", configFile) - f, err = util.ReadConfig(configFile) - if err != nil { - log.Fatalf("Error reading configuration file %s: %s", configFile, err) - } - } - configSpec, err = config.Parse(wh.UUID, f) - if err != nil { - log.Fatal(err) - } - if embedConfig { - configSpec.EmbedFS = wh.ocpConfig - configSpec.EmbedFSDir = path.Join(ocpCfgDir, workload) - } - indexerConfig := configSpec.GlobalConfig.IndexerConfig - if indexerConfig.Type != "" { - log.Infof("📁 Creating indexer: %s", indexerConfig.Type) - indexer, err = indexers.NewIndexer(indexerConfig) - if err != nil { - log.Fatalf("%v indexer: %v", indexerConfig.Type, err.Error()) - } - } - if wh.MetricsEndpoint != "" { - embedConfig = false - metrics.DecodeMetricsEndpoint(wh.MetricsEndpoint, &metricsEndpoints) - } else { - regularProfile := prometheus.MetricEndpoint{ - Endpoint: wh.prometheusURL, - AlertProfile: alertsProfile, - Profile: metricsProfile, - Token: wh.prometheusToken, - } - reportingProfile := prometheus.MetricEndpoint{ - Endpoint: wh.prometheusURL, - Profile: reportProfile, - Token: wh.prometheusToken, - } - switch ProfileType(wh.ProfileType) { - case regular: - metricsEndpoints = append(metricsEndpoints, regularProfile) - case reporting: - reportingProfile.AlertProfile = alertsProfile - metricsEndpoints = append(metricsEndpoints, reportingProfile) - for i := range configSpec.GlobalConfig.Measurements { - configSpec.GlobalConfig.Measurements[i].PodLatencyMetrics = types.Quantiles - } - case both: - metricsEndpoints = append(metricsEndpoints, regularProfile, reportingProfile) - default: - log.Fatalf("Metrics profile type not supported: %v", wh.ProfileType) - } - } - for _, metricsEndpoint := range metricsEndpoints { - // Updating the prometheus endpoint actually being used in spec. - auth := prometheus.Auth{ - Token: metricsEndpoint.Token, - SkipTLSVerify: true, - } - p, err := prometheus.NewPrometheusClient(configSpec, metricsEndpoint.Endpoint, auth, stepSize, metadata, embedConfig) - if err != nil { - log.Fatal(err) - } - p.ReadProfile(metricsEndpoint.Profile) - if err != nil { - log.Fatal(err) - } - if wh.Alerting && metricsEndpoint.AlertProfile != "" { - alertM, err = alerting.NewAlertManager(metricsEndpoint.AlertProfile, wh.Metadata.UUID, indexer, p, embedConfig) - if err != nil { - log.Fatal(err) - } - } - prometheusClients = append(prometheusClients, p) - alertMs = append(alertMs, alertM) - alertM = nil - } - configSpec.GlobalConfig.GCMetrics = wh.GcMetrics - rc, err = burner.Run(configSpec, prometheusClients, alertMs, indexer, wh.Timeout, metadata) - if err != nil { - wh.Metadata.ExecutionErrors = err.Error() - log.Error(err) - } - wh.Metadata.Passed = rc == 0 - if indexerConfig.Type != "" { - IndexMetadata(indexer, wh.Metadata) - } - log.Info("👋 Exiting kube-burner ", wh.UUID) - os.Exit(rc) -} - -// ExtractWorkload extracts the given workload and metrics profile to the current diretory -func (wh *WorkloadHelper) ExtractWorkload(workload, metricsProfile string) error { - dirContent, err := wh.ocpConfig.ReadDir(path.Join(ocpCfgDir, workload)) - if err != nil { - return err - } - workloadContent, _ := wh.ocpConfig.ReadFile(ocpCfgDir) - if err = util.CreateFile(fmt.Sprintf("%v.yml", workload), workloadContent); err != nil { - return err - } - for _, f := range dirContent { - fileContent, _ := wh.ocpConfig.ReadFile(path.Join(ocpCfgDir, workload, f.Name())) - err := util.CreateFile(f.Name(), fileContent) - if err != nil { - return err - } - } - metricsProfileContent, _ := wh.ocpConfig.ReadFile(path.Join(ocpCfgDir, metricsProfile)) - if err = util.CreateFile(metricsProfile, metricsProfileContent); err != nil { - return err - } - reportProfileContent, _ := wh.ocpConfig.ReadFile(path.Join(ocpCfgDir, reportProfile)) - if err = util.CreateFile(reportProfile, reportProfileContent); err != nil { - return err - } - alertsProfileContent, _ := wh.ocpConfig.ReadFile(path.Join(ocpCfgDir, alertsProfile)) - if err = util.CreateFile(alertsProfile, alertsProfileContent); err != nil { - return err - } - return nil -} - -// IndexMetadata indexes metadata using given indexer. -func IndexMetadata(indexer *indexers.Indexer, metadata BenchmarkMetadata) { - log.Info("Indexing cluster metadata document") - metadata.EndDate = time.Now().UTC() - msg, err := (*indexer).Index([]interface{}{metadata}, indexers.IndexingOpts{ - MetricName: metadata.MetricName, - }) - if err != nil { - log.Error(err.Error()) - } else { - log.Info(msg) - } -} diff --git a/index.go b/index.go index 87c95e96..3cd342bd 100644 --- a/index.go +++ b/index.go @@ -20,9 +20,10 @@ import ( "github.com/cloud-bulldozer/go-commons/indexers" ocpmetadata "github.com/cloud-bulldozer/go-commons/ocp-metadata" - "github.com/cloud-bulldozer/kube-burner/pkg/config" - "github.com/cloud-bulldozer/kube-burner/pkg/prometheus" - "github.com/cloud-bulldozer/kube-burner/pkg/util/metrics" + "github.com/kube-burner/kube-burner/pkg/config" + "github.com/kube-burner/kube-burner/pkg/prometheus" + "github.com/kube-burner/kube-burner/pkg/util/metrics" + "github.com/kube-burner/kube-burner/pkg/workloads" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -54,9 +55,9 @@ func NewIndex(metricsEndpoint *string, ocpMetaAgent *ocpmetadata.Metadata) *cobr } esServer, _ := cmd.Flags().GetString("es-server") esIndex, _ := cmd.Flags().GetString("es-index") - configSpec.GlobalConfig.UUID = uuid + workloads.ConfigSpec.GlobalConfig.UUID = uuid if esServer != "" && esIndex != "" { - configSpec.GlobalConfig.IndexerConfig = indexers.IndexerConfig{ + workloads.ConfigSpec.GlobalConfig.IndexerConfig = indexers.IndexerConfig{ Type: indexers.ElasticIndexer, Servers: []string{esServer}, Index: esIndex, @@ -65,7 +66,7 @@ func NewIndex(metricsEndpoint *string, ocpMetaAgent *ocpmetadata.Metadata) *cobr if metricsDirectory == "collected-metrics" { metricsDirectory = metricsDirectory + "-" + uuid } - configSpec.GlobalConfig.IndexerConfig = indexers.IndexerConfig{ + workloads.ConfigSpec.GlobalConfig.IndexerConfig = indexers.IndexerConfig{ Type: indexers.LocalIndexer, MetricsDirectory: metricsDirectory, } @@ -86,7 +87,7 @@ func NewIndex(metricsEndpoint *string, ocpMetaAgent *ocpmetadata.Metadata) *cobr "sdnType": clusterMetadata.SDNType, } metricsScraper := metrics.ProcessMetricsScraperConfig(metrics.ScraperConfig{ - ConfigSpec: configSpec, + ConfigSpec: workloads.ConfigSpec, PrometheusStep: prometheusStep, MetricsEndpoint: *metricsEndpoint, MetricsProfile: metricsProfile, @@ -96,6 +97,7 @@ func NewIndex(metricsEndpoint *string, ocpMetaAgent *ocpmetadata.Metadata) *cobr UserMetaData: userMetadata, RawMetadata: metadata, }) + docsToIndex := make(map[string][]interface{}) for _, prometheusClients := range metricsScraper.PrometheusClients { prometheusJob := prometheus.Job{ Start: time.Unix(start, 0), @@ -105,12 +107,14 @@ func NewIndex(metricsEndpoint *string, ocpMetaAgent *ocpmetadata.Metadata) *cobr }, } prometheusClients.JobList = append(prometheusClients.JobList, prometheusJob) - if prometheusClients.ScrapeJobsMetrics(metricsScraper.Indexer) != nil { + if prometheusClients.ScrapeJobsMetrics(docsToIndex) != nil { rc = 1 } } - if configSpec.GlobalConfig.IndexerConfig.Type == indexers.LocalIndexer && tarballName != "" { - if err := metrics.CreateTarball(configSpec.GlobalConfig.IndexerConfig, tarballName); err != nil { + log.Infof("Indexing metrics with UUID %s", uuid) + metrics.IndexDatapoints(docsToIndex, metricsScraper.Indexer) + if workloads.ConfigSpec.GlobalConfig.IndexerConfig.Type == indexers.LocalIndexer && tarballName != "" { + if err := metrics.CreateTarball(workloads.ConfigSpec.GlobalConfig.IndexerConfig, tarballName); err != nil { log.Fatal(err) } } diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..553508eb --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,86 @@ +docs_dir: docs/ +repo_url: https://github.com/kube-burner/kube-burner-ocp +nav: +- OpenShift: index.md +site_name: Kube-burner-ocp +plugins: + - search + - include-markdown +extra: + version: + provider: mike + limit: 3 +extra_css: + - css/extra.css +theme: + version: latest + name: material + font: + text: Roboto + code: Roboto Mono + palette: + # Palette toggle for light mode + - scheme: default + media: "(prefers-color-scheme: light)" + primary: indigo + toggle: + icon: material/weather-night + name: Switch to dark mode + # Palette toggle for dark mode + - scheme: slate + media: "(prefers-color-scheme: dark)" + primary: indigo + toggle: + icon: material/weather-sunny + + logo: media/logo/kube-burner-logo-mini.png + favicon: media/logo/kube-burner-logo-mini.png + features: + - navigation.instant + - navigation.tracking + - navigation.indexes + - navigation.top + - navigation.footer + - toc.integrate + - search.suggest + - search.highlight + - search.share + - content.code.copy + icon: + repo: fontawesome/brands/github +markdown_extensions: + # Python Markdown + - abbr + - admonition + - attr_list + - def_list + - footnotes + - md_in_html + - toc: + permalink: true + + # Python Markdown Extensions + - pymdownx.arithmatex: + generic: true + - pymdownx.betterem: + smart_enable: all + - pymdownx.caret + - pymdownx.details + - pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg + - pymdownx.highlight + - pymdownx.inlinehilite + - pymdownx.keys + - pymdownx.mark + - pymdownx.smartsymbols + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - pymdownx.tabbed: + alternate_style: true + - pymdownx.tasklist: + custom_checkbox: true + - pymdownx.tilde diff --git a/networkpolicy.go b/networkpolicy.go index 2c948294..d328f8e7 100644 --- a/networkpolicy.go +++ b/networkpolicy.go @@ -19,7 +19,7 @@ import ( "os" "time" - "github.com/cloud-bulldozer/kube-burner/pkg/workloads" + "github.com/kube-burner/kube-burner/pkg/workloads" "github.com/spf13/cobra" ) @@ -42,7 +42,7 @@ func NewNetworkPolicy(wh *workloads.WorkloadHelper, variant string) *cobra.Comma os.Setenv("CHURN_DELETION_STRATEGY", churnDeletionStrategy) }, Run: func(cmd *cobra.Command, args []string) { - wh.Run(cmd.Name(), MetricsProfileMap[cmd.Name()]) + wh.Run(cmd.Name(), getMetrics(cmd, "metrics.yml"), alertsProfiles) }, } cmd.Flags().IntVar(&iterations, "iterations", 0, fmt.Sprintf("%v iterations", variant)) diff --git a/node-density-cni.go b/node-density-cni.go index 321b82a4..50445750 100644 --- a/node-density-cni.go +++ b/node-density-cni.go @@ -19,7 +19,7 @@ import ( "os" "time" - "github.com/cloud-bulldozer/kube-burner/pkg/workloads" + "github.com/kube-burner/kube-burner/pkg/workloads" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -38,7 +38,7 @@ func NewNodeDensityCNI(wh *workloads.WorkloadHelper) *cobra.Command { PreRun: func(cmd *cobra.Command, args []string) { wh.Metadata.Benchmark = cmd.Name() totalPods := wh.Metadata.WorkerNodesCount * podsPerNode - podCount, err := wh.OcpMetaAgent.GetCurrentPodCount() + podCount, err := wh.MetadataAgent.GetCurrentPodCount() if err != nil { log.Fatal(err) } @@ -48,7 +48,7 @@ func NewNodeDensityCNI(wh *workloads.WorkloadHelper) *cobra.Command { os.Setenv("POD_READY_THRESHOLD", fmt.Sprintf("%v", podReadyThreshold)) }, Run: func(cmd *cobra.Command, args []string) { - wh.Run(cmd.Name(), MetricsProfileMap[cmd.Name()]) + wh.Run(cmd.Name(), getMetrics(cmd, "metrics.yml"), alertsProfiles) }, } cmd.Flags().DurationVar(&podReadyThreshold, "pod-ready-threshold", 1*time.Minute, "Pod ready timeout threshold") diff --git a/node-density-heavy.go b/node-density-heavy.go index aef28e5a..e53442e5 100644 --- a/node-density-heavy.go +++ b/node-density-heavy.go @@ -19,7 +19,7 @@ import ( "os" "time" - "github.com/cloud-bulldozer/kube-burner/pkg/workloads" + "github.com/kube-burner/kube-burner/pkg/workloads" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -38,7 +38,7 @@ func NewNodeDensityHeavy(wh *workloads.WorkloadHelper) *cobra.Command { PreRun: func(cmd *cobra.Command, args []string) { wh.Metadata.Benchmark = cmd.Name() totalPods := wh.Metadata.WorkerNodesCount * podsPerNode - podCount, err := wh.OcpMetaAgent.GetCurrentPodCount() + podCount, err := wh.MetadataAgent.GetCurrentPodCount() if err != nil { log.Fatal(err) } @@ -50,7 +50,7 @@ func NewNodeDensityHeavy(wh *workloads.WorkloadHelper) *cobra.Command { os.Setenv("ITERATIONS_PER_NAMESPACE", fmt.Sprint(iterationsPerNamespace)) }, Run: func(cmd *cobra.Command, args []string) { - wh.Run(cmd.Name(), MetricsProfileMap[cmd.Name()]) + wh.Run(cmd.Name(), getMetrics(cmd, "metrics.yml"), alertsProfiles) }, } cmd.Flags().DurationVar(&podReadyThreshold, "pod-ready-threshold", 2*time.Minute, "Pod ready timeout threshold") diff --git a/node-density.go b/node-density.go index 1efd210a..219d6786 100644 --- a/node-density.go +++ b/node-density.go @@ -19,7 +19,7 @@ import ( "os" "time" - "github.com/cloud-bulldozer/kube-burner/pkg/workloads" + "github.com/kube-burner/kube-burner/pkg/workloads" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -37,7 +37,7 @@ func NewNodeDensity(wh *workloads.WorkloadHelper) *cobra.Command { PreRun: func(cmd *cobra.Command, args []string) { wh.Metadata.Benchmark = cmd.Name() totalPods := wh.Metadata.WorkerNodesCount * podsPerNode - podCount, err := wh.OcpMetaAgent.GetCurrentPodCount() + podCount, err := wh.MetadataAgent.GetCurrentPodCount() if err != nil { log.Fatal(err.Error()) } @@ -46,7 +46,7 @@ func NewNodeDensity(wh *workloads.WorkloadHelper) *cobra.Command { os.Setenv("CONTAINER_IMAGE", containerImage) }, Run: func(cmd *cobra.Command, args []string) { - wh.Run(cmd.Name(), MetricsProfileMap[cmd.Name()]) + wh.Run(cmd.Name(), getMetrics(cmd, "metrics.yml"), alertsProfiles) }, } cmd.Flags().IntVar(&podsPerNode, "pods-per-node", 245, "Pods per node") diff --git a/pvc-density.go b/pvc-density.go index 65424a87..d9d63b7a 100644 --- a/pvc-density.go +++ b/pvc-density.go @@ -1,3 +1,17 @@ +// Copyright 2023 The Kube-burner Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package ocp import ( @@ -6,7 +20,7 @@ import ( "regexp" "strings" - "github.com/cloud-bulldozer/kube-burner/pkg/workloads" + "github.com/kube-burner/kube-burner/pkg/workloads" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -23,6 +37,7 @@ var dynamicStorageProvisioners = map[string]string{ // NewPVCDensity holds pvc-density workload func NewPVCDensity(wh *workloads.WorkloadHelper) *cobra.Command { + var iterations int var storageProvisioners []string var claimSize string @@ -50,13 +65,12 @@ func NewPVCDensity(wh *workloads.WorkloadHelper) *cobra.Command { os.Setenv("STORAGE_PROVISIONER", fmt.Sprint(dynamicStorageProvisioners[provisioner])) }, Run: func(cmd *cobra.Command, args []string) { - wh.Run(cmd.Name(), MetricsProfileMap[cmd.Name()]) + wh.Run(cmd.Name(), getMetrics(cmd, "metrics.yml"), alertsProfiles) }, } cmd.Flags().IntVar(&iterations, "iterations", 0, fmt.Sprintf("%v iterations", iterations)) - cmd.Flags().StringVar(&provisioner, "provisioner", provisioner, fmt.Sprintf( - "[%s]", strings.Join(storageProvisioners, " "))) + cmd.Flags().StringVar(&provisioner, "provisioner", provisioner, fmt.Sprintf("[%s]", strings.Join(storageProvisioners, " "))) cmd.Flags().StringVar(&claimSize, "claim-size", "256Mi", "claim-size=256Mi") cmd.Flags().StringVar(&containerImage, "container-image", "gcr.io/google_containers/pause:3.1", "Container image") diff --git a/test/helpers.bash b/test/helpers.bash index a4cc64ff..67005779 100644 --- a/test/helpers.bash +++ b/test/helpers.bash @@ -1,56 +1,68 @@ #!/bin/bash # vi: ft=bash -# shellcheck disable=SC2086 +# shellcheck disable=SC2086,SC2068 KIND_VERSION=${KIND_VERSION:-v0.19.0} K8S_VERSION=${K8S_VERSION:-v1.27.0} +setup-kind() { + KIND_FOLDER=$(mktemp -d) + echo "Downloading kind" + curl -LsS https://github.com/kubernetes-sigs/kind/releases/download/"${KIND_VERSION}"/kind-linux-amd64 -o ${KIND_FOLDER}/kind-linux-amd64 + chmod +x ${KIND_FOLDER}/kind-linux-amd64 + echo "Deploying cluster" + ${KIND_FOLDER}/kind-linux-amd64 create cluster --config kind.yml --image kindest/node:"${K8S_VERSION}" --name kind --wait 300s -v=1 +} + +destroy-kind() { + echo "Destroying kind server" + "${KIND_FOLDER}"/kind-linux-amd64 delete cluster +} + setup-prometheus() { echo "Setting up prometheus instance" podman run --rm -d --name prometheus --network=host docker.io/prom/prometheus:latest sleep 10 } +check_ns() { + echo "Checking the number of namespaces labeled with \"${1}\" is \"${2}\"" + if [[ $(kubectl get ns -l "${1}" -o name | wc -l) != "${2}" ]]; then + echo "Number of namespaces labeled with ${1} less than expected" + return 1 + fi +} -check_running_pods() { - local running_pods=0 - local pods=0 - namespaces=$(kubectl get ns -l "${1}" --no-headers | awk '{print $1}') - for ns in ${namespaces}; do - pods=$(kubectl get pod -n "${ns}" | grep -c Running) - running_pods=$((running_pods + pods)) - done - if [[ "${running_pods}" != "${2}" ]]; then - echo "Running pods in namespaces labeled with \"${1}\" different from expected" +check_destroyed_ns() { + echo "Checking namespace \"${1}\" has been destroyed" + if [[ $(kubectl get ns -l "${1}" -o name | wc -l) != 0 ]]; then + echo "Namespaces labeled with \"${1}\" not destroyed" return 1 fi } -check_files() { - rc=0 - file_list="${TEMP_FOLDER}/top2PrometheusCPU.json ${TEMP_FOLDER}/prometheusRSS.json ${TEMP_FOLDER}/podLatencyMeasurement-namespaced.json ${TEMP_FOLDER}/podLatencyQuantilesMeasurement-namespaced.json" - if [[ $LATENCY == "true" ]]; then - file_list="${TEMP_FOLDER}/podLatencyMeasurement-namespaced.json ${TEMP_FOLDER}/podLatencyQuantilesMeasurement-namespaced.json" +check_destroyed_pods() { + echo "Checking pods have been destroyed in namespace ${1}" + if [[ $(kubectl get pod -n "${1}" -l "${2}" -o name | wc -l) != 0 ]]; then + echo "Pods in namespace ${1} not destroyed" + return 1 fi - if [[ $ALERTING == "true" ]]; then - file_list=" ${TEMP_FOLDER}/alert.json" +} + +check_running_pods() { + running_pods=$(kubectl get pod -A -l ${1} --field-selector=status.phase==Running --no-headers | wc -l) + if [[ "${running_pods}" != "${2}" ]]; then + echo "Running pods in cluster labeled with ${1} different from expected: Expected=${2}, observed=${running_pods}" + return 1 fi - for f in ${file_list}; do - echo "Checking file ${f}" - if [[ ! -f $f ]]; then - echo "File ${f} not present" - rc=$((rc + 1)) - continue - fi - if [[ $(jq .[0].metricName ${f}) == "" ]]; then - echo "Incorrect format in ${f}" - cat "${f}" +} + +check_running_pods_in_ns() { + running_pods=$(kubectl get pod -n "${1}" -l kube-burner-job=namespaced | grep -c Running) + if [[ "${running_pods}" != "${2}" ]]; then + echo "Running pods in namespace $1 different from expected. Expected=${2}, observed=${running_pods}" + return 1 fi - done - if [[ ${rc} != 0 ]]; then - echo "Content of ${TEMP_FOLDER}:" - ls -l ${TEMP_FOLDER} - fi } check_file_list() { @@ -70,32 +82,12 @@ check_file_list() { return 0 } -test_init_checks() { - rc=0 - if [[ ${INDEXING_TYPE} == "local" ]]; then - check_files - fi - check_ns kube-burner-job=namespaced,kube-burner-uuid="${UUID}" 6 - rc=$((rc + $?)) - check_running_pods kube-burner-job=namespaced,kube-burner-uuid="${UUID}" 6 - rc=$((rc + $?)) - timeout 500 kube-burner init -c kube-burner-delete.yml --uuid "${UUID}" --log-level=debug - check_destroyed_ns kube-burner-job=not-namespaced,kube-burner-uuid="${UUID}" - rc=$((rc + $?)) - echo "Running kube-burner destroy" - kube-burner destroy --uuid "${UUID}" - check_destroyed_ns kube-burner-job=namespaced,kube-burner-uuid="${UUID}" - rc=$((rc + $?)) - echo "Evaluating alerts" - kube-burner check-alerts -u http://localhost:9090 -a alert-profile.yaml --start "$(date -d '-2 minutes' +%s)" - return ${rc} -} - print_events() { kubectl get events --sort-by='.lastTimestamp' -A } check_metric_value() { + sleep 3s # There's some delay on the documents to show up in OpenSearch for metric in "${@}"; do endpoint="${ES_SERVER}/${ES_INDEX}/_search?q=uuid.keyword:${UUID}+AND+metricName.keyword:${metric}" RESULT=$(curl -sS ${endpoint} | jq '.hits.total.value // error') @@ -111,3 +103,8 @@ check_metric_value() { fi done } + +run_cmd(){ + echo "$@" + ${@} +} diff --git a/test/test-ocp.bats b/test/test-ocp.bats index 15e2609e..e3b03f20 100755 --- a/test/test-ocp.bats +++ b/test/test-ocp.bats @@ -19,7 +19,6 @@ setup() { } teardown() { - echo "Last bats run command: ${BATS_RUN_COMMAND} from $(pwd)" oc delete ns -l kube-burner-uuid="${UUID}" --ignore-not-found } @@ -28,76 +27,60 @@ teardown_file() { } @test "node-density with indexing" { - run kube-burner ocp node-density --pods-per-node=75 --pod-ready-threshold=10s ${COMMON_FLAGS} - [ "$status" -eq 0 ] - run check_metric_value etcdVersion clusterMetadata jobSummary podLatencyMeasurement podLatencyQuantilesMeasurement - [ "$status" -eq 0 ] + run_cmd kube-burner-ocp node-density --pods-per-node=75 --pod-ready-threshold=10s ${COMMON_FLAGS} + check_metric_value etcdVersion clusterMetadata jobSummary podLatencyMeasurement podLatencyQuantilesMeasurement } @test "node-density-heavy with indexing" { - run kube-burner ocp node-density-heavy --pods-per-node=75 --uuid=abcd --local-indexing --gc-metrics=true - [ "$status" -eq 0 ] - run check_file_list collected-metrics-abcd/etcdVersion.json collected-metrics-abcd/clusterMetadata.json collected-metrics-abcd/jobSummary-node-density-heavy.json collected-metrics-abcd/jobSummary-garbage-collection.json collected-metrics-abcd/podLatencyMeasurement-node-density-heavy.json collected-metrics-abcd/podLatencyQuantilesMeasurement-node-density-heavy.json - [ "$status" -eq 0 ] + run_cmd kube-burner-ocp node-density-heavy --pods-per-node=75 --uuid=abcd --local-indexing --gc-metrics=true + check_file_list collected-metrics-abcd/etcdVersion.json collected-metrics-abcd/clusterMetadata.json collected-metrics-abcd/jobSummary-node-density-heavy.json collected-metrics-abcd/jobSummary-garbage-collection.json collected-metrics-abcd/podLatencyMeasurement-node-density-heavy.json collected-metrics-abcd/podLatencyQuantilesMeasurement-node-density-heavy.json } @test "cluster-density-ms: metrics-endpoint=true; es-indexing=true" { - run kube-burner ocp cluster-density-ms --iterations=1 --churn=false --metrics-endpoint metrics-endpoints.yaml ${COMMON_FLAGS} - [ "$status" -eq 0 ] - run check_metric_value clusterMetadata jobSummary podLatencyMeasurement podLatencyQuantilesMeasurement - [ "$status" -eq 0 ] + run_cmd kube-burner-ocp cluster-density-ms --iterations=1 --churn=false --metrics-endpoint metrics-endpoints.yaml ${COMMON_FLAGS} + check_metric_value clusterMetadata jobSummary podLatencyMeasurement podLatencyQuantilesMeasurement } @test "cluster-density-v2: profile-type=both; user-metadata=true; es-indexing=true; churning=true" { - run kube-burner ocp cluster-density-v2 --iterations=5 --churn-duration=1m --churn-delay=5s --profile-type=both ${COMMON_FLAGS} --user-metadata=user-metadata.yml - [ "$status" -eq 0 ] - run check_metric_value cpu-kubelet clusterMetadata jobSummary podLatencyMeasurement podLatencyQuantilesMeasurement etcdVersion - [ "$status" -eq 0 ] + run_cmd kube-burner-ocp cluster-density-v2 --iterations=5 --churn-duration=1m --churn-delay=5s --profile-type=both ${COMMON_FLAGS} --user-metadata=user-metadata.yml + check_metric_value cpu-kubelet clusterMetadata jobSummary podLatencyMeasurement podLatencyQuantilesMeasurement etcdVersion } @test "cluster-density-v2 with gvr churn deletion strategy" { - run kube-burner ocp cluster-density-v2 --iterations=2 --churn=true --churn-duration=1m --churn-delay=10s --churn-deletion-strategy=gvr ${COMMON_FLAGS} - [ "$status" -eq 0 ] - run check_metric_value etcdVersion clusterMetadata jobSummary podLatencyMeasurement podLatencyQuantilesMeasurement - [ "$status" -eq 0 ] + run_cmd kube-burner-ocp cluster-density-v2 --iterations=2 --churn=true --churn-duration=1m --churn-delay=10s --churn-deletion-strategy=gvr ${COMMON_FLAGS} + check_metric_value etcdVersion clusterMetadata jobSummary podLatencyMeasurement podLatencyQuantilesMeasurement } @test "cluster-density-v2: indexing=false; churning=false" { - run kube-burner ocp cluster-density-v2 --iterations=2 --churn=false --uuid=${UUID} - [ "$status" -eq 0 ] + run_cmd kube-burner-ocp cluster-density-v2 --iterations=2 --churn=false --uuid=${UUID} } @test "node-density-cni: gc=false; alerting=false" { # Disable gc and avoid metric indexing - run kube-burner ocp node-density-cni --pods-per-node=75 --gc=false --uuid=${UUID} --alerting=false + run_cmd kube-burner-ocp node-density-cni --pods-per-node=75 --gc=false --uuid=${UUID} --alerting=false oc delete ns -l kube-burner-uuid=${UUID} trap - ERR } @test "cluster-density-v2 timeout check" { - run kube-burner ocp cluster-density-v2 --iterations=1 --churn-duration=5m --timeout=1s + run timeout 10s kube-burner-ocp cluster-density-v2 --iterations=1 --churn-duration=5m --timeout=1s [ "$status" -eq 2 ] } @test "index: local-indexing=true" { - run kube-burner ocp index --uuid="${UUID}" --metrics-profile metrics-profile.yaml - [ "$status" -eq 0 ] + run_cmd kube-burner-ocp index --uuid="${UUID}" --metrics-profile metrics-profile.yaml } @test "index: metrics-endpoints=true; es-indexing=true" { - run kube-burner ocp index --uuid="${UUID}" --metrics-endpoint metrics-endpoints.yaml --metrics-profile metrics-profile.yaml --es-server=https://search-perfscale-dev-chmf5l4sh66lvxbnadi4bznl3a.us-west-2.es.amazonaws.com:443 --es-index=ripsaw-kube-burner - [ "$status" -eq 0 ] + run_cmd kube-burner-ocp index --uuid="${UUID}" --metrics-endpoint metrics-endpoints.yaml --metrics-profile metrics-profile.yaml --es-server=https://search-perfscale-dev-chmf5l4sh66lvxbnadi4bznl3a.us-west-2.es.amazonaws.com:443 --es-index=ripsaw-kube-burner } @test "networkpolicy-multitenant" { - run kube-burner ocp networkpolicy-multitenant --iterations 5 ${COMMON_FLAGS} - [ "$status" -eq 0 ] + run_cmd kube-burner-ocp networkpolicy-multitenant --iterations 5 ${COMMON_FLAGS} } @test "pvc-density" { # Since 'aws' is the chosen storage provisioner, this will only execute successfully if the ocp environment is aws - run kube-burner ocp pvc-density --iterations=2 --provisioner=aws ${COMMON_FLAGS} - [ "$status" -eq 0 ] - run check_metric_value clusterMetadata jobSummary podLatencyMeasurement podLatencyQuantilesMeasurement - [ "$status" -eq 0 ] + run_cmd kube-burner-ocp pvc-density --iterations=2 --provisioner=aws ${COMMON_FLAGS} + check_metric_value clusterMetadata jobSummary podLatencyMeasurement podLatencyQuantilesMeasurement } diff --git a/types.go b/types.go index 8c58aa0d..1d019436 100644 --- a/types.go +++ b/types.go @@ -1,67 +1,25 @@ -package ocp - -import ( - "embed" - "time" +// Copyright 2023 The Kube-burner Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. - "github.com/cloud-bulldozer/go-commons/indexers" - ocpmetadata "github.com/cloud-bulldozer/go-commons/ocp-metadata" - "k8s.io/client-go/rest" -) +package ocp type ProfileType string -var MetricsProfileMap = map[string]string{ - "cluster-density-ms": "metrics-aggregated.yml", - "cluster-density-v2": "metrics-aggregated.yml", - "crd-scale": "metrics-aggregated.yml", - "node-density": "metrics.yml", - "node-density-heavy": "metrics.yml", - "node-density-cni": "metrics.yml", - "networkpolicy-multitenant": "metrics.yml", - "networkpolicy-matchlabels": "metrics.yml", - "networkpolicy-matchexpressions": "metrics.yml", - "pvc-density": "metrics.yml", -} - const ( - regular ProfileType = "regular" - reporting ProfileType = "reporting" - both ProfileType = "both" + Regular ProfileType = "regular" + Reporting ProfileType = "reporting" + Both ProfileType = "both" ) -type Config struct { - UUID string - EsServer string - Esindex string - QPS int - Burst int - Gc bool - GcMetrics bool - Indexer indexers.IndexerType - Alerting bool - Timeout time.Duration - MetricsEndpoint string - ProfileType string -} - -type BenchmarkMetadata struct { - ocpmetadata.ClusterMetadata - UUID string `json:"uuid"` - Benchmark string `json:"benchmark"` - Timestamp time.Time `json:"timestamp"` - EndDate time.Time `json:"endDate"` - Passed bool `json:"passed"` - ExecutionErrors string `json:"executionErrors"` - UserMetadata map[string]interface{} `json:"metadata,omitempty"` -} - -type WorkloadHelper struct { - Config - prometheusURL string - prometheusToken string - Metadata BenchmarkMetadata - ocpConfig embed.FS - OcpMetaAgent ocpmetadata.Metadata - restConfig *rest.Config -} +var alertsProfiles = []string{"alerts.yml"} diff --git a/utils.go b/utils.go deleted file mode 100644 index b644916f..00000000 --- a/utils.go +++ /dev/null @@ -1,48 +0,0 @@ -package ocp - -import ( - "context" - - "github.com/openshift/client-go/config/clientset/versioned" - log "github.com/sirupsen/logrus" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/rest" -) - -// Verifies container registry and reports its status -func verifyContainerRegistry(restConfig *rest.Config) bool { - // Create an OpenShift client using the default configuration - client, err := versioned.NewForConfig(restConfig) - if err != nil { - log.Error("Error connecting to the openshift cluster", err) - return false - } - // Get the image registry object - imageRegistry, err := client.ConfigV1().ClusterOperators().Get(context.TODO(), "image-registry", metav1.GetOptions{}) - if err != nil { - log.Error("Error getting image registry object:", err) - return false - } - - // Check the status conditions - logMessage := "" - readyFlag := false - for _, condition := range imageRegistry.Status.Conditions { - if condition.Type == "Available" && condition.Status == "True" { - readyFlag = true - logMessage += " up and running" - } - if condition.Type == "Progressing" && condition.Status == "False" && condition.Reason == "Ready" { - logMessage += " ready to use" - } - if condition.Type == "Degraded" && condition.Status == "False" && condition.Reason == "AsExpected" { - logMessage += " with a healthy state" - } - } - if readyFlag { - log.Infof("Cluster image registry is%s", logMessage) - } else { - log.Info("Cluster image registry is not up and running") - } - return readyFlag -} diff --git a/web-burner.go b/web-burner.go new file mode 100644 index 00000000..0aa69b9b --- /dev/null +++ b/web-burner.go @@ -0,0 +1,59 @@ +// Copyright 2022 The Kube-burner Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ocp + +import ( + "fmt" + "os" + "time" + + "github.com/kube-burner/kube-burner/pkg/workloads" + "github.com/spf13/cobra" +) + +// NewClusterDensity holds cluster-density workload +func NewWebBurner(wh *workloads.WorkloadHelper, variant string) *cobra.Command { + var limitcount, scale int + var bfd, crd, probe, sriov bool + var bridge string + var podReadyThreshold time.Duration + cmd := &cobra.Command{ + Use: variant, + Short: fmt.Sprintf("Runs %v workload", variant), + PreRun: func(cmd *cobra.Command, args []string) { + wh.Metadata.Benchmark = cmd.Name() + os.Setenv("BFD", fmt.Sprint(bfd)) + os.Setenv("BRIDGE", fmt.Sprint(bridge)) + os.Setenv("CRD", fmt.Sprintf("%v", crd)) + os.Setenv("LIMITCOUNT", fmt.Sprint(limitcount)) + os.Setenv("POD_READY_THRESHOLD", fmt.Sprintf("%v", podReadyThreshold)) + os.Setenv("PROBE", fmt.Sprint(probe)) + os.Setenv("SCALE", fmt.Sprint(scale)) + os.Setenv("SRIOV", fmt.Sprint(sriov)) + }, + Run: func(cmd *cobra.Command, args []string) { + wh.Run(cmd.Name(), getMetrics(cmd, "metrics.yml"), alertsProfiles) + }, + } + cmd.Flags().DurationVar(&podReadyThreshold, "pod-ready-threshold", 2*time.Minute, "Pod ready timeout threshold") + cmd.Flags().IntVar(&limitcount, "limitcount", 1, "Limitcount") + cmd.Flags().IntVar(&scale, "scale", 1, "Scale") + cmd.Flags().BoolVar(&bfd, "bfd", true, "Enable BFD") + cmd.Flags().BoolVar(&crd, "crd", true, "Enable AdminPolicyBasedExternalRoute CR") + cmd.Flags().BoolVar(&probe, "probe", false, "Enable readiness probes") + cmd.Flags().BoolVar(&sriov, "sriov", true, "Enable SRIOV") + cmd.Flags().StringVar(&bridge, "bridge", "br-ex", "Data-plane bridge") + return cmd +}