diff --git a/cmd/chaosd/ctl/command/disk.go b/cmd/chaosd/ctl/command/disk.go index f4803d3..5db02e5 100644 --- a/cmd/chaosd/ctl/command/disk.go +++ b/cmd/chaosd/ctl/command/disk.go @@ -67,7 +67,7 @@ func NewDiskWritePayloadCommand(dep fx.Option, options *core.DiskCommand) *cobra }, } - cmd.Flags().Uint64VarP(&options.Size, "size", "s", 0, + 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"+ @@ -86,7 +86,7 @@ func NewDiskReadPayloadCommand(dep fx.Option, options *core.DiskCommand) *cobra. }, } - cmd.Flags().Uint64VarP(&options.Size, "size", "s", 0, + cmd.Flags().StringVarP(&options.Size, "size", "s", "", "'size' specifies how many data will read from the file path with unit MB.") cmd.Flags().StringVarP(&options.Path, "path", "p", "", "'path' specifies the location to read data.\n"+ @@ -105,11 +105,13 @@ func NewDiskFillCommand(dep fx.Option, options *core.DiskCommand) *cobra.Command }, } - cmd.Flags().Uint64VarP(&options.Size, "size", "s", 0, + 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", "", "'path' specifies the location to fill data in.\n"+ "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") commonFlags(cmd, &options.CommonAttackConfig) return cmd diff --git a/cmd/chaosd/ctl/command/disk_test.go b/cmd/chaosd/ctl/command/disk_test.go index 83d0032..33a48e7 100644 --- a/cmd/chaosd/ctl/command/disk_test.go +++ b/cmd/chaosd/ctl/command/disk_test.go @@ -15,6 +15,7 @@ package command import ( "os" + "strconv" "testing" "go.uber.org/fx" @@ -43,7 +44,7 @@ func TestServer_DiskFill(t *testing.T) { Action: core.DiskFillAction, Kind: core.DiskAttack, }, - Size: 1024, + Size: "1024", Path: "temp", FillByFallocate: true, }, @@ -55,7 +56,7 @@ func TestServer_DiskFill(t *testing.T) { Action: core.DiskFillAction, Kind: core.DiskAttack, }, - Size: 24, + Size: "24", Path: "temp", FillByFallocate: false, }, @@ -85,8 +86,10 @@ func TestServer_DiskFill(t *testing.T) { return } - if uint64(stat.Size()) != tt.command.Size*1024*1024 { - t.Errorf("DiskFill() size %v, expect %d", stat.Size(), tt.command.Size*1024*1024) + 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) return } os.Remove(tt.command.Path) @@ -109,7 +112,7 @@ func TestServer_DiskPayload(t *testing.T) { Action: core.DiskWritePayloadAction, Kind: core.DiskAttack, }, - Size: 24, + Size: "24", Path: "temp", }, wantErr: false, @@ -120,7 +123,7 @@ func TestServer_DiskPayload(t *testing.T) { Action: core.DiskReadPayloadAction, Kind: core.DiskAttack, }, - Size: 24, + Size: "24", Path: "temp", }, wantErr: false, diff --git a/pkg/core/disk.go b/pkg/core/disk.go index 66b662b..0ce5a6c 100644 --- a/pkg/core/disk.go +++ b/pkg/core/disk.go @@ -16,6 +16,7 @@ package core import ( "encoding/json" "fmt" + "strconv" ) const ( @@ -27,18 +28,27 @@ const ( type DiskCommand struct { CommonAttackConfig - Size uint64 `json:"size"` + Size string `json:"size"` Path string `json:"path"` + Percent string `json:"percent"` FillByFallocate bool `json:"fill_by_fallocate"` } var _ AttackConfig = &DiskCommand{} -func (d DiskCommand) Validate() error { - if d.Action == DiskFillAction || d.Action == DiskWritePayloadAction || d.Action == DiskReadPayloadAction { - return nil +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) } - return fmt.Errorf("invalid disk attack action %v", d.Action) + 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) + } + _, err := strconv.ParseUint(d.Percent, 10, 0) + if d.Size == "" && err != nil { + return fmt.Errorf("unsupport percent : %s, DiskCommand : %v", d.Percent, d) + } + return nil } func (d DiskCommand) RecoverData() string { diff --git a/pkg/server/chaosd/disk.go b/pkg/server/chaosd/disk.go index 34bb543..c320898 100644 --- a/pkg/server/chaosd/disk.go +++ b/pkg/server/chaosd/disk.go @@ -18,7 +18,10 @@ import ( "io/ioutil" "os" "os/exec" + "path/filepath" "strconv" + "strings" + "syscall" "github.com/pingcap/errors" "github.com/pingcap/log" @@ -38,18 +41,18 @@ func (disk diskAttack) Attack(options core.AttackConfig, env Environment) (err e attack := options.(*core.DiskCommand) if options.String() == core.DiskFillAction { - return disk.attackDiskFill(env.AttackUid, attack) + return disk.attackDiskFill(attack) } - return disk.attackDiskPayload(env.AttackUid, attack) + return disk.attackDiskPayload(attack) } -func (diskAttack) attackDiskPayload(uid string, payload *core.DiskCommand) error { +func (diskAttack) attackDiskPayload(payload *core.DiskCommand) error { switch payload.Action { case core.DiskWritePayloadAction: if payload.Path == "" { payload.Path = "/dev/null" } - cmd := exec.Command("bash", "-c", fmt.Sprintf(DDWritePayloadCommand, payload.Path, "1M", strconv.FormatUint(payload.Size, 10))) + cmd := exec.Command("bash", "-c", fmt.Sprintf(DDWritePayloadCommand, payload.Path, "1M", payload.Size)) output, err := cmd.CombinedOutput() if err != nil { @@ -64,7 +67,7 @@ func (diskAttack) attackDiskPayload(uid string, payload *core.DiskCommand) error log.Error(fmt.Sprintf("payload action: %s", payload.Action), zap.Error(err)) return err } - cmd := exec.Command("bash", "-c", fmt.Sprintf(DDReadPayloadCommand, payload.Path, "1M", strconv.FormatUint(payload.Size, 10))) + cmd := exec.Command("bash", "-c", fmt.Sprintf(DDReadPayloadCommand, payload.Path, "1M", payload.Size)) output, err := cmd.CombinedOutput() if err != nil { @@ -83,7 +86,7 @@ func (diskAttack) attackDiskPayload(uid string, payload *core.DiskCommand) error const DDFillCommand = "dd if=/dev/zero of=%s bs=%s count=%s iflag=fullblock" const DDFallocateCommand = "fallocate -l %sM %s" -func (diskAttack) attackDiskFill(uid string, fill *core.DiskCommand) error { +func (diskAttack) attackDiskFill(fill *core.DiskCommand) error { if fill.Path == "" { tempFile, err := ioutil.TempFile("", "example") if err != nil { @@ -113,11 +116,32 @@ func (diskAttack) attackDiskFill(uid string, fill *core.DiskCommand) error { } var cmd *exec.Cmd + 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) + s := syscall.Statfs_t{} + err = syscall.Statfs(dir, &s) + if err != nil { + log.Error(fmt.Sprintf("unexpected err when using syscall.Statfs"), zap.Error(err)) + return err + } + reservedBlocks := s.Bfree - s.Bavail + totalM := uint64(s.Frsize) * (s.Blocks - reservedBlocks) / 1024 / 1024 + fill.Size = strconv.FormatUint(totalM*percent/100, 10) + } + if fill.FillByFallocate { - cmd = exec.Command("bash", "-c", fmt.Sprintf(DDFallocateCommand, strconv.FormatUint(fill.Size, 10), fill.Path)) + cmd = exec.Command("bash", "-c", fmt.Sprintf(DDFallocateCommand, 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", strconv.FormatUint(fill.Size, 10))) + cmd = exec.Command("bash", "-c", fmt.Sprintf(DDFillCommand, fill.Path, "1M", fill.Size)) } output, err := cmd.CombinedOutput()