support user defined attack (#170)

* support user-defined attack

Signed-off-by: xiang <xiang13225080@163.com>

* add integration test

Signed-off-by: xiang <xiang13225080@163.com>

* support http server

Signed-off-by: xiang <xiang13225080@163.com>

* format

Signed-off-by: xiang <xiang13225080@163.com>

* minor refine

Signed-off-by: xiang <xiang13225080@163.com>

* address comment

Signed-off-by: xiang <xiang13225080@163.com>

Co-authored-by: Ti Chi Robot <ti-community-prow-bot@tidb.io>
This commit is contained in:
WangXiang 2022-06-20 17:06:37 +08:00 committed by GitHub
parent e4cabb4419
commit b003e0ad4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 274 additions and 11 deletions

View File

@ -40,6 +40,7 @@ func NewAttackCommand() *cobra.Command {
NewRedisAttackCommand(&uid),
NewFileAttackCommand(&uid),
NewVMAttackCommand(&uid),
NewUserDefinedCommand(&uid),
)
return cmd

View File

@ -0,0 +1,64 @@
// 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

@ -31,17 +31,18 @@ const (
)
const (
ProcessAttack = "process"
NetworkAttack = "network"
StressAttack = "stress"
DiskAttack = "disk"
ClockAttack = "clock"
HostAttack = "host"
JVMAttack = "jvm"
KafkaAttack = "kafka"
RedisAttack = "redis"
FileAttack = "file"
VMAttack = "vm"
ProcessAttack = "process"
NetworkAttack = "network"
StressAttack = "stress"
DiskAttack = "disk"
ClockAttack = "clock"
HostAttack = "host"
JVMAttack = "jvm"
KafkaAttack = "kafka"
RedisAttack = "redis"
FileAttack = "file"
VMAttack = "vm"
UserDefinedAttack = "userDefined"
)
const (
@ -119,6 +120,8 @@ func GetAttackByKind(kind string) *AttackConfig {
attackConfig = &FileCommand{}
case VMAttack:
attackConfig = &VMOption{}
case UserDefinedAttack:
attackConfig = &UserDefinedOption{}
default:
return nil
}

58
pkg/core/user_defined.go Normal file
View File

@ -0,0 +1,58 @@
// 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

@ -73,6 +73,8 @@ func (s *Server) RecoverAttack(uid string) error {
attackType = FileAttack
case core.VMAttack:
attackType = VMAttack
case core.UserDefinedAttack:
attackType = UserDefinedAttack
default:
return perr.Errorf("chaos experiment kind %s not found", exp.Kind)
}

View File

@ -0,0 +1,64 @@
// 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

@ -95,6 +95,7 @@ func (s *httpServer) handler(engine *gin.Engine) {
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)
}
@ -404,6 +405,38 @@ func (s *httpServer) createRedisAttack(c *gin.Context) {
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
}
c.JSON(http.StatusOK, utils.AttackSuccessResponse(uid))
}
// @Summary Create recover attack.
// @Description Create recover attack.
// @Tags attack

View File

@ -0,0 +1,38 @@
#!/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 -u
cur=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
cd $cur
bin_path=../../../bin
# test user defined attack
${bin_path}/chaosd attack user-defined --attack-cmd "touch /tmp/chaos" --recover-cmd "rm /tmp/chaos" --uid 1234567890
if [ ! -e /tmp/chaos ]; then
echo "file not exist"
exit 1
fi
${bin_path}/chaosd recover 1234567890
if [ -e /tmp/chaos ]; then
echo "file still exist"
exit 1
fi
exit 0