├── .github ├── PULL_REQUEST_TEMPLATE.md ├── issue_template.md └── workflows │ ├── upload_chart.yml │ └── upload_operator_image.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── README_CN.md ├── build ├── bin │ ├── entrypoint │ └── user_setup ├── image │ ├── amd │ │ └── Dockerfile │ └── arm │ │ └── Dockerfile ├── musl │ └── Dockerfile └── spec.go ├── channel ├── client.go └── client_test.go ├── cmd ├── hookfs │ └── main.go └── manager │ └── main.go ├── deploy ├── crds │ └── chaosblade.io_chaosblades_crd.yaml ├── helm │ ├── chaosblade-operator-arm64 │ │ ├── .helmignore │ │ ├── Chart.yaml │ │ ├── crds │ │ │ └── crd.yaml │ │ ├── templates │ │ │ ├── NOTES.txt │ │ │ ├── _helpers.tpl │ │ │ ├── daemonset.yaml │ │ │ ├── deployment.yaml │ │ │ ├── rbac.yaml │ │ │ ├── secret.yaml │ │ │ └── service.yaml │ │ └── values.yaml │ └── chaosblade-operator │ │ ├── .helmignore │ │ ├── Chart.yaml │ │ ├── crds │ │ └── crd.yaml │ │ ├── templates │ │ ├── NOTES.txt │ │ ├── _helpers.tpl │ │ ├── daemonset.yaml │ │ ├── deployment.yaml │ │ ├── rbac.yaml │ │ ├── secret.yaml │ │ └── service.yaml │ │ └── values.yaml ├── olm │ ├── Makefile │ └── deploy │ │ ├── crd.yaml │ │ ├── crds │ │ └── chaosblade_v1alpha1_chaosblade_crd.yaml │ │ ├── olm-catalog │ │ └── chaosblade-operator │ │ │ ├── 0.5.1 │ │ │ ├── chaosblade-operator.v0.5.1.clusterserviceversion.yaml │ │ │ └── chaosblade_v1alpha1_chaosblade_crd.yaml │ │ │ ├── 0.6.0 │ │ │ ├── chaosblade-operator.v0.6.0.clusterserviceversion.yaml │ │ │ └── chaosblade_v1alpha1_chaosblade_crd.yaml │ │ │ └── chaosblade-operator.package.yaml │ │ ├── operator.yaml │ │ ├── role.yaml │ │ ├── role_binding.yaml │ │ └── service_account.yaml └── oss │ ├── crd.yaml │ ├── operator.yaml │ ├── rbac.yaml │ ├── service.yaml │ └── webhook-cert-job.yaml ├── examples ├── delay_pod_network_by_names.yaml ├── delete_pod_by_labels.yaml ├── delete_pod_by_names.yaml ├── fail_pod_by_labels.yaml ├── increase_container_cpu_load_by_id.yaml ├── kill_container_process_by_id.yaml ├── node-cpu-load.yml ├── node-disk-load-burn-read.yml ├── node-disk-load-burn-write.yml ├── node-disk-load-fill.yml ├── node-mem-load.yml ├── node-network-delay-by-names.yml ├── node-network-loss-by-names.yml ├── pod-cpu-load-by-names.yml ├── pod-delete_by_names.yaml ├── remove_container_by_id.yaml └── tamper_container_dns_by_id.yaml ├── exec ├── container │ ├── application.go │ ├── container.go │ └── controller.go ├── controller.go ├── model │ ├── category.go │ ├── context.go │ ├── controller.go │ ├── controller_test.go │ ├── copy.go │ ├── deploy.go │ ├── download.go │ ├── executor.go │ ├── executor_copy.go │ ├── executor_nsexec.go │ ├── filter.go │ ├── filter_pod.go │ ├── filter_pod_test.go │ ├── model.go │ ├── osexp.go │ └── parallelizer.go ├── node │ ├── controller.go │ ├── filter.go │ └── node.go └── pod │ ├── controller.go │ ├── delete.go │ ├── failexp.go │ ├── fsexp.go │ └── pod.go ├── go.mod ├── go.sum ├── pkg ├── apis │ ├── addtoscheme_chaosblade_v1alpha1.go │ ├── apis.go │ └── chaosblade │ │ ├── group.go │ │ ├── meta │ │ └── zz_generated.openapi.go │ │ └── v1alpha1 │ │ ├── doc.go │ │ ├── register.go │ │ ├── types.go │ │ ├── zz_generated.deepcopy.go │ │ └── zz_generated.openapi.go ├── controller │ ├── add_chaosblade.go │ ├── chaosblade │ │ ├── controller.go │ │ ├── daemonset.go │ │ └── predicate.go │ └── controller.go ├── hookfs │ ├── client.go │ ├── hook.go │ ├── server.go │ └── types.go ├── runtime │ ├── chaosblade │ │ └── chaosblade.go │ ├── product │ │ ├── aliyun │ │ │ └── aliyun.go │ │ └── community │ │ │ └── community.go │ └── runtime.go └── webhook │ ├── pod │ ├── mutator.go │ └── mutator_test.go │ └── webhook.go └── version └── version.go /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | ### Describe what this PR does / why we need it 7 | 8 | 9 | ### Does this pull request fix one issue? 10 | 11 | 12 | 13 | ### Describe how you did it 14 | 15 | 16 | ### Describe how to verify it 17 | 18 | 19 | ### Special notes for reviews 20 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | ## Issue Description 8 | 9 | Type: *bug report* or *feature request* 10 | 11 | ### Describe what happened (or what feature you want) 12 | 13 | 14 | ### Describe what you expected to happen 15 | 16 | 17 | ### How to reproduce it (as minimally and precisely as possible) 18 | 19 | 1. 20 | 2. 21 | 3. 22 | 23 | ### Tell us your environment 24 | 25 | 26 | ### Anything else we need to know? 27 | 28 | -------------------------------------------------------------------------------- /.github/workflows/upload_chart.yml: -------------------------------------------------------------------------------- 1 | name: Upload Chart 2 | 3 | on: 4 | workflow_dispatch: {} 5 | push: 6 | tags: 7 | - v* 8 | 9 | permissions: read-all 10 | 11 | jobs: 12 | Upload-Chart: 13 | permissions: 14 | # https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions#authenticating-to-package-registries-on-github 15 | packages: write 16 | strategy: 17 | matrix: 18 | # arch: [ amd64, arm64 ] 19 | image: 20 | [ chaosblade-operator ] 21 | outputs: 22 | binary_tag: ${{ steps.binary_tag.outputs.binary_tag }} 23 | runs-on: ubuntu-20.04 24 | steps: 25 | - name: Extract Binary Tag 26 | id: binary_tag 27 | shell: bash 28 | run: | 29 | BINARY_TAG=${GITHUB_REF##*/} 30 | echo "::set-output name=binary_tag::$(echo $BINARY_TAG)" 31 | 32 | - name: Helm Install 33 | uses: azure/setup-helm@v3 34 | with: 35 | version: v3.9.3 # default is latest (stable) 36 | id: install 37 | 38 | - uses: actions/checkout@v2 39 | - name: Helm Package 40 | run: | 41 | helm package deploy/helm/chaosblade-operator 42 | helm package deploy/helm/chaosblade-operator-arm64 43 | 44 | - name: Setup OSSUTIL environment 45 | uses: yizhoumo/setup-ossutil@v1.1.3 46 | env: 47 | BINARY_TAG: ${{ steps.binary_tag.outputs.binary_tag }} 48 | with: 49 | endpoint: ${{ secrets.OSS_ENDPOINT }} 50 | access-key-id: ${{ secrets.OSS_ACCESS_KEY_ID }} 51 | access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }} 52 | ossutil-version: '1.7.14' # Optional, default to '1.7.14'. Use 'latest' to get the latest version. 53 | - run: | 54 | ossutil cp -f chaosblade-operator-$BINARY_TAG.tgz oss://chaosblade/agent/github/$BINARY_TAG/chaosblade-operator-$BINARY_TAG.tgz 55 | ossutil cp -f chaosblade-operator-arm64-$BINARY_TAG.tgz oss://chaosblade/agent/github/$BINARY_TAG/chaosblade-operator-arm64-$BINARY_TAG.tgz 56 | -------------------------------------------------------------------------------- /.github/workflows/upload_operator_image.yml: -------------------------------------------------------------------------------- 1 | name: Upload Operator Images 2 | 3 | on: 4 | workflow_dispatch: {} 5 | push: 6 | tags: 7 | - v* 8 | 9 | permissions: read-all 10 | 11 | jobs: 12 | Upload: 13 | permissions: 14 | # https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions#authenticating-to-package-registries-on-github 15 | packages: write 16 | strategy: 17 | matrix: 18 | # arch: [ amd64, arm64 ] 19 | image: 20 | [ chaosblade-operator ] 21 | outputs: 22 | image_tag: ${{ steps.image_tag.outputs.image_tag }} 23 | runs-on: ubuntu-20.04 24 | steps: 25 | - name: Extract Image Tag 26 | id: image_tag 27 | shell: bash 28 | run: | 29 | IMAGE_TAG=${GITHUB_REF##*/} 30 | echo "::set-output name=image_tag::$(echo $IMAGE_TAG)" 31 | 32 | - uses: actions/checkout@v2 33 | - name: Login to GitHub Container registry 34 | uses: docker/login-action@v1 35 | with: 36 | username: ${{ secrets.GHCR_USER }} 37 | password: ${{ secrets.GHCR_PASSWORD }} 38 | 39 | - name: Set Up Go 1.17 40 | uses: actions/setup-go@v3 41 | with: 42 | go-version: 1.17 43 | id: go 44 | 45 | - name: Build Image 46 | run: | 47 | make docker-build 48 | make docker-build-arm64 49 | 50 | - name: Upload Image 51 | run: | 52 | make push_image 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Temporary Build Files 2 | build/_output 3 | build/_test 4 | target 5 | # Created by https://www.gitignore.io/api/go,vim,emacs,visualstudiocode 6 | ### Emacs ### 7 | # -*- mode: gitignore; -*- 8 | *~ 9 | \#*\# 10 | /.emacs.desktop 11 | /.emacs.desktop.lock 12 | *.elc 13 | auto-save-list 14 | tramp 15 | .\#* 16 | # Org-mode 17 | .org-id-locations 18 | *_archive 19 | # flymake-mode 20 | *_flymake.* 21 | # eshell files 22 | /eshell/history 23 | /eshell/lastdir 24 | # elpa packages 25 | /elpa/ 26 | # reftex files 27 | *.rel 28 | # AUCTeX auto folder 29 | /auto/ 30 | # cask packages 31 | .cask/ 32 | dist/ 33 | # Flycheck 34 | flycheck_*.el 35 | # server auth directory 36 | /server/ 37 | # projectiles files 38 | .projectile 39 | projectile-bookmarks.eld 40 | deploy/helm/chaosblade-operator-*.tgz 41 | # directory configuration 42 | .dir-locals.el 43 | # saveplace 44 | places 45 | # url cache 46 | url/cache/ 47 | # cedet 48 | ede-projects.el 49 | # smex 50 | smex-items 51 | # company-statistics 52 | company-statistics-cache.el 53 | # anaconda-mode 54 | anaconda-mode/ 55 | ### Go ### 56 | # Binaries for programs and plugins 57 | *.exe 58 | *.exe~ 59 | *.dll 60 | *.so 61 | *.dylib 62 | .DS_Store 63 | # Test binary, build with 'go test -c' 64 | *.test 65 | # Output of the go coverage tool, specifically when used with LiteIDE 66 | *.out 67 | vendor 68 | ### Vim ### 69 | # swap 70 | .sw[a-p] 71 | .*.sw[a-p] 72 | # session 73 | Session.vim 74 | # temporary 75 | .netrwhist 76 | # auto-generated tag files 77 | tags 78 | ### VisualStudioCode ### 79 | .vscode/* 80 | .history 81 | # End of https://www.gitignore.io/api/go,vim,emacs,visualstudiocode 82 | 83 | ### Goland ### 84 | .idea 85 | 86 | ### vendor ### 87 | vendor 88 | 89 | ### project ### 90 | build/cache 91 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build clean 2 | 3 | GO_ENV=CGO_ENABLED=1 4 | GO_MODULE=GO111MODULE=on 5 | GO=env $(GO_ENV) $(GO_MODULE) go 6 | 7 | UNAME := $(shell uname) 8 | 9 | ifeq ($(BLADE_VERSION), ) 10 | BLADE_VERSION=1.7.4 11 | endif 12 | ifeq ($(BLADE_VENDOR), ) 13 | BLADE_VENDOR=community 14 | endif 15 | 16 | BUILD_TARGET=target 17 | BUILD_TARGET_DIR_NAME=chaosblade-$(BLADE_VERSION) 18 | BUILD_TARGET_PKG_DIR=$(BUILD_TARGET)/chaosblade-$(BLADE_VERSION) 19 | BUILD_TARGET_BIN=$(BUILD_TARGET_PKG_DIR)/bin 20 | BUILD_TARGET_YAML=$(BUILD_TARGET_PKG_DIR)/yaml 21 | BUILD_IMAGE_PATH=build/image/blade 22 | 23 | OS_YAML_FILE_NAME=chaosblade-k8s-spec-$(BLADE_VERSION).yaml 24 | OS_YAML_FILE_PATH=$(BUILD_TARGET_YAML)/$(OS_YAML_FILE_NAME) 25 | 26 | VERSION_PKG=github.com/chaosblade-io/chaosblade-operator/version 27 | GO_X_FLAGS=-X=$(VERSION_PKG).CombinedVersion=$(BLADE_VERSION),$(BLADE_VENDOR) 28 | GO_FLAGS=-ldflags $(GO_X_FLAGS) 29 | 30 | ifeq ($(GOOS), linux) 31 | GO_FLAGS=-ldflags="-linkmode external -extldflags -static $(GO_X_FLAGS)" 32 | endif 33 | 34 | build: build_yaml build_fuse 35 | 36 | build_all: pre_build build docker-build 37 | build_all_arm64: pre_build build docker-build-arm64 38 | 39 | docker-build: 40 | GOOS="linux" GOARCH="amd64" go build $(GO_FLAGS) -o build/_output/bin/chaosblade-operator cmd/manager/main.go 41 | docker buildx build -f build/image/amd/Dockerfile --platform=linux/amd64 -t ghcr.io/chaosblade-io/chaosblade-operator:${BLADE_VERSION} . 42 | 43 | docker-build-arm64: 44 | GOOS="linux" GOARCH="arm64" go build $(GO_FLAGS) -o build/_output/bin/chaosblade-operator cmd/manager/main.go 45 | docker buildx build -f build/image/arm/Dockerfile --platform=linux/arm64 -t ghcr.io/chaosblade-io/chaosblade-operator-arm64:${BLADE_VERSION} . 46 | 47 | push_image: 48 | docker push ghcr.io/chaosblade-io/chaosblade-operator:${BLADE_VERSION} 49 | docker push ghcr.io/chaosblade-io/chaosblade-operator-arm64:${BLADE_VERSION} 50 | 51 | #operator-sdk 0.19.0 build 52 | build_all_operator: pre_build build build_image 53 | build_image: 54 | operator-sdk build --go-build-args="$(GO_FLAGS)" ghcr.io/chaosblade-io/chaosblade-operator:${BLADE_VERSION} 55 | 56 | build_image_arm64: 57 | GOOS="linux" GOARCH="arm64" operator-sdk build --go-build-args="$(GO_FLAGS)" ghcr.io/chaosblade-io/chaosblade-operator-arm64:${BLADE_VERSION} 58 | 59 | # only build_fuse and yaml 60 | build_linux: 61 | docker build -f build/musl/Dockerfile -t chaosblade-operator-build-musl:latest build/musl 62 | docker run --rm \ 63 | -v $(shell echo -n ${GOPATH}):/go \ 64 | -v $(shell pwd):/go/src/github.com/chaosblade-io/chaosblade-operator \ 65 | -w /go/src/github.com/chaosblade-io/chaosblade-operator \ 66 | chaosblade-operator-build-musl:latest 67 | 68 | build_arm64: 69 | docker run --rm --privileged multiarch/qemu-user-static:register --reset 70 | docker run --rm \ 71 | -v $(shell echo -n ${GOPATH}):/go \ 72 | -v $(shell pwd):/go/src/github.com/chaosblade-io/chaosblade-operator \ 73 | -w /go/src/github.com/chaosblade-io/chaosblade-operator \ 74 | chaosblade-io/chaosblade-build-arm:latest 75 | 76 | pre_build: 77 | mkdir -p $(BUILD_TARGET_BIN) $(BUILD_TARGET_YAML) 78 | 79 | build_spec_yaml: build/spec.go 80 | $(GO) run $< $(OS_YAML_FILE_PATH) 81 | 82 | build_yaml: pre_build build_spec_yaml 83 | 84 | build_fuse: 85 | $(GO) build $(GO_FLAGS) -o $(BUILD_TARGET_BIN)/chaos_fuse cmd/hookfs/main.go 86 | 87 | # test 88 | test: 89 | go test -race -coverprofile=coverage.txt -covermode=atomic ./... 90 | # clean all build result 91 | clean: 92 | go clean ./... 93 | rm -rf $(BUILD_TARGET) 94 | rm -rf $(BUILD_IMAGE_PATH)/$(BUILD_TARGET_DIR_NAME) 95 | -------------------------------------------------------------------------------- /build/bin/entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | # This is documented here: 4 | # https://docs.openshift.com/container-platform/3.11/creating_images/guidelines.html#openshift-specific-guidelines 5 | 6 | #if ! whoami &>/dev/null; then 7 | # if [ -w /etc/passwd ]; then 8 | # echo "${USER_NAME:-chaosblade-operator}:x:$(id -u):$(id -g):${USER_NAME:-chaosblade-operator} user:${HOME}:/sbin/nologin" >> /etc/passwd 9 | # fi 10 | #fi 11 | 12 | exec ${OPERATOR} $@ 13 | -------------------------------------------------------------------------------- /build/bin/user_setup: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -x 3 | 4 | # ensure $HOME exists and is accessible by group 0 (we don't know what the runtime UID will be) 5 | mkdir -p ${HOME} 6 | chown ${USER_UID}:0 ${HOME} 7 | chmod ug+rwx ${HOME} 8 | 9 | # runtime user will need to be able to self-insert in /etc/passwd 10 | chmod g+rw /etc/passwd 11 | 12 | # no need for this script to remain in the image after running 13 | rm $0 14 | -------------------------------------------------------------------------------- /build/image/amd/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.19 as builder 2 | 3 | ENV OPERATOR=/usr/local/bin/chaosblade-operator 4 | COPY build/_output/bin/chaosblade-operator /usr/local/bin/ 5 | 6 | FROM registry.access.redhat.com/ubi8/ubi-minimal:latest 7 | 8 | ENV OPERATOR=/usr/local/bin/chaosblade-operator \ 9 | CHAOSBLADE_HOME=/opt/chaosblade 10 | 11 | COPY --from=builder ${OPERATOR} /usr/local/bin/ 12 | COPY build/bin /usr/local/bin 13 | 14 | RUN chmod 777 /usr/local/bin/user_setup 15 | RUN /usr/local/bin/user_setup 16 | 17 | ENTRYPOINT ["/usr/local/bin/entrypoint"] 18 | -------------------------------------------------------------------------------- /build/image/arm/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM multiarch/alpine:arm64-edge as builder 2 | 3 | ENV OPERATOR=/usr/local/bin/chaosblade-operator 4 | COPY build/_output/bin/chaosblade-operator /usr/local/bin/ 5 | 6 | FROM registry.access.redhat.com/ubi8/ubi-minimal:latest 7 | 8 | ENV OPERATOR=/usr/local/bin/chaosblade-operator \ 9 | CHAOSBLADE_HOME=/opt/chaosblade 10 | 11 | COPY --from=builder ${OPERATOR} /usr/local/bin/ 12 | COPY build/bin /usr/local/bin 13 | 14 | RUN chmod 777 /usr/local/bin/user_setup 15 | RUN /usr/local/bin/user_setup 16 | 17 | ENTRYPOINT ["/usr/local/bin/entrypoint"] 18 | -------------------------------------------------------------------------------- /build/musl/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.20.13 2 | LABEL maintainer="Changjun Xiao" 3 | 4 | # # The image is used to build chaosblade for musl 5 | RUN wget http://www.musl-libc.org/releases/musl-1.1.21.tar.gz \ 6 | && tar -zxvf musl-1.1.21.tar.gz \ 7 | && rm musl-1.1.21.tar.gz \ 8 | && cd musl* \ 9 | && ./configure \ 10 | && make \ 11 | && make install \ 12 | && rm -rf musl* 13 | 14 | ENV CC /usr/local/musl/bin/musl-gcc 15 | ENV GOOS linux 16 | 17 | ENTRYPOINT [ "make" ] 18 | -------------------------------------------------------------------------------- /build/spec.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "log" 21 | "os" 22 | 23 | "github.com/chaosblade-io/chaosblade-operator/exec/container" 24 | "github.com/chaosblade-io/chaosblade-operator/exec/node" 25 | "github.com/chaosblade-io/chaosblade-operator/exec/pod" 26 | 27 | "github.com/chaosblade-io/chaosblade-spec-go/spec" 28 | "github.com/chaosblade-io/chaosblade-spec-go/util" 29 | ) 30 | 31 | // main creates the yaml file of the experiments about kubernetes 32 | func main() { 33 | if len(os.Args) < 2 { 34 | log.Panicln("less yaml file path") 35 | } 36 | if len(os.Args) == 3 { 37 | container.JvmSpecFileForYaml = os.Args[2] 38 | } 39 | err := util.CreateYamlFile(getModels(), os.Args[1]) 40 | if err != nil { 41 | log.Panicf("create yaml file error, %v", err) 42 | } 43 | } 44 | 45 | func getModels() *spec.Models { 46 | models := make([]*spec.Models, 0) 47 | nodeResourceModelSpec := node.NewResourceModelSpec(nil) 48 | for _, modelSpec := range nodeResourceModelSpec.ExpModels() { 49 | model := util.ConvertSpecToModels(modelSpec, spec.ExpPrepareModel{}, nodeResourceModelSpec.Scope()) 50 | models = append(models, model) 51 | } 52 | podResourceModelSpec := pod.NewResourceModelSpec(nil) 53 | for _, modelSpec := range podResourceModelSpec.ExpModels() { 54 | model := util.ConvertSpecToModels(modelSpec, spec.ExpPrepareModel{}, podResourceModelSpec.Scope()) 55 | models = append(models, model) 56 | } 57 | containerResourceModelSpec := container.NewResourceModelSpec(nil) 58 | for _, modelSpec := range containerResourceModelSpec.ExpModels() { 59 | model := util.ConvertSpecToModels(modelSpec, spec.ExpPrepareModel{}, containerResourceModelSpec.Scope()) 60 | models = append(models, model) 61 | } 62 | return util.MergeModels(models...) 63 | } 64 | -------------------------------------------------------------------------------- /channel/client.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package channel 18 | 19 | import ( 20 | "bytes" 21 | "fmt" 22 | "io" 23 | "net/url" 24 | "strings" 25 | 26 | "github.com/sirupsen/logrus" 27 | 28 | corev1 "k8s.io/api/core/v1" 29 | "k8s.io/client-go/kubernetes" 30 | "k8s.io/client-go/kubernetes/scheme" 31 | "k8s.io/client-go/rest" 32 | "k8s.io/client-go/tools/remotecommand" 33 | "sigs.k8s.io/controller-runtime/pkg/cache" 34 | "sigs.k8s.io/controller-runtime/pkg/client" 35 | "sigs.k8s.io/controller-runtime/pkg/manager" 36 | ) 37 | 38 | // Client contains the kubernetes client, operator client and kubeconfig 39 | type Client struct { 40 | kubernetes.Interface 41 | client.Client 42 | Config *rest.Config 43 | } 44 | 45 | // NewClientFunc returns the controller client 46 | func NewClientFunc() manager.NewClientFunc { 47 | return func(cache cache.Cache, config *rest.Config, options client.Options) (client.Client, error) { 48 | // Create the Client for Write operations. 49 | c, err := client.New(config, options) 50 | if err != nil { 51 | return nil, err 52 | } 53 | cli := &Client{} 54 | cli.Interface = kubernetes.NewForConfigOrDie(config) 55 | cli.Client = &client.DelegatingClient{ 56 | Reader: &client.DelegatingReader{ 57 | CacheReader: cache, 58 | ClientReader: c, 59 | }, 60 | Writer: c, 61 | StatusClient: c, 62 | } 63 | cli.Config = config 64 | return cli, nil 65 | } 66 | } 67 | 68 | type IOStreams struct { 69 | In io.Reader 70 | Out io.Writer 71 | ErrOut io.Writer 72 | } 73 | 74 | type StreamOptions struct { 75 | IOStreams 76 | Stdin bool 77 | TTY bool 78 | OutDecoder func(bytes []byte) interface{} 79 | ErrDecoder func(bytes []byte) interface{} 80 | } 81 | 82 | type ExecOptions struct { 83 | StreamOptions 84 | PodName string 85 | PodNamespace string 86 | ContainerName string 87 | Command []string 88 | IgnoreOutput bool 89 | } 90 | 91 | // Exec command in pod 92 | func (c *Client) Exec(options *ExecOptions) interface{} { 93 | logFields := logrus.WithFields(logrus.Fields{ 94 | "command": options.Command, 95 | "podName": options.PodName, 96 | "podNamespace": options.PodNamespace, 97 | "container": options.ContainerName, 98 | }) 99 | logFields.Infof("Exec command in pod") 100 | request := c.CoreV1().RESTClient().Post(). 101 | Resource("pods"). 102 | Name(options.PodName). 103 | Namespace(options.PodNamespace). 104 | SubResource("exec"). 105 | VersionedParams( 106 | &corev1.PodExecOptions{ 107 | Container: options.ContainerName, 108 | Command: options.Command, 109 | Stdin: options.Stdin, 110 | Stdout: true, 111 | Stderr: true, 112 | TTY: options.TTY, 113 | }, scheme.ParameterCodec) 114 | output := bytes.NewBuffer([]byte{}) 115 | options.Out = output 116 | errput := bytes.NewBuffer([]byte{}) 117 | options.ErrOut = errput 118 | 119 | err := execute("POST", request.URL(), c.Config, options) 120 | errMsg := strings.TrimSpace(errput.String()) 121 | outMsg := strings.TrimSpace(output.String()) 122 | execLog := logFields.WithFields(logrus.Fields{ 123 | "err": errMsg, 124 | "out": outMsg, 125 | }) 126 | if errMsg != "" { 127 | execLog.Infof("get err message") 128 | return options.ErrDecoder(errput.Bytes()) 129 | } 130 | if err != nil { 131 | execLog.WithError(err).Errorln("Invoke exec command error") 132 | return options.ErrDecoder([]byte(err.Error())) 133 | } 134 | if outMsg != "" { 135 | execLog.Infof("get output message") 136 | return options.OutDecoder(output.Bytes()) 137 | } 138 | if options.IgnoreOutput { 139 | return nil 140 | } 141 | return options.ErrDecoder([]byte(fmt.Sprintf("cannot get output of pods/%s/exec, maybe kubelet cannot be accessed or container not found", 142 | options.PodName))) 143 | } 144 | 145 | // "172.21.1.11:8080/api/v1/namespaces/default/pods/my-nginx-3855515330-l1uqk/exec 146 | // ?container=my-nginx&stdin=1&stdout=1&stderr=1&tty=1&command=%2Fbin%2Fbash" 147 | func execute(method string, url *url.URL, config *rest.Config, options *ExecOptions) error { 148 | exec, err := remotecommand.NewSPDYExecutor(config, method, url) 149 | if err != nil { 150 | return err 151 | } 152 | return exec.Stream(remotecommand.StreamOptions{ 153 | Stdin: options.StreamOptions.In, 154 | Stdout: options.StreamOptions.Out, 155 | Stderr: options.StreamOptions.ErrOut, 156 | Tty: options.StreamOptions.TTY, 157 | }) 158 | } 159 | -------------------------------------------------------------------------------- /channel/client_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package channel 18 | 19 | import ( 20 | "reflect" 21 | "testing" 22 | 23 | "github.com/chaosblade-io/chaosblade-spec-go/spec" 24 | "k8s.io/client-go/kubernetes" 25 | "k8s.io/client-go/rest" 26 | "sigs.k8s.io/controller-runtime/pkg/client" 27 | "sigs.k8s.io/controller-runtime/pkg/client/config" 28 | ) 29 | 30 | func TestClient_Exec(t *testing.T) { 31 | type fields struct { 32 | Interface kubernetes.Interface 33 | Client client.Client 34 | Config *rest.Config 35 | } 36 | type args struct { 37 | options *ExecOptions 38 | } 39 | kubeconfig := config.GetConfigOrDie() 40 | tests := []struct { 41 | name string 42 | fields fields 43 | args args 44 | want *spec.Response 45 | }{ 46 | { 47 | name: "test exec", 48 | fields: fields{ 49 | Interface: kubernetes.NewForConfigOrDie(kubeconfig), 50 | Client: nil, 51 | Config: kubeconfig, 52 | }, 53 | args: args{ 54 | options: &ExecOptions{ 55 | StreamOptions: StreamOptions{ 56 | ErrDecoder: func(bytes []byte) interface{} { 57 | content := string(bytes) 58 | return spec.Decode(content, spec.ReturnFail(spec.Code[spec.K8sInvokeError], content)) 59 | }, 60 | OutDecoder: func(bytes []byte) interface{} { 61 | content := string(bytes) 62 | return spec.Decode(content, spec.ReturnFail(spec.Code[spec.K8sInvokeError], content)) 63 | }, 64 | }, 65 | PodName: "frontend-6c887c56c8-4g7sh", 66 | PodNamespace: "default", 67 | ContainerName: "php-redis", 68 | Command: []string{"/opt/chaosblade/blade", "create", "cpu", "fullload"}, 69 | IgnoreOutput: false, 70 | }, 71 | }, 72 | want: nil, 73 | }, 74 | } 75 | for _, tt := range tests { 76 | t.Run(tt.name, func(t *testing.T) { 77 | c := &Client{ 78 | Interface: tt.fields.Interface, 79 | Client: tt.fields.Client, 80 | Config: tt.fields.Config, 81 | } 82 | if got := c.Exec(tt.args.options); !reflect.DeepEqual(got, tt.want) { 83 | t.Errorf("Exec() = %v, want %v", got, tt.want) 84 | } 85 | }) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /cmd/hookfs/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2019 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "flag" 22 | "fmt" 23 | "math/rand" 24 | "os" 25 | "os/exec" 26 | "time" 27 | 28 | "github.com/chaosblade-io/chaosblade-spec-go/util" 29 | "github.com/ethercflow/hookfs/hookfs" 30 | "github.com/sirupsen/logrus" 31 | "sigs.k8s.io/controller-runtime/pkg/runtime/signals" 32 | 33 | chaosbladehook "github.com/chaosblade-io/chaosblade-operator/pkg/hookfs" 34 | ) 35 | 36 | var ( 37 | address string 38 | pidFile string 39 | original string 40 | mountpoint string 41 | ) 42 | 43 | func main() { 44 | flag.StringVar(&address, "address", ":65534", "The address to bind") 45 | flag.StringVar(&original, "original", "", "Mapping of the original disk, not affected by the drill") 46 | flag.StringVar(&mountpoint, "mountpoint", "", "The disk of the drill. The affected directories are controlled by the path flag.") 47 | rand.Seed(time.Now().UnixNano()) 48 | flag.Parse() 49 | 50 | logFields := logrus.WithFields(logrus.Fields{ 51 | "address": address, 52 | "original": original, 53 | "mountpoint": mountpoint, 54 | }) 55 | stopCh := signals.SetupSignalHandler() 56 | chaosbladeHookServer := chaosbladehook.NewChaosbladeHookServer(address) 57 | logFields.Infoln("Start chaosblade hook server.") 58 | go chaosbladeHookServer.Start(stopCh) 59 | 60 | logFields.Infoln("Start fuse server.") 61 | if err := startFuseServer(stopCh); err != nil { 62 | logFields.WithError(err).Fatalln("Start fuse server failed") 63 | } 64 | } 65 | 66 | // startFuseServer starts hookfs server 67 | func startFuseServer(stop <-chan struct{}) error { 68 | if !util.IsExist(original) { 69 | if err := os.MkdirAll(original, os.FileMode(755)); err != nil { 70 | return fmt.Errorf("create original directory error, %v", err) 71 | } 72 | } 73 | if !util.IsExist(mountpoint) { 74 | if err := os.MkdirAll(mountpoint, os.FileMode(755)); err != nil { 75 | return fmt.Errorf("create mountpoint directory error, %v", err) 76 | } 77 | } 78 | fs, err := hookfs.NewHookFs(original, mountpoint, &chaosbladehook.ChaosbladeHook{MountPoint: mountpoint}) 79 | if err != nil { 80 | return fmt.Errorf("create hookfs error, %v", err) 81 | } 82 | errCh := make(chan error) 83 | go func() { 84 | errCh <- fs.Serve() 85 | }() 86 | for { 87 | select { 88 | case <-stop: 89 | logFields := logrus.WithFields(logrus.Fields{ 90 | "address": address, 91 | "original": original, 92 | "mountpoint": mountpoint, 93 | }) 94 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 95 | defer cancel() 96 | cmd := exec.CommandContext(ctx, "fusermount", "-zu", mountpoint) 97 | logFields.Infof("Start unmount fuse volume, cmd: %v", cmd) 98 | if err := cmd.Run(); err != nil { 99 | logFields.WithError(err).Errorln("Failed to execute fusermount") 100 | } 101 | return err 102 | case err := <-errCh: 103 | if err != nil { 104 | return err 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /cmd/manager/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "flag" 22 | "runtime" 23 | "strings" 24 | 25 | "github.com/operator-framework/operator-sdk/pkg/k8sutil" 26 | "github.com/operator-framework/operator-sdk/pkg/leader" 27 | "github.com/operator-framework/operator-sdk/pkg/log/zap" 28 | sdkVersion "github.com/operator-framework/operator-sdk/version" 29 | "github.com/sirupsen/logrus" 30 | "github.com/spf13/pflag" 31 | "k8s.io/apimachinery/pkg/api/meta" 32 | "sigs.k8s.io/controller-runtime/pkg/webhook" 33 | // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) 34 | _ "k8s.io/client-go/plugin/pkg/client/auth" 35 | "k8s.io/client-go/rest" 36 | "sigs.k8s.io/controller-runtime/pkg/cache" 37 | "sigs.k8s.io/controller-runtime/pkg/client/apiutil" 38 | "sigs.k8s.io/controller-runtime/pkg/client/config" 39 | "sigs.k8s.io/controller-runtime/pkg/manager" 40 | "sigs.k8s.io/controller-runtime/pkg/runtime/log" 41 | "sigs.k8s.io/controller-runtime/pkg/runtime/signals" 42 | 43 | "github.com/chaosblade-io/chaosblade-operator/channel" 44 | "github.com/chaosblade-io/chaosblade-operator/pkg/apis" 45 | "github.com/chaosblade-io/chaosblade-operator/pkg/controller" 46 | operator "github.com/chaosblade-io/chaosblade-operator/pkg/runtime" 47 | "github.com/chaosblade-io/chaosblade-operator/pkg/runtime/chaosblade" 48 | webhookcfg "github.com/chaosblade-io/chaosblade-operator/pkg/webhook" 49 | mutator "github.com/chaosblade-io/chaosblade-operator/pkg/webhook/pod" 50 | "github.com/chaosblade-io/chaosblade-operator/version" 51 | ) 52 | 53 | func printVersion() { 54 | logrus.Infof("Go Version: %s", runtime.Version()) 55 | logrus.Infof("Go OS/Arch: %s/%s", runtime.GOOS, runtime.GOARCH) 56 | logrus.Infof("Version of operator-sdk: %v", sdkVersion.Version) 57 | logrus.Infof("Operator Version: %v", version.Version) 58 | logrus.Infof("Operator Product: %v", version.Product) 59 | logrus.Infof("Daemonset Enable: %t", chaosblade.DaemonsetEnable) 60 | } 61 | 62 | func main() { 63 | pflag.CommandLine.AddFlagSet(zap.FlagSet()) 64 | pflag.CommandLine.AddFlagSet(operator.FlagSet()) 65 | pflag.CommandLine.AddFlagSet(webhookcfg.FlagSet()) 66 | pflag.CommandLine.AddGoFlagSet(flag.CommandLine) 67 | pflag.Parse() 68 | 69 | initLogger() 70 | printVersion() 71 | 72 | cfg, err := config.GetConfig() 73 | if err != nil { 74 | logrus.Fatalf("Get apiserver config error, %v", err) 75 | } 76 | err = leader.Become(context.TODO(), "chaosblade-operator-lock") 77 | if err != nil { 78 | logrus.Fatalf("Become leader error, %v", err) 79 | } 80 | cfg.QPS = operator.QPS 81 | mgr, err := createManager(cfg) 82 | if err != nil { 83 | logrus.Fatalf("Create operator manager error, %v", err) 84 | } 85 | addComponentsToManager(mgr) 86 | logrus.Infoln("Starting the manager.") 87 | if err := mgr.Start(signals.SetupSignalHandler()); err != nil { 88 | logrus.Fatalf("Manager exited non-zero, %v", err) 89 | } 90 | } 91 | 92 | func addComponentsToManager(mgr manager.Manager) { 93 | logrus.Infof("Add all resources to scheme") 94 | // Setup Scheme for all resources 95 | if err := apis.AddToScheme(mgr.GetScheme()); err != nil { 96 | logrus.Fatalf("Add all resources to scheme error, %v", err) 97 | } 98 | logrus.Infof("Add all controllers to manager") 99 | // Setup all Controllers 100 | if err := controller.AddToManager(mgr); err != nil { 101 | logrus.Fatalf("Add all controllers to manager error, %v", err) 102 | } 103 | if webhookcfg.Enable { 104 | logrus.Infof("Webhook enabled, add it to manager") 105 | if err := addWebhook(mgr); err != nil { 106 | logrus.Fatalf("Add webhook to manager error, %v", err) 107 | } 108 | } 109 | } 110 | 111 | // Init logrus and controller-runtime log 112 | func initLogger() { 113 | level, err := logrus.ParseLevel(operator.LogLevel) 114 | if err != nil { 115 | level = logrus.InfoLevel 116 | } 117 | logrus.SetLevel(level) 118 | log.SetLogger(zap.Logger()) 119 | } 120 | 121 | func addWebhook(m manager.Manager) error { 122 | hookServer := &webhook.Server{ 123 | Port: webhookcfg.Port, 124 | } 125 | if err := m.Add(hookServer); err != nil { 126 | return err 127 | } 128 | logrus.Infof("registering %s to the webhook server", "mutating-pods") 129 | hookServer.Register("/mutating-pods", &webhook.Admission{Handler: &mutator.Mutator{}}) 130 | return nil 131 | } 132 | 133 | // createManager supports multi namespaces configuration 134 | func createManager(cfg *rest.Config) (manager.Manager, error) { 135 | watchNamespace, err := k8sutil.GetWatchNamespace() 136 | if err != nil { 137 | return nil, err 138 | } 139 | logrus.Infof("Get watch namespace is %s", watchNamespace) 140 | if strings.Contains(watchNamespace, ",") { 141 | namespaces := strings.Split(watchNamespace, ",") 142 | return manager.New(cfg, manager.Options{ 143 | NewCache: cache.MultiNamespacedCacheBuilder(namespaces), 144 | MapperProvider: func(c *rest.Config) (meta.RESTMapper, error) { 145 | return apiutil.NewDynamicRESTMapper(c) 146 | }, 147 | NewClient: channel.NewClientFunc(), 148 | }) 149 | } 150 | return manager.New(cfg, manager.Options{ 151 | Namespace: watchNamespace, 152 | MapperProvider: func(c *rest.Config) (meta.RESTMapper, error) { 153 | return apiutil.NewDynamicRESTMapper(c) 154 | }, 155 | NewClient: channel.NewClientFunc(), 156 | }) 157 | } 158 | -------------------------------------------------------------------------------- /deploy/helm/chaosblade-operator-arm64/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /deploy/helm/chaosblade-operator-arm64/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | appVersion: "1.7.4" 3 | description: ChaosBlade Operator 4 | name: chaosblade-operator-arm64 5 | version: 1.7.4 6 | home: https://github.com/chaosblade-io 7 | -------------------------------------------------------------------------------- /deploy/helm/chaosblade-operator-arm64/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | Thank you for using chaosblade. -------------------------------------------------------------------------------- /deploy/helm/chaosblade-operator-arm64/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | -------------------------------------------------------------------------------- /deploy/helm/chaosblade-operator-arm64/templates/daemonset.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.daemonset.enable }} 2 | apiVersion: apps/v1 3 | kind: DaemonSet 4 | metadata: 5 | name: chaosblade-tool 6 | labels: 7 | name: chaosblade-tool 8 | app: chaosblade-tool 9 | spec: 10 | selector: 11 | matchLabels: 12 | name: chaosblade-tool 13 | app: chaosblade-tool 14 | updateStrategy: 15 | type: RollingUpdate 16 | template: 17 | metadata: 18 | labels: 19 | name: chaosblade-tool 20 | app: chaosblade-tool 21 | spec: 22 | affinity: 23 | nodeAffinity: 24 | requiredDuringSchedulingIgnoredDuringExecution: 25 | nodeSelectorTerms: 26 | - matchExpressions: 27 | - key: type 28 | operator: NotIn 29 | values: 30 | - virtual-kubelet 31 | containers: 32 | - name: chaosblade-tool 33 | image: {{ .Values.blade.repository }}:{{ .Values.blade.version }} 34 | imagePullPolicy: {{ .Values.blade.pullPolicy }} 35 | env: 36 | - name: KUBERNETES_NODENAME 37 | valueFrom: 38 | fieldRef: 39 | fieldPath: spec.nodeName 40 | - name: DOCKER_API_VERSION 41 | value: "1.14.0" 42 | - name: CGROUP_ROOT 43 | value: "/host-sys/fs/cgroup/" 44 | securityContext: 45 | privileged: true 46 | volumeMounts: 47 | - mountPath: /var/run/docker.sock 48 | name: docker-socket 49 | - mountPath: /opt/chaosblade/chaosblade.dat 50 | name: chaosblade-db-volume 51 | - mountPath: /etc/hosts 52 | name: hosts 53 | - mountPath: /var/log/audit 54 | name: audit 55 | - mountPath: /var/lib/docker 56 | name: docker-lib 57 | - mountPath: /etc/docker 58 | name: docker-etc 59 | - mountPath: /run/containerd 60 | name: containerd 61 | - mountPath: /var/lib/containerd 62 | name: containerd-lib 63 | - mountPath: /etc/containerd 64 | name: containerd-etc 65 | - mountPath: /var/run/netns 66 | name: netns 67 | - mountPath: /host-sys 68 | name: sys 69 | dnsPolicy: ClusterFirstWithHostNet 70 | hostNetwork: true 71 | hostPID: true 72 | tolerations: 73 | - effect: NoSchedule 74 | operator: Exists 75 | volumes: 76 | - hostPath: 77 | path: /var/run/docker.sock 78 | name: docker-socket 79 | - hostPath: 80 | path: /var/run/chaosblade.dat 81 | type: FileOrCreate 82 | name: chaosblade-db-volume 83 | - hostPath: 84 | path: /etc/hosts 85 | name: hosts 86 | - hostPath: 87 | path: /var/lib/docker 88 | name: docker-lib 89 | - hostPath: 90 | path: /etc/docker 91 | name: docker-etc 92 | - hostPath: 93 | path: /var/log/audit 94 | name: audit 95 | - hostPath: 96 | path: /run/containerd 97 | name: containerd 98 | - hostPath: 99 | path: /var/lib/containerd 100 | name: containerd-lib 101 | - hostPath: 102 | path: /etc/containerd 103 | name: containerd-etc 104 | - hostPath: 105 | path: /var/run/netns 106 | name: netns 107 | - hostPath: 108 | path: /sys 109 | name: sys 110 | serviceAccountName: chaosblade 111 | {{- end }} 112 | -------------------------------------------------------------------------------- /deploy/helm/chaosblade-operator-arm64/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: chaosblade-operator 5 | namespace: {{ .Release.Namespace }} 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | name: chaosblade-operator 11 | template: 12 | metadata: 13 | labels: 14 | name: chaosblade-operator 15 | part-of: chaosblade 16 | spec: 17 | dnsPolicy: {{ .Values.network.dns.policy }} 18 | hostNetwork: {{ .Values.network.host }} 19 | serviceAccountName: chaosblade 20 | initContainers: 21 | - name: chaosblade-tool 22 | image: {{ .Values.blade.repository }}:{{ .Values.blade.version }} 23 | imagePullPolicy: {{ .Values.blade.pullPolicy }} 24 | command: [ "cp", "-R","/opt/chaosblade", "/home" ] 25 | volumeMounts: 26 | - mountPath: /home 27 | name: chaosblade 28 | containers: 29 | - name: chaosblade-operator 30 | image: {{ .Values.operator.repository }}:{{ .Values.operator.version }} 31 | command: ["chaosblade-operator"] 32 | args: 33 | {{- if .Values.blade.repository }} 34 | - '--chaosblade-image-repository={{ .Values.blade.repository }}' 35 | {{- end }} 36 | {{- if .Values.blade.version }} 37 | - '--chaosblade-version={{ .Values.blade.version }}' 38 | {{- end }} 39 | {{- if .Values.blade.pullPolicy }} 40 | - '--chaosblade-image-pull-policy={{ .Values.blade.pullPolicy }}' 41 | {{- end }} 42 | {{- if .Values.env.zapLevel }} 43 | - '--zap-level={{ .Values.env.zapLevel }}' 44 | {{- end }} 45 | {{- if .Values.env.logLevel }} 46 | - '--log-level={{ .Values.env.logLevel }}' 47 | {{- end }} 48 | {{- if .Values.webhook.enable }} 49 | - '--webhook-enable' 50 | {{- end }} 51 | {{- if .Values.daemonset.enable }} 52 | - '--daemonset-enable' 53 | {{- end }} 54 | {{- if .Values.remove.blade.interval }} 55 | - '--remove-blade-interval={{ .Values.remove.blade.interval }}' 56 | {{- end }} 57 | {{- if .Values.blade.downloadUrl }} 58 | - '--chaosblade-download-url={{ .Values.blade.downloadUrl }}' 59 | {{- end }} 60 | - '--chaosblade-namespace={{ .Release.Namespace }}' 61 | imagePullPolicy: {{ .Values.operator.pullPolicy }} 62 | env: 63 | - name: WATCH_NAMESPACE 64 | value: "" 65 | - name: POD_NAME 66 | valueFrom: 67 | fieldRef: 68 | fieldPath: metadata.name 69 | - name: OPERATOR_NAME 70 | value: "chaosblade-operator" 71 | ports: 72 | - containerPort: 9443 73 | protocol: TCP 74 | volumeMounts: 75 | - mountPath: /tmp/k8s-webhook-server/serving-certs 76 | name: cert 77 | readOnly: true 78 | - mountPath: /opt 79 | name: chaosblade 80 | volumes: 81 | - name: cert 82 | secret: 83 | defaultMode: 420 84 | secretName: chaosblade-webhook-server-cert 85 | - name: chaosblade 86 | emptyDir: {} 87 | -------------------------------------------------------------------------------- /deploy/helm/chaosblade-operator-arm64/templates/rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: chaosblade 5 | labels: 6 | name: chaosblade 7 | namespace: {{ .Release.Namespace }} 8 | 9 | --- 10 | apiVersion: rbac.authorization.k8s.io/v1 11 | kind: ClusterRole 12 | metadata: 13 | name: chaosblade 14 | labels: 15 | name: chaosblade 16 | rules: 17 | - apiGroups: 18 | - '' 19 | resources: 20 | - pods 21 | - pods/exec 22 | - configmaps 23 | verbs: 24 | - "*" 25 | - apiGroups: 26 | - '' 27 | resources: 28 | - nodes 29 | verbs: 30 | - get 31 | - list 32 | - watch 33 | - apiGroups: 34 | - apps 35 | resources: 36 | - daemonsets 37 | - deployments 38 | verbs: 39 | - "*" 40 | - apiGroups: 41 | - chaosblade.io 42 | resources: 43 | - chaosblades 44 | - chaosblades/status 45 | verbs: 46 | - "*" 47 | --- 48 | apiVersion: rbac.authorization.k8s.io/v1 49 | kind: ClusterRoleBinding 50 | metadata: 51 | name: chaosblade 52 | labels: 53 | name: chaosblade 54 | roleRef: 55 | kind: ClusterRole 56 | name: chaosblade 57 | apiGroup: rbac.authorization.k8s.io 58 | subjects: 59 | - kind: ServiceAccount 60 | name: chaosblade 61 | namespace: {{ .Release.Namespace }} 62 | -------------------------------------------------------------------------------- /deploy/helm/chaosblade-operator-arm64/templates/secret.yaml: -------------------------------------------------------------------------------- 1 | {{- $ca := genCA "chaosblade-webhook-server-ca" 3650 }} 2 | {{- $cn := "chaosblade-webhook-server" }} 3 | {{- $dns1 := printf "%s.%s" $cn .Release.Namespace }} 4 | {{- $dns2 := printf "%s.%s.svc" $cn .Release.Namespace }} 5 | {{- $cert := genSignedCert $cn nil (list $dns1 $dns2) 3650 $ca }} 6 | 7 | apiVersion: admissionregistration.k8s.io/v1 8 | kind: MutatingWebhookConfiguration 9 | metadata: 10 | name: chaosblade-operator 11 | namespace: {{ .Release.Namespace }} 12 | labels: 13 | app: chaosblade-operator 14 | chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" 15 | release: "{{ .Release.Name }}" 16 | heritage: "{{ .Release.Service }}" 17 | webhooks: 18 | - clientConfig: 19 | caBundle: {{ $ca.Cert | b64enc | quote }} 20 | service: 21 | name: chaosblade-webhook-server 22 | namespace: {{ .Release.Namespace }} 23 | path: /mutating-pods 24 | name: "{{ .Chart.Name }}.{{ .Release.Namespace }}.svc" 25 | failurePolicy: Ignore 26 | rules: 27 | - apiGroups: 28 | - "" 29 | apiVersions: 30 | - v1 31 | operations: 32 | - CREATE 33 | - UPDATE 34 | resources: 35 | - pods 36 | sideEffects: None 37 | admissionReviewVersions: ["v1beta1"] 38 | --- 39 | apiVersion: v1 40 | kind: Secret 41 | metadata: 42 | name: chaosblade-webhook-server-cert 43 | namespace: {{ .Release.Namespace }} 44 | labels: 45 | app: chaosblade-operator 46 | chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" 47 | heritage: {{ .Release.Service }} 48 | release: {{ .Release.Name }} 49 | type: kubernetes.io/tls 50 | data: 51 | tls.crt: {{ $cert.Cert | b64enc | quote }} 52 | tls.key: {{ $cert.Key | b64enc | quote }} 53 | ca.crt: {{ $ca.Cert | b64enc | quote }} 54 | -------------------------------------------------------------------------------- /deploy/helm/chaosblade-operator-arm64/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: chaosblade-webhook-server 5 | namespace: {{ .Release.Namespace }} 6 | spec: 7 | ports: 8 | - port: 443 9 | targetPort: 9443 10 | selector: 11 | name: chaosblade-operator 12 | -------------------------------------------------------------------------------- /deploy/helm/chaosblade-operator-arm64/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for chaosblade. 2 | 3 | # chaosblade-operator 4 | operator: 5 | repository: ghcr.io/chaosblade-io/chaosblade-operator-arm64 6 | version: 1.7.4 7 | # image.pullPolicy: must be Always|IfNotPresent|Never 8 | pullPolicy: IfNotPresent 9 | # qps of kubernetes client 10 | qps: 20 11 | reconcileCount: 20 12 | 13 | blade: 14 | repository: ghcr.io/chaosblade-io/chaosblade-tool-arm64 15 | version: 1.7.4 16 | pullPolicy: IfNotPresent 17 | downloadUrl: "" 18 | 19 | env: 20 | logLevel: info 21 | 22 | webhook: 23 | enable: true 24 | 25 | daemonset: 26 | enable: true 27 | 28 | remove: 29 | blade: 30 | interval: 72h 31 | 32 | network: 33 | host: false 34 | dns: 35 | policy: ClusterFirst 36 | -------------------------------------------------------------------------------- /deploy/helm/chaosblade-operator/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /deploy/helm/chaosblade-operator/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | appVersion: "1.7.4" 3 | description: ChaosBlade Operator 4 | name: chaosblade-operator 5 | version: 1.7.4 6 | home: https://github.com/chaosblade-io 7 | -------------------------------------------------------------------------------- /deploy/helm/chaosblade-operator/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | Thank you for using chaosblade. -------------------------------------------------------------------------------- /deploy/helm/chaosblade-operator/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | -------------------------------------------------------------------------------- /deploy/helm/chaosblade-operator/templates/daemonset.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.daemonset.enable }} 2 | apiVersion: apps/v1 3 | kind: DaemonSet 4 | metadata: 5 | name: chaosblade-tool 6 | labels: 7 | name: chaosblade-tool 8 | app: chaosblade-tool 9 | spec: 10 | selector: 11 | matchLabels: 12 | name: chaosblade-tool 13 | app: chaosblade-tool 14 | updateStrategy: 15 | type: RollingUpdate 16 | template: 17 | metadata: 18 | labels: 19 | name: chaosblade-tool 20 | app: chaosblade-tool 21 | spec: 22 | affinity: 23 | nodeAffinity: 24 | requiredDuringSchedulingIgnoredDuringExecution: 25 | nodeSelectorTerms: 26 | - matchExpressions: 27 | - key: type 28 | operator: NotIn 29 | values: 30 | - virtual-kubelet 31 | containers: 32 | - name: chaosblade-tool 33 | image: {{ .Values.blade.repository }}:{{ .Values.blade.version }} 34 | imagePullPolicy: {{ .Values.blade.pullPolicy }} 35 | env: 36 | - name: KUBERNETES_NODENAME 37 | valueFrom: 38 | fieldRef: 39 | fieldPath: spec.nodeName 40 | - name: DOCKER_API_VERSION 41 | value: "1.14.0" 42 | - name: CGROUP_ROOT 43 | value: "/host-sys/fs/cgroup/" 44 | securityContext: 45 | privileged: true 46 | volumeMounts: 47 | - mountPath: /var/run/docker.sock 48 | name: docker-socket 49 | - mountPath: /opt/chaosblade/chaosblade.dat 50 | name: chaosblade-db-volume 51 | - mountPath: /etc/hosts 52 | name: hosts 53 | - mountPath: /var/log/audit 54 | name: audit 55 | - mountPath: /var/lib/docker 56 | name: docker-lib 57 | - mountPath: /etc/docker 58 | name: docker-etc 59 | - mountPath: /run/containerd 60 | name: containerd 61 | - mountPath: /var/lib/containerd 62 | name: containerd-lib 63 | - mountPath: /etc/containerd 64 | name: containerd-etc 65 | - mountPath: /var/run/netns 66 | name: netns 67 | - mountPath: /host-sys 68 | name: sys 69 | dnsPolicy: ClusterFirstWithHostNet 70 | hostNetwork: true 71 | hostPID: true 72 | tolerations: 73 | - effect: NoSchedule 74 | operator: Exists 75 | volumes: 76 | - hostPath: 77 | path: /var/run/docker.sock 78 | name: docker-socket 79 | - hostPath: 80 | path: /var/run/chaosblade.dat 81 | type: FileOrCreate 82 | name: chaosblade-db-volume 83 | - hostPath: 84 | path: /etc/hosts 85 | name: hosts 86 | - hostPath: 87 | path: /var/lib/docker 88 | name: docker-lib 89 | - hostPath: 90 | path: /etc/docker 91 | name: docker-etc 92 | - hostPath: 93 | path: /var/log/audit 94 | name: audit 95 | - hostPath: 96 | path: /run/containerd 97 | name: containerd 98 | - hostPath: 99 | path: /var/lib/containerd 100 | name: containerd-lib 101 | - hostPath: 102 | path: /etc/containerd 103 | name: containerd-etc 104 | - hostPath: 105 | path: /var/run/netns 106 | name: netns 107 | - hostPath: 108 | path: /sys 109 | name: sys 110 | serviceAccountName: chaosblade 111 | {{- end }} 112 | -------------------------------------------------------------------------------- /deploy/helm/chaosblade-operator/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: chaosblade-operator 5 | namespace: {{ .Release.Namespace }} 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | name: chaosblade-operator 11 | template: 12 | metadata: 13 | labels: 14 | name: chaosblade-operator 15 | part-of: chaosblade 16 | spec: 17 | dnsPolicy: {{ .Values.network.dns.policy }} 18 | hostNetwork: {{ .Values.network.host }} 19 | serviceAccountName: chaosblade 20 | initContainers: 21 | - name: chaosblade-tool 22 | image: {{ .Values.blade.repository }}:{{ .Values.blade.version }} 23 | imagePullPolicy: {{ .Values.blade.pullPolicy }} 24 | command: [ "cp", "-R","/opt/chaosblade", "/home" ] 25 | volumeMounts: 26 | - mountPath: /home 27 | name: chaosblade 28 | containers: 29 | - name: chaosblade-operator 30 | image: {{ .Values.operator.repository }}:{{ .Values.operator.version }} 31 | command: ["chaosblade-operator"] 32 | args: 33 | {{- if .Values.blade.repository }} 34 | - '--chaosblade-image-repository={{ .Values.blade.repository }}' 35 | {{- end }} 36 | {{- if .Values.blade.version }} 37 | - '--chaosblade-version={{ .Values.blade.version }}' 38 | {{- end }} 39 | {{- if .Values.blade.pullPolicy }} 40 | - '--chaosblade-image-pull-policy={{ .Values.blade.pullPolicy }}' 41 | {{- end }} 42 | {{- if .Values.env.zapLevel }} 43 | - '--zap-level={{ .Values.env.zapLevel }}' 44 | {{- end }} 45 | {{- if .Values.env.logLevel }} 46 | - '--log-level={{ .Values.env.logLevel }}' 47 | {{- end }} 48 | {{- if .Values.webhook.enable }} 49 | - '--webhook-enable' 50 | {{- end }} 51 | {{- if .Values.daemonset.enable }} 52 | - '--daemonset-enable' 53 | {{- end }} 54 | {{- if .Values.remove.blade.interval }} 55 | - '--remove-blade-interval={{ .Values.remove.blade.interval }}' 56 | {{- end }} 57 | {{- if .Values.blade.downloadUrl }} 58 | - '--chaosblade-download-url={{ .Values.blade.downloadUrl }}' 59 | {{- end }} 60 | - '--chaosblade-namespace={{ .Release.Namespace }}' 61 | imagePullPolicy: {{ .Values.operator.pullPolicy }} 62 | env: 63 | - name: WATCH_NAMESPACE 64 | value: "" 65 | - name: POD_NAME 66 | valueFrom: 67 | fieldRef: 68 | fieldPath: metadata.name 69 | - name: OPERATOR_NAME 70 | value: "chaosblade-operator" 71 | ports: 72 | - containerPort: 9443 73 | protocol: TCP 74 | volumeMounts: 75 | - mountPath: /tmp/k8s-webhook-server/serving-certs 76 | name: cert 77 | readOnly: true 78 | - mountPath: /opt 79 | name: chaosblade 80 | volumes: 81 | - name: cert 82 | secret: 83 | defaultMode: 420 84 | secretName: chaosblade-webhook-server-cert 85 | - name: chaosblade 86 | emptyDir: {} 87 | -------------------------------------------------------------------------------- /deploy/helm/chaosblade-operator/templates/rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: chaosblade 5 | labels: 6 | name: chaosblade 7 | namespace: {{ .Release.Namespace }} 8 | 9 | --- 10 | apiVersion: rbac.authorization.k8s.io/v1 11 | kind: ClusterRole 12 | metadata: 13 | name: chaosblade 14 | labels: 15 | name: chaosblade 16 | rules: 17 | - apiGroups: 18 | - '' 19 | resources: 20 | - pods 21 | - pods/exec 22 | - configmaps 23 | verbs: 24 | - "*" 25 | - apiGroups: 26 | - '' 27 | resources: 28 | - nodes 29 | verbs: 30 | - get 31 | - list 32 | - watch 33 | - apiGroups: 34 | - apps 35 | resources: 36 | - daemonsets 37 | - deployments 38 | verbs: 39 | - "*" 40 | - apiGroups: 41 | - chaosblade.io 42 | resources: 43 | - chaosblades 44 | - chaosblades/status 45 | verbs: 46 | - "*" 47 | --- 48 | apiVersion: rbac.authorization.k8s.io/v1 49 | kind: ClusterRoleBinding 50 | metadata: 51 | name: chaosblade 52 | labels: 53 | name: chaosblade 54 | roleRef: 55 | kind: ClusterRole 56 | name: chaosblade 57 | apiGroup: rbac.authorization.k8s.io 58 | subjects: 59 | - kind: ServiceAccount 60 | name: chaosblade 61 | namespace: {{ .Release.Namespace }} 62 | -------------------------------------------------------------------------------- /deploy/helm/chaosblade-operator/templates/secret.yaml: -------------------------------------------------------------------------------- 1 | {{- $ca := genCA "chaosblade-webhook-server-ca" 3650 }} 2 | {{- $cn := "chaosblade-webhook-server" }} 3 | {{- $dns1 := printf "%s.%s" $cn .Release.Namespace }} 4 | {{- $dns2 := printf "%s.%s.svc" $cn .Release.Namespace }} 5 | {{- $cert := genSignedCert $cn nil (list $dns1 $dns2) 3650 $ca }} 6 | 7 | apiVersion: admissionregistration.k8s.io/v1 8 | kind: MutatingWebhookConfiguration 9 | metadata: 10 | name: chaosblade-operator 11 | namespace: {{ .Release.Namespace }} 12 | labels: 13 | app: chaosblade-operator 14 | chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" 15 | release: "{{ .Release.Name }}" 16 | heritage: "{{ .Release.Service }}" 17 | webhooks: 18 | - clientConfig: 19 | caBundle: {{ $ca.Cert | b64enc | quote }} 20 | service: 21 | name: chaosblade-webhook-server 22 | namespace: {{ .Release.Namespace }} 23 | path: /mutating-pods 24 | name: "{{ .Chart.Name }}.{{ .Release.Namespace }}.svc" 25 | failurePolicy: Ignore 26 | rules: 27 | - apiGroups: 28 | - "" 29 | apiVersions: 30 | - v1 31 | operations: 32 | - CREATE 33 | - UPDATE 34 | resources: 35 | - pods 36 | sideEffects: None 37 | admissionReviewVersions: ["v1beta1"] 38 | --- 39 | apiVersion: v1 40 | kind: Secret 41 | metadata: 42 | name: chaosblade-webhook-server-cert 43 | namespace: {{ .Release.Namespace }} 44 | labels: 45 | app: chaosblade-operator 46 | chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" 47 | heritage: {{ .Release.Service }} 48 | release: {{ .Release.Name }} 49 | type: kubernetes.io/tls 50 | data: 51 | tls.crt: {{ $cert.Cert | b64enc | quote }} 52 | tls.key: {{ $cert.Key | b64enc | quote }} 53 | ca.crt: {{ $ca.Cert | b64enc | quote }} 54 | -------------------------------------------------------------------------------- /deploy/helm/chaosblade-operator/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: chaosblade-webhook-server 5 | namespace: {{ .Release.Namespace }} 6 | spec: 7 | ports: 8 | - port: 443 9 | targetPort: 9443 10 | selector: 11 | name: chaosblade-operator 12 | -------------------------------------------------------------------------------- /deploy/helm/chaosblade-operator/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for chaosblade. 2 | 3 | # chaosblade-operator 4 | operator: 5 | repository: ghcr.io/chaosblade-io/chaosblade-operator 6 | version: 1.7.4 7 | # image.pullPolicy: must be Always|IfNotPresent|Never 8 | pullPolicy: IfNotPresent 9 | # qps of kubernetes client 10 | qps: 20 11 | reconcileCount: 20 12 | 13 | blade: 14 | repository: ghcr.io/chaosblade-io/chaosblade-tool 15 | version: 1.7.4 16 | pullPolicy: IfNotPresent 17 | downloadUrl: "" 18 | 19 | env: 20 | logLevel: info 21 | 22 | webhook: 23 | enable: true 24 | 25 | daemonset: 26 | enable: true 27 | 28 | remove: 29 | blade: 30 | interval: 72h 31 | 32 | network: 33 | host: false 34 | dns: 35 | policy: ClusterFirst 36 | -------------------------------------------------------------------------------- /deploy/olm/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build clean 2 | 3 | BLADE_VERSION=0.6.0 4 | 5 | # Build [OLM](https://github.com/operator-framework/operator-lifecycle-manager) 6 | build: 7 | operator-sdk olm-catalog gen-csv \ 8 | --operator-name chaosblade-operator \ 9 | --csv-version $(BLADE_VERSION) \ 10 | --update-crds --verbose 11 | # Change `olm` keyword to `chaosblade-operator` 12 | sed 's/olm/chaosblade-operator/g' deploy/olm-catalog/olm/olm.package.yaml \ 13 | > deploy/olm-catalog/chaosblade-operator/chaosblade-operator.package.yaml 14 | 15 | rm -rf deploy/olm-catalog/olm 16 | 17 | clean: 18 | rm -rf deploy/olm-catalog -------------------------------------------------------------------------------- /deploy/olm/deploy/olm-catalog/chaosblade-operator/chaosblade-operator.package.yaml: -------------------------------------------------------------------------------- 1 | channels: 2 | - currentCSV: chaosblade-operator.v0.6.0 3 | name: alpha 4 | defaultChannel: alpha 5 | packageName: chaosblade-operator 6 | -------------------------------------------------------------------------------- /deploy/olm/deploy/operator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: chaosblade-operator 5 | namespace: kube-system 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | name: chaosblade-operator 11 | template: 12 | metadata: 13 | labels: 14 | name: chaosblade-operator 15 | spec: 16 | serviceAccountName: chaosblade 17 | containers: 18 | - name: chaosblade-operator 19 | # Replace this with the built image name 20 | image: chaosbladeio/chaosblade-operator:0.6.0 21 | command: ["chaosblade-operator"] 22 | args: 23 | - --blade-version=0.6.0 24 | - --image-repo=chaosbladeio/chaosblade-tool 25 | - --pull-policy=IfNotPresent 26 | - --namespace=kube-system 27 | imagePullPolicy: IfNotPresent 28 | env: 29 | - name: WATCH_NAMESPACE 30 | value: "" 31 | - name: POD_NAME 32 | valueFrom: 33 | fieldRef: 34 | fieldPath: metadata.name 35 | - name: OPERATOR_NAME 36 | value: "chaosblade-operator" 37 | -------------------------------------------------------------------------------- /deploy/olm/deploy/role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1beta1 2 | kind: ClusterRole 3 | metadata: 4 | name: chaosblade 5 | labels: 6 | name: chaosblade 7 | rules: 8 | - apiGroups: 9 | - '' 10 | resources: 11 | - pods 12 | - pods/exec 13 | - services 14 | - endpoints 15 | - persistentvolumeclaims 16 | - persistentvolumes 17 | - events 18 | - configmaps 19 | - secrets 20 | - namespaces 21 | - nodes 22 | verbs: 23 | - "*" 24 | - apiGroups: 25 | - extensions 26 | resources: 27 | - deployments 28 | - daemonsets 29 | - replicasets 30 | - ingresses 31 | verbs: 32 | - "*" 33 | - apiGroups: 34 | - apps 35 | resources: 36 | - deployments 37 | - daemonsets 38 | - replicasets 39 | - statefulsets 40 | verbs: 41 | - "*" 42 | - apiGroups: 43 | - chaosblade.io 44 | resources: 45 | - chaosblades 46 | - chaosblades/status 47 | verbs: 48 | - "*" 49 | -------------------------------------------------------------------------------- /deploy/olm/deploy/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1beta1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: chaosblade 5 | labels: 6 | name: chaosblade 7 | roleRef: 8 | kind: ClusterRole 9 | name: chaosblade 10 | apiGroup: rbac.authorization.k8s.io 11 | subjects: 12 | - kind: ServiceAccount 13 | name: chaosblade 14 | namespace: kube-system 15 | -------------------------------------------------------------------------------- /deploy/olm/deploy/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: chaosblade 5 | labels: 6 | name: chaosblade 7 | namespace: kube-system 8 | -------------------------------------------------------------------------------- /deploy/oss/crd.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: chaosblades.chaosblade.io 5 | spec: 6 | group: chaosblade.io 7 | names: 8 | kind: ChaosBlade 9 | listKind: ChaosBladeList 10 | plural: chaosblades 11 | singular: chaosblade 12 | shortNames: [blade] 13 | scope: Cluster 14 | subresources: 15 | status: {} 16 | validation: 17 | openAPIV3Schema: 18 | properties: 19 | apiVersion: 20 | description: 'APIVersion defines the versioned schema of this representation 21 | of an object. Servers should convert recognized schemas to the latest 22 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' 23 | type: string 24 | kind: 25 | description: 'Kind is a string value representing the REST resource this 26 | object represents. Servers may infer this from the endpoint the client 27 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' 28 | type: string 29 | metadata: 30 | type: object 31 | spec: 32 | properties: 33 | experiments: 34 | description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster 35 | Important: Run "operator-sdk generate k8s" to regenerate code after 36 | modifying this file Add custom validation using kubebuilder tags: 37 | https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html' 38 | items: 39 | properties: 40 | action: 41 | description: Action is the experiment scenario of the target, 42 | such as delay, load 43 | type: string 44 | desc: 45 | description: Desc is the experiment description 46 | type: string 47 | matchers: 48 | description: Matchers is the experiment rules 49 | items: 50 | properties: 51 | name: 52 | description: Name is the name of flag 53 | type: string 54 | value: 55 | description: 'TODO: Temporarily defined as an array for 56 | all flags Value is the value of flag' 57 | items: 58 | type: string 59 | type: array 60 | required: 61 | - name 62 | - value 63 | type: object 64 | type: array 65 | scope: 66 | description: Scope is the area of the experiments, currently support 67 | node, pod and container 68 | type: string 69 | target: 70 | description: Target is the experiment target, such as cpu, network 71 | type: string 72 | required: 73 | - scope 74 | - target 75 | - action 76 | type: object 77 | type: array 78 | required: 79 | - experiments 80 | type: object 81 | status: 82 | properties: 83 | expStatuses: 84 | description: 'Important: Run "operator-sdk generate k8s" to regenerate 85 | code after modifying this file Add custom validation using kubebuilder 86 | tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html' 87 | items: 88 | properties: 89 | action: 90 | type: string 91 | error: 92 | type: string 93 | resStatuses: 94 | description: ResStatuses is the details of the experiment 95 | items: 96 | properties: 97 | error: 98 | description: experiment error 99 | type: string 100 | id: 101 | description: experiment uid in chaosblade 102 | type: string 103 | kind: 104 | description: Kind 105 | type: string 106 | name: 107 | description: resource name 108 | type: string 109 | nodeName: 110 | description: NodeName 111 | type: string 112 | state: 113 | description: experiment state 114 | type: string 115 | success: 116 | description: success 117 | type: boolean 118 | uid: 119 | description: resource uid 120 | type: string 121 | required: 122 | - state 123 | - kind 124 | - success 125 | type: object 126 | type: array 127 | scope: 128 | description: experiment scope for cache 129 | type: string 130 | state: 131 | description: State is used to describe the experiment result 132 | type: string 133 | success: 134 | description: Success is used to judge the experiment result 135 | type: boolean 136 | target: 137 | type: string 138 | required: 139 | - scope 140 | - target 141 | - action 142 | - success 143 | - state 144 | type: object 145 | type: array 146 | phase: 147 | description: Phase indicates the state of the experiment Initial -> 148 | Running -> Updating -> Destroying -> Destroyed 149 | type: string 150 | required: 151 | - expStatuses 152 | type: object 153 | version: v1alpha1 154 | versions: 155 | - name: v1alpha1 156 | served: true 157 | storage: true 158 | -------------------------------------------------------------------------------- /deploy/oss/operator.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: chaosblade-operator 5 | namespace: kube-system 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | name: chaosblade-operator 11 | template: 12 | metadata: 13 | labels: 14 | name: chaosblade-operator 15 | spec: 16 | serviceAccountName: chaosblade 17 | containers: 18 | - name: chaosblade-operator 19 | # Replace this with the built image name 20 | image: chaosbladeio/chaosblade-operator:0.6.0 21 | command: ["chaosblade-operator"] 22 | args: 23 | - --chaosblade-version=0.6.0 24 | - --chaosblade-image-repository=chaosbladeio/chaosblade-tool 25 | - --chaosblade-image-pull-policy=IfNotPresent 26 | - --chaosblade-namespace=kube-system 27 | - --webhook-enable 28 | imagePullPolicy: IfNotPresent 29 | env: 30 | - name: WATCH_NAMESPACE 31 | value: "" 32 | - name: POD_NAME 33 | valueFrom: 34 | fieldRef: 35 | fieldPath: metadata.name 36 | - name: OPERATOR_NAME 37 | value: "chaosblade-operator" 38 | ports: 39 | - containerPort: 9443 40 | protocol: TCP 41 | volumeMounts: 42 | - mountPath: /tmp/k8s-webhook-server/serving-certs 43 | name: cert 44 | readOnly: true 45 | volumes: 46 | - name: cert 47 | secret: 48 | defaultMode: 420 49 | secretName: chaosblade-webhook-server-cert -------------------------------------------------------------------------------- /deploy/oss/rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: chaosblade 5 | labels: 6 | name: chaosblade 7 | namespace: kube-system 8 | --- 9 | apiVersion: rbac.authorization.k8s.io/v1 10 | kind: ClusterRole 11 | metadata: 12 | name: chaosblade 13 | labels: 14 | name: chaosblade 15 | rules: 16 | - apiGroups: 17 | - '' 18 | resources: 19 | - pods 20 | - pods/exec 21 | - services 22 | - endpoints 23 | - persistentvolumeclaims 24 | - persistentvolumes 25 | - events 26 | - configmaps 27 | - secrets 28 | - namespaces 29 | - nodes 30 | verbs: 31 | - "*" 32 | - apiGroups: 33 | - extensions 34 | resources: 35 | - deployments 36 | - daemonsets 37 | - replicasets 38 | - ingresses 39 | verbs: 40 | - "*" 41 | - apiGroups: 42 | - apps 43 | resources: 44 | - deployments 45 | - daemonsets 46 | - replicasets 47 | - statefulsets 48 | verbs: 49 | - "*" 50 | - apiGroups: 51 | - chaosblade.io 52 | resources: 53 | - chaosblades 54 | - chaosblades/status 55 | verbs: 56 | - "*" 57 | - apiGroups: 58 | - admissionregistration.k8s.io 59 | resources: 60 | - mutatingwebhookconfigurations 61 | - validatingwebhookconfigurations 62 | verbs: ["get","create","update","list","watch","patch"] 63 | - apiGroups: 64 | - certificates.k8s.io 65 | resources: 66 | - certificatesigningrequests 67 | - certificatesigningrequests/approval 68 | verbs: ["get","create","update","list","watch","patch"] 69 | 70 | 71 | --- 72 | apiVersion: rbac.authorization.k8s.io/v1 73 | kind: ClusterRoleBinding 74 | metadata: 75 | name: chaosblade 76 | labels: 77 | name: chaosblade 78 | roleRef: 79 | kind: ClusterRole 80 | name: chaosblade 81 | apiGroup: rbac.authorization.k8s.io 82 | subjects: 83 | - kind: ServiceAccount 84 | name: chaosblade 85 | namespace: kube-system 86 | -------------------------------------------------------------------------------- /deploy/oss/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: chaosblade-webhook-server 5 | namespace: kube-system 6 | spec: 7 | ports: 8 | - port: 443 9 | targetPort: 9443 10 | selector: 11 | name: chaosblade-operator 12 | -------------------------------------------------------------------------------- /deploy/oss/webhook-cert-job.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: webhook-certs-job 5 | namespace: kube-system 6 | labels: 7 | app.kubernetes.io/component: webhook-certs-job 8 | spec: 9 | template: 10 | metadata: 11 | name: webhook-certs-job 12 | spec: 13 | restartPolicy: Never 14 | serviceAccountName: chaosblade 15 | containers: 16 | - name: webhook-job-certs 17 | image: bitnami/kubectl:latest 18 | imagePullPolicy: IfNotPresent 19 | command: 20 | - "sh" 21 | - "-c" 22 | - | 23 | set -e 24 | set -x 25 | K8S_SERVICE=chaosblade-webhook-server 26 | K8S_SECRET=chaosblade-webhook-server-cert 27 | K8S_NAMESPACE=kube-system 28 | 29 | # test if secret already exists 30 | certs=$(kubectl get secret ${K8S_SECRET} --ignore-not-found -n ${K8S_NAMESPACE} -o name) 31 | if [ "${certs}" = "secret/${K8S_SECRET}" ];then 32 | echo "Secret already exists" 33 | exit 0 34 | fi 35 | 36 | csrName=${K8S_SERVICE}.${K8S_NAMESPACE} 37 | tmpdir=$(mktemp -d) 38 | echo "Creating certs in tmpdir ${tmpdir} " 39 | 40 | cat <> ${tmpdir}/csr.conf 41 | [req] 42 | req_extensions = v3_req 43 | distinguished_name = req_distinguished_name 44 | [req_distinguished_name] 45 | [ v3_req ] 46 | basicConstraints = CA:FALSE 47 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 48 | extendedKeyUsage = serverAuth 49 | subjectAltName = @alt_names 50 | [alt_names] 51 | DNS.1 = ${K8S_SERVICE} 52 | DNS.2 = ${K8S_SERVICE}.${K8S_NAMESPACE} 53 | DNS.3 = ${K8S_SERVICE}.${K8S_NAMESPACE}.svc 54 | EOF 55 | 56 | openssl genrsa -out ${tmpdir}/server-key.pem 2048 57 | openssl req -new -key ${tmpdir}/server-key.pem -subj "/CN=${K8S_SERVICE}.${K8S_NAMESPACE}.svc" -out ${tmpdir}/server.csr -config ${tmpdir}/csr.conf 58 | 59 | # clean-up any previously created CSR for our service. Ignore errors if not present. 60 | kubectl delete csr ${csrName} 2>/dev/null || true 61 | 62 | # create server cert/key CSR and send to k8s API 63 | cat <&2 100 | exit 1 101 | fi 102 | 103 | echo ${serverCert} | openssl base64 -d -A -out ${tmpdir}/server-cert.pem 104 | 105 | # create the secret with CA cert and server cert/key 106 | kubectl create secret generic ${K8S_SECRET} \ 107 | --from-file=tls.key=${tmpdir}/server-key.pem \ 108 | --from-file=tls.crt=${tmpdir}/server-cert.pem \ 109 | --dry-run -o yaml | 110 | kubectl -n ${K8S_NAMESPACE} apply -f - 111 | 112 | -------------------------------------------------------------------------------- /examples/delay_pod_network_by_names.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: chaosblade.io/v1alpha1 2 | kind: ChaosBlade 3 | metadata: 4 | name: delay-pod-network-by-names 5 | spec: 6 | experiments: 7 | - scope: pod 8 | target: network 9 | action: delay 10 | desc: "delay pod network by names" 11 | matchers: 12 | - name: names 13 | value: 14 | - "redis-slave-674d68586-jnf7f" 15 | - name: namespace 16 | value: 17 | - "default" 18 | - name: local-port 19 | value: ["6379"] 20 | - name: interface 21 | value: ["eth0"] 22 | - name: time 23 | value: ["3000"] 24 | - name: offset 25 | value: ["1000"] 26 | -------------------------------------------------------------------------------- /examples/delete_pod_by_labels.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: chaosblade.io/v1alpha1 2 | kind: ChaosBlade 3 | metadata: 4 | name: delete-two-pod-by-labels 5 | spec: 6 | experiments: 7 | - scope: pod 8 | target: pod 9 | action: delete 10 | desc: "delete pod by labels" 11 | matchers: 12 | - name: labels 13 | value: 14 | - "app=guestbook" 15 | - name: namespace 16 | value: 17 | - "default" 18 | - name: evict-count 19 | value: 20 | - "2" -------------------------------------------------------------------------------- /examples/delete_pod_by_names.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: chaosblade.io/v1alpha1 2 | kind: ChaosBlade 3 | metadata: 4 | name: delete-pod-by-names 5 | spec: 6 | experiments: 7 | - scope: pod 8 | target: pod 9 | action: delete 10 | desc: "delete pod by names" 11 | matchers: 12 | - name: names 13 | value: 14 | - "redis-slave-674d68586-86r2t" 15 | - "frontend-d89756ff7-hmm62" 16 | - name: namespace 17 | value: 18 | - "default" 19 | -------------------------------------------------------------------------------- /examples/fail_pod_by_labels.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: chaosblade.io/v1alpha1 2 | kind: ChaosBlade 3 | metadata: 4 | name: delete-two-pod-by-labels 5 | spec: 6 | experiments: 7 | - scope: pod 8 | target: pod 9 | action: fail 10 | desc: "inject fail image to select pod" 11 | matchers: 12 | - name: labels 13 | value: 14 | - "app=guestbook" 15 | - name: namespace 16 | value: 17 | - "default" -------------------------------------------------------------------------------- /examples/increase_container_cpu_load_by_id.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: chaosblade.io/v1alpha1 2 | kind: ChaosBlade 3 | metadata: 4 | name: increase-container-cpu-load-by-id 5 | spec: 6 | experiments: 7 | - scope: container 8 | target: cpu 9 | action: fullload 10 | desc: "increase container cpu load by id" 11 | matchers: 12 | - name: container-ids 13 | value: 14 | - "2ff814b246f86" 15 | - name: cpu-percent 16 | value: ["100"] 17 | # pod names 18 | - name: names 19 | value: ["frontend-d89756ff7-pbnnc"] 20 | # or use pod labels 21 | -------------------------------------------------------------------------------- /examples/kill_container_process_by_id.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: chaosblade.io/v1alpha1 2 | kind: ChaosBlade 3 | metadata: 4 | name: kill-container-process-by-id 5 | spec: 6 | experiments: 7 | - scope: container 8 | target: process 9 | action: kill 10 | desc: "kill container process by id" 11 | matchers: 12 | - name: container-ids 13 | value: 14 | - "f1de335b4eeaf" 15 | - name: process 16 | value: ["top"] 17 | - name: names 18 | value: ["frontend-d89756ff7-tl4xl"] -------------------------------------------------------------------------------- /examples/node-cpu-load.yml: -------------------------------------------------------------------------------- 1 | apiVersion: chaosblade.io/v1alpha1 2 | kind: ChaosBlade 3 | metadata: 4 | name: node-cpu-load.yml 5 | spec: 6 | experiments: 7 | - scope: node 8 | target: cpu 9 | action: fullload 10 | desc: "increase node cpu load by names" 11 | matchers: 12 | - name: names 13 | value: 14 | - "node-example-01" 15 | - name: cpu-percent 16 | value: 17 | - "80" 18 | -------------------------------------------------------------------------------- /examples/node-disk-load-burn-read.yml: -------------------------------------------------------------------------------- 1 | apiVersion: chaosblade.io/v1alpha1 2 | kind: ChaosBlade 3 | metadata: 4 | name: node-disk-load-burn-read 5 | spec: 6 | experiments: 7 | - scope: node 8 | target: disk 9 | action : "burn" 10 | desc: "increase disk burn by names" 11 | matchers: 12 | - name: names 13 | value: 14 | - "node-example-01" 15 | - name: path 16 | value: 17 | - "/home" 18 | - name: size 19 | value: 20 | - "20" 21 | - name: timeout 22 | value: 23 | - "100" 24 | - name: read 25 | value: 26 | - "true" 27 | -------------------------------------------------------------------------------- /examples/node-disk-load-burn-write.yml: -------------------------------------------------------------------------------- 1 | apiVersion: chaosblade.io/v1alpha1 2 | kind: ChaosBlade 3 | metadata: 4 | name: node-disk-load-burn-write 5 | spec: 6 | experiments: 7 | - scope: node 8 | target: disk 9 | action : "burn" 10 | desc: "increase disk burn by names" 11 | matchers: 12 | - name: names 13 | value: 14 | - "node-example-01" 15 | - name: path 16 | value: 17 | - "/home" 18 | - name: size 19 | value: 20 | - "20" 21 | - name: timeout 22 | value: 23 | - "100" 24 | - name: write 25 | value: 26 | - "true" 27 | -------------------------------------------------------------------------------- /examples/node-disk-load-fill.yml: -------------------------------------------------------------------------------- 1 | apiVersion: chaosblade.io/v1alpha1 2 | kind: ChaosBlade 3 | metadata: 4 | name: node-disk-load-fill 5 | spec: 6 | experiments: 7 | - scope: node 8 | target: disk 9 | action : "fill" 10 | desc: "increase disk fill by names" 11 | matchers: 12 | - name: names 13 | value: 14 | - "node-example-01" 15 | - name: path 16 | value: 17 | - "/" 18 | - name: size 19 | value: 20 | - "2048" 21 | -------------------------------------------------------------------------------- /examples/node-mem-load.yml: -------------------------------------------------------------------------------- 1 | apiVersion: chaosblade.io/v1alpha1 2 | kind: ChaosBlade 3 | metadata: 4 | name: node-mem-load 5 | spec: 6 | experiments: 7 | - scope: node 8 | target: mem 9 | action : "load" 10 | desc: "increase node mem load by names" 11 | matchers: 12 | - name: names 13 | value: 14 | - "node-example-01" 15 | - name: mode 16 | value: 17 | - "ram" 18 | - name: mem-percent 19 | value: 20 | - "80" 21 | -------------------------------------------------------------------------------- /examples/node-network-delay-by-names.yml: -------------------------------------------------------------------------------- 1 | apiVersion: chaosblade.io/v1alpha1 2 | kind: ChaosBlade 3 | metadata: 4 | name: node-network-delay-by-names 5 | spec: 6 | experiments: 7 | - scope: node 8 | target: network 9 | action: delay 10 | desc: "delay pod network by names" 11 | matchers: 12 | - name: names 13 | value: ["node-example-01"] 14 | - name: interface 15 | value: ["eth0"] 16 | - name: time 17 | value: ["3000"] 18 | - name: offset 19 | value: ["1000"] 20 | -------------------------------------------------------------------------------- /examples/node-network-loss-by-names.yml: -------------------------------------------------------------------------------- 1 | apiVersion: chaosblade.io/v1alpha1 2 | kind: ChaosBlade 3 | metadata: 4 | name: node-network-loss-by-names 5 | spec: 6 | experiments: 7 | - scope: node 8 | target: network 9 | action: loss 10 | desc: "node network loss" 11 | matchers: 12 | - name: names 13 | value: ["node-example-01"] 14 | - name: percent 15 | value: ["1"] 16 | - name: interface 17 | value: ["eth0"] 18 | -------------------------------------------------------------------------------- /examples/pod-cpu-load-by-names.yml: -------------------------------------------------------------------------------- 1 | apiVersion: chaosblade.io/v1alpha1 2 | kind: ChaosBlade 3 | metadata: 4 | name: cpu-load 5 | spec: 6 | experiments: 7 | - scope: pod 8 | target: cpu 9 | action: fullload 10 | desc: "increase node cpu load by names" 11 | matchers: 12 | - name: names 13 | value: 14 | - "pod-example-01" 15 | - name: cpu-percent 16 | value: 17 | - "80" 18 | -------------------------------------------------------------------------------- /examples/pod-delete_by_names.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: chaosblade.io/v1alpha1 2 | kind: ChaosBlade 3 | metadata: 4 | name: delete-pod-by-names 5 | spec: 6 | experiments: 7 | - scope: pod 8 | target: pod 9 | action: delete 10 | desc: "delete pod by names" 11 | matchers: 12 | - name: names 13 | value: 14 | - "mypod" 15 | - name: namespace 16 | value: 17 | - "default" 18 | -------------------------------------------------------------------------------- /examples/remove_container_by_id.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: chaosblade.io/v1alpha1 2 | kind: ChaosBlade 3 | metadata: 4 | name: remove-container-by-id 5 | spec: 6 | experiments: 7 | - scope: container 8 | target: container 9 | action: remove 10 | desc: "remove container by id" 11 | matchers: 12 | - name: container-ids 13 | value: ["072aa6bbf2e2e2"] 14 | # pod name 15 | - name: names 16 | value: ["frontend-d89756ff7-szblb"] 17 | - name: namespace 18 | value: ["default"] 19 | -------------------------------------------------------------------------------- /examples/tamper_container_dns_by_id.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: chaosblade.io/v1alpha1 2 | kind: ChaosBlade 3 | metadata: 4 | name: tamper-container-dns-by-id 5 | spec: 6 | experiments: 7 | - scope: container 8 | target: network 9 | action: dns 10 | desc: "tamper container dns by id" 11 | matchers: 12 | - name: container-ids 13 | value: 14 | - "4b25f66580c4" 15 | - name: domain 16 | value: ["www.baidu.com"] 17 | - name: ip 18 | value: ["10.0.0.1"] 19 | # pod names 20 | - name: names 21 | value: ["frontend-d89756ff7-trsxf"] 22 | # or use pod labels 23 | -------------------------------------------------------------------------------- /exec/container/application.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package container 18 | 19 | import ( 20 | "fmt" 21 | "path" 22 | 23 | "github.com/chaosblade-io/chaosblade-spec-go/spec" 24 | "github.com/chaosblade-io/chaosblade-spec-go/util" 25 | "github.com/sirupsen/logrus" 26 | 27 | "github.com/chaosblade-io/chaosblade-operator/pkg/runtime/chaosblade" 28 | "github.com/chaosblade-io/chaosblade-operator/version" 29 | ) 30 | 31 | var JvmSpecFileForYaml = "" 32 | 33 | // getJvmModels returns java experiment specs 34 | func getJvmModels() []spec.ExpModelCommandSpec { 35 | var jvmSpecFile = path.Join(chaosblade.OperatorChaosBladeYaml, fmt.Sprintf("chaosblade-jvm-spec-%s.yaml", version.Version)) 36 | if JvmSpecFileForYaml != "" { 37 | jvmSpecFile = JvmSpecFileForYaml 38 | } 39 | modelCommandSpecs := make([]spec.ExpModelCommandSpec, 0) 40 | models, err := util.ParseSpecsToModel(jvmSpecFile, nil) 41 | if err != nil { 42 | logrus.Warningf("parse java spec failed, so skip it, %s", err) 43 | return modelCommandSpecs 44 | } 45 | for idx := range models.Models { 46 | modelCommandSpecs = append(modelCommandSpecs, &models.Models[idx]) 47 | } 48 | return modelCommandSpecs 49 | } 50 | -------------------------------------------------------------------------------- /exec/controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package exec 18 | 19 | import ( 20 | "context" 21 | "sync" 22 | 23 | "github.com/sirupsen/logrus" 24 | 25 | "github.com/chaosblade-io/chaosblade-spec-go/spec" 26 | 27 | "github.com/chaosblade-io/chaosblade-operator/channel" 28 | "github.com/chaosblade-io/chaosblade-operator/exec/container" 29 | "github.com/chaosblade-io/chaosblade-operator/exec/model" 30 | "github.com/chaosblade-io/chaosblade-operator/exec/node" 31 | "github.com/chaosblade-io/chaosblade-operator/exec/pod" 32 | "github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1" 33 | ) 34 | 35 | // ResourceDispatchedController contains all resource controllers exclude node resource 36 | type ResourceDispatchedController struct { 37 | Controllers map[string]model.ExperimentController 38 | } 39 | 40 | var executor *ResourceDispatchedController 41 | var once sync.Once 42 | 43 | // NewDispatcherExecutor initialized when operator starting 44 | func NewDispatcherExecutor(client *channel.Client) *ResourceDispatchedController { 45 | once.Do(func() { 46 | executor = &ResourceDispatchedController{ 47 | Controllers: make(map[string]model.ExperimentController, 0), 48 | } 49 | executor.register( 50 | node.NewExpController(client), 51 | pod.NewExpController(client), 52 | container.NewExpController(client), 53 | ) 54 | }) 55 | return executor 56 | } 57 | 58 | func (e *ResourceDispatchedController) Name() string { 59 | return "dispatch" 60 | } 61 | 62 | func (e *ResourceDispatchedController) Create(bladeName string, expSpec v1alpha1.ExperimentSpec) v1alpha1.ExperimentStatus { 63 | logrus.WithField("experiment", bladeName).Infof("start to create experiment") 64 | controller := e.Controllers[expSpec.Scope] 65 | if controller == nil { 66 | logrus.WithField("experiment", bladeName).WithField("scope", expSpec.Scope).Errorf("controller not found") 67 | return v1alpha1.ExperimentStatus{ 68 | State: "Error", 69 | Error: "can not find the scope controller for creating", 70 | } 71 | } 72 | ctx := model.SetExperimentIdToContext(context.Background(), bladeName) 73 | response := controller.Create(ctx, expSpec) 74 | experimentStatus := createExperimentStatusByResponse(response) 75 | experimentStatus.Scope = expSpec.Scope 76 | experimentStatus.Target = expSpec.Target 77 | experimentStatus.Action = expSpec.Action 78 | return experimentStatus 79 | } 80 | 81 | func (e *ResourceDispatchedController) Destroy(bladeName string, expSpec v1alpha1.ExperimentSpec, oldExpStatus v1alpha1.ExperimentStatus) v1alpha1.ExperimentStatus { 82 | controller := e.Controllers[expSpec.Scope] 83 | if controller == nil { 84 | return v1alpha1.ExperimentStatus{ 85 | State: "Error", 86 | Error: "can not find the scope controller for destroying", 87 | } 88 | } 89 | if oldExpStatus.ResStatuses == nil || 90 | len(oldExpStatus.ResStatuses) == 0 { 91 | return model.CreateDestroyedStatus(oldExpStatus) 92 | } 93 | ctx := spec.SetDestroyFlag(context.Background(), bladeName) 94 | ctx = model.SetExperimentIdToContext(ctx, bladeName) 95 | response := controller.Destroy(ctx, expSpec, oldExpStatus) 96 | newExpStatus := createExperimentStatusByResponse(response) 97 | newExpStatus = validateAndSetNecessaryFields(newExpStatus, oldExpStatus) 98 | return newExpStatus 99 | } 100 | 101 | // validateAndSetNecessaryFields to resolve status overwriting when the experiment is destroyed. 102 | func validateAndSetNecessaryFields(status v1alpha1.ExperimentStatus, oldExpStatus v1alpha1.ExperimentStatus) v1alpha1.ExperimentStatus { 103 | status.Scope = oldExpStatus.Scope 104 | status.Target = oldExpStatus.Target 105 | status.Action = oldExpStatus.Action 106 | if status.State == "Error" { 107 | status.State = oldExpStatus.State 108 | } 109 | if status.ResStatuses == nil { 110 | return status 111 | } 112 | for _, s := range status.ResStatuses { 113 | for _, os := range oldExpStatus.ResStatuses { 114 | if s.Id != os.Id { 115 | continue 116 | } 117 | if s.State == "Error" { 118 | s.State = os.State 119 | } 120 | } 121 | } 122 | return status 123 | } 124 | 125 | // createExperimentStatusByResponse wraps experiment statuses 126 | func createExperimentStatusByResponse(response *spec.Response) v1alpha1.ExperimentStatus { 127 | experimentStatus := v1alpha1.ExperimentStatus{} 128 | if response.Result != nil { 129 | experimentStatus = response.Result.(v1alpha1.ExperimentStatus) 130 | } else { 131 | if response.Success { 132 | experimentStatus = v1alpha1.CreateSuccessExperimentStatus([]v1alpha1.ResourceStatus{}) 133 | } else { 134 | experimentStatus = v1alpha1.CreateFailExperimentStatus(response.Err, []v1alpha1.ResourceStatus{}) 135 | } 136 | } 137 | return experimentStatus 138 | } 139 | 140 | func (e *ResourceDispatchedController) register(cs ...model.ExperimentController) { 141 | for _, c := range cs { 142 | e.Controllers[c.Name()] = c 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /exec/model/category.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package model 18 | 19 | const CategorySystemContainer = "system_container" 20 | -------------------------------------------------------------------------------- /exec/model/context.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package model 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "strings" 23 | 24 | "github.com/sirupsen/logrus" 25 | ) 26 | 27 | const ContainerObjectMetaListKey = "ContainerObjectMetaListKey" 28 | const ExperimentIdKey = "ExperimentIdKey" 29 | 30 | type ContainerObjectMeta struct { 31 | // experiment id 32 | Id string 33 | ContainerRuntime string 34 | ContainerId string 35 | ContainerName string 36 | PodName string 37 | NodeName string 38 | Namespace string 39 | } 40 | 41 | type ContainerMatchedList []ContainerObjectMeta 42 | 43 | // GetExperimentIdFromContext 44 | func GetExperimentIdFromContext(ctx context.Context) string { 45 | experimentId := ctx.Value(ExperimentIdKey) 46 | if experimentId == nil { 47 | return "UnknownId" 48 | } 49 | return experimentId.(string) 50 | } 51 | 52 | // SetExperimentIdToContext 53 | func SetExperimentIdToContext(ctx context.Context, experimentId string) context.Context { 54 | return context.WithValue(ctx, ExperimentIdKey, experimentId) 55 | } 56 | 57 | // GetContainerObjectMetaListFromContext returns the matched container list 58 | func GetContainerObjectMetaListFromContext(ctx context.Context) (ContainerMatchedList, error) { 59 | containerObjectMetaListValue := ctx.Value(ContainerObjectMetaListKey) 60 | if containerObjectMetaListValue == nil { 61 | return nil, fmt.Errorf("less container object meta in context") 62 | } 63 | containerObjectMetaList := containerObjectMetaListValue.(ContainerMatchedList) 64 | return containerObjectMetaList, nil 65 | } 66 | 67 | // SetContainerObjectMetaListToContext 68 | func SetContainerObjectMetaListToContext(ctx context.Context, containerMatchedList ContainerMatchedList) context.Context { 69 | logrus.WithField("experiment", GetExperimentIdFromContext(ctx)).Infof("set container list: %+v", containerMatchedList) 70 | return context.WithValue(ctx, ContainerObjectMetaListKey, containerMatchedList) 71 | } 72 | 73 | func (c *ContainerObjectMeta) GetIdentifier() string { 74 | identifier := fmt.Sprintf("%s/%s/%s", c.Namespace, c.NodeName, c.PodName) 75 | if c.ContainerName != "" { 76 | identifier = fmt.Sprintf("%s/%s", identifier, c.ContainerName) 77 | } 78 | if c.ContainerId != "" { 79 | identifier = fmt.Sprintf("%s/%s", identifier, c.ContainerId) 80 | } 81 | if c.ContainerRuntime != "" { 82 | identifier = fmt.Sprintf("%s/%s", identifier, c.ContainerRuntime) 83 | } 84 | return identifier 85 | } 86 | 87 | // Namespace/Node/Pod/ContainerName/ContainerId/containerRuntime 88 | func ParseIdentifier(identifier string) ContainerObjectMeta { 89 | ss := strings.SplitN(identifier, "/", 6) 90 | meta := ContainerObjectMeta{} 91 | switch len(ss) { 92 | case 0: 93 | return meta 94 | case 1: 95 | meta.Namespace = ss[0] 96 | case 2: 97 | meta.Namespace = ss[0] 98 | meta.NodeName = ss[1] 99 | case 3: 100 | meta.Namespace = ss[0] 101 | meta.NodeName = ss[1] 102 | meta.PodName = ss[2] 103 | case 4: 104 | meta.Namespace = ss[0] 105 | meta.NodeName = ss[1] 106 | meta.PodName = ss[2] 107 | meta.ContainerName = ss[3] 108 | case 5: 109 | meta.Namespace = ss[0] 110 | meta.NodeName = ss[1] 111 | meta.PodName = ss[2] 112 | meta.ContainerName = ss[3] 113 | meta.ContainerId = ss[4] 114 | case 6: 115 | meta.Namespace = ss[0] 116 | meta.NodeName = ss[1] 117 | meta.PodName = ss[2] 118 | meta.ContainerName = ss[3] 119 | meta.ContainerId = ss[4] 120 | meta.ContainerRuntime = ss[5] 121 | } 122 | return meta 123 | } 124 | -------------------------------------------------------------------------------- /exec/model/controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package model 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "math" 23 | "strconv" 24 | "strings" 25 | 26 | "github.com/sirupsen/logrus" 27 | 28 | "github.com/chaosblade-io/chaosblade-spec-go/spec" 29 | 30 | "github.com/chaosblade-io/chaosblade-operator/channel" 31 | "github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1" 32 | ) 33 | 34 | type ExpController interface { 35 | // controller Name 36 | Name() string 37 | // Create 38 | Create(bladeName string, expSpec v1alpha1.ExperimentSpec) v1alpha1.ExperimentStatus 39 | // Destroy 40 | Destroy(bladeName string, expSpec v1alpha1.ExperimentSpec, oldExpStatus v1alpha1.ExperimentStatus) v1alpha1.ExperimentStatus 41 | } 42 | 43 | type ExperimentController interface { 44 | // controller Name 45 | Name() string 46 | // Create experiment 47 | Create(ctx context.Context, expSpec v1alpha1.ExperimentSpec) *spec.Response 48 | // Destroy 49 | Destroy(ctx context.Context, expSpec v1alpha1.ExperimentSpec, oldExpStatus v1alpha1.ExperimentStatus) *spec.Response 50 | } 51 | 52 | type BaseExperimentController struct { 53 | Client *channel.Client 54 | ResourceModelSpec ResourceExpModelSpec 55 | } 56 | 57 | func (b *BaseExperimentController) Destroy(ctx context.Context, expSpec v1alpha1.ExperimentSpec) *spec.Response { 58 | expModel := ExtractExpModelFromExperimentSpec(expSpec) 59 | return b.Exec(ctx, expModel) 60 | } 61 | 62 | // Exec gets action executor and execute experiments 63 | func (b *BaseExperimentController) Exec(ctx context.Context, expModel *spec.ExpModel) *spec.Response { 64 | experimentId := GetExperimentIdFromContext(ctx) 65 | logrusField := logrus.WithField("experiment", experimentId) 66 | logrusField.Infof("start to execute: %+v", expModel) 67 | // get action spec 68 | actionSpec := b.ResourceModelSpec.GetExpActionModelSpec(expModel.Target, expModel.ActionName) 69 | if actionSpec == nil { 70 | errMsg := "can not find the action handler" 71 | logrusField.WithFields(logrus.Fields{ 72 | "target": expModel.Target, 73 | "action": expModel.ActionName, 74 | }).Errorf(errMsg) 75 | handler := fmt.Sprintf("%s.%s", expModel.Target, expModel.ActionName) 76 | errMsg = spec.HandlerExecNotFound.Sprintf(handler) 77 | return spec.ResponseFailWithResult(spec.HandlerExecNotFound, 78 | v1alpha1.CreateFailExperimentStatus(errMsg, []v1alpha1.ResourceStatus{}), handler) 79 | } 80 | expModel.ActionPrograms = actionSpec.Programs() 81 | // invoke action executor 82 | response := actionSpec.Executor().Exec(experimentId, ctx, expModel) 83 | return response 84 | } 85 | 86 | // ExtractExpModelFromExperimentSpec convert ExperimentSpec to ExpModel 87 | func ExtractExpModelFromExperimentSpec(experimentSpec v1alpha1.ExperimentSpec) *spec.ExpModel { 88 | expModel := &spec.ExpModel{ 89 | Target: experimentSpec.Target, 90 | Scope: experimentSpec.Scope, 91 | ActionName: experimentSpec.Action, 92 | ActionFlags: make(map[string]string, 0), 93 | } 94 | if experimentSpec.Matchers != nil { 95 | for _, flag := range experimentSpec.Matchers { 96 | expModel.ActionFlags[flag.Name] = strings.Join(flag.Value, ",") 97 | } 98 | } 99 | return expModel 100 | } 101 | 102 | func GetResourceCount(resourceCount int, flags map[string]string) (int, *spec.Response) { 103 | if resourceCount == 0 { 104 | return 0, spec.Success() 105 | } 106 | count := math.MaxInt32 107 | percent := 100 108 | var err error 109 | countValue := flags[ResourceCountFlag.Name] 110 | if countValue != "" { 111 | count, err = strconv.Atoi(countValue) 112 | if err != nil { 113 | return 0, spec.ResponseFailWithFlags(spec.ParameterIllegal, ResourceCountFlag.Name, countValue, err) 114 | } 115 | if count == 0 { 116 | return 0, spec.ResponseFailWithFlags(spec.ParameterIllegal, ResourceCountFlag.Name, countValue, 117 | "it must be a positive integer") 118 | } 119 | } 120 | percentValue := flags[ResourcePercentFlag.Name] 121 | if percentValue != "" { 122 | percent, err = strconv.Atoi(percentValue) 123 | if err != nil { 124 | return 0, spec.ResponseFailWithFlags(spec.ParameterIllegal, ResourcePercentFlag.Name, percentValue, err) 125 | } 126 | if percent == 0 { 127 | return 0, spec.ResponseFailWithFlags(spec.ParameterIllegal, ResourcePercentFlag.Name, percentValue, 128 | "it must be a positive integer") 129 | } 130 | } 131 | percentCount := int(math.Round(float64(percent) / 100.0 * float64(resourceCount))) 132 | if count > percentCount { 133 | count = percentCount 134 | } 135 | if count > resourceCount { 136 | return resourceCount, spec.Success() 137 | } 138 | return count, spec.Success() 139 | } 140 | 141 | // CreateDestroyedStatus returns the ExperimentStatus with destroyed state 142 | func CreateDestroyedStatus(oldExpStatus v1alpha1.ExperimentStatus) v1alpha1.ExperimentStatus { 143 | statuses := make([]v1alpha1.ResourceStatus, 0) 144 | if oldExpStatus.ResStatuses != nil { 145 | for _, status := range oldExpStatus.ResStatuses { 146 | statuses = append(statuses, v1alpha1.ResourceStatus{ 147 | // experiment uid in chaosblade 148 | Id: status.Id, 149 | // experiment state 150 | State: v1alpha1.DestroyedState, 151 | Success: true, 152 | // resource name 153 | Kind: status.Kind, 154 | Identifier: status.Identifier, 155 | }) 156 | } 157 | } 158 | return v1alpha1.CreateDestroyedExperimentStatus(statuses) 159 | } 160 | -------------------------------------------------------------------------------- /exec/model/controller_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package model 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | func TestGetResourceCount(t *testing.T) { 24 | type args struct { 25 | resourceCount int 26 | flags map[string]string 27 | } 28 | tests := []struct { 29 | name string 30 | args args 31 | want int 32 | wantErr bool 33 | }{ 34 | {name: "evict-percent=0", args: args{10, map[string]string{"evict-count": "", "evict-percent": "0"}}, want: 0, wantErr: false}, 35 | {name: "evict-percent=10", args: args{10, map[string]string{"evict-count": "", "evict-percent": "10"}}, want: 1, wantErr: false}, 36 | {name: "evict-percent=55", args: args{10, map[string]string{"evict-count": "", "evict-percent": "55"}}, want: 6, wantErr: false}, 37 | {name: "evict-percent=100", args: args{10, map[string]string{"evict-count": "", "evict-percent": "100"}}, want: 10, wantErr: false}, 38 | {name: "evict-count=5,evict-percent==10", args: args{10, map[string]string{"evict-count": "5", "evict-percent": "10"}}, want: 1, wantErr: false}, 39 | {name: "evict-count=20", args: args{10, map[string]string{"evict-count": "20", "evict-percent": ""}}, want: 10, wantErr: false}, 40 | } 41 | for _, tt := range tests { 42 | t.Run(tt.name, func(t *testing.T) { 43 | got, err := GetResourceCount(tt.args.resourceCount, tt.args.flags) 44 | if (err != nil) != tt.wantErr { 45 | t.Errorf("GetResourceCount() error = %v, wantErr %v", err, tt.wantErr) 46 | return 47 | } 48 | if got != tt.want { 49 | t.Errorf("GetResourceCount() got = %v, want %v", got, tt.want) 50 | } 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /exec/model/copy.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 The Kubernetes Authors. 3 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package model 19 | 20 | import ( 21 | "archive/tar" 22 | "errors" 23 | "fmt" 24 | "io" 25 | "io/ioutil" 26 | "os" 27 | "path" 28 | "path/filepath" 29 | "strings" 30 | 31 | "github.com/chaosblade-io/chaosblade-spec-go/spec" 32 | "github.com/chaosblade-io/chaosblade-spec-go/util" 33 | "github.com/sirupsen/logrus" 34 | 35 | "github.com/chaosblade-io/chaosblade-operator/channel" 36 | ) 37 | 38 | type CopyOptions struct { 39 | DeployOptions 40 | } 41 | 42 | func makeTar(srcPath, destPath string, writer io.Writer) error { 43 | tarWriter := tar.NewWriter(writer) 44 | defer tarWriter.Close() 45 | 46 | srcPath = path.Clean(srcPath) 47 | destPath = path.Clean(destPath) 48 | err := recursiveTar(path.Dir(srcPath), path.Base(srcPath), path.Dir(destPath), path.Base(destPath), tarWriter) 49 | return err 50 | } 51 | 52 | func recursiveTar(srcBase, srcFile, destBase, destFile string, tw *tar.Writer) error { 53 | logrus.WithFields(logrus.Fields{ 54 | "srcBase": srcBase, 55 | "srcFile": srcFile, 56 | "destBase": destBase, 57 | "destFile": destFile, 58 | }).Debugln("recursiveTar") 59 | srcPath := path.Join(srcBase, srcFile) 60 | matchedPaths, err := filepath.Glob(srcPath) 61 | if err != nil { 62 | return err 63 | } 64 | for _, fpath := range matchedPaths { 65 | stat, err := os.Lstat(fpath) 66 | if err != nil { 67 | return err 68 | } 69 | if stat.IsDir() { 70 | files, err := ioutil.ReadDir(fpath) 71 | if err != nil { 72 | return err 73 | } 74 | if len(files) == 0 { 75 | //case empty directory 76 | hdr, _ := tar.FileInfoHeader(stat, fpath) 77 | hdr.Name = destFile 78 | if err := tw.WriteHeader(hdr); err != nil { 79 | return err 80 | } 81 | } 82 | for _, f := range files { 83 | if err := recursiveTar(srcBase, path.Join(srcFile, f.Name()), destBase, path.Join(destFile, f.Name()), tw); err != nil { 84 | return err 85 | } 86 | } 87 | return nil 88 | } else if stat.Mode()&os.ModeSymlink != 0 { 89 | //case soft link 90 | hdr, _ := tar.FileInfoHeader(stat, fpath) 91 | target, err := os.Readlink(fpath) 92 | if err != nil { 93 | return err 94 | } 95 | 96 | hdr.Linkname = target 97 | hdr.Name = destFile 98 | if err := tw.WriteHeader(hdr); err != nil { 99 | return err 100 | } 101 | } else { 102 | //case regular file or other file type like pipe 103 | hdr, err := tar.FileInfoHeader(stat, fpath) 104 | if err != nil { 105 | return err 106 | } 107 | hdr.Name = destFile 108 | 109 | if err := tw.WriteHeader(hdr); err != nil { 110 | return err 111 | } 112 | 113 | f, err := os.Open(fpath) 114 | if err != nil { 115 | return err 116 | } 117 | defer f.Close() 118 | 119 | if _, err := io.Copy(tw, f); err != nil { 120 | return err 121 | } 122 | return f.Close() 123 | } 124 | } 125 | return nil 126 | } 127 | 128 | func (o *CopyOptions) execute(options *channel.ExecOptions) error { 129 | if len(options.PodNamespace) == 0 { 130 | options.PodNamespace = o.Namespace 131 | } 132 | 133 | if len(o.Container) > 0 { 134 | options.ContainerName = o.Container 135 | } 136 | if err := o.client.Exec(options); err != nil { 137 | return err.(error) 138 | } 139 | return nil 140 | } 141 | 142 | // DeployToPod copies src file or directory to specify container 143 | func (o *CopyOptions) DeployToPod(experimentId, src, dest string) error { 144 | if len(src) == 0 || len(dest) == 0 { 145 | return errors.New("filepath can not be empty") 146 | } 147 | reader, writer := io.Pipe() 148 | 149 | // strip trailing slash (if any) 150 | if dest != "/" && strings.HasSuffix(string(dest[len(dest)-1]), "/") { 151 | dest = dest[:len(dest)-1] 152 | } 153 | 154 | go func() error { 155 | defer writer.Close() 156 | err := makeTar(src, dest, writer) 157 | if err != nil { 158 | util.Errorf(experimentId, util.GetRunFuncName(), spec.K8sExecFailed.Sprintf("tar", err.Error())) 159 | return spec.ResponseFailWithFlags(spec.K8sExecFailed, "tar", err) 160 | } 161 | return nil 162 | }() 163 | cmdArr := []string{"tar", "--no-same-permissions", "--no-same-owner", "-xmf", "-"} 164 | destDir := path.Dir(dest) 165 | if len(destDir) > 0 { 166 | cmdArr = append(cmdArr, "-C", destDir) 167 | } 168 | options := &channel.ExecOptions{ 169 | StreamOptions: channel.StreamOptions{ 170 | IOStreams: channel.IOStreams{ 171 | In: reader, 172 | }, 173 | Stdin: true, 174 | ErrDecoder: func(bytes []byte) interface{} { 175 | return fmt.Errorf(string(bytes)) 176 | }, 177 | OutDecoder: func(bytes []byte) interface{} { 178 | return nil 179 | }, 180 | }, 181 | PodName: o.PodName, 182 | PodNamespace: o.Namespace, 183 | ContainerName: o.Container, 184 | Command: cmdArr, 185 | IgnoreOutput: true, 186 | } 187 | return o.execute(options) 188 | } 189 | -------------------------------------------------------------------------------- /exec/model/deploy.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/chaosblade-io/chaosblade-operator/channel" 7 | ) 8 | 9 | type DeployMode interface { 10 | DeployToPod(experimentId, src, dest string) error 11 | } 12 | 13 | type DeployOptions struct { 14 | Container string 15 | Namespace string 16 | PodName string 17 | client *channel.Client 18 | } 19 | 20 | // CheckFileExists return nil if dest file exists 21 | func (o *DeployOptions) CheckFileExists(dest string) error { 22 | options := &channel.ExecOptions{ 23 | StreamOptions: channel.StreamOptions{ 24 | ErrDecoder: func(bytes []byte) interface{} { 25 | return fmt.Errorf(string(bytes)) 26 | }, 27 | OutDecoder: func(bytes []byte) interface{} { 28 | return nil 29 | }, 30 | }, 31 | PodNamespace: o.Namespace, 32 | PodName: o.PodName, 33 | ContainerName: o.Container, 34 | Command: []string{"test", "-e", dest}, 35 | IgnoreOutput: true, 36 | } 37 | if err := o.client.Exec(options); err != nil { 38 | return err.(error) 39 | } 40 | return nil 41 | } 42 | 43 | func (o *DeployOptions) CreateDir(dir string) error { 44 | if len(dir) == 0 { 45 | return fmt.Errorf("illegal directory name") 46 | } 47 | options := &channel.ExecOptions{ 48 | StreamOptions: channel.StreamOptions{ 49 | ErrDecoder: func(bytes []byte) interface{} { 50 | return fmt.Errorf(string(bytes)) 51 | }, 52 | OutDecoder: func(bytes []byte) interface{} { 53 | return nil 54 | }, 55 | }, 56 | PodName: o.PodName, 57 | PodNamespace: o.Namespace, 58 | ContainerName: o.Container, 59 | Command: []string{"mkdir", "-p", dir}, 60 | IgnoreOutput: true, 61 | } 62 | if err := o.client.Exec(options); err != nil { 63 | return err.(error) 64 | } 65 | return nil 66 | } 67 | -------------------------------------------------------------------------------- /exec/model/download.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "path" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/sirupsen/logrus" 11 | 12 | "github.com/chaosblade-io/chaosblade-operator/channel" 13 | "github.com/chaosblade-io/chaosblade-operator/pkg/runtime/chaosblade" 14 | ) 15 | 16 | /* 17 | . 18 | ├── bin 19 | ├── blade 20 | ├── lib.tar.gz 21 | └── yaml.tar.gz 22 | */ 23 | 24 | const bin = "bin" 25 | const blade = "blade" 26 | const lib = "lib.tar.gz" 27 | const yaml = "yaml.tar.gz" 28 | 29 | type DownloadOptions struct { 30 | DeployOptions 31 | url string 32 | } 33 | 34 | func (d *DownloadOptions) DeployToPod(experimentId, src, dest string) error { 35 | if len(src) == 0 { 36 | return errors.New("the chaosblade downloaded address is empty") 37 | } 38 | url := d.getUrl(src) 39 | // code=$( curl -s -L -w %{http_code} -o /opt/yaml.tar.gz https://xxx/temp/yaml.tar.gz ) && [ $code = 200 ] && tar -zxf /opt/yaml.tar.gz -C /opt && echo $code || echo $code 40 | var command []string 41 | isTarFile := strings.HasSuffix(url, "tar.gz") 42 | if isTarFile { 43 | dest = fmt.Sprintf("%s.%s", dest, "tar.gz") 44 | } 45 | command = []string{"sh", "-c", "curl -s -L -w %{http_code} " + fmt.Sprintf("-o %s %s && chmod 755 %s", dest, url, dest)} 46 | options := &channel.ExecOptions{ 47 | StreamOptions: channel.StreamOptions{ 48 | ErrDecoder: func(bytes []byte) interface{} { 49 | return string(bytes) 50 | }, 51 | OutDecoder: func(bytes []byte) interface{} { 52 | return string(bytes) 53 | }, 54 | }, 55 | PodNamespace: d.Namespace, 56 | PodName: d.PodName, 57 | ContainerName: d.Container, 58 | Command: command, 59 | IgnoreOutput: false, 60 | } 61 | statusCode := d.client.Exec(options).(string) 62 | logrus.WithFields( 63 | logrus.Fields{ 64 | "experimentId": experimentId, 65 | "pod": d.PodName, 66 | "container": d.Container, 67 | "command": command, 68 | "result": statusCode, 69 | }).Infof("download to the target container") 70 | code, err := strconv.Atoi(strings.TrimSpace(statusCode)) 71 | if err != nil { 72 | return errors.New(statusCode) 73 | } 74 | if code != 200 { 75 | return fmt.Errorf("response code is %d", code) 76 | } 77 | if isTarFile { 78 | return d.uncompress(experimentId, dest) 79 | } 80 | return nil 81 | } 82 | 83 | func (d *DownloadOptions) uncompress(experimentId, file string) error { 84 | dir := path.Dir(file) 85 | command := []string{"/bin/sh", "-c", fmt.Sprintf("tar -zxf %s -C %s && chmod -R 755 %s", file, dir, dir)} 86 | options := &channel.ExecOptions{ 87 | StreamOptions: channel.StreamOptions{ 88 | ErrDecoder: func(bytes []byte) interface{} { 89 | return string(bytes) 90 | }, 91 | OutDecoder: func(bytes []byte) interface{} { 92 | return string(bytes) 93 | }, 94 | }, 95 | PodNamespace: d.Namespace, 96 | PodName: d.PodName, 97 | ContainerName: d.Container, 98 | Command: command, 99 | IgnoreOutput: true, 100 | } 101 | error := d.client.Exec(options) 102 | logrus.WithFields( 103 | logrus.Fields{ 104 | "experimentId": experimentId, 105 | "pod": d.PodName, 106 | "container": d.Container, 107 | "command": command, 108 | "result": error, 109 | }).Infof("uncompress in the target container") 110 | if error == nil { 111 | return nil 112 | } 113 | return errors.New(error.(string)) 114 | } 115 | 116 | func (d *DownloadOptions) getUrl(srcFile string) string { 117 | var obj = srcFile 118 | switch srcFile { 119 | case chaosblade.OperatorChaosBladeBlade: 120 | obj = blade 121 | break 122 | case chaosblade.OperatorChaosBladeYaml: 123 | obj = yaml 124 | break 125 | case chaosblade.OperatorChaosBladeLib: 126 | obj = lib 127 | break 128 | default: 129 | obj = strings.TrimPrefix(srcFile, chaosblade.OperatorChaosBladePath+"/") 130 | } 131 | return fmt.Sprintf("%s/%s", d.url, obj) 132 | } 133 | -------------------------------------------------------------------------------- /exec/model/filter.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package model 18 | 19 | import ( 20 | "fmt" 21 | "strings" 22 | 23 | "github.com/chaosblade-io/chaosblade-spec-go/spec" 24 | "github.com/sirupsen/logrus" 25 | "k8s.io/api/core/v1" 26 | "k8s.io/apimachinery/pkg/selection" 27 | pkglabels "k8s.io/apimachinery/pkg/labels" 28 | ) 29 | 30 | func GetOneAvailableContainerIdFromPod(pod v1.Pod) (containerId, containerName, runtime string, err error) { 31 | containerStatuses := pod.Status.ContainerStatuses 32 | if containerStatuses == nil || len(containerStatuses) == 0 { 33 | return "", "", "", fmt.Errorf("the container statues is empty in %s pod", pod.Name) 34 | } 35 | for _, containerStatus := range containerStatuses { 36 | if containerStatus.State.Running == nil { 37 | continue 38 | } 39 | runtime, containerId := TruncateContainerObjectMetaUid(containerStatus.ContainerID) 40 | return containerId, containerStatus.Name, runtime, nil 41 | } 42 | return "", "", "", fmt.Errorf("cannot find a valiable container in %s pod", pod.Name) 43 | } 44 | 45 | func ParseLabels(labels string) []pkglabels.Requirement { 46 | labelArr := strings.Split(labels, ",") 47 | requirements := make([]pkglabels.Requirement, 0, len(labelArr)) 48 | labelsMap := make(map[string][]string, 0) 49 | if labels == "" { 50 | return requirements 51 | } 52 | 53 | for _, label := range labelArr { 54 | keyValue := strings.SplitN(label, "=", 2) 55 | if len(keyValue) != 2 { 56 | logrus.Warningf("label %s is illegal", label) 57 | continue 58 | } 59 | if labelsMap[keyValue[0]] == nil { 60 | valueArr := make([]string, 0) 61 | valueArr = append(valueArr, keyValue[1]) 62 | labelsMap[keyValue[0]] = valueArr 63 | } else { 64 | labelsMap[keyValue[0]] = append(labelsMap[keyValue[0]], keyValue[1]) 65 | } 66 | } 67 | 68 | for label, value := range labelsMap { 69 | requirement, err := pkglabels.NewRequirement(label, selection.In, value) 70 | if err != nil { 71 | logrus.Warningf("requirement %s-%s is illegal", label, value) 72 | continue 73 | } 74 | requirements = append(requirements, *requirement) 75 | } 76 | return requirements 77 | } 78 | 79 | func MapContains(bigMap map[string]string, requirements []pkglabels.Requirement) bool { 80 | if bigMap == nil || requirements == nil { 81 | return false 82 | } 83 | labelSet := pkglabels.Set(bigMap) 84 | for i := 0; i < len(requirements); i++ { 85 | if requirements[i].Matches(labelSet) { 86 | return true 87 | } 88 | } 89 | return false 90 | } 91 | 92 | func CheckFlags(flags map[string]string) *spec.Response { 93 | // Must include one flag in the count, percent, labels and names 94 | expFlags := []*spec.ExpFlag{ 95 | ResourceCountFlag, 96 | ResourcePercentFlag, 97 | ResourceLabelsFlag, 98 | ResourceNamesFlag, 99 | } 100 | value := "" 101 | flagsNames := make([]string, 0) 102 | for _, flag := range expFlags { 103 | flagsNames = append(flagsNames, flag.Name) 104 | value = fmt.Sprintf("%s%s", value, flags[flag.Name]) 105 | } 106 | if value == "" { 107 | return spec.ResponseFailWithFlags(spec.ParameterLess, strings.Join(flagsNames, "|")) 108 | } 109 | return spec.Success() 110 | } 111 | -------------------------------------------------------------------------------- /exec/model/filter_pod.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package model 18 | 19 | import ( 20 | "context" 21 | "math/rand" 22 | "strings" 23 | "time" 24 | 25 | "github.com/sirupsen/logrus" 26 | "k8s.io/api/core/v1" 27 | "k8s.io/apimachinery/pkg/types" 28 | "sigs.k8s.io/controller-runtime/pkg/client" 29 | 30 | "github.com/chaosblade-io/chaosblade-spec-go/spec" 31 | pkglabels "k8s.io/apimachinery/pkg/labels" 32 | 33 | "github.com/chaosblade-io/chaosblade-operator/channel" 34 | ) 35 | 36 | const DefaultNamespace = "default" 37 | 38 | func CheckPodFlags(flags map[string]string) *spec.Response { 39 | namespace := flags[ResourceNamespaceFlag.Name] 40 | if namespace == "" { 41 | return spec.ResponseFailWithFlags(spec.ParameterLess, ResourceNamespaceFlag.Name) 42 | } 43 | namespacesValue := strings.Split(namespace, ",") 44 | if len(namespacesValue) > 1 { 45 | return spec.ResponseFailWithFlags(spec.ParameterInvalidNSNotOne, ResourceNamespaceFlag.Name) 46 | } 47 | return CheckFlags(flags) 48 | } 49 | 50 | // GetMatchedPodResources return matched pods 51 | func (b *BaseExperimentController) GetMatchedPodResources(ctx context.Context, expModel spec.ExpModel) ([]v1.Pod, *spec.Response) { 52 | flags := expModel.ActionFlags 53 | if flags[ResourceNamespaceFlag.Name] == "" { 54 | expModel.ActionFlags[ResourceNamespaceFlag.Name] = DefaultNamespace 55 | } 56 | if resp := CheckPodFlags(flags); !resp.Success { 57 | return nil, resp 58 | } 59 | pods, resp := resourceFunc(ctx, b.Client, flags) 60 | if !resp.Success { 61 | return pods, resp 62 | } 63 | return b.filterByOtherFlags(pods, flags) 64 | } 65 | 66 | func (b *BaseExperimentController) filterByOtherFlags(pods []v1.Pod, flags map[string]string) ([]v1.Pod, *spec.Response) { 67 | random := flags["random"] == "true" 68 | groupKey := flags[ResourceGroupKeyFlag.Name] 69 | if groupKey == "" { 70 | count, resp := GetResourceCount(len(pods), flags) 71 | if !resp.Success { 72 | return pods[:count], resp 73 | } 74 | if random { 75 | return randomPodSelected(pods, count), spec.Success() 76 | } 77 | return pods[:count], spec.Success() 78 | } 79 | groupPods := make(map[string][]v1.Pod, 0) 80 | keys := strings.Split(groupKey, ",") 81 | for _, pod := range pods { 82 | for _, key := range keys { 83 | labelValue := pod.Labels[key] 84 | podList := groupPods[labelValue] 85 | if podList == nil { 86 | podList = []v1.Pod{} 87 | groupPods[labelValue] = podList 88 | } 89 | groupPods[labelValue] = append(podList, pod) 90 | } 91 | } 92 | result := make([]v1.Pod, 0) 93 | for _, podList := range groupPods { 94 | count, resp := GetResourceCount(len(podList), flags) 95 | if !resp.Success { 96 | return pods[:count], resp 97 | } 98 | if random { 99 | result = append(result, randomPodSelected(podList, count)...) 100 | } else { 101 | result = append(result, podList[:count]...) 102 | } 103 | } 104 | if len(result) == 0 { 105 | return result, spec.ResponseFailWithFlags(spec.ParameterInvalidK8sPodQuery, ResourceGroupKeyFlag.Name) 106 | } 107 | return result, spec.Success() 108 | } 109 | 110 | // resourceFunc is used to query the target resource 111 | var resourceFunc = func(ctx context.Context, client2 *channel.Client, flags map[string]string) ([]v1.Pod, *spec.Response) { 112 | namespace := flags[ResourceNamespaceFlag.Name] 113 | labels := flags[ResourceLabelsFlag.Name] 114 | requirements := ParseLabels(labels) 115 | logrusField := logrus.WithField("experiment", GetExperimentIdFromContext(ctx)) 116 | pods := make([]v1.Pod, 0) 117 | names := flags[ResourceNamesFlag.Name] 118 | logrusField.Debugf("namespace: %s, labels: %s, names: %s", namespace, labels, names) 119 | if names != "" { 120 | nameArr := strings.Split(names, ",") 121 | for _, name := range nameArr { 122 | pod := v1.Pod{} 123 | err := client2.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: name}, &pod) 124 | if err != nil { 125 | logrusField.Warningf("can not find the pod by %s name in %s namespace, %v", name, namespace, err) 126 | continue 127 | } 128 | if len(requirements) > 0 { 129 | if MapContains(pod.Labels, requirements) { 130 | pods = append(pods, pod) 131 | } 132 | } else { 133 | pods = append(pods, pod) 134 | } 135 | } 136 | logrusField.Infof("get pods by names %s, len is %d", names, len(pods)) 137 | if len(pods) == 0 { 138 | return pods, spec.ResponseFailWithFlags(spec.ParameterInvalidK8sPodQuery, names) 139 | } 140 | return pods, spec.Success() 141 | } 142 | if labels != "" && len(requirements) == 0 { 143 | msg := spec.ParameterIllegal.Sprintf(ResourceLabelsFlag.Name, labels, "data format error") 144 | logrusField.Warningln(msg) 145 | return pods, spec.ResponseFailWithFlags(spec.ParameterLess, ResourceLabelsFlag.Name, labels, "data format error, example: key=value") 146 | } 147 | if len(requirements) > 0 { 148 | podList := v1.PodList{} 149 | selector := pkglabels.NewSelector().Add(requirements...) 150 | opts := client.ListOptions{Namespace: namespace, LabelSelector: selector} 151 | err := client2.List(context.TODO(), &podList, &opts) 152 | if err != nil { 153 | return pods, spec.ResponseFailWithFlags(spec.K8sExecFailed, "PodList", err) 154 | } 155 | if len(podList.Items) == 0 { 156 | return pods, spec.ResponseFailWithFlags(spec.ParameterInvalidK8sPodQuery, ResourceLabelsFlag.Name) 157 | } 158 | pods = podList.Items 159 | logrusField.Infof("get pods by labels %s, len is %d", labels, len(pods)) 160 | } 161 | return pods, spec.Success() 162 | } 163 | 164 | func randomPodSelected(pods []v1.Pod, count int) []v1.Pod { 165 | if len(pods) == 0 { 166 | return pods 167 | } 168 | rand.Seed(time.Now().UnixNano()) 169 | for i := len(pods) - 1; i > 0; i-- { 170 | num := rand.Intn(i + 1) 171 | pods[i], pods[num] = pods[num], pods[i] 172 | } 173 | return pods[:count] 174 | } 175 | -------------------------------------------------------------------------------- /exec/model/filter_pod_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package model 18 | 19 | import ( 20 | "reflect" 21 | "testing" 22 | 23 | v1 "k8s.io/api/core/v1" 24 | v12 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | ) 26 | 27 | func Test_randomSelected(t *testing.T) { 28 | originList := []v1.Pod{ 29 | {ObjectMeta: v12.ObjectMeta{Name: "1"}}, 30 | {ObjectMeta: v12.ObjectMeta{Name: "2"}}, 31 | {ObjectMeta: v12.ObjectMeta{Name: "3"}}, 32 | {ObjectMeta: v12.ObjectMeta{Name: "4"}}, 33 | {ObjectMeta: v12.ObjectMeta{Name: "5"}}, 34 | {ObjectMeta: v12.ObjectMeta{Name: "6"}}, 35 | {ObjectMeta: v12.ObjectMeta{Name: "7"}}, 36 | {ObjectMeta: v12.ObjectMeta{Name: "8"}}, 37 | {ObjectMeta: v12.ObjectMeta{Name: "9"}}, 38 | {ObjectMeta: v12.ObjectMeta{Name: "10"}}, 39 | } 40 | randomList := randomPodSelected(originList, 5) 41 | var randomNameList []string 42 | for _, item := range randomList { 43 | randomNameList = append(randomNameList, item.ObjectMeta.Name) 44 | } 45 | t.Logf("randomNameList()=%v", randomNameList) 46 | if reflect.DeepEqual(randomNameList, []string{"1", "2", "3", "4", "5"}) { 47 | t.Errorf("randomPodSelected() is invalid") 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /exec/model/osexp.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package model 18 | 19 | import ( 20 | "github.com/chaosblade-io/chaosblade-exec-os/exec/cpu" 21 | "github.com/chaosblade-io/chaosblade-exec-os/exec/disk" 22 | "github.com/chaosblade-io/chaosblade-exec-os/exec/file" 23 | "github.com/chaosblade-io/chaosblade-exec-os/exec/mem" 24 | "github.com/chaosblade-io/chaosblade-exec-os/exec/network" 25 | "github.com/chaosblade-io/chaosblade-exec-os/exec/process" 26 | "github.com/chaosblade-io/chaosblade-exec-os/exec/script" 27 | "github.com/chaosblade-io/chaosblade-spec-go/spec" 28 | ) 29 | 30 | type OSSubResourceModelSpec struct { 31 | BaseSubResourceExpModelSpec 32 | } 33 | 34 | func NewOSSubResourceModelSpec() SubResourceExpModelSpec { 35 | modelSpec := &OSSubResourceModelSpec{ 36 | BaseSubResourceExpModelSpec{ 37 | ExpModelSpecs: []spec.ExpModelCommandSpec{ 38 | cpu.NewCpuCommandModelSpec(), 39 | network.NewNetworkCommandSpec(), 40 | process.NewProcessCommandModelSpec(), 41 | disk.NewDiskCommandSpec(), 42 | mem.NewMemCommandModelSpec(), 43 | file.NewFileCommandSpec(), 44 | script.NewScriptCommandModelSpec(), 45 | }, 46 | }, 47 | } 48 | return modelSpec 49 | } 50 | -------------------------------------------------------------------------------- /exec/model/parallelizer.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "sync" 4 | 5 | const ( 6 | maxWorkers = 64 //magic number 7 | ) 8 | 9 | type DoWorkFunc func(workID int) 10 | 11 | func ParallelizeExec(workCount int, doWork DoWorkFunc) { 12 | workers := maxWorkers 13 | toExec := make(chan int, workCount) 14 | 15 | for i := 0; i < workCount; i++ { 16 | toExec <- i 17 | } 18 | close(toExec) 19 | 20 | if workCount < workers { 21 | workers = workCount 22 | } 23 | 24 | wg := sync.WaitGroup{} 25 | wg.Add(workers) 26 | for i := 0; i < workers; i++ { 27 | go func() { 28 | defer wg.Done() 29 | for workID := range toExec { 30 | doWork(workID) 31 | } 32 | }() 33 | } 34 | wg.Wait() 35 | } 36 | -------------------------------------------------------------------------------- /exec/node/controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package node 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/chaosblade-io/chaosblade-spec-go/spec" 23 | "github.com/sirupsen/logrus" 24 | v1 "k8s.io/api/core/v1" 25 | 26 | "github.com/chaosblade-io/chaosblade-operator/channel" 27 | "github.com/chaosblade-io/chaosblade-operator/exec/model" 28 | "github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1" 29 | ) 30 | 31 | type ExpController struct { 32 | model.BaseExperimentController 33 | } 34 | 35 | func NewExpController(client *channel.Client) model.ExperimentController { 36 | return &ExpController{ 37 | model.BaseExperimentController{ 38 | Client: client, 39 | ResourceModelSpec: NewResourceModelSpec(client), 40 | }, 41 | } 42 | } 43 | 44 | func (*ExpController) Name() string { 45 | return "node" 46 | } 47 | 48 | func (e *ExpController) Create(ctx context.Context, expSpec v1alpha1.ExperimentSpec) *spec.Response { 49 | expModel := model.ExtractExpModelFromExperimentSpec(expSpec) 50 | experimentId := model.GetExperimentIdFromContext(ctx) 51 | logrusField := logrus.WithField("experiment", experimentId) 52 | // get nodes 53 | nodes, resp := e.getMatchedNodeResources(ctx, *expModel) 54 | if !resp.Success { 55 | logrusField.Errorf("uid: %s, get matched node resources failed, %v", experimentId, resp.Err) 56 | resp.Result = v1alpha1.CreateFailExperimentStatus(resp.Err, []v1alpha1.ResourceStatus{}) 57 | return resp 58 | } 59 | logrusField.Infof("creating node experiment, node count is %d", len(nodes)) 60 | containerMatchedList := getContainerMatchedList(nodes) 61 | ctx = model.SetContainerObjectMetaListToContext(ctx, containerMatchedList) 62 | return e.Exec(ctx, expModel) 63 | } 64 | 65 | func (e *ExpController) Destroy(ctx context.Context, expSpec v1alpha1.ExperimentSpec, oldExpStatus v1alpha1.ExperimentStatus) *spec.Response { 66 | logrus.WithField("experiment", model.GetExperimentIdFromContext(ctx)).Infoln("start to destroy") 67 | expModel := model.ExtractExpModelFromExperimentSpec(expSpec) 68 | statuses := oldExpStatus.ResStatuses 69 | if statuses == nil { 70 | return spec.ReturnSuccess(v1alpha1.CreateSuccessExperimentStatus([]v1alpha1.ResourceStatus{})) 71 | } 72 | containerObjectMetaList := model.ContainerMatchedList{} 73 | for _, status := range statuses { 74 | if !status.Success { 75 | continue 76 | } 77 | containerObjectMeta := model.ParseIdentifier(status.Identifier) 78 | containerObjectMeta.Id = status.Id 79 | containerObjectMetaList = append(containerObjectMetaList, containerObjectMeta) 80 | } 81 | ctx = model.SetContainerObjectMetaListToContext(ctx, containerObjectMetaList) 82 | return e.Exec(ctx, expModel) 83 | } 84 | 85 | // getContainerMatchedList transports selected pods 86 | func getContainerMatchedList(nodes []v1.Node) model.ContainerMatchedList { 87 | containerObjectMetaList := model.ContainerMatchedList{} 88 | for _, n := range nodes { 89 | containerObjectMetaList = append(containerObjectMetaList, model.ContainerObjectMeta{ 90 | NodeName: n.Name, 91 | }) 92 | } 93 | return containerObjectMetaList 94 | } 95 | -------------------------------------------------------------------------------- /exec/node/filter.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package node 18 | 19 | import ( 20 | "context" 21 | "strings" 22 | 23 | "github.com/sirupsen/logrus" 24 | "k8s.io/api/core/v1" 25 | pkglabels "k8s.io/apimachinery/pkg/labels" 26 | "k8s.io/apimachinery/pkg/types" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | 29 | "github.com/chaosblade-io/chaosblade-spec-go/spec" 30 | 31 | "github.com/chaosblade-io/chaosblade-operator/channel" 32 | "github.com/chaosblade-io/chaosblade-operator/exec/model" 33 | ) 34 | 35 | func (e *ExpController) getMatchedNodeResources(ctx context.Context, expModel spec.ExpModel) ([]v1.Node, *spec.Response) { 36 | flags := expModel.ActionFlags 37 | if resp := model.CheckFlags(flags); !resp.Success { 38 | return nil, resp 39 | } 40 | nodes, resp := resourceFunc(ctx, e.Client, flags) 41 | if !resp.Success { 42 | return nil, resp 43 | } 44 | return e.filterByOtherFlags(nodes, flags) 45 | } 46 | 47 | func (e *ExpController) filterByOtherFlags(nodes []v1.Node, flags map[string]string) ([]v1.Node, *spec.Response) { 48 | groupKey := flags[model.ResourceGroupKeyFlag.Name] 49 | if groupKey == "" { 50 | count, resp := model.GetResourceCount(len(nodes), flags) 51 | return nodes[:count], resp 52 | } 53 | groupNodes := make(map[string][]v1.Node, 0) 54 | keys := strings.Split(groupKey, ",") 55 | for _, node := range nodes { 56 | for _, key := range keys { 57 | nodeList := groupNodes[node.Labels[key]] 58 | if nodeList == nil { 59 | nodeList = make([]v1.Node, 0) 60 | } 61 | nodeList = append(nodeList, node) 62 | } 63 | } 64 | result := make([]v1.Node, 0) 65 | for _, nodeList := range groupNodes { 66 | count, resp := model.GetResourceCount(len(nodeList), flags) 67 | if !resp.Success { 68 | return nodes[:count], resp 69 | } 70 | result = append(result, nodeList[:count]...) 71 | } 72 | return result, spec.Success() 73 | } 74 | 75 | var resourceFunc = func(ctx context.Context, client2 *channel.Client, flags map[string]string) ([]v1.Node, *spec.Response) { 76 | labels := flags[model.ResourceLabelsFlag.Name] 77 | requirements := model.ParseLabels(labels) 78 | logrusField := logrus.WithField("experiment", model.GetExperimentIdFromContext(ctx)) 79 | nodes := make([]v1.Node, 0) 80 | names := flags[model.ResourceNamesFlag.Name] 81 | if names != "" { 82 | nameArr := strings.Split(names, ",") 83 | for _, name := range nameArr { 84 | node := v1.Node{} 85 | err := client2.Get(context.TODO(), types.NamespacedName{Name: name}, &node) 86 | if err != nil { 87 | // Skip the invalid name 88 | logrusField.Warningf("can not find the node by %s name, %v", name, err) 89 | continue 90 | } 91 | if len(requirements) > 0 { 92 | if model.MapContains(node.Labels, requirements) { 93 | nodes = append(nodes, node) 94 | } 95 | } else { 96 | nodes = append(nodes, node) 97 | } 98 | } 99 | logrusField.Infof("get nodes by name %s, len is %d", names, len(nodes)) 100 | if len(nodes) == 0 { 101 | return nodes, spec.ResponseFailWithFlags(spec.ParameterInvalidK8sNodeQuery, names) 102 | } 103 | return nodes, spec.Success() 104 | } 105 | if labels != "" && len(requirements) == 0 { 106 | logrusField.Warningln(spec.ParameterIllegal.Sprintf(model.ResourceLabelsFlag.Name, labels, "illegal labels")) 107 | return nodes, spec.ResponseFailWithFlags(spec.ParameterIllegal, model.ResourceLabelsFlag.Name, labels, "illegal labels") 108 | } 109 | if len(requirements) > 0 { 110 | nodeList := v1.NodeList{} 111 | selector := pkglabels.NewSelector().Add(requirements...) 112 | opts := client.ListOptions{LabelSelector: selector} 113 | err := client2.List(context.TODO(), &nodeList, &opts) 114 | if err != nil { 115 | return nodes, spec.ResponseFailWithFlags(spec.K8sExecFailed, "ListNode", err) 116 | } 117 | nodes = nodeList.Items 118 | logrusField.Infof("get nodes by labels %s, len is %d", labels, len(nodes)) 119 | } 120 | if len(nodes) == 0 { 121 | return nodes, spec.ResponseFailWithFlags(spec.ParameterInvalidK8sNodeQuery, labels) 122 | } 123 | return nodes, spec.Success() 124 | } 125 | -------------------------------------------------------------------------------- /exec/pod/controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2019 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package pod 18 | 19 | import ( 20 | "context" 21 | "github.com/chaosblade-io/chaosblade-exec-cri/exec/container" 22 | 23 | "github.com/sirupsen/logrus" 24 | v1 "k8s.io/api/core/v1" 25 | 26 | "github.com/chaosblade-io/chaosblade-spec-go/spec" 27 | 28 | "github.com/chaosblade-io/chaosblade-operator/channel" 29 | "github.com/chaosblade-io/chaosblade-operator/exec/model" 30 | "github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1" 31 | ) 32 | 33 | type ExpController struct { 34 | model.BaseExperimentController 35 | } 36 | 37 | func NewExpController(client *channel.Client) model.ExperimentController { 38 | return &ExpController{ 39 | model.BaseExperimentController{ 40 | Client: client, 41 | ResourceModelSpec: NewResourceModelSpec(client), 42 | }, 43 | } 44 | } 45 | 46 | func (*ExpController) Name() string { 47 | return "pod" 48 | } 49 | 50 | // Create pod resource experiments 51 | func (e *ExpController) Create(ctx context.Context, expSpec v1alpha1.ExperimentSpec) *spec.Response { 52 | expModel := model.ExtractExpModelFromExperimentSpec(expSpec) 53 | experimentId := model.GetExperimentIdFromContext(ctx) 54 | logrusField := logrus.WithField("experiment", experimentId) 55 | pods, resp := e.GetMatchedPodResources(ctx, *expModel) 56 | if !resp.Success { 57 | logrusField.Errorf("uid: %s, get matched pod experiment failed, %v", experimentId, resp.Err) 58 | resp.Result = v1alpha1.CreateFailExperimentStatus(resp.Err, []v1alpha1.ResourceStatus{}) 59 | } 60 | logrusField.Infof("creating pod experiment, pod count is %d", len(pods)) 61 | containerObjectMetaList := getContainerMatchedList(experimentId, pods) 62 | if len(containerObjectMetaList) == 0 { 63 | logrusField.Errorf("uid: %s, get container from context failed", experimentId) 64 | return spec.ResponseFailWithResult(spec.ContainerInContextNotFound, 65 | v1alpha1.CreateFailExperimentStatus(spec.ContainerInContextNotFound.Msg, []v1alpha1.ResourceStatus{})) 66 | } 67 | ctx = model.SetContainerObjectMetaListToContext(ctx, containerObjectMetaList) 68 | return e.Exec(ctx, expModel) 69 | } 70 | 71 | func (e *ExpController) Destroy(ctx context.Context, expSpec v1alpha1.ExperimentSpec, oldExpStatus v1alpha1.ExperimentStatus) *spec.Response { 72 | logrus.WithField("experiment", model.GetExperimentIdFromContext(ctx)).Infoln("start to destroy") 73 | expModel := model.ExtractExpModelFromExperimentSpec(expSpec) 74 | statuses := oldExpStatus.ResStatuses 75 | if statuses == nil { 76 | return spec.ReturnSuccess(v1alpha1.CreateSuccessExperimentStatus([]v1alpha1.ResourceStatus{})) 77 | } 78 | containerObjectMetaList := model.ContainerMatchedList{} 79 | for _, status := range statuses { 80 | if !status.Success { 81 | continue 82 | } 83 | containerObjectMeta := model.ParseIdentifier(status.Identifier) 84 | containerObjectMeta.Id = status.Id 85 | containerObjectMetaList = append(containerObjectMetaList, containerObjectMeta) 86 | } 87 | ctx = model.SetContainerObjectMetaListToContext(ctx, containerObjectMetaList) 88 | return e.Exec(ctx, expModel) 89 | } 90 | 91 | // getContainerMatchedList transports selected pods 92 | func getContainerMatchedList(experimentId string, pods []v1.Pod) model.ContainerMatchedList { 93 | containerObjectMetaList := model.ContainerMatchedList{} 94 | for _, p := range pods { 95 | containerId, containerName, runtime, err := model.GetOneAvailableContainerIdFromPod(p) 96 | if runtime == container.DockerRuntime { 97 | containerId = containerId[:12] 98 | } 99 | 100 | if err != nil { 101 | logrus.WithField("experiment", experimentId).WithField("pod", p.Name). 102 | Errorf("get an available container failed, %v", err) 103 | continue 104 | } 105 | containerObjectMetaList = append(containerObjectMetaList, model.ContainerObjectMeta{ 106 | ContainerId: containerId, 107 | ContainerName: containerName, 108 | ContainerRuntime: runtime, 109 | PodName: p.Name, 110 | NodeName: p.Spec.NodeName, 111 | Namespace: p.Namespace, 112 | }) 113 | } 114 | return containerObjectMetaList 115 | } 116 | -------------------------------------------------------------------------------- /exec/pod/delete.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package pod 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | "github.com/chaosblade-io/chaosblade-spec-go/util" 24 | "github.com/sirupsen/logrus" 25 | "k8s.io/api/core/v1" 26 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | 28 | "github.com/chaosblade-io/chaosblade-spec-go/spec" 29 | 30 | "github.com/chaosblade-io/chaosblade-operator/channel" 31 | "github.com/chaosblade-io/chaosblade-operator/exec/model" 32 | "github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1" 33 | ) 34 | 35 | type DeletePodActionSpec struct { 36 | spec.BaseExpActionCommandSpec 37 | } 38 | 39 | func NewDeletePodActionSpec(client *channel.Client) spec.ExpActionCommandSpec { 40 | return &DeletePodActionSpec{ 41 | spec.BaseExpActionCommandSpec{ 42 | ActionMatchers: []spec.ExpFlagSpec{}, 43 | ActionFlags: []spec.ExpFlagSpec{ 44 | &spec.ExpFlag{ 45 | Name: "random", 46 | Desc: "Randomly select pod", 47 | NoArgs: true, 48 | }, 49 | }, 50 | ActionExecutor: &DeletePodActionExecutor{client: client}, 51 | ActionExample: 52 | `# Deletes the POD under the specified default namespace that is app=guestbook 53 | blade create k8s pod-pod delete --labels app=guestbook --namespace default --evict-count 2 --kubeconfig ~/.kube/config`, 54 | ActionCategories: []string{model.CategorySystemContainer}, 55 | }, 56 | } 57 | } 58 | 59 | func (*DeletePodActionSpec) Name() string { 60 | return "delete" 61 | } 62 | 63 | func (*DeletePodActionSpec) Aliases() []string { 64 | return []string{} 65 | } 66 | 67 | func (*DeletePodActionSpec) ShortDesc() string { 68 | return "Delete pods" 69 | } 70 | 71 | func (*DeletePodActionSpec) LongDesc() string { 72 | return "Delete pods" 73 | } 74 | 75 | type DeletePodActionExecutor struct { 76 | client *channel.Client 77 | } 78 | 79 | func (*DeletePodActionExecutor) Name() string { 80 | return "delete" 81 | } 82 | 83 | func (*DeletePodActionExecutor) SetChannel(channel spec.Channel) { 84 | } 85 | 86 | func (d *DeletePodActionExecutor) Exec(uid string, ctx context.Context, model *spec.ExpModel) *spec.Response { 87 | if _, ok := spec.IsDestroy(ctx); ok { 88 | return d.destroy(uid, ctx, model) 89 | } else { 90 | return d.create(uid, ctx, model) 91 | } 92 | } 93 | 94 | func (d *DeletePodActionExecutor) create(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response { 95 | containerObjectMetaList, err := model.GetContainerObjectMetaListFromContext(ctx) 96 | if err != nil { 97 | util.Errorf(uid, util.GetRunFuncName(), err.Error()) 98 | return spec.ResponseFailWithResult(spec.ContainerInContextNotFound, 99 | v1alpha1.CreateFailExperimentStatus(spec.ContainerInContextNotFound.Msg, []v1alpha1.ResourceStatus{})) 100 | } 101 | statuses := make([]v1alpha1.ResourceStatus, 0) 102 | success := false 103 | for _, meta := range containerObjectMetaList { 104 | status := v1alpha1.ResourceStatus{ 105 | Kind: v1alpha1.PodKind, 106 | Identifier: fmt.Sprintf("%s/%s/%s", meta.Namespace, meta.NodeName, meta.PodName), 107 | } 108 | objectMeta := metav1.ObjectMeta{Name: meta.PodName, Namespace: meta.Namespace} 109 | err := d.client.Delete(context.TODO(), &v1.Pod{ObjectMeta: objectMeta}) 110 | if err != nil { 111 | logrus.WithField("experiment", model.GetExperimentIdFromContext(ctx)). 112 | Warningf("delete pod %s err, %v", meta.PodName, err) 113 | status = status.CreateFailResourceStatus(err.Error(), spec.K8sExecFailed.Code) 114 | } else { 115 | status = status.CreateSuccessResourceStatus() 116 | success = true 117 | } 118 | statuses = append(statuses, status) 119 | } 120 | var experimentStatus v1alpha1.ExperimentStatus 121 | if success { 122 | experimentStatus = v1alpha1.CreateSuccessExperimentStatus(statuses) 123 | } else { 124 | experimentStatus = v1alpha1.CreateFailExperimentStatus("see resStatuses for details", statuses) 125 | } 126 | return spec.ReturnResultIgnoreCode(experimentStatus) 127 | } 128 | 129 | func (d *DeletePodActionExecutor) destroy(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response { 130 | containerObjectMetaList, err := model.GetContainerObjectMetaListFromContext(ctx) 131 | if err != nil { 132 | util.Errorf(uid, util.GetRunFuncName(), err.Error()) 133 | return spec.ResponseFailWithResult(spec.ContainerInContextNotFound, 134 | v1alpha1.CreateFailExperimentStatus(spec.ContainerInContextNotFound.Msg, []v1alpha1.ResourceStatus{})) 135 | } 136 | experimentStatus := v1alpha1.CreateDestroyedExperimentStatus([]v1alpha1.ResourceStatus{}) 137 | statuses := experimentStatus.ResStatuses 138 | for _, c := range containerObjectMetaList { 139 | status := v1alpha1.ResourceStatus{ 140 | Id: c.Id, 141 | Kind: v1alpha1.PodKind, 142 | State: v1alpha1.DestroyedState, 143 | Success: true, 144 | Identifier: c.GetIdentifier(), 145 | } 146 | statuses = append(statuses, status) 147 | } 148 | experimentStatus.ResStatuses = statuses 149 | return spec.ReturnResultIgnoreCode(experimentStatus) 150 | } 151 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/chaosblade-io/chaosblade-operator 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/chaosblade-io/chaosblade-exec-cri v1.7.4 7 | github.com/chaosblade-io/chaosblade-exec-os v1.7.4 8 | github.com/chaosblade-io/chaosblade-spec-go v1.7.4 9 | github.com/ethercflow/hookfs v0.3.0 10 | github.com/go-openapi/spec v0.19.4 11 | github.com/hanwen/go-fuse v1.0.0 12 | github.com/operator-framework/operator-sdk v0.17.0 13 | github.com/sirupsen/logrus v1.8.1 14 | github.com/spf13/pflag v1.0.5 15 | k8s.io/api v0.20.6 16 | k8s.io/apimachinery v0.20.6 17 | k8s.io/client-go v12.0.0+incompatible 18 | k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd 19 | sigs.k8s.io/controller-runtime v0.6.0 20 | ) 21 | 22 | require ( 23 | cloud.google.com/go v0.54.0 // indirect 24 | github.com/Azure/go-autorest v14.2.0+incompatible // indirect 25 | github.com/Azure/go-autorest/autorest v0.11.1 // indirect 26 | github.com/Azure/go-autorest/autorest/adal v0.9.5 // indirect 27 | github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect 28 | github.com/Azure/go-autorest/logger v0.2.0 // indirect 29 | github.com/Azure/go-autorest/tracing v0.6.0 // indirect 30 | github.com/Microsoft/go-winio v0.4.17 // indirect 31 | github.com/Microsoft/hcsshim v0.8.21 // indirect 32 | github.com/PuerkitoBio/purell v1.1.1 // indirect 33 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 34 | github.com/beorn7/perks v1.0.1 // indirect 35 | github.com/bits-and-blooms/bitset v1.2.0 // indirect 36 | github.com/cespare/xxhash/v2 v2.1.1 // indirect 37 | github.com/containerd/cgroups v1.0.2-0.20210605143700-23b51209bf7b // indirect 38 | github.com/containerd/containerd v1.5.6 // indirect 39 | github.com/containerd/continuity v0.1.0 // indirect 40 | github.com/containerd/fifo v1.0.0 // indirect 41 | github.com/containerd/ttrpc v1.0.2 // indirect 42 | github.com/containerd/typeurl v1.0.2 // indirect 43 | github.com/coreos/go-systemd/v22 v22.3.2 // indirect 44 | github.com/davecgh/go-spew v1.1.1 // indirect 45 | github.com/docker/distribution v2.7.1+incompatible // indirect 46 | github.com/docker/docker v1.4.2-0.20200203170920-46ec8731fbce // indirect 47 | github.com/docker/go-connections v0.4.0 // indirect 48 | github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect 49 | github.com/docker/go-units v0.4.0 // indirect 50 | github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 // indirect 51 | github.com/emicklei/go-restful v2.9.5+incompatible // indirect 52 | github.com/evanphx/json-patch v4.9.0+incompatible // indirect 53 | github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect 54 | github.com/go-logr/logr v0.2.1 // indirect 55 | github.com/go-logr/zapr v0.2.0 // indirect 56 | github.com/go-ole/go-ole v1.2.6 // indirect 57 | github.com/go-openapi/jsonpointer v0.19.3 // indirect 58 | github.com/go-openapi/jsonreference v0.19.3 // indirect 59 | github.com/go-openapi/swag v0.19.5 // indirect 60 | github.com/godbus/dbus/v5 v5.0.4 // indirect 61 | github.com/gogo/googleapis v1.4.0 // indirect 62 | github.com/gogo/protobuf v1.3.2 // indirect 63 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect 64 | github.com/golang/protobuf v1.5.0 // indirect 65 | github.com/google/go-cmp v0.5.6 // indirect 66 | github.com/google/gofuzz v1.1.0 // indirect 67 | github.com/google/uuid v1.2.0 // indirect 68 | github.com/googleapis/gnostic v0.4.1 // indirect 69 | github.com/hashicorp/golang-lru v0.5.3 // indirect 70 | github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c // indirect 71 | github.com/imdario/mergo v0.3.12 // indirect 72 | github.com/json-iterator/go v1.1.10 // indirect 73 | github.com/klauspost/compress v1.11.13 // indirect 74 | github.com/mailru/easyjson v0.7.0 // indirect 75 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect 76 | github.com/moby/locker v1.0.1 // indirect 77 | github.com/moby/sys/mountinfo v0.4.1 // indirect 78 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 79 | github.com/modern-go/reflect2 v1.0.1 // indirect 80 | github.com/opencontainers/go-digest v1.0.0 // indirect 81 | github.com/opencontainers/image-spec v1.0.1 // indirect 82 | github.com/opencontainers/runc v1.0.2 // indirect 83 | github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 // indirect 84 | github.com/opencontainers/selinux v1.8.2 // indirect 85 | github.com/pkg/errors v0.9.1 // indirect 86 | github.com/prometheus/client_golang v1.7.1 // indirect 87 | github.com/prometheus/client_model v0.2.0 // indirect 88 | github.com/prometheus/common v0.10.0 // indirect 89 | github.com/prometheus/procfs v0.6.0 // indirect 90 | github.com/shirou/gopsutil v3.21.11+incompatible // indirect 91 | github.com/tklauser/go-sysconf v0.3.9 // indirect 92 | github.com/tklauser/numcpus v0.3.0 // indirect 93 | github.com/yusufpapurcu/wmi v1.2.4 // indirect 94 | go.opencensus.io v0.22.3 // indirect 95 | go.uber.org/atomic v1.6.0 // indirect 96 | go.uber.org/automaxprocs v1.3.0 // indirect 97 | go.uber.org/multierr v1.5.0 // indirect 98 | go.uber.org/zap v1.14.1 // indirect 99 | golang.org/x/crypto v0.1.0 // indirect 100 | golang.org/x/net v0.1.0 // indirect 101 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect 102 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect 103 | golang.org/x/sys v0.1.0 // indirect 104 | golang.org/x/term v0.1.0 // indirect 105 | golang.org/x/text v0.4.0 // indirect 106 | golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect 107 | gomodules.xyz/jsonpatch/v2 v2.0.1 // indirect 108 | google.golang.org/appengine v1.6.5 // indirect 109 | google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a // indirect 110 | google.golang.org/grpc v1.39.0 // indirect 111 | google.golang.org/protobuf v1.26.0 // indirect 112 | gopkg.in/fsnotify.v1 v1.4.7 // indirect 113 | gopkg.in/inf.v0 v0.9.1 // indirect 114 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect 115 | gopkg.in/yaml.v2 v2.4.0 // indirect 116 | k8s.io/klog v1.0.0 // indirect 117 | k8s.io/klog/v2 v2.4.0 // indirect 118 | k8s.io/utils v0.0.0-20201110183641-67b214c5f920 // indirect 119 | sigs.k8s.io/structured-merge-diff/v4 v4.0.3 // indirect 120 | sigs.k8s.io/yaml v1.2.0 // indirect 121 | ) 122 | 123 | replace k8s.io/client-go => k8s.io/client-go v0.20.6 // Required by prometheus-operator 124 | -------------------------------------------------------------------------------- /pkg/apis/addtoscheme_chaosblade_v1alpha1.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package apis 18 | 19 | import ( 20 | "github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1" 21 | ) 22 | 23 | func init() { 24 | // Register the types with the Scheme so the components can map objects to GroupVersionKinds and back 25 | AddToSchemes = append(AddToSchemes, v1alpha1.SchemeBuilder.AddToScheme) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/apis/apis.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package apis 18 | 19 | import ( 20 | "k8s.io/apimachinery/pkg/runtime" 21 | ) 22 | 23 | // AddToSchemes may be used to add all resources defined in the project to a Scheme 24 | var AddToSchemes runtime.SchemeBuilder 25 | 26 | // AddToScheme adds all Resources to the Scheme 27 | func AddToScheme(s *runtime.Scheme) error { 28 | return AddToSchemes.AddToScheme(s) 29 | } 30 | -------------------------------------------------------------------------------- /pkg/apis/chaosblade/group.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Package chaosblade contains chaosblade API versions. 18 | // 19 | // This file ensures Go source parsers acknowledge the chaosblade package 20 | // and any child packages. It can be removed if any other Go source files are 21 | // added to this package. 22 | package chaosblade 23 | -------------------------------------------------------------------------------- /pkg/apis/chaosblade/meta/zz_generated.openapi.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // +build !ignore_autogenerated 18 | 19 | // This file was autogenerated by openapi-gen. Do not edit it manually! 20 | 21 | package meta 22 | 23 | import ( 24 | common "k8s.io/kube-openapi/pkg/common" 25 | ) 26 | 27 | func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { 28 | return map[string]common.OpenAPIDefinition{} 29 | } 30 | -------------------------------------------------------------------------------- /pkg/apis/chaosblade/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Package v1alpha1 contains API Schema definitions for the chaosblade v1alpha1 API group 18 | // +k8s:deepcopy-gen=package,register 19 | // +groupName=chaosblade.io 20 | package v1alpha1 21 | -------------------------------------------------------------------------------- /pkg/apis/chaosblade/v1alpha1/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // NOTE: Boilerplate only. Ignore this file. 18 | 19 | // Package v1alpha1 contains API Schema definitions for the chaosblade v1alpha1 API group 20 | // +k8s:deepcopy-gen=package,register 21 | // +groupName=chaosblade.io 22 | package v1alpha1 23 | 24 | import ( 25 | "k8s.io/apimachinery/pkg/runtime/schema" 26 | "sigs.k8s.io/controller-runtime/pkg/runtime/scheme" 27 | ) 28 | 29 | var ( 30 | // SchemeGroupVersion is group version used to register these objects 31 | SchemeGroupVersion = schema.GroupVersion{Group: "chaosblade.io", Version: "v1alpha1"} 32 | 33 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 34 | SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion} 35 | ) 36 | -------------------------------------------------------------------------------- /pkg/apis/chaosblade/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | // +build !ignore_autogenerated 2 | 3 | // Code generated by operator-sdk. DO NOT EDIT. 4 | 5 | package v1alpha1 6 | 7 | import ( 8 | runtime "k8s.io/apimachinery/pkg/runtime" 9 | ) 10 | 11 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 12 | func (in *ChaosBlade) DeepCopyInto(out *ChaosBlade) { 13 | *out = *in 14 | out.TypeMeta = in.TypeMeta 15 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 16 | in.Spec.DeepCopyInto(&out.Spec) 17 | in.Status.DeepCopyInto(&out.Status) 18 | return 19 | } 20 | 21 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChaosBlade. 22 | func (in *ChaosBlade) DeepCopy() *ChaosBlade { 23 | if in == nil { 24 | return nil 25 | } 26 | out := new(ChaosBlade) 27 | in.DeepCopyInto(out) 28 | return out 29 | } 30 | 31 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 32 | func (in *ChaosBlade) DeepCopyObject() runtime.Object { 33 | if c := in.DeepCopy(); c != nil { 34 | return c 35 | } 36 | return nil 37 | } 38 | 39 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 40 | func (in *ChaosBladeList) DeepCopyInto(out *ChaosBladeList) { 41 | *out = *in 42 | out.TypeMeta = in.TypeMeta 43 | in.ListMeta.DeepCopyInto(&out.ListMeta) 44 | if in.Items != nil { 45 | in, out := &in.Items, &out.Items 46 | *out = make([]ChaosBlade, len(*in)) 47 | for i := range *in { 48 | (*in)[i].DeepCopyInto(&(*out)[i]) 49 | } 50 | } 51 | return 52 | } 53 | 54 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChaosBladeList. 55 | func (in *ChaosBladeList) DeepCopy() *ChaosBladeList { 56 | if in == nil { 57 | return nil 58 | } 59 | out := new(ChaosBladeList) 60 | in.DeepCopyInto(out) 61 | return out 62 | } 63 | 64 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 65 | func (in *ChaosBladeList) DeepCopyObject() runtime.Object { 66 | if c := in.DeepCopy(); c != nil { 67 | return c 68 | } 69 | return nil 70 | } 71 | 72 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 73 | func (in *ChaosBladeSpec) DeepCopyInto(out *ChaosBladeSpec) { 74 | *out = *in 75 | if in.Experiments != nil { 76 | in, out := &in.Experiments, &out.Experiments 77 | *out = make([]ExperimentSpec, len(*in)) 78 | for i := range *in { 79 | (*in)[i].DeepCopyInto(&(*out)[i]) 80 | } 81 | } 82 | return 83 | } 84 | 85 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChaosBladeSpec. 86 | func (in *ChaosBladeSpec) DeepCopy() *ChaosBladeSpec { 87 | if in == nil { 88 | return nil 89 | } 90 | out := new(ChaosBladeSpec) 91 | in.DeepCopyInto(out) 92 | return out 93 | } 94 | 95 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 96 | func (in *ChaosBladeStatus) DeepCopyInto(out *ChaosBladeStatus) { 97 | *out = *in 98 | if in.ExpStatuses != nil { 99 | in, out := &in.ExpStatuses, &out.ExpStatuses 100 | *out = make([]ExperimentStatus, len(*in)) 101 | for i := range *in { 102 | (*in)[i].DeepCopyInto(&(*out)[i]) 103 | } 104 | } 105 | return 106 | } 107 | 108 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChaosBladeStatus. 109 | func (in *ChaosBladeStatus) DeepCopy() *ChaosBladeStatus { 110 | if in == nil { 111 | return nil 112 | } 113 | out := new(ChaosBladeStatus) 114 | in.DeepCopyInto(out) 115 | return out 116 | } 117 | 118 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 119 | func (in *ExperimentSpec) DeepCopyInto(out *ExperimentSpec) { 120 | *out = *in 121 | if in.Matchers != nil { 122 | in, out := &in.Matchers, &out.Matchers 123 | *out = make([]FlagSpec, len(*in)) 124 | for i := range *in { 125 | (*in)[i].DeepCopyInto(&(*out)[i]) 126 | } 127 | } 128 | return 129 | } 130 | 131 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExperimentSpec. 132 | func (in *ExperimentSpec) DeepCopy() *ExperimentSpec { 133 | if in == nil { 134 | return nil 135 | } 136 | out := new(ExperimentSpec) 137 | in.DeepCopyInto(out) 138 | return out 139 | } 140 | 141 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 142 | func (in *ExperimentStatus) DeepCopyInto(out *ExperimentStatus) { 143 | *out = *in 144 | if in.ResStatuses != nil { 145 | in, out := &in.ResStatuses, &out.ResStatuses 146 | *out = make([]ResourceStatus, len(*in)) 147 | copy(*out, *in) 148 | } 149 | return 150 | } 151 | 152 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExperimentStatus. 153 | func (in *ExperimentStatus) DeepCopy() *ExperimentStatus { 154 | if in == nil { 155 | return nil 156 | } 157 | out := new(ExperimentStatus) 158 | in.DeepCopyInto(out) 159 | return out 160 | } 161 | 162 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 163 | func (in *FlagSpec) DeepCopyInto(out *FlagSpec) { 164 | *out = *in 165 | if in.Value != nil { 166 | in, out := &in.Value, &out.Value 167 | *out = make([]string, len(*in)) 168 | copy(*out, *in) 169 | } 170 | return 171 | } 172 | 173 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlagSpec. 174 | func (in *FlagSpec) DeepCopy() *FlagSpec { 175 | if in == nil { 176 | return nil 177 | } 178 | out := new(FlagSpec) 179 | in.DeepCopyInto(out) 180 | return out 181 | } 182 | 183 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 184 | func (in *ResourceStatus) DeepCopyInto(out *ResourceStatus) { 185 | *out = *in 186 | return 187 | } 188 | 189 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceStatus. 190 | func (in *ResourceStatus) DeepCopy() *ResourceStatus { 191 | if in == nil { 192 | return nil 193 | } 194 | out := new(ResourceStatus) 195 | in.DeepCopyInto(out) 196 | return out 197 | } 198 | -------------------------------------------------------------------------------- /pkg/apis/chaosblade/v1alpha1/zz_generated.openapi.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // +build !ignore_autogenerated 18 | 19 | // This file was autogenerated by openapi-gen. Do not edit it manually! 20 | 21 | package v1alpha1 22 | 23 | import ( 24 | spec "github.com/go-openapi/spec" 25 | common "k8s.io/kube-openapi/pkg/common" 26 | ) 27 | 28 | func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { 29 | return map[string]common.OpenAPIDefinition{ 30 | "github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1.ChaosBlade": schema_pkg_apis_chaosblade_v1alpha1_ChaosBlade(ref), 31 | "github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1.ChaosBladeSpec": schema_pkg_apis_chaosblade_v1alpha1_ChaosBladeSpec(ref), 32 | "github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1.ChaosBladeStatus": schema_pkg_apis_chaosblade_v1alpha1_ChaosBladeStatus(ref), 33 | } 34 | } 35 | 36 | func schema_pkg_apis_chaosblade_v1alpha1_ChaosBlade(ref common.ReferenceCallback) common.OpenAPIDefinition { 37 | return common.OpenAPIDefinition{ 38 | Schema: spec.Schema{ 39 | SchemaProps: spec.SchemaProps{ 40 | Description: "ChaosBlade is the Schema for the chaosblades API", 41 | Properties: map[string]spec.Schema{ 42 | "kind": { 43 | SchemaProps: spec.SchemaProps{ 44 | Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds", 45 | Type: []string{"string"}, 46 | Format: "", 47 | }, 48 | }, 49 | "apiVersion": { 50 | SchemaProps: spec.SchemaProps{ 51 | Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources", 52 | Type: []string{"string"}, 53 | Format: "", 54 | }, 55 | }, 56 | "metadata": { 57 | SchemaProps: spec.SchemaProps{ 58 | Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), 59 | }, 60 | }, 61 | "spec": { 62 | SchemaProps: spec.SchemaProps{ 63 | Ref: ref("github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1.ChaosBladeSpec"), 64 | }, 65 | }, 66 | "status": { 67 | SchemaProps: spec.SchemaProps{ 68 | Ref: ref("github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1.ChaosBladeStatus"), 69 | }, 70 | }, 71 | }, 72 | }, 73 | }, 74 | Dependencies: []string{ 75 | "github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1.ChaosBladeSpec", "github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1.ChaosBladeStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, 76 | } 77 | } 78 | 79 | func schema_pkg_apis_chaosblade_v1alpha1_ChaosBladeSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { 80 | return common.OpenAPIDefinition{ 81 | Schema: spec.Schema{ 82 | SchemaProps: spec.SchemaProps{ 83 | Description: "ChaosBladeSpec defines the desired state of ChaosBlade", 84 | Properties: map[string]spec.Schema{ 85 | "experiments": { 86 | SchemaProps: spec.SchemaProps{ 87 | Description: "INSERT ADDITIONAL SPEC FIELDS - desired state of cluster Important: Run \"operator-sdk generate k8s\" to regenerate code after modifying this file Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html", 88 | Type: []string{"array"}, 89 | Items: &spec.SchemaOrArray{ 90 | Schema: &spec.Schema{ 91 | SchemaProps: spec.SchemaProps{ 92 | Ref: ref("github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1.ExperimentSpec"), 93 | }, 94 | }, 95 | }, 96 | }, 97 | }, 98 | }, 99 | Required: []string{"experiments"}, 100 | }, 101 | }, 102 | Dependencies: []string{ 103 | "github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1.ExperimentSpec"}, 104 | } 105 | } 106 | 107 | func schema_pkg_apis_chaosblade_v1alpha1_ChaosBladeStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { 108 | return common.OpenAPIDefinition{ 109 | Schema: spec.Schema{ 110 | SchemaProps: spec.SchemaProps{ 111 | Description: "ChaosBladeStatus defines the observed state of ChaosBlade", 112 | Properties: map[string]spec.Schema{ 113 | "phase": { 114 | SchemaProps: spec.SchemaProps{ 115 | Description: "Phase indicates the state of the experiment\n Initial -> Running -> Updating -> Destroying -> Destroyed", 116 | Type: []string{"string"}, 117 | Format: "", 118 | }, 119 | }, 120 | "expStatuses": { 121 | SchemaProps: spec.SchemaProps{ 122 | Description: "Important: Run \"operator-sdk generate k8s\" to regenerate code after modifying this file Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html", 123 | Type: []string{"array"}, 124 | Items: &spec.SchemaOrArray{ 125 | Schema: &spec.Schema{ 126 | SchemaProps: spec.SchemaProps{ 127 | Ref: ref("github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1.ExperimentStatus"), 128 | }, 129 | }, 130 | }, 131 | }, 132 | }, 133 | }, 134 | Required: []string{"expStatuses"}, 135 | }, 136 | }, 137 | Dependencies: []string{ 138 | "github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1.ExperimentStatus"}, 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /pkg/controller/add_chaosblade.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package controller 18 | 19 | import ( 20 | "github.com/chaosblade-io/chaosblade-operator/pkg/controller/chaosblade" 21 | ) 22 | 23 | func init() { 24 | // AddToManagerFuncs is a list of functions to create controllers and add them to a manager. 25 | AddToManagerFuncs = append(AddToManagerFuncs, chaosblade.Add) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/controller/chaosblade/daemonset.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package chaosblade 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | "github.com/operator-framework/operator-sdk/pkg/k8sutil" 24 | "github.com/sirupsen/logrus" 25 | appsv1 "k8s.io/api/apps/v1" 26 | corev1 "k8s.io/api/core/v1" 27 | apierrors "k8s.io/apimachinery/pkg/api/errors" 28 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 30 | "k8s.io/apimachinery/pkg/runtime/schema" 31 | "k8s.io/apimachinery/pkg/types" 32 | 33 | "github.com/chaosblade-io/chaosblade-operator/pkg/runtime/chaosblade" 34 | ) 35 | 36 | // Deploy the chaosblade tool with daemonset mode 37 | func deployChaosBladeTool(rcb *ReconcileChaosBlade) error { 38 | references, err := createOwnerReferences(rcb) 39 | if err != nil { 40 | return err 41 | } 42 | daemonSet := &appsv1.DaemonSet{ 43 | ObjectMeta: metav1.ObjectMeta{ 44 | Name: chaosblade.DaemonsetPodName, 45 | Namespace: chaosblade.DaemonsetPodNamespace, 46 | Labels: chaosblade.DaemonsetPodLabels, 47 | OwnerReferences: references, 48 | }, 49 | Spec: createDaemonsetSpec(), 50 | } 51 | 52 | if err := rcb.client.Create(context.TODO(), daemonSet); err != nil { 53 | if apierrors.IsAlreadyExists(err) { 54 | logrus.Info("chaosblade tool exits, skip to deploy") 55 | return nil 56 | } 57 | return err 58 | } 59 | return nil 60 | } 61 | 62 | func createOwnerReferences(rcb *ReconcileChaosBlade) ([]metav1.OwnerReference, error) { 63 | // get chaosblade operator deployment object 64 | // Using a unstructured object. 65 | u := &unstructured.Unstructured{} 66 | u.SetGroupVersionKind(schema.GroupVersionKind{ 67 | Group: "apps", 68 | Kind: "Deployment", 69 | Version: "v1", 70 | }) 71 | namespace, err := k8sutil.GetOperatorNamespace() 72 | if err != nil { 73 | return nil, err 74 | } 75 | err = rcb.client.Get(context.TODO(), types.NamespacedName{ 76 | Namespace: namespace, 77 | Name: "chaosblade-operator", 78 | }, u) 79 | if err != nil { 80 | logrus.WithError(err).Error("cannot get chaosblade-operator deployment from apps/v1") 81 | return nil, err 82 | } 83 | trueVar := true 84 | return []metav1.OwnerReference{ 85 | { 86 | APIVersion: u.GetAPIVersion(), 87 | Kind: u.GetKind(), 88 | Name: u.GetName(), 89 | UID: u.GetUID(), 90 | Controller: &trueVar, 91 | }, 92 | }, nil 93 | } 94 | 95 | // createDaemonsetSpec 96 | func createDaemonsetSpec() appsv1.DaemonSetSpec { 97 | return appsv1.DaemonSetSpec{ 98 | Selector: &metav1.LabelSelector{MatchLabels: chaosblade.DaemonsetPodLabels}, 99 | Template: createPodTemplateSpec(), 100 | MinReadySeconds: 5, 101 | UpdateStrategy: appsv1.DaemonSetUpdateStrategy{Type: appsv1.RollingUpdateDaemonSetStrategyType}, 102 | } 103 | } 104 | 105 | // createPodTemplateSpec 106 | func createPodTemplateSpec() corev1.PodTemplateSpec { 107 | return corev1.PodTemplateSpec{ 108 | ObjectMeta: metav1.ObjectMeta{ 109 | Name: chaosblade.DaemonsetPodName, 110 | Labels: chaosblade.DaemonsetPodLabels, 111 | }, 112 | Spec: createPodSpec(), 113 | } 114 | } 115 | 116 | func createPodSpec() corev1.PodSpec { 117 | pathType := corev1.HostPathFileOrCreate 118 | periodSeconds := int64(30) 119 | return corev1.PodSpec{ 120 | Containers: []corev1.Container{createContainer()}, 121 | Affinity: createAffinity(), 122 | DNSPolicy: corev1.DNSClusterFirstWithHostNet, 123 | HostNetwork: true, 124 | HostPID: true, 125 | Tolerations: []corev1.Toleration{{Effect: corev1.TaintEffectNoSchedule, Operator: corev1.TolerationOpExists}}, 126 | TerminationGracePeriodSeconds: &periodSeconds, 127 | SchedulerName: corev1.DefaultSchedulerName, 128 | RestartPolicy: corev1.RestartPolicyAlways, 129 | Volumes: []corev1.Volume{ 130 | { 131 | Name: "docker-socket", 132 | VolumeSource: corev1.VolumeSource{HostPath: &corev1.HostPathVolumeSource{Path: "/var/run/docker.sock"}}, 133 | }, 134 | { 135 | Name: "chaosblade-db-volume", 136 | VolumeSource: corev1.VolumeSource{HostPath: &corev1.HostPathVolumeSource{ 137 | Path: "/var/run/chaosblade.dat", 138 | Type: &pathType, 139 | }}, 140 | }, 141 | { 142 | Name: "hosts", 143 | VolumeSource: corev1.VolumeSource{HostPath: &corev1.HostPathVolumeSource{Path: "/etc/hosts"}}, 144 | }, 145 | }, 146 | } 147 | } 148 | 149 | func createAffinity() *corev1.Affinity { 150 | return &corev1.Affinity{ 151 | NodeAffinity: &corev1.NodeAffinity{ 152 | RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ 153 | NodeSelectorTerms: []corev1.NodeSelectorTerm{{ 154 | MatchExpressions: []corev1.NodeSelectorRequirement{{ 155 | Key: "type", 156 | Operator: corev1.NodeSelectorOpNotIn, 157 | Values: []string{"virtual-kubelet"}, 158 | }}}, 159 | }, 160 | }, 161 | }, 162 | } 163 | } 164 | 165 | func createContainer() corev1.Container { 166 | trueVar := true 167 | return corev1.Container{ 168 | Name: chaosblade.DaemonsetPodName, 169 | Image: fmt.Sprintf("%s:%s", chaosblade.Constant.ImageRepoFunc(), chaosblade.Version), 170 | ImagePullPolicy: corev1.PullPolicy(chaosblade.PullPolicy), 171 | VolumeMounts: []corev1.VolumeMount{ 172 | {Name: "docker-socket", MountPath: "/var/run/docker.sock"}, 173 | {Name: "chaosblade-db-volume", MountPath: "/opt/chaosblade/chaosblade.dat"}, 174 | {Name: "hosts", MountPath: "/etc/hosts"}, 175 | }, 176 | SecurityContext: &corev1.SecurityContext{Privileged: &trueVar}, 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /pkg/controller/chaosblade/predicate.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package chaosblade 18 | 19 | import ( 20 | "encoding/json" 21 | "reflect" 22 | 23 | "github.com/sirupsen/logrus" 24 | "sigs.k8s.io/controller-runtime/pkg/event" 25 | 26 | "github.com/chaosblade-io/chaosblade-operator/pkg/apis/chaosblade/v1alpha1" 27 | ) 28 | 29 | type SpecUpdatedPredicateForRunningPhase struct { 30 | } 31 | 32 | func (sup *SpecUpdatedPredicateForRunningPhase) Create(e event.CreateEvent) bool { 33 | if e.Object == nil { 34 | return false 35 | } 36 | obj, ok := e.Object.(*v1alpha1.ChaosBlade) 37 | if !ok { 38 | return false 39 | } 40 | logrus.Infof("trigger create event, name: %s", obj.Name) 41 | logrus.Debugf("creating obj: %+v", obj) 42 | if obj.GetDeletionTimestamp() != nil { 43 | logrus.Infof("unexpected phase for cb creating, name: %s, phase: %s", obj.Name, obj.Status.Phase) 44 | return false 45 | } 46 | if obj.Status.Phase == v1alpha1.ClusterPhaseInitial { 47 | return true 48 | } 49 | logrus.Infof("unexpected phase for cb creating, name: %s, phase: %s", obj.Name, obj.Status.Phase) 50 | return false 51 | } 52 | 53 | func (*SpecUpdatedPredicateForRunningPhase) Delete(e event.DeleteEvent) bool { 54 | if e.Object == nil { 55 | return false 56 | } 57 | obj, ok := e.Object.(*v1alpha1.ChaosBlade) 58 | if !ok { 59 | return false 60 | } 61 | logrus.Infof("trigger delete event, name: %s", obj.Name) 62 | logrus.Debugf("deleting obj: %+v", obj) 63 | return contains(obj.GetFinalizers(), chaosbladeFinalizer) 64 | } 65 | 66 | func (*SpecUpdatedPredicateForRunningPhase) Update(e event.UpdateEvent) bool { 67 | if e.ObjectOld == nil { 68 | return false 69 | } 70 | oldObj, ok := e.ObjectOld.(*v1alpha1.ChaosBlade) 71 | if !ok { 72 | return false 73 | } 74 | logrus.Infof("trigger update event, name: %s", oldObj.Name) 75 | newObj, ok := e.ObjectNew.(*v1alpha1.ChaosBlade) 76 | if !ok { 77 | return false 78 | } 79 | logrus.Debugf("updating oldObj: %+v", oldObj) 80 | logrus.Debugf("updating newObj: %+v", newObj) 81 | if !reflect.DeepEqual(newObj.Spec, oldObj.Spec) { 82 | bytes, err := json.Marshal(oldObj.Spec.DeepCopy()) 83 | if err != nil { 84 | logrus.Warningf("marshal old spec failed, %+v", err) 85 | return false 86 | } 87 | newObj.SetAnnotations(map[string]string{"preSpec": string(bytes)}) 88 | return true 89 | } 90 | 91 | if newObj.Status.Phase == v1alpha1.ClusterPhaseInitial { 92 | return true 93 | } 94 | // delete Error chaosblade 95 | if oldObj.GetDeletionTimestamp() == nil && 96 | newObj.GetDeletionTimestamp() != nil { 97 | return true 98 | } 99 | if newObj.Status.Phase == v1alpha1.ClusterPhaseRunning || 100 | newObj.Status.Phase == v1alpha1.ClusterPhaseError || 101 | newObj.Status.Phase == v1alpha1.ClusterPhaseDestroying { 102 | return false 103 | } 104 | if newObj.Status.Phase != oldObj.Status.Phase { 105 | return true 106 | } 107 | if !reflect.DeepEqual(newObj.Status, oldObj.Status) { 108 | return true 109 | } 110 | if newObj.GetDeletionTimestamp() != nil { 111 | if contains(newObj.GetFinalizers(), chaosbladeFinalizer) { 112 | return true 113 | } 114 | logrus.Infof("cannot find the %s finalizer, so skip the update event", chaosbladeFinalizer) 115 | return false 116 | } 117 | logrus.Infof("spec not changed under %s phase, so skip the update event", newObj.Status.Phase) 118 | return false 119 | } 120 | 121 | func (*SpecUpdatedPredicateForRunningPhase) Generic(e event.GenericEvent) bool { 122 | return false 123 | } 124 | -------------------------------------------------------------------------------- /pkg/controller/controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package controller 18 | 19 | import ( 20 | "sigs.k8s.io/controller-runtime/pkg/manager" 21 | ) 22 | 23 | // AddToManagerFuncs is a list of functions to add all Controllers to the Manager 24 | var AddToManagerFuncs []func(manager.Manager) error 25 | 26 | // AddToManager adds all Controllers to the Manager 27 | func AddToManager(m manager.Manager) error { 28 | for _, f := range AddToManagerFuncs { 29 | if err := f(m); err != nil { 30 | return err 31 | } 32 | } 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /pkg/hookfs/client.go: -------------------------------------------------------------------------------- 1 | package hookfs 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net" 9 | "net/http" 10 | "time" 11 | 12 | "github.com/chaosblade-io/chaosblade-spec-go/util" 13 | "github.com/sirupsen/logrus" 14 | ) 15 | 16 | type ChaosBladeHookClient struct { 17 | client *http.Client 18 | addr string 19 | } 20 | 21 | func NewChabladeHookClient(addr string) *ChaosBladeHookClient { 22 | return &ChaosBladeHookClient{ 23 | addr: addr, 24 | client: &http.Client{ 25 | Timeout: 30 * time.Second, 26 | Transport: &http.Transport{ 27 | DialContext: (&net.Dialer{ 28 | Timeout: 5 * time.Second, 29 | }).DialContext, 30 | DisableKeepAlives: true, 31 | }, 32 | }, 33 | } 34 | } 35 | 36 | func (c *ChaosBladeHookClient) InjectFault(ctx context.Context, injectMsg *InjectMessage) error { 37 | url := "http://" + c.addr + InjectPath 38 | body, err := json.Marshal(injectMsg) 39 | if err != nil { 40 | return err 41 | } 42 | logrus.WithField("injectMsg", injectMsg).Infoln("Inject fault") 43 | result, err, code := util.PostCurl(url, body, "application/json") 44 | if err != nil { 45 | return err 46 | } 47 | logrus.WithField("injectMsg", injectMsg).Infof("Response is %s", result) 48 | if code != http.StatusOK { 49 | return fmt.Errorf(result) 50 | } 51 | return nil 52 | } 53 | 54 | func (c *ChaosBladeHookClient) Revoke(ctx context.Context) error { 55 | url := "http://" + c.addr + RecoverPath 56 | req, err := http.NewRequest("GET", url, nil) 57 | if err != nil { 58 | return err 59 | } 60 | req.Header.Set("Content-Type", "application/json") 61 | resp, err := c.client.Do(req) 62 | if err != nil { 63 | return err 64 | } 65 | defer resp.Body.Close() 66 | bytes, err := ioutil.ReadAll(resp.Body) 67 | if err != nil { 68 | return err 69 | } 70 | result := string(bytes) 71 | logrus.Infof("Revoke fault, response is %s", result) 72 | if resp.StatusCode != http.StatusOK { 73 | return fmt.Errorf(result) 74 | } 75 | return nil 76 | } 77 | -------------------------------------------------------------------------------- /pkg/hookfs/server.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2019 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package hookfs 18 | 19 | import ( 20 | "context" 21 | "encoding/json" 22 | "fmt" 23 | "net/http" 24 | "sync" 25 | 26 | "github.com/sirupsen/logrus" 27 | ) 28 | 29 | var ( 30 | injectFaultCache sync.Map 31 | ) 32 | 33 | func init() { 34 | injectFaultCache = sync.Map{} 35 | } 36 | 37 | type InjectMessage struct { 38 | Methods []string `json:"methods"` 39 | Path string `json:"path"` 40 | Delay uint32 `json:"delay"` 41 | Percent uint32 `json:"percent"` 42 | Random bool `json:"random"` 43 | Errno uint32 `json:"errno"` 44 | } 45 | 46 | type ChaosbladeHookServer struct { 47 | addr string 48 | } 49 | 50 | func NewChaosbladeHookServer(addr string) *ChaosbladeHookServer { 51 | return &ChaosbladeHookServer{ 52 | addr: addr, 53 | } 54 | } 55 | 56 | func (s *ChaosbladeHookServer) Start(stop <-chan struct{}) error { 57 | mux := http.NewServeMux() 58 | mux.HandleFunc(InjectPath, s.InjectHandler) 59 | mux.HandleFunc(RecoverPath, s.RecoverHandler) 60 | errCh := make(chan error) 61 | server := &http.Server{ 62 | Addr: s.addr, 63 | Handler: mux, 64 | } 65 | go func() { 66 | errCh <- server.ListenAndServe() 67 | }() 68 | for { 69 | select { 70 | case <-stop: 71 | return server.Shutdown(context.Background()) 72 | case err := <-errCh: 73 | if err != nil { 74 | return err 75 | } 76 | } 77 | } 78 | } 79 | 80 | func (s *ChaosbladeHookServer) InjectHandler(w http.ResponseWriter, r *http.Request) { 81 | var injectMsg InjectMessage 82 | if err := json.NewDecoder(r.Body).Decode(&injectMsg); err != nil { 83 | logrus.WithError(err).Errorf("Cannot Decode Request Message, %+v", r) 84 | http.Error(w, "Cannot Decode Request Message", http.StatusBadRequest) 85 | return 86 | } 87 | logrus.WithField("injectMsg", injectMsg).Infoln("Inject Fault") 88 | for _, method := range injectMsg.Methods { 89 | injectFaultCache.Store(method, &injectMsg) 90 | } 91 | fmt.Fprintf(w, "success") 92 | } 93 | func (s *ChaosbladeHookServer) RecoverHandler(w http.ResponseWriter, r *http.Request) { 94 | logrus.Infoln("recover all fault") 95 | for _, method := range defaultHookPoints { 96 | injectFaultCache.Delete(method) 97 | } 98 | fmt.Fprintf(w, "success") 99 | } 100 | -------------------------------------------------------------------------------- /pkg/hookfs/types.go: -------------------------------------------------------------------------------- 1 | package hookfs 2 | 3 | var defaultHookPoints = []string{ 4 | "read", 5 | "write", 6 | "mkdir", 7 | "rmdir", 8 | "opendir", 9 | "fsync", 10 | "flush", 11 | "release", 12 | "truncate", 13 | "getattr", 14 | "chown", 15 | "utimens", 16 | "allocate", 17 | "getlk", 18 | "setlk", 19 | "setlkw", 20 | "statfs", 21 | "readlink", 22 | "symlink", 23 | "create", 24 | "access", 25 | "link", 26 | "mknod", 27 | "rename", 28 | "unlink", 29 | "getxattr", 30 | "listxattr", 31 | "removexattr", 32 | "setxattr", 33 | } 34 | 35 | var ( 36 | InjectPath = "/inject" 37 | RecoverPath = "/recover" 38 | ) 39 | -------------------------------------------------------------------------------- /pkg/runtime/chaosblade/chaosblade.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package chaosblade 18 | 19 | import ( 20 | "github.com/spf13/pflag" 21 | 22 | "github.com/chaosblade-io/chaosblade-operator/version" 23 | ) 24 | 25 | var ( 26 | ImageRepository string 27 | Version string 28 | PullPolicy string 29 | DaemonsetEnable bool 30 | RemoveBladeInterval string 31 | DownloadUrl string 32 | ) 33 | 34 | const ( 35 | OperatorChaosBladePath = "/opt/chaosblade" 36 | OperatorChaosBladeBin = "/opt/chaosblade/bin" 37 | OperatorChaosBladeLib = "/opt/chaosblade/lib" 38 | OperatorChaosBladeYaml = "/opt/chaosblade/yaml" 39 | OperatorChaosBladeBlade = "/opt/chaosblade/blade" 40 | ) 41 | 42 | const DaemonsetPodName = "chaosblade-tool" 43 | const DefaultRemoveBladeInterval = "72h" 44 | 45 | var DaemonsetPodLabels = map[string]string{ 46 | "app": "chaosblade-tool", 47 | } 48 | 49 | // set in runtime 50 | var DaemonsetPodNamespace string 51 | var DaemonsetPodNames = map[string]string{} 52 | 53 | var Products = map[string]*ProductConstant{} 54 | 55 | var Constant *ProductConstant 56 | 57 | type ProductConstant struct { 58 | ImageRepoFunc func() string 59 | } 60 | 61 | var f *pflag.FlagSet 62 | 63 | func init() { 64 | f = pflag.NewFlagSet("chaosblade", pflag.ExitOnError) 65 | // chaosblade config 66 | f.StringVar(&Version, "chaosblade-version", version.Version, "Chaosblade tool version") 67 | f.StringVar(&ImageRepository, "chaosblade-image-repository", "chaosbladeio/chaosblade-tool", "Image repository of chaosblade tool") 68 | f.StringVar(&PullPolicy, "chaosblade-image-pull-policy", "IfNotPresent", "Pulling policy of chaosblade image, default value is IfNotPresent.") 69 | f.BoolVar(&DaemonsetEnable, "daemonset-enable", false, "Deploy chaosblade daemonset to resolve chaos experiment environment of network, default value is false.") 70 | f.StringVar(&RemoveBladeInterval, "remove-blade-interval", DefaultRemoveBladeInterval, "Periodically clean up blade state is destroying, default value is 24h.") 71 | f.StringVar(&DownloadUrl, "chaosblade-download-url", "", "The chaosblade downloaded address which works when the chaosblade is deployed in download mode.") 72 | f.StringVar(&DaemonsetPodNamespace, "chaosblade-namespace", "chaosblade", "The chaosblade deployment namespace") 73 | } 74 | 75 | func FlagSet() *pflag.FlagSet { 76 | return f 77 | } 78 | -------------------------------------------------------------------------------- /pkg/runtime/product/aliyun/aliyun.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package aliyun 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/spf13/pflag" 23 | 24 | "github.com/chaosblade-io/chaosblade-operator/pkg/runtime/chaosblade" 25 | ) 26 | 27 | var ( 28 | // flag 29 | RegionId string 30 | Environment string 31 | ) 32 | 33 | const ( 34 | AHAS = "ahas" 35 | prodEnv = "prod" 36 | publicRegion = "cn-public" 37 | ) 38 | 39 | var f *pflag.FlagSet 40 | 41 | func init() { 42 | f = pflag.NewFlagSet("aliyun", pflag.ExitOnError) 43 | f.StringVar(&RegionId, "aliyun-region-id", "", "Region id for cloud provider") 44 | f.StringVar(&Environment, "aliyun-environment", "", "Environment for cloud provider") 45 | 46 | chaosblade.Products[AHAS] = &chaosblade.ProductConstant{ 47 | ImageRepoFunc: ImageRepoForAliyun, 48 | } 49 | } 50 | 51 | var ImageRepoForAliyun = func() string { 52 | if RegionId == publicRegion { 53 | if Environment == prodEnv { 54 | return fmt.Sprintf("registry.cn-hangzhou.aliyuncs.com/ahascr-public/chaosblade-tool") 55 | } 56 | return fmt.Sprintf("registry.cn-hangzhou.aliyuncs.com/ahas-public/chaosblade-tool") 57 | } 58 | if Environment == prodEnv { 59 | return fmt.Sprintf("registry-vpc.%s.aliyuncs.com/ahascr/chaosblade-tool", RegionId) 60 | } 61 | return fmt.Sprintf("registry-vpc.%s.aliyuncs.com/ahas/chaosblade-tool", RegionId) 62 | } 63 | 64 | func FlagSet() *pflag.FlagSet { 65 | return f 66 | } 67 | -------------------------------------------------------------------------------- /pkg/runtime/product/community/community.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package community 18 | 19 | import ( 20 | "github.com/chaosblade-io/chaosblade-operator/pkg/runtime/chaosblade" 21 | ) 22 | 23 | const Community = "community" 24 | 25 | func init() { 26 | chaosblade.Products[Community] = &chaosblade.ProductConstant{ 27 | ImageRepoFunc: ImageRepoForCommunity, 28 | } 29 | } 30 | 31 | var ImageRepoForCommunity = func() string { 32 | return chaosblade.ImageRepository 33 | } 34 | -------------------------------------------------------------------------------- /pkg/runtime/runtime.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package runtime 18 | 19 | import ( 20 | "github.com/spf13/pflag" 21 | 22 | "github.com/chaosblade-io/chaosblade-operator/pkg/runtime/chaosblade" 23 | "github.com/chaosblade-io/chaosblade-operator/pkg/runtime/product/aliyun" 24 | _ "github.com/chaosblade-io/chaosblade-operator/pkg/runtime/product/community" 25 | "github.com/chaosblade-io/chaosblade-operator/version" 26 | ) 27 | 28 | var flagSet *pflag.FlagSet 29 | var LogLevel string 30 | var MaxConcurrentReconciles int 31 | var QPS float32 32 | 33 | func init() { 34 | flagSet = pflag.NewFlagSet("operator", pflag.ExitOnError) 35 | flagSet.StringVar(&LogLevel, "log-level", "info", "Log level, such as panic|fatal|error|warn|info|debug|trace") 36 | flagSet.IntVar(&MaxConcurrentReconciles, "reconcile-count", 20, "Max concurrent reconciles count, default value is 20") 37 | flagSet.Float32Var(&QPS, "qps", 20, "qps of kubernetes client") 38 | 39 | flagSet.AddFlagSet(aliyun.FlagSet()) 40 | flagSet.AddFlagSet(chaosblade.FlagSet()) 41 | 42 | initRuntimeData() 43 | } 44 | 45 | func initRuntimeData() { 46 | chaosblade.Constant = chaosblade.Products[version.Product] 47 | } 48 | 49 | func FlagSet() *pflag.FlagSet { 50 | return flagSet 51 | } 52 | -------------------------------------------------------------------------------- /pkg/webhook/pod/mutator.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2019 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package pod 18 | 19 | import ( 20 | "context" 21 | "encoding/json" 22 | "fmt" 23 | "net/http" 24 | "path" 25 | 26 | "github.com/sirupsen/logrus" 27 | corev1 "k8s.io/api/core/v1" 28 | "k8s.io/apimachinery/pkg/api/resource" 29 | "sigs.k8s.io/controller-runtime/pkg/client" 30 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 31 | 32 | "github.com/chaosblade-io/chaosblade-operator/pkg/runtime/chaosblade" 33 | "github.com/chaosblade-io/chaosblade-operator/version" 34 | ) 35 | 36 | var ( 37 | FuseServerPort int32 38 | SidecarImage string 39 | ) 40 | 41 | const ( 42 | SidecarName = "chaosblade-fuse" 43 | FuseServerPortName = "fuse-port" 44 | ) 45 | 46 | // PodMutator set default values for pod 47 | type Mutator struct { 48 | client client.Client 49 | decoder *admission.Decoder 50 | } 51 | 52 | func (v *Mutator) Handle(ctx context.Context, req admission.Request) admission.Response { 53 | pod := &corev1.Pod{} 54 | err := v.decoder.Decode(req, pod) 55 | if err != nil { 56 | return admission.Errored(http.StatusBadRequest, err) 57 | } 58 | patchPod := pod.DeepCopy() 59 | err = v.mutatePodsFn(patchPod) 60 | if err != nil { 61 | logrus.WithError(err).Errorln("mutate pod failed") 62 | return admission.Errored(http.StatusInternalServerError, err) 63 | } 64 | originalBytes, err := json.Marshal(pod) 65 | if err != nil { 66 | logrus.WithError(err).Errorln("Marshal original pod err") 67 | return admission.Allowed("") 68 | } 69 | expectedBytes, err := json.Marshal(patchPod) 70 | if err != nil { 71 | logrus.WithError(err).Errorln("Marshal patched pod err") 72 | } 73 | return admission.PatchResponseFromRaw(originalBytes, expectedBytes) 74 | } 75 | 76 | // PodMutator set default values for pod 77 | func (v *Mutator) mutatePodsFn(pod *corev1.Pod) error { 78 | if pod.Annotations == nil { 79 | return nil 80 | } 81 | injectVolumeName, ok := pod.Annotations["chaosblade/inject-volume"] 82 | if !ok { 83 | logrus.WithField("name", pod.Name).Infoln("pod has no chaosblade/inject-volume annotation") 84 | return nil 85 | } 86 | injectSubPath, ok := pod.Annotations["chaosblade/inject-volume-subpath"] 87 | if !ok { 88 | logrus.WithField("name", pod.Name).Infoln("pod has no chaosblade/inject-volume annotation") 89 | return nil 90 | } 91 | 92 | for _, container := range pod.Spec.Containers { 93 | if container.Name == SidecarName { 94 | logrus.WithField("name", pod.Name).Infoln("sidecar has been injected") 95 | return nil 96 | } 97 | } 98 | 99 | var targetVolumeMount corev1.VolumeMount 100 | //inject sidecar for the first container 101 | for _, volumeMount := range pod.Spec.Containers[0].VolumeMounts { 102 | if volumeMount.Name == injectVolumeName { 103 | if volumeMount.MountPropagation == nil { 104 | return fmt.Errorf("target volume mount propagation must be HostToContainer or Bidirectional") 105 | } 106 | if *(volumeMount.MountPropagation) != corev1.MountPropagationHostToContainer && 107 | *(volumeMount.MountPropagation) != corev1.MountPropagationBidirectional { 108 | return fmt.Errorf("target volume mount propagation is not support") 109 | } 110 | targetVolumeMount = volumeMount 111 | mountPropagation := corev1.MountPropagationBidirectional 112 | targetVolumeMount.MountPropagation = &mountPropagation 113 | } 114 | } 115 | 116 | if targetVolumeMount.Name == "" { 117 | return fmt.Errorf("pod has no volume mount %s", injectVolumeName) 118 | } 119 | 120 | privileged := true 121 | runAsUser := int64(0) //root 122 | mountPoint := path.Join(targetVolumeMount.MountPath, injectSubPath) 123 | original := path.Join(targetVolumeMount.MountPath, fmt.Sprintf("fuse-%s", injectSubPath)) 124 | logrus.WithFields(logrus.Fields{ 125 | "mountPoint": mountPoint, 126 | "mountPath": targetVolumeMount.MountPath, 127 | "podName": pod.Name, 128 | }).Infof("Get matched pod") 129 | if mountPoint == targetVolumeMount.MountPath { 130 | original = path.Join(path.Dir(targetVolumeMount.MountPath), 131 | fmt.Sprintf("fuse-%s", path.Base(targetVolumeMount.MountPath))) 132 | } 133 | sidecar := corev1.Container{ 134 | Name: SidecarName, 135 | Image: GetSidecarImage(), 136 | ImagePullPolicy: corev1.PullAlways, 137 | Command: []string{ 138 | "/opt/chaosblade/bin/chaos_fuse", 139 | }, 140 | 141 | Args: []string{ 142 | fmt.Sprintf("--address=:%d", FuseServerPort), 143 | fmt.Sprintf("--mountpoint=%s", mountPoint), 144 | fmt.Sprintf("--original=%s", original), 145 | }, 146 | Resources: corev1.ResourceRequirements{ 147 | Requests: corev1.ResourceList{ 148 | corev1.ResourceCPU: resource.MustParse("100m"), 149 | corev1.ResourceMemory: resource.MustParse("50Mi"), 150 | }, 151 | Limits: corev1.ResourceList{ 152 | corev1.ResourceCPU: resource.MustParse("100m"), 153 | corev1.ResourceMemory: resource.MustParse("50Mi"), 154 | }, 155 | }, 156 | Ports: []corev1.ContainerPort{ 157 | { 158 | Name: FuseServerPortName, 159 | ContainerPort: FuseServerPort, 160 | }, 161 | }, 162 | SecurityContext: &corev1.SecurityContext{ 163 | Privileged: &privileged, 164 | RunAsUser: &runAsUser, 165 | }, 166 | VolumeMounts: []corev1.VolumeMount{ 167 | targetVolumeMount, 168 | }, 169 | } 170 | containers := []corev1.Container{} 171 | containers = append(containers, sidecar, pod.Spec.Containers[0]) 172 | pod.Spec.Containers = containers 173 | return nil 174 | } 175 | 176 | // InjectClient injects the client. 177 | func (v *Mutator) InjectClient(c client.Client) error { 178 | v.client = c 179 | return nil 180 | } 181 | 182 | // InjectDecoder injects the decoder. 183 | func (v *Mutator) InjectDecoder(d *admission.Decoder) error { 184 | v.decoder = d 185 | return nil 186 | } 187 | 188 | func GetSidecarImage() string { 189 | if SidecarImage != "" { 190 | return SidecarImage 191 | } 192 | return fmt.Sprintf("%s:%s", chaosblade.Constant.ImageRepoFunc(), version.Version) 193 | } 194 | -------------------------------------------------------------------------------- /pkg/webhook/pod/mutator_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2019 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package pod 18 | 19 | import ( 20 | "fmt" 21 | "testing" 22 | 23 | v1 "k8s.io/api/core/v1" 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | ) 26 | 27 | func Test_mutatePodsFn(t *testing.T) { 28 | bidirectional := v1.MountPropagationBidirectional 29 | //hostToContainer := v1.MountPropagationHostToContainer 30 | None := v1.MountPropagationNone 31 | tests := []struct { 32 | pod *v1.Pod 33 | err error 34 | }{ 35 | { 36 | pod: &v1.Pod{ 37 | ObjectMeta: metav1.ObjectMeta{ 38 | Name: "pod-0", 39 | Annotations: map[string]string{ 40 | "chaosblade/inject-volume": "fuse-test", 41 | "chaosblade/inject-volume-subpath": "data", 42 | }, 43 | }, 44 | Spec: v1.PodSpec{ 45 | Containers: []v1.Container{ 46 | { 47 | Name: "test-0", 48 | Image: "test-0", 49 | VolumeMounts: []v1.VolumeMount{ 50 | { 51 | Name: "fuse-test", 52 | MountPath: "/data", 53 | MountPropagation: &bidirectional, 54 | }, 55 | }, 56 | }, 57 | }, 58 | }, 59 | }, 60 | err: nil, 61 | }, 62 | { 63 | pod: &v1.Pod{ 64 | ObjectMeta: metav1.ObjectMeta{ 65 | Name: "pod-1", 66 | Annotations: map[string]string{ 67 | "chaosblade/inject-volume": "fuse-test", 68 | "chaosblade/inject-volume-subpath": "/data", 69 | }, 70 | }, 71 | Spec: v1.PodSpec{ 72 | Containers: []v1.Container{ 73 | { 74 | Name: "test-1", 75 | Image: "test-1", 76 | VolumeMounts: []v1.VolumeMount{ 77 | { 78 | Name: "data", 79 | MountPath: "/data", 80 | MountPropagation: &bidirectional, 81 | }, 82 | }, 83 | }, 84 | }, 85 | }, 86 | }, 87 | err: fmt.Errorf("pod has no volume mount fuse-test"), 88 | }, 89 | { 90 | pod: &v1.Pod{ 91 | ObjectMeta: metav1.ObjectMeta{ 92 | Name: "pod-2", 93 | Annotations: map[string]string{ 94 | "chaosblade/inject-volume": "data", 95 | "chaosblade/inject-volume-subpath": "/data", 96 | }, 97 | }, 98 | Spec: v1.PodSpec{ 99 | Containers: []v1.Container{ 100 | { 101 | Name: "test-2", 102 | Image: "test-2", 103 | VolumeMounts: []v1.VolumeMount{ 104 | { 105 | Name: "data", 106 | MountPath: "/data", 107 | }, 108 | }, 109 | }, 110 | }, 111 | }, 112 | }, 113 | err: fmt.Errorf("target volume mount propagation must be HostToContainer or Bidirectional"), 114 | }, 115 | { 116 | pod: &v1.Pod{ 117 | ObjectMeta: metav1.ObjectMeta{ 118 | Name: "pod-3", 119 | Annotations: map[string]string{ 120 | "chaosblade/inject-volume": "data", 121 | "chaosblade/inject-volume-subpath": "/data", 122 | }, 123 | }, 124 | Spec: v1.PodSpec{ 125 | Containers: []v1.Container{ 126 | { 127 | Name: "test-3", 128 | Image: "test-3", 129 | VolumeMounts: []v1.VolumeMount{ 130 | { 131 | Name: "data", 132 | MountPath: "/data", 133 | MountPropagation: &None, 134 | }, 135 | }, 136 | }, 137 | }, 138 | }, 139 | }, 140 | err: fmt.Errorf("target volume mount propagation is not support"), 141 | }, 142 | } 143 | 144 | mutator := &Mutator{} 145 | for _, test := range tests { 146 | err := mutator.mutatePodsFn(test.pod) 147 | if err != nil && err.Error() != test.err.Error() { 148 | t.Errorf("unexpected result %v, expected result: %v", err, test.err) 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /pkg/webhook/webhook.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package webhook 18 | 19 | import ( 20 | "github.com/spf13/pflag" 21 | 22 | mutator "github.com/chaosblade-io/chaosblade-operator/pkg/webhook/pod" 23 | ) 24 | 25 | var ( 26 | Port int 27 | Enable bool 28 | ) 29 | 30 | var f *pflag.FlagSet 31 | 32 | func init() { 33 | f = pflag.NewFlagSet("webhook", pflag.ExitOnError) 34 | f.StringVar(&mutator.SidecarImage, "fuse-sidecar-image", "", "Fuse sidecar image") 35 | f.Int32Var(&mutator.FuseServerPort, "fuse-server-port", 65534, "Fuse server port") 36 | 37 | f.IntVar(&Port, "webhook-port", 9443, "The port on which to serve HTTPS.") 38 | f.BoolVar(&Enable, "webhook-enable", false, "Whether to enable webhook") 39 | } 40 | 41 | func FlagSet() *pflag.FlagSet { 42 | return f 43 | } 44 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 1999-2020 Alibaba Group Holding Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package version 18 | 19 | import ( 20 | "strconv" 21 | "strings" 22 | ) 23 | 24 | const criVersion = "1.5.0" 25 | var ( 26 | Version = "unknown" 27 | Product = "community" 28 | 29 | // Version#Product 30 | CombinedVersion = "" 31 | Delimiter = "," 32 | ) 33 | 34 | func init() { 35 | if CombinedVersion != "" { 36 | fields := strings.Split(CombinedVersion, Delimiter) 37 | if len(fields) > 0 { 38 | Version = fields[0] 39 | } 40 | if len(fields) > 1 { 41 | Product = fields[1] 42 | } 43 | } 44 | } 45 | 46 | func CheckVerisonHaveCriCommand() bool { 47 | verisonA := strings.Split(Version, ".") 48 | criA := strings.Split(criVersion, ".") 49 | if len(verisonA) != 3 { 50 | return false 51 | } 52 | 53 | for k, v := range verisonA { 54 | vi, err := strconv.Atoi(v) 55 | if err != nil { 56 | return false 57 | } 58 | 59 | ci, _ := strconv.Atoi(criA[k]) 60 | 61 | if ci == vi { 62 | continue 63 | } 64 | 65 | if vi < ci { 66 | return false 67 | } 68 | return true 69 | } 70 | return true 71 | } --------------------------------------------------------------------------------