mirror of https://github.com/chaos-mesh/chaosd.git
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:
parent
e4cabb4419
commit
b003e0ad4a
|
|
@ -40,6 +40,7 @@ func NewAttackCommand() *cobra.Command {
|
|||
NewRedisAttackCommand(&uid),
|
||||
NewFileAttackCommand(&uid),
|
||||
NewVMAttackCommand(&uid),
|
||||
NewUserDefinedCommand(&uid),
|
||||
)
|
||||
|
||||
return cmd
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Loading…
Reference in New Issue