mirror of https://github.com/chaos-mesh/chaosd.git
Add more disk attack support (#60)
This commit is contained in:
parent
5f8e051e28
commit
e765b99d65
|
@ -26,10 +26,10 @@ import (
|
|||
)
|
||||
|
||||
func NewDiskAttackCommand() *cobra.Command {
|
||||
options := core.NewDiskCommand()
|
||||
options := core.NewDiskOption()
|
||||
dep := fx.Options(
|
||||
server.Module,
|
||||
fx.Provide(func() *core.DiskCommand {
|
||||
fx.Provide(func() *core.DiskOption {
|
||||
return options
|
||||
}),
|
||||
)
|
||||
|
@ -45,7 +45,7 @@ func NewDiskAttackCommand() *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func NewDiskPayloadCommand(dep fx.Option, options *core.DiskCommand) *cobra.Command {
|
||||
func NewDiskPayloadCommand(dep fx.Option, options *core.DiskOption) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "add-payload <subcommand>",
|
||||
Short: "add disk payload",
|
||||
|
@ -59,7 +59,7 @@ func NewDiskPayloadCommand(dep fx.Option, options *core.DiskCommand) *cobra.Comm
|
|||
return cmd
|
||||
}
|
||||
|
||||
func NewDiskWritePayloadCommand(dep fx.Option, options *core.DiskCommand) *cobra.Command {
|
||||
func NewDiskWritePayloadCommand(dep fx.Option, options *core.DiskOption) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "write",
|
||||
Short: "write payload",
|
||||
|
@ -70,14 +70,19 @@ func NewDiskWritePayloadCommand(dep fx.Option, options *core.DiskCommand) *cobra
|
|||
}
|
||||
|
||||
cmd.Flags().StringVarP(&options.Size, "size", "s", "",
|
||||
"'size' specifies how many data will fill in the file path with unit MB.")
|
||||
cmd.Flags().StringVarP(&options.Path, "path", "p", "/dev/null",
|
||||
"'path' specifies the location to fill data in.\n"+
|
||||
"If path not provided, payload will write into /dev/null")
|
||||
"'size' specifies how many units of data will write into the file path."+
|
||||
"'unit' specifies the unit of data, support c=1, w=2, b=512, kB=1000, K=1024, MB=1000*1000,"+
|
||||
"M=1024*1024, , GB=1000*1000*1000, G=1024*1024*1024 BYTES"+
|
||||
"example : 1M | 512kB")
|
||||
cmd.Flags().StringVarP(&options.Path, "path", "p", "",
|
||||
"'path' specifies the location to fill data in."+
|
||||
"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")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func NewDiskReadPayloadCommand(dep fx.Option, options *core.DiskCommand) *cobra.Command {
|
||||
func NewDiskReadPayloadCommand(dep fx.Option, options *core.DiskOption) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "read",
|
||||
Short: "read payload",
|
||||
|
@ -88,14 +93,19 @@ func NewDiskReadPayloadCommand(dep fx.Option, options *core.DiskCommand) *cobra.
|
|||
}
|
||||
|
||||
cmd.Flags().StringVarP(&options.Size, "size", "s", "",
|
||||
"'size' specifies how many data will read from the file path with unit MB.")
|
||||
"'size' specifies how many units of data will read from the file path."+
|
||||
"'unit' specifies the unit of data, support c=1, w=2, b=512, kB=1000, K=1024, MB=1000*1000,"+
|
||||
"M=1024*1024, , GB=1000*1000*1000, G=1024*1024*1024 BYTES"+
|
||||
"example : 1M | 512kB")
|
||||
cmd.Flags().StringVarP(&options.Path, "path", "p", "",
|
||||
"'path' specifies the location to read data.\n"+
|
||||
"If path not provided, payload will raise an error")
|
||||
"'path' specifies the location to read data."+
|
||||
"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")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func NewDiskFillCommand(dep fx.Option, options *core.DiskCommand) *cobra.Command {
|
||||
func NewDiskFillCommand(dep fx.Option, options *core.DiskOption) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "fill",
|
||||
Short: "fill disk",
|
||||
|
@ -106,17 +116,21 @@ func NewDiskFillCommand(dep fx.Option, options *core.DiskCommand) *cobra.Command
|
|||
}
|
||||
|
||||
cmd.Flags().StringVarP(&options.Size, "size", "s", "",
|
||||
"'size' specifies how many data will fill in the file path with unit MB.")
|
||||
"'size' specifies how many units of data will fill in the file path."+
|
||||
"'unit' specifies the unit of data, support c=1, w=2, b=512, kB=1000, K=1024, MB=1000*1000,"+
|
||||
"M=1024*1024, , GB=1000*1000*1000, G=1024*1024*1024 BYTES"+
|
||||
"example : 1M | 512kB")
|
||||
cmd.Flags().StringVarP(&options.Path, "path", "p", "",
|
||||
"'path' specifies the location to fill data in.\n"+
|
||||
"'path' specifies the location to fill data in."+
|
||||
"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.DestroyFile, "destroy", "d", false, "destroy file after filled in or allocated")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func processDiskAttack(options *core.DiskCommand, chaos *chaosd.Server) {
|
||||
func processDiskAttack(options *core.DiskOption, chaos *chaosd.Server) {
|
||||
if err := options.Validate(); err != nil {
|
||||
utils.ExitWithError(utils.ExitBadArgs, err)
|
||||
}
|
||||
|
|
|
@ -15,20 +15,21 @@ package attack
|
|||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"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
|
||||
command *core.DiskCommand
|
||||
option *core.DiskOption
|
||||
wantErr bool
|
||||
}
|
||||
|
||||
|
@ -40,26 +41,28 @@ func TestServer_DiskFill(t *testing.T) {
|
|||
return []diskTest{
|
||||
{
|
||||
name: "0",
|
||||
command: &core.DiskCommand{
|
||||
option: &core.DiskOption{
|
||||
CommonAttackConfig: core.CommonAttackConfig{
|
||||
Action: core.DiskFillAction,
|
||||
Kind: core.DiskAttack,
|
||||
},
|
||||
Size: "1024",
|
||||
Path: "temp",
|
||||
FillByFallocate: true,
|
||||
Size: "1024M",
|
||||
Path: "temp",
|
||||
FillByFallocate: true,
|
||||
PayloadProcessNum: 1,
|
||||
},
|
||||
wantErr: false,
|
||||
}, {
|
||||
name: "1",
|
||||
command: &core.DiskCommand{
|
||||
option: &core.DiskOption{
|
||||
CommonAttackConfig: core.CommonAttackConfig{
|
||||
Action: core.DiskFillAction,
|
||||
Kind: core.DiskAttack,
|
||||
},
|
||||
Size: "24",
|
||||
Path: "temp",
|
||||
FillByFallocate: false,
|
||||
Size: "24MB",
|
||||
Path: "temp",
|
||||
FillByFallocate: false,
|
||||
PayloadProcessNum: 1,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
|
@ -68,7 +71,7 @@ func TestServer_DiskFill(t *testing.T) {
|
|||
fx.Invoke(func(s *chaosd.Server, tests []diskTest) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
f, err := os.Create(tt.command.Path)
|
||||
f, err := os.Create(tt.option.Path)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected err %v when creating temp file", err)
|
||||
return
|
||||
|
@ -76,24 +79,23 @@ func TestServer_DiskFill(t *testing.T) {
|
|||
if f != nil {
|
||||
_ = f.Close()
|
||||
}
|
||||
_, err = s.ExecuteAttack(chaosd.DiskAttack, tt.command)
|
||||
_, err = s.ExecuteAttack(chaosd.DiskAttack, tt.option)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("DiskFill() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
stat, err := os.Stat(tt.command.Path)
|
||||
stat, err := os.Stat(tt.option.Path)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected err %v when stat temp file", err)
|
||||
return
|
||||
}
|
||||
|
||||
size, _ := strconv.ParseUint(tt.command.Size, 10, 0)
|
||||
|
||||
if uint64(stat.Size()) != size*1024*1024 {
|
||||
t.Errorf("DiskFill() size %v, expect %d", stat.Size(), size*1024*1024)
|
||||
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.command.Path)
|
||||
os.Remove(tt.option.Path)
|
||||
})
|
||||
}
|
||||
}),
|
||||
|
@ -108,24 +110,26 @@ func TestServer_DiskPayload(t *testing.T) {
|
|||
return []diskTest{
|
||||
{
|
||||
name: "0",
|
||||
command: &core.DiskCommand{
|
||||
option: &core.DiskOption{
|
||||
CommonAttackConfig: core.CommonAttackConfig{
|
||||
Action: core.DiskWritePayloadAction,
|
||||
Kind: core.DiskAttack,
|
||||
},
|
||||
Size: "24",
|
||||
Path: "temp",
|
||||
Size: "24M",
|
||||
Path: "temp",
|
||||
PayloadProcessNum: 1,
|
||||
},
|
||||
wantErr: false,
|
||||
}, {
|
||||
name: "1",
|
||||
command: &core.DiskCommand{
|
||||
option: &core.DiskOption{
|
||||
CommonAttackConfig: core.CommonAttackConfig{
|
||||
Action: core.DiskReadPayloadAction,
|
||||
Kind: core.DiskAttack,
|
||||
},
|
||||
Size: "24",
|
||||
Path: "temp",
|
||||
Size: "24M",
|
||||
Path: "temp",
|
||||
PayloadProcessNum: 1,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
|
@ -134,7 +138,7 @@ func TestServer_DiskPayload(t *testing.T) {
|
|||
fx.Invoke(func(s *chaosd.Server, tests []diskTest) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
f, err := os.Create(tt.command.Path)
|
||||
f, err := os.Create(tt.option.Path)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected err %v when creating temp file", err)
|
||||
return
|
||||
|
@ -143,23 +147,191 @@ func TestServer_DiskPayload(t *testing.T) {
|
|||
_ = f.Close()
|
||||
}
|
||||
|
||||
_, err = s.ExecuteAttack(chaosd.DiskAttack, &core.DiskCommand{
|
||||
_, err = s.ExecuteAttack(chaosd.DiskAttack, &core.DiskOption{
|
||||
CommonAttackConfig: core.CommonAttackConfig{
|
||||
Action: core.DiskFillAction,
|
||||
Kind: core.DiskAttack,
|
||||
},
|
||||
Size: tt.command.Size,
|
||||
Size: tt.option.Size,
|
||||
Path: "temp",
|
||||
FillByFallocate: true,
|
||||
})
|
||||
_, err = s.ExecuteAttack(chaosd.DiskAttack, tt.command)
|
||||
_, err = s.ExecuteAttack(chaosd.DiskAttack, tt.option)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("DiskPayload() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
os.Remove(tt.command.Path)
|
||||
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,
|
||||
DestroyFile: 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,
|
||||
DestroyFile: 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,
|
||||
}))
|
||||
}
|
||||
|
|
4
go.mod
4
go.mod
|
@ -2,12 +2,14 @@ module github.com/chaos-mesh/chaosd
|
|||
|
||||
require (
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
|
||||
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/google/uuid v1.1.1
|
||||
github.com/hashicorp/go-multierror v1.1.0
|
||||
github.com/joomcode/errorx v1.0.1
|
||||
github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936
|
||||
github.com/olekukonko/tablewriter v0.0.4
|
||||
|
@ -20,7 +22,7 @@ require (
|
|||
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.7.0 // indirect
|
||||
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
|
||||
|
|
3
go.sum
3
go.sum
|
@ -56,6 +56,7 @@ github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4Rq
|
|||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
|
@ -415,11 +416,13 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t
|
|||
github.com/grpc-ecosystem/grpc-gateway v1.14.1/go.mod h1:6CwZWGDSPRJidgKAtJVvND6soZe6fT7iteq8wDPdhb0=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
|
||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
|
|
|
@ -17,6 +17,8 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/chaos-mesh/chaosd/pkg/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -25,40 +27,56 @@ const (
|
|||
DiskReadPayloadAction = "read-payload"
|
||||
)
|
||||
|
||||
type DiskCommand struct {
|
||||
type DiskOption struct {
|
||||
CommonAttackConfig
|
||||
|
||||
Size string `json:"size"`
|
||||
Path string `json:"path"`
|
||||
Percent string `json:"percent"`
|
||||
FillByFallocate bool `json:"fill_by_fallocate"`
|
||||
Size string `json:"size"`
|
||||
Path string `json:"path"`
|
||||
Percent string `json:"percent"`
|
||||
FillByFallocate bool `json:"fill_by_fallocate"`
|
||||
DestroyFile bool `json:"destroy_file"`
|
||||
PayloadProcessNum uint8 `json:"payload_process_num"`
|
||||
}
|
||||
|
||||
var _ AttackConfig = &DiskCommand{}
|
||||
var _ AttackConfig = &DiskOption{}
|
||||
|
||||
func (d *DiskCommand) Validate() error {
|
||||
if d.Percent == "" && d.Size == "" {
|
||||
return fmt.Errorf("one of percent and size must not be empty, DiskCommand : %v", d)
|
||||
func (d *DiskOption) Validate() error {
|
||||
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.FillByFallocate && (d.Size == "0" || (d.Size == "" && d.Percent == "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, DiskCommand : %v", d)
|
||||
if d.Action == DiskFillAction {
|
||||
if 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 := strconv.ParseUint(d.Percent, 10, 0)
|
||||
if d.Size == "" && err != nil {
|
||||
return fmt.Errorf("unsupport percent : %s, DiskCommand : %v", d.Percent, d)
|
||||
|
||||
if d.PayloadProcessNum == 0 {
|
||||
return fmt.Errorf("unsupport process num : %d, DiskOption : %v", d.PayloadProcessNum, d.Action)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d DiskCommand) RecoverData() string {
|
||||
func (d DiskOption) RecoverData() string {
|
||||
data, _ := json.Marshal(d)
|
||||
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func NewDiskCommand() *DiskCommand {
|
||||
return &DiskCommand{
|
||||
func NewDiskOption() *DiskOption {
|
||||
return &DiskOption{
|
||||
CommonAttackConfig: CommonAttackConfig{
|
||||
Kind: DiskAttack,
|
||||
},
|
||||
|
|
|
@ -15,13 +15,14 @@ package chaosd
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"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"
|
||||
|
@ -35,87 +36,151 @@ 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,direct,fullblock"
|
||||
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.DiskCommand)
|
||||
attack := options.(*core.DiskOption)
|
||||
|
||||
if options.String() == core.DiskFillAction {
|
||||
return disk.attackDiskFill(attack)
|
||||
return disk.diskFill(attack)
|
||||
}
|
||||
return disk.attackDiskPayload(attack)
|
||||
return disk.diskPayload(attack)
|
||||
}
|
||||
|
||||
func (diskAttack) attackDiskPayload(payload *core.DiskCommand) error {
|
||||
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 == "" {
|
||||
payload.Path = "/dev/null"
|
||||
err := initWritePayloadPath(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
err := os.Remove(payload.Path)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("unexpected err when removing temp file %s", payload.Path), zap.Error(err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
cmd := exec.Command("bash", "-c", fmt.Sprintf(DDWritePayloadCommand, payload.Path, "1M", payload.Size))
|
||||
output, err := cmd.CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
log.Error(string(output), zap.Error(err))
|
||||
} else {
|
||||
log.Info(string(output))
|
||||
}
|
||||
return err
|
||||
case core.DiskReadPayloadAction:
|
||||
cmdFormat = DDReadPayloadCommand
|
||||
if payload.Path == "" {
|
||||
err := errors.Errorf("empty read payload path")
|
||||
log.Error(fmt.Sprintf("payload action: %s", payload.Action), zap.Error(err))
|
||||
return err
|
||||
err := initReadPayloadPath(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
cmd := exec.Command("bash", "-c", fmt.Sprintf(DDReadPayloadCommand, payload.Path, "1M", payload.Size))
|
||||
output, err := cmd.CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
log.Error(string(output), zap.Error(err))
|
||||
} else {
|
||||
log.Info(string(output))
|
||||
}
|
||||
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()
|
||||
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
|
||||
}
|
||||
|
||||
const DDFillCommand = "dd if=/dev/zero of=%s bs=%s count=%s iflag=fullblock"
|
||||
const DDFallocateCommand = "fallocate -l %sM %s"
|
||||
const FallocateCommand = "fallocate -l %s %s"
|
||||
|
||||
func (diskAttack) attackDiskFill(fill *core.DiskCommand) error {
|
||||
// diskFill will execute a dd command (DDFillCommand or FallocateCommand)
|
||||
// to fill the disk.
|
||||
func (diskAttack) diskFill(fill *core.DiskOption) error {
|
||||
if fill.Path == "" {
|
||||
tempFile, err := ioutil.TempFile("", "example")
|
||||
var err error
|
||||
fill.Path, err = utils.CreateTempFile()
|
||||
if err != nil {
|
||||
log.Error("unexpected err when open temp file", zap.Error(err))
|
||||
log.Error(fmt.Sprintf("unexpected err when CreateTempFile in action: %s", fill.Action))
|
||||
return err
|
||||
}
|
||||
|
||||
if tempFile != nil {
|
||||
err = tempFile.Close()
|
||||
if err != nil {
|
||||
log.Error("unexpected err when close temp file", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := errors.Errorf("unexpected err : file get from ioutil.TempFile is nil")
|
||||
log.Error(fmt.Sprintf("payload action: %s", fill.Action), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
fill.Path = tempFile.Name()
|
||||
defer func() {
|
||||
err := os.Remove(fill.Path)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("unexpected err when removing temp file %s", fill.Path), zap.Error(err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
defer func() {
|
||||
if fill.DestroyFile {
|
||||
err := os.Remove(fill.Path)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("unexpected err when removing file %s", fill.Path), zap.Error(err))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if fill.Size != "" {
|
||||
fill.Size = strings.Trim(fill.Size, " ")
|
||||
} else if fill.Percent != "" {
|
||||
|
@ -131,14 +196,15 @@ func (diskAttack) attackDiskFill(fill *core.DiskCommand) error {
|
|||
log.Error("fail to get disk total size", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
fill.Size = strconv.FormatUint(totalSize/1024/1024*percent/100, 10)
|
||||
fill.Size = strconv.FormatUint(totalSize*percent/100, 10)
|
||||
}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
if fill.FillByFallocate {
|
||||
cmd = exec.Command("bash", "-c", fmt.Sprintf(DDFallocateCommand, fill.Size, fill.Path))
|
||||
cmd = exec.Command("bash", "-c", fmt.Sprintf(FallocateCommand, fill.Size, fill.Path))
|
||||
} else {
|
||||
//1M means the block size. The bytes size dd read | write is (block size) * (size).
|
||||
cmd = exec.Command("bash", "-c", fmt.Sprintf(DDFillCommand, fill.Path, "1M", fill.Size))
|
||||
//1Unit means the block size. The bytes size dd read | write is (block size) * (size).
|
||||
cmd = exec.Command("bash", "-c", fmt.Sprintf(DDFillCommand, fill.Path, fill.Size, "1"))
|
||||
}
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
|
|
|
@ -187,13 +187,13 @@ func (s *httpServer) createStressAttack(c *gin.Context) {
|
|||
// @Description Create disk attack.
|
||||
// @Tags attack
|
||||
// @Produce json
|
||||
// @Param request body core.DiskCommand true "Request body"
|
||||
// @Param request body core.DiskOption true "Request body"
|
||||
// @Success 200 {object} utils.Response
|
||||
// @Failure 400 {object} utils.APIError
|
||||
// @Failure 500 {object} utils.APIError
|
||||
// @Router /api/attack/disk [post]
|
||||
func (s *httpServer) createDiskAttack(c *gin.Context) {
|
||||
attack := &core.DiskCommand{
|
||||
attack := &core.DiskOption{
|
||||
CommonAttackConfig: core.CommonAttackConfig{
|
||||
Kind: core.ProcessAttack,
|
||||
},
|
||||
|
|
|
@ -28,3 +28,8 @@ func GetDiskTotalSize(path string) (total uint64, err error) {
|
|||
total = uint64(s.Bsize) * (s.Blocks - reservedBlocks)
|
||||
return total, err
|
||||
}
|
||||
|
||||
func GetRootDevice() (string, error) {
|
||||
// TODO: complete get device of root on darwin
|
||||
return "", nil
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ package utils
|
|||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/shirou/gopsutil/disk"
|
||||
)
|
||||
|
||||
// GetDiskTotalSize returns the total bytes in disk
|
||||
|
@ -28,3 +30,17 @@ func GetDiskTotalSize(path string) (total uint64, err error) {
|
|||
total = uint64(s.Frsize) * (s.Blocks - reservedBlocks)
|
||||
return total, nil
|
||||
}
|
||||
|
||||
// GetRootDevice returns the device which "/" mount on.
|
||||
func GetRootDevice() (string, error) {
|
||||
mapStat, err := disk.Partitions(false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, stat := range mapStat {
|
||||
if stat.Mountpoint == "/" {
|
||||
return stat.Device, nil
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
// 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 (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/pingcap/errors"
|
||||
"github.com/pingcap/log"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// CreateTempFile will create a temp file in current directory.
|
||||
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))
|
||||
return "", err
|
||||
}
|
||||
|
||||
if tempFile != nil {
|
||||
err = tempFile.Close()
|
||||
if err != nil {
|
||||
log.Error("unexpected err when close temp file", zap.Error(err))
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
err := errors.Errorf("unexpected err : file get from ioutil.TempFile is nil")
|
||||
return "", err
|
||||
}
|
||||
return tempFile.Name(), nil
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
// 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"
|
||||
"strconv"
|
||||
|
||||
"github.com/alecthomas/units"
|
||||
)
|
||||
|
||||
var (
|
||||
// See https://en.wikipedia.org/wiki/Binary_prefix
|
||||
shortBinaryUnitMap = units.MakeUnitMap("", "c", 1024)
|
||||
binaryUnitMap = units.MakeUnitMap("iB", "c", 1024)
|
||||
decimalUnitMap = units.MakeUnitMap("B", "c", 1000)
|
||||
)
|
||||
|
||||
// ParseUnit parse a digit with unit such as "K" , "KiB", "KB", "c", "MiB", "MB", "M".
|
||||
// If input string is a digit without unit ,
|
||||
// it will be regarded as a digit with unit M(1024*1024 bytes).
|
||||
func ParseUnit(s string) (uint64, error) {
|
||||
if _, err := strconv.Atoi(s); err == nil {
|
||||
s += "M"
|
||||
}
|
||||
if n, err := units.ParseUnit(s, shortBinaryUnitMap); err == nil {
|
||||
return uint64(n), nil
|
||||
}
|
||||
|
||||
if n, err := units.ParseUnit(s, binaryUnitMap); err == nil {
|
||||
return uint64(n), nil
|
||||
}
|
||||
|
||||
if n, err := units.ParseUnit(s, decimalUnitMap); err == nil {
|
||||
return uint64(n), nil
|
||||
}
|
||||
return 0, fmt.Errorf("units: unknown unit %s", s)
|
||||
}
|
||||
|
||||
// DdArgBlock is command arg for dd. BlockSize is bs.Count is count.
|
||||
type DdArgBlock struct {
|
||||
BlockSize string
|
||||
Count string
|
||||
}
|
||||
|
||||
// This func split bytes in to processNum + 1 dd arg blocks.
|
||||
// Every ddArgBlock can generate one dd command.
|
||||
// If bytes is bigger than processNum M ,
|
||||
// bytes will be split into processNum dd commands with bs = 1M ,count = bytes/ processNum M.
|
||||
// If bytes is not bigger than processNum M ,
|
||||
// bytes will be split into processNum dd commands with bs = bytes / uint64(processNum) ,count = 1.
|
||||
// And one ddArgBlock stand by the rest bytes will also add to the end of slice,
|
||||
// even if rest bytes = 0.
|
||||
func SplitBytesByProcessNum(bytes uint64, processNum uint8) ([]DdArgBlock, error) {
|
||||
if bytes == 0 {
|
||||
return []DdArgBlock{{
|
||||
BlockSize: "1M",
|
||||
Count: "0",
|
||||
}}, nil
|
||||
}
|
||||
if processNum == 0 {
|
||||
return nil, fmt.Errorf("num must not be zero")
|
||||
}
|
||||
ddArgBlocks := make([]DdArgBlock, processNum)
|
||||
if bytes > uint64(processNum)*(1<<20) {
|
||||
count := (bytes >> 20) / uint64(processNum)
|
||||
for i := range ddArgBlocks {
|
||||
ddArgBlocks[i].Count = strconv.FormatUint(count, 10)
|
||||
ddArgBlocks[i].BlockSize = "1M"
|
||||
bytes -= count << 20
|
||||
}
|
||||
} else {
|
||||
blockSize := bytes / uint64(processNum)
|
||||
for i := range ddArgBlocks {
|
||||
ddArgBlocks[i].Count = "1"
|
||||
ddArgBlocks[i].BlockSize = strconv.FormatUint(blockSize, 10) + "c"
|
||||
bytes -= blockSize
|
||||
}
|
||||
}
|
||||
ddArgBlocks = append(ddArgBlocks, DdArgBlock{
|
||||
BlockSize: "1",
|
||||
Count: strconv.FormatUint(bytes, 10) + "c",
|
||||
})
|
||||
return ddArgBlocks, nil
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
// 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 (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseUnit(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
want uint64
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "0",
|
||||
args: "0",
|
||||
want: 0,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "1",
|
||||
args: "1",
|
||||
want: 1 << 20,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "1MS",
|
||||
args: "1MS",
|
||||
want: 0,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ParseUnit(tt.args)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ParseUnit() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("ParseUnit() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitByteSize(t *testing.T) {
|
||||
type args struct {
|
||||
b uint64
|
||||
num uint8
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []DdArgBlock
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "0",
|
||||
args: args{
|
||||
b: 0,
|
||||
num: 0,
|
||||
},
|
||||
want: []DdArgBlock{
|
||||
{
|
||||
BlockSize: "1M",
|
||||
Count: "0",
|
||||
},
|
||||
{
|
||||
BlockSize: "",
|
||||
Count: "",
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "1",
|
||||
args: args{
|
||||
b: 1 << 20,
|
||||
num: 0,
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "2",
|
||||
args: args{
|
||||
b: 1 << 20,
|
||||
num: 2,
|
||||
},
|
||||
want: []DdArgBlock{{
|
||||
BlockSize: "524288c",
|
||||
Count: "1",
|
||||
}, {
|
||||
BlockSize: "524288c",
|
||||
Count: "1",
|
||||
}, {
|
||||
BlockSize: "1",
|
||||
Count: "0c",
|
||||
}},
|
||||
wantErr: false,
|
||||
}, {
|
||||
name: "3",
|
||||
args: args{
|
||||
b: 1<<20 + 1,
|
||||
num: 2,
|
||||
},
|
||||
want: []DdArgBlock{{
|
||||
BlockSize: "524288c",
|
||||
Count: "1",
|
||||
}, {
|
||||
BlockSize: "524288c",
|
||||
Count: "1",
|
||||
}, {
|
||||
BlockSize: "1",
|
||||
Count: "1c",
|
||||
}},
|
||||
wantErr: false,
|
||||
}, {
|
||||
name: "4",
|
||||
args: args{
|
||||
b: 5 << 20,
|
||||
num: 2,
|
||||
},
|
||||
want: []DdArgBlock{{
|
||||
BlockSize: "1M",
|
||||
Count: "2",
|
||||
}, {
|
||||
BlockSize: "1M",
|
||||
Count: "2",
|
||||
}, {
|
||||
BlockSize: "1",
|
||||
Count: "1048576c",
|
||||
}},
|
||||
wantErr: false,
|
||||
}, {
|
||||
name: "5",
|
||||
args: args{
|
||||
b: 5<<20 + 1,
|
||||
num: 2,
|
||||
},
|
||||
want: []DdArgBlock{{
|
||||
BlockSize: "1M",
|
||||
Count: "2",
|
||||
}, {
|
||||
BlockSize: "1M",
|
||||
Count: "2",
|
||||
}, {
|
||||
BlockSize: "1",
|
||||
Count: "1048577c",
|
||||
}},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := SplitBytesByProcessNum(tt.args.b, tt.args.num)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("SplitBytesByProcessNum() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("SplitBytesByProcessNum() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue