RadisChaos: Add Cache Expiration (#175)

* add redis cache expiration

Signed-off-by: FingerLeader <wanxfinger@gmail.com>

* edit some details

Signed-off-by: FingerLeader <wanxfinger@gmail.com>

* edit some details

Signed-off-by: FingerLeader <wanxfinger@gmail.com>

* modify default value of key

Signed-off-by: FingerLeader <wanxfinger@gmail.com>

* edit some details

Signed-off-by: FingerLeader <wanxfinger@gmail.com>
This commit is contained in:
Ningxuan Wang 2022-06-19 10:58:35 +08:00 committed by GitHub
parent f1df7d3f15
commit e4cabb4419
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 94 additions and 5 deletions

View File

@ -45,6 +45,7 @@ func NewRedisAttackCommand(uid *string) *cobra.Command {
NewRedisSentinelStopCommand(dep, options),
NewRedisCachePenetrationCommand(dep, options),
NewRedisCacheLimitCommand(dep, options),
NewRedisCacheExpirationCommand(dep, options),
)
return cmd
@ -60,7 +61,7 @@ func NewRedisSentinelRestartCommand(dep fx.Option, options *core.RedisCommand) *
},
}
cmd.Flags().StringVarP(&options.Addr, "addr", "a", "", "")
cmd.Flags().StringVarP(&options.Addr, "addr", "a", "", "The address of redis server")
cmd.Flags().StringVarP(&options.Password, "password", "p", "", "The password of server")
cmd.Flags().StringVarP(&options.Conf, "conf", "c", "", "The config of Redis server")
cmd.Flags().BoolVarP(&options.FlushConfig, "flush-config", "", true, " Force Sentinel to rewrite its configuration on disk")
@ -80,7 +81,7 @@ func NewRedisSentinelStopCommand(dep fx.Option, options *core.RedisCommand) *cob
},
}
cmd.Flags().StringVarP(&options.Addr, "addr", "a", "", "")
cmd.Flags().StringVarP(&options.Addr, "addr", "a", "", "The address of redis server")
cmd.Flags().StringVarP(&options.Password, "password", "p", "", "The password of server")
cmd.Flags().StringVarP(&options.Conf, "conf", "c", "", "The config path of Redis server")
cmd.Flags().BoolVarP(&options.FlushConfig, "flush-config", "", true, "Force Sentinel to rewrite its configuration on disk")
@ -99,7 +100,7 @@ func NewRedisCachePenetrationCommand(dep fx.Option, options *core.RedisCommand)
},
}
cmd.Flags().StringVarP(&options.Addr, "addr", "a", "", "")
cmd.Flags().StringVarP(&options.Addr, "addr", "a", "", "The address of redis server")
cmd.Flags().StringVarP(&options.Password, "password", "p", "", "The password of server")
cmd.Flags().IntVarP(&options.RequestNum, "request-num", "", 0, "The number of requests")
@ -116,7 +117,7 @@ func NewRedisCacheLimitCommand(dep fx.Option, options *core.RedisCommand) *cobra
},
}
cmd.Flags().StringVarP(&options.Addr, "addr", "a", "", "")
cmd.Flags().StringVarP(&options.Addr, "addr", "a", "", "The address of redis server")
cmd.Flags().StringVarP(&options.Password, "password", "p", "", "The password of server")
cmd.Flags().StringVarP(&options.CacheSize, "size", "s", "0", "The size of cache")
cmd.Flags().StringVarP(&options.Percent, "percent", "", "", "The percentage of maxmemory")
@ -124,6 +125,25 @@ func NewRedisCacheLimitCommand(dep fx.Option, options *core.RedisCommand) *cobra
return cmd
}
func NewRedisCacheExpirationCommand(dep fx.Option, options *core.RedisCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "cache-expiration",
Short: "expire keys in Redis",
Run: func(*cobra.Command, []string) {
options.Action = core.RedisCacheExpirationAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(redisAttackF)).Run()
},
}
cmd.Flags().StringVarP(&options.Addr, "addr", "a", "", "The address of redis server")
cmd.Flags().StringVarP(&options.Password, "password", "p", "", "The password of server")
cmd.Flags().StringVarP(&options.Key, "key", "k", "", "The key to be set a expiration, default expire all keys")
cmd.Flags().StringVarP(&options.Expiration, "expiration", "", "0", `The expiration of the key. A expiration string should be able to be converted to a time duration, such as "5s" or "30m"`)
cmd.Flags().StringVarP(&options.Option, "option", "", "", "The additional options of expiration, only NX, XX, GT, LT supported")
return cmd
}
func redisAttackF(chaos *chaosd.Server, options *core.RedisCommand) {
if err := options.Validate(); err != nil {
utils.ExitWithError(utils.ExitBadArgs, err)

View File

@ -24,9 +24,13 @@ const (
RedisSentinelStopAction = "stop"
RedisCachePenetrationAction = "penetration"
RedisCacheLimitAction = "cacheLimit"
RedisCacheExpirationAction = "expiration"
)
var _ AttackConfig = &RedisCommand{}
var (
_ AttackConfig = &RedisCommand{}
ValidOptions = map[string]bool{"XX": true, "NX": true, "GT": true, "LT": true}
)
type RedisCommand struct {
CommonAttackConfig
@ -39,6 +43,9 @@ type RedisCommand struct {
RequestNum int `json:"requestNum,omitempty"`
CacheSize string `json:"cacheSize,omitempty"`
Percent string `json:"percent,omitempty"`
Key string `json:"key,omitempty"`
Expiration string `json:"expiration,omitempty"`
Option string `json:"option,omitempty"`
OriginCacheSize string `json:"originCacheSize,omitempty"`
}
@ -60,6 +67,10 @@ func (r *RedisCommand) Validate() error {
if r.CacheSize != "0" && r.Percent != "" {
return errors.New("only one of cachesize and percent can be set")
}
case RedisCacheExpirationAction:
if _, ok := ValidOptions[r.Option]; ok {
return errors.New("option invalid")
}
}
return nil
}

View File

@ -18,6 +18,7 @@ import (
"math"
"os/exec"
"strconv"
"time"
"github.com/go-redis/redis/v8"
"github.com/pingcap/errors"
@ -31,6 +32,10 @@ var RedisAttack AttackType = redisAttack{}
const (
STATUSOK = "OK"
OPTIONNX = "NX"
OPTIONXX = "XX"
OPTIONGT = "GT"
OPTIONLT = "LT"
)
func (redisAttack) Attack(options core.AttackConfig, env Environment) error {
@ -97,6 +102,9 @@ func (redisAttack) Attack(options core.AttackConfig, env Environment) error {
if result != STATUSOK {
return errors.WithStack(errors.Errorf("redis command status is %s", result))
}
case core.RedisCacheExpirationAction:
return env.Chaos.expireKeys(attack, cli)
}
return nil
}
@ -170,3 +178,53 @@ func (s *Server) recoverSentinelStop(attack *core.RedisCommand) error {
}
return nil
}
func (s *Server) expireKeys(attack *core.RedisCommand, cli *redis.Client) error {
expiration, err := time.ParseDuration(attack.Expiration)
if err != nil {
return errors.WithStack(err)
}
if attack.Key == "" {
// Get all keys from the server
allKeys, err := cli.Keys(cli.Context(), "*").Result()
if err != nil {
return errors.WithStack(err)
}
for _, key := range allKeys {
result, err := ExpireFunc(cli, key, expiration, attack.Option).Result()
if err != nil {
return errors.WithStack(err)
}
if !result {
return errors.WithStack(errors.Errorf("expire failed"))
}
}
} else {
result, err := ExpireFunc(cli, attack.Key, expiration, attack.Option).Result()
if err != nil {
return errors.WithStack(err)
}
if !result {
return errors.WithStack(errors.Errorf("expire failed"))
}
}
return nil
}
func ExpireFunc(cli *redis.Client, key string, expiration time.Duration, option string) *redis.BoolCmd {
switch option {
case OPTIONNX:
return cli.ExpireNX(cli.Context(), key, expiration)
case OPTIONXX:
return cli.ExpireXX(cli.Context(), key, expiration)
case OPTIONGT:
return cli.ExpireGT(cli.Context(), key, expiration)
case OPTIONLT:
return cli.ExpireLT(cli.Context(), key, expiration)
default:
return cli.Expire(cli.Context(), key, expiration)
}
}