mirror of https://github.com/chaos-mesh/chaosd.git
				
				
				
			
		
			
				
	
	
		
			289 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			289 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
// 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 (
 | 
						|
	"encoding/json"
 | 
						|
	"fmt"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/pingcap/log"
 | 
						|
	"go.uber.org/zap"
 | 
						|
 | 
						|
	"github.com/chaos-mesh/chaosd/pkg/utils"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	DiskFillAction         = "fill"
 | 
						|
	DiskWritePayloadAction = "write-payload"
 | 
						|
	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
 | 
						|
 | 
						|
	Size              string `json:"size,omitempty"`
 | 
						|
	Path              string `json:"path,omitempty"`
 | 
						|
	Percent           string `json:"percent,omitempty"`
 | 
						|
	PayloadProcessNum uint8  `json:"payload-process-num,omitempty"`
 | 
						|
 | 
						|
	FillByFallocate bool `json:"fallocate,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
func NewDiskOption() *DiskOption {
 | 
						|
	return &DiskOption{
 | 
						|
		CommonAttackConfig: CommonAttackConfig{
 | 
						|
			Kind: DiskAttack,
 | 
						|
		},
 | 
						|
		PayloadProcessNum: 1,
 | 
						|
		FillByFallocate:   true,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func NewDiskOptionForServer() *DiskOption {
 | 
						|
	return &DiskOption{
 | 
						|
		CommonAttackConfig: CommonAttackConfig{
 | 
						|
			Kind: DiskServerAttack,
 | 
						|
		},
 | 
						|
		PayloadProcessNum: 1,
 | 
						|
		FillByFallocate:   true,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
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
 | 
						|
			opt.Path, err = os.Getwd()
 | 
						|
			if err != nil {
 | 
						|
				log.Error("unexpected err when execute os.Getwd()", zap.Error(err))
 | 
						|
				return "", err
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		fi, 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 := os.WriteFile(opt.Path, b, 0600); err != nil {
 | 
						|
					return "", err
 | 
						|
				}
 | 
						|
				if err := os.Remove(opt.Path); err != nil {
 | 
						|
					return "", err
 | 
						|
				}
 | 
						|
				return opt.Path, nil
 | 
						|
			}
 | 
						|
			return "", err
 | 
						|
		}
 | 
						|
		if fi.IsDir() {
 | 
						|
			opt.Path, err = utils.CreateTempFile(opt.Path)
 | 
						|
			if err != nil {
 | 
						|
				log.Error(fmt.Sprintf("unexpected err : %v , when CreateTempFile in action %s with path %s.", err, opt.Action, opt.Path))
 | 
						|
				return "", err
 | 
						|
			}
 | 
						|
			if err := os.Remove(opt.Path); err != nil {
 | 
						|
				return "", err
 | 
						|
			}
 | 
						|
			return opt.Path, err
 | 
						|
		}
 | 
						|
		return "", fmt.Errorf("fill into an existing file")
 | 
						|
	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)
 | 
						|
}
 |