mirror of https://github.com/chaos-mesh/chaosd.git
factor disk attack&add schedule support for disk read&write (#77)
* fix bugs in SplitBytesByProcessNum & add overwrite control Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> * delete overwrite control Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> * add recover for disk&&delete fill destory Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> * patch Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> * factor disk attack Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> * add schedule support for disk read&write Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> * remove some test & add new unit test & fix some bug Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> * fix some mistakes in log Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> * fix unsupportted unit `c` in fallocate. Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> * roll back scheduler Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> * add comment Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> * save some indentation Signed-off-by: Andrewmatilde <davis6813585853062@outlook.com> * add a comment Signed-off-by: andrewmatilde <davis6813585853062@outlook.com>
This commit is contained in:
parent
c2fddc2942
commit
352ba5a47a
|
|
@ -13,7 +13,11 @@
|
|||
|
||||
package attack
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/chaos-mesh/chaosd/pkg/core"
|
||||
)
|
||||
|
||||
func NewAttackCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -35,3 +39,8 @@ func NewAttackCommand() *cobra.Command {
|
|||
|
||||
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".`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ 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
|
||||
}
|
||||
|
||||
|
|
@ -103,6 +104,7 @@ 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
|
||||
}
|
||||
|
||||
|
|
@ -126,15 +128,17 @@ func NewDiskFillCommand(dep fx.Option, options *core.DiskOption) *cobra.Command
|
|||
"If path not provided, a temp file will be generated and deleted immediately after data filled in or allocated")
|
||||
cmd.Flags().StringVarP(&options.Percent, "percent", "c", "",
|
||||
"'percent' how many percent data of disk will fill in the file path")
|
||||
cmd.Flags().BoolVarP(&options.FillByFallocate, "fallocate", "f", true, "fill disk by fallocate instead of dd")
|
||||
cmd.Flags().BoolVarP(&options.FillByFAllocate, "fallocate", "f", true, "fill disk by fallocate instead of dd")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func processDiskAttack(options *core.DiskOption, chaos *chaosd.Server) {
|
||||
if err := options.Validate(); err != nil {
|
||||
attackConfig, err := options.PreProcess()
|
||||
if err != nil {
|
||||
utils.ExitWithError(utils.ExitBadArgs, err)
|
||||
}
|
||||
uid, err := chaos.ExecuteAttack(chaosd.DiskAttack, options, core.CommandMode)
|
||||
|
||||
uid, err := chaos.ExecuteAttack(chaosd.DiskAttack, attackConfig, core.CommandMode)
|
||||
if err != nil {
|
||||
utils.ExitWithError(utils.ExitError, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,323 +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 (
|
||||
"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,
|
||||
}))
|
||||
}
|
||||
289
pkg/core/disk.go
289
pkg/core/disk.go
|
|
@ -18,7 +18,12 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pingcap/log"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/chaos-mesh/chaosd/pkg/utils"
|
||||
)
|
||||
|
|
@ -29,6 +34,41 @@ 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
|
||||
|
||||
|
|
@ -37,71 +77,7 @@ type DiskOption struct {
|
|||
Percent string `json:"percent"`
|
||||
PayloadProcessNum uint8 `json:"payload_process_num"`
|
||||
|
||||
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)
|
||||
FillByFAllocate bool `json:"fill_by_fallocate"`
|
||||
}
|
||||
|
||||
func NewDiskOption() *DiskOption {
|
||||
|
|
@ -111,3 +87,186 @@ func NewDiskOption() *DiskOption {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// Check if the path is valid.
|
||||
opt.Path, err = utils.CreateTempFile()
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("unexpected err when CreateTempFile in action: %s", opt.Action))
|
||||
return "", err
|
||||
}
|
||||
if err := os.Remove(opt.Path); err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
_, 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 := ioutil.WriteFile(opt.Path, b, 0600); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := os.Remove(opt.Path); err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
return "", fmt.Errorf("fill into an existing file")
|
||||
}
|
||||
}
|
||||
return opt.Path, nil
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
// 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 (
|
||||
"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)
|
||||
|
||||
opt = DiskOption{
|
||||
CommonAttackConfig: CommonAttackConfig{
|
||||
Action: DiskReadPayloadAction,
|
||||
},
|
||||
Path: "/",
|
||||
}
|
||||
_, err = initPath(&opt)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
|
@ -101,7 +101,7 @@ func GetAttackByKind(kind string) *AttackConfig {
|
|||
case StressAttack:
|
||||
attackConfig = &StressCommand{}
|
||||
case DiskAttack:
|
||||
attackConfig = &DiskOption{}
|
||||
attackConfig = &DiskAttackConfig{}
|
||||
case JVMAttack:
|
||||
attackConfig = &JVMCommand{}
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -46,11 +46,6 @@ 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) {
|
||||
if err = options.Validate(); err != nil {
|
||||
err = core.ErrAttackConfigValidation.Wrap(err, "attack config validation failed")
|
||||
return
|
||||
}
|
||||
|
||||
uid = options.GetUID()
|
||||
if len(uid) == 0 {
|
||||
uid = uuid.New().String()
|
||||
|
|
|
|||
|
|
@ -17,196 +17,43 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/pingcap/errors"
|
||||
"github.com/pingcap/log"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/chaos-mesh/chaosd/pkg/server/utils"
|
||||
|
||||
"github.com/chaos-mesh/chaosd/pkg/core"
|
||||
"github.com/chaos-mesh/chaosd/pkg/utils"
|
||||
)
|
||||
|
||||
type diskAttack struct{}
|
||||
|
||||
var DiskAttack AttackType = diskAttack{}
|
||||
|
||||
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)
|
||||
func (disk diskAttack) Attack(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")
|
||||
}
|
||||
return disk.diskPayload(attack)
|
||||
}
|
||||
|
||||
func initWritePayloadPath(payload *core.DiskOption) error {
|
||||
var err error
|
||||
payload.Path, err = utils.CreateTempFile()
|
||||
if err != nil {
|
||||
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))
|
||||
|
||||
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()
|
||||
if attackConf.Action == core.DiskFillAction {
|
||||
if attackConf.FAllocateOption != nil {
|
||||
cmd := core.FAllocateCommand.Unmarshal(*attackConf.FAllocateOption)
|
||||
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.Error(string(output), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
log.Info(string(output))
|
||||
}(cmd)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if errs != nil {
|
||||
return errs
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
// 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.Error(fmt.Sprintf("unexpected err when CreateTempFile in action: %s", fill.Action))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
return nil
|
||||
}
|
||||
|
||||
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))
|
||||
for _, DdOption := range *attackConf.DdOptions {
|
||||
cmd := core.DdCommand.Unmarshal(DdOption)
|
||||
output, err := cmd.CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -215,22 +62,69 @@ func (diskAttack) diskFill(fill *core.DiskOption) error {
|
|||
}
|
||||
log.Info(string(output))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if attackConf.DdOptions != nil {
|
||||
duration, _ := options.ScheduleDuration()
|
||||
var deadline <-chan time.Time
|
||||
if duration != nil {
|
||||
deadline = time.After(*duration)
|
||||
}
|
||||
|
||||
if len(*attackConf.DdOptions) == 0 {
|
||||
return nil
|
||||
}
|
||||
rest := (*attackConf.DdOptions)[len(*attackConf.DdOptions)-1]
|
||||
*attackConf.DdOptions = (*attackConf.DdOptions)[:len(*attackConf.DdOptions)-1]
|
||||
|
||||
cmd := core.DdCommand.Unmarshal(rest)
|
||||
err := utils.ExecWithDeadline(deadline, cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var mu sync.Mutex
|
||||
var errs error
|
||||
wg.Add(len(*attackConf.DdOptions))
|
||||
for _, ddOpt := range *attackConf.DdOptions {
|
||||
cmd := core.DdCommand.Unmarshal(ddOpt)
|
||||
|
||||
go func(cmd *exec.Cmd) {
|
||||
defer wg.Done()
|
||||
err := utils.ExecWithDeadline(deadline, cmd)
|
||||
if err != nil {
|
||||
log.Error(cmd.String(), zap.Error(err))
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
errs = multierror.Append(errs, err)
|
||||
return
|
||||
}
|
||||
}(cmd)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if errs != nil {
|
||||
return errs
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (diskAttack) Recover(exp core.Experiment, _ Environment) error {
|
||||
config, err := exp.GetRequestCommand()
|
||||
attackConfig, err := exp.GetRequestCommand()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
option := *config.(*core.DiskOption)
|
||||
switch option.Action {
|
||||
config := *attackConfig.(*core.DiskAttackConfig)
|
||||
switch config.Action {
|
||||
case core.DiskFillAction, core.DiskWritePayloadAction:
|
||||
err = os.Remove(option.Path)
|
||||
err = os.Remove(config.Path)
|
||||
if err != nil {
|
||||
log.Warn(fmt.Sprintf("recover disk: remove %s failed", option.Path), zap.Error(err))
|
||||
log.Warn(fmt.Sprintf("recover disk: remove %s failed", config.Path), zap.Error(err))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
// 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 (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/chaos-mesh/chaosd/pkg/core"
|
||||
)
|
||||
|
||||
func Test_diskAttack_Attack(t *testing.T) {
|
||||
opt := core.DiskOption{
|
||||
CommonAttackConfig: core.CommonAttackConfig{
|
||||
Action: core.DiskFillAction,
|
||||
},
|
||||
Size: "10M",
|
||||
Path: "./a",
|
||||
PayloadProcessNum: 1,
|
||||
}
|
||||
conf, err := opt.PreProcess()
|
||||
assert.NoError(t, err)
|
||||
err = DiskAttack.Attack(conf, Environment{})
|
||||
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, Environment{})
|
||||
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)
|
||||
}
|
||||
|
|
@ -116,6 +116,12 @@ func (s *httpServer) createProcessAttack(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
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.ProcessAttack, attack, core.ServerMode)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
|
|
@ -141,6 +147,12 @@ func (s *httpServer) createNetworkAttack(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
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.NetworkAttack, attack, core.ServerMode)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
|
|
@ -166,6 +178,12 @@ func (s *httpServer) createStressAttack(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
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.StressAttack, attack, core.ServerMode)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
|
|
@ -185,14 +203,20 @@ func (s *httpServer) createStressAttack(c *gin.Context) {
|
|||
// @Failure 500 {object} utils.APIError
|
||||
// @Router /api/attack/disk [post]
|
||||
func (s *httpServer) createDiskAttack(c *gin.Context) {
|
||||
attack := core.NewDiskOption()
|
||||
if err := c.ShouldBindJSON(attack); err != nil {
|
||||
options := core.NewDiskOption()
|
||||
if err := c.ShouldBindJSON(options); err != nil {
|
||||
c.AbortWithError(http.StatusBadRequest, utils.ErrInternalServer.WrapWithNoMessage(err))
|
||||
return
|
||||
}
|
||||
|
||||
uid, err := s.chaos.ExecuteAttack(chaosd.DiskAttack, attack, core.ServerMode)
|
||||
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.DiskAttack, attackConfig, core.ServerMode)
|
||||
if err != nil {
|
||||
handleError(c, err)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
// 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
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
// 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 (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type Command struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (c Command) Unmarshal(val interface{}) *exec.Cmd {
|
||||
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 exec.Command(c.Name, options...) //
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
// 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 (
|
||||
"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())
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue