Compare commits

..

No commits in common. "main" and "v1.0.1" have entirely different histories.
main ... v1.0.1

116 changed files with 2328 additions and 10035 deletions

View File

@ -4,51 +4,48 @@ on:
branches:
- main
- release-*
paths:
- .github/workflows/ci.yml
- Makefile
- go.*
- '**.go'
jobs:
pull:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
arch: [amd64, arm64]
job:
- verify
- build
- unit-test
- integration-test
runs-on: ${{ fromJson('{"amd64":"ubuntu-latest", "arm64":["self-hosted", "Linux", "ARM64"]}')[matrix.arch] }}
steps:
- uses: actions/checkout@v4
- name: Set up Go 1.16
uses: actions/setup-go@v1
with:
go-version: 1.16.2
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.sha }}
path: go/src/github.com/${{ github.repository }}
- uses: actions/setup-go@v5
with:
go-version: 1.20.x
- name: ${{ matrix.job }}
run: |
# use sh function
# workaround for https://github.com/actions/setup-go/issues/14
export GOPATH=${GITHUB_WORKSPACE}/go
export PATH=$PATH:$GOPATH/bin
make groupimports || echo 0
#use sh function
if [[ "$job" == "verify" ]]; then
# preload go modules before goimports
go mod download -x
make check
make groupimports || echo 0
echo "Please make check before creating a PR"
git diff --quiet -- . || (git diff | cat && false)
elif [[ "$job" == "build" ]]; then
make build
elif [[ "$job" == "unit-test" ]]; then
make unit-test
make test
elif [[ "$job" == "integration-test" ]]; then
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
sudo apt-get update
sudo apt-get install -y stress-ng
make integration-test
else

View File

@ -1,26 +0,0 @@
name: ci
on:
pull_request:
branches:
- main
- release-*
paths-ignore:
- .github/workflows/ci.yml
- Makefile
- go.*
- "**.go"
jobs:
pull:
strategy:
matrix:
arch: [amd64, arm64]
job:
- verify
- build
- unit-test
- integration-test
runs-on: ${{ fromJson('{"amd64":"ubuntu-latest", "arm64":["self-hosted", "Linux", "ARM64"]}')[matrix.arch] }}
steps:
- run: echo "Not required to run pull jobs."

View File

@ -7,35 +7,60 @@ on:
jobs:
run:
name: Upload
strategy:
fail-fast: false
matrix:
arch: [amd64, arm64]
runs-on: ${{ fromJson('{"amd64":"ubuntu-latest", "arm64":["self-hosted", "Linux", "ARM64"]}')[matrix.arch] }}
container: ${{ fromJson('{"amd64":"docker.io/rockylinux:8", "arm64":"docker.io/rockylinux:8"}')[matrix.arch] }}
runs-on: ubuntu-latest
# glibc version 2.17
container: docker.io/centos:7.2.1511
steps:
- uses: actions/checkout@v4
- name: Set up Go 1.16
uses: actions/setup-go@v1
with:
fetch-depth: 0
- uses: actions/setup-go@v5
with:
go-version: 1.20.x
go-version: 1.16.2
id: go
- name: Prepare tools
- name: Prepare build environment
run: |
dnf install -y make gcc python3
# actions/checkout require git v2.X
yum -y install https://packages.endpoint.com/rhel/7/os/x86_64/endpoint-repo-1.7-1.x86_64.rpm
yum install -y gcc
yum install -y make
yum install -y binutils
yum install -y git
- name: Build binary and related tools
run: make build
- uses: actions/checkout@master
with:
# Must use at least depth 2!
fetch-depth: 2
- name: Setup python3
run: |
yum install -y python3
alias python=python3
- name: Configure awscli
run: |
pip3 install awscli
printf "%s\n" ${{ secrets.AWS_ACCESS_KEY }} ${{ secrets.AWS_SECRET_KEY }} ${{ secrets.AWS_REGION }} "json" | aws configure
# TODO: release on github package / release
- name: Build binary
run: make build
- name: Upload files
run: |
mv bin chaosd-latest-linux-${{ matrix.arch }}
tar czvf chaosd-latest-linux-${{ matrix.arch }}.tar.gz chaosd-latest-linux-${{ matrix.arch }}
aws s3 cp chaosd-latest-linux-${{ matrix.arch }}.tar.gz ${{ secrets.AWS_BUCKET_NAME }}/chaosd-latest-linux-${{ matrix.arch }}.tar.gz
# download tools
curl -fsSL -o byteman.tar.gz https://mirrors.chaos-mesh.org/latest/byteman.tar.gz
curl -fsSL -o stress-ng https://mirrors.chaos-mesh.org/latest/stress-ng
tar zxvf byteman.tar.gz
chmod +x ./stress-ng
# prepare package
mkdir chaosd-latest-linux-amd64
mkdir chaosd-latest-linux-amd64/tools
mv bin/chaosd chaosd-latest-linux-amd64/
mv bin/PortOccupyTool chaosd-latest-linux-amd64/tools/
mv byteman chaosd-latest-linux-amd64/tools/
mv stress-ng chaosd-latest-linux-amd64/tools/
# upload package
tar czvf chaosd-latest-linux-amd64.tar.gz chaosd-latest-linux-amd64
aws s3 cp chaosd-latest-linux-amd64.tar.gz ${{ secrets.AWS_BUCKET_NAME }}/chaosd-latest-linux-amd64.tar.gz

View File

@ -6,35 +6,62 @@ on:
jobs:
run:
name: Upload
strategy:
fail-fast: false
matrix:
arch: [amd64, arm64]
runs-on: ${{ fromJson('{"amd64":"ubuntu-latest", "arm64":["self-hosted", "Linux", "ARM64"]}')[matrix.arch] }}
container: ${{ fromJson('{"amd64":"docker.io/rockylinux:8", "arm64":"docker.io/rockylinux:8"}')[matrix.arch] }}
runs-on: ubuntu-latest
# glibc version 2.17
container: docker.io/centos:7.2.1511
steps:
- uses: actions/checkout@v4
- name: Set up Go 1.16
uses: actions/setup-go@v1
with:
fetch-depth: 0
- uses: actions/setup-go@v5
with:
go-version: 1.20.x
go-version: 1.16.2
id: go
- name: Prepare tools
- name: Prepare build environment
run: |
dnf install -y make gcc python3
# actions/checkout require git v2.X
yum -y install https://packages.endpoint.com/rhel/7/os/x86_64/endpoint-repo-1.7-1.x86_64.rpm
yum install -y gcc
yum install -y make
yum install -y binutils
yum install -y git
- name: Build binary and related tools
run: make build
- uses: actions/checkout@master
with:
# Must use at least depth 2!
fetch-depth: 2
- name: Setup python3
run: |
yum install -y python3
alias python=python3
- name: Configure awscli
run: |
pip3 install awscli
printf "%s\n" ${{ secrets.AWS_ACCESS_KEY }} ${{ secrets.AWS_SECRET_KEY }} ${{ secrets.AWS_REGION }} "json" | aws configure
- name: Build binary
run: make build
- name: Upload files
run: |
GIT_TAG=${GITHUB_REF##*/}
mv bin chaosd-${GIT_TAG}-linux-${{ matrix.arch }}
tar czvf chaosd-${GIT_TAG}-linux-${{ matrix.arch }}.tar.gz chaosd-${GIT_TAG}-linux-${{ matrix.arch }}
aws s3 cp chaosd-${GIT_TAG}-linux-${{ matrix.arch }}.tar.gz ${{ secrets.AWS_BUCKET_NAME }}/chaosd-${GIT_TAG}-linux-${{ matrix.arch }}.tar.gz
# download tools
curl -fsSL -o byteman.tar.gz https://mirrors.chaos-mesh.org/latest/byteman.tar.gz
curl -fsSL -o stress-ng https://mirrors.chaos-mesh.org/latest/stress-ng
tar zxvf byteman.tar.gz
chmod +x ./stress-ng
# prepare package
mkdir chaosd-${GIT_TAG}-linux-amd64
mkdir chaosd-${GIT_TAG}-linux-amd64/tools
mv bin/chaosd chaosd-${GIT_TAG}-linux-amd64/
mv bin/PortOccupyTool chaosd-${GIT_TAG}-linux-amd64/tools/
mv byteman chaosd-${GIT_TAG}-linux-amd64/tools/
mv stress-ng chaosd-${GIT_TAG}-linux-amd64/tools/
# upload package
tar czvf chaosd-${GIT_TAG}-linux-amd64.tar.gz chaosd-${GIT_TAG}-linux-amd64
aws s3 cp chaosd-${GIT_TAG}-linux-amd64.tar.gz ${{ secrets.AWS_BUCKET_NAME }}/chaosd-${GIT_TAG}-linux-amd64.tar.gz

11
.gitignore vendored
View File

@ -15,15 +15,6 @@
# Dependency directories (remove the comment below to include it)
vendor/
.vscode/
.idea/
*.iml
*.swp
*.log
*.fail.go
.DS_Store
.idea
bin/
test/integration_test/**/*.*
!test/integration_test/**/*.sh

View File

@ -10,7 +10,6 @@ GO := $(GOENV) go
CGO := $(CGOENV) go
GOTEST := TEST_USE_EXISTING_CLUSTER=false NO_PROXY="${NO_PROXY},testhost" go test
SHELL := /usr/bin/env bash
BYTEMAN_DIR := byteman-chaos-mesh-download-v4.0.20-0.12
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))
@ -30,25 +29,11 @@ endif
PACKAGE_LIST := go list ./... | grep -vE "chaos-daemon/test|pkg/ptrace|zz_generated|vendor"
PACKAGE_DIRECTORIES := $(PACKAGE_LIST) | sed 's|github.com/chaos-mesh/chaosd/||'
UNAME_M := $(shell uname -m)
ifeq ($(UNAME_M),x86_64)
ARCH = x86_64
endif
ifeq ($(UNAME_M),amd64)
ARCH = x86_64
endif
ifeq ($(UNAME_M),aarch64)
ARCH = aarch64
endif
ifeq ($(UNAME_M),arm64)
ARCH = aarch64
endif
$(GOBIN)/revive:
$(GO) install github.com/mgechev/revive@v1.0.2-0.20200225072153-6219ca02fffb
$(GO) get github.com/mgechev/revive@v1.0.2-0.20200225072153-6219ca02fffb
$(GOBIN)/goimports:
$(GO) install golang.org/x/tools/cmd/goimports@v0.1.1
$(GO) get golang.org/x/tools/cmd/goimports@v0.0.0-20200309202150-20ab64c0d93f
build: binary
@ -76,29 +61,8 @@ endif
chaosd:
$(CGOENV) go build -ldflags '$(LDFLAGS)' -tags "${BUILD_TAGS}" -o bin/chaosd ./cmd/main.go
chaos-tools:
$(CGOENV) go build -o bin/tools/PortOccupyTool tools/PortOccupyTool.go
$(CGOENV) go build -o bin/tools/FileTool tools/file/*.go
ifeq (,$(wildcard bin/tools/stress-ng))
curl -fsSL -o ./bin/tools/stress-ng https://github.com/chaos-mesh/stress-ng/releases/download/v0.14.02/stress-ng-${ARCH}
chmod +x ./bin/tools/stress-ng
endif
ifeq (,$(wildcard bin/tools/byteman))
curl -fsSL -o ${BYTEMAN_DIR}.tar.gz https://mirrors.chaos-mesh.org/${BYTEMAN_DIR}.tar.gz
tar zxvf ${BYTEMAN_DIR}.tar.gz
mv ${BYTEMAN_DIR} ./bin/tools/byteman
endif
ifeq (,$(wildcard bin/tools/memStress))
curl -fsSL -o memStress_v0.3-${ARCH}-linux-gnu.tar.gz https://github.com/chaos-mesh/memStress/releases/download/v0.3/memStress_v0.3-${ARCH}-linux-gnu.tar.gz
tar zxvf memStress_v0.3-${ARCH}-linux-gnu.tar.gz
mv memStress ./bin/tools/memStress
endif
ifeq (,$(wildcard bin/tools/tproxy))
curl -fsSL -o tproxy-${ARCH}.tar.gz https://github.com/chaos-mesh/chaos-tproxy/releases/download/v0.5.4/tproxy-${ARCH}.tar.gz
tar zxvf tproxy-${ARCH}.tar.gz
mv tproxy ./bin/tools/tproxy
endif
$(CGOENV) go build -o bin/PortOccupyTool tools/PortOccupyTool.go
swagger_spec:
ifeq ($(SWAGGER),1)
@ -136,7 +100,7 @@ tidy:
GO111MODULE=on go mod tidy
git diff -U --exit-code go.mod go.sum
unit-test:
test:
rm -rf cover.* cover
$(GOTEST) $$($(PACKAGE_LIST)) -coverprofile cover.out.tmp
cat cover.out.tmp | grep -v "_generated.deepcopy.go" > cover.out

374
README.md
View File

@ -2,31 +2,23 @@
[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/chaos-mesh/chaosd)
chaosd is an easy-to-use Chaos Engineering tool used to inject failures to a physical node.
chaosd is an easy-to-use Chaos Engineering tool used to inject failures to a physical node. Currently, two modes are supported:
## Document
- **Command mode** - Using chaosd as a command-line tool. Supported failure types are:
- [Process attack](#process-attack)
- [Network attack](#network-attack)
- [Stress attack](#stress-attack)
- [Disk attack](#disk-attack)
- [Host attack](#host-attack)
- [Recover attack](#recover-attack)
For details about the introduction and usage of chaosd, refer to the [documentation](https://chaos-mesh.org/docs/chaosd-overview/).
## Types of fault
You can use Chaosd to simulate the following fault types:
- Process: Injects faults into the processes. Operations such as killing the process or stopping the process are supported.
- Network: Injects faults into the network of physical machines. Operations such as increasing network latency, losing packets, and corrupting packets are supported.
- Stress: Injects stress on the CPU or memory of the physical machines.
- Disk: Injects faults into disks of the physical machines. Operations such as increasing disk load of reads and writes, and filling disks are supported.
- Host: Injects faults into the physical machine. Operations such as shutdown the physical machine are supported.
For details about the introduction and usage of each fault type, refer to the related documentation.
## Work modes
You can use Chaosd in the following modes:
- Command-line mode: Run Chaosd directly as a command-line tool to inject and recover faults.
- Service mode: Run Chaosd as a service in the background, to inject and recover faults by sending HTTP requests.
- **Server mode** - Running chaosd as a daemon server. Supported failure types are:
- [Process attack](#process-attack-1)
- [Network attack](#network-attack-1)
- [Stress attack](#stress-attack-1)
- [Disk attack](#disk-attack-1)
- [Recover attack](#recover-attack-1)
## Prerequisites
@ -44,24 +36,9 @@ You can either build directly from the source or download the binary to finish t
- Build from source code
Build chaosd:
```bash
make chaosd
```
Build or download tools related to Chaosd:
```bash
make chaos-tools
```
Put Chaosd into `PATH`:
```
mv ./bin /usr/local/chaosd
export PATH=$PATH:/usr/local/chaosd
mv chaosd /usr/local/bin/chaosd
```
- Download binary
@ -72,21 +49,326 @@ You can either build directly from the source or download the binary to finish t
curl -fsSL -o chaosd-latest-linux-amd64.tar.gz https://mirrors.chaos-mesh.org/chaosd-latest-linux-amd64.tar.gz
```
If you want to download the release version, you can replace the `latest` in the above command with the version number. For example, download `v1.1.1` by executing the command below:
If you want to download the release version, you can replace the `latest` in the above command with the version number. For example, download `v1.0.0` by executing the command below:
```bash
curl -fsSL -o chaosd-v1.1.1-linux-amd64.tar.gz https://mirrors.chaos-mesh.org/chaosd-v1.1.1-linux-amd64.tar.gz
curl -fsSL -o chaosd-v1.0.0-linux-amd64.tar.gz https://mirrors.chaos-mesh.org/chaosd-v1.0.0-linux-amd64.tar.gz
```
Then uncompress the archived file
Then uncompress the archived file, and you can go into the folder and execute chaosd
```bash
tar zxvf chaosd-latest-linux-amd64.tar.gz
tar zxvf chaosd-latest-linux-amd64.tar.gz && cd chaosd-latest-linux-amd64
```
Put Chaosd into `PATH`:
## Usages
### Command mode
#### Process attack
Attacks a process according to the PID or process name. Supported tasks are:
- **kill process**
Description: Kills a process by sending the `SIGKILL` signal
Sample usage:
```bash
mv ./chaosd-latest-linux-amd64 /usr/local/chaosd
export PATH=$PATH:/usr/local/chaosd
$ chaosd attack process kill -p [pid] # set pid or pod name
# the generated uid is used to recover chaos attack
Attack network successfully, uid: 2c865e6f-299f-4adf-ab37-94dc4fb8fea6
```
- **stop process**
Description: Kills a process by sending the `SIGKILL` signal
Sample usage:
```bash
$ chaosd attack process stop -p [pid] # set pid or pod name
```
#### Network attack
Attacks the network using `iptables`, `ipset`, and `tc`. Supported tasks are:
- **delay network packet**
Description: Sends messages with the specified latency
Sample usage:
```bash
$ chaosd attack network delay -d eth0 -i 172.16.4.4 -l 10ms
```
- **lose network packet**
Description: Drops network packets randomly
Sample usage:
```bash
$ chaosd attack network loss -d eth0 -i 172.16.4.4 --percent 50
```
- **corrupt network packet**
Description: Causes packet corruption
Sample usage:
```bash
$ chaosd attack network corrupt -d eth0 -i 172.16.4.4 --percent 50
```
- **duplicate network packet**
Description: Sends duplicated packets
Sample usage:
```bash
$ chaosd attack network duplicate -d eth0 -i 172.16.4.4 --percent 50
```
#### Stress attack
Generates stress on the host. Supported tasks are:
- **CPU stress**
Description: Generates stress on the host CPU
Sample usage:
```bash
$ chaosd attack stress cpu -l 100 -w 2
```
- **Memory stress**
Description: Generates stress on the host memory
Sample usage:
```bash
$ chaosd attack stress mem -w 2 # stress 2 CPU and each cpu loads 100%
```
#### Disk attack
Attacks the disk by increasing write/read payload, or filling up the disk. Supported tasks are:
- **add payload**
Description: Add read/write payload
Sample usage:
```bash
./bin/chaosd attack disk add-payload read --path /tmp/temp --size 100
```
```bash
./bin/chaosd attack disk add-payload write --path /tmp/temp --size 100
```
- **fill disk**
Description: Fills up the disk
Sample usage:
```bash
./bin/chaosd attack disk fill --fallocate true --path /tmp/temp --size 100 //filling using fallocate
```
```bash
./bin/chaosd attack disk fill --fallocate false --path /tmp/temp --size 100 //filling by writing data to files
```
#### Host attack
Shuts down the host
Sample usage:
```bash
./bin/chaosd attack host shutdown
```
> **Note:**
>
> This command will shut down the host. Be cautious when you execute it.
#### Recover attack
Recovers an attack
Sample usage:
```bash
$ chaosd recover 2c865e6f-299f-4adf-ab37-94dc4fb8fea6
```
### Server Mode
To enter server mode, execute the following:
```bash
nohup ./bin/chaosd server > chaosd.log 2>&1 &
```
And then you can inject failures by sending HTTP requests.
> **Note**:
>
> Make sure you are operating with the privileges to run iptables, ipset, etc. Or you can run chaosd with `sudo`.
#### Process attack
Attacks a process according to the PID or process name. Supported tasks are:
- **kill process**
Description: Kills a process by sending the `SIGKILL` signal
Sample usage:
```bash
curl -X POST "127.0.0.1:31767/api/attack/process" -H "Content-Type: application/json" -d '{"process": "{pid}", "signal": 9}' # set pid or pod name
{"status":200,"message":"attack successfully","uid":"e6d01a30-4528-4c70-b4fb-4dc47c4d39be"}
```
- **stop process**
Description: Kills a process by sending the `SIGKILL` signal
Sample usage:
```bash
curl -X POST "127.0.0.1:31767/api/attack/process" -H "Content-Type: application/json" -d '{"process": "{pid}", "signal": 15}' # set pid or pod name
{"status":200,"message":"attack successfully","uid":"ecf3f564-c4c0-4aaf-83c6-4b511a6e3a85"}
```
#### Network attack
Attacks the network using `iptables`, `ipset`, and `tc`. Supported tasks are:
- **delay network packet**
Description: Sends messages with the specified latency
Sample usage:
```bash
$ curl -X POST "127.0.0.1:31767/api/attack/network" -H "Content-Type: application/json" -d '{"device": "eth0", "ipaddress": "172.16.4.4", "action": "delay", "latency": "10ms", "jitter": "10ms", "correlation": "0"}'
```
- **lose network packet**
Description: Drops network packets randomly
Sample usage:
```bash
$ curl -X POST "127.0.0.1:31767/api/attack/network" -H "Content-Type: application/json" -d '{"device": "eth0", "ipaddress": "172.16.4.4", "action": "loss", "percent": "50", "correlation": "0"}'
```
- **corrupt network packet**
Description: Causes packet corruption
Sample usage:
```bash
$ curl -X POST "127.0.0.1:31767/api/attack/network" -H "Content-Type: application/json" -d '{"device": "eth0", "ipaddress": "172.16.4.4", "action": "corrupt", "percent": "50", "correlation": "0"}'
```
- **duplicate network packet**
Description: Sends duplicated packets
Sample usage:
```bash
$ curl -X POST "127.0.0.1:31767/api/attack/network" -H "Content-Type: application/json" -d '{"device": "eth0", "ipaddress": "172.16.4.4", "action": "duplicate", "percent": "50", "correlation": "0"}'
```
#### Stress attack
Generates stress on the host. Supported tasks are:
- **CPU stress**
Description: Generates stress on the host CPU
Sample usage:
```bash
$ curl -X POST 127.0.0.1:31767/api/attack/stress -H "Content-Type:application/json" -d '{"action":"cpu", "load": 100, "workers": 2}'
```
- **Memory stress**
Description: Generates stress on the host memory
Sample usage:
```bash
$ curl -X POST 127.0.0.1:31767/api/attack/stress -H "Content-Type:application/json" -d '{"action":"mem", "workers": 2}'
```
#### Disk attack
Attacks the disk by increasing write/read payload, or filling up the disk. Supported tasks are:
- Add payload
Description: Add read/write payload
Sample usage:
```bash
curl -X POST "127.0.0.1:31767/api/attack/disk" -H "Content-Type: application/json" -d '{"action":"read-payload","size":1024,"path":"temp"}'
```
```bash
curl -X POST "127.0.0.1:31767/api/attack/disk" -H "Content-Type: application/json" -d '{"action":"write-payload","size":1024,"path":"temp"}'
```
- Fill disk
Description: Fills up the disk
Sample usage:
```bash
curl -X POST "127.0.0.1:31767/api/attack/disk" -H "Content-Type: application/json" -d '{"action":"fill", "size":1024, "path":"temp", "fill_by_fallocate": true}' //filling using fallocate
```
```bash
curl -X POST "127.0.0.1:31767/api/attack/disk" -H "Content-Type: application/json" -d '{"action":"fill", "size":1024, "path":"temp", "fill_by_fallocate": false}' //filling by writing data to files
```
#### Recover attack
Recovers an attack
Sample usage:
```bash
$ curl -X DELETE "127.0.0.1:31767/api/attack/20df86e9-96e7-47db-88ce-dd31bc70c4f0"
```
## Development
You can develop chaosd directly from your browser in a pre-configured development environment in the cloud:
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/chaos-mesh/chaosd)

View File

@ -1,16 +0,0 @@
# Chaosd Roadmap
This document defines the roadmap for Chaosd development.
## v2.0
- [ ] Support HTTP attack to simulate HTTP faults, such as abort connection, delay, etc.
- [ ] Support IO attack to simulate file system faults, such as IO delay and read/write errors.
- [x] Support workflow to manage a group of chaos experiments.
- [x] Support use Dashboard to manage chaos experiments.
- [x] Support time skew.
- [ ] JVM Attack supports fault injection for applications including MySQL, PostgreSQL, RoketMQ, etc.
- [ ] Support file attack, including deleting files, appending data to files, renaming files, modifying files' privilege.
- [ ] Support inject faults into Kafka, including high payload, the message queue is full, unable to write into, etc.
- [ ] Support inject faults into Zookeeper, including fail to receive publisher's message, fail to update config, fail to notify the update, etc.
- [ ] Support inject faults into Redis, including switch primary and secondary, cache hotspot, the sentinel is unavailable, sentinel restart, etc.

View File

@ -13,11 +13,7 @@
package attack
import (
"github.com/spf13/cobra"
"github.com/chaos-mesh/chaosd/pkg/core"
)
import "github.com/spf13/cobra"
func NewAttackCommand() *cobra.Command {
cmd := &cobra.Command{
@ -25,29 +21,14 @@ func NewAttackCommand() *cobra.Command {
Short: "Attack related commands",
}
var uid string
cmd.PersistentFlags().StringVarP(&uid, "uid", "", "", "the experiment ID")
cmd.AddCommand(
NewProcessAttackCommand(&uid),
NewNetworkAttackCommand(&uid),
NewStressAttackCommand(&uid),
NewDiskAttackCommand(&uid),
NewHostAttackCommand(&uid),
NewJVMAttackCommand(&uid),
NewClockAttackCommand(&uid),
NewKafkaAttackCommand(&uid),
NewRedisAttackCommand(&uid),
NewFileAttackCommand(&uid),
NewHTTPAttackCommand(&uid),
NewVMAttackCommand(&uid),
NewUserDefinedCommand(&uid),
NewProcessAttackCommand(),
NewNetworkAttackCommand(),
NewStressAttackCommand(),
NewDiskAttackCommand(),
NewHostAttackCommand(),
NewJVMAttackCommand(),
)
return cmd
}
func SetScheduleFlags(cmd *cobra.Command, conf *core.SchedulerConfig) {
cmd.Flags().StringVar(&conf.Duration, "duration", "",
`Work duration of attacks.A duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "1.5h" or "2h45m".Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`)
}

View File

@ -1,68 +0,0 @@
// Copyright 2021 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package attack
import (
"fmt"
"github.com/spf13/cobra"
"go.uber.org/fx"
"github.com/chaos-mesh/chaosd/cmd/server"
"github.com/chaos-mesh/chaosd/pkg/core"
"github.com/chaos-mesh/chaosd/pkg/server/chaosd"
"github.com/chaos-mesh/chaosd/pkg/utils"
)
func NewClockAttackCommand(uid *string) *cobra.Command {
options := core.NewClockOption()
dep := fx.Options(
server.Module,
fx.Provide(func() *core.ClockOption {
options.UID = *uid
return options
}),
)
cmd := &cobra.Command{
Use: "clock attack",
Short: "clock skew",
Run: func(*cobra.Command, []string) {
options.Action = core.ClockAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(processClockAttack)).Run()
},
}
cmd.Flags().IntVarP(&options.Pid, "pid", "p", 0, "Pid of target program.")
cmd.Flags().StringVarP(&options.TimeOffset, "time-offset", "t", "", "Specifies the length of time offset.")
cmd.Flags().StringVarP(&options.ClockIdsSlice, "clock-ids-slice", "c", "CLOCK_REALTIME",
"The identifier of the particular clock on which to act."+
"More clock description in linux kernel can be found in man page of clock_getres, clock_gettime, clock_settime."+
"Muti clock ids should be split with \",\"")
return cmd
}
func processClockAttack(options *core.ClockOption, chaos *chaosd.Server) {
err := options.PreProcess()
if err != nil {
utils.ExitWithError(utils.ExitBadArgs, err)
}
uid, err := chaos.ExecuteAttack(chaosd.ClockAttack, options, core.CommandMode)
if err != nil {
utils.ExitWithError(utils.ExitError, err)
}
utils.NormalExit(fmt.Sprintf("Clock attack %v successfully, uid: %s", options, uid))
}

View File

@ -25,12 +25,11 @@ import (
"github.com/chaos-mesh/chaosd/pkg/utils"
)
func NewDiskAttackCommand(uid *string) *cobra.Command {
func NewDiskAttackCommand() *cobra.Command {
options := core.NewDiskOption()
dep := fx.Options(
server.Module,
fx.Provide(func() *core.DiskOption {
options.UID = *uid
return options
}),
)
@ -80,7 +79,6 @@ func NewDiskWritePayloadCommand(dep fx.Option, options *core.DiskOption) *cobra.
"If path not provided, payload will write into a temp file, temp file will be deleted after writing")
cmd.Flags().Uint8VarP(&options.PayloadProcessNum, "process-num", "n", 1,
"'process-num' specifies the number of process work on writing , default 1, only 1-255 is valid value")
SetScheduleFlags(cmd, &options.SchedulerConfig)
return cmd
}
@ -104,7 +102,6 @@ func NewDiskReadPayloadCommand(dep fx.Option, options *core.DiskOption) *cobra.C
"If path not provided, payload will read from disk mount on \"/\"")
cmd.Flags().Uint8VarP(&options.PayloadProcessNum, "process-num", "n", 1,
"'process-num' specifies the number of process work on reading , default 1, only 1-255 is valid value")
SetScheduleFlags(cmd, &options.SchedulerConfig)
return cmd
}
@ -114,7 +111,7 @@ func NewDiskFillCommand(dep fx.Option, options *core.DiskOption) *cobra.Command
Short: "fill disk",
Run: func(*cobra.Command, []string) {
options.Action = core.DiskFillAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(processDiskAttack)).Run()
utils.FxNewAppWithoutLog(dep, fx.Invoke(processDiskAttack), fx.NopLogger).Run()
},
}
@ -133,12 +130,10 @@ func NewDiskFillCommand(dep fx.Option, options *core.DiskOption) *cobra.Command
}
func processDiskAttack(options *core.DiskOption, chaos *chaosd.Server) {
attackConfig, err := options.PreProcess()
if err != nil {
if err := options.Validate(); err != nil {
utils.ExitWithError(utils.ExitBadArgs, err)
}
uid, err := chaos.ExecuteAttack(chaosd.DiskAttack, attackConfig, core.CommandMode)
uid, err := chaos.ExecuteAttack(chaosd.DiskAttack, options, core.CommandMode)
if err != nil {
utils.ExitWithError(utils.ExitError, err)
}

323
cmd/attack/disk_test.go Normal file
View File

@ -0,0 +1,323 @@
// Copyright 2021 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package attack
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
"go.uber.org/fx"
"go.uber.org/fx/fxtest"
"github.com/chaos-mesh/chaosd/cmd/server"
"github.com/chaos-mesh/chaosd/pkg/core"
"github.com/chaos-mesh/chaosd/pkg/server/chaosd"
"github.com/chaos-mesh/chaosd/pkg/utils"
)
type diskTest struct {
name string
option *core.DiskOption
wantErr bool
}
func TestServer_DiskFill(t *testing.T) {
fxtest.New(
t,
server.Module,
fx.Provide(func() []diskTest {
return []diskTest{
{
name: "0",
option: &core.DiskOption{
CommonAttackConfig: core.CommonAttackConfig{
Action: core.DiskFillAction,
Kind: core.DiskAttack,
},
Size: "1024M",
Path: "./temp",
FillByFallocate: true,
PayloadProcessNum: 1,
},
wantErr: false,
}, {
name: "1",
option: &core.DiskOption{
CommonAttackConfig: core.CommonAttackConfig{
Action: core.DiskFillAction,
Kind: core.DiskAttack,
},
Size: "24MB",
Path: "./temp",
FillByFallocate: false,
PayloadProcessNum: 1,
},
wantErr: false,
},
}
}),
fx.Invoke(func(s *chaosd.Server, tests []diskTest) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := s.ExecuteAttack(chaosd.DiskAttack, tt.option, core.CommandMode)
if (err != nil) != tt.wantErr {
t.Errorf("DiskFill() error = %v, wantErr %v", err, tt.wantErr)
return
}
stat, err := os.Stat(tt.option.Path)
if err != nil {
t.Errorf("unexpected err %v when stat temp file", err)
return
}
size, _ := utils.ParseUnit(tt.option.Size)
if stat.Size() != int64(size) {
t.Errorf("DiskFill() size %v, expect %d", stat.Size(), size)
return
}
os.Remove(tt.option.Path)
})
}
}),
)
}
func TestServer_DiskPayload(t *testing.T) {
fxtest.New(
t,
server.Module,
fx.Provide(func() []diskTest {
return []diskTest{
{
name: "0",
option: &core.DiskOption{
CommonAttackConfig: core.CommonAttackConfig{
Action: core.DiskWritePayloadAction,
Kind: core.DiskAttack,
},
Size: "24M",
Path: "./temp",
PayloadProcessNum: 1,
},
wantErr: false,
}, {
name: "1",
option: &core.DiskOption{
CommonAttackConfig: core.CommonAttackConfig{
Action: core.DiskReadPayloadAction,
Kind: core.DiskAttack,
},
Size: "24M",
Path: "./temp",
PayloadProcessNum: 1,
},
wantErr: false,
},
}
}),
fx.Invoke(func(s *chaosd.Server, tests []diskTest) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := s.ExecuteAttack(chaosd.DiskAttack, &core.DiskOption{
CommonAttackConfig: core.CommonAttackConfig{
Action: core.DiskFillAction,
Kind: core.DiskAttack,
},
PayloadProcessNum: 1,
Size: tt.option.Size,
Path: "./temp",
FillByFallocate: true,
}, core.CommandMode)
if err != nil {
t.Error(err)
return
}
_, err = s.ExecuteAttack(chaosd.DiskAttack, tt.option, core.CommandMode)
if (err != nil) != tt.wantErr {
t.Errorf("DiskPayload() error = %v, wantErr %v", err, tt.wantErr)
return
}
os.Remove(tt.option.Path)
})
}
}),
)
}
type writeArgs struct {
Size string
Path string
PayloadProcessNum uint8
}
func writeArgsToDiskOption(args writeArgs) core.DiskOption {
return core.DiskOption{
CommonAttackConfig: core.CommonAttackConfig{
SchedulerConfig: core.SchedulerConfig{},
Action: core.DiskWritePayloadAction,
Kind: "",
},
Size: args.Size,
Path: args.Path,
Percent: "",
FillByFallocate: false,
PayloadProcessNum: args.PayloadProcessNum,
}
}
func writeArgsAttack(args writeArgs) error {
opt := writeArgsToDiskOption(args)
return chaosd.DiskAttack.Attack(&opt, chaosd.Environment{})
}
func TestNewDiskWritePayloadCommand(t *testing.T) {
var opt core.DiskOption
var err error
opt = writeArgsToDiskOption(writeArgs{
Size: "",
Path: "",
PayloadProcessNum: 0,
})
err = opt.Validate()
assert.EqualError(t, err, "one of percent and size must not be empty, DiskOption : write-payload")
opt = writeArgsToDiskOption(writeArgs{
Size: "1Ms",
Path: "",
PayloadProcessNum: 0,
})
err = opt.Validate()
assert.EqualError(t, err, "unknown units of size : 1Ms, DiskOption : write-payload")
opt = writeArgsToDiskOption(writeArgs{
Size: "0",
Path: "",
PayloadProcessNum: 0,
})
err = opt.Validate()
assert.EqualError(t, err, "unsupport process num : 0, DiskOption : write-payload")
opt = writeArgsToDiskOption(writeArgs{
Size: "0",
Path: "",
PayloadProcessNum: 1,
})
err = opt.Validate()
assert.NoError(t, err)
assert.NoError(t, writeArgsAttack(writeArgs{
Size: "0",
Path: "",
PayloadProcessNum: 1,
}))
assert.NoError(t, writeArgsAttack(writeArgs{
Size: "0",
Path: "",
PayloadProcessNum: 255,
}))
assert.NoError(t, writeArgsAttack(writeArgs{
Size: "1",
Path: "",
PayloadProcessNum: 2,
}))
assert.Error(t, writeArgsAttack(writeArgs{
Size: "1",
Path: "&^%$#@#$%^&*(",
PayloadProcessNum: 5,
}))
}
type readArgs struct {
Size string
Path string
PayloadProcessNum uint8
}
func readArgsToDiskOption(args readArgs) core.DiskOption {
return core.DiskOption{
CommonAttackConfig: core.CommonAttackConfig{
SchedulerConfig: core.SchedulerConfig{},
Action: core.DiskReadPayloadAction,
Kind: "",
},
Size: args.Size,
Path: args.Path,
Percent: "",
FillByFallocate: false,
PayloadProcessNum: args.PayloadProcessNum,
}
}
func readArgsAttack(args readArgs) error {
opt := readArgsToDiskOption(args)
return chaosd.DiskAttack.Attack(&opt, chaosd.Environment{})
}
func TestNewDiskReadPayloadCommand(t *testing.T) {
var opt core.DiskOption
var err error
opt = readArgsToDiskOption(readArgs{
Size: "",
Path: "",
PayloadProcessNum: 0,
})
err = opt.Validate()
assert.EqualError(t, err, "one of percent and size must not be empty, DiskOption : read-payload")
opt = readArgsToDiskOption(readArgs{
Size: "1Ms",
Path: "",
PayloadProcessNum: 0,
})
err = opt.Validate()
assert.EqualError(t, err, "unknown units of size : 1Ms, DiskOption : read-payload")
opt = readArgsToDiskOption(readArgs{
Size: "0",
Path: "",
PayloadProcessNum: 0,
})
err = opt.Validate()
assert.EqualError(t, err, "unsupport process num : 0, DiskOption : read-payload")
opt = readArgsToDiskOption(readArgs{
Size: "0",
Path: "",
PayloadProcessNum: 1,
})
err = opt.Validate()
assert.NoError(t, err)
assert.NoError(t, readArgsAttack(readArgs{
Size: "0",
Path: "/dev/zero",
PayloadProcessNum: 1,
}))
assert.NoError(t, readArgsAttack(readArgs{
Size: "1",
Path: "/dev/zero",
PayloadProcessNum: 2,
}))
assert.NoError(t, readArgsAttack(readArgs{
Size: "100GB",
Path: "/dev/zero",
PayloadProcessNum: 1,
}))
}

View File

@ -1,172 +0,0 @@
// Copyright 2022 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package attack
import (
"fmt"
"github.com/spf13/cobra"
"go.uber.org/fx"
"github.com/chaos-mesh/chaosd/cmd/server"
"github.com/chaos-mesh/chaosd/pkg/core"
"github.com/chaos-mesh/chaosd/pkg/server/chaosd"
"github.com/chaos-mesh/chaosd/pkg/utils"
)
func NewFileAttackCommand(uid *string) *cobra.Command {
options := core.NewFileCommand()
dep := fx.Options(
server.Module,
fx.Provide(func() *core.FileCommand {
options.UID = *uid
return options
}),
)
cmd := &cobra.Command{
Use: "file <subcommand>",
Short: "File attack related commands",
}
cmd.AddCommand(
NewFileCreateCommand(dep, options),
NewFileModifyPrivilegeCommand(dep, options),
NewFileDeleteCommand(dep, options),
NewFileRenameCommand(dep, options),
NewFileAppendCommand(dep, options),
NewFileReplaceCommand(dep, options),
)
return cmd
}
func NewFileCreateCommand(dep fx.Option, options *core.FileCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "create",
Short: "create file",
Run: func(*cobra.Command, []string) {
options.Action = core.FileCreateAction
options.CompleteDefaults()
utils.FxNewAppWithoutLog(dep, fx.Invoke(commonFileAttackFunc)).Run()
},
}
cmd.Flags().StringVarP(&options.FileName, "file-name", "f", "", "the name of file to be created")
cmd.Flags().StringVarP(&options.DirName, "dir-name", "d", "", "the name of directory to be created")
return cmd
}
func NewFileModifyPrivilegeCommand(dep fx.Option, options *core.FileCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "modify",
Short: "modify file privilege",
Run: func(cmd *cobra.Command, args []string) {
options.Action = core.FileModifyPrivilegeAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(commonFileAttackFunc)).Run()
},
}
cmd.Flags().StringVarP(&options.FileName, "file-name", "f", "", "file to be change privilege")
cmd.Flags().Uint32VarP(&options.Privilege, "privilege", "p", 0, "privilege to be update")
return cmd
}
func NewFileDeleteCommand(dep fx.Option, options *core.FileCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "delete",
Short: "delete file",
Run: func(cmd *cobra.Command, args []string) {
options.Action = core.FileDeleteAction
options.CompleteDefaults()
utils.FxNewAppWithoutLog(dep, fx.Invoke(commonFileAttackFunc)).Run()
},
}
cmd.Flags().StringVarP(&options.FileName, "file-name", "f", "", "the file to be deleted")
cmd.Flags().StringVarP(&options.DirName, "dir-name", "d", "", "the directory to be deleted")
return cmd
}
func NewFileRenameCommand(dep fx.Option, options *core.FileCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "rename",
Short: "rename file",
Run: func(cmd *cobra.Command, args []string) {
options.Action = core.FileRenameAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(commonFileAttackFunc)).Run()
},
}
cmd.Flags().StringVarP(&options.SourceFile, "source-file", "s", "", "the source file/dir of rename")
cmd.Flags().StringVarP(&options.DestFile, "dest-file", "d", "", "the destination file/dir of rename")
return cmd
}
func NewFileAppendCommand(dep fx.Option, options *core.FileCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "append",
Short: "append file",
Run: func(cmd *cobra.Command, args []string) {
options.Action = core.FileAppendAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(commonFileAttackFunc)).Run()
},
}
cmd.Flags().StringVarP(&options.FileName, "file-name", "f", "", "append data to the file")
cmd.Flags().StringVarP(&options.Data, "data", "d", "", "append data")
cmd.Flags().IntVarP(&options.Count, "count", "c", 1, "append count with default value is 1")
return cmd
}
func NewFileReplaceCommand(dep fx.Option, options *core.FileCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "replace",
Short: "replace data in file",
Run: func(cmd *cobra.Command, args []string) {
options.Action = core.FileReplaceAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(commonFileAttackFunc)).Run()
},
}
cmd.Flags().StringVarP(&options.FileName, "file-name", "f", "", "replace data in the file")
cmd.Flags().StringVarP(&options.OriginStr, "origin-string", "o", "", "the origin string to be replaced")
cmd.Flags().StringVarP(&options.DestStr, "dest-string", "d", "", "the destination string to replace the origin string")
cmd.Flags().IntVarP(&options.Line, "line", "l", 0, "the line number to replace, default is 0, means replace all lines")
return cmd
}
func commonFileAttackFunc(options *core.FileCommand, chaos *chaosd.Server) {
if err := options.Validate(); err != nil {
utils.ExitWithError(utils.ExitBadArgs, err)
}
uid, err := chaos.ExecuteAttack(chaosd.FileAttack, options, core.CommandMode)
if err != nil {
utils.ExitWithError(utils.ExitError, err)
}
utils.NormalExit(fmt.Sprintf("Attack file successfully, uid: %s", uid))
}

View File

@ -25,12 +25,11 @@ import (
"github.com/chaos-mesh/chaosd/pkg/utils"
)
func NewHostAttackCommand(uid *string) *cobra.Command {
func NewHostAttackCommand() *cobra.Command {
options := core.NewHostCommand()
dep := fx.Options(
server.Module,
fx.Provide(func() *core.HostCommand {
options.UID = *uid
return options
}),
)
@ -41,7 +40,6 @@ func NewHostAttackCommand(uid *string) *cobra.Command {
}
cmd.AddCommand(NewHostShutdownCommand(dep, options))
cmd.AddCommand(NewHostRebootCommand(dep, options))
return cmd
}
@ -52,21 +50,6 @@ func NewHostShutdownCommand(dep fx.Option, options *core.HostCommand) *cobra.Com
Short: "shutdowns system, this action will trigger shutdown of the host machine",
Run: func(*cobra.Command, []string) {
options.Action = core.HostShutdownAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(hostAttackF)).Run()
},
}
return cmd
}
func NewHostRebootCommand(dep fx.Option, options *core.HostCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "reboot",
Short: "reboot system, this action will trigger reboot of the host machine",
Run: func(*cobra.Command, []string) {
options.Action = core.HostRebootAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(hostAttackF)).Run()
},
}

View File

@ -1,143 +0,0 @@
// Copyright 2022 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package attack
import (
"fmt"
"github.com/spf13/cobra"
"go.uber.org/fx"
"github.com/chaos-mesh/chaosd/cmd/server"
"github.com/chaos-mesh/chaosd/pkg/core"
"github.com/chaos-mesh/chaosd/pkg/server/chaosd"
"github.com/chaos-mesh/chaosd/pkg/utils"
)
func NewHTTPAttackCommand(uid *string) *cobra.Command {
option := core.NewHTTPAttackOption()
dep := fx.Options(
server.Module,
fx.Provide(func() *core.HTTPAttackOption {
option.UID = *uid
return option
}),
)
cmd := &cobra.Command{
Use: "http <subcommand>",
Short: "HTTP attack related commands",
}
cmd.AddCommand(
NewHTTPAbortCommand(dep, option),
NewHTTPDelayCommand(dep, option),
NewHTTPConfigCommand(dep, option),
NewHTTPRequestCommand(dep, option),
)
return cmd
}
func NewHTTPAbortCommand(dep fx.Option, o *core.HTTPAttackOption) *cobra.Command {
cmd := &cobra.Command{
Use: "abort",
Short: "abort selected HTTP connection",
Run: func(*cobra.Command, []string) {
o.Action = core.HTTPAbortAction
o.Abort = true
utils.FxNewAppWithoutLog(dep, fx.Invoke(processHTTPAttack)).Run()
},
}
setTarget(cmd, o)
setSelector(cmd, o)
return cmd
}
func NewHTTPDelayCommand(dep fx.Option, o *core.HTTPAttackOption) *cobra.Command {
cmd := &cobra.Command{
Use: "delay",
Short: "delay selected HTTP Package",
Run: func(*cobra.Command, []string) {
o.Action = core.HTTPDelayAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(processHTTPAttack)).Run()
},
}
setTarget(cmd, o)
setSelector(cmd, o)
cmd.Flags().StringVarP(&o.Delay, "delay time", "d", "", "Delay represents the delay of the target request/response.")
return cmd
}
func setTarget(cmd *cobra.Command, o *core.HTTPAttackOption) {
cmd.Flags().UintSliceVarP(&o.ProxyPorts, "proxy-ports", "p", nil,
"composed with one of the port of HTTP connection, "+
"we will only attack HTTP connection with port inside proxy_ports")
cmd.Flags().StringVarP(&o.Target, "target", "t", "",
"HTTP target: Request or Response")
}
func setSelector(cmd *cobra.Command, c *core.HTTPAttackOption) {
cmd.Flags().Int32Var(&c.Port, "port", 0, "The TCP port that the target service listens on.")
cmd.Flags().StringVar(&c.Path, "path", "",
"Match path of Uri with wildcard matches.")
cmd.Flags().StringVarP(&c.Method, "method", "m", "", "HTTP method")
cmd.Flags().StringVarP(&c.Code, "code", "c", "", "Code is a rule to select target by http status code in response.")
}
func NewHTTPConfigCommand(dep fx.Option, o *core.HTTPAttackOption) *cobra.Command {
cmd := &cobra.Command{
Use: "config",
Short: "attack with config file",
Run: func(*cobra.Command, []string) {
o.Action = core.HTTPConfigAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(processHTTPAttack)).Run()
},
}
cmd.Flags().StringVarP(&o.FilePath, "file path", "p", "", "Config file path.")
return cmd
}
func NewHTTPRequestCommand(dep fx.Option, o *core.HTTPAttackOption) *cobra.Command {
cmd := &cobra.Command{
Use: "request",
Short: "request specific URL, only support GET",
Run: func(*cobra.Command, []string) {
o.Action = core.HTTPRequestAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(processHTTPAttack)).Run()
},
}
cmd.Flags().StringVarP(&o.HTTPRequestConfig.URL, "url", "", "", "Request to send")
cmd.Flags().IntVarP(&o.HTTPRequestConfig.Count, "count", "c", 1, "Number of requests to send")
cmd.Flags().BoolVarP(&o.HTTPRequestConfig.EnableConnPool, "enable-conn-pool", "p", false, "Enable connection pool")
return cmd
}
func processHTTPAttack(o *core.HTTPAttackOption, chaos *chaosd.Server) {
attackConfig, err := o.PreProcess()
if err != nil {
utils.ExitWithError(utils.ExitBadArgs, err)
}
uid, err := chaos.ExecuteAttack(chaosd.HTTPAttack, attackConfig, core.CommandMode)
if err != nil {
utils.ExitWithError(utils.ExitError, err)
}
utils.NormalExit(fmt.Sprintf("HTTP attack successfully, uid: %s", uid))
}

View File

@ -25,12 +25,11 @@ import (
"github.com/chaos-mesh/chaosd/pkg/utils"
)
func NewJVMAttackCommand(uid *string) *cobra.Command {
func NewJVMAttackCommand() *cobra.Command {
options := core.NewJVMCommand()
dep := fx.Options(
server.Module,
fx.Provide(func() *core.JVMCommand {
options.UID = *uid
return options
}),
)
@ -40,8 +39,38 @@ func NewJVMAttackCommand(uid *string) *cobra.Command {
Short: "JVM attack related commands",
}
cmd.AddCommand(
NewJVMInstallCommand(dep, options),
NewJVMSubmitCommand(dep, options),
)
return cmd
}
func NewJVMInstallCommand(dep fx.Option, options *core.JVMCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "install [options]",
Short: "install agent to Java process",
Run: func(*cobra.Command, []string) {
options.Type = core.JVMInstallType
utils.FxNewAppWithoutLog(dep, fx.Invoke(jvmCommandFunc)).Run()
},
}
cmd.Flags().IntVarP(&options.Port, "port", "", 9288, "the port of agent server")
cmd.Flags().IntVarP(&options.Pid, "pid", "", 0, "the pid of Java process which need to attach")
return cmd
}
func NewJVMSubmitCommand(dep fx.Option, options *core.JVMCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "submit [options]",
Short: "submit rules for byteman agent",
}
options.Type = core.JVMSubmitType
cmd.PersistentFlags().IntVarP(&options.Port, "port", "", 9288, "the port of agent server")
cmd.PersistentFlags().IntVarP(&options.Pid, "pid", "", 0, "the pid of Java process which need to attach")
cmd.AddCommand(
NewJVMLatencyCommand(dep, options),
@ -50,7 +79,6 @@ func NewJVMAttackCommand(uid *string) *cobra.Command {
NewJVMStressCommand(dep, options),
NewJVMGCCommand(dep, options),
NewJVMRuleFileCommand(dep, options),
NewJVMMySQLCommand(dep, options),
)
return cmd
@ -68,7 +96,7 @@ func NewJVMLatencyCommand(dep fx.Option, options *core.JVMCommand) *cobra.Comman
cmd.Flags().StringVarP(&options.Class, "class", "c", "", "Java class name")
cmd.Flags().StringVarP(&options.Method, "method", "m", "", "the method name in Java class")
cmd.Flags().IntVarP(&options.LatencyDuration, "latency", "", 0, "the latency duration, unit ms")
cmd.Flags().StringVarP(&options.LatencyDuration, "latency", "", "", "the latency duration")
return cmd
}
@ -85,7 +113,7 @@ func NewJVMReturnCommand(dep fx.Option, options *core.JVMCommand) *cobra.Command
cmd.Flags().StringVarP(&options.Class, "class", "c", "", "Java class name")
cmd.Flags().StringVarP(&options.Method, "method", "m", "", "the method name in Java class")
cmd.Flags().StringVarP(&options.ReturnValue, "value", "", "", "the return value for action 'return'. Only supports number and string types.")
cmd.Flags().StringVarP(&options.ReturnValue, "value", "", "", "the return value for action 'return', only support number and string type now")
return cmd
}
@ -117,8 +145,8 @@ func NewJVMStressCommand(dep fx.Option, options *core.JVMCommand) *cobra.Command
},
}
cmd.Flags().IntVarP(&options.CPUCount, "cpu-count", "", 0, "the CPU core number")
cmd.Flags().StringVarP(&options.MemoryType, "mem-type", "", "", "the memory type to be allocated. The value can be 'stack' or 'heap'")
cmd.Flags().IntVarP(&options.CPUCount, "cpu-count", "", 0, "the CPU core number need to use")
cmd.Flags().IntVarP(&options.MemorySize, "mem-size", "", 0, "the memory size need to locate, the unit is MB")
return cmd
}
@ -151,26 +179,6 @@ func NewJVMRuleFileCommand(dep fx.Option, options *core.JVMCommand) *cobra.Comma
return cmd
}
func NewJVMMySQLCommand(dep fx.Option, options *core.JVMCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "mysql [options]",
Short: "inject fault into MySQL client",
Run: func(*cobra.Command, []string) {
options.Action = core.JVMMySQLAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(jvmCommandFunc)).Run()
},
}
cmd.Flags().StringVarP(&options.SQLType, "sql-type", "", "", "the match sql type")
cmd.Flags().StringVarP(&options.Database, "database", "d", "", "the match database")
cmd.Flags().StringVarP(&options.Table, "table", "t", "", "the match table")
cmd.Flags().StringVarP(&options.MySQLConnectorVersion, "mysql-connector-version", "v", "8", "the version of mysql-connector-java, only support 5.X.X(set to 5) and 8.X.X(set to 8)")
cmd.Flags().StringVarP(&options.ThrowException, "exception", "", "", "the exception message needs to throw")
cmd.Flags().IntVarP(&options.LatencyDuration, "latency", "", 0, "the latency duration, unit ms")
return cmd
}
func jvmCommandFunc(options *core.JVMCommand, chaos *chaosd.Server) {
options.CompleteDefaults()

View File

@ -1,132 +0,0 @@
// Copyright 2022 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package attack
import (
"fmt"
"github.com/spf13/cobra"
"go.uber.org/fx"
"github.com/chaos-mesh/chaosd/cmd/server"
"github.com/chaos-mesh/chaosd/pkg/core"
"github.com/chaos-mesh/chaosd/pkg/server/chaosd"
"github.com/chaos-mesh/chaosd/pkg/utils"
)
func NewKafkaAttackCommand(uid *string) *cobra.Command {
options := core.NewKafkaCommand()
dep := fx.Options(
server.Module,
fx.Provide(func() *core.KafkaCommand {
options.UID = *uid
return options
}),
)
cmd := &cobra.Command{
Use: "kafka <subcommand>",
Short: "Kafka attack related commands",
}
cmd.AddCommand(
NewKafkaFillCommand(dep, options),
NewKafkaFloodCommand(dep, options),
NewKafkaIOCommand(dep, options),
)
cmd.PersistentFlags().StringVarP(&options.Topic, "topic", "T", "", "the topic to attack")
_ = cmd.MarkPersistentFlagRequired("topic")
return cmd
}
func NewKafkaFillCommand(dep fx.Option, options *core.KafkaCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "fill [options]",
Short: "Fill kafka cluster with messages",
ValidArgsFunction: cobra.NoFileCompletions,
Run: func(*cobra.Command, []string) {
options.Action = core.KafkaFillAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(kafkaCommandFunc)).Run()
},
}
cmd.Flags().StringVarP(&options.Host, "host", "H", "localhost", "the host of kafka server")
cmd.Flags().Uint16VarP(&options.Port, "port", "P", 9092, "the port of kafka server")
cmd.Flags().StringVarP(&options.Username, "username", "u", "", "the username of kafka client")
cmd.Flags().StringVarP(&options.Password, "password", "p", "", "the password of kafka client")
cmd.Flags().StringVarP(&options.AuthMechanism, "auth-mechanism", "a", "sasl/plain", "the authentication mechanism of kafka, supported value: sasl/plain, sasl/scram-sha-256, sasl/scram-sha-512")
cmd.Flags().UintVarP(&options.MessageSize, "size", "s", 4*1024, "the size of each message")
cmd.Flags().Uint64VarP(&options.MaxBytes, "max-bytes", "m", 1<<34, "the max bytes to fill")
cmd.Flags().StringVarP(&options.ReloadCommand, "reload-cmd", "r", "", "the command to reload kafka config")
return cmd
}
func NewKafkaFloodCommand(dep fx.Option, options *core.KafkaCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "flood [options]",
Short: "Flood kafka cluster with messages",
ValidArgsFunction: cobra.NoFileCompletions,
Run: func(*cobra.Command, []string) {
options.Action = core.KafkaFloodAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(kafkaCommandFunc)).Run()
},
}
cmd.Flags().StringVarP(&options.Host, "host", "H", "localhost", "the host of kafka server")
cmd.Flags().Uint16VarP(&options.Port, "port", "P", 9092, "the port of kafka server")
cmd.Flags().StringVarP(&options.Username, "username", "u", "", "the username of kafka client")
cmd.Flags().StringVarP(&options.Password, "password", "p", "", "the password of kafka client")
cmd.Flags().StringVarP(&options.AuthMechanism, "auth-mechanism", "a", "sasl/plain", "the authentication mechanism of kafka, supported value: sasl/plain, sasl/scram-sha-256, sasl/scram-sha-512")
cmd.Flags().UintVarP(&options.MessageSize, "size", "s", 1024, "the size of each message")
cmd.Flags().UintVarP(&options.Threads, "threads", "t", 100, "the numbers of worker threads")
return cmd
}
func NewKafkaIOCommand(dep fx.Option, options *core.KafkaCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "io [options]",
Short: "Make kafka cluster non-writable/non-readable",
ValidArgsFunction: cobra.NoFileCompletions,
Run: func(*cobra.Command, []string) {
options.Action = core.KafkaIOAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(kafkaCommandFunc)).Run()
},
}
cmd.Flags().StringVarP(&options.Host, "host", "H", "localhost", "the host of kafka server")
cmd.Flags().Uint16VarP(&options.Port, "port", "P", 9092, "the port of kafka server")
cmd.Flags().StringVarP(&options.Username, "username", "u", "", "the username of kafka client")
cmd.Flags().StringVarP(&options.Password, "password", "p", "", "the password of kafka client")
cmd.Flags().StringVarP(&options.AuthMechanism, "auth-mechanism", "a", "sasl/plain", "the authentication mechanism of kafka, supported value: sasl/plain, sasl/scram-sha-256, sasl/scram-sha-512")
cmd.Flags().StringVarP(&options.ConfigFile, "config", "c", "/etc/kafka/server.properties", "the path of server config")
cmd.Flags().BoolVarP(&options.NonReadable, "non-readable", "r", false, "make kafka cluster non-readable")
cmd.Flags().BoolVarP(&options.NonWritable, "non-writable", "w", false, "make kafka cluster non-writable")
return cmd
}
func kafkaCommandFunc(options *core.KafkaCommand, chaos *chaosd.Server) {
options.CompleteDefaults()
if err := options.Validate(); err != nil {
utils.ExitWithError(utils.ExitBadArgs, err)
}
uid, err := chaos.ExecuteAttack(chaosd.KafkaAttack, options, core.CommandMode)
if err != nil {
utils.ExitWithError(utils.ExitError, err)
}
utils.NormalExit(fmt.Sprintf("Attack kafka successfully, uid: %s", uid))
}

View File

@ -25,12 +25,11 @@ import (
"github.com/chaos-mesh/chaosd/pkg/utils"
)
func NewNetworkAttackCommand(uid *string) *cobra.Command {
func NewNetworkAttackCommand() *cobra.Command {
options := core.NewNetworkCommand()
dep := fx.Options(
server.Module,
fx.Provide(func() *core.NetworkCommand {
options.UID = *uid
return options
}),
)
@ -45,12 +44,8 @@ func NewNetworkAttackCommand(uid *string) *cobra.Command {
NewNetworkLossCommand(dep, options),
NewNetworkCorruptCommand(dep, options),
NetworkDuplicateCommand(dep, options),
NetworkPartitionCommand(dep, options),
NetworkDNSCommand(dep, options),
NewNetworkPortOccupiedCommand(dep, options),
NewNetworkBandwidthCommand(dep, options),
NewNICDownCommand(dep, options),
NewNetworkFloodCommand(dep, options),
)
return cmd
@ -84,7 +79,6 @@ func NewNetworkDelayCommand(dep fx.Option, options *core.NetworkCommand) *cobra.
cmd.Flags().StringVarP(&options.Hostname, "hostname", "H", "", "only impact traffic to these hostnames")
cmd.Flags().StringVarP(&options.IPProtocol, "protocol", "p", "",
"only impact traffic using this IP protocol, supported: tcp, udp, icmp, all")
cmd.Flags().StringVarP(&options.AcceptTCPFlags, "accept-tcp-flags", "", "", "only the packet which match the tcp flag can be accepted, others will be dropped. only set when the protocol is tcp.")
return cmd
}
@ -159,7 +153,7 @@ func NetworkDuplicateCommand(dep fx.Option, options *core.NetworkCommand) *cobra
},
}
cmd.Flags().StringVar(&options.Percent, "percent", "1", "percentage of packets to duplicate (10 is 10%)")
cmd.Flags().StringVar(&options.Percent, "percent", "1", "percentage of packets to corrupt (10 is 10%)")
cmd.Flags().StringVarP(&options.Correlation, "correlation", "c", "0", "correlation is percentage (10 is 10%)")
cmd.Flags().StringVarP(&options.Device, "device", "d", "", "the network interface to impact")
cmd.Flags().StringVarP(&options.EgressPort, "egress-port", "e", "",
@ -176,29 +170,6 @@ func NetworkDuplicateCommand(dep fx.Option, options *core.NetworkCommand) *cobra
return cmd
}
func NetworkPartitionCommand(dep fx.Option, options *core.NetworkCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "partition",
Short: "partition",
Run: func(*cobra.Command, []string) {
options.Action = core.NetworkPartitionAction
options.CompleteDefaults()
utils.FxNewAppWithoutLog(dep, fx.Invoke(commonNetworkAttackFunc)).Run()
},
}
cmd.Flags().StringVarP(&options.IPAddress, "ip", "i", "", "only impact egress traffic to these IP addresses")
cmd.Flags().StringVarP(&options.Hostname, "hostname", "H", "", "only impact traffic to these hostnames")
cmd.Flags().StringVarP(&options.Direction, "direction", "", "both", "specifies the partition direction, values can be 'to', 'from' or 'both'. 'from' means packets coming from the 'IPAddress' or 'Hostname' and going to your server, 'to' means packets originating from your server and going to the 'IPAddress' or 'Hostname'.")
cmd.Flags().StringVarP(&options.Device, "device", "d", "", "the network interface to impact")
cmd.Flags().StringVarP(&options.IPProtocol, "protocol", "p", "",
"only impact traffic using this IP protocol, supported: tcp, udp, icmp, all")
cmd.Flags().StringVarP(&options.AcceptTCPFlags, "accept-tcp-flags", "", "", "only the packet which match the tcp flag can be accepted, others will be dropped. only set when the protocol is tcp.")
return cmd
}
func NetworkDNSCommand(dep fx.Option, options *core.NetworkCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "dns",
@ -213,36 +184,12 @@ func NetworkDNSCommand(dep fx.Option, options *core.NetworkCommand) *cobra.Comma
cmd.Flags().StringVarP(&options.DNSServer, "dns-server", "", "123.123.123.123",
"update the DNS server in /etc/resolv.conf with this value")
cmd.Flags().StringVarP(&options.DNSDomainName, "dns-domain-name", "d", "", "map this host to specified IP")
cmd.Flags().StringVarP(&options.DNSHost, "dns-hostname", "H", "", "map this host to specified IP")
cmd.Flags().StringVarP(&options.DNSIp, "dns-ip", "i", "", "map specified host to this IP address")
return cmd
}
func NewNetworkBandwidthCommand(dep fx.Option, options *core.NetworkCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "bandwidth",
Short: "limit network bandwidth",
Run: func(*cobra.Command, []string) {
options.Action = core.NetworkBandwidthAction
options.CompleteDefaults()
utils.FxNewAppWithoutLog(dep, fx.Invoke(commonNetworkAttackFunc)).Run()
},
}
cmd.Flags().StringVarP(&options.Rate, "rate", "r", "", "the speed knob, allows bps, kbps, mbps, gbps, tbps unit. bps means bytes per second")
cmd.Flags().Uint32VarP(&options.Limit, "limit", "l", 0, "the number of bytes that can be queued waiting for tokens to become available")
cmd.Flags().Uint32VarP(&options.Buffer, "buffer", "b", 0, "the maximum amount of bytes that tokens can be available for instantaneously")
cmd.Flags().Uint64VarP(options.Peakrate, "peakrate", "", 0, "the maximum depletion rate of the bucket")
cmd.Flags().Uint32VarP(options.Minburst, "minburst", "m", 0, "specifies the size of the peakrate bucket")
cmd.Flags().StringVarP(&options.Device, "device", "d", "", "the network interface to impact")
cmd.Flags().StringVarP(&options.IPAddress, "ip", "i", "", "only impact egress traffic to these IP addresses")
cmd.Flags().StringVarP(&options.Hostname, "hostname", "H", "", "only impact traffic to these hostnames")
return cmd
}
func commonNetworkAttackFunc(options *core.NetworkCommand, chaos *chaosd.Server) {
if err := options.Validate(); err != nil {
utils.ExitWithError(utils.ExitBadArgs, err)
@ -262,7 +209,7 @@ func NewNetworkPortOccupiedCommand(dep fx.Option, options *core.NetworkCommand)
Short: "attack network port",
Run: func(cmd *cobra.Command, args []string) {
options.Action = core.NetworkPortOccupiedAction
options.Action = core.NetworkPortOccupied
options.CompleteDefaults()
utils.FxNewAppWithoutLog(dep, fx.Invoke(commonNetworkAttackFunc)).Run()
},
@ -271,41 +218,3 @@ func NewNetworkPortOccupiedCommand(dep fx.Option, options *core.NetworkCommand)
cmd.Flags().StringVarP(&options.Port, "port", "p", "", "this specified port is to occupied")
return cmd
}
func NewNetworkFloodCommand(dep fx.Option, options *core.NetworkCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "flood",
Short: "generate a mount of network traffic by using iperf client",
Run: func(cmd *cobra.Command, args []string) {
options.Action = core.NetworkFloodAction
options.CompleteDefaults()
utils.FxNewAppWithoutLog(dep, fx.Invoke(commonNetworkAttackFunc)).Run()
},
}
cmd.Flags().StringVarP(&options.Rate, "rate", "r", "", "the speed of network traffic, allows bps, kbps, mbps, gbps, tbps unit. bps means bytes per second")
cmd.Flags().StringVarP(&options.IPAddress, "ip", "i", "", "generate traffic to this IP address")
cmd.Flags().StringVarP(&options.Port, "port", "p", "", "generate traffic to this port on the IP address")
cmd.Flags().Int32VarP(&options.Parallel, "parallel", "", 1, "number of iperf parallel client threads to run")
cmd.Flags().StringVarP(&options.Duration, "duration", "", "99999999", "number of seconds to run the iperf test")
return cmd
}
func NewNICDownCommand(dep fx.Option, options *core.NetworkCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "down",
Short: "down network interface card",
Run: func(cmd *cobra.Command, args []string) {
options.Action = core.NetworkNICDownAction
options.CompleteDefaults()
utils.FxNewAppWithoutLog(dep, fx.Invoke(commonNetworkAttackFunc)).Run()
},
}
cmd.Flags().StringVarP(&options.Device, "device", "d", "", "the network interface to impact")
SetScheduleFlags(cmd, &options.SchedulerConfig)
return cmd
}

View File

@ -27,12 +27,11 @@ import (
"github.com/chaos-mesh/chaosd/pkg/utils"
)
func NewProcessAttackCommand(uid *string) *cobra.Command {
func NewProcessAttackCommand() *cobra.Command {
options := core.NewProcessCommand()
dep := fx.Options(
server.Module,
fx.Provide(func() *core.ProcessCommand {
options.UID = *uid
return options
}),
)
@ -62,7 +61,6 @@ func NewProcessKillCommand(dep fx.Option, options *core.ProcessCommand) *cobra.C
cmd.Flags().StringVarP(&options.Process, "process", "p", "", "The process name or the process ID")
cmd.Flags().IntVarP(&options.Signal, "signal", "s", 9, "The signal number to send")
cmd.Flags().StringVarP(&options.RecoverCmd, "recover-cmd", "r", "", "The command to be run when recovering experiment")
return cmd
}

View File

@ -1,157 +0,0 @@
// Copyright 2022 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package attack
import (
"fmt"
"github.com/spf13/cobra"
"go.uber.org/fx"
"github.com/chaos-mesh/chaosd/cmd/server"
"github.com/chaos-mesh/chaosd/pkg/core"
"github.com/chaos-mesh/chaosd/pkg/server/chaosd"
"github.com/chaos-mesh/chaosd/pkg/utils"
)
func NewRedisAttackCommand(uid *string) *cobra.Command {
options := core.NewRedisCommand()
dep := fx.Options(
server.Module,
fx.Provide(func() *core.RedisCommand {
options.UID = *uid
return options
}),
)
cmd := &cobra.Command{
Use: "redis <subcommand>",
Short: "Redis attack related commands",
}
cmd.AddCommand(
NewRedisSentinelRestartCommand(dep, options),
NewRedisSentinelStopCommand(dep, options),
NewRedisCachePenetrationCommand(dep, options),
NewRedisCacheLimitCommand(dep, options),
NewRedisCacheExpirationCommand(dep, options),
)
return cmd
}
func NewRedisSentinelRestartCommand(dep fx.Option, options *core.RedisCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "sentinel-restart",
Short: "restart sentinel",
Run: func(*cobra.Command, []string) {
options.Action = core.RedisSentinelRestartAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(redisAttackF)).Run()
},
}
cmd.Flags().StringVarP(&options.Addr, "addr", "a", "", "The address of redis server")
cmd.Flags().StringVarP(&options.Password, "password", "p", "", "The password of server")
cmd.Flags().StringVarP(&options.Conf, "conf", "c", "", "The config of Redis server")
cmd.Flags().BoolVarP(&options.FlushConfig, "flush-config", "", true, " Force Sentinel to rewrite its configuration on disk")
cmd.Flags().StringVarP(&options.RedisPath, "redis-path", "", "", "The path of the redis-server command")
return cmd
}
func NewRedisSentinelStopCommand(dep fx.Option, options *core.RedisCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "sentinel-stop",
Short: "stop sentinel",
Run: func(*cobra.Command, []string) {
options.Action = core.RedisSentinelStopAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(redisAttackF)).Run()
},
}
cmd.Flags().StringVarP(&options.Addr, "addr", "a", "", "The address of redis server")
cmd.Flags().StringVarP(&options.Password, "password", "p", "", "The password of server")
cmd.Flags().StringVarP(&options.Conf, "conf", "c", "", "The config path of Redis server")
cmd.Flags().BoolVarP(&options.FlushConfig, "flush-config", "", true, "Force Sentinel to rewrite its configuration on disk")
cmd.Flags().StringVarP(&options.RedisPath, "redis-path", "", "", "The path of the redis-server command")
return cmd
}
func NewRedisCachePenetrationCommand(dep fx.Option, options *core.RedisCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "cache-penetration",
Short: "penetrate cache",
Run: func(*cobra.Command, []string) {
options.Action = core.RedisCachePenetrationAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(redisAttackF)).Run()
},
}
cmd.Flags().StringVarP(&options.Addr, "addr", "a", "", "The address of redis server")
cmd.Flags().StringVarP(&options.Password, "password", "p", "", "The password of server")
cmd.Flags().IntVarP(&options.RequestNum, "request-num", "", 0, "The number of requests")
return cmd
}
func NewRedisCacheLimitCommand(dep fx.Option, options *core.RedisCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "cache-limit",
Short: "set maxmemory of Redis",
Run: func(*cobra.Command, []string) {
options.Action = core.RedisCacheLimitAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(redisAttackF)).Run()
},
}
cmd.Flags().StringVarP(&options.Addr, "addr", "a", "", "The address of redis server")
cmd.Flags().StringVarP(&options.Password, "password", "p", "", "The password of server")
cmd.Flags().StringVarP(&options.CacheSize, "size", "s", "0", "The size of cache")
cmd.Flags().StringVarP(&options.Percent, "percent", "", "", "The percentage of maxmemory")
return cmd
}
func NewRedisCacheExpirationCommand(dep fx.Option, options *core.RedisCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "cache-expiration",
Short: "expire keys in Redis",
Run: func(*cobra.Command, []string) {
options.Action = core.RedisCacheExpirationAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(redisAttackF)).Run()
},
}
cmd.Flags().StringVarP(&options.Addr, "addr", "a", "", "The address of redis server")
cmd.Flags().StringVarP(&options.Password, "password", "p", "", "The password of server")
cmd.Flags().StringVarP(&options.Key, "key", "k", "", "The key to be set a expiration, default expire all keys")
cmd.Flags().StringVarP(&options.Expiration, "expiration", "", "0", `The expiration of the key. A expiration string should be able to be converted to a time duration, such as "5s" or "30m"`)
cmd.Flags().StringVarP(&options.Option, "option", "", "", "The additional options of expiration, only NX, XX, GT, LT supported")
return cmd
}
func redisAttackF(chaos *chaosd.Server, options *core.RedisCommand) {
if err := options.Validate(); err != nil {
utils.ExitWithError(utils.ExitBadArgs, err)
}
uid, err := chaos.ExecuteAttack(chaosd.RedisAttack, options, core.CommandMode)
if err != nil {
utils.ExitWithError(utils.ExitError, err)
}
utils.NormalExit(fmt.Sprintf("Attack redis %s successfully, uid: %s", options.Action, uid))
}

View File

@ -25,12 +25,11 @@ import (
"github.com/chaos-mesh/chaosd/pkg/utils"
)
func NewStressAttackCommand(uid *string) *cobra.Command {
func NewStressAttackCommand() *cobra.Command {
options := core.NewStressCommand()
dep := fx.Options(
server.Module,
fx.Provide(func() *core.StressCommand {
options.UID = *uid
return options
}),
)
@ -54,7 +53,6 @@ func NewStressCPUCommand(dep fx.Option, options *core.StressCommand) *cobra.Comm
Short: "continuously stress CPU out",
Run: func(*cobra.Command, []string) {
options.Action = core.StressCPUAction
options.CompleteDefaults()
utils.FxNewAppWithoutLog(dep, fx.Invoke(stressAttackF)).Run()
},
}
@ -72,11 +70,11 @@ func NewStressMemCommand(dep fx.Option, options *core.StressCommand) *cobra.Comm
Short: "continuously stress virtual memory out",
Run: func(*cobra.Command, []string) {
options.Action = core.StressMemAction
options.CompleteDefaults()
utils.FxNewAppWithoutLog(dep, fx.Invoke(stressAttackF)).Run()
},
}
cmd.Flags().IntVarP(&options.Workers, "workers", "w", 1, "Workers specifies N workers to apply the stressor.")
cmd.Flags().StringVarP(&options.Size, "size", "s", "", "Size specifies N bytes consumed per vm worker, default is the total available memory. One can specify the size as % of total available memory or in units of B, KB/KiB, MB/MiB, GB/GiB, TB/TiB..")
cmd.Flags().StringSliceVarP(&options.Options, "options", "o", []string{}, "extend stress-ng options.")

View File

@ -1,64 +0,0 @@
// Copyright 2022 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package attack
import (
"fmt"
"github.com/spf13/cobra"
"go.uber.org/fx"
"github.com/chaos-mesh/chaosd/cmd/server"
"github.com/chaos-mesh/chaosd/pkg/core"
"github.com/chaos-mesh/chaosd/pkg/server/chaosd"
"github.com/chaos-mesh/chaosd/pkg/utils"
)
func NewUserDefinedCommand(uid *string) *cobra.Command {
options := core.NewUserDefinedOption()
dep := fx.Options(
server.Module,
fx.Provide(func() *core.UserDefinedOption {
options.UID = *uid
return options
}),
)
cmd := &cobra.Command{
Use: "user-defined <subcommand>",
Short: "user defined attack related commands",
Run: func(*cobra.Command, []string) {
options.CompleteDefaults()
utils.FxNewAppWithoutLog(dep, fx.Invoke(userDefinedAttackFunc)).Run()
},
}
cmd.Flags().StringVarP(&options.AttackCmd, "attack-cmd", "a", "", "the command to be executed when attack")
cmd.Flags().StringVarP(&options.RecoverCmd, "recover-cmd", "r", "", "the command to be executed when recover")
return cmd
}
func userDefinedAttackFunc(options *core.UserDefinedOption, chaos *chaosd.Server) {
if err := options.Validate(); err != nil {
utils.ExitWithError(utils.ExitBadArgs, err)
}
uid, err := chaos.ExecuteAttack(chaosd.UserDefinedAttack, options, core.CommandMode)
if err != nil {
utils.ExitWithError(utils.ExitError, err)
}
utils.NormalExit(fmt.Sprintf("Attack user defined comamnd successfully, uid: %s", uid))
}

View File

@ -1,58 +0,0 @@
// Copyright 2022 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package attack
import (
"fmt"
"github.com/spf13/cobra"
"go.uber.org/fx"
"github.com/chaos-mesh/chaosd/cmd/server"
"github.com/chaos-mesh/chaosd/pkg/core"
"github.com/chaos-mesh/chaosd/pkg/server/chaosd"
"github.com/chaos-mesh/chaosd/pkg/utils"
)
func NewVMAttackCommand(uid *string) *cobra.Command {
options := core.NewVMOption()
dep := fx.Options(
server.Module,
fx.Provide(func() *core.VMOption {
options.UID = *uid
return options
}),
)
cmd := &cobra.Command{
Use: "vm attack",
Short: "vm attack by using virsh",
Run: func(*cobra.Command, []string) {
options.Action = core.VMAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(vmAttack)).Run()
},
}
cmd.Flags().StringVarP(&options.VMName, "vm-name", "v", "", "The name of the vm to be destoryed")
return cmd
}
func vmAttack(options *core.VMOption, chaos *chaosd.Server) {
uid, err := chaos.ExecuteAttack(chaosd.VMAttack, options, core.CommandMode)
if err != nil {
utils.ExitWithError(utils.ExitError, err)
}
utils.NormalExit(fmt.Sprintf("VM attack %v successfully, uid: %s", options, uid))
}

View File

@ -1,89 +0,0 @@
// Copyright 2022 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package completion
import (
"os"
"github.com/spf13/cobra"
"github.com/chaos-mesh/chaosd/pkg/utils"
)
// NewCompletionCommand returns the completion command
func NewCompletionCommand() *cobra.Command {
return &cobra.Command{
Use: "completion [bash|zsh|fish|powershell]",
Short: "Generate completion script",
Long: `To load completions:
Bash:
$ source <(./bin/chaosd completion bash)
# To load completions for each session, execute once:
Linux:
$ ./bin/chaosd completion bash > /etc/bash_completion.d/chaosd
MacOS:
$ ./bin/chaosd completion bash > /usr/local/etc/bash_completion.d/chaosd
Zsh:
$ compdef _chaosd ./bin/chaosd
# If shell completion is not already enabled in your environment you will need
# to enable it. You can execute the following once:
$ echo "autoload -U compinit; compinit" >> ~/.zshrc
# To load completions for each session, execute once:
$ ./bin/chaosd completion zsh > "${fpath[1]}/_chaosd"
# You will need to start a new shell for this setup to take effect.
Fish:
$ ./bin/chaosd completion fish | source
# To load completions for each session, execute once:
$ ./bin/chaosd completion fish > ~/.config/fish/completions/chaosd.fish
Powershell:
PS> ./bin/chaosd completion powershell | Out-String | Invoke-Expression
# To load completions for every new session, run:
PS> ./bin/chaosd completion powershell > chaosd.ps1
# and source this file from your powershell profile.
`,
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.ExactValidArgs(1),
Run: func(cmd *cobra.Command, args []string) {
switch args[0] {
case "bash":
if err := cmd.Root().GenBashCompletion(os.Stdout); err != nil {
utils.ExitWithError(utils.ExitError, err)
}
case "zsh":
if err := cmd.Root().GenZshCompletion(os.Stdout); err != nil {
utils.ExitWithError(utils.ExitError, err)
}
case "fish":
if err := cmd.Root().GenFishCompletion(os.Stdout, true); err != nil {
utils.ExitWithError(utils.ExitError, err)
}
}
},
}
}

BIN
cmd/main Executable file

Binary file not shown.

View File

@ -21,11 +21,8 @@ import (
"github.com/spf13/cobra"
_ "github.com/swaggo/swag"
"go.uber.org/zap"
ctrl "sigs.k8s.io/controller-runtime"
ctrlzap "sigs.k8s.io/controller-runtime/pkg/log/zap"
"github.com/chaos-mesh/chaosd/cmd/attack"
"github.com/chaos-mesh/chaosd/cmd/completion"
"github.com/chaos-mesh/chaosd/cmd/recover"
"github.com/chaos-mesh/chaosd/cmd/search"
"github.com/chaos-mesh/chaosd/cmd/server"
@ -42,8 +39,8 @@ var rootCmd = &cobra.Command{
}
func init() {
cobra.OnInitialize(setLog)
rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "", "", "the log level of chaosd. The value can be 'debug', 'info', 'warn' and 'error'")
cobra.OnInitialize(setLogLevel)
rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "", "", "the log level of chaosd, the value can be 'debug', 'info', 'warn' and 'error'")
rootCmd.AddCommand(
server.NewServerCommand(),
@ -51,13 +48,12 @@ func init() {
recover.NewRecoverCommand(),
search.NewSearchCommand(),
version.NewVersionCommand(),
completion.NewCompletionCommand(),
)
_ = utils.SetRuntimeEnv()
}
func setLog() {
func setLogLevel() {
conf := &log.Config{Level: logLevel}
lg, r, err := log.InitLogger(conf)
if err != nil {
@ -66,9 +62,6 @@ func setLog() {
}
log.ReplaceGlobals(lg, r)
// set log of controller-runtime, so that can print logs in chaos mesh
ctrl.SetLogger(ctrlzap.New(ctrlzap.UseDevMode(true)))
// only in debug mode print log of go.uber.org/fx
if strings.ToLower(logLevel) == "debug" {
utils.PrintFxLog = true

View File

@ -1,46 +0,0 @@
// Copyright 2021 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package recover
import (
"github.com/pkg/errors"
"github.com/chaos-mesh/chaosd/pkg/core"
"github.com/chaos-mesh/chaosd/pkg/server/chaosd"
)
type completionContext struct {
uids chan string
err chan error
}
func newCompletionCtx() *completionContext {
return &completionContext{
uids: make(chan string),
err: make(chan error),
}
}
func listUid(ctx *completionContext, chaos *chaosd.Server) {
exps, err := chaos.Search(&core.SearchCommand{Status: core.Success})
if err != nil {
ctx.err <- errors.Wrap(err, "list exp")
return
}
for _, exp := range exps {
ctx.uids <- exp.Uid
}
close(ctx.uids)
}

View File

@ -14,15 +14,11 @@
package recover
import (
"context"
"fmt"
"github.com/spf13/cobra"
"go.uber.org/fx"
"github.com/pingcap/errors"
"github.com/pingcap/log"
"github.com/chaos-mesh/chaosd/cmd/server"
"github.com/chaos-mesh/chaosd/pkg/server/chaosd"
"github.com/chaos-mesh/chaosd/pkg/utils"
@ -42,10 +38,9 @@ func NewRecoverCommand() *cobra.Command {
)
cmd := &cobra.Command{
Use: "recover UID",
Short: "Recover a chaos experiment",
Args: cobra.MinimumNArgs(1),
ValidArgsFunction: completeUid,
Use: "recover UID",
Short: "Recover a chaos experiment",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
utils.ExitWithMsg(utils.ExitBadArgs, "UID is required")
@ -65,33 +60,3 @@ func recoverCommandF(chaos *chaosd.Server, options *recoverCommand) {
utils.NormalExit(fmt.Sprintf("Recover %s successfully", options.uid))
}
func completeUid(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
completionCtx := newCompletionCtx()
completionDep := fx.Options(
server.Module,
fx.Provide(func() *completionContext {
return completionCtx
}),
)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
if err := utils.FxNewAppWithoutLog(completionDep, fx.Invoke(listUid)).Start(ctx); err != nil {
log.Error(errors.Wrap(err, "start application").Error())
}
}()
var uids []string
for {
select {
case uid := <-completionCtx.uids:
if len(uid) == 0 {
return uids, cobra.ShellCompDirectiveNoFileComp
}
uids = append(uids, uid)
case err := <-completionCtx.err:
log.Error(err.Error())
return nil, cobra.ShellCompDirectiveNoFileComp
}
}
}

View File

@ -51,7 +51,7 @@ func NewSearchCommand() *cobra.Command {
cmd.Flags().StringVarP(&options.Status, "status", "s", "", "attack status, "+
"supported value: created, success, error, destroyed, revoked")
cmd.Flags().StringVarP(&options.Kind, "kind", "k", "", "attack kind, "+
"supported value: network, process, stress, disk, host, jvm")
"supported value: network, process")
cmd.Flags().Uint32VarP(&options.Offset, "offset", "o", 0, "starting to search attacks from offset")
cmd.Flags().Uint32VarP(&options.Limit, "limit", "l", 0, "limit the count of attacks")
cmd.Flags().BoolVar(&options.Asc, "asc", false, "order by CreateTime, "+

View File

@ -30,13 +30,8 @@ func NewServerCommand() *cobra.Command {
Run: serverCommandFunc,
}
cmd.Flags().IntVarP(&conf.ListenPort, "port", "p", 31767, "listen port of the Chaosd http Server")
cmd.Flags().IntVar(&conf.ListenHttpsPort, "https-port", 31768, "listen port of the Chaosd https Server")
cmd.Flags().IntVarP(&conf.ListenPort, "port", "p", 31767, "listen port of the Chaosd Server")
cmd.Flags().StringVarP(&conf.ListenHost, "host", "a", "0.0.0.0", "listen host of the Chaosd Server")
cmd.Flags().StringVar(&conf.SSLCertFile, "cert", "", "path to a PEM encoded certificate file")
cmd.Flags().StringVar(&conf.SSLKeyFile, "key", "", "path to a PEM encoded private key file")
cmd.Flags().StringVar(&conf.SSLClientCAFile, "CA", "", "path to a PEM encoded CA's certificate file")
cmd.Flags().StringVar(&conf.ServerName, "server-name", "chaosd.chaos-mesh.org", "server name is used to verify the hostname on the returned certificates")
cmd.Flags().StringVarP(&conf.Runtime, "runtime", "r", "docker", "current container runtime")
cmd.Flags().BoolVar(&conf.EnablePprof, "enable-pprof", true, "enable pprof")
cmd.Flags().IntVar(&conf.PprofPort, "pprof-port", 31766, "listen port of the pprof server")

230
go.mod
View File

@ -1,199 +1,73 @@
module github.com/chaos-mesh/chaosd
require (
github.com/Jeffail/tunny v0.1.4
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
github.com/alecthomas/units v0.0.0-20210927113745-59d0afb8317a
github.com/chaos-mesh/chaos-mesh v0.9.1-0.20220812140450-4bc7ef589c13
github.com/chaos-mesh/chaos-mesh/api v0.0.0
github.com/containerd/containerd v1.5.10
github.com/docker/docker v20.10.7+incompatible
github.com/dustin/go-humanize v1.0.0
github.com/gin-gonic/gin v1.8.1
github.com/go-logr/logr v1.2.0
github.com/go-logr/zapr v1.2.0
github.com/go-redis/redis/v8 v8.11.5
github.com/google/uuid v1.2.0
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf
github.com/chaos-mesh/chaos-mesh v0.9.1-0.20210329064057-23471399d8f4
github.com/chaos-mesh/chaos-mesh/api/v1alpha1 v0.0.0
github.com/containerd/containerd v1.2.3
github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0
github.com/gin-gonic/gin v1.6.3
github.com/go-logr/zapr v0.1.0
github.com/google/uuid v1.1.1
github.com/hashicorp/go-multierror v1.1.0
github.com/joomcode/errorx v1.0.1
github.com/magiconair/properties v1.8.5
github.com/olekukonko/tablewriter v0.0.5
github.com/onsi/gomega v1.18.1
github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936
github.com/olekukonko/tablewriter v0.0.4
github.com/onsi/gomega v1.9.0
github.com/pingcap/errors v0.11.5-0.20190809092503-95897b64e011
github.com/pingcap/failpoint v0.0.0-20200210140405-f8f9fb234798
github.com/pingcap/log v0.0.0-20200117041106-d28c14d3b1cd
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.11.0
github.com/robfig/cron/v3 v3.0.1
github.com/samber/lo v1.37.0
github.com/samber/mo v1.8.0
github.com/segmentio/kafka-go v0.4.31
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/spf13/cobra v1.4.0
github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7
github.com/spf13/cobra v1.1.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.1
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe
github.com/swaggo/gin-swagger v1.5.0
github.com/swaggo/swag v1.8.3
go.uber.org/fx v1.17.1
go.uber.org/zap v1.21.0
google.golang.org/grpc v1.40.0
github.com/stretchr/testify v1.7.0
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14
github.com/swaggo/gin-swagger v1.2.0
github.com/swaggo/swag v1.6.7
go.uber.org/fx v1.13.1
go.uber.org/zap v1.15.0
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a // indirect
golang.org/x/text v0.3.3 // indirect
google.golang.org/grpc v1.27.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
gorm.io/driver/sqlite v1.1.4
gorm.io/gorm v1.20.7
k8s.io/api v0.23.1
k8s.io/apimachinery v0.23.1
sigs.k8s.io/controller-runtime v0.11.0
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/Microsoft/go-winio v0.5.0 // indirect
github.com/Microsoft/hcsshim v0.8.23 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.2.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chaos-mesh/chaos-driver v0.2.1 // indirect
github.com/cilium/ebpf v0.6.2 // indirect
github.com/containerd/cgroups v1.0.2-0.20210605143700-23b51209bf7b // indirect
github.com/containerd/continuity v0.1.0 // indirect
github.com/containerd/fifo v1.0.0 // indirect
github.com/containerd/ttrpc v1.1.0 // indirect
github.com/containerd/typeurl v1.0.2 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/ethereum/go-ethereum v1.10.2 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/spec v0.20.6 // indirect
github.com/go-openapi/swag v0.21.1 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.11.0 // indirect
github.com/go-stack/stack v1.8.0 // indirect
github.com/goccy/go-json v0.9.7 // indirect
github.com/godbus/dbus/v5 v5.0.4 // indirect
github.com/gogo/googleapis v1.4.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.5 // indirect
github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa // indirect
github.com/googleapis/gnostic v0.5.5 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.14.2 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/mattn/go-sqlite3 v2.0.1+incompatible // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/sys/mountinfo v0.4.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/opencontainers/runc v1.0.2 // indirect
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 // indirect
github.com/opencontainers/selinux v1.8.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.2 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.28.0 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
github.com/retailnext/iptables_exporter v0.1.0 // indirect
github.com/romana/ipset v1.0.0 // indirect
github.com/romana/rlog v0.0.0-20171115192701-f018bc92e7d7 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.4.0 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852 // indirect
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // indirect
github.com/xdg/stringprep v1.0.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
go.opencensus.io v0.23.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/dig v1.14.1 // indirect
go.uber.org/multierr v1.8.0 // indirect
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
golang.org/x/tools v0.1.11 // indirect
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.23.0 // indirect
k8s.io/client-go v0.23.1 // indirect
k8s.io/component-base v0.23.1 // indirect
k8s.io/cri-api v0.20.6 // indirect
k8s.io/klog/v2 v2.30.0 // indirect
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b // indirect
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.0 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
k8s.io/api v0.17.0
k8s.io/apimachinery v0.17.0
sigs.k8s.io/controller-runtime v0.4.0
)
replace (
// github.com/chaos-mesh/chaos-mesh require /api/v1alpha1 v0.0.0, but v0.0.0 can not be found, so use replace here
github.com/chaos-mesh/chaos-mesh/api => github.com/chaos-mesh/chaos-mesh/api v0.0.0-20220717162241-8644a0680800
google.golang.org/grpc => google.golang.org/grpc v1.33.2
k8s.io/api => k8s.io/api v0.23.1
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.23.1
k8s.io/apimachinery => k8s.io/apimachinery v0.23.1
k8s.io/apiserver => k8s.io/apiserver v0.23.1
k8s.io/cli-runtime => k8s.io/cli-runtime v0.23.1
k8s.io/client-go => k8s.io/client-go v0.23.1
k8s.io/cloud-provider => k8s.io/cloud-provider v0.23.1
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.23.1
k8s.io/code-generator => k8s.io/code-generator v0.23.1
k8s.io/component-base => k8s.io/component-base v0.23.1
k8s.io/cri-api => k8s.io/cri-api v0.23.1
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.23.1
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.23.1
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.23.1
k8s.io/kube-proxy => k8s.io/kube-proxy v0.23.1
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.23.1
k8s.io/kubectl => k8s.io/kubectl v0.23.1
k8s.io/kubelet => k8s.io/kubelet v0.23.1
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.23.1
k8s.io/metrics => k8s.io/metrics v0.23.1
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.23.1
github.com/chaos-mesh/chaos-mesh/api/v1alpha1 => github.com/chaos-mesh/chaos-mesh/api/v1alpha1 v0.0.0-20210329070828-9be168b2b489
google.golang.org/grpc => google.golang.org/grpc v1.26.0
k8s.io/api => k8s.io/api v0.17.0
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.17.0
k8s.io/apimachinery => k8s.io/apimachinery v0.17.1-beta.0
k8s.io/apiserver => k8s.io/apiserver v0.17.0
k8s.io/cli-runtime => k8s.io/cli-runtime v0.17.0
k8s.io/client-go => k8s.io/client-go v0.17.0
k8s.io/cloud-provider => k8s.io/cloud-provider v0.17.0
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.17.0
k8s.io/code-generator => k8s.io/code-generator v0.17.1-beta.0
k8s.io/component-base => k8s.io/component-base v0.17.0
k8s.io/cri-api => k8s.io/cri-api v0.17.1-beta.0
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.17.0
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.17.0
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.17.0
k8s.io/kube-proxy => k8s.io/kube-proxy v0.17.0
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.17.0
k8s.io/kubectl => k8s.io/kubectl v0.17.0
k8s.io/kubelet => k8s.io/kubelet v0.17.0
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.17.0
k8s.io/metrics => k8s.io/metrics v0.17.0
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.17.0
vbom.ml/util => github.com/fvbommel/util v0.0.2
)
go 1.18
go 1.14

1820
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -17,14 +17,16 @@ set -euo pipefail
function chaosd::version::get_version_vars() {
if [[ -n ${GIT_COMMIT-} ]] || GIT_COMMIT=$(git rev-parse "HEAD^{commit}" 2>/dev/null); then
# Use git describe to find the version based on tags.
# --always would show the unique abbr commit as fallback.
if [[ -n ${GIT_VERSION-} ]] || GIT_VERSION=$(git describe --always --tags --abbrev=14 "${GIT_COMMIT}^{commit}" 2>/dev/null); then
# if current commit is not on a certain tag
if ! git describe --tags --exact-match >/dev/null 2>&1 ; then
# GIT_VERSION=gitBranch-gitCommitHash
IFS='-' read -ra GIT_ARRAY <<< "$GIT_VERSION"
GIT_VERSION=$(git rev-parse --abbrev-ref HEAD)-${GIT_ARRAY[${#GIT_ARRAY[@]}-1]}
if [[ -n ${GIT_VERSION-} ]] || GIT_VERSION=$(git describe --tags --abbrev=14 "${GIT_COMMIT}^{commit}" 2>/dev/null); then
DASHES_IN_VERSION=$(echo "${GIT_VERSION}" | sed "s/[^-]//g")
if [[ "${DASHES_IN_VERSION}" == "---" ]] ; then
# We have distance to subversion (v1.1.0-subversion-1-gCommitHash)
GIT_VERSION=$(echo "${GIT_VERSION}" | sed "s/-\([0-9]\{1,\}\)-g\([0-9a-f]\{14\}\)$/.\1\+\2/")
elif [[ "${DASHES_IN_VERSION}" == "--" ]] ; then
# We have distance to base tag (v1.1.0-1-gCommitHash)
GIT_VERSION=$(echo "${GIT_VERSION}" | sed "s/-g\([0-9a-f]\{14\}\)$/+\1/")
fi
fi
fi

View File

@ -26,17 +26,12 @@ type Config struct {
Version bool
ListenPort int
ListenHttpsPort int
ListenHost string
SSLCertFile string
SSLKeyFile string
SSLClientCAFile string
Runtime string
EnablePprof bool
PprofPort int
Platform string
ServerName string
ListenPort int
ListenHost string
Runtime string
EnablePprof bool
PprofPort int
Platform string
}
// Parse parses flag definitions from the argument list.
@ -53,11 +48,6 @@ func (c *Config) Address() string {
return fmt.Sprintf("%s:%d", c.ListenHost, c.ListenPort)
}
// Get the https server address
func (c *Config) HttpsServerAddress() string {
return fmt.Sprintf("%s:%d", c.ListenHost, c.ListenHttpsPort)
}
// Validate is used to validate if some configurations are right.
func (c *Config) Validate() error {
if !checkPlatform(c.Platform) {
@ -68,14 +58,6 @@ func (c *Config) Validate() error {
return errors.Errorf("container runtime %s is not supported", c.Runtime)
}
if (len(c.SSLCertFile) > 0 || len(c.SSLKeyFile) > 0) && (len(c.SSLCertFile) == 0 || len(c.SSLKeyFile) == 0) {
return errors.New("provide both certificate and private key")
}
if len(c.SSLClientCAFile) > 0 && len(c.SSLCertFile) == 0 {
return errors.New("attempting to use client CA without providing server certificate")
}
return nil
}

View File

@ -1,122 +0,0 @@
// Copyright 2021 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import (
"encoding/json"
"fmt"
"os"
"strings"
"syscall"
"time"
"github.com/pingcap/log"
"go.uber.org/zap"
"github.com/chaos-mesh/chaos-mesh/pkg/time/utils"
)
const (
ClockAction = "clock"
)
type ClockOption struct {
CommonAttackConfig
Pid int `json:"pid,omitempty"`
TimeOffset string `json:"time-offset,omitempty"`
SecDelta int64
NsecDelta int64
ClockIdsSlice string `json:"clock-ids-slice,omitempty"`
Store ClockFuncStore
ClockIdsMask uint64
}
type ClockFuncStore struct {
CodeOfGetClockFunc []byte
OriginAddress uint64
}
func NewClockOption() *ClockOption {
return &ClockOption{
CommonAttackConfig: CommonAttackConfig{
Kind: ClockAttack,
},
}
}
func (opt *ClockOption) PreProcess() error {
clkIds := strings.Split(opt.ClockIdsSlice, ",")
offset, err := time.ParseDuration(opt.TimeOffset)
if err != nil {
return err
}
opt.SecDelta = int64(offset / time.Second)
opt.NsecDelta = int64(offset % time.Second)
clockIdsMask, err := utils.EncodeClkIds(clkIds)
if err != nil {
log.Error("error while converting clock ids to mask", zap.Error(err))
return err
}
if clockIdsMask == 0 {
log.Error("clock ids must not be empty")
return fmt.Errorf("clock ids must not be empty")
}
opt.ClockIdsMask = clockIdsMask
if uint64(opt.SecDelta) > 1<<31 {
log.Warn("Monotonic clock will be broken when sec delta is too large or too small.")
if uint64(opt.SecDelta) > 1<<56 {
log.Warn("Time zone info will be broken when sec delta is too large or too small.")
}
}
if uint64(opt.NsecDelta) > 1<<56 {
log.Warn("Time will be broken when nanosecond delta is too large or too small")
}
// Since os.FindProcess in unix systems will always succeed
// regardless of whether the process exists (https://pkg.go.dev/os#FindProcess),
// we need to use process.Signal to check if pid is accessible.
process, err := os.FindProcess(opt.Pid)
if err != nil {
log.Error("failed to find process", zap.Error(err))
return err
}
err = process.Signal(syscall.Signal(0))
if err != nil {
log.Error("pid may not be accessible", zap.Error(err))
return err
}
return nil
}
func (opt *ClockOption) CompleteDefaults() {
if len(opt.ClockIdsSlice) == 0 {
opt.ClockIdsSlice = "CLOCK_REALTIME"
}
}
func (opt ClockOption) RecoverData() string {
data, _ := json.Marshal(opt)
return string(data)
}

View File

@ -33,9 +33,6 @@ type AttackConfig interface {
// CompleteDefaults is used to fill flags with default values
CompleteDefaults()
// GetUID returns the experiment's ID
GetUID() string
}
type SchedulerConfig struct {
@ -60,7 +57,6 @@ type CommonAttackConfig struct {
Action string `json:"action"`
Kind string `json:"kind"`
UID string `json:"uid"`
}
func (config CommonAttackConfig) String() string {
@ -83,7 +79,3 @@ func (config *CommonAttackConfig) Validate() error {
}
return nil
}
func (config *CommonAttackConfig) GetUID() string {
return config.UID
}

View File

@ -16,13 +16,9 @@ package core
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/pingcap/log"
"go.uber.org/zap"
"github.com/chaos-mesh/chaosd/pkg/utils"
)
@ -33,50 +29,79 @@ const (
DiskReadPayloadAction = "read-payload"
)
var _ AttackConfig = &DiskAttackConfig{}
type DiskAttackConfig struct {
CommonAttackConfig
DdOptions *[]DdOption
FAllocateOption *FAllocateOption
Path string
}
func (d DiskAttackConfig) RecoverData() string {
data, _ := json.Marshal(d)
return string(data)
}
var DdCommand = utils.Command{Name: "dd"}
type DdOption struct {
ReadPath string `dd:"if"`
WritePath string `dd:"of"`
BlockSize string `dd:"bs"`
Count string `dd:"count"`
Iflag string `dd:"iflag"`
Oflag string `dd:"oflag"`
Conv string `dd:"conv"`
}
var FAllocateCommand = utils.Command{Name: "fallocate"}
type FAllocateOption struct {
LengthOpt string `fallocate:"-"`
Length string `fallocate:"-"`
FileName string `fallocate:"-"`
}
type DiskOption struct {
CommonAttackConfig
Size string `json:"size,omitempty"`
Path string `json:"path,omitempty"`
Percent string `json:"percent,omitempty"`
PayloadProcessNum uint8 `json:"payload-process-num,omitempty"`
Size string `json:"size"`
Path string `json:"path"`
Percent string `json:"percent"`
PayloadProcessNum uint8 `json:"payload_process_num"`
FillByFallocate bool `json:"fallocate,omitempty"`
FillByFallocate bool `json:"fill_by_fallocate"`
}
var _ AttackConfig = &DiskOption{}
func (d *DiskOption) Validate() error {
if err := d.CommonAttackConfig.Validate(); err != nil {
return err
}
var byteSize uint64
var err error
if d.Size == "" {
if d.Percent == "" {
return fmt.Errorf("one of percent and size must not be empty, DiskOption : %v", d)
}
if byteSize, err = strconv.ParseUint(d.Percent, 10, 0); err != nil {
return fmt.Errorf("unsupport percent : %s, DiskOption : %v", d.Percent, d)
}
} else {
if byteSize, err = utils.ParseUnit(d.Size); err != nil {
return fmt.Errorf("unknown units of size : %s, DiskOption : %v", d.Size, d)
}
}
if d.Action == DiskFillAction || d.Action == DiskWritePayloadAction {
if d.Action == DiskFillAction && d.FillByFallocate && byteSize == 0 {
return fmt.Errorf("fallocate not suppurt 0 size or 0 percent data, "+
"if you want allocate a 0 size file please set fallocate=false, DiskOption : %v", d)
}
_, err := os.Stat(d.Path)
if err != nil {
if os.IsNotExist(err) {
// check if Path of file is valid when Path is not empty
if d.Path != "" {
var b []byte
if err := ioutil.WriteFile(d.Path, b, 0644); err != nil {
return err
}
if err := os.Remove(d.Path); err != nil {
return err
}
}
} else {
return err
}
} else {
if d.Action == DiskFillAction {
return fmt.Errorf("fill into an existing file")
}
return fmt.Errorf("write into an existing file")
}
}
if d.PayloadProcessNum == 0 {
return fmt.Errorf("unsupport process num : %d, DiskOption : %v", d.PayloadProcessNum, d.Action)
}
return nil
}
func (d DiskOption) RecoverData() string {
data, _ := json.Marshal(d)
return string(data)
}
func NewDiskOption() *DiskOption {
@ -84,205 +109,5 @@ func NewDiskOption() *DiskOption {
CommonAttackConfig: CommonAttackConfig{
Kind: DiskAttack,
},
PayloadProcessNum: 1,
FillByFallocate: true,
}
}
func NewDiskOptionForServer() *DiskOption {
return &DiskOption{
CommonAttackConfig: CommonAttackConfig{
Kind: DiskServerAttack,
},
PayloadProcessNum: 1,
FillByFallocate: true,
}
}
func (opt *DiskOption) PreProcess() (*DiskAttackConfig, error) {
if err := opt.CommonAttackConfig.Validate(); err != nil {
return nil, err
}
path, err := initPath(opt)
if err != nil {
return nil, err
}
byteSize, err := initSize(opt)
if err != nil {
return nil, err
}
if opt.Action == DiskFillAction && opt.FillByFallocate && byteSize != 0 {
return &DiskAttackConfig{
CommonAttackConfig: opt.CommonAttackConfig,
DdOptions: nil,
FAllocateOption: &FAllocateOption{
LengthOpt: "-l",
Length: strconv.FormatUint(byteSize, 10),
FileName: path,
},
Path: path,
}, nil
}
ddOptions, err := initDdOptions(opt, path, byteSize)
if err != nil {
return nil, err
}
return &DiskAttackConfig{
CommonAttackConfig: opt.CommonAttackConfig,
DdOptions: &ddOptions,
FAllocateOption: nil,
Path: path,
}, nil
}
func initDdOptions(opt *DiskOption, path string, byteSize uint64) ([]DdOption, error) {
ddBlocks, err := utils.SplitBytesByProcessNum(byteSize, opt.PayloadProcessNum)
if err != nil {
log.Error("fail to split disk size", zap.Error(err))
return nil, err
}
var ddOpts []DdOption
switch opt.Action {
case DiskFillAction:
for _, block := range ddBlocks {
ddOpts = append(ddOpts, DdOption{
ReadPath: "/dev/zero",
WritePath: path,
BlockSize: block.BlockSize,
Count: block.Count,
Iflag: "fullblock", // fullblock : accumulate full blocks of input.
Oflag: "append",
Conv: "notrunc", // notrunc : do not truncate the output file.
})
}
case DiskWritePayloadAction:
for _, block := range ddBlocks {
ddOpts = append(ddOpts, DdOption{
ReadPath: "/dev/zero",
WritePath: path,
BlockSize: block.BlockSize,
Count: block.Count,
Oflag: "dsync", // dsync : use synchronized I/O for data.
})
}
case DiskReadPayloadAction:
for _, block := range ddBlocks {
ddOpts = append(ddOpts, DdOption{
ReadPath: path,
WritePath: "/dev/null",
BlockSize: block.BlockSize,
Count: block.Count,
Iflag: "dsync,fullblock,nocache", // nocache : Request to drop cache.
})
}
}
return ddOpts, nil
}
func initPath(opt *DiskOption) (string, error) {
switch opt.Action {
case DiskFillAction, DiskWritePayloadAction:
if opt.Path == "" {
var err error
opt.Path, err = os.Getwd()
if err != nil {
log.Error("unexpected err when execute os.Getwd()", zap.Error(err))
return "", err
}
}
fi, err := os.Stat(opt.Path)
if err != nil {
// check if Path of file is valid when Path is not empty
if os.IsNotExist(err) {
var b []byte
if err := os.WriteFile(opt.Path, b, 0600); err != nil {
return "", err
}
if err := os.Remove(opt.Path); err != nil {
return "", err
}
return opt.Path, nil
}
return "", err
}
if fi.IsDir() {
opt.Path, err = utils.CreateTempFile(opt.Path)
if err != nil {
log.Error(fmt.Sprintf("unexpected err : %v , when CreateTempFile in action %s with path %s.", err, opt.Action, opt.Path))
return "", err
}
if err := os.Remove(opt.Path); err != nil {
return "", err
}
return opt.Path, err
}
return "", fmt.Errorf("fill into an existing file")
case DiskReadPayloadAction:
if opt.Path == "" {
path, err := utils.GetRootDevice()
if err != nil {
log.Error("err when GetRootDevice in reading payload", zap.Error(err))
return "", err
}
if path == "" {
err = fmt.Errorf("can not get root device path")
log.Error(fmt.Sprintf("payload action: %s", opt.Action), zap.Error(err))
return "", err
}
return path, nil
}
var fi os.FileInfo
var err error
if fi, err = os.Stat(opt.Path); err != nil {
return "", err
}
if fi.IsDir() {
return "", fmt.Errorf("path is a dictory, path : %s", opt.Path)
}
f, err := os.Open(opt.Path)
if err != nil {
return "", err
}
err = f.Close()
if err != nil {
return "", nil
}
return opt.Path, nil
default:
return "", fmt.Errorf("unsupported action %s", opt.Action)
}
}
func initSize(opt *DiskOption) (uint64, error) {
if opt.Size != "" {
byteSize, err := utils.ParseUnit(opt.Size)
if err != nil {
log.Error(fmt.Sprintf("fail to get parse size per units , %s", opt.Size), zap.Error(err))
return 0, err
}
return byteSize, nil
} else if opt.Percent != "" {
opt.Percent = strings.Trim(opt.Percent, " %")
percent, err := strconv.ParseUint(opt.Percent, 10, 0)
if err != nil {
log.Error(fmt.Sprintf("unexcepted err when parsing disk percent '%s'", opt.Percent), zap.Error(err))
return 0, err
}
dir := filepath.Dir(opt.Path)
totalSize, err := utils.GetDiskTotalSize(dir)
if err != nil {
log.Error("fail to get disk total size", zap.Error(err))
return 0, err
}
return totalSize * percent / 100, nil
}
if opt.Action == DiskFillAction {
return 0, fmt.Errorf("one of percent and size must not be empty, DiskOption : %v", opt)
}
return 0, fmt.Errorf("size must not be empty, DiskOption : %v", opt)
}

View File

@ -1,55 +0,0 @@
// Copyright 2023 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_initSize(t *testing.T) {
opt := DiskOption{
CommonAttackConfig: CommonAttackConfig{
Action: DiskFillAction,
},
Size: "1024M",
}
byteSize, err := initSize(&opt)
assert.NoError(t, err)
assert.EqualValues(t, 1024<<20, byteSize)
opt.Percent = "99%"
opt.Size = ""
byteSize, err = initSize(&opt)
assert.NoError(t, err)
t.Logf("percent %s with bytesize %sGB\n", opt.Percent, strconv.Itoa(int(byteSize>>30)))
opt.Percent = ""
opt.Size = ""
_, err = initSize(&opt)
assert.Error(t, err)
}
func Test_initPath(t *testing.T) {
opt := DiskOption{
CommonAttackConfig: CommonAttackConfig{
Action: DiskFillAction,
},
Path: "/1/12/1/2/1/21",
}
_, err := initPath(&opt)
assert.Error(t, err)
}

View File

@ -31,20 +31,12 @@ const (
)
const (
ProcessAttack = "process"
NetworkAttack = "network"
StressAttack = "stress"
DiskAttack = "disk"
DiskServerAttack = "disk-server"
ClockAttack = "clock"
HostAttack = "host"
JVMAttack = "jvm"
KafkaAttack = "kafka"
RedisAttack = "redis"
FileAttack = "file"
HTTPAttack = "http"
VMAttack = "vm"
UserDefinedAttack = "userDefined"
ProcessAttack = "process"
NetworkAttack = "network"
StressAttack = "stress"
DiskAttack = "disk"
HostAttack = "host"
JVMAttack = "jvm"
)
const (
@ -85,21 +77,8 @@ func (exp *Experiment) GetRequestCommand() (AttackConfig, error) {
return exp.cachedRequestCommand, nil
}
attackConfig := GetAttackByKind(exp.Kind)
if attackConfig == nil {
return nil, perr.Errorf("chaos experiment kind %s not found", exp.Kind)
}
if err := json.Unmarshal([]byte(exp.RecoverCommand), attackConfig); err != nil {
return nil, err
}
exp.cachedRequestCommand = *attackConfig
return *attackConfig, nil
}
func GetAttackByKind(kind string) *AttackConfig {
var attackConfig AttackConfig
switch kind {
switch exp.Kind {
case ProcessAttack:
attackConfig = &ProcessCommand{}
case NetworkAttack:
@ -109,28 +88,14 @@ func GetAttackByKind(kind string) *AttackConfig {
case StressAttack:
attackConfig = &StressCommand{}
case DiskAttack:
attackConfig = &DiskAttackConfig{}
case DiskServerAttack:
attackConfig = &DiskAttackConfig{}
case JVMAttack:
attackConfig = &JVMCommand{}
case ClockAttack:
attackConfig = &ClockOption{}
case KafkaAttack:
attackConfig = &KafkaCommand{}
case RedisAttack:
attackConfig = &RedisCommand{}
case FileAttack:
attackConfig = &FileCommand{}
case HTTPAttack:
attackConfig = &HTTPAttackConfig{}
case VMAttack:
attackConfig = &VMOption{}
case UserDefinedAttack:
attackConfig = &UserDefinedOption{}
attackConfig = &DiskOption{}
default:
return nil
return nil, perr.Errorf("chaos experiment kind %s not found", exp.Kind)
}
return &attackConfig
if err := json.Unmarshal([]byte(exp.RecoverCommand), attackConfig); err != nil {
return nil, err
}
exp.cachedRequestCommand = attackConfig
return attackConfig, nil
}

View File

@ -52,8 +52,7 @@ type ExperimentRun struct {
func (exp Experiment) NewRun() *ExperimentRun {
return &ExperimentRun{
ExperimentID: exp.ID,
// TODO: maybe need to use specified uid
UID: uuid.New().String(),
Status: RunStarted,
UID: uuid.New().String(),
Status: RunStarted,
}
}

View File

@ -1,167 +0,0 @@
// Copyright 2022 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import (
"encoding/json"
"github.com/pingcap/errors"
)
type FileCommand struct {
CommonAttackConfig
// FileName is the name of the file to be created, modified, deleted, renamed, or appended.
FileName string `json:"file-name,omitempty"`
// DirName is the directory name to create or delete.
DirName string `json:"dir-name,omitempty"`
// Privilege is the file privilege to be set.
Privilege uint32 `json:"privilege,omitempty"`
// SourceFile is the name need to be renamed.
SourceFile string `json:"source-file,omitempty"`
// DestFile is the name to be renamed.
DestFile string `json:"dest-file,omitempty"`
// Data is the data for append.
Data string `json:"data,omitempty"`
// Count is the number of times to append the data.
Count int `json:"count,omitempty"`
// OriginPrivilege used to save the file's origin privilege.
OriginPrivilege int `json:"origin-privilege,omitempty"`
// OriginStr is the origin string of the file.
OriginStr string `json:"origin-string,omitempty"`
// DestStr is the destination string of the file.
DestStr string `json:"dest-string,omitempty"`
// Line is the line number of the file to be replaced.
Line int `json:"line,omitempty"`
}
var _ AttackConfig = &FileCommand{}
const (
FileCreateAction = "create"
FileModifyPrivilegeAction = "modify"
FileDeleteAction = "delete"
FileRenameAction = "rename"
FileAppendAction = "append"
FileReplaceAction = "replace"
)
func (n *FileCommand) Validate() error {
if err := n.CommonAttackConfig.Validate(); err != nil {
return err
}
switch n.Action {
case FileCreateAction:
return n.validFileCreate()
case FileModifyPrivilegeAction:
return n.validFileModify()
case FileDeleteAction:
return n.validFileDelete()
case FileRenameAction:
return n.validFileRename()
case FileAppendAction:
return n.validFileAppend()
case FileReplaceAction:
return n.validFileReplace()
default:
return errors.Errorf("file action %s not supported", n.Action)
}
}
func (n *FileCommand) validFileCreate() error {
if len(n.FileName) == 0 && len(n.DirName) == 0 {
return errors.New("one of file-name and dir-name is required")
}
return nil
}
func (n *FileCommand) validFileModify() error {
if len(n.FileName) == 0 {
return errors.New("file name is required")
}
if n.Privilege == 0 {
return errors.New("file privilege is required")
}
return nil
}
func (n *FileCommand) validFileDelete() error {
if len(n.FileName) == 0 && len(n.DirName) == 0 {
return errors.New("one of file-name and dir-name is required")
}
return nil
}
func (n *FileCommand) validFileRename() error {
if len(n.SourceFile) == 0 || len(n.DestFile) == 0 {
return errors.New("both source file and destination file are required")
}
return nil
}
func (n *FileCommand) validFileAppend() error {
if len(n.FileName) == 0 {
return errors.New("file-name is required")
}
if len(n.Data) == 0 {
return errors.New("append data is required")
}
return nil
}
func (n *FileCommand) validFileReplace() error {
if len(n.FileName) == 0 {
return errors.New("file-name is required")
}
if len(n.OriginStr) == 0 || len(n.DestStr) == 0 {
return errors.New("both origin and destination string are required")
}
return nil
}
func (n *FileCommand) CompleteDefaults() {
switch n.Action {
case FileAppendAction:
n.setDefaultForFileAppend()
}
}
func (n *FileCommand) setDefaultForFileAppend() {
if n.Count == 0 {
n.Count = 1
}
}
func (n FileCommand) RecoverData() string {
data, _ := json.Marshal(n)
return string(data)
}
func NewFileCommand() *FileCommand {
return &FileCommand{
CommonAttackConfig: CommonAttackConfig{
Kind: FileAttack,
},
}
}

View File

@ -19,7 +19,6 @@ import (
const (
HostShutdownAction = "shutdown"
HostRebootAction = "reboot"
)
type HostCommand struct {
@ -41,7 +40,8 @@ func (h HostCommand) RecoverData() string {
func NewHostCommand() *HostCommand {
return &HostCommand{
CommonAttackConfig: CommonAttackConfig{
Kind: HostAttack,
Kind: HostAttack,
Action: HostShutdownAction,
},
}
}

View File

@ -1,176 +0,0 @@
// Copyright 2022 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import (
"encoding/json"
"os"
"path/filepath"
"strconv"
"time"
"github.com/go-logr/logr"
"github.com/go-logr/zapr"
"go.uber.org/zap"
"github.com/chaos-mesh/chaos-mesh/pkg/chaosdaemon/tproxyconfig"
"github.com/pingcap/errors"
)
const (
TargetRequest tproxyconfig.PodHttpChaosTarget = "Request"
TargetResponse tproxyconfig.PodHttpChaosTarget = "Response"
)
const (
HTTPAbortAction = "abort"
HTTPDelayAction = "delay"
HTTPConfigAction = "config"
HTTPRequestAction = "request"
)
var _ AttackConfig = &HTTPAttackConfig{}
type HTTPAttackConfig struct {
CommonAttackConfig
Config tproxyconfig.Config
ProxyPID int
Logger logr.Logger
HTTPRequestConfig
}
func (c HTTPAttackConfig) RecoverData() string {
data, _ := json.Marshal(c)
return string(data)
}
type HTTPAttackOption struct {
CommonAttackConfig
ProxyPorts []uint `json:"proxy_ports"`
Target string `json:"target"`
Port int32 `json:"port,omitempty"`
Path string `json:"path,omitempty"`
Method string `json:"method,omitempty"`
Code string `json:"code,omitempty"`
Abort bool `json:"abort"`
Delay string `json:"delay"`
FilePath string `json:"file_path,omitempty"`
HTTPRequestConfig `json:",inline"`
}
type HTTPRequestConfig struct {
// used for HTTP request, now only support GET
URL string `json:"url,omitempty"`
EnableConnPool bool `json:"enable-conn-pool,omitempty"`
Count int `json:"count,omitempty"`
}
func NewHTTPAttackOption() *HTTPAttackOption {
return &HTTPAttackOption{
CommonAttackConfig: CommonAttackConfig{
Kind: HTTPAttack,
},
}
}
func (o *HTTPAttackOption) PreProcess() (*HTTPAttackConfig, error) {
var c tproxyconfig.Config
zapLogger, err := zap.NewDevelopment()
if err != nil {
return nil, err
}
logger := zapr.NewLogger(zapLogger).WithName("HTTP Attack")
switch o.CommonAttackConfig.Action {
case HTTPAbortAction, HTTPDelayAction:
switch o.Target {
case string(TargetRequest), string(TargetResponse):
default:
return nil, errors.New("HTTP Attack Target must be Request or Response")
}
rule := tproxyconfig.PodHttpChaosBaseRule{
Target: tproxyconfig.PodHttpChaosTarget(o.Target),
Selector: tproxyconfig.PodHttpChaosSelector{},
Actions: tproxyconfig.PodHttpChaosActions{},
}
if o.Path != "" {
rule.Selector.Path = &o.Path
}
if o.Method != "" {
rule.Selector.Method = &o.Method
}
if o.Code != "" {
code, err := strconv.Atoi(o.Code)
if err != nil {
return nil, errors.Wrapf(err, "parsing %v", o)
}
codeI32 := int32(code)
rule.Selector.Code = &codeI32
}
if o.Port != 0 {
rule.Selector.Port = &o.Port
}
rule.Actions.Abort = &o.Abort
if o.CommonAttackConfig.Action == HTTPDelayAction {
rule.Actions.Abort = nil
_, err := time.ParseDuration(o.Delay)
if err != nil {
return nil, errors.Wrapf(err, "HTTP Delay")
}
rule.Actions.Delay = &o.Delay
}
ports := make([]uint32, len(o.ProxyPorts))
for i, port := range o.ProxyPorts {
ports[i] = uint32(port)
}
c.ProxyPorts = ports
c.Rules = []tproxyconfig.PodHttpChaosBaseRule{rule}
case HTTPConfigAction:
b, err := os.ReadFile(o.FilePath)
if err != nil {
return nil, errors.Wrap(err, "read HTTP attack config file")
}
ext := filepath.Ext(o.FilePath)
switch ext {
case ".json":
err := json.Unmarshal(b, &c)
if err != nil {
return nil, errors.Wrap(err, "json unmarshal HTTP attack config file")
}
default:
return nil, errors.Errorf("ext: %s, is not support", ext)
}
case HTTPRequestAction:
if o.URL == "" {
return nil, errors.New("URL is required")
}
default:
return nil, errors.Errorf("unsupported action: %s", o.CommonAttackConfig.Action)
}
if len(c.ProxyPorts) == 0 && o.CommonAttackConfig.Action != HTTPRequestAction {
return nil, errors.New("proxy_ports is not an option, you must offer it")
}
return &HTTPAttackConfig{
CommonAttackConfig: o.CommonAttackConfig,
Config: c,
Logger: logger,
HTTPRequestConfig: o.HTTPRequestConfig,
}, nil
}

View File

@ -23,201 +23,112 @@ import (
)
const (
// jvm action
JVMInstallType = "install"
JVMSubmitType = "submit"
JVMLatencyAction = "latency"
JVMExceptionAction = "exception"
JVMReturnAction = "return"
JVMStressAction = "stress"
JVMGCAction = "gc"
JVMRuleFileAction = "rule-file"
JVMRuleDataAction = "rule-data"
JVMMySQLAction = "mysql"
// for action 'mysql', 'gc' and 'stress'
SQLHelper = "org.chaos_mesh.byteman.helper.SQLHelper"
GCHelper = "org.chaos_mesh.byteman.helper.GCHelper"
StressHelper = "org.chaos_mesh.byteman.helper.StressHelper"
// the trigger point for 'gc' and 'stress'
TriggerClass = "org.chaos_mesh.chaos_agent.TriggerThread"
TriggerMethod = "triggerFunc"
MySQL5InjectClass = "com.mysql.jdbc.MysqlIO"
MySQL5InjectMethod = "sqlQueryDirect"
MySQL5Exception = "java.sql.SQLException(\"%s\")"
MySQL8InjectClass = "com.mysql.cj.NativeSession"
MySQL8InjectMethod = "execSQL"
MySQL8Exception = "com.mysql.cj.exceptions.CJException(\"%s\")"
)
// byteman rule template
const (
SimpleRuleTemplate = `
RULE {{.Name}}
CLASS {{.Class}}
METHOD {{.Method}}
AT ENTRY
IF true
DO
{{.Do}};
ENDRULE
`
CompleteRuleTemplate = `
RULE {{.Name}}
CLASS {{.Class}}
METHOD {{.Method}}
HELPER {{.Helper}}
AT ENTRY
BIND {{.Bind}};
IF {{.Condition}}
DO
{{.Do}};
ENDRULE
`
JVMRuleFileAction = "rule_file"
)
type JVMCommand struct {
CommonAttackConfig
JVMCommonSpec
JVMClassMethodSpec
JVMStressSpec
JVMMySQLSpec
// rule name, should be unique, and will generate by chaosd automatically
Name string `json:"name,omitempty"`
Name string
// fault action, values can be latency, exception, return, stress, gc, rule-file, rule-data, mysql
Action string `json:"action,omitempty"`
// the return value for action 'return'
ReturnValue string `json:"value,omitempty"`
// the exception which needs to throw for action `exception`
// or the exception message needs to throw in action `mysql`
ThrowException string `json:"exception,omitempty"`
// the latency duration for action 'latency'
// or the latency duration in action `mysql`
LatencyDuration int `json:"latency,omitempty"`
// btm rule file path for action 'rule-file'
RuleFile string `json:"rule-file,omitempty"`
// RuleData used to save the rule file's data, will use it when recover, for action 'rule-data'
RuleData string `json:"rule-data,omitempty"`
}
type JVMCommonSpec struct {
// the port of agent server
Port int `json:"port,omitempty"`
// the pid of Java process which need to attach
Pid int `json:"pid,omitempty"`
}
type JVMClassMethodSpec struct {
// Java class
Class string `json:"class,omitempty"`
Class string
// the method in Java class
Method string `json:"method,omitempty"`
}
Method string
// fault action, values can be latency, exception, return, stress
Action string
// the return value for action 'return'
ReturnValue string
// the exception which needs to throw dor action `exception`
ThrowException string
// the latency duration for action 'latency'
LatencyDuration string
type JVMStressSpec struct {
// the CPU core number need to use, only set it when action is stress
CPUCount int `json:"cpu-count,omitempty"`
CPUCount int
// the memory type need to locate, only set it when action is stress, the value can be 'stack' or 'heap'
MemoryType string `json:"mem-type,omitempty"`
}
// the memory size need to locate, only set it when action is stress
MemorySize int
// JVMMySQLSpec is the specification of MySQL fault injection in JVM
// only when SQL match the Database, Table and SQLType, chaosd will inject fault
// for example:
//
// SQL is "select * from test.t1",
// only when ((Database == "test" || Database == "") && (Table == "t1" || Table == "") && (SQLType == "select" || SQLType == "")) is true, chaosd will inject fault
type JVMMySQLSpec struct {
// the version of mysql-connector-java, only support 5.X.X(set to 5) and 8.X.X(set to 8) now
MySQLConnectorVersion string `json:"mysql-connector-version,omitempty"`
// attach or agent
Type string
// the match database
// default value is "", means match all database
Database string `json:"database,omitempty"`
// the port of agent server
Port int
// the match table
// default value is "", means match all table
Table string `json:"table,omitempty"`
// the pid of Java process which need to attach
Pid int
// the match sql type
// default value is "", means match all SQL type
SQLType string `json:"sql-type,omitempty"`
}
// below is only used for template
Do string
type BytemanTemplateSpec struct {
Name string
Class string
Method string
Helper string
Bind string
Condition string
Do string
StressType string
// below is only used for stress template
StressType string
StressValueName string
StressValue string
StressValue int
// btm rule file path
RuleFile string
// RuleData used to save the rule file's data, will use it when recover
RuleData []byte
}
func (j *JVMCommand) Validate() error {
if j.Pid == 0 {
return errors.New("pid can't be 0")
}
switch j.Type {
case JVMInstallType:
if j.Pid == 0 {
return errors.New("pid can't be 0")
}
case JVMSubmitType:
switch j.Action {
case JVMStressAction:
if j.CPUCount == 0 && j.MemorySize == 0 {
return errors.New("must set one of cpu-count and mem-size when action is 'stress'")
}
switch j.Action {
case JVMStressAction:
if j.CPUCount == 0 && len(j.MemoryType) == 0 {
return errors.New("must set one of cpu-count and mem-type when action is 'stress'")
if j.CPUCount > 0 && j.MemorySize > 0 {
return errors.New("inject stress on both CPU and memory is not support now")
}
case JVMGCAction:
// do nothing
case JVMExceptionAction, JVMReturnAction, JVMLatencyAction:
if len(j.Class) == 0 {
return errors.New("class not provided")
}
if len(j.Method) == 0 {
return errors.New("method not provided")
}
case JVMRuleFileAction:
if len(j.RuleFile) == 0 {
return errors.New("rule file not provided")
}
case "":
return errors.New("action not provided, action can be 'latency', 'exception', 'return', 'stress' or 'gc'")
default:
return errors.New(fmt.Sprintf("action %s not supported, action can be 'latency', 'exception', 'return', 'stress' or 'gc'", j.Action))
}
if j.CPUCount > 0 && len(j.MemoryType) > 0 {
return errors.New("inject stress on both CPU and memory is not support now")
}
case JVMGCAction:
// do nothing
case JVMExceptionAction, JVMReturnAction, JVMLatencyAction:
if len(j.Class) == 0 {
return errors.New("class not provided")
}
if len(j.JVMClassMethodSpec.Method) == 0 {
return errors.New("method not provided")
}
case JVMRuleFileAction:
if len(j.RuleFile) == 0 {
return errors.New("rule file not provided")
}
case JVMRuleDataAction:
if len(j.RuleData) == 0 {
return errors.New("rule data not provide")
}
case JVMMySQLAction:
if len(j.MySQLConnectorVersion) == 0 {
return errors.New("MySQL connector version not provided")
}
if len(j.ThrowException) == 0 && j.LatencyDuration == 0 {
return errors.New("must set one of exception or latency")
}
case "":
return errors.New("action not provided")
return errors.New("type not provided, type can be 'install' or 'submit'")
default:
return errors.New(fmt.Sprintf("action %s not supported, action can be 'latency', 'exception', 'return', 'stress', 'gc', 'rule-file' of 'rule-data'", j.Action))
return errors.New(fmt.Sprintf("type %s not supported, type can be 'install' or 'submit'", j.Type))
}
return nil
@ -230,12 +141,10 @@ func (j *JVMCommand) RecoverData() string {
}
func (j *JVMCommand) CompleteDefaults() {
if len(j.Name) == 0 {
j.Name = fmt.Sprintf("%s-%s-%s-%s", j.Class, j.Method, j.Action, utils.RandomStringWithCharset(5))
}
if j.Port == 0 {
j.Port = 9288
if j.Type == JVMSubmitType {
if len(j.Name) == 0 {
j.Name = fmt.Sprintf("%s-%s-%s-%s-%s", j.Class, j.Method, j.Action, j.Type, utils.RandomStringWithCharset(5))
}
}
}

View File

@ -28,90 +28,79 @@ func TestJVMCommand(t *testing.T) {
}{
{
&JVMCommand{},
"type not provided",
},
{
&JVMCommand{
Type: JVMInstallType,
},
"pid can't be 0",
},
{
&JVMCommand{
JVMCommonSpec: JVMCommonSpec{
Pid: 1234,
},
Type: JVMInstallType,
Pid: 123,
},
"",
},
{
&JVMCommand{
Type: JVMSubmitType,
},
"action not provided",
},
{
&JVMCommand{
JVMCommonSpec: JVMCommonSpec{
Pid: 1234,
},
Type: JVMSubmitType,
Action: "test",
},
"action test not supported",
},
{
&JVMCommand{
JVMCommonSpec: JVMCommonSpec{
Pid: 1234,
},
Type: JVMSubmitType,
Action: JVMLatencyAction,
},
"class not provided",
},
{
&JVMCommand{
JVMCommonSpec: JVMCommonSpec{
Pid: 1234,
},
Type: JVMSubmitType,
Action: JVMExceptionAction,
JVMClassMethodSpec: JVMClassMethodSpec{
Class: "test",
},
Class: "test",
},
"method not provided",
},
{
&JVMCommand{
JVMCommonSpec: JVMCommonSpec{
Pid: 1234,
},
Type: JVMSubmitType,
Action: JVMExceptionAction,
JVMClassMethodSpec: JVMClassMethodSpec{
Class: "test",
Method: "test",
},
Class: "test",
Method: "test",
},
"",
},
{
&JVMCommand{
JVMCommonSpec: JVMCommonSpec{
Pid: 1234,
},
Type: JVMSubmitType,
Action: JVMStressAction,
},
"must set one of cpu-count and mem-type",
"must set one of cpu-count and mem-size",
},
{
&JVMCommand{
JVMCommonSpec: JVMCommonSpec{
Pid: 1234,
},
Action: JVMStressAction,
JVMStressSpec: JVMStressSpec{
CPUCount: 1,
MemoryType: "heap",
},
Type: JVMSubmitType,
Action: JVMStressAction,
CPUCount: 1,
MemorySize: 1,
},
"inject stress on both CPU and memory is not support now",
},
{
&JVMCommand{
JVMCommonSpec: JVMCommonSpec{
Pid: 1234,
},
Action: JVMStressAction,
JVMStressSpec: JVMStressSpec{
CPUCount: 1,
},
Type: JVMSubmitType,
Action: JVMStressAction,
CPUCount: 1,
},
"",
},

View File

@ -1,176 +0,0 @@
// Copyright 2022 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import (
"encoding/json"
"os"
"github.com/pkg/errors"
)
type KafkaAttackAction string
const (
// Kafka actions
KafkaFillAction KafkaAttackAction = "fill"
KafkaFloodAction = "flood"
KafkaIOAction = "io"
)
type KafkaAuthMechanism string
const (
SaslPlain KafkaAuthMechanism = "sasl/plain"
SaslScream256 = "sasl/scram-sha-256"
SaslScram512 = "sasl/scram-sha-512"
AuthMechanismEmpty = ""
)
var _ AttackConfig = &KafkaCommand{}
type KafkaCommand struct {
CommonAttackConfig
// global options
Action KafkaAttackAction
Topic string `json:"topic,omitempty"`
// options for fill and flood attack
Host string `json:"host,omitempty"`
Port uint16 `json:"port,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
AuthMechanism string `json:"authMechanism,omitempty"`
MessageSize uint `json:"messageSize,omitempty"`
MaxBytes uint64 `json:"maxBytes,omitempty"`
// options for fill attack
ReloadCommand string `json:"reloadCommand,omitempty"`
// options for flood attack
Threads uint `json:"threads,omitempty"`
// options for fill and io attack
ConfigFile string `json:"configFile,omitempty"`
// options for io attack
NonReadable bool `json:"nonReadable,omitempty"`
NonWritable bool `json:"nonWritable,omitempty"`
// recover data for io attack
OriginModeOfFiles map[string]uint32 `json:"originModeOfFiles,omitempty"`
OriginConfig string `json:"originConfig,omitempty"`
}
func (c *KafkaCommand) Validate() error {
if c.Topic == "" {
return errors.New("topic is required")
}
if err := c.validateAuthMechanism(); err != nil {
return err
}
switch c.Action {
case KafkaFillAction:
return c.validateFillAction()
case KafkaFloodAction:
return c.validateFloodAction()
case KafkaIOAction:
return c.validateIOAction()
default:
return errors.Errorf("invalid action: %s", c.Action)
}
}
func (c *KafkaCommand) validateAuthMechanism() error {
if c.Username != "" && c.AuthMechanism == "" {
return errors.New("auth mechanism is required")
}
switch KafkaAuthMechanism(c.AuthMechanism) {
case SaslPlain:
fallthrough
case SaslScram512:
fallthrough
case SaslScream256:
fallthrough
case AuthMechanismEmpty:
return nil
default:
return errors.Errorf("invalid auth mechanism: %s", c.AuthMechanism)
}
}
func (c *KafkaCommand) validateDSNAndMessageSize() error {
if c.Host == "" {
return errors.New("host is required")
}
if c.Port == 0 {
return errors.New("port is required")
}
if c.MessageSize == 0 {
return errors.New("message size is required")
}
return nil
}
func (c *KafkaCommand) validateFillAction() error {
if c.MaxBytes == 0 {
return errors.New("max bytes is required")
}
if c.ReloadCommand == "" {
return errors.New("reload command is required")
}
if _, err := os.Stat(c.ConfigFile); errors.Is(err, os.ErrNotExist) {
return errors.Errorf("config file %s not exists", c.ConfigFile)
}
return c.validateDSNAndMessageSize()
}
func (c *KafkaCommand) validateFloodAction() error {
if c.Threads == 0 {
return errors.New("threads is required")
}
return c.validateDSNAndMessageSize()
}
func (c *KafkaCommand) validateIOAction() error {
if _, err := os.Stat(c.ConfigFile); errors.Is(err, os.ErrNotExist) {
return errors.Errorf("config file %s not exists", c.ConfigFile)
}
if !c.NonReadable && !c.NonWritable {
return errors.New("at least one of non-readable or non-writable is required")
}
return nil
}
func (c *KafkaCommand) RecoverData() string {
data, _ := json.Marshal(c)
return string(data)
}
func (c *KafkaCommand) CompleteDefaults() {
c.CommonAttackConfig.CompleteDefaults()
}
func NewKafkaCommand() *KafkaCommand {
return &KafkaCommand{
CommonAttackConfig: CommonAttackConfig{
Kind: KafkaAttack,
},
OriginModeOfFiles: make(map[string]uint32),
}
}

View File

@ -20,69 +20,44 @@ import (
"strings"
"time"
"github.com/chaos-mesh/chaos-mesh/api/v1alpha1"
"github.com/chaos-mesh/chaos-mesh/controllers/podnetworkchaos/netutils"
"github.com/chaos-mesh/chaos-mesh/pkg/chaosdaemon/pb"
"github.com/chaos-mesh/chaos-mesh/pkg/netem"
"github.com/pingcap/errors"
"github.com/chaos-mesh/chaos-mesh/pkg/chaosdaemon/pb"
"github.com/chaos-mesh/chaosd/pkg/utils"
)
type NetworkCommand struct {
CommonAttackConfig
Latency string `json:"latency,omitempty"`
Jitter string `json:"jitter,omitempty"`
Correlation string `json:"correlation,omitempty"`
Percent string `json:"percent,omitempty"`
Device string `json:"device,omitempty"`
SourcePort string `json:"source-port,omitempty"`
EgressPort string `json:"egress-port,omitempty"`
IPAddress string `json:"ip-address,omitempty"`
IPProtocol string `json:"ip-protocol,omitempty"`
Hostname string `json:"hostname,omitempty"`
Direction string `json:"direction,omitempty"`
Latency string
Jitter string
Correlation string
Percent string
Device string
SourcePort string
EgressPort string
IPAddress string
IPProtocol string
Hostname string
// used for DNS attack
DNSServer string `json:"dns-server,omitempty"`
DNSIp string `json:"dns-ip,omitempty"`
DNSDomainName string `json:"dns-domain-name,omitempty"`
// used for port occupied or flood
Port string `json:"port,omitempty"`
PortPid int32 `json:"port-pid,omitempty"`
*BandwidthSpec `json:",inline"`
// only the packet which match the tcp flag can be accepted, others will be dropped.
// only set when the IPProtocol is tcp, used for partition.
AcceptTCPFlags string `json:"accept-tcp-flags,omitempty"`
// used for flood
// number of iperf parallel client threads to run
Parallel int32 `json:"parallel,omitempty"`
// used for flood
// the pid of iperf
IperfPid int32 `json:"iperf-pid,omitempty"`
DNSServer string
Port string
PortPid int32
DNSIp string
DNSHost string
}
var _ AttackConfig = &NetworkCommand{}
const (
NetworkDelayAction = "delay"
NetworkLossAction = "loss"
NetworkCorruptAction = "corrupt"
NetworkDuplicateAction = "duplicate"
NetworkDNSAction = "dns"
NetworkPartitionAction = "partition"
NetworkBandwidthAction = "bandwidth"
NetworkPortOccupiedAction = "occupied"
NetworkNICDownAction = "down"
NetworkFloodAction = "flood"
NetIPSet = "hash:net"
NetworkDelayAction = "delay"
NetworkLossAction = "loss"
NetworkCorruptAction = "corrupt"
NetworkDuplicateAction = "duplicate"
NetworkDNSAction = "dns"
NetworkPortOccupied = "occupied"
)
func (n *NetworkCommand) Validate() error {
@ -96,16 +71,8 @@ func (n *NetworkCommand) Validate() error {
return n.validNetworkCommon()
case NetworkDNSAction:
return n.validNetworkDNS()
case NetworkPartitionAction:
return n.validNetworkPartition()
case NetworkPortOccupiedAction:
case NetworkPortOccupied:
return n.validNetworkOccupied()
case NetworkBandwidthAction:
return n.validNetworkBandwidth()
case NetworkNICDownAction:
return n.validNetworkNICDown()
case NetworkFloodAction:
return n.validNetworkFlood()
default:
return errors.Errorf("network action %s not supported", n.Action)
}
@ -138,21 +105,9 @@ func (n *NetworkCommand) validNetworkDelay() error {
return errors.Errorf("ip addressed %s not valid", n.IPAddress)
}
if len(n.AcceptTCPFlags) > 0 && n.IPProtocol != "tcp" {
return errors.Errorf("protocol should be 'tcp' when set accept-tcp-flags")
}
return checkProtocolAndPorts(n.IPProtocol, n.SourcePort, n.EgressPort)
}
func (n *NetworkCommand) validNetworkBandwidth() error {
if len(n.Rate) == 0 || n.Limit == 0 || n.Buffer == 0 {
return errors.Errorf("rate, limit and buffer both are required when action is bandwidth")
}
return nil
}
func (n *NetworkCommand) validNetworkCommon() error {
if len(n.Percent) == 0 {
return errors.New("percent is required")
@ -177,30 +132,6 @@ func (n *NetworkCommand) validNetworkCommon() error {
return checkProtocolAndPorts(n.IPProtocol, n.SourcePort, n.EgressPort)
}
func (n *NetworkCommand) validNetworkPartition() error {
if len(n.Device) == 0 {
return errors.New("device is required")
}
if !utils.CheckIPs(n.IPAddress) {
return errors.Errorf("ip addressed %s not valid", n.IPAddress)
}
if n.Direction != "to" && n.Direction != "from" && n.Direction != "both" {
return errors.Errorf("direction should be one of to, from or both, but got %s", n.Direction)
}
if len(n.AcceptTCPFlags) > 0 && n.IPProtocol != "tcp" {
return errors.Errorf("protocol should be 'tcp' when set accept-tcp-flags")
}
if !utils.CheckIPProtocols(n.IPProtocol) {
return errors.Errorf("ip protocols %s not valid", n.IPProtocol)
}
return nil
}
func (n *NetworkCommand) validNetworkDNS() error {
if !utils.CheckIPs(n.DNSServer) {
return errors.Errorf("server addresse %s not valid", n.DNSServer)
@ -210,8 +141,8 @@ func (n *NetworkCommand) validNetworkDNS() error {
return errors.Errorf("ip addresse %s not valid", n.DNSIp)
}
if (len(n.DNSDomainName) != 0 && len(n.DNSIp) == 0) || (len(n.DNSDomainName) == 0 && len(n.DNSIp) != 0) {
return errors.Errorf("DNS host %s must match a DNS ip %s", n.DNSDomainName, n.DNSIp)
if (len(n.DNSHost) != 0 && len(n.DNSIp) == 0) || (len(n.DNSHost) == 0 && len(n.DNSIp) != 0) {
return errors.Errorf("DNS host %s must match a DNS ip %s", n.DNSHost, n.DNSIp)
}
return nil
@ -224,42 +155,6 @@ func (n *NetworkCommand) validNetworkOccupied() error {
return nil
}
func (n *NetworkCommand) validNetworkNICDown() error {
if len(n.Duration) == 0 {
return errors.New("duration is required")
}
if len(n.Device) == 0 {
return errors.New("device is required")
}
return nil
}
func (n *NetworkCommand) validNetworkFlood() error {
if len(n.IPAddress) == 0 {
return errors.New("IP is required")
}
if !utils.CheckIPs(n.IPAddress) {
return errors.Errorf("ip addressed %s not valid", n.IPAddress)
}
if len(n.Port) == 0 {
return errors.New("port is required")
}
if len(n.Rate) == 0 {
return errors.New("rate is required")
}
if len(n.Duration) == 0 {
return errors.New("duration is required")
}
return nil
}
func (n *NetworkCommand) CompleteDefaults() {
switch n.Action {
case NetworkDelayAction:
@ -268,10 +163,6 @@ func (n *NetworkCommand) CompleteDefaults() {
n.setDefaultForNetworkLoss()
case NetworkDNSAction:
n.setDefaultForNetworkDNS()
case NetworkDuplicateAction:
n.setDefaultForNetworkDuplicate()
case NetworkCorruptAction:
n.setDefaultForNetworkCorrupt()
}
}
@ -291,18 +182,6 @@ func (n *NetworkCommand) setDefaultForNetworkLoss() {
}
}
func (n *NetworkCommand) setDefaultForNetworkDuplicate() {
if len(n.Correlation) == 0 {
n.Correlation = "0"
}
}
func (n *NetworkCommand) setDefaultForNetworkCorrupt() {
if len(n.Correlation) == 0 {
n.Correlation = "0"
}
}
func (n *NetworkCommand) setDefaultForNetworkDNS() {
if len(n.DNSServer) == 0 {
n.DNSServer = "123.123.123.123"
@ -414,34 +293,12 @@ func (n *NetworkCommand) ToDuplicateNetem() (*pb.Netem, error) {
}
func (n *NetworkCommand) ToTC(ipset string) (*pb.Tc, error) {
if n.Action == NetworkBandwidthAction {
tbf, err := netem.FromBandwidth(&v1alpha1.BandwidthSpec{
Rate: n.Rate,
Limit: n.Limit,
Buffer: n.Buffer,
Peakrate: n.Peakrate,
Minburst: n.Minburst,
})
if err != nil {
return nil, err
}
return &pb.Tc{
Type: pb.Tc_BANDWIDTH,
Tbf: tbf,
Ipset: ipset,
Device: n.Device,
}, nil
}
tc := &pb.Tc{
Type: pb.Tc_NETEM,
Ipset: ipset,
Protocol: n.IPProtocol,
SourcePort: n.SourcePort,
EgressPort: n.EgressPort,
Device: n.Device,
}
var (
@ -465,8 +322,6 @@ func (n *NetworkCommand) ToTC(ipset string) (*pb.Tc, error) {
if netem, err = n.ToDuplicateNetem(); err != nil {
return nil, errors.WithStack(err)
}
case NetworkPartitionAction:
default:
return nil, errors.Errorf("action %s not supported", n.Action)
}
@ -499,7 +354,6 @@ func (n *NetworkCommand) ToIPSet(name string) (*pb.IPSet, error) {
return &pb.IPSet{
Name: name,
Cidrs: cidrs,
Type: NetIPSet,
}, nil
}
@ -511,83 +365,21 @@ func (n *NetworkCommand) NeedApplyIPSet() bool {
return false
}
func (n *NetworkCommand) NeedApplyIptables() bool {
return true
}
func (n *NetworkCommand) NeedApplyTC() bool {
switch n.Action {
case NetworkDelayAction, NetworkLossAction, NetworkCorruptAction, NetworkDuplicateAction, NetworkBandwidthAction:
case NetworkDelayAction, NetworkLossAction, NetworkCorruptAction, NetworkDuplicateAction:
return true
default:
return false
}
}
func (n *NetworkCommand) AdditionalChain(ipset, device, uid string) ([]*pb.Chain, error) {
chains := make([]*pb.Chain, 0, 2)
var toChains, fromChains []*pb.Chain
var err error
if n.Direction == "to" || n.Direction == "both" {
toChains, err = n.getAdditionalChain(ipset, device, "to", uid)
if err != nil {
return nil, err
}
}
if n.Direction == "from" || n.Direction == "both" {
fromChains, err = n.getAdditionalChain(ipset, device, "from", uid)
if err != nil {
return nil, err
}
}
chains = append(chains, toChains...)
chains = append(chains, fromChains...)
return chains, nil
}
func (n *NetworkCommand) getAdditionalChain(ipset, device, direction, uid string) ([]*pb.Chain, error) {
var directionStr string
var directionChain pb.Chain_Direction
if direction == "to" {
directionStr = "OUTPUT"
directionChain = pb.Chain_OUTPUT
} else if direction == "from" {
directionStr = "INPUT"
directionChain = pb.Chain_INPUT
} else {
return nil, errors.New(fmt.Sprintf("direction %s not supported", n.Direction))
}
chains := make([]*pb.Chain, 0, 2)
// The `targetLength`s in `netutils.CompressName()` are different because of
// the need to distinguish between the different chains.
if len(n.AcceptTCPFlags) > 0 {
chains = append(chains, &pb.Chain{
Name: fmt.Sprintf("%s/%s", directionStr, netutils.CompressName(uid, 19, "")),
Ipsets: []string{ipset},
Direction: directionChain,
Protocol: n.IPProtocol,
TcpFlags: n.AcceptTCPFlags,
Target: "ACCEPT",
Device: device,
})
}
if n.Action == NetworkPartitionAction {
chains = append(chains, &pb.Chain{
Name: fmt.Sprintf("%s/%s", directionStr, netutils.CompressName(uid, 20, "")),
Ipsets: []string{ipset},
Direction: directionChain,
Protocol: n.IPProtocol,
Target: "DROP",
Device: device,
})
}
return chains, nil
}
func (n *NetworkCommand) NeedApplyEtcHosts() bool {
if len(n.DNSDomainName) > 0 || len(n.DNSIp) > 0 {
if len(n.DNSHost) > 0 || len(n.DNSIp) > 0 {
return true
}
@ -598,11 +390,8 @@ func (n *NetworkCommand) NeedApplyDNSServer() bool {
return len(n.DNSServer) > 0
}
func (n *NetworkCommand) NeedAdditionalChains() bool {
if n.Action == NetworkPartitionAction || (n.Action == NetworkDelayAction && len(n.AcceptTCPFlags) != 0) {
return true
}
return false
func (n *NetworkCommand) ToChain() (*pb.Chain, error) {
return nil, nil
}
func NewNetworkCommand() *NetworkCommand {
@ -610,9 +399,5 @@ func NewNetworkCommand() *NetworkCommand {
CommonAttackConfig: CommonAttackConfig{
Kind: NetworkAttack,
},
BandwidthSpec: &BandwidthSpec{
Peakrate: new(uint64),
Minburst: new(uint32),
},
}
}

View File

@ -70,8 +70,6 @@ type IptablesRule struct {
Direction string `json:"direction"`
// Experiment represents the experiment which the rule belong to.
Experiment string `gorm:"index:experiment" json:"experiment"`
Protocol string `json:"protocol"`
}
func (i *IptablesRule) ToChain() *pb.Chain {
@ -424,7 +422,7 @@ func toNetem(spec *TcParameter) (*pb.Netem, error) {
for _, spec := range emSpecs {
em, err := spec.ToNetem()
if err != nil {
return nil, errors.WithStack(err)
return nil, err
}
merged = mergeNetem(merged, em)
}

View File

@ -1,156 +0,0 @@
// Copyright 2022 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import (
"testing"
"github.com/chaos-mesh/chaos-mesh/pkg/chaosdaemon/pb"
)
func TestPatitionChain(t *testing.T) {
t.Run("PattitionOnDirection", func(t *testing.T) {
testCases := []struct {
cmd *NetworkCommand
chains []*pb.Chain
}{
{
cmd: &NetworkCommand{
CommonAttackConfig: CommonAttackConfig{
Action: NetworkPartitionAction,
},
Direction: "to",
IPProtocol: "tcp",
},
chains: []*pb.Chain{
{
Name: "OUTPUT/3c552_e0172bc4fd046_",
Ipsets: []string{"test"},
Direction: pb.Chain_OUTPUT,
Protocol: "tcp",
Target: "DROP",
},
},
},
{
cmd: &NetworkCommand{
CommonAttackConfig: CommonAttackConfig{
Action: NetworkPartitionAction,
},
Direction: "from",
IPProtocol: "tcp",
},
chains: []*pb.Chain{
{
Name: "INPUT/3c552_e0172bc4fd046_",
Ipsets: []string{"test"},
Direction: pb.Chain_INPUT,
Protocol: "tcp",
Target: "DROP",
},
},
},
{
cmd: &NetworkCommand{
CommonAttackConfig: CommonAttackConfig{
Action: NetworkPartitionAction,
},
Direction: "both",
IPProtocol: "tcp",
},
chains: []*pb.Chain{
{
Name: "OUTPUT/3c552_e0172bc4fd046_",
Ipsets: []string{"test"},
Direction: pb.Chain_OUTPUT,
Protocol: "tcp",
Target: "DROP",
},
{
Name: "INPUT/3c552_e0172bc4fd046_",
Ipsets: []string{"test"},
Direction: pb.Chain_INPUT,
Protocol: "tcp",
Target: "DROP",
},
},
},
{
cmd: &NetworkCommand{
CommonAttackConfig: CommonAttackConfig{
Action: NetworkPartitionAction,
},
Direction: "both",
IPProtocol: "tcp",
AcceptTCPFlags: "SYN,ACK SYN,ACK",
},
chains: []*pb.Chain{
{
Name: "OUTPUT/3c552_e0172bc4fd04_",
Ipsets: []string{"test"},
Direction: pb.Chain_OUTPUT,
Protocol: "tcp",
TcpFlags: "SYN,ACK SYN,ACK",
Target: "ACCEPT",
},
{
Name: "OUTPUT/3c552_e0172bc4fd046_",
Ipsets: []string{"test"},
Direction: pb.Chain_OUTPUT,
Protocol: "tcp",
Target: "DROP",
},
{
Name: "INPUT/3c552_e0172bc4fd04_",
Ipsets: []string{"test"},
Direction: pb.Chain_INPUT,
Protocol: "tcp",
TcpFlags: "SYN,ACK SYN,ACK",
Target: "ACCEPT",
},
{
Name: "INPUT/3c552_e0172bc4fd046_",
Ipsets: []string{"test"},
Direction: pb.Chain_INPUT,
Protocol: "tcp",
Target: "DROP",
},
},
},
}
for _, tc := range testCases {
chains, err := tc.cmd.AdditionalChain("test", "eth0", "3c5528e1-4c32-4f80-983c-913ad7e860e2")
if err != nil {
t.Errorf("failed to partition chain: %v", err)
}
if len(chains) != len(tc.chains) {
t.Errorf("invalid chains. expected: %v, actual: %v", tc.chains, chains)
}
for i, chain := range chains {
if chain.Name != tc.chains[i].Name {
t.Errorf("invalid chain name. expected: %v, actual: %v", tc.chains[i].Name, chain.Name)
}
if chain.Ipsets[0] != "test" {
t.Errorf("invalid ipsets. expected: %v, actual: %v", tc.chains[i].Ipsets, chain.Ipsets)
}
if chain.Direction != tc.chains[i].Direction {
t.Errorf("invalid direction. expected: %v, actual: %v", tc.chains[i].Direction, chain.Direction)
}
if chain.Target != tc.chains[i].Target {
t.Errorf("invalid target. expected: %v, actual: %v", tc.chains[i].Target, chain.Target)
}
}
}
})
}

View File

@ -30,10 +30,9 @@ type ProcessCommand struct {
CommonAttackConfig
// Process defines the process name or the process ID.
Process string `json:"process,omitempty"`
Signal int `json:"signal,omitempty"`
PIDs []int
RecoverCmd string `json:"recoverCmd,omitempty"`
Process string
Signal int
PIDs []int
// TODO: support these feature
// Newest bool
// Oldest bool

View File

@ -1,90 +0,0 @@
// Copyright 2020 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import (
"encoding/json"
"github.com/pingcap/errors"
)
const (
RedisSentinelRestartAction = "restart"
RedisSentinelStopAction = "stop"
RedisCachePenetrationAction = "penetration"
RedisCacheLimitAction = "cacheLimit"
RedisCacheExpirationAction = "expiration"
)
var (
_ AttackConfig = &RedisCommand{}
ValidOptions = map[string]bool{"XX": true, "NX": true, "GT": true, "LT": true}
)
type RedisCommand struct {
CommonAttackConfig
Addr string `json:"addr,omitempty"`
Password string `json:"password,omitempty"`
Conf string `json:"conf,omitempty"`
FlushConfig bool `json:"flushConfig,omitempty"`
RedisPath string `json:"redisPath,omitempty"`
RequestNum int `json:"requestNum,omitempty"`
CacheSize string `json:"cacheSize,omitempty"`
Percent string `json:"percent,omitempty"`
Key string `json:"key,omitempty"`
Expiration string `json:"expiration,omitempty"`
Option string `json:"option,omitempty"`
OriginCacheSize string `json:"originCacheSize,omitempty"`
}
func (r *RedisCommand) Validate() error {
if err := r.CommonAttackConfig.Validate(); err != nil {
return err
}
if len(r.Addr) == 0 {
return errors.New("addr of redis server is required")
}
switch r.Action {
case RedisCachePenetrationAction:
if r.RequestNum == 0 {
return errors.New("request-num is required")
}
case RedisCacheLimitAction:
if r.CacheSize != "0" && r.Percent != "" {
return errors.New("only one of cachesize and percent can be set")
}
case RedisCacheExpirationAction:
if _, ok := ValidOptions[r.Option]; ok {
return errors.New("option invalid")
}
}
return nil
}
func (r RedisCommand) RecoverData() string {
data, _ := json.Marshal(r)
return string(data)
}
func NewRedisCommand() *RedisCommand {
return &RedisCommand{
CommonAttackConfig: CommonAttackConfig{
Kind: RedisAttack,
},
}
}

View File

@ -33,8 +33,10 @@ func (s SearchCommand) Validate() error {
}
if len(s.Kind) > 0 {
attack := GetAttackByKind(s.Kind)
if attack == nil {
switch s.Kind {
case NetworkAttack, ProcessAttack:
break
default:
return errors.Errorf("type %s not supported", s.Kind)
}
}

View File

@ -27,11 +27,11 @@ const (
type StressCommand struct {
CommonAttackConfig
Load int `json:"load,omitempty"`
Workers int `json:"workers,omitempty"`
Size string `json:"size,omitempty"`
Options []string `json:"options,omitempty"`
StressngPid int32 `json:"stress-ng-pid,omitempty"`
Load int
Workers int
Size string
Options []string
StressngPid int32
}
var _ AttackConfig = &StressCommand{}
@ -47,12 +47,6 @@ func (s *StressCommand) Validate() error {
return nil
}
func (s *StressCommand) CompleteDefaults() {
if s.Workers == 0 {
s.Workers = 1
}
}
func (s StressCommand) RecoverData() string {
data, _ := json.Marshal(s)

View File

@ -1,58 +0,0 @@
// Copyright 2022 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import (
"encoding/json"
"github.com/pingcap/errors"
)
var _ AttackConfig = &UserDefinedOption{}
type UserDefinedOption struct {
CommonAttackConfig
AttackCmd string `json:"attackCmd,omitempty"`
RecoverCmd string `json:"recoverCmd,omitempty"`
}
func (u *UserDefinedOption) Validate() error {
if err := u.CommonAttackConfig.Validate(); err != nil {
return err
}
if len(u.AttackCmd) == 0 {
return errors.New("attack command not provided")
}
if len(u.RecoverCmd) == 0 {
return errors.New("recover command not provided")
}
return nil
}
func (u *UserDefinedOption) RecoverData() string {
data, _ := json.Marshal(u)
return string(data)
}
func NewUserDefinedOption() *UserDefinedOption {
return &UserDefinedOption{
CommonAttackConfig: CommonAttackConfig{
Kind: UserDefinedAttack,
},
}
}

View File

@ -1,46 +0,0 @@
// Copyright 2021 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package core
import (
"encoding/json"
)
const (
VMAction = "vm"
)
type VMOption struct {
CommonAttackConfig
VMName string `json:"vm-name,omitempty"`
}
func NewVMOption() *VMOption {
return &VMOption{
CommonAttackConfig: CommonAttackConfig{
Kind: VMAction,
},
}
}
func (opt *VMOption) CompleteDefaults() {
return
}
func (opt VMOption) RecoverData() string {
data, _ := json.Marshal(opt)
return string(data)
}

View File

@ -29,14 +29,6 @@ type NodeCRClient struct {
Pid uint32
}
func (n *NodeCRClient) ListContainerIDs(_ context.Context) ([]string, error) {
return nil, nil
}
func (n *NodeCRClient) GetLabelsFromContainerID(_ context.Context, _ string) (map[string]string, error) {
return nil, nil
}
func (n *NodeCRClient) GetPidFromContainerID(_ context.Context, _ string) (uint32, error) {
return n.Pid, nil
}

View File

@ -30,14 +30,7 @@ type Environment struct {
}
type AttackType interface {
// Attack execute attack with options and env.
// ExecuteAttack will store the options ahead of Attack be executed
// and will store options again after Attack be executed.
// We can also use env.Chaos.expStore to touch the storage of chaosd.
// But do not update it with your own uid ,
// because it will be covered after Attack executed with options.
Attack(options core.AttackConfig, env Environment) error
// Recover can get marshaled options data from experiment and recover it.
Recover(experiment core.Experiment, env Environment) error
}
@ -53,11 +46,13 @@ func (s *Server) newEnvironment(uid string) Environment {
// If options.Schedule isn't provided, then the attack is executed immediately.
// Otherwise the attack is scheduled based on the provided schedule spec and duration.
func (s *Server) ExecuteAttack(attackType AttackType, options core.AttackConfig, launchMode string) (uid string, err error) {
uid = options.GetUID()
if len(uid) == 0 {
uid = uuid.New().String()
if err = options.Validate(); err != nil {
err = core.ErrAttackConfigValidation.Wrap(err, "attack config validation failed")
return
}
uid = uuid.New().String()
exp := &core.Experiment{
Uid: uid,
Status: core.Created,
@ -66,26 +61,25 @@ func (s *Server) ExecuteAttack(attackType AttackType, options core.AttackConfig,
RecoverCommand: options.RecoverData(),
LaunchMode: launchMode,
}
if err = s.expStore.Set(context.Background(), exp); err != nil {
if err = s.exp.Set(context.Background(), exp); err != nil {
err = perr.WithStack(err)
return
}
defer func() {
if err != nil {
if err := s.expStore.Update(context.Background(), uid, core.Error, err.Error(), options.RecoverData()); err != nil {
if err := s.exp.Update(context.Background(), uid, core.Error, err.Error(), options.RecoverData()); err != nil {
log.Error("failed to update experiment", zap.Error(err))
}
return
}
var newStatus string
if len(options.Cron()) > 0 {
newStatus = core.Scheduled
} else {
newStatus = core.Success
}
if err := s.expStore.Update(context.Background(), uid, newStatus, "", options.RecoverData()); err != nil {
if err := s.exp.Update(context.Background(), uid, newStatus, "", options.RecoverData()); err != nil {
log.Error("failed to update experiment", zap.Error(err))
}
}()

View File

@ -1,287 +0,0 @@
// Copyright 2021 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package chaosd
import (
"bytes"
"debug/elf"
"fmt"
"runtime"
"github.com/go-logr/zapr"
"github.com/chaos-mesh/chaos-mesh/pkg/mapreader"
"github.com/chaos-mesh/chaos-mesh/pkg/ptrace"
"github.com/pingcap/log"
"go.uber.org/zap"
"github.com/chaos-mesh/chaosd/pkg/core"
)
type clockAttack struct{}
var ClockAttack AttackType = clockAttack{}
// Copied from chaos-mesh/pkg/time/time_linux_amd64,
// I will move the recover part into it just future.
var fakeImage = []byte{
0xb8, 0xe4, 0x00, 0x00, 0x00, //mov $0xe4,%eax
0x0f, 0x05, //syscall
0xba, 0x01, 0x00, 0x00, 0x00, //mov $0x1,%edx
0x89, 0xf9, //mov %edi,%ecx
0xd3, 0xe2, //shl %cl,%edx
0x48, 0x8d, 0x0d, 0x74, 0x00, 0x00, 0x00, //lea 0x74(%rip),%rcx # <CLOCK_IDS_MASK>
0x48, 0x63, 0xd2, //movslq %edx,%rdx
0x48, 0x85, 0x11, //test %rdx,(%rcx)
0x74, 0x6b, //je 108a <clock_gettime+0x8a>
0x48, 0x8d, 0x15, 0x6d, 0x00, 0x00, 0x00, //lea 0x6d(%rip),%rdx # <TV_SEC_DELTA>
0x4c, 0x8b, 0x46, 0x08, //mov 0x8(%rsi),%r8
0x48, 0x8b, 0x0a, //mov (%rdx),%rcx
0x48, 0x8d, 0x15, 0x67, 0x00, 0x00, 0x00, //lea 0x67(%rip),%rdx # <TV_NSEC_DELTA>
0x48, 0x8b, 0x3a, //mov (%rdx),%rdi
0x4a, 0x8d, 0x14, 0x07, //lea (%rdi,%r8,1),%rdx
0x48, 0x81, 0xfa, 0x00, 0xca, 0x9a, 0x3b, //cmp $0x3b9aca00,%rdx
0x7e, 0x1c, //jle <clock_gettime+0x60>
0x0f, 0x1f, 0x40, 0x00, //nopl 0x0(%rax)
0x48, 0x81, 0xef, 0x00, 0xca, 0x9a, 0x3b, //sub $0x3b9aca00,%rdi
0x48, 0x83, 0xc1, 0x01, //add $0x1,%rcx
0x49, 0x8d, 0x14, 0x38, //lea (%r8,%rdi,1),%rdx
0x48, 0x81, 0xfa, 0x00, 0xca, 0x9a, 0x3b, //cmp $0x3b9aca00,%rdx
0x7f, 0xe8, //jg <clock_gettime+0x48>
0x48, 0x85, 0xd2, //test %rdx,%rdx
0x79, 0x1e, //jns <clock_gettime+0x83>
0x4a, 0x8d, 0xbc, 0x07, 0x00, 0xca, 0x9a, //lea 0x3b9aca00(%rdi,%r8,1),%rdi
0x3b, //
0x0f, 0x1f, 0x00, //nopl (%rax)
0x48, 0x89, 0xfa, //mov %rdi,%rdx
0x48, 0x83, 0xe9, 0x01, //sub $0x1,%rcx
0x48, 0x81, 0xc7, 0x00, 0xca, 0x9a, 0x3b, //add $0x3b9aca00,%rdi
0x48, 0x85, 0xd2, //test %rdx,%rdx
0x78, 0xed, //js <clock_gettime+0x70>
0x48, 0x01, 0x0e, //add %rcx,(%rsi)
0x48, 0x89, 0x56, 0x08, //mov %rdx,0x8(%rsi)
0xc3, //retq
// constant
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //CLOCK_IDS_MASK
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //TV_SEC_DELTA
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //TV_NSEC_DELTA
}
func (c clockAttack) Attack(options core.AttackConfig, env Environment) error {
var opt *core.ClockOption
var ok bool
if opt, ok = options.(*core.ClockOption); !ok {
return fmt.Errorf("AttackConfig -> *ClockOption meet error")
}
runtime.LockOSThread()
defer func() {
runtime.UnlockOSThread()
}()
zapLogger, err := zap.NewDevelopment()
if err != nil {
return err
}
logger := zapr.NewLogger(zapLogger)
program, err := ptrace.Trace(opt.Pid, logger)
if err != nil {
return err
}
defer func() {
err = program.Detach()
if err != nil {
log.Error("fail to detach program", zap.Error(err), zap.Int("pid", opt.Pid))
}
}()
var vdsoEntry *mapreader.Entry
for index := range program.Entries {
// reverse loop is faster
e := program.Entries[len(program.Entries)-index-1]
if e.Path == "[vdso]" {
vdsoEntry = &e
break
}
}
if vdsoEntry == nil {
return fmt.Errorf("cannot find [vdso] entry")
}
// minus tailing variable part
// 24 = 3 * 8 because we have three variables
constImageLen := len(fakeImage) - 24
var fakeEntry *mapreader.Entry
// find injected image to avoid redundant inject (which will lead to memory leak)
for _, e := range program.Entries {
e := e
image, err := program.ReadSlice(e.StartAddress, uint64(constImageLen))
if err != nil {
continue
}
if bytes.Equal(*image, fakeImage[0:constImageLen]) {
fakeEntry = &e
log.Warn("found injected image", zap.Uint64("addr", fakeEntry.StartAddress))
}
}
if fakeEntry == nil {
fakeEntry, err = program.MmapSlice(fakeImage)
if err != nil {
return err
}
}
fakeAddr := fakeEntry.StartAddress
// 139 is the index of CLOCK_IDS_MASK in fakeImage
err = program.WriteUint64ToAddr(fakeAddr+139, opt.ClockIdsMask)
if err != nil {
return err
}
// 147 is the index of TV_SEC_DELTA in fakeImage
err = program.WriteUint64ToAddr(fakeAddr+147, uint64(opt.SecDelta))
if err != nil {
return err
}
// 155 is the index of TV_NSEC_DELTA in fakeImage
err = program.WriteUint64ToAddr(fakeAddr+155, uint64(opt.NsecDelta))
if err != nil {
return err
}
originAddr, size, err := FindSymbolInEntry(*program, "clock_gettime", vdsoEntry)
if err != nil {
return err
}
funcBytes, err := program.ReadSlice(originAddr, size)
exps, err := env.Chaos.Search(&core.SearchCommand{
Status: core.Success,
Kind: core.ClockAttack,
})
if err != nil {
return err
}
for _, exp := range exps {
if exp.Kind == core.ClockAttack {
lastOptions, err := exp.GetRequestCommand()
if err != nil {
return err
}
var lastOpt *core.ClockOption
var ok bool
if lastOpt, ok = lastOptions.(*core.ClockOption); !ok {
log.Warn("AttackConfig -> *ClockOption meet error")
continue
}
if lastOpt.Pid == opt.Pid {
return fmt.Errorf("plz recover the last clock attack on pid : %d first \n"+
"chaosd recover %s", opt.Pid, exp.Uid)
}
}
}
opt.Store = core.ClockFuncStore{
CodeOfGetClockFunc: *funcBytes,
OriginAddress: originAddr,
}
if err != nil {
return err
}
err = program.JumpToFakeFunc(originAddr, fakeAddr)
return err
}
func (c clockAttack) Recover(exp core.Experiment, env Environment) error {
options, err := exp.GetRequestCommand()
if err != nil {
return err
}
var opt *core.ClockOption
var ok bool
if opt, ok = options.(*core.ClockOption); !ok {
return fmt.Errorf("AttackConfig -> *ClockOption meet error")
}
runtime.LockOSThread()
defer func() {
runtime.UnlockOSThread()
}()
zapLogger, err := zap.NewDevelopment()
if err != nil {
return err
}
logger := zapr.NewLogger(zapLogger)
program, err := ptrace.Trace(opt.Pid, logger)
if err != nil {
return err
}
defer func() {
err = program.Detach()
if err != nil {
log.Error("fail to detach program", zap.Error(err), zap.Int("pid", opt.Pid))
}
}()
err = program.PtraceWriteSlice(opt.Store.OriginAddress, opt.Store.CodeOfGetClockFunc)
return err
}
// FindSymbolInEntry finds symbol in entry through parsing elf
func FindSymbolInEntry(p ptrace.TracedProgram, symbolName string, entry *mapreader.Entry) (addr uint64, size uint64, err error) {
libBuffer, err := p.GetLibBuffer(entry)
if err != nil {
return 0, 0, err
}
reader := bytes.NewReader(*libBuffer)
vdsoElf, err := elf.NewFile(reader)
if err != nil {
return 0, 0, err
}
loadOffset := uint64(0)
for _, prog := range vdsoElf.Progs {
if prog.Type == elf.PT_LOAD {
loadOffset = prog.Vaddr - prog.Off
// break here is enough for vdso
break
}
}
symbols, err := vdsoElf.DynamicSymbols()
if err != nil {
return 0, 0, err
}
for _, symbol := range symbols {
if symbol.Name == symbolName {
offset := symbol.Value
return entry.StartAddress + (offset - loadOffset), symbol.Size, nil
}
}
return 0, 0, fmt.Errorf("cannot find symbol")
}

View File

@ -1,33 +0,0 @@
// Copyright 2021 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package chaosd
import (
"fmt"
"github.com/chaos-mesh/chaosd/pkg/core"
)
type clockAttack struct{}
var ClockAttack AttackType = clockAttack{}
func (c clockAttack) Attack(options core.AttackConfig, env Environment) error {
return fmt.Errorf("clock attack not supported")
}
func (c clockAttack) Recover(exp core.Experiment, env Environment) error {
return fmt.Errorf("clock recover not supported")
}

View File

@ -14,104 +14,224 @@
package chaosd
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"sync"
"github.com/hashicorp/go-multierror"
"github.com/pingcap/errors"
"github.com/pingcap/log"
"go.uber.org/zap"
pkgUtils "github.com/chaos-mesh/chaosd/pkg/utils"
"github.com/chaos-mesh/chaosd/pkg/core"
"github.com/chaos-mesh/chaosd/pkg/utils"
)
type diskAttack struct{}
var DiskAttack AttackType = diskAttack{}
func handleDiskAttackOutput(output []byte, err error, c chan interface{}) {
const DDWritePayloadCommand = "dd if=/dev/zero of=%s bs=%s count=%s oflag=dsync"
const DDReadPayloadCommand = "dd if=%s of=/dev/null bs=%s count=%s iflag=dsync,fullblock,nocache"
func (disk diskAttack) Attack(options core.AttackConfig, env Environment) (err error) {
attack := options.(*core.DiskOption)
if options.String() == core.DiskFillAction {
return disk.diskFill(attack)
}
return disk.diskPayload(attack)
}
func initWritePayloadPath(payload *core.DiskOption) error {
var err error
payload.Path, err = utils.CreateTempFile()
if err != nil {
log.Error(string(output), zap.Error(err))
c <- err
log.Error(fmt.Sprintf("unexpected err when CreateTempFile in action: %s", payload.Action))
return err
}
return nil
}
func initReadPayloadPath(payload *core.DiskOption) error {
path, err := utils.GetRootDevice()
if err != nil {
log.Error("err when GetRootDevice in reading payload", zap.Error(err))
return err
}
if path == "" {
err = errors.Errorf("can not get root device path")
log.Error(fmt.Sprintf("payload action: %s", payload.Action), zap.Error(err))
return err
}
payload.Path = path
return nil
}
// diskPayload will execute a dd command (DDWritePayloadCommand or DDReadPayloadCommand)
// to add a write or read payload.
func (diskAttack) diskPayload(payload *core.DiskOption) error {
var cmdFormat string
switch payload.Action {
case core.DiskWritePayloadAction:
cmdFormat = DDWritePayloadCommand
if payload.Path == "" {
err := initWritePayloadPath(payload)
if err != nil {
return err
}
}
case core.DiskReadPayloadAction:
cmdFormat = DDReadPayloadCommand
if payload.Path == "" {
err := initReadPayloadPath(payload)
if err != nil {
return err
}
}
default:
err := errors.Errorf("invalid payload action")
log.Error(fmt.Sprintf("payload action: %s", payload.Action), zap.Error(err))
return err
}
byteSize, err := utils.ParseUnit(payload.Size)
if err != nil {
log.Error(fmt.Sprintf("fail to get parse size per units , %s", payload.Size), zap.Error(err))
return err
}
ddBlocks, err := utils.SplitBytesByProcessNum(byteSize, payload.PayloadProcessNum)
if err != nil {
log.Error(fmt.Sprintf("split size ,process num %d", payload.PayloadProcessNum), zap.Error(err))
return err
}
if len(ddBlocks) == 0 {
return nil
}
rest := ddBlocks[len(ddBlocks)-1]
ddBlocks = ddBlocks[:len(ddBlocks)-1]
cmd := exec.Command("bash", "-c", fmt.Sprintf(cmdFormat, payload.Path, rest.BlockSize, rest.Count))
output, err := cmd.CombinedOutput()
if err != nil {
log.Error(cmd.String()+string(output), zap.Error(err))
}
log.Info(string(output))
}
func (diskAttack) Attack(options core.AttackConfig, env Environment) error {
err := ApplyDiskAttack(options, env)
if err != nil {
return err
var wg sync.WaitGroup
var mu sync.Mutex
var errs error
wg.Add(len(ddBlocks))
for _, sizeBlock := range ddBlocks {
cmd := exec.Command("bash", "-c", fmt.Sprintf(cmdFormat, payload.Path, sizeBlock.BlockSize, sizeBlock.Count))
go func(cmd *exec.Cmd) {
defer wg.Done()
output, err := cmd.CombinedOutput()
if err != nil {
log.Error(cmd.String()+string(output), zap.Error(err))
mu.Lock()
defer mu.Unlock()
errs = multierror.Append(errs, err)
return
}
log.Info(string(output))
}(cmd)
}
wg.Wait()
if errs != nil {
return errs
}
return nil
}
func handleOutputChannelError(c chan interface{}) error {
close(c)
var multiErrs error
for i := range c {
if err, ok := i.(error); ok {
multiErrs = multierror.Append(multiErrs, err)
}
}
if multiErrs != nil {
return multiErrs
}
return nil
}
// dd command with 'oflag=append conv=notrunc' will append new data in the file.
const DDFillCommand = "dd if=/dev/zero of=%s bs=%s count=%s iflag=fullblock oflag=append conv=notrunc"
const FallocateCommand = "fallocate -l %s %s"
func ApplyDiskAttack(options core.AttackConfig, env Environment) error {
var attackConf *core.DiskAttackConfig
var ok bool
if attackConf, ok = options.(*core.DiskAttackConfig); !ok {
return fmt.Errorf("AttackConfig -> *DiskAttackConfig meet error")
}
poolSize := getPoolSize(attackConf)
outputChan := make(chan interface{}, poolSize+1)
if attackConf.Action == core.DiskFillAction {
cmdPool := pkgUtils.NewCommandPools(context.Background(), nil, poolSize)
env.Chaos.CmdPools[env.AttackUid] = cmdPool
fillDisk(attackConf, cmdPool, NewOutputHandler(handleDiskAttackOutput, outputChan))
cmdPool.Wait()
cmdPool.Close()
return handleOutputChannelError(outputChan)
}
if attackConf.DdOptions != nil {
var cmdPool *pkgUtils.CommandPools
deadline := getDeadline(options)
if deadline != nil {
cmdPool = pkgUtils.NewCommandPools(context.Background(), deadline, poolSize)
}
cmdPool = pkgUtils.NewCommandPools(context.Background(), nil, poolSize)
env.Chaos.CmdPools[env.AttackUid] = cmdPool
applyPayload(attackConf, cmdPool, NewOutputHandler(handleDiskAttackOutput, outputChan))
cmdPool.Wait()
cmdPool.Close()
return handleOutputChannelError(outputChan)
}
return nil
}
func (diskAttack) Recover(exp core.Experiment, env Environment) error {
attackConfig, err := exp.GetRequestCommand()
if err != nil {
return err
}
config := *attackConfig.(*core.DiskAttackConfig)
switch config.Action {
case core.DiskFillAction, core.DiskWritePayloadAction:
err = os.Remove(config.Path)
// diskFill will execute a dd command (DDFillCommand or FallocateCommand)
// to fill the disk.
func (diskAttack) diskFill(fill *core.DiskOption) error {
if fill.Path == "" {
var err error
fill.Path, err = utils.CreateTempFile()
if err != nil {
log.Warn(fmt.Sprintf("recover disk: remove %s failed", config.Path), zap.Error(err))
log.Error(fmt.Sprintf("unexpected err when CreateTempFile in action: %s", fill.Action))
return err
}
}
if cmdPool, ok := env.Chaos.CmdPools[exp.Uid]; ok {
cmdPool.Close()
if fill.Size != "" {
fill.Size = strings.Trim(fill.Size, " ")
} else if fill.Percent != "" {
fill.Percent = strings.Trim(fill.Percent, " ")
percent, err := strconv.ParseUint(fill.Percent, 10, 0)
if err != nil {
log.Error(fmt.Sprintf(" unexcepted err when parsing disk percent '%s'", fill.Percent), zap.Error(err))
return err
}
dir := filepath.Dir(fill.Path)
totalSize, err := utils.GetDiskTotalSize(dir)
if err != nil {
log.Error("fail to get disk total size", zap.Error(err))
return err
}
fill.Size = strconv.FormatUint(totalSize*percent/100, 10) + "c"
}
var cmd *exec.Cmd
if fill.FillByFallocate {
cmd = exec.Command("bash", "-c", fmt.Sprintf(FallocateCommand, fill.Size, fill.Path))
output, err := cmd.CombinedOutput()
if err != nil {
log.Error(string(output), zap.Error(err))
return err
}
log.Info(string(output))
} else {
byteSize, err := utils.ParseUnit(fill.Size)
if err != nil {
log.Error("fail to parse disk size", zap.Error(err))
return err
}
ddBlocks, err := utils.SplitBytesByProcessNum(byteSize, 1)
if err != nil {
log.Error("fail to split disk size", zap.Error(err))
return err
}
for _, block := range ddBlocks {
cmd = exec.Command("bash", "-c", fmt.Sprintf(DDFillCommand, fill.Path, block.BlockSize, block.Count))
output, err := cmd.CombinedOutput()
if err != nil {
log.Error(string(output), zap.Error(err))
return err
}
log.Info(string(output))
}
}
return nil
}
func (diskAttack) Recover(exp core.Experiment, _ Environment) error {
config, err := exp.GetRequestCommand()
if err != nil {
return err
}
option := *config.(*core.DiskOption)
switch option.Action {
case core.DiskFillAction, core.DiskWritePayloadAction:
err = os.Remove(option.Path)
if err != nil {
log.Warn(fmt.Sprintf("recover disk: remove %s failed", option.Path), zap.Error(err))
}
}
return nil
}

View File

@ -1,172 +0,0 @@
// Copyright 2021 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package chaosd
import (
"context"
"fmt"
"os"
"time"
"github.com/pingcap/log"
"go.uber.org/zap"
pkgUtils "github.com/chaos-mesh/chaosd/pkg/utils"
"github.com/chaos-mesh/chaosd/pkg/core"
)
type diskServerAttack struct{}
var DiskServerAttack AttackType = diskServerAttack{}
func handleDiskServerOutput(output []byte, err error, _ chan interface{}) {
if err != nil {
log.Error(string(output), zap.Error(err))
}
log.Info(string(output))
}
func (diskServerAttack) Attack(options core.AttackConfig, env Environment) error {
err := ApplyDiskServerAttack(options, env)
if err != nil {
return err
}
return nil
}
type OutputHandler struct {
StdoutHandler func([]byte, error, chan interface{})
OutputChan chan interface{}
}
func NewOutputHandler(
handler func([]byte, error, chan interface{}),
outputChan chan interface{}) *OutputHandler {
return &OutputHandler{
StdoutHandler: handler,
OutputChan: outputChan,
}
}
func getPoolSize(attackConf *core.DiskAttackConfig) int {
poolSize := 1
if attackConf.DdOptions != nil && len(*attackConf.DdOptions) > 0 {
poolSize = len(*attackConf.DdOptions)
}
return poolSize
}
func fillDisk(
attackConf *core.DiskAttackConfig,
cmdPool *pkgUtils.CommandPools,
outputHandler *OutputHandler) {
if attackConf.FAllocateOption != nil {
name, args := core.FAllocateCommand.GetCmdArgs(*attackConf.FAllocateOption)
runner := pkgUtils.NewCommandRunner(name, args).
WithOutputHandler(outputHandler.StdoutHandler, outputHandler.OutputChan)
cmdPool.Start(runner)
return
}
for _, DdOption := range *attackConf.DdOptions {
name, args := core.DdCommand.GetCmdArgs(DdOption)
runner := pkgUtils.NewCommandRunner(name, args).
WithOutputHandler(outputHandler.StdoutHandler, outputHandler.OutputChan)
cmdPool.Start(runner)
}
return
}
func getDeadline(options core.AttackConfig) *time.Time {
duration, _ := options.ScheduleDuration()
if duration != nil {
deadline := time.Now().Add(*duration)
return &deadline
}
return nil
}
func applyPayload(
attackConf *core.DiskAttackConfig,
cmdPool *pkgUtils.CommandPools,
outputHandler *OutputHandler) {
if len(*attackConf.DdOptions) == 0 {
return
}
rest := (*attackConf.DdOptions)[len(*attackConf.DdOptions)-1]
*attackConf.DdOptions = (*attackConf.DdOptions)[:len(*attackConf.DdOptions)-1]
name, args := core.DdCommand.GetCmdArgs(rest)
runner := pkgUtils.NewCommandRunner(name, args).
WithOutputHandler(outputHandler.StdoutHandler, outputHandler.OutputChan)
cmdPool.Start(runner)
for _, ddOpt := range *attackConf.DdOptions {
name, args := core.DdCommand.GetCmdArgs(ddOpt)
runner := pkgUtils.NewCommandRunner(name, args).
WithOutputHandler(outputHandler.StdoutHandler, outputHandler.OutputChan)
cmdPool.Start(runner)
}
}
func ApplyDiskServerAttack(options core.AttackConfig, env Environment) error {
var attackConf *core.DiskAttackConfig
var ok bool
if attackConf, ok = options.(*core.DiskAttackConfig); !ok {
return fmt.Errorf("AttackConfig -> *DiskAttackConfig meet error")
}
poolSize := getPoolSize(attackConf)
if attackConf.Action == core.DiskFillAction {
cmdPool := pkgUtils.NewCommandPools(context.Background(), nil, poolSize)
env.Chaos.CmdPools[env.AttackUid] = cmdPool
fillDisk(attackConf, cmdPool, NewOutputHandler(handleDiskServerOutput, nil))
return nil
}
if attackConf.DdOptions != nil {
var cmdPool *pkgUtils.CommandPools
deadline := getDeadline(options)
if deadline != nil {
cmdPool = pkgUtils.NewCommandPools(context.Background(), deadline, poolSize)
}
cmdPool = pkgUtils.NewCommandPools(context.Background(), nil, poolSize)
env.Chaos.CmdPools[env.AttackUid] = cmdPool
applyPayload(attackConf, cmdPool, NewOutputHandler(handleDiskServerOutput, nil))
}
return nil
}
func (diskServerAttack) Recover(exp core.Experiment, env Environment) error {
attackConfig, err := exp.GetRequestCommand()
if err != nil {
return err
}
config := *attackConfig.(*core.DiskAttackConfig)
switch config.Action {
case core.DiskFillAction, core.DiskWritePayloadAction:
err = os.Remove(config.Path)
if err != nil {
log.Warn(fmt.Sprintf("recover disk: remove %s failed", config.Path), zap.Error(err))
}
}
if cmdPool, ok := env.Chaos.CmdPools[exp.Uid]; ok {
log.Info(fmt.Sprintf("stop disk attack,read: %s", config.Path))
cmdPool.Close()
}
return nil
}

View File

@ -1,74 +0,0 @@
// Copyright 2023 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package chaosd
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/chaos-mesh/chaosd/pkg/core"
"github.com/chaos-mesh/chaosd/pkg/utils"
)
func Test_diskAttack_Attack(t *testing.T) {
opt := core.DiskOption{
CommonAttackConfig: core.CommonAttackConfig{
Action: core.DiskFillAction,
},
Size: "10M",
Path: "./a",
PayloadProcessNum: 1,
}
env := Environment{
AttackUid: "a",
Chaos: &Server{
CmdPools: make(map[string]*utils.CommandPools),
},
}
conf, err := opt.PreProcess()
assert.NoError(t, err)
err = DiskAttack.Attack(conf, env)
assert.NoError(t, err)
f, err := os.Open("./a")
assert.NoError(t, err)
fi, err := f.Stat()
assert.NoError(t, err)
assert.Equal(t, int64(10), fi.Size()>>20)
err = os.Remove("./a")
assert.NoError(t, err)
opt.Action = core.DiskWritePayloadAction
opt.PayloadProcessNum = 4
wConf, err := opt.PreProcess()
assert.NoError(t, err)
err = DiskAttack.Attack(wConf, env)
assert.NoError(t, err)
f, err = os.Open("./a")
assert.NoError(t, err)
fi, err = f.Stat()
assert.NoError(t, err)
assert.Equal(t, fi.Size()>>20, int64(2))
err = os.Remove("./a")
assert.NoError(t, err)
opt.Action = core.DiskReadPayloadAction
opt.PayloadProcessNum = 4
opt.Path = "./"
_, err = opt.PreProcess()
assert.Error(t, err)
}

View File

@ -1,278 +0,0 @@
// Copyright 2022 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package chaosd
import (
"fmt"
"strconv"
"strings"
"github.com/pingcap/errors"
"github.com/pingcap/log"
"go.uber.org/zap"
"github.com/chaos-mesh/chaosd/pkg/core"
"github.com/chaos-mesh/chaosd/pkg/utils"
)
type fileAttack struct{}
var FileAttack AttackType = fileAttack{}
func (fileAttack) Attack(options core.AttackConfig, env Environment) (err error) {
attack := options.(*core.FileCommand)
switch attack.Action {
case core.FileCreateAction:
if err = env.Chaos.createFile(attack, env.AttackUid); err != nil {
return errors.WithStack(err)
}
case core.FileModifyPrivilegeAction:
if err = env.Chaos.modifyFilePrivilege(attack, env.AttackUid); err != nil {
return errors.WithStack(err)
}
case core.FileDeleteAction:
if err = env.Chaos.deleteFile(attack, env.AttackUid); err != nil {
return errors.WithStack(err)
}
case core.FileRenameAction:
if err = env.Chaos.renameFile(attack, env.AttackUid); err != nil {
return errors.WithStack(err)
}
case core.FileAppendAction:
if err = env.Chaos.appendFile(attack, env.AttackUid); err != nil {
return errors.WithStack(err)
}
case core.FileReplaceAction:
if err = env.Chaos.replaceFile(attack, env.AttackUid); err != nil {
return errors.WithStack(err)
}
}
return nil
}
func (s *Server) createFile(attack *core.FileCommand, uid string) error {
var cmdStr string
if len(attack.DirName) > 0 {
cmdStr = fmt.Sprintf("FileTool create --dir-name %s", attack.DirName)
} else {
cmdStr = fmt.Sprintf("FileTool create --file-name %s", attack.FileName)
}
_, err := utils.ExecuteCmd(cmdStr)
if err != nil {
return errors.WithStack(err)
}
return nil
}
func (s *Server) modifyFilePrivilege(attack *core.FileCommand, uid string) error {
// get the privilege of file and save it, used for recover
cmdStr := "stat -c %a" + " " + attack.FileName
output, err := utils.ExecuteCmd(cmdStr)
if err != nil {
return errors.WithStack(err)
}
fileModeStr := strings.Replace(output, "\n", "", -1)
attack.OriginPrivilege, err = strconv.Atoi(string(fileModeStr))
if err != nil {
log.Error("transform string to int failed", zap.String("string", fileModeStr), zap.Error(err))
return errors.WithStack(err)
}
// modify the file privilege
cmdStr = fmt.Sprintf("FileTool modify --file-name %s --privilege %d", attack.FileName, attack.Privilege)
_, err = utils.ExecuteCmd(cmdStr)
if err != nil {
return errors.WithStack(err)
}
return nil
}
// deleteFile will not really delete the file, just rename it
func (s *Server) deleteFile(attack *core.FileCommand, uid string) error {
var source, dest string
if len(attack.FileName) > 0 {
dest = fmt.Sprintf("%s.%s", attack.FileName, uid)
source = attack.FileName
} else if len(attack.DirName) > 0 {
dest = fmt.Sprintf("%s.%s", attack.DirName, uid)
source = attack.DirName
}
return renameFile(source, dest)
}
func (s *Server) renameFile(attack *core.FileCommand, uid string) error {
return renameFile(attack.SourceFile, attack.DestFile)
}
func renameFile(source, dest string) error {
cmdStr := fmt.Sprintf("FileTool rename --old-name %s --new-name %s", source, dest)
_, err := utils.ExecuteCmd(cmdStr)
if err != nil {
return errors.WithStack(err)
}
return nil
}
func (s *Server) appendFile(attack *core.FileCommand, uid string) error {
// first backup the file
backupName := getBackupName(attack.FileName, uid)
cmdStr := fmt.Sprintf("FileTool copy --file-name %s --copy-file-name %s", attack.FileName, backupName)
_, err := utils.ExecuteCmd(cmdStr)
if err != nil {
return errors.WithStack(err)
}
cmdStr = fmt.Sprintf("FileTool append --count %d --data %s --file-name %s", attack.Count, attack.Data, attack.FileName)
_, err = utils.ExecuteCmd(cmdStr)
if err != nil {
return errors.WithStack(err)
}
return nil
}
func (s *Server) replaceFile(attack *core.FileCommand, uid string) error {
cmdStr := fmt.Sprintf("FileTool replace --file-name %s --origin-string %s --dest-string %s --line %d", attack.FileName, attack.OriginStr, attack.DestStr, attack.Line)
_, err := utils.ExecuteCmd(cmdStr)
if err != nil {
return errors.WithStack(err)
}
return nil
}
func (fileAttack) Recover(exp core.Experiment, env Environment) error {
config, err := exp.GetRequestCommand()
if err != nil {
return err
}
attack := config.(*core.FileCommand)
switch attack.Action {
case core.FileCreateAction:
if err = env.Chaos.recoverCreateFile(attack); err != nil {
return errors.WithStack(err)
}
case core.FileModifyPrivilegeAction:
if err = env.Chaos.recoverModifyPrivilege(attack); err != nil {
return errors.WithStack(err)
}
case core.FileDeleteAction:
if err = env.Chaos.recoverDeleteFile(attack, env.AttackUid); err != nil {
return errors.WithStack(err)
}
case core.FileRenameAction:
if err = env.Chaos.recoverRenameFile(attack); err != nil {
return errors.WithStack(err)
}
case core.FileAppendAction:
if err = env.Chaos.recoverAppendFile(attack, env.AttackUid); err != nil {
return errors.WithStack(err)
}
case core.FileReplaceAction:
if err = env.Chaos.recoverReplaceFile(attack, env.AttackUid); err != nil {
return errors.WithStack(err)
}
}
return nil
}
func (s *Server) recoverCreateFile(attack *core.FileCommand) error {
var fileName string
if len(attack.FileName) > 0 {
fileName = attack.FileName
} else if len(attack.DirName) > 0 {
fileName = attack.DirName
}
cmdStr := fmt.Sprintf("FileTool delete --file-name %s", fileName)
_, err := utils.ExecuteCmd(cmdStr)
if err != nil {
return errors.WithStack(err)
}
return nil
}
func (s *Server) recoverModifyPrivilege(attack *core.FileCommand) error {
cmdStr := fmt.Sprintf("FileTool modify --file-name %s --privilege %d", attack.FileName, attack.OriginPrivilege)
_, err := utils.ExecuteCmd(cmdStr)
if err != nil {
return errors.WithStack(err)
}
return nil
}
// recoverDeleteFile just rename the backup file/dir
func (s *Server) recoverDeleteFile(attack *core.FileCommand, uid string) error {
var backupName, sourceName string
if len(attack.FileName) > 0 {
backupName = getBackupName(attack.FileName, uid)
sourceName = attack.FileName
} else if len(attack.DirName) > 0 {
backupName = getBackupName(attack.DirName, uid)
sourceName = attack.DirName
}
err := renameFile(backupName, sourceName)
if err != nil {
log.Error("recover delete file/dir failed", zap.Error(err))
return errors.WithStack(err)
}
return nil
}
func (s *Server) recoverRenameFile(attack *core.FileCommand) error {
return renameFile(attack.DestFile, attack.SourceFile)
}
func (s *Server) recoverAppendFile(attack *core.FileCommand, uid string) error {
backupName := getBackupName(attack.FileName, uid)
cmdStr := fmt.Sprintf("FileTool rename --old-name %s --new-name %s", backupName, attack.FileName)
_, err := utils.ExecuteCmd(cmdStr)
if err != nil {
return errors.WithStack(err)
}
return nil
}
func (s *Server) recoverReplaceFile(attack *core.FileCommand, uid string) error {
// TODO: this is not a good way to recover
// For example:
// The origin content is "test text", and replace "test" with "text", the result is "text text".
// After recover, the file content is "test test", not equal to the origin content.
cmdStr := fmt.Sprintf("FileTool replace --file-name %s --origin-string %s --dest-string %s --line %d", attack.FileName, attack.DestStr, attack.OriginStr, attack.Line)
_, err := utils.ExecuteCmd(cmdStr)
if err != nil {
return errors.WithStack(err)
}
return nil
}
// getBackupName gets the backup file or directory name
func getBackupName(source string, uid string) string {
return fmt.Sprintf("%s.%s", source, uid)
}

View File

@ -14,7 +14,6 @@
package chaosd
import (
"github.com/pingcap/errors"
perr "github.com/pkg/errors"
"github.com/chaos-mesh/chaosd/pkg/core"
@ -23,7 +22,6 @@ import (
type HostManager interface {
Name() string
Shutdown() error
Reboot() error
}
type hostAttack struct{}
@ -31,23 +29,9 @@ type hostAttack struct{}
var HostAttack AttackType = hostAttack{}
func (hostAttack) Attack(options core.AttackConfig, _ Environment) error {
hostOption, ok := options.(*core.HostCommand)
if !ok {
return errors.New("the type is not HostOption")
if err := Host.Shutdown(); err != nil {
return perr.WithStack(err)
}
if hostOption.Action == core.HostShutdownAction {
if err := Host.Shutdown(); err != nil {
return perr.WithStack(err)
}
}
if hostOption.Action == core.HostRebootAction {
if err := Host.Reboot(); err != nil {
return perr.WithStack(err)
}
}
return nil
}

View File

@ -1,3 +1,5 @@
// +build aix darwin dragonfly freebsd js,wasm linux nacl netbsd openbsd solaris
// Copyright 2021 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
@ -11,9 +13,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build aix || darwin || dragonfly || freebsd || (js && wasm) || linux || nacl || netbsd || openbsd || solaris
// +build aix darwin dragonfly freebsd js,wasm linux nacl netbsd openbsd solaris
package chaosd
import (
@ -29,8 +28,6 @@ var Host HostManager = UnixHost{}
const CmdShutdown = "shutdown"
const CmdReboot = "reboot"
func (h UnixHost) Name() string {
return "unix"
}
@ -43,12 +40,3 @@ func (h UnixHost) Shutdown() error {
}
return err
}
func (h UnixHost) Reboot() error {
cmd := exec.Command(CmdReboot)
output, err := cmd.CombinedOutput()
if err != nil {
log.Error(string(output), zap.Error(err))
}
return err
}

View File

@ -1,195 +0,0 @@
// Copyright 2022 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package chaosd
import (
"bufio"
"bytes"
"encoding/json"
"io"
"io/fs"
"io/ioutil"
"net"
"net/http"
"os/exec"
"strings"
"time"
"github.com/pkg/errors"
"github.com/shirou/gopsutil/process"
"github.com/chaos-mesh/chaosd/pkg/core"
)
type attackHTTP struct{}
var HTTPAttack AttackType = attackHTTP{}
func (attackHTTP) Attack(options core.AttackConfig, _ Environment) error {
var attackConf *core.HTTPAttackConfig
var ok bool
if attackConf, ok = options.(*core.HTTPAttackConfig); !ok {
return errors.New("AttackConfig -> *HTTPAttackConfig meet error")
}
if attackConf.Action == core.HTTPRequestAction {
return attackHTTPRequest(attackConf)
}
cmd := exec.Command("tproxy", "-i", "-vv")
stdin, err := cmd.StdinPipe()
if err != nil {
return errors.Wrap(err, "create stdin pipe")
}
stdout, err := cmd.StdoutPipe()
if err != nil {
return errors.Wrap(err, "create stdout pipe")
}
err = cmd.Start()
if err != nil {
return errors.Wrapf(err, "start command `%s`", cmd.String())
}
config, err := json.Marshal(&attackConf.Config)
attackConf.Logger.Info(string(config))
if err != nil {
return errors.Wrap(err, "applying HTTP attack")
}
req, err := http.NewRequest(http.MethodPut, "/", bytes.NewReader(config))
if err != nil {
return errors.Wrap(err, "create http request")
}
err = req.Write(stdin)
if err != nil {
return errors.Wrap(err, "cannot request tproxy")
}
resp, err := http.ReadResponse(bufio.NewReader(stdout), req)
if err != nil {
return errors.Wrap(err, "cannot read response")
}
if resp.StatusCode != http.StatusOK {
by, err := io.ReadAll(resp.Body)
if err != nil {
return errors.Wrapf(err, "cannot read err resp body, %s", resp.Status)
}
return errors.Errorf("%s: %s", resp.Status, string(by))
}
by, err := io.ReadAll(resp.Body)
if err != nil {
return errors.Wrapf(err, "cannot read resp body")
}
attackConf.Logger.Info(string(by))
attackConf.ProxyPID = cmd.Process.Pid
// In linux, a child process will become orphan process when a parent process dies.
// But Golang runtime maintains a finalizer for a child process.
// Release() will clear the finalizer for chaos-tproxy here.
err = cmd.Process.Release()
if err != nil {
return errors.Wrapf(err, "Fatal error : release process fail, please clear PID: %d", attackConf.ProxyPID)
}
return nil
}
func (attackHTTP) Recover(exp core.Experiment, _ Environment) error {
config, err := exp.GetRequestCommand()
if err != nil {
return err
}
attack, ok := config.(*core.HTTPAttackConfig)
if !ok {
return errors.Errorf("AttackConfig -> *HTTPAttackConfig meet error")
}
proc, err := process.NewProcess(int32(attack.ProxyPID))
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return nil
}
return err
}
procName, err := proc.Name()
if err != nil {
return errors.Wrapf(err, "unexpected error when proc.Name. process pid: %d", proc.Pid)
}
if !strings.Contains(procName, "tproxy") {
attack.Logger.Info("the process %s:%d is not chaos-tproxy, please check and clear it manually\n", procName, attack.ProxyPID)
return nil
}
if err := proc.Terminate(); err != nil {
attack.Logger.Info("the chaos-tproxy process kill failed with error: %s\n", err.Error())
return nil
}
return nil
}
func attackHTTPRequest(attackConf *core.HTTPAttackConfig) error {
if attackConf.EnableConnPool {
var HTTPTransport = &http.Transport{
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 60 * time.Second,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 60 * time.Second,
ExpectContinueTimeout: 30 * time.Second,
MaxIdleConnsPerHost: 100,
}
client := &http.Client{
Transport: HTTPTransport,
}
for i := 0; i < attackConf.Count; i++ {
req, err := http.NewRequest(http.MethodGet, attackConf.URL, nil)
if err != nil {
return errors.Wrap(err, "create HTTP request")
}
resp, err := client.Do(req)
if err != nil {
return errors.Wrap(err, "HTTP request")
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return errors.Wrap(err, "read response body")
}
attackConf.Logger.Info("response body: " + string(data))
}
} else {
for i := 0; i < attackConf.Count; i++ {
resp, err := http.Get(attackConf.URL)
if err != nil {
return errors.Wrap(err, "HTTP request")
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return errors.Wrap(err, "read response body")
}
attackConf.Logger.Info("response body: " + string(data))
}
}
return nil
}

View File

@ -18,9 +18,7 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
"strings"
"text/template"
"github.com/pingcap/errors"
@ -30,104 +28,159 @@ import (
"github.com/chaos-mesh/chaosd/pkg/core"
)
const ruleTemplate = `
RULE {{.Name}}
CLASS {{.Class}}
METHOD {{.Method}}
AT ENTRY
IF true
DO
{{.Do}};
ENDRULE
`
const stressRuleTemplate = `
RULE {{.Name}}
STRESS {{.StressType}}
{{.StressValueName}} {{.StressValue}}
ENDRULE
`
const gcRuleTemplate = `
RULE {{.Name}}
GC
ENDRULE
`
type jvmAttack struct{}
var JVMAttack AttackType = jvmAttack{}
const bmInstallCommand = "bminstall.sh -b -Dorg.jboss.byteman.transform.all -Dorg.jboss.byteman.verbose -Dorg.jboss.byteman.compileToBytecode -p %d %d"
const bmInstallCommand = "bminstall.sh -b -Dorg.jboss.byteman.transform.all -Dorg.jboss.byteman.verbose -p %d %d"
const bmSubmitCommand = "bmsubmit.sh -p %d -%s %s"
func (j jvmAttack) Attack(options core.AttackConfig, env Environment) (err error) {
// install agent
attack := options.(*core.JVMCommand)
if attack.Type == core.JVMInstallType {
return j.install(attack)
} else if attack.Type == core.JVMSubmitType {
return j.submit(attack)
}
return errors.Errorf("attack type %s not supported", attack.Type)
}
func (j jvmAttack) install(attack *core.JVMCommand) error {
var err error
bmInstallCmd := fmt.Sprintf(bmInstallCommand, attack.Port, attack.Pid)
cmd := exec.Command("bash", "-c", bmInstallCmd)
output, err := cmd.CombinedOutput()
if err != nil {
// this error will occured when install agent more than once, and will ignore this error and continue to submit rule
errMsg1 := "Agent JAR loaded but agent failed to initialize"
// these two errors will occured when java version less or euqal to 1.8, and don't know why
// but it can install agent success even with this error, so just ignore it now.
// TODO: Investigate the cause of these two error
errMsg2 := "Provider sun.tools.attach.LinuxAttachProvider not found"
errMsg3 := "install java.io.IOException: Non-numeric value found"
if !strings.Contains(string(output), errMsg1) && !strings.Contains(string(output), errMsg2) &&
!strings.Contains(string(output), errMsg3) {
log.Error(string(output), zap.Error(err))
return err
}
log.Debug(string(output), zap.Error(err))
}
// submit helper jar
bmSubmitCmd := fmt.Sprintf(bmSubmitCommand, attack.Port, "b", fmt.Sprintf("%s/lib/byteman-helper.jar", os.Getenv("BYTEMAN_HOME")))
cmd = exec.Command("bash", "-c", bmSubmitCmd)
output, err = cmd.CombinedOutput()
if err != nil {
log.Error(string(output), zap.Error(err))
return err
}
if len(output) > 0 {
log.Info("submit helper", zap.String("output", string(output)))
}
// submit rules
log.Info(string(output))
return err
}
func (j jvmAttack) submit(attack *core.JVMCommand) error {
ruleFile, err := j.generateRuleFile(attack)
if err != nil {
return err
}
bmSubmitCmd = fmt.Sprintf(bmSubmitCommand, attack.Port, "l", ruleFile)
cmd = exec.Command("bash", "-c", bmSubmitCmd)
output, err = cmd.CombinedOutput()
bmSubmitCmd := fmt.Sprintf(bmSubmitCommand, attack.Port, "l", ruleFile)
cmd := exec.Command("bash", "-c", bmSubmitCmd)
output, err := cmd.CombinedOutput()
if err != nil {
log.Error(string(output), zap.Error(err))
return err
}
if len(output) > 0 {
log.Info("submit rules", zap.String("output", string(output)))
}
log.Info(string(output))
return nil
}
func (j jvmAttack) generateRuleFile(attack *core.JVMCommand) (string, error) {
var err error
if len(attack.RuleData) > 0 {
filename, err := writeDataIntoFile(attack.RuleData, "rule.btm")
if err != nil {
return "", err
}
log.Info("byteman rule", zap.String("rule", string(attack.RuleData)), zap.String("file", filename))
return filename, nil
}
if len(attack.RuleFile) > 0 {
data, err := ioutil.ReadFile(attack.RuleFile)
attack.RuleData, err = ioutil.ReadFile(attack.RuleFile)
if err != nil {
return "", err
}
attack.RuleData = string(data)
log.Info("rule file data:" + attack.RuleData)
log.Info("rule file data:" + string(attack.RuleData))
return attack.RuleFile, nil
}
attack.RuleData, err = generateRuleData(attack)
if len(attack.Do) == 0 {
switch attack.Action {
case core.JVMLatencyAction:
attack.Do = fmt.Sprintf("Thread.sleep(%s)", attack.LatencyDuration)
case core.JVMExceptionAction:
attack.Do = fmt.Sprintf("throw new %s", attack.ThrowException)
case core.JVMReturnAction:
attack.Do = fmt.Sprintf("return %s", attack.ReturnValue)
case core.JVMStressAction:
if attack.CPUCount > 0 {
attack.StressType = "CPU"
attack.StressValueName = "CPUCOUNT"
attack.StressValue = attack.CPUCount
} else {
attack.StressType = "MEMORY"
attack.StressValueName = "MEMORYSIZE"
attack.StressValue = attack.MemorySize
}
}
}
buf := new(bytes.Buffer)
var t *template.Template
switch attack.Action {
case core.JVMStressAction:
t = template.Must(template.New("byteman rule").Parse(stressRuleTemplate))
case core.JVMExceptionAction, core.JVMLatencyAction, core.JVMReturnAction:
t = template.Must(template.New("byteman rule").Parse(ruleTemplate))
case core.JVMGCAction:
t = template.Must(template.New("byteman rule").Parse(gcRuleTemplate))
default:
return "", errors.Errorf("jvm action %s not supported", attack.Action)
}
if t == nil {
return "", errors.Errorf("parse byeman rule template failed")
}
err = t.Execute(buf, attack)
if err != nil {
log.Error("executing template", zap.Error(err))
return "", err
}
log.Info("byteman rule", zap.String("rule", buf.String()))
tmpfile, err := ioutil.TempFile("", "rule.btm")
if err != nil {
return "", err
}
filename, err := writeDataIntoFile(attack.RuleData, "rule.btm")
if err != nil {
log.Info("create btm file", zap.String("file", tmpfile.Name()))
if _, err := tmpfile.Write(buf.Bytes()); err != nil {
return "", err
}
log.Info("byteman rule", zap.String("rule", attack.RuleData), zap.String("file", filename))
return filename, nil
attack.RuleData = buf.Bytes()
if err := tmpfile.Close(); err != nil {
return "", err
}
return tmpfile.Name(), nil
}
func (j jvmAttack) Recover(exp core.Experiment, env Environment) error {
@ -136,13 +189,22 @@ func (j jvmAttack) Recover(exp core.Experiment, env Environment) error {
return err
}
filename, err := writeDataIntoFile(attack.RuleData, "rule.btm")
tmpfile, err := ioutil.TempFile("", "rule.btm")
if err != nil {
return err
}
log.Info("create btm file", zap.String("file", filename))
bmSubmitCmd := fmt.Sprintf(bmSubmitCommand, attack.Port, "u", filename)
if _, err := tmpfile.Write(attack.RuleData); err != nil {
return err
}
if err := tmpfile.Close(); err != nil {
return err
}
log.Info("create btm file", zap.String("file", tmpfile.Name()))
bmSubmitCmd := fmt.Sprintf(bmSubmitCommand, attack.Port, "u", tmpfile.Name())
cmd := exec.Command("bash", "-c", bmSubmitCmd)
output, err := cmd.CombinedOutput()
if err != nil {
@ -154,104 +216,3 @@ func (j jvmAttack) Recover(exp core.Experiment, env Environment) error {
return nil
}
func generateRuleData(attack *core.JVMCommand) (string, error) {
bytemanTemplateSpec := core.BytemanTemplateSpec{
Name: attack.Name,
Class: attack.Class,
Method: attack.Method,
}
var mysqlException string
switch attack.Action {
case core.JVMLatencyAction:
bytemanTemplateSpec.Do = fmt.Sprintf("Thread.sleep(%d)", attack.LatencyDuration)
case core.JVMExceptionAction:
bytemanTemplateSpec.Do = fmt.Sprintf("throw new %s", attack.ThrowException)
case core.JVMReturnAction:
bytemanTemplateSpec.Do = fmt.Sprintf("return %s", attack.ReturnValue)
case core.JVMStressAction:
bytemanTemplateSpec.Helper = core.StressHelper
bytemanTemplateSpec.Class = core.TriggerClass
bytemanTemplateSpec.Method = core.TriggerMethod
// the bind and condition is useless, only used for fill the template
bytemanTemplateSpec.Bind = "flag:boolean=true"
bytemanTemplateSpec.Condition = "true"
if attack.CPUCount > 0 {
bytemanTemplateSpec.Do = fmt.Sprintf("injectCPUStress(\"%s\", %d)", attack.Name, attack.CPUCount)
} else {
bytemanTemplateSpec.Do = fmt.Sprintf("injectMemStress(\"%s\", \"%s\")", attack.Name, attack.MemoryType)
}
case core.JVMGCAction:
bytemanTemplateSpec.Helper = core.GCHelper
bytemanTemplateSpec.Class = core.TriggerClass
bytemanTemplateSpec.Method = core.TriggerMethod
// the bind and condition is useless, only used for fill the template
bytemanTemplateSpec.Bind = "flag:boolean=true"
bytemanTemplateSpec.Condition = "true"
bytemanTemplateSpec.Do = "gc()"
case core.JVMMySQLAction:
bytemanTemplateSpec.Helper = core.SQLHelper
// the first parameter of matchDBTable is the database which the SQL execute in, because the SQL may not contain database, for example: select * from t1;
// can't get the database information now, so use a "" instead
// TODO: get the database information and fill it in matchDBTable function
bytemanTemplateSpec.Bind = fmt.Sprintf("flag:boolean=matchDBTable(\"\", $2, \"%s\", \"%s\", \"%s\")", attack.Database, attack.Table, attack.SQLType)
bytemanTemplateSpec.Condition = "flag"
if attack.MySQLConnectorVersion == "5" {
bytemanTemplateSpec.Class = core.MySQL5InjectClass
bytemanTemplateSpec.Method = core.MySQL5InjectMethod
mysqlException = core.MySQL5Exception
} else if attack.MySQLConnectorVersion == "8" {
bytemanTemplateSpec.Class = core.MySQL8InjectClass
bytemanTemplateSpec.Method = core.MySQL8InjectMethod
mysqlException = core.MySQL8Exception
} else {
return "", errors.Errorf("mysql connector version %s is not supported", attack.MySQLConnectorVersion)
}
if len(attack.ThrowException) > 0 {
exception := fmt.Sprintf(mysqlException, attack.ThrowException)
bytemanTemplateSpec.Do = fmt.Sprintf("throw new %s", exception)
} else if attack.LatencyDuration > 0 {
bytemanTemplateSpec.Do = fmt.Sprintf("Thread.sleep(%d)", attack.LatencyDuration)
}
}
buf := new(bytes.Buffer)
var t *template.Template
switch attack.Action {
case core.JVMStressAction, core.JVMGCAction, core.JVMMySQLAction:
t = template.Must(template.New("byteman rule").Parse(core.CompleteRuleTemplate))
case core.JVMExceptionAction, core.JVMLatencyAction, core.JVMReturnAction:
t = template.Must(template.New("byteman rule").Parse(core.SimpleRuleTemplate))
default:
return "", errors.Errorf("jvm action %s not supported", attack.Action)
}
if t == nil {
return "", errors.Errorf("parse byeman rule template failed")
}
err := t.Execute(buf, bytemanTemplateSpec)
if err != nil {
log.Error("executing template", zap.Error(err))
return "", err
}
return buf.String(), nil
}
func writeDataIntoFile(data string, filename string) (string, error) {
tmpfile, err := ioutil.TempFile("", filename)
if err != nil {
return "", err
}
if _, err := tmpfile.WriteString(data); err != nil {
return "", err
}
if err := tmpfile.Close(); err != nil {
return "", err
}
return tmpfile.Name(), err
}

View File

@ -1,153 +0,0 @@
// Copyright 2021 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package chaosd
import (
"testing"
. "github.com/onsi/gomega"
"github.com/chaos-mesh/chaosd/pkg/core"
)
func TestGenerateRuleData(t *testing.T) {
g := NewGomegaWithT(t)
testCases := []struct {
cmd *core.JVMCommand
ruleData string
}{
{
&core.JVMCommand{
Name: "test",
JVMCommonSpec: core.JVMCommonSpec{
Pid: 1234,
},
Action: core.JVMExceptionAction,
JVMClassMethodSpec: core.JVMClassMethodSpec{
Class: "testClass",
Method: "testMethod",
},
ThrowException: "java.io.IOException(\"BOOM\")",
},
"\nRULE test\nCLASS testClass\nMETHOD testMethod\nAT ENTRY\nIF true\nDO\n\tthrow new java.io.IOException(\"BOOM\");\nENDRULE\n",
},
{
&core.JVMCommand{
Name: "test",
JVMCommonSpec: core.JVMCommonSpec{
Pid: 1234,
},
Action: core.JVMReturnAction,
JVMClassMethodSpec: core.JVMClassMethodSpec{
Class: "testClass",
Method: "testMethod",
},
ReturnValue: "\"test\"",
},
"\nRULE test\nCLASS testClass\nMETHOD testMethod\nAT ENTRY\nIF true\nDO\n\treturn \"test\";\nENDRULE\n",
},
{
&core.JVMCommand{
Name: "test",
JVMCommonSpec: core.JVMCommonSpec{
Pid: 1234,
},
Action: core.JVMLatencyAction,
JVMClassMethodSpec: core.JVMClassMethodSpec{
Class: "testClass",
Method: "testMethod",
},
LatencyDuration: 5000,
},
"\nRULE test\nCLASS testClass\nMETHOD testMethod\nAT ENTRY\nIF true\nDO\n\tThread.sleep(5000);\nENDRULE\n",
},
{
&core.JVMCommand{
Name: "test",
JVMCommonSpec: core.JVMCommonSpec{
Pid: 1234,
},
Action: core.JVMStressAction,
JVMStressSpec: core.JVMStressSpec{
CPUCount: 1,
},
},
"\nRULE test\nCLASS org.chaos_mesh.chaos_agent.TriggerThread\nMETHOD triggerFunc\nHELPER org.chaos_mesh.byteman.helper.StressHelper\nAT ENTRY\nBIND flag:boolean=true;\nIF true\nDO\n\tinjectCPUStress(\"test\", 1);\nENDRULE\n",
},
{
&core.JVMCommand{
Name: "test",
JVMCommonSpec: core.JVMCommonSpec{
Pid: 1234,
},
Action: core.JVMStressAction,
JVMStressSpec: core.JVMStressSpec{
MemoryType: "heap",
},
},
"\nRULE test\nCLASS org.chaos_mesh.chaos_agent.TriggerThread\nMETHOD triggerFunc\nHELPER org.chaos_mesh.byteman.helper.StressHelper\nAT ENTRY\nBIND flag:boolean=true;\nIF true\nDO\n\tinjectMemStress(\"test\", \"heap\");\nENDRULE\n",
},
{
&core.JVMCommand{
Name: "test",
JVMCommonSpec: core.JVMCommonSpec{
Pid: 1234,
},
Action: core.JVMGCAction,
},
"\nRULE test\nCLASS org.chaos_mesh.chaos_agent.TriggerThread\nMETHOD triggerFunc\nHELPER org.chaos_mesh.byteman.helper.GCHelper\nAT ENTRY\nBIND flag:boolean=true;\nIF true\nDO\n\tgc();\nENDRULE\n",
},
{
&core.JVMCommand{
Name: "test",
JVMCommonSpec: core.JVMCommonSpec{
Pid: 1234,
},
Action: core.JVMMySQLAction,
JVMMySQLSpec: core.JVMMySQLSpec{
MySQLConnectorVersion: "8",
Database: "test",
Table: "t1",
SQLType: "select",
},
ThrowException: "BOOM",
},
"\nRULE test\nCLASS com.mysql.cj.NativeSession\nMETHOD execSQL\nHELPER org.chaos_mesh.byteman.helper.SQLHelper\nAT ENTRY\nBIND flag:boolean=matchDBTable(\"\", $2, \"test\", \"t1\", \"select\");\nIF flag\nDO\n\tthrow new com.mysql.cj.exceptions.CJException(\"BOOM\");\nENDRULE\n",
},
{
&core.JVMCommand{
Name: "test",
JVMCommonSpec: core.JVMCommonSpec{
Pid: 1234,
},
Action: core.JVMMySQLAction,
JVMMySQLSpec: core.JVMMySQLSpec{
MySQLConnectorVersion: "8",
Database: "test",
Table: "t1",
SQLType: "select",
},
LatencyDuration: 5000,
},
"\nRULE test\nCLASS com.mysql.cj.NativeSession\nMETHOD execSQL\nHELPER org.chaos_mesh.byteman.helper.SQLHelper\nAT ENTRY\nBIND flag:boolean=matchDBTable(\"\", $2, \"test\", \"t1\", \"select\");\nIF flag\nDO\n\tThread.sleep(5000);\nENDRULE\n",
},
}
for _, testCase := range testCases {
ruleData, err := generateRuleData(testCase.cmd)
g.Expect(err).ShouldNot(HaveOccurred())
g.Expect(ruleData).Should(Equal(testCase.ruleData))
}
}

View File

@ -1,421 +0,0 @@
// Copyright 2022 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package chaosd
import (
"context"
"encoding/json"
"fmt"
"io/fs"
"io/ioutil"
"os"
"os/exec"
"path"
"strings"
"sync"
"time"
"github.com/dustin/go-humanize"
"github.com/magiconair/properties"
"github.com/pingcap/errors"
"github.com/pingcap/log"
perr "github.com/pkg/errors"
client "github.com/segmentio/kafka-go"
"github.com/segmentio/kafka-go/sasl/plain"
"github.com/segmentio/kafka-go/sasl/scram"
"go.uber.org/zap"
"github.com/chaos-mesh/chaosd/pkg/core"
)
type kafkaAttack struct{}
var KafkaAttack AttackType = kafkaAttack{}
func (j kafkaAttack) Attack(options core.AttackConfig, env Environment) (err error) {
attack := options.(*core.KafkaCommand)
switch attack.Action {
case core.KafkaFillAction:
return attackKafkaFill(context.TODO(), attack)
case core.KafkaFloodAction:
return attackKafkaFlood(context.TODO(), attack)
case core.KafkaIOAction:
return attackKafkaIO(attack)
default:
return errors.Errorf("invalid action: %s", attack.Action)
}
}
func (j kafkaAttack) Recover(exp core.Experiment, env Environment) error {
attack := new(core.KafkaCommand)
if err := json.Unmarshal([]byte(exp.RecoverCommand), attack); err != nil {
return perr.Wrap(err, "unmarshal kafka command")
}
switch attack.Action {
case core.KafkaFillAction:
return recoverKafkaFill(attack)
case core.KafkaIOAction:
return recoverKafkaIO(attack, env)
default:
return nil
}
}
func newDialer(attack *core.KafkaCommand) (dialer *client.Dialer, err error) {
dialer = &client.Dialer{
Timeout: 10 * time.Second,
DualStack: true,
}
if attack.Username != "" {
switch core.KafkaAuthMechanism(attack.AuthMechanism) {
case core.SaslPlain:
dialer.SASLMechanism = plain.Mechanism{
Username: attack.Username,
Password: attack.Password,
}
case core.SaslScream256:
dialer.SASLMechanism, err = scram.Mechanism(scram.SHA256, attack.Username, attack.Password)
case core.SaslScram512:
dialer.SASLMechanism, err = scram.Mechanism(scram.SHA512, attack.Username, attack.Password)
default:
return nil, errors.Errorf("invalid auth mechanism: %s", attack.AuthMechanism)
}
if err != nil {
return nil, perr.Wrap(err, "create scram mechanism")
}
}
return dialer, nil
}
func dial(attack *core.KafkaCommand) (conn *client.Conn, err error) {
endpoint := fmt.Sprintf("%s:%d", attack.Host, attack.Port)
dialer, err := newDialer(attack)
if err != nil {
return nil, perr.Wrap(err, "new dialer")
}
conn, err = dialer.Dial("tcp", endpoint)
if err != nil {
return nil, perr.Wrapf(err, "dial endpoint: %s", endpoint)
}
return conn, nil
}
func dialPartition(ctx context.Context, attack *core.KafkaCommand, partition int) (conn *client.Conn, err error) {
endpoint := fmt.Sprintf("%s:%d", attack.Host, attack.Port)
dialer, err := newDialer(attack)
if err != nil {
return nil, perr.Wrap(err, "new dialer")
}
conn, err = dialer.DialLeader(ctx, "tcp", endpoint, attack.Topic, partition)
if err != nil {
return nil, perr.Wrapf(err, "dial endpoint: %s", endpoint)
}
return conn, nil
}
func getPartitions(attack *core.KafkaCommand) (partitions []int, err error) {
dialer, err := newDialer(attack)
if err != nil {
return nil, err
}
endpoint := fmt.Sprintf("%s:%d", attack.Host, attack.Port)
conn, err := dialer.Dial("tcp", endpoint)
if err != nil {
return nil, errors.Wrapf(err, "dial endpoint: %s", endpoint)
}
if attack.Topic == "" {
return nil, errors.New("topic is required")
}
pars, err := conn.ReadPartitions(attack.Topic)
if err != nil {
return nil, errors.Wrapf(err, "read partitions of topic %s", attack.Topic)
}
for _, par := range pars {
if par.Error != nil {
return nil, errors.Wrap(err, "read partition")
}
partitions = append(partitions, par.ID)
}
return partitions, nil
}
func attackKafkaFill(ctx context.Context, attack *core.KafkaCommand) (err error) {
// TODO: make it configurable
const messagePerRequest = 128
msg := make([]byte, attack.MessageSize)
msgList := make([]client.Message, 0, messagePerRequest)
for i := 0; i < messagePerRequest; i++ {
msgList = append(msgList, client.Message{Topic: attack.Topic, Value: msg})
}
partitions, err := getPartitions(attack)
if err != nil {
return err
}
writeChan := make(chan uint64)
errChan := make(chan error)
c, cancel := context.WithCancel(ctx)
defer cancel()
for _, partition := range partitions {
p := partition
go func() {
conn, err := dialPartition(c, attack, p)
if err != nil {
errChan <- err
return
}
for c.Err() == nil {
n, err := conn.WriteMessages(msgList...)
if err != nil {
errChan <- perr.Wrap(err, "write messages")
return
}
writeChan <- uint64(n)
}
}()
}
start := time.Now()
written := "0 B"
bytes := uint64(0)
err = func() error {
for {
select {
case <-ctx.Done():
return nil
case err := <-errChan:
return err
case n := <-writeChan:
bytes += uint64(n)
newWritten := humanize.Bytes(bytes)
if newWritten != written {
written = newWritten
log.Info(fmt.Sprintf("write %s in %s", written, time.Now().Sub(start)))
}
if bytes >= attack.MaxBytes {
return nil
}
}
}
}()
if err != nil {
return err
}
attack.OriginConfig, err = setRetentionBytes(attack, attack.MaxBytes)
return err
}
func recoverKafkaFill(attack *core.KafkaCommand) error {
newConfigFile := attack.ConfigFile + ".new"
if err := ioutil.WriteFile(newConfigFile, []byte(attack.OriginConfig), 0644); err != nil {
return perr.Wrapf(err, "write config file %s", newConfigFile)
}
err := os.Rename(newConfigFile, attack.ConfigFile)
if err != nil {
return perr.Wrapf(err, "rename config file %s to %s", newConfigFile, attack.ConfigFile)
}
rcmd := exec.Command("bash", "-c", attack.ReloadCommand)
if err := rcmd.Start(); err != nil {
return perr.Wrapf(err, "reload command %s", attack.ReloadCommand)
}
if err := rcmd.Wait(); err != nil {
return perr.Wrapf(err, "wait reload command %s", attack.ReloadCommand)
}
return err
}
func setRetentionBytes(attack *core.KafkaCommand, bytes uint64) (string, error) {
config, err := os.ReadFile(attack.ConfigFile)
if err != nil {
return "", perr.Wrap(err, "read config")
}
p, err := properties.Load(config, properties.UTF8)
if err != nil {
return "", perr.Wrapf(err, "load config file %s", attack.ConfigFile)
}
p.Set("log.retention.bytes", fmt.Sprintf("%d", bytes))
newConfigFile := attack.ConfigFile + ".new"
if err = ioutil.WriteFile(newConfigFile, []byte(p.String()), 0644); err != nil {
return "", perr.Wrapf(err, "write config file %s", newConfigFile)
}
err = os.Rename(newConfigFile, attack.ConfigFile)
if err != nil {
return "", perr.Wrapf(err, "rename config file %s to %s", newConfigFile, attack.ConfigFile)
}
rcmd := exec.Command("bash", "-c", attack.ReloadCommand)
if err := rcmd.Start(); err != nil {
return "", perr.Wrapf(err, "reload command %s", attack.ReloadCommand)
}
if err := rcmd.Wait(); err != nil {
return "", perr.Wrapf(err, "wait reload command %s", attack.ReloadCommand)
}
return string(config), nil
}
func attackKafkaFlood(ctx context.Context, attack *core.KafkaCommand) (err error) {
msg := make([]byte, attack.MessageSize)
wg := new(sync.WaitGroup)
for i := 0; i < int(attack.Threads); i++ {
logger := log.With(zap.String("thread", fmt.Sprintf("thread-%d", i)))
wg.Add(1)
go func() {
defer wg.Done()
conn, err := dialPartition(ctx, attack, 0)
if err != nil {
logger.Error(perr.Wrapf(err, "dial kafka broker: %s", fmt.Sprintf("%s:%d", attack.Host, attack.Port)).Error())
return
}
defer conn.Close()
for {
select {
case <-ctx.Done():
return
default:
start := time.Now()
succeeded := 0
failed := 0
for time.Now().Sub(start) < time.Second {
_, err = conn.WriteMessages(client.Message{Value: msg})
if err != nil {
failed++
logger.Debug("write message", zap.Error(err))
continue
}
succeeded++
}
logger.Info(fmt.Sprintf("succeeded: %d, failed: %d", succeeded, failed))
}
}
}()
}
wg.Wait()
return nil
}
func attackIOPath(
attack *core.KafkaCommand,
path string,
stat func(string) (fs.FileInfo, error),
chmod func(string, os.FileMode) error,
) error {
meta, err := stat(path)
if err != nil {
return perr.Wrapf(err, "stat %s", path)
}
mode := meta.Mode()
attack.OriginModeOfFiles[path] = uint32(meta.Mode())
if attack.NonReadable {
mode &= ^os.FileMode(0444)
}
if attack.NonWritable {
mode &= ^os.FileMode(0222)
}
log.Debug(fmt.Sprintf("change permission of %s to %s", path, mode))
err = chmod(path, mode)
if err != nil {
return perr.Wrapf(err, "change permission of %s", path)
}
return nil
}
func recoverIOPath(
path string,
originMode uint32,
chmod func(string, os.FileMode) error,
) error {
err := chmod(path, os.FileMode(originMode))
if err != nil {
return perr.Wrapf(err, "change permission of %s", path)
}
return nil
}
func attackKafkaIO(attack *core.KafkaCommand) error {
p, err := properties.LoadFile(attack.ConfigFile, properties.UTF8)
if err != nil {
return perr.Wrapf(err, "load config file %s", attack.ConfigFile)
}
partitionDirs, err := findPartitionDirs(attack, strings.Split(p.GetString("log.dirs", "/var/lib/kafka"), ","))
if err != nil {
return err
}
for _, dirPath := range partitionDirs {
err = attackIOPath(attack, dirPath, os.Stat, os.Chmod)
if err != nil {
return err
}
files, err := os.ReadDir(dirPath)
if err != nil {
return perr.Wrapf(err, "read partition dir %s", dirPath)
}
for _, file := range files {
if !strings.HasSuffix(file.Name(), ".log") {
continue
}
filePath := path.Join(dirPath, file.Name())
err = attackIOPath(attack, filePath, os.Stat, os.Chmod)
if err != nil {
return err
}
}
}
return nil
}
func recoverKafkaIO(attack *core.KafkaCommand, env Environment) error {
for path, originMode := range attack.OriginModeOfFiles {
err := recoverIOPath(path, originMode, os.Chmod)
if err != nil {
return err
}
}
return nil
}
func findPartitionDirs(attack *core.KafkaCommand, logDirs []string) ([]string, error) {
partitions, err := getPartitions(attack)
if err != nil {
return nil, err
}
dirs := make([]string, 0, len(partitions))
for _, partition := range partitions {
dirName := fmt.Sprintf("%s-%d", attack.Topic, partition)
for _, dir := range logDirs {
entries, err := os.ReadDir(strings.TrimSpace(dir))
if err != nil {
return nil, perr.Wrapf(err, "read dir: %s", dir)
}
for _, entry := range entries {
if entry.IsDir() && entry.Name() == dirName {
dirs = append(dirs, path.Join(strings.TrimSpace(dir), entry.Name()))
}
}
}
}
return dirs, nil
}

View File

@ -1,188 +0,0 @@
// Copyright 2022 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package chaosd
import (
"io/fs"
"os"
"testing"
"time"
perr "github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/chaos-mesh/chaosd/pkg/core"
)
type fakeFileInfo struct {
mode os.FileMode
}
func (i *fakeFileInfo) Name() string {
panic("unimplemented")
}
func (i *fakeFileInfo) Size() int64 {
panic("unimplemented")
}
func (i *fakeFileInfo) Mode() os.FileMode {
return i.mode
}
func (i *fakeFileInfo) ModTime() time.Time {
panic("unimplemented")
}
func (i *fakeFileInfo) IsDir() bool {
panic("unimplemented")
}
func (i *fakeFileInfo) Sys() interface{} {
panic("unimplemented")
}
type fakeFs map[string]*fakeFileInfo
func newFakeFs() fakeFs {
return make(fakeFs)
}
func (f fakeFs) stat(path string) (fs.FileInfo, error) {
info, ok := f[path]
if !ok {
return nil, perr.Errorf("fail to stat: %s", path)
}
return info, nil
}
func (f fakeFs) chmod(path string, mode os.FileMode) error {
info, ok := f[path]
if !ok {
return perr.Errorf("fail to stat: %s", path)
}
info.mode = mode
return nil
}
func (f fakeFs) clone() fakeFs {
bak := newFakeFs()
for p, i := range f {
bak[p] = &fakeFileInfo{
mode: i.mode,
}
}
return bak
}
func (f fakeFs) equal(other fakeFs) bool {
if len(f) != len(other) {
return false
}
for p, info := range f {
otherInfo, ok := other[p]
if !ok || info.mode != otherInfo.mode {
return false
}
}
return true
}
func (f fakeFs) nonreadable() fakeFs {
bak := f.clone()
for _, i := range bak {
i.mode &= ^os.FileMode(0444)
}
return bak
}
func (f fakeFs) nonwritable() fakeFs {
bak := f.clone()
for _, i := range bak {
i.mode &= ^os.FileMode(0222)
}
return bak
}
func TestAttackIO(t *testing.T) {
originFs := fakeFs{
"/a": &fakeFileInfo{
mode: os.FileMode(0777),
},
"/b": &fakeFileInfo{
mode: os.FileMode(0700),
},
"/c": &fakeFileInfo{
mode: os.FileMode(0070),
},
"/d": &fakeFileInfo{
mode: os.FileMode(0007),
},
"/e": &fakeFileInfo{
mode: os.FileMode(0123),
},
"/f": &fakeFileInfo{
mode: os.FileMode(0124),
},
"/g": &fakeFileInfo{
mode: os.FileMode(0247),
},
}
assert.True(t, originFs.equal(originFs))
assert.False(t, originFs.equal(originFs.nonreadable()))
bakFs := originFs.clone()
assert.True(t, originFs.equal(bakFs))
attack := core.NewKafkaCommand()
attack.NonReadable = true
// make only "/a" non-readable
err := attackIOPath(attack, "/a", bakFs.stat, bakFs.chmod)
assert.Nil(t, err)
assert.Equal(t, bakFs["/a"].Mode(), originFs.nonreadable()["/a"].Mode())
// recover
err = recoverIOPath("/a", uint32(originFs["/a"].Mode()), bakFs.chmod)
assert.Nil(t, err)
assert.True(t, bakFs.equal(originFs))
// make all path non-readable
for p := range bakFs {
err := attackIOPath(attack, p, bakFs.stat, bakFs.chmod)
assert.Nil(t, err)
}
assert.True(t, bakFs.equal(originFs.nonreadable()))
// recover
for p, mode := range attack.OriginModeOfFiles {
err := recoverIOPath(p, mode, bakFs.chmod)
assert.Nil(t, err)
}
assert.True(t, bakFs.equal(originFs))
// make all path non-readable and non-writable
attack.NonWritable = true
for p := range bakFs {
err := attackIOPath(attack, p, bakFs.stat, bakFs.chmod)
assert.Nil(t, err)
}
assert.True(t, bakFs.equal(originFs.nonreadable().nonwritable()))
// recover
for p, mode := range attack.OriginModeOfFiles {
err := recoverIOPath(p, mode, bakFs.chmod)
assert.Nil(t, err)
}
assert.True(t, bakFs.equal(originFs))
}

View File

@ -17,9 +17,7 @@ import (
"bufio"
"context"
"encoding/json"
"errors"
"fmt"
"io/fs"
"io/ioutil"
"os"
"os/exec"
@ -27,15 +25,17 @@ import (
"strings"
"syscall"
"github.com/go-logr/zapr"
"github.com/chaos-mesh/chaos-mesh/pkg/bpm"
"github.com/chaos-mesh/chaos-mesh/pkg/chaosdaemon/pb"
perrors "github.com/pingcap/errors"
"github.com/pingcap/log"
"github.com/shirou/gopsutil/process"
"go.uber.org/zap"
"github.com/pingcap/errors"
"github.com/pingcap/log"
"github.com/chaos-mesh/chaos-mesh/pkg/chaosdaemon/pb"
"github.com/chaos-mesh/chaosd/pkg/core"
)
@ -53,71 +53,54 @@ func (networkAttack) Attack(options core.AttackConfig, env Environment) (err err
case core.NetworkDNSAction:
if attack.NeedApplyEtcHosts() {
if err = env.Chaos.applyEtcHosts(attack, env.AttackUid, env); err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
}
if attack.NeedApplyDNSServer() {
if err = env.Chaos.updateDNSServer(attack); err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
}
case core.NetworkPortOccupiedAction:
case core.NetworkPortOccupied:
return env.Chaos.applyPortOccupied(attack)
case core.NetworkDelayAction, core.NetworkLossAction, core.NetworkCorruptAction, core.NetworkDuplicateAction, core.NetworkBandwidthAction, core.NetworkPartitionAction:
case core.NetworkDelayAction, core.NetworkLossAction, core.NetworkCorruptAction, core.NetworkDuplicateAction:
if attack.NeedApplyIPSet() {
ipsetName, err = env.Chaos.applyIPSet(attack, env.AttackUid)
if err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
}
if err = env.Chaos.applyIptables(attack, ipsetName, env.AttackUid); err != nil {
return perrors.WithStack(err)
if attack.NeedApplyIptables() {
if err = env.Chaos.applyIptables(attack, env.AttackUid); err != nil {
return errors.WithStack(err)
}
}
// Because some tcs add filter iptables which will not be stored in the DB, we must re-apply these tcs to add the iptables.
if err = env.Chaos.applyTC(attack, ipsetName, env.AttackUid); err != nil {
return perrors.WithStack(err)
if attack.NeedApplyTC() {
if err = env.Chaos.applyTC(attack, ipsetName, env.AttackUid); err != nil {
return errors.WithStack(err)
}
}
case core.NetworkNICDownAction:
if err := env.Chaos.getNICIP(attack); err != nil {
return perrors.WithStack(err)
}
NICDownCommand := fmt.Sprintf("ifconfig %s down", attack.Device)
cmd := exec.Command("bash", "-c", NICDownCommand)
_, err := cmd.CombinedOutput()
if err != nil {
return perrors.WithStack(err)
}
if attack.Duration != "-1" {
err := env.Chaos.recoverNICDownScheduled(attack)
return perrors.WithStack(err)
}
case core.NetworkFloodAction:
return env.Chaos.applyFlood(attack)
}
return nil
}
func (s *Server) applyIPSet(attack *core.NetworkCommand, uid string) (string, error) {
ipset, err := attack.ToIPSet(fmt.Sprintf("chaos-%.16s", uid))
ipset, err := attack.ToIPSet(fmt.Sprintf("chaos-%s", uid[:16]))
if err != nil {
return "", perrors.WithStack(err)
return "", errors.WithStack(err)
}
if _, err := s.svr.FlushIPSets(context.Background(), &pb.IPSetsRequest{
Ipsets: []*pb.IPSet{ipset},
EnterNS: false,
}); err != nil {
return "", perrors.WithStack(err)
return "", errors.WithStack(err)
}
if err := s.ipsetRule.Set(context.Background(), &core.IPSetRule{
@ -125,47 +108,43 @@ func (s *Server) applyIPSet(attack *core.NetworkCommand, uid string) (string, er
Cidrs: strings.Join(ipset.Cidrs, ","),
Experiment: uid,
}); err != nil {
return "", perrors.WithStack(err)
return "", errors.WithStack(err)
}
return ipset.Name, nil
}
func (s *Server) applyIptables(attack *core.NetworkCommand, ipset, uid string) error {
func (s *Server) applyIptables(attack *core.NetworkCommand, uid string) error {
iptables, err := s.iptablesRule.List(context.Background())
if err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
chains := core.IptablesRuleList(iptables).ToChains()
newChain, err := attack.ToChain()
if err != nil {
return errors.WithStack(err)
}
var newChains []*pb.Chain
// Presently, only partition and delay with `accept-tcp-flags` need to add additional chains
if attack.NeedAdditionalChains() {
newChains, err = attack.AdditionalChain(ipset, attack.Device, uid)
if err != nil {
return perrors.WithStack(err)
}
chains = append(chains, newChains...)
if newChain != nil {
chains = append(chains, newChain)
}
if _, err := s.svr.SetIptablesChains(context.Background(), &pb.IptablesChainsRequest{
Chains: chains,
EnterNS: false,
}); err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
for _, newChain := range newChains {
if err := s.iptablesRule.Set(context.Background(), &core.IptablesRule{
Name: newChain.Name,
IPSets: strings.Join(newChain.Ipsets, ","),
Direction: pb.Chain_Direction_name[int32(newChain.Direction)],
Protocol: newChain.Protocol,
Experiment: uid,
}); err != nil {
return perrors.WithStack(err)
}
}
// TODO: cwen0
//if err := s.iptablesRule.Set(context.Background(), &core.IptablesRule{
// Name: newChain.Name,
// IPSets: strings.Join(newChain.Ipsets, ","),
// Direction: pb.Chain_Direction_name[int32(newChain.Direction)],
// Experiment: uid,
//}); err != nil {
// return errors.WithStack(err)
//}
return nil
}
@ -173,30 +152,22 @@ func (s *Server) applyIptables(attack *core.NetworkCommand, ipset, uid string) e
func (s *Server) applyTC(attack *core.NetworkCommand, ipset string, uid string) error {
tcRules, err := s.tcRule.FindByDevice(context.Background(), attack.Device)
if err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
tcs, err := core.TCRuleList(tcRules).ToTCs()
if err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
var newTC *pb.Tc
if attack.NeedApplyTC() {
newTC, err = attack.ToTC(ipset)
if err != nil {
return perrors.WithStack(err)
}
tcs = append(tcs, newTC)
newTC, err := attack.ToTC(ipset)
if err != nil {
return errors.WithStack(err)
}
if _, err := s.svr.SetTcs(context.Background(), &pb.TcsRequest{Tcs: tcs, EnterNS: false}); err != nil {
return perrors.WithStack(err)
}
if !attack.NeedApplyTC() {
return nil
tcs = append(tcs, newTC)
if _, err := s.svr.SetTcs(context.Background(), &pb.TcsRequest{Tcs: tcs, Device: attack.Device, EnterNS: false}); err != nil {
return errors.WithStack(err)
}
tc := &core.TcParameter{
@ -224,21 +195,13 @@ func (s *Server) applyTC(attack *core.NetworkCommand, ipset string, uid string)
Duplicate: attack.Percent,
Correlation: attack.Correlation,
}
case core.NetworkBandwidthAction:
tc.Bandwidth = &core.BandwidthSpec{
Rate: attack.Rate,
Limit: attack.Limit,
Buffer: attack.Buffer,
Peakrate: attack.Peakrate,
Minburst: attack.Minburst,
}
default:
return perrors.Errorf("network %s attack not supported", attack.Action)
return errors.Errorf("network %s attack not supported", attack.Action)
}
tcString, err := json.Marshal(tc)
if err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
if err := s.tcRule.Set(context.Background(), &core.TCRule{
@ -251,7 +214,7 @@ func (s *Server) applyTC(attack *core.NetworkCommand, ipset string, uid string)
EgressPort: newTC.EgressPort,
Experiment: uid,
}); err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
return nil
@ -273,12 +236,12 @@ func (s *Server) applyEtcHosts(attack *core.NetworkCommand, uid string, env Envi
stdout, err := backupCmd.CombinedOutput()
if err != nil {
log.Error(backupCmd.String()+string(stdout), zap.Error(err))
return perrors.WithStack(err)
return errors.WithStack(err)
}
fileBytes, err := ioutil.ReadFile("/etc/hosts.chaosd." + uid) // #nosec
if err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
lines := strings.Split(string(fileBytes), "\n")
@ -287,21 +250,21 @@ func (s *Server) applyEtcHosts(attack *core.NetworkCommand, uid string, env Envi
// example:
// 10.86.33.102 qunarzz.com q.qunarzz.com common.qunarzz.com
// 127.0.0.1 localhost
needle := "^(\\d{1,3})(\\.\\d{1,3}){3}.*\\b" + attack.DNSDomainName + "\\b.*"
needle := "^(\\d{1,3})(\\.\\d{1,3}){3}.*\\b" + attack.DNSHost + "\\b.*"
re, err := regexp.Compile(needle)
if err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
// match IP address, eg: 127.0.0.1
reIp, err := regexp.Compile(`^(\d{1,3})(\.\d{1,3}){3}`)
if err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
fd, err := os.OpenFile("/etc/hosts", os.O_RDWR|os.O_APPEND, 0600)
if err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
defer func() {
if err := fd.Close(); err != nil {
@ -322,54 +285,29 @@ func (s *Server) applyEtcHosts(attack *core.NetworkCommand, uid string, env Envi
line = line + "\n"
_, err := w.WriteString(line)
if err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
}
// if not match any, then add a new line.
if newFlag {
_, err := w.WriteString(attack.DNSIp + "\t" + attack.DNSDomainName + "\n")
_, err := w.WriteString(attack.DNSIp + "\t" + attack.DNSHost + "\n")
if err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
}
err = w.Flush()
if err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
err = fd.Sync()
if err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
recoverFlag = false
return nil
}
func (s *Server) applyFlood(attack *core.NetworkCommand) error {
cmd := bpm.DefaultProcessBuilder("bash", "-c", fmt.Sprintf("iperf -u -c %s -t %s -p %s -P %d -b %s", attack.IPAddress, attack.Duration, attack.Port, attack.Parallel, attack.Rate)).
Build(context.Background())
// Build will set SysProcAttr.Pdeathsig = syscall.SIGTERM, and so iperf will exit while chaosd exit
// so reset it here
cmd.Cmd.SysProcAttr = &syscall.SysProcAttr{}
zapLogger, err := zap.NewDevelopment()
if err != nil {
return err
}
logger := zapr.NewLogger(zapLogger)
backgroundProcessManager := bpm.StartBackgroundProcessManager(nil, logger)
_, err = backgroundProcessManager.StartProcess(context.Background(), cmd)
if err != nil {
return err
}
attack.IperfPid = int32(cmd.Process.Pid)
log.Info("Start iperf process successfully", zap.String("command", cmd.String()), zap.Int32("Pid", attack.IperfPid))
return nil
}
func (networkAttack) Recover(exp core.Experiment, env Environment) error {
config, err := exp.GetRequestCommand()
if err != nil {
@ -381,35 +319,37 @@ func (networkAttack) Recover(exp core.Experiment, env Environment) error {
case core.NetworkDNSAction:
if attack.NeedApplyEtcHosts() {
if err := env.Chaos.recoverEtcHosts(attack, env.AttackUid); err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
}
return env.Chaos.recoverDNSServer(attack)
case core.NetworkPortOccupiedAction:
case core.NetworkPortOccupied:
return env.Chaos.recoverPortOccupied(attack, env.AttackUid)
case core.NetworkDelayAction, core.NetworkLossAction, core.NetworkCorruptAction, core.NetworkDuplicateAction, core.NetworkPartitionAction, core.NetworkBandwidthAction:
if err := env.Chaos.recoverIPSet(env.AttackUid); err != nil {
return perrors.WithStack(err)
case core.NetworkDelayAction, core.NetworkLossAction, core.NetworkCorruptAction, core.NetworkDuplicateAction:
if attack.NeedApplyIPSet() {
if err := env.Chaos.recoverIPSet(env.AttackUid); err != nil {
return errors.WithStack(err)
}
}
if err := env.Chaos.recoverIptables(env.AttackUid); err != nil {
return perrors.WithStack(err)
if attack.NeedApplyIptables() {
if err := env.Chaos.recoverIptables(env.AttackUid); err != nil {
return errors.WithStack(err)
}
}
if err := env.Chaos.recoverTC(env.AttackUid, attack.Device); err != nil {
return perrors.WithStack(err)
if attack.NeedApplyTC() {
if err := env.Chaos.recoverTC(env.AttackUid, attack.Device); err != nil {
return errors.WithStack(err)
}
}
case core.NetworkNICDownAction:
return env.Chaos.recoverNICDown(attack)
case core.NetworkFloodAction:
return env.Chaos.recoverFlood(attack)
}
return nil
}
func (s *Server) recoverIPSet(uid string) error {
if err := s.ipsetRule.DeleteByExperiment(context.Background(), uid); err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
return nil
@ -417,12 +357,12 @@ func (s *Server) recoverIPSet(uid string) error {
func (s *Server) recoverIptables(uid string) error {
if err := s.iptablesRule.DeleteByExperiment(context.Background(), uid); err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
iptables, err := s.iptablesRule.List(context.Background())
if err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
chains := core.IptablesRuleList(iptables).ToChains()
@ -431,7 +371,7 @@ func (s *Server) recoverIptables(uid string) error {
Chains: chains,
EnterNS: false,
}); err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
return nil
@ -439,18 +379,18 @@ func (s *Server) recoverIptables(uid string) error {
func (s *Server) recoverTC(uid string, device string) error {
if err := s.tcRule.DeleteByExperiment(context.Background(), uid); err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
tcRules, err := s.tcRule.FindByDevice(context.Background(), device)
tcs, err := core.TCRuleList(tcRules).ToTCs()
if err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
if _, err := s.svr.SetTcs(context.Background(), &pb.TcsRequest{Tcs: tcs, EnterNS: false}); err != nil {
return perrors.WithStack(err)
if _, err := s.svr.SetTcs(context.Background(), &pb.TcsRequest{Tcs: tcs, Device: device, EnterNS: false}); err != nil {
return errors.WithStack(err)
}
return nil
@ -462,7 +402,7 @@ func (s *Server) updateDNSServer(attack *core.NetworkCommand) error {
Enable: true,
EnterNS: false,
}); err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
return nil
@ -473,7 +413,7 @@ func (s *Server) recoverDNSServer(attack *core.NetworkCommand) error {
Enable: false,
EnterNS: false,
}); err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
return nil
@ -488,28 +428,24 @@ func (s *Server) applyPortOccupied(attack *core.NetworkCommand) error {
flag, err := checkPortIsListened(attack.Port)
if err != nil {
if flag {
return perrors.Errorf("port %s has been occupied", attack.Port)
return errors.Errorf("port %s has been occupied", attack.Port)
}
return perrors.WithStack(err)
return errors.WithStack(err)
}
if flag {
return perrors.Errorf("port %s has been occupied", attack.Port)
return errors.Errorf("port %s has been occupied", attack.Port)
}
args := fmt.Sprintf("-p=%s", attack.Port)
cmd := bpm.DefaultProcessBuilder("PortOccupyTool", args).Build(context.Background())
cmd := bpm.DefaultProcessBuilder("PortOccupyTool", args).Build()
cmd.Cmd.SysProcAttr = &syscall.SysProcAttr{}
zapLogger, err := zap.NewDevelopment()
backgroundProcessManager := bpm.NewBackgroundProcessManager()
err = backgroundProcessManager.StartProcess(cmd)
if err != nil {
return err
}
logger := zapr.NewLogger(zapLogger)
backgroundProcessManager := bpm.StartBackgroundProcessManager(nil, logger)
_, err = backgroundProcessManager.StartProcess(context.Background(), cmd)
if err != nil {
return perrors.WithStack(err)
return errors.WithStack(err)
}
attack.PortPid = int32(cmd.Process.Pid)
@ -527,7 +463,7 @@ func checkPortIsListened(port string) (bool, error) {
return false, nil
}
log.Error(cmd.String()+string(stdout), zap.Error(err))
return true, perrors.WithStack(err)
return true, errors.WithStack(err)
}
if string(stdout) == "" {
@ -566,87 +502,7 @@ func (s *Server) recoverEtcHosts(attack *core.NetworkCommand, uid string) error
stdout, err := recoverCmd.CombinedOutput()
if err != nil {
log.Error(recoverCmd.String()+string(stdout), zap.Error(err))
return perrors.WithStack(err)
return errors.WithStack(err)
}
return nil
}
func (s *Server) recoverNICDown(attack *core.NetworkCommand) error {
NICUpCommand := fmt.Sprintf("ifconfig %s %s up", attack.Device, attack.IPAddress)
recoverCmd := exec.Command("bash", "-c", NICUpCommand)
_, err := recoverCmd.CombinedOutput()
if err != nil {
return perrors.WithStack(err)
}
return nil
}
func (s *Server) recoverNICDownScheduled(attack *core.NetworkCommand) error {
NICUpCommand := fmt.Sprintf("sleep %s && ifconfig %s %s up", attack.Duration, attack.Device, attack.IPAddress)
recoverCmd := exec.Command("bash", "-c", NICUpCommand)
_, err := recoverCmd.CombinedOutput()
if err != nil {
return perrors.WithStack(err)
}
return nil
}
func (s *Server) recoverFlood(attack *core.NetworkCommand) error {
proc, err := process.NewProcess(attack.IperfPid)
if err != nil {
if errors.Is(err, process.ErrorProcessNotRunning) || errors.Is(err, fs.ErrNotExist) {
log.Warn("Failed to get iperf process", zap.Error(err))
return nil
}
return err
}
procName, err := proc.Name()
if err != nil {
return err
}
if !strings.Contains(procName, "iperf") {
log.Warn("the process is not iperf, maybe it is killed by manual")
return nil
}
if err := proc.Kill(); err != nil {
log.Error("the iperf process kill failed", zap.Error(err))
return err
}
return nil
}
// getNICIP() uses `ifconfig` to get interfaces' IP. The reason for
// not using net.Interfaces() is that net.Interfaces() can't get
// sub interfaces.
func (s *Server) getNICIP(attack *core.NetworkCommand) error {
getIPCommand := fmt.Sprintf("ifconfig %s | awk '/inet\\>/ {print $2}'", attack.Device)
cmd := exec.Command("bash", "-c", getIPCommand)
stdout, err := cmd.StdoutPipe()
if err != nil {
return perrors.WithStack(err)
}
if err = cmd.Start(); err != nil {
return perrors.WithStack(err)
}
stdoutBytes := make([]byte, 1024)
_, err = stdout.Read(stdoutBytes)
if err != nil {
return perrors.WithStack(err)
}
// When stdoutBytes is converted to string, the string will be IPAddress with a few unnecessary
// zeros, which makes IPAddress' format wrong, so the trailing zeros needs to be trimmed.
attack.IPAddress = strings.TrimRight(string(stdoutBytes), "\n\x00")
return nil
}

View File

@ -14,16 +14,11 @@
package chaosd
import (
"fmt"
"os/exec"
"strconv"
"syscall"
"github.com/mitchellh/go-ps"
"github.com/pingcap/errors"
"github.com/shirou/gopsutil/process"
"go.uber.org/zap"
"github.com/pingcap/log"
"github.com/chaos-mesh/chaosd/pkg/core"
)
@ -35,28 +30,30 @@ var ProcessAttack AttackType = processAttack{}
func (processAttack) Attack(options core.AttackConfig, _ Environment) error {
attack := options.(*core.ProcessCommand)
processes, err := process.Processes()
processes, err := ps.Processes()
if err != nil {
return errors.WithStack(err)
}
notFound := true
for _, p := range processes {
pid := int(p.Pid)
name, err := p.Name()
if err != nil {
return errors.WithStack(err)
}
if attack.Process == strconv.Itoa(pid) || attack.Process == name {
if attack.Process == strconv.Itoa(p.Pid()) || attack.Process == p.Executable() {
notFound = false
switch attack.Signal {
case int(syscall.SIGKILL):
err = syscall.Kill(p.Pid(), syscall.SIGKILL)
case int(syscall.SIGTERM):
err = syscall.Kill(p.Pid(), syscall.SIGTERM)
case int(syscall.SIGSTOP):
err = syscall.Kill(p.Pid(), syscall.SIGSTOP)
default:
return errors.Errorf("signal %d is not supported", attack.Signal)
}
err = syscall.Kill(pid, syscall.Signal(attack.Signal))
if err != nil {
err = errors.Annotate(err, fmt.Sprintf("kill process with signal %d", attack.Signal))
return errors.WithStack(err)
}
attack.PIDs = append(attack.PIDs, pid)
attack.PIDs = append(attack.PIDs, p.Pid())
}
}
@ -75,23 +72,13 @@ func (processAttack) Recover(exp core.Experiment, _ Environment) error {
}
pcmd := config.(*core.ProcessCommand)
if pcmd.Signal != int(syscall.SIGSTOP) {
if pcmd.RecoverCmd == "" {
return core.ErrNonRecoverableAttack.New("only SIGSTOP process attack and process attack with the recover-cmd are supported to recover")
}
return core.ErrNonRecoverableAttack.New("only SIGSTOP process attack is supported to recover")
}
rcmd := exec.Command("bash", "-c", pcmd.RecoverCmd)
if err := rcmd.Start(); err != nil {
for _, pid := range pcmd.PIDs {
if err := syscall.Kill(pid, syscall.SIGCONT); err != nil {
return errors.WithStack(err)
}
log.Info("Execute recover-cmd successfully", zap.String("recover-cmd", pcmd.RecoverCmd))
} else {
for _, pid := range pcmd.PIDs {
if err := syscall.Kill(pid, syscall.SIGCONT); err != nil {
return errors.WithStack(err)
}
}
}
return nil

View File

@ -25,7 +25,7 @@ import (
)
func (s *Server) RecoverAttack(uid string) error {
exp, err := s.expStore.FindByUid(context.Background(), uid)
exp, err := s.exp.FindByUid(context.Background(), uid)
if err != nil {
return err
}
@ -61,24 +61,8 @@ func (s *Server) RecoverAttack(uid string) error {
attackType = StressAttack
case core.DiskAttack:
attackType = DiskAttack
case core.DiskServerAttack:
attackType = DiskServerAttack
case core.JVMAttack:
attackType = JVMAttack
case core.ClockAttack:
attackType = ClockAttack
case core.KafkaAttack:
attackType = KafkaAttack
case core.RedisAttack:
attackType = RedisAttack
case core.FileAttack:
attackType = FileAttack
case core.HTTPAttack:
attackType = HTTPAttack
case core.VMAttack:
attackType = VMAttack
case core.UserDefinedAttack:
attackType = UserDefinedAttack
default:
return perr.Errorf("chaos experiment kind %s not found", exp.Kind)
}
@ -93,7 +77,7 @@ func (s *Server) RecoverAttack(uid string) error {
}
}
if err := s.expStore.Update(context.Background(), uid, core.Destroyed, "", exp.RecoverCommand); err != nil {
if err := s.exp.Update(context.Background(), uid, core.Destroyed, "", exp.RecoverCommand); err != nil {
return perr.WithStack(err)
}
return nil

View File

@ -1,230 +0,0 @@
// Copyright 2022 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package chaosd
import (
"fmt"
"math"
"os/exec"
"strconv"
"time"
"github.com/go-redis/redis/v8"
"github.com/pingcap/errors"
"github.com/chaos-mesh/chaosd/pkg/core"
)
type redisAttack struct{}
var RedisAttack AttackType = redisAttack{}
const (
STATUSOK = "OK"
OPTIONNX = "NX"
OPTIONXX = "XX"
OPTIONGT = "GT"
OPTIONLT = "LT"
)
func (redisAttack) Attack(options core.AttackConfig, env Environment) error {
attack := options.(*core.RedisCommand)
cli := redis.NewClient(&redis.Options{
Addr: attack.Addr,
Password: attack.Password,
})
_, err := cli.Ping(cli.Context()).Result()
if err != nil {
return errors.WithStack(err)
}
switch attack.Action {
case core.RedisSentinelRestartAction:
err := env.Chaos.shutdownSentinelServer(attack, cli)
if err != nil {
return errors.WithStack(err)
}
return env.Chaos.recoverSentinelStop(attack)
case core.RedisSentinelStopAction:
return env.Chaos.shutdownSentinelServer(attack, cli)
case core.RedisCachePenetrationAction:
pipe := cli.Pipeline()
for i := 0; i < attack.RequestNum; i++ {
pipe.Get(cli.Context(), "CHAOS_MESH_nqE3BWm7khHv")
}
_, err := pipe.Exec(cli.Context())
if err != redis.Nil {
return errors.WithStack(err)
}
case core.RedisCacheLimitAction:
// `maxmemory` is an interface listwith content similar to `[maxmemory 1024]`
maxmemory, err := cli.ConfigGet(cli.Context(), "maxmemory").Result()
if err != nil {
return errors.WithStack(err)
}
// Get the value of maxmemory
attack.OriginCacheSize = fmt.Sprint(maxmemory[1])
var cacheSize string
if attack.Percent != "" {
percentage, err := strconv.ParseFloat(attack.Percent[0:len(attack.Percent)-1], 64)
if err != nil {
return errors.WithStack(err)
}
originCacheSize, err := strconv.ParseFloat(attack.OriginCacheSize, 64)
if err != nil {
return errors.WithStack(err)
}
cacheSize = fmt.Sprint(int(math.Floor(originCacheSize / 100.0 * percentage)))
} else {
cacheSize = attack.CacheSize
}
result, err := cli.ConfigSet(cli.Context(), "maxmemory", cacheSize).Result()
if err != nil {
return errors.WithStack(err)
}
if result != STATUSOK {
return errors.WithStack(errors.Errorf("redis command status is %s", result))
}
case core.RedisCacheExpirationAction:
return env.Chaos.expireKeys(attack, cli)
}
return nil
}
func (redisAttack) Recover(exp core.Experiment, env Environment) error {
config, err := exp.GetRequestCommand()
if err != nil {
return err
}
attack := config.(*core.RedisCommand)
switch attack.Action {
case core.RedisSentinelStopAction:
return env.Chaos.recoverSentinelStop(attack)
case core.RedisCacheLimitAction:
cli := redis.NewClient(&redis.Options{
Addr: attack.Addr,
Password: attack.Password,
})
result, err := cli.ConfigSet(cli.Context(), "maxmemory", attack.OriginCacheSize).Result()
if err != nil {
return errors.WithStack(err)
}
if result != STATUSOK {
return errors.WithStack(errors.Errorf("redis command status is %s", result))
}
}
return nil
}
func (s *Server) shutdownSentinelServer(attack *core.RedisCommand, cli *redis.Client) error {
if attack.FlushConfig {
// Because redis.Client doesn't have the func `FlushConfig()`, a redis.SentinelClient has to be created
sentinelCli := redis.NewSentinelClient(&redis.Options{
Addr: attack.Addr,
})
result, err := sentinelCli.FlushConfig(sentinelCli.Context()).Result()
if err != nil {
return errors.WithStack(err)
}
if result != STATUSOK {
return errors.WithStack(errors.Errorf("redis command status is %s", result))
}
}
// If cli.Shutdown() runs successfully, the result will be nil and the err will be "connection refused"
result, err := cli.Shutdown(cli.Context()).Result()
if result != "" {
return errors.WithStack(err)
}
return nil
}
func (s *Server) recoverSentinelStop(attack *core.RedisCommand) error {
if attack.Conf == "" {
return errors.WithStack(errors.Errorf("redis config does not exist"))
}
var redisPath string
if attack.RedisPath != "" {
redisPath = attack.RedisPath + "/redis-server"
} else {
redisPath = "redis-server"
}
recoverCmd := exec.Command(redisPath, attack.Conf, "--sentinel")
_, err := recoverCmd.CombinedOutput()
if err != nil {
return errors.WithStack(err)
}
return nil
}
func (s *Server) expireKeys(attack *core.RedisCommand, cli *redis.Client) error {
expiration, err := time.ParseDuration(attack.Expiration)
if err != nil {
return errors.WithStack(err)
}
if attack.Key == "" {
// Get all keys from the server
allKeys, err := cli.Keys(cli.Context(), "*").Result()
if err != nil {
return errors.WithStack(err)
}
for _, key := range allKeys {
result, err := ExpireFunc(cli, key, expiration, attack.Option).Result()
if err != nil {
return errors.WithStack(err)
}
if !result {
return errors.WithStack(errors.Errorf("expire failed"))
}
}
} else {
result, err := ExpireFunc(cli, attack.Key, expiration, attack.Option).Result()
if err != nil {
return errors.WithStack(err)
}
if !result {
return errors.WithStack(errors.Errorf("expire failed"))
}
}
return nil
}
func ExpireFunc(cli *redis.Client, key string, expiration time.Duration, option string) *redis.BoolCmd {
switch option {
case OPTIONNX:
return cli.ExpireNX(cli.Context(), key, expiration)
case OPTIONXX:
return cli.ExpireXX(cli.Context(), key, expiration)
case OPTIONGT:
return cli.ExpireGT(cli.Context(), key, expiration)
case OPTIONLT:
return cli.ExpireLT(cli.Context(), key, expiration)
default:
return cli.Expire(cli.Context(), key, expiration)
}
}

View File

@ -23,7 +23,7 @@ import (
func (s *Server) Search(conds *core.SearchCommand) ([]*core.Experiment, error) {
if len(conds.UID) > 0 {
exp, err := s.expStore.FindByUid(context.Background(), conds.UID)
exp, err := s.exp.FindByUid(context.Background(), conds.UID)
if err != nil {
return nil, errors.WithStack(err)
}
@ -31,7 +31,7 @@ func (s *Server) Search(conds *core.SearchCommand) ([]*core.Experiment, error) {
return []*core.Experiment{exp}, nil
}
exps, err := s.expStore.ListByConditions(context.Background(), conds)
exps, err := s.exp.ListByConditions(context.Background(), conds)
if err != nil {
return nil, errors.WithStack(err)
}

View File

@ -19,11 +19,10 @@ import (
"github.com/chaos-mesh/chaosd/pkg/config"
"github.com/chaos-mesh/chaosd/pkg/core"
"github.com/chaos-mesh/chaosd/pkg/scheduler"
"github.com/chaos-mesh/chaosd/pkg/utils"
)
type Server struct {
expStore core.ExperimentStore
exp core.ExperimentStore
ExpRun core.ExperimentRunStore
Cron scheduler.Scheduler
ipsetRule core.IPSetRuleStore
@ -31,8 +30,6 @@ type Server struct {
tcRule core.TCRuleStore
conf *config.Config
svr *chaosdaemon.DaemonServer
CmdPools map[string]*utils.CommandPools
}
func NewServer(
@ -47,13 +44,12 @@ func NewServer(
) *Server {
return &Server{
conf: conf,
expStore: exp,
exp: exp,
Cron: cron,
ExpRun: expRun,
ipsetRule: ipset,
iptablesRule: iptables,
tcRule: tc,
svr: svr,
CmdPools: make(map[string]*utils.CommandPools),
}
}

View File

@ -14,15 +14,10 @@
package chaosd
import (
"context"
"errors"
"fmt"
"io/fs"
"strings"
"syscall"
"github.com/go-logr/zapr"
"github.com/chaos-mesh/chaos-mesh/api/v1alpha1"
"github.com/chaos-mesh/chaos-mesh/pkg/bpm"
"github.com/pingcap/log"
@ -37,18 +32,10 @@ type stressAttack struct{}
var StressAttack AttackType = stressAttack{}
const (
CPUSTRESSORTOOL = "stress-ng"
MEMORYSTRESSORTOOL = "memStress"
)
func (stressAttack) Attack(options core.AttackConfig, _ Environment) (err error) {
attack := options.(*core.StressCommand)
stressors := &v1alpha1.Stressors{}
var stressorTool string
if attack.Action == core.StressCPUAction {
stressorTool = CPUSTRESSORTOOL
stressors.CPUStressor = &v1alpha1.CPUStressor{
Stressor: v1alpha1.Stressor{
Workers: attack.Workers,
@ -57,7 +44,6 @@ func (stressAttack) Attack(options core.AttackConfig, _ Environment) (err error)
Options: attack.Options,
}
} else if attack.Action == core.StressMemAction {
stressorTool = MEMORYSTRESSORTOOL
stressors.MemoryStressor = &v1alpha1.MemoryStressor{
Stressor: v1alpha1.Stressor{
Workers: attack.Workers,
@ -67,46 +53,32 @@ func (stressAttack) Attack(options core.AttackConfig, _ Environment) (err error)
}
}
var stressorsStr string
if attack.Action == core.StressCPUAction {
stressorsStr, _, err = stressors.Normalize()
if err != nil {
return
}
} else if attack.Action == core.StressMemAction {
_, stressorsStr, err = stressors.Normalize()
if err != nil {
return
}
}
errs := stressors.Validate(nil, field.NewPath("stressors"))
errs := stressors.Validate(field.NewPath("stressors"))
if len(errs) > 0 {
return errors.New(errs.ToAggregate().Error())
}
stressorsStr, err := stressors.Normalize()
if err != nil {
return
}
log.Info("stressors normalize", zap.String("arguments", stressorsStr))
cmd := bpm.DefaultProcessBuilder(stressorTool, strings.Fields(stressorsStr)...).
Build(context.Background())
cmd := bpm.DefaultProcessBuilder("stress-ng", strings.Fields(stressorsStr)...).
Build()
// Build will set SysProcAttr.Pdeathsig = syscall.SIGTERM, and so stress-ng will exit while chaosd exit
// so reset it here
cmd.Cmd.SysProcAttr = &syscall.SysProcAttr{}
zapLogger, err := zap.NewDevelopment()
if err != nil {
return err
}
logger := zapr.NewLogger(zapLogger)
backgroundProcessManager := bpm.StartBackgroundProcessManager(nil, logger)
_, err = backgroundProcessManager.StartProcess(context.Background(), cmd)
backgroundProcessManager := bpm.NewBackgroundProcessManager()
err = backgroundProcessManager.StartProcess(cmd)
if err != nil {
return
}
attack.StressngPid = int32(cmd.Process.Pid)
log.Info(fmt.Sprintf("Start %s process successfully", stressorTool), zap.String("command", cmd.String()), zap.Int32("Pid", attack.StressngPid))
log.Info("Start stress-ng process successfully", zap.String("command", cmd.String()), zap.Int32("Pid", attack.StressngPid))
return nil
}
@ -119,11 +91,6 @@ func (stressAttack) Recover(exp core.Experiment, _ Environment) error {
attack := config.(*core.StressCommand)
proc, err := process.NewProcess(attack.StressngPid)
if err != nil {
log.Warn("Failed to get process", zap.Error(err))
if errors.Is(err, process.ErrorProcessNotRunning) || errors.Is(err, fs.ErrNotExist) {
return nil
}
return err
}
@ -132,13 +99,13 @@ func (stressAttack) Recover(exp core.Experiment, _ Environment) error {
return err
}
if !strings.Contains(procName, CPUSTRESSORTOOL) && !strings.Contains(procName, MEMORYSTRESSORTOOL) {
log.Warn("the process is not stress-ng or memStress, maybe it is killed by manual")
if !strings.Contains(procName, "stress-ng") {
log.Warn("the process is not stress-ng, maybe it is killed by manual")
return nil
}
if err := proc.Kill(); err != nil {
log.Error("the process kill failed", zap.Error(err))
log.Error("the stress-ng process kill failed", zap.Error(err))
return err
}

View File

@ -1,64 +0,0 @@
// Copyright 2022 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package chaosd
import (
"os/exec"
"github.com/pingcap/log"
"github.com/pkg/errors"
"go.uber.org/zap"
"github.com/chaos-mesh/chaosd/pkg/core"
)
type userDefinedAttack struct{}
var UserDefinedAttack AttackType = userDefinedAttack{}
func (userDefinedAttack) Attack(options core.AttackConfig, _ Environment) error {
option := options.(*core.UserDefinedOption)
log.Info("attack command", zap.String("command", option.AttackCmd))
cmd := exec.Command("bash", "-c", option.AttackCmd)
output, err := cmd.CombinedOutput()
if err != nil {
return errors.Wrap(err, string(output))
}
if len(output) > 0 {
log.Info("attack command", zap.String("output", string(output)))
}
return nil
}
func (userDefinedAttack) Recover(exp core.Experiment, _ Environment) error {
config, err := exp.GetRequestCommand()
if err != nil {
return err
}
option := config.(*core.UserDefinedOption)
log.Info("recover command", zap.String("command", option.RecoverCmd))
cmd := exec.Command("bash", "-c", option.RecoverCmd)
output, err := cmd.CombinedOutput()
if err != nil {
return errors.Wrap(err, string(output))
}
if len(output) > 0 {
log.Info("recover command", zap.String("command", option.RecoverCmd), zap.String("output", string(output)))
}
return nil
}

View File

@ -1,66 +0,0 @@
// Copyright 2022 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package chaosd
import (
"errors"
"fmt"
"os/exec"
"github.com/pingcap/log"
"go.uber.org/zap"
"github.com/chaos-mesh/chaosd/pkg/core"
)
type vmAttack struct{}
var VMAttack AttackType = vmAttack{}
func (vm vmAttack) Attack(options core.AttackConfig, env Environment) error {
vmOption, ok := options.(*core.VMOption)
if !ok {
return errors.New("the type is not VMOption")
}
cmd := exec.Command("bash", "-c", fmt.Sprintf("virsh destroy %s", vmOption.VMName))
output, err := cmd.CombinedOutput()
if err != nil {
log.Error(string(output), zap.Error(err))
return err
}
return nil
}
func (vmAttack) Recover(exp core.Experiment, _ Environment) error {
attackConfig, err := exp.GetRequestCommand()
if err != nil {
return err
}
vmOption, ok := attackConfig.(*core.VMOption)
if !ok {
return errors.New("the type is not VMOption")
}
cmd := exec.Command("bash", "-c", fmt.Sprintf("virsh start %s", vmOption.VMName))
output, err := cmd.CombinedOutput()
if err != nil {
log.Error(string(output), zap.Error(err))
return err
}
return nil
}

View File

@ -1,131 +0,0 @@
// Copyright 2021 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package httpserver
import (
"crypto/tls"
"crypto/x509"
"net/http"
"os"
"time"
"github.com/gin-gonic/gin"
"github.com/pingcap/log"
"go.uber.org/zap"
"github.com/chaos-mesh/chaosd/pkg/server/utils"
)
const (
MTLSServer = "mTLS"
TLSServer = "tls"
HTTPServer = "http"
)
var (
errMissingClientCert = utils.ErrAuth.New("Sorry, but you need to provide a client certificate to continue")
)
func (s *HttpServer) serverMode() string {
if len(s.conf.SSLCertFile) > 0 {
if len(s.conf.SSLClientCAFile) > 0 {
return MTLSServer
}
return TLSServer
}
return HTTPServer
}
func (s *HttpServer) startHttpsServer() (err error) {
mode := s.serverMode()
if mode == HTTPServer {
return nil
}
httpsServerAddr := s.conf.HttpsServerAddress()
e := gin.Default()
e.Use(utils.MWHandleErrors())
if mode == MTLSServer {
log.Info("starting HTTPS server with Client Auth", zap.String("address", httpsServerAddr))
caCert, ioErr := os.ReadFile(s.conf.SSLClientCAFile)
if ioErr != nil {
err = ioErr
return
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
tlsConfig := &tls.Config{
ClientCAs: caCertPool,
ClientAuth: tls.RequestClientCert,
ServerName: s.conf.ServerName,
}
e.Use(authenticateClientCert(tlsConfig))
// According to https://github.com/gin-gonic/gin/issues/531, we need to register middlewares before RouterGroup.
s.handler(e)
server := &http.Server{
Addr: httpsServerAddr,
TLSConfig: tlsConfig,
Handler: e,
}
err = server.ListenAndServeTLS(s.conf.SSLCertFile, s.conf.SSLKeyFile)
} else if mode == TLSServer {
log.Info("starting HTTPS server", zap.String("address", httpsServerAddr))
s.handler(e)
err = e.RunTLS(httpsServerAddr, s.conf.SSLCertFile, s.conf.SSLKeyFile)
}
return
}
func verifyCertificates(config *tls.Config, certs []*x509.Certificate) error {
t := config.Time
if t == nil {
t = time.Now
}
opts := x509.VerifyOptions{
Roots: config.ClientCAs,
CurrentTime: t(),
Intermediates: x509.NewCertPool(),
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
for _, cert := range certs[1:] {
opts.Intermediates.AddCert(cert)
}
_, err := certs[0].Verify(opts)
if err != nil {
return err
}
return nil
}
func authenticateClientCert(config *tls.Config) gin.HandlerFunc {
return func(ctx *gin.Context) {
clientTLS := ctx.Request.TLS
if len(clientTLS.PeerCertificates) > 0 {
if err := verifyCertificates(config, clientTLS.PeerCertificates); err != nil {
_ = ctx.AbortWithError(http.StatusForbidden, utils.ErrAuth.Wrap(err, "Unauthorized certificate credentials"))
} else {
ctx.Next()
}
} else {
_ = ctx.AbortWithError(http.StatusUnauthorized, errMissingClientCert)
}
}
}

View File

@ -22,7 +22,7 @@ import (
"github.com/chaos-mesh/chaosd/pkg/core"
)
func (s *HttpServer) listExperiments(c *gin.Context) {
func (s *httpServer) listExperiments(c *gin.Context) {
mode, ok := c.GetQuery("launch_mode")
var chaosList []*core.Experiment
var err error
@ -32,17 +32,17 @@ func (s *HttpServer) listExperiments(c *gin.Context) {
chaosList, err = s.exp.List(context.Background())
}
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.JSON(http.StatusOK, chaosList)
}
func (s *HttpServer) listExperimentRuns(c *gin.Context) {
func (s *httpServer) listExperimentRuns(c *gin.Context) {
uid := c.Param("uid")
runsList, err := s.chaos.ExpRun.ListByExperimentUID(context.Background(), uid)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.JSON(http.StatusOK, runsList)

View File

@ -20,7 +20,6 @@ import (
"github.com/joomcode/errorx"
"github.com/pingcap/log"
"go.uber.org/zap"
"gorm.io/gorm"
"github.com/chaos-mesh/chaosd/pkg/config"
"github.com/chaos-mesh/chaosd/pkg/core"
@ -30,56 +29,50 @@ import (
"github.com/chaos-mesh/chaosd/pkg/swaggerserver"
)
type HttpServer struct {
conf *config.Config
chaos *chaosd.Server
exp core.ExperimentStore
type httpServer struct {
conf *config.Config
chaos *chaosd.Server
exp core.ExperimentStore
engine *gin.Engine
}
func NewServer(
conf *config.Config,
chaos *chaosd.Server,
exp core.ExperimentStore,
) *HttpServer {
return &HttpServer{
conf: conf,
chaos: chaos,
exp: exp,
) *httpServer {
e := gin.Default()
e.Use(utils.MWHandleErrors())
return &httpServer{
conf: conf,
chaos: chaos,
exp: exp,
engine: e,
}
}
func Register(s *HttpServer, scheduler scheduler.Scheduler) {
func Register(s *httpServer, scheduler scheduler.Scheduler) {
if s.conf.Platform != config.LocalPlatform {
return
}
handler(s)
go func() {
if err := s.startHttpServer(); err != nil {
addr := s.conf.Address()
log.Debug("starting HTTP server", zap.String("address", addr))
if err := s.engine.Run(addr); err != nil {
log.Fatal("failed to start HTTP server", zap.Error(err))
}
}()
go func() {
if err := s.startHttpsServer(); err != nil {
log.Fatal("failed to start HTTPS server", zap.Error(err))
}
}()
scheduler.Start()
}
func (s *HttpServer) startHttpServer() error {
httpServerAddr := s.conf.Address()
log.Info("starting HTTP server", zap.String("address", httpServerAddr))
e := gin.Default()
e.Use(utils.MWHandleErrors())
s.systemHandler(e)
if s.serverMode() == HTTPServer {
s.handler(e)
}
return e.Run(httpServerAddr)
}
func (s *HttpServer) handler(engine *gin.Engine) {
api := engine.Group("/api")
func handler(s *httpServer) {
api := s.engine.Group("/api")
{
api.GET("/swagger/*any", swaggerserver.Handler())
}
@ -90,12 +83,6 @@ func (s *HttpServer) handler(engine *gin.Engine) {
attack.POST("/stress", s.createStressAttack)
attack.POST("/network", s.createNetworkAttack)
attack.POST("/disk", s.createDiskAttack)
attack.POST("/clock", s.createClockAttack)
attack.POST("/jvm", s.createJVMAttack)
attack.POST("/kafka", s.createKafkaAttack)
attack.POST("/vm", s.createVMAttack)
attack.POST("/redis", s.createRedisAttack)
attack.POST("/user_defined", s.createUserDefinedAttack)
attack.DELETE("/:uid", s.recoverAttack)
}
@ -105,10 +92,7 @@ func (s *HttpServer) handler(engine *gin.Engine) {
experiments.GET("/", s.listExperiments)
experiments.GET("/:uid/runs", s.listExperimentRuns)
}
}
func (s *HttpServer) systemHandler(engine *gin.Engine) {
api := engine.Group("/api")
system := api.Group("/system")
{
system.GET("/health", s.healthcheck)
@ -125,17 +109,10 @@ func (s *HttpServer) systemHandler(engine *gin.Engine) {
// @Failure 400 {object} utils.APIError
// @Failure 500 {object} utils.APIError
// @Router /api/attack/process [post]
func (s *HttpServer) createProcessAttack(c *gin.Context) {
func (s *httpServer) createProcessAttack(c *gin.Context) {
attack := core.NewProcessCommand()
if err := c.ShouldBindJSON(attack); err != nil {
_ = c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
return
}
attack.CompleteDefaults()
if err := attack.Validate(); err != nil {
err = core.ErrAttackConfigValidation.Wrap(err, "attack config validation failed")
handleError(c, err)
c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
return
}
@ -157,17 +134,10 @@ func (s *HttpServer) createProcessAttack(c *gin.Context) {
// @Failure 400 {object} utils.APIError
// @Failure 500 {object} utils.APIError
// @Router /api/attack/network [post]
func (s *HttpServer) createNetworkAttack(c *gin.Context) {
func (s *httpServer) createNetworkAttack(c *gin.Context) {
attack := core.NewNetworkCommand()
if err := c.ShouldBindJSON(attack); err != nil {
_ = c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
return
}
attack.CompleteDefaults()
if err := attack.Validate(); err != nil {
err = core.ErrAttackConfigValidation.Wrap(err, "attack config validation failed")
handleError(c, err)
c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
return
}
@ -189,17 +159,10 @@ func (s *HttpServer) createNetworkAttack(c *gin.Context) {
// @Failure 400 {object} utils.APIError
// @Failure 500 {object} utils.APIError
// @Router /api/attack/stress [post]
func (s *HttpServer) createStressAttack(c *gin.Context) {
func (s *httpServer) createStressAttack(c *gin.Context) {
attack := core.NewStressCommand()
if err := c.ShouldBindJSON(attack); err != nil {
_ = c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
return
}
attack.CompleteDefaults()
if err := attack.Validate(); err != nil {
err = core.ErrAttackConfigValidation.Wrap(err, "attack config validation failed")
handleError(c, err)
c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
return
}
@ -221,246 +184,15 @@ func (s *HttpServer) createStressAttack(c *gin.Context) {
// @Failure 400 {object} utils.APIError
// @Failure 500 {object} utils.APIError
// @Router /api/attack/disk [post]
func (s *HttpServer) createDiskAttack(c *gin.Context) {
options := core.NewDiskOptionForServer()
if err := c.ShouldBindJSON(options); err != nil {
_ = c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
return
}
options.CompleteDefaults()
attackConfig, err := options.PreProcess()
if err != nil {
err = core.ErrAttackConfigValidation.Wrap(err, "attack config validation failed")
handleError(c, err)
return
}
uid, err := s.chaos.ExecuteAttack(chaosd.DiskServerAttack, attackConfig, core.ServerMode)
if err != nil {
handleError(c, err)
return
}
c.JSON(http.StatusOK, utils.AttackSuccessResponse(uid))
}
// @Summary Create clock attack.
// @Description Create clock attack.
// @Tags attack
// @Produce json
// @Param request body core.ClockOption true "Request body"
// @Success 200 {object} utils.Response
// @Failure 400 {object} utils.APIError
// @Failure 500 {object} utils.APIError
// @Router /api/attack/clock [post]
func (s *HttpServer) createClockAttack(c *gin.Context) {
options := core.NewClockOption()
if err := c.ShouldBindJSON(options); err != nil {
_ = c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
return
}
options.CompleteDefaults()
err := options.PreProcess()
if err != nil {
err = core.ErrAttackConfigValidation.Wrap(err, "attack config validation failed")
handleError(c, err)
return
}
uid, err := s.chaos.ExecuteAttack(chaosd.ClockAttack, options, core.ServerMode)
if err != nil {
handleError(c, err)
return
}
c.JSON(http.StatusOK, utils.AttackSuccessResponse(uid))
}
// @Summary Create http attack.
// @Description Create http attack.
// @Tags attack
// @Produce json
// @Param request body core.HTTPAttackOption true "Request body"
// @Success 200 {object} utils.Response
// @Failure 400 {object} utils.APIError
// @Failure 500 {object} utils.APIError
// @Router /api/attack/http [post]
func (s *HttpServer) createHTTPAttack(c *gin.Context) {
attack := core.NewHTTPAttackOption()
func (s *httpServer) createDiskAttack(c *gin.Context) {
attack := core.NewDiskOption()
if err := c.ShouldBindJSON(attack); err != nil {
_ = c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
return
}
attackConfig, err := attack.PreProcess()
if err != nil {
err = core.ErrAttackConfigValidation.Wrap(err, "attack config validation failed")
handleError(c, err)
return
}
uid, err := s.chaos.ExecuteAttack(chaosd.DiskAttack, attack, core.ServerMode)
uid, err := s.chaos.ExecuteAttack(chaosd.HostAttack, attackConfig, core.ServerMode)
if err != nil {
handleError(c, err)
return
}
c.JSON(http.StatusOK, utils.AttackSuccessResponse(uid))
}
// @Summary Create JVM attack.
// @Description Create JVM attack.
// @Tags attack
// @Produce json
// @Param request body core.JVMCommand true "Request body"
// @Success 200 {object} utils.Response
// @Failure 400 {object} utils.APIError
// @Failure 500 {object} utils.APIError
// @Router /api/attack/jvm [post]
func (s *HttpServer) createJVMAttack(c *gin.Context) {
options := core.NewJVMCommand()
if err := c.ShouldBindJSON(options); err != nil {
_ = c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
return
}
options.CompleteDefaults()
if err := options.Validate(); err != nil {
err = core.ErrAttackConfigValidation.Wrap(err, "attack config validation failed")
handleError(c, err)
return
}
uid, err := s.chaos.ExecuteAttack(chaosd.JVMAttack, options, core.ServerMode)
if err != nil {
handleError(c, err)
return
}
c.JSON(http.StatusOK, utils.AttackSuccessResponse(uid))
}
// @Summary Create Kafka attack.
// @Description Create Kafka attack.
// @Tags attack
// @Produce json
// @Param request body core.KafkaCommand true "Request body"
// @Success 200 {object} utils.Response
// @Failure 400 {object} utils.APIError
// @Failure 500 {object} utils.APIError
// @Router /api/attack/kafka [post]
func (s *HttpServer) createKafkaAttack(c *gin.Context) {
options := core.NewKafkaCommand()
if err := c.ShouldBindJSON(options); err != nil {
_ = c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
return
}
options.CompleteDefaults()
if err := options.Validate(); err != nil {
err = core.ErrAttackConfigValidation.Wrap(err, "attack config validation failed")
handleError(c, err)
return
}
uid, err := s.chaos.ExecuteAttack(chaosd.KafkaAttack, options, core.ServerMode)
if err != nil {
handleError(c, err)
return
}
c.JSON(http.StatusOK, utils.AttackSuccessResponse(uid))
}
// @Summary Create VM attack.
// @Description Create VM attack.
// @Tags attack
// @Produce json
// @Param request body core.VMOption true "Request body"
// @Success 200 {object} utils.Response
// @Failure 400 {object} utils.APIError
// @Failure 500 {object} utils.APIError
// @Router /api/attack/vm [post]
func (s *HttpServer) createVMAttack(c *gin.Context) {
options := core.NewVMOption()
if err := c.ShouldBindJSON(options); err != nil {
_ = c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
return
}
options.CompleteDefaults()
if err := options.Validate(); err != nil {
err = core.ErrAttackConfigValidation.Wrap(err, "attack config validation failed")
handleError(c, err)
return
}
uid, err := s.chaos.ExecuteAttack(chaosd.VMAttack, options, core.ServerMode)
if err != nil {
handleError(c, err)
return
}
c.JSON(http.StatusOK, utils.AttackSuccessResponse(uid))
}
// @Summary Create redis attack.
// @Description Create redis attack.
// @Tags attack
// @Produce json
// @Param request body core.RedisCommand true "Request body"
// @Success 200 {object} utils.Response
// @Failure 400 {object} utils.APIError
// @Failure 500 {object} utils.APIError
// @Router /api/attack/redis [post]
func (s *HttpServer) createRedisAttack(c *gin.Context) {
attack := core.NewRedisCommand()
if err := c.ShouldBindJSON(attack); err != nil {
_ = c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
return
}
attack.CompleteDefaults()
if err := attack.Validate(); err != nil {
err = core.ErrAttackConfigValidation.Wrap(err, "attack config validation failed")
handleError(c, err)
return
}
uid, err := s.chaos.ExecuteAttack(chaosd.RedisAttack, attack, core.ServerMode)
if err != nil {
handleError(c, err)
return
}
c.JSON(http.StatusOK, utils.AttackSuccessResponse(uid))
}
// @Summary Create user defined attack.
// @Description Create user defined attack.
// @Tags attack
// @Produce json
// @Param request body core.RedisCommand true "Request body"
// @Success 200 {object} utils.Response
// @Failure 400 {object} utils.APIError
// @Failure 500 {object} utils.APIError
// @Router /api/attack/user_defined [post]
func (s *HttpServer) createUserDefinedAttack(c *gin.Context) {
attack := core.NewUserDefinedOption()
if err := c.ShouldBindJSON(attack); err != nil {
_ = c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
return
}
attack.CompleteDefaults()
if err := attack.Validate(); err != nil {
err = core.ErrAttackConfigValidation.Wrap(err, "attack config validation failed")
handleError(c, err)
return
}
uid, err := s.chaos.ExecuteAttack(chaosd.UserDefinedAttack, attack, core.ServerMode)
if err != nil {
handleError(c, err)
return
@ -477,7 +209,7 @@ func (s *HttpServer) createUserDefinedAttack(c *gin.Context) {
// @Success 200 {object} utils.Response
// @Failure 500 {object} utils.APIError
// @Router /api/attack/{uid} [delete]
func (s *HttpServer) recoverAttack(c *gin.Context) {
func (s *httpServer) recoverAttack(c *gin.Context) {
uid := c.Param("uid")
err := s.chaos.RecoverAttack(uid)
if err != nil {
@ -489,10 +221,6 @@ func (s *HttpServer) recoverAttack(c *gin.Context) {
}
func handleError(c *gin.Context, err error) {
if err == gorm.ErrRecordNotFound {
_ = c.AbortWithError(http.StatusNotFound, utils.ErrNotFound.WrapWithNoMessage(err))
return
}
if errorx.IsOfType(err, core.ErrAttackConfigValidation) {
_ = c.AbortWithError(http.StatusBadRequest, utils.ErrInvalidRequest.WrapWithNoMessage(err))
} else {

View File

@ -26,10 +26,10 @@ type healthInfo struct {
Message string `json:"message"`
}
func (s *HttpServer) healthcheck(c *gin.Context) {
func (s *httpServer) healthcheck(c *gin.Context) {
c.JSON(http.StatusOK, healthInfo{Status: 0})
}
func (s *HttpServer) version(c *gin.Context) {
func (s *httpServer) version(c *gin.Context) {
c.JSON(http.StatusOK, version.Get())
}

View File

@ -16,11 +16,6 @@ package server
import (
"os"
"github.com/go-logr/logr"
"github.com/go-logr/zapr"
"github.com/prometheus/client_golang/prometheus"
"go.uber.org/zap"
"go.uber.org/fx"
"github.com/chaos-mesh/chaos-mesh/pkg/chaosdaemon"
@ -33,7 +28,6 @@ import (
var Module = fx.Options(
fx.Provide(
provideNIl,
chaosd.NewServer,
httpserver.NewServer,
crclient.NewNodeCRClient,
@ -42,12 +36,3 @@ var Module = fx.Options(
scheduler.NewScheduler,
),
)
func provideNIl() (prometheus.Registerer, logr.Logger) {
zapLogger, err := zap.NewDevelopment()
if err != nil {
panic(err)
}
logger := zapr.NewLogger(zapLogger)
return nil, logger
}

View File

@ -23,7 +23,6 @@ import (
var (
ErrNS = errorx.NewNamespace("error.api")
ErrAuth = ErrNS.NewType("auth")
ErrOther = ErrNS.NewType("other")
ErrInvalidRequest = ErrNS.NewType("invalid_request")
ErrInternalServer = ErrNS.NewType("internal_server_error")

View File

@ -1,47 +0,0 @@
// Copyright 2021 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
"os/exec"
"time"
"github.com/pingcap/log"
"go.uber.org/zap"
)
func ExecWithDeadline(t <-chan time.Time, cmd *exec.Cmd) error {
done := make(chan error, 1)
var output []byte
var err error
go func() {
output, err = cmd.CombinedOutput()
done <- err
}()
select {
case <-t:
if err := cmd.Process.Kill(); err != nil {
log.Error("failed to kill process: ", zap.Error(err))
return err
}
case err := <-done:
if err != nil {
log.Error(err.Error()+string(output), zap.Error(err))
return err
}
log.Info(string(output))
}
return nil
}

View File

@ -17,9 +17,10 @@ import (
"context"
"errors"
perr "github.com/pkg/errors"
"gorm.io/gorm"
perr "github.com/pkg/errors"
"github.com/chaos-mesh/chaosd/pkg/core"
"github.com/chaos-mesh/chaosd/pkg/store/dbstore"
)

View File

@ -11,7 +11,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !swagger_server
// +build !swagger_server
package swaggerserver

View File

@ -11,7 +11,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build swagger_server
// +build swagger_server
package swaggerserver

View File

@ -1,54 +0,0 @@
// Copyright 2021 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
"context"
"fmt"
"os/exec"
"reflect"
)
type Command struct {
Name string
}
func (c Command) Unmarshal(val interface{}) *exec.Cmd {
name, args := c.GetCmdArgs(val)
return exec.Command(name, args...)
}
func (c Command) UnmarshalWithCtx(ctx context.Context, val interface{}) *exec.Cmd {
name, args := c.GetCmdArgs(val)
return exec.CommandContext(ctx, name, args...)
}
func (c Command) GetCmdArgs(val interface{}) (string, []string) {
v := reflect.ValueOf(val)
var options []string
for i := 0; i < v.NumField(); i++ {
tag := v.Type().Field(i).Tag.Get(c.Name)
if v.Field(i).String() == "" || tag == "" {
continue
}
if tag == "-" {
options = append(options, v.Field(i).String())
} else {
options = append(options, fmt.Sprintf("%s=%v", tag, v.Field(i).String()))
}
}
return c.Name, options
}

View File

@ -1,47 +0,0 @@
// Copyright 2023 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
"fmt"
"testing"
)
func TestCommand_Unmarshal(t *testing.T) {
type dd struct {
If string `dd:"if"`
Of string `dd:"oflag"`
Iflag string `dd:"iflag"`
}
dc := Command{Name: "dd"}
tests := []struct {
name string
d dd
}{
{
name: "0",
d: dd{
"/dev/zero",
"i,2,3",
"",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := dc.Unmarshal(tt.d)
fmt.Println(cmd.String())
})
}
}

View File

@ -31,15 +31,12 @@ func SetRuntimeEnv() error {
return err
}
bytemanHome := os.Getenv("BYTEMAN_HOME")
if len(bytemanHome) == 0 {
err = os.Setenv("BYTEMAN_HOME", fmt.Sprintf("%s/tools/byteman", wd))
if err != nil {
return err
}
}
path := os.Getenv("PATH")
bytemanHome := fmt.Sprintf("%s/tools/byteman", wd)
err = os.Setenv("BYTEMAN_HOME", bytemanHome)
if err != nil {
return err
}
err = os.Setenv("PATH", fmt.Sprintf("%s/tools:%s/bin:%s", wd, bytemanHome, path))
if err != nil {
return err

View File

@ -1,117 +0,0 @@
// Copyright 2023 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
"context"
"fmt"
"os/exec"
"sync"
"time"
"github.com/Jeffail/tunny"
"github.com/samber/lo"
"github.com/samber/mo"
)
// CommandPools is a group of commands runner
type CommandPools struct {
cancel context.CancelFunc
pools *tunny.Pool
wg sync.WaitGroup
}
// NewCommandPools returns a new CommandPools
func NewCommandPools(ctx context.Context, deadline *time.Time, size int) *CommandPools {
var ctx2 context.Context
var cancel context.CancelFunc
if deadline != nil {
ctx2, cancel = context.WithDeadline(ctx, *deadline)
} else {
ctx2, cancel = context.WithCancel(ctx)
}
return &CommandPools{
cancel: cancel,
pools: tunny.NewFunc(size, func(payload interface{}) interface{} {
cmdPayload, ok := payload.(lo.Tuple2[string, []string])
if !ok {
return mo.Err[[]byte](fmt.Errorf("payload is not CommandPayload"))
}
name, args := cmdPayload.Unpack()
cmd := exec.CommandContext(ctx2, name, args...)
output, err := cmd.CombinedOutput()
if err != nil {
return mo.Err[[]byte](fmt.Errorf("%s: %s", err, string(output)))
}
return mo.Ok[[]byte](output)
}),
}
}
type CommandRunner struct {
Name string
Args []string
outputHandler func([]byte, error, chan interface{})
outputChanel chan interface{}
}
func NewCommandRunner(name string, args []string) *CommandRunner {
return &CommandRunner{
Name: name,
Args: args,
outputHandler: func(bytes []byte, err error, c chan interface{}) {},
outputChanel: nil,
}
}
func (r *CommandRunner) WithOutputHandler(
handler func([]byte, error, chan interface{}),
outputChanel chan interface{},
) *CommandRunner {
r.outputHandler = handler
r.outputChanel = outputChanel
return r
}
func (p *CommandPools) Process(name string, args []string) ([]byte, error) {
result, ok := p.pools.Process(lo.Tuple2[string, []string]{
A: name,
B: args,
}).(mo.Result[[]byte])
if !ok {
return nil, fmt.Errorf("payload is not Result[[]byte]")
}
return result.Get()
}
// Start command async.
func (p *CommandPools) Start(runner *CommandRunner) {
p.wg.Add(1)
go func() {
output, err := p.Process(runner.Name, runner.Args)
runner.outputHandler(output, err, runner.outputChanel)
p.wg.Done()
}()
}
func (p *CommandPools) Wait() {
p.wg.Wait()
}
func (p *CommandPools) Close() {
p.cancel()
p.Wait()
p.pools.Close()
}

View File

@ -1,81 +0,0 @@
// Copyright 2023 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package utils
import (
"context"
"math"
"testing"
"time"
"github.com/pingcap/log"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
)
func TestCommandPools_Cancel(t *testing.T) {
now := time.Now()
cmdPools := NewCommandPools(context.Background(), nil, 1)
var gErr []error
runner := NewCommandRunner("sleep", []string{"10s"}).
WithOutputHandler(func(output []byte, err error, _ chan interface{}) {
if err != nil {
log.Error(string(output), zap.Error(err))
gErr = append(gErr, err)
}
log.Info(string(output))
}, nil)
cmdPools.Start(runner)
cmdPools.Close()
assert.Less(t, time.Since(now).Seconds(), 10.0)
assert.Equal(t, 1, len(gErr))
}
func TestCommandPools_Deadline(t *testing.T) {
now := time.Now()
deadline := time.Now().Add(time.Millisecond * 50)
cmdPools := NewCommandPools(context.Background(), &deadline, 1)
var gErr []error
runner := NewCommandRunner("sleep", []string{"10s"}).
WithOutputHandler(func(output []byte, err error, _ chan interface{}) {
if err != nil {
log.Error(string(output), zap.Error(err))
gErr = append(gErr, err)
}
log.Info(string(output))
}, nil)
cmdPools.Start(runner)
cmdPools.Wait()
assert.Less(t, math.Abs(float64(time.Since(now).Milliseconds()-50)), 10.0)
assert.Equal(t, 1, len(gErr))
}
func TestCommandPools_Normal(t *testing.T) {
now := time.Now()
cmdPools := NewCommandPools(context.Background(), nil, 1)
var gErr []error
runner := NewCommandRunner("sleep", []string{"1s"}).
WithOutputHandler(func(output []byte, err error, _ chan interface{}) {
if err != nil {
log.Error(string(output), zap.Error(err))
gErr = append(gErr, err)
}
log.Info(string(output))
}, nil)
cmdPools.Start(runner)
cmdPools.Wait()
assert.Less(t, time.Since(now).Seconds(), 2.0)
assert.Equal(t, 0, len(gErr))
}

View File

@ -15,6 +15,7 @@ package utils
import (
"io/ioutil"
"os"
"github.com/pingcap/errors"
"github.com/pingcap/log"
@ -22,7 +23,12 @@ import (
)
// CreateTempFile will create a temp file in current directory.
func CreateTempFile(path string) (string, error) {
func CreateTempFile() (string, error) {
path, err := os.Getwd()
if err != nil {
log.Error("unexpected err when execute os.Getwd()", zap.Error(err))
return "", err
}
tempFile, err := ioutil.TempFile(path, "example")
if err != nil {
log.Error("unexpected err when open temp file", zap.Error(err))

View File

@ -80,6 +80,10 @@ func TestSplitByteSize(t *testing.T) {
BlockSize: "1M",
Count: "0",
},
{
BlockSize: "",
Count: "",
},
},
wantErr: false,
},
@ -105,8 +109,8 @@ func TestSplitByteSize(t *testing.T) {
BlockSize: "524288c",
Count: "1",
}, {
BlockSize: "1M",
Count: "0",
BlockSize: "1",
Count: "0c",
}},
wantErr: false,
}, {
@ -122,8 +126,8 @@ func TestSplitByteSize(t *testing.T) {
BlockSize: "524288c",
Count: "1",
}, {
BlockSize: "1c",
Count: "1",
BlockSize: "1",
Count: "1c",
}},
wantErr: false,
}, {
@ -139,8 +143,8 @@ func TestSplitByteSize(t *testing.T) {
BlockSize: "1M",
Count: "2",
}, {
BlockSize: "1048576c",
Count: "1",
BlockSize: "1",
Count: "1048576c",
}},
wantErr: false,
}, {
@ -156,8 +160,8 @@ func TestSplitByteSize(t *testing.T) {
BlockSize: "1M",
Count: "2",
}, {
BlockSize: "1048577c",
Count: "1",
BlockSize: "1",
Count: "1048577c",
}},
wantErr: false,
},

View File

@ -15,11 +15,7 @@ package utils
import (
"math/rand"
"os/exec"
"time"
"github.com/pingcap/log"
"go.uber.org/zap"
)
const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
@ -32,18 +28,3 @@ func RandomStringWithCharset(length int) string {
}
return string(b)
}
func ExecuteCmd(cmdStr string) (string, error) {
log.Info("execute cmd", zap.String("cmd", cmdStr))
cmd := exec.Command("bash", "-c", cmdStr)
output, err := cmd.CombinedOutput()
if err != nil {
log.Error(string(output), zap.Error(err))
return "", err
}
if len(output) > 0 {
log.Info("command output: "+string(output), zap.String("command", cmdStr))
}
return string(output), nil
}

View File

@ -1,132 +0,0 @@
#!/usr/bin/env bash
# Copyright 2022 Chaos Mesh Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# See the License for the specific language governing permissions and
# limitations under the License.
set -eu
cur=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
cd $cur
bin_path=../../../bin
chaos_test_file="/tmp/chaos-test"
echo "create file"
${bin_path}/chaosd attack file create --file-name ${chaos_test_file} --uid 12345
if [[ ! (-e ${chaos_test_file}) ]]; then
echo "${chaos_test_file} not exists"
exit 1
fi
${bin_path}/chaosd recover 12345
if [[ (-e ${chaos_test_file}) ]]; then
echo "${chaos_test_file} exists after recover"
exit 1
fi
echo "create directory"
${bin_path}/chaosd attack file create --dir-name ${chaos_test_file} --uid 12345
if [[ ! (-e ${chaos_test_file}) ]]; then
echo "${chaos_test_file} not exists"
exit 1
fi
${bin_path}/chaosd recover 12345
if [[ (-e ${chaos_test_file}) ]]; then
echo "${chaos_test_file} exists after recover"
exit 1
fi
echo "delete file"
touch ${chaos_test_file}
${bin_path}/chaosd attack file delete --dir-name ${chaos_test_file} --uid 12345
if [[ (-e ${chaos_test_file}) ]]; then
echo "${chaos_test_file} exists after delete"
exit 1
fi
${bin_path}/chaosd recover 12345
if [[ ! (-e ${chaos_test_file}) ]]; then
echo "${chaos_test_file} not exists after recover"
exit 1
fi
echo "append file"
touch ${chaos_test_file}
${bin_path}/chaosd attack file append --file-name ${chaos_test_file} --data "chaos-mesh" --count 5 --uid 12345
num=`cat ${chaos_test_file} | grep "chaos-mesh" | wc -l`
if [[ ${num} -ne 5 ]]; then
echo "append file failed"
exit 1
fi
${bin_path}/chaosd recover 12345
num=`cat ${chaos_test_file} | grep "chaos-mesh" | wc -l`
if [[ ${num} -ne 0 ]]; then
echo "recover append file failed"
exit 1
fi
echo "modify file"
${bin_path}/chaosd attack file modify --file-name ${chaos_test_file} --privilege 777 --uid 12345
privilege=`stat -c %a ${chaos_test_file}`
if [[ ${privilege} -ne 777 ]]; then
echo "modify file failed"
exit 1
fi
${bin_path}/chaosd recover 12345
privilege=`stat -c %a ${chaos_test_file}`
if [[ ${privilege} == 777 ]]; then
echo "recover modify file failed"
exit 1
fi
echo "rename file"
${bin_path}/chaosd attack file rename --source-file ${chaos_test_file} --dest-file ${chaos_test_file}-bak --uid 12345
if [[ (-e ${chaos_test_file}) ]]; then
echo "${chaos_test_file} exists after rename"
exit 1
fi
if [[ ! (-e ${chaos_test_file}-bak) ]]; then
echo "${chaos_test_file}-bak not exists after rename"
exit 1
fi
${bin_path}/chaosd recover 12345
if [[ ! (-e ${chaos_test_file}) ]]; then
echo "${chaos_test_file} not exists after recover"
exit 1
fi
if [[ (-e ${chaos_test_file}-bak) ]]; then
echo "${chaos_test_file}-bak exists after recover"
exit 1
fi
rm ${chaos_test_file}
exit 0

View File

@ -13,28 +13,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
set -eu
set -u
cur=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
cd $cur
bin_path=../../../bin
echo "download byteman example"
if [[ ! (-e byteman-example) ]]; then
git clone https://github.com/chaos-mesh/byteman-example.git
fi
echo "download byteman && set environment variable"
byteman_dir="byteman-chaos-mesh-download-v4.0.20-0.12"
if [[ ! (-e ${byteman_dir}.tar.gz) ]]; then
curl -fsSL -o ${byteman_dir}.tar.gz https://mirrors.chaos-mesh.org/${byteman_dir}.tar.gz
tar zxvf ${byteman_dir}.tar.gz
fi
export BYTEMAN_HOME=$cur/${byteman_dir}
export PATH=$PATH:${BYTEMAN_HOME}/bin
echo "build && run Java example program helloworld"
echo "download && build && run Java example program"
git clone https://github.com/WangXiangUSTC/byteman-example.git
cd byteman-example/example.helloworld
javac HelloWorld/Main.java
jar cfme HelloWorld.jar Manifest.txt HelloWorld.Main HelloWorld/Main.class
@ -46,68 +33,22 @@ cat helloworld.log
# TODO: get the PID more accurately
pid=`pgrep -n java`
echo "run chaosd to inject failure into JVM, and check"
echo "download byteman && set environment variable"
curl -fsSL -o chaosd-byteman-download.tar.gz https://mirrors.chaos-mesh.org/jvm/chaosd-byteman-download.tar.gz
tar zxvf chaosd-byteman-download.tar.gz
export BYTEMAN_HOME=$cur/chaosd-byteman-download
export PATH=$PATH:${BYTEMAN_HOME}/bin
$bin_path/chaosd attack jvm return --class Main --method getnum --port 9288 --value 99999 --pid $pid
sleep 1
echo "run chaosd to inject failure into JVM, and check"
$bin_path/chaosd attack jvm install --port 9288 --pid $pid
$bin_path/chaosd attack jvm submit return --class Main --method getnum --port 9288 --value 99999
check_contains "99999" helloworld.log
$bin_path/chaosd attack jvm exception --class Main --method sayhello --port 9288 --exception 'java.io.IOException("BOOM")' --pid $pid
sleep 1
$bin_path/chaosd attack jvm submit exception --class Main --method sayhello --port 9288 --exception 'java.io.IOException("BOOM")'
check_contains "BOOM" helloworld.log
kill $pid
# TODO: add test for latency, stress and gc
echo "download && run tidb"
case $( uname -m ) in
aarch64) ARCH=arm64;;
arm64) ARCH=arm64;;
*) ARCH=amd64;;
esac
tidb_dir="tidb-v5.3.0-linux-$ARCH"
if [[ ! (-e ${tidb_dir}.tar.gz) ]]; then
curl -fsSL -o ${tidb_dir}.tar.gz https://download.pingcap.org/${tidb_dir}.tar.gz
tar zxvf ${tidb_dir}.tar.gz
fi
${tidb_dir}/bin/tidb-server -store mocktikv -P 4111 > tidb.log 2>&1 &
sleep 5
tidb_pid=`pgrep -n tidb-server`
echo "build && run Java example program mysqldemo"
cd byteman-example/mysqldemo
mvn -X package -Dmaven.test.skip=true -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true
export MYSQL_DSN=jdbc:"mysql://127.0.0.1:4111/test"
export MYSQL_USER=root
export MYSQL_CONNECTOR_VERSION=8
mvn exec:java -Dexec.mainClass="com.mysqldemo.App" > mysqldemo.log 2>&1 &
# make sure it works
for (( i=0; i<=20; i++ ))
do
tail_log=`tail -1 mysqldemo.log`
if [ "$tail_log" == "Server start!" ]; then
break
fi
sleep 5
done
cd -
# TODO: get the PID more accurately
pid=`pgrep -n java`
echo "send request to mysqldemo, and can get result success"
curl -X GET "http://127.0.0.1:8001/query?sql=SELECT%20*%20FROM%20mysql.user" > user_info.log
check_contains "root" user_info.log
$bin_path/chaosd attack jvm mysql --database mysql --table user --port 9299 --exception "BOOM" --pid $pid
sleep 1
echo "send request to mysqldemo, and will get a BOOM exception"
curl -X GET "http://127.0.0.1:8001/query?sql=SELECT%20*%20FROM%20mysql.user" > user_info.log
check_contains "BOOM" user_info.log
echo "clean"
kill $pid
kill $tidb_pid

View File

@ -1,64 +0,0 @@
#!/usr/bin/env bash
# Copyright 2021 Chaos Mesh Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# See the License for the specific language governing permissions and
# limitations under the License.
# Generate script because certs expire in 1 year (365 days)
mkdir -p client server
# generate server certificate
openssl req \
-x509 \
-newkey rsa:4096 \
-keyout server/server_key.pem \
-out server/server_cert.pem \
-nodes \
-days 365 \
-subj "/CN=localhost/O=Client\ Certificate\ Demo"
# generate server-signed (valid) certificate
openssl req \
-newkey rsa:4096 \
-keyout client/valid_key.pem \
-out client/valid_csr.pem \
-nodes \
-days 365 \
-subj "/CN=Valid"
# sign with server_cert.pem
openssl x509 \
-req \
-in client/valid_csr.pem \
-CA server/server_cert.pem \
-CAkey server/server_key.pem \
-out client/valid_cert.pem \
-set_serial 01 \
-days 365
# generate self-signed (invalid) certificate
openssl req \
-newkey rsa:4096 \
-keyout client/invalid_key.pem \
-out client/invalid_csr.pem \
-nodes \
-days 365 \
-subj "/CN=Invalid"
# sign with invalid_csr.pem
openssl x509 \
-req \
-in client/invalid_csr.pem \
-signkey client/invalid_key.pem \
-out client/invalid_cert.pem \
-days 365

Some files were not shown because too many files have changed in this diff Show More