|
|
package shell
import ( "bytes" "flag" "fmt" "github.com/alecthomas/units" "github.com/chrislusf/seaweedfs/weed/config" "github.com/chrislusf/seaweedfs/weed/filer" "github.com/chrislusf/seaweedfs/weed/pb/s3_pb" "io" "strconv" "strings"
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb" )
func init() { Commands = append(Commands, &commandS3CircuitBreaker{}) }
type commandS3CircuitBreaker struct { }
func (c *commandS3CircuitBreaker) Name() string { return "s3.circuit.breaker" }
func (c *commandS3CircuitBreaker) Help() string { return `configure and apply s3 circuit breaker options for each bucket
# examples # add s3.circuit.breaker -actions Read,Write -values 500,200 -global -enable -apply -type count s3.circuit.breaker -actions Write -values 200MiB -global -enable -apply -type bytes s3.circuit.breaker -actions Write -values 200MiB -bucket x,y,z -enable -apply -type bytes
#delete s3.circuit.breaker -actions Write -bucket x,y,z -delete -apply -type bytes s3.circuit.breaker -actions Write -bucket x,y,z -delete -apply s3.circuit.breaker -actions Write -delete -apply ` }
func (c *commandS3CircuitBreaker) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) { dir := config.CircuitBreakerConfigDir file := config.CircuitBreakerConfigFile
s3CircuitBreakerCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError) buckets := s3CircuitBreakerCommand.String("buckets", "", "comma separated buckets names") global := s3CircuitBreakerCommand.Bool("global", false, "comma separated buckets names")
actions := s3CircuitBreakerCommand.String("actions", "", "comma separated actions names: Read,Write,List,Tagging,Admin") limitType := s3CircuitBreakerCommand.String("type", "", "count|bytes simultaneous requests count") values := s3CircuitBreakerCommand.String("values", "", "comma separated max values,Maximum number of simultaneous requests content length, support byte unit: eg: 1k, 10m, 1g")
enabled := s3CircuitBreakerCommand.Bool("enable", true, "enable or disable circuit breaker") deleted := s3CircuitBreakerCommand.Bool("delete", false, "delete users, actions or access keys")
apply := s3CircuitBreakerCommand.Bool("apply", false, "update and apply current configuration")
if err = s3CircuitBreakerCommand.Parse(args); err != nil { return nil }
var buf bytes.Buffer if err = commandEnv.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error { return filer.ReadEntry(commandEnv.MasterClient, client, dir, file, &buf) }); err != nil && err != filer_pb.ErrNotFound { return err }
cbCfg := &s3_pb.S3CircuitBreakerConfig{ Buckets: make(map[string]*s3_pb.CbOptions), } if buf.Len() > 0 { if err = filer.ParseS3ConfigurationFromBytes(buf.Bytes(), cbCfg); err != nil { return err } }
if *deleted { cmdBuckets, cmdActions, _, err := c.initActionsAndValues(buckets, actions, limitType, values, true) if err != nil { return err }
if len(cmdBuckets) <= 0 && !*global { if len(cmdActions) > 0 { deleteGlobalActions(cbCfg, cmdActions, limitType) if cbCfg.Buckets != nil { var allBuckets []string for bucket, _ := range cbCfg.Buckets { allBuckets = append(allBuckets, bucket) } deleteBucketsActions(allBuckets, cbCfg, cmdActions, limitType) } } else { cbCfg.Global = nil cbCfg.Buckets = nil } } else { if len(cmdBuckets) > 0 { deleteBucketsActions(cmdBuckets, cbCfg, cmdActions, limitType) } if *global { deleteGlobalActions(cbCfg, cmdActions, nil) } } } else { cmdBuckets, cmdActions, cmdValues, err := c.initActionsAndValues(buckets, actions, limitType, values, false) if err != nil { return err }
if len(cmdActions) > 0 && len(*buckets) <= 0 && !*global { return fmt.Errorf("one of -global and -buckets must be specified") }
if len(*buckets) > 0 { for _, bucket := range cmdBuckets { var cbOptions *s3_pb.CbOptions var exists bool if cbOptions, exists = cbCfg.Buckets[bucket]; !exists { cbOptions = &s3_pb.CbOptions{} cbCfg.Buckets[bucket] = cbOptions } cbOptions.Enabled = *enabled
if len(cmdActions) > 0 { err = insertOrUpdateValues(cbOptions, cmdActions, cmdValues, limitType) if err != nil { return err } }
if len(cbOptions.Actions) <= 0 && !cbOptions.Enabled { delete(cbCfg.Buckets, bucket) } } }
if *global { globalOptions := cbCfg.Global if globalOptions == nil { globalOptions = &s3_pb.CbOptions{Actions: make(map[string]int64, len(cmdActions))} cbCfg.Global = globalOptions } globalOptions.Enabled = *enabled
if len(cmdActions) > 0 { err = insertOrUpdateValues(globalOptions, cmdActions, cmdValues, limitType) if err != nil { return err } }
if len(globalOptions.Actions) <= 0 && !globalOptions.Enabled { cbCfg.Global = nil } } }
buf.Reset() err = filer.ProtoToText(&buf, cbCfg) if err != nil { return err }
fmt.Fprintf(writer, string(buf.Bytes())) fmt.Fprintln(writer)
if *apply { if err := commandEnv.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error { return filer.SaveInsideFiler(client, dir, file, buf.Bytes()) }); err != nil { return err }
}
return nil }
func insertOrUpdateValues(cbOptions *s3_pb.CbOptions, cmdActions []string, cmdValues []int64, limitType *string) error { if len(*limitType) == 0 { return fmt.Errorf("type not valid, only 'count' and 'bytes' are allowed") }
if cbOptions.Actions == nil { cbOptions.Actions = make(map[string]int64, len(cmdActions)) }
if len(cmdValues) > 0 { for i, action := range cmdActions { cbOptions.Actions[config.Concat(action, *limitType)] = cmdValues[i] } } return nil }
func deleteBucketsActions(cmdBuckets []string, cbCfg *s3_pb.S3CircuitBreakerConfig, cmdActions []string, limitType *string) { if cbCfg.Buckets == nil { return }
if len(cmdActions) == 0 { for _, bucket := range cmdBuckets { delete(cbCfg.Buckets, bucket) } } else { for _, bucket := range cmdBuckets { if cbOption, ok := cbCfg.Buckets[bucket]; ok { if len(cmdActions) > 0 && cbOption.Actions != nil { for _, action := range cmdActions { delete(cbOption.Actions, config.Concat(action, *limitType)) } }
if len(cbOption.Actions) == 0 && !cbOption.Enabled { delete(cbCfg.Buckets, bucket) } } } }
if len(cbCfg.Buckets) == 0 { cbCfg.Buckets = nil } }
func deleteGlobalActions(cbCfg *s3_pb.S3CircuitBreakerConfig, cmdActions []string, limitType *string) { globalOptions := cbCfg.Global if globalOptions == nil { return }
if len(cmdActions) == 0 && globalOptions.Actions != nil { globalOptions.Actions = nil return } else { for _, action := range cmdActions { delete(globalOptions.Actions, config.Concat(action, *limitType)) } }
if len(globalOptions.Actions) == 0 && !globalOptions.Enabled { cbCfg.Global = nil } }
func (c *commandS3CircuitBreaker) initActionsAndValues(buckets, actions, limitType, values *string, deleteOp bool) (cmdBuckets, cmdActions []string, cmdValues []int64, err error) { if len(*buckets) > 0 { cmdBuckets = strings.Split(*buckets, ",") }
if len(*actions) > 0 { cmdActions = strings.Split(*actions, ",")
//check action valid
for _, action := range cmdActions { var found bool for _, allowedAction := range config.AllowedActions { if allowedAction == action { found = true } } if !found { return nil, nil, nil, fmt.Errorf("value(%s) of flag[-action] not valid, allowed actions: %v", *actions, config.AllowedActions) } } }
if !deleteOp { if len(cmdActions) < 0 { for _, action := range config.AllowedActions { cmdActions = append(cmdActions, action) } }
if len(*limitType) > 0 { switch *limitType { case config.LimitTypeCount: elements := strings.Split(*values, ",") if len(cmdActions) != len(elements) { if len(elements) != 1 || len(elements) == 0 { return nil, nil, nil, fmt.Errorf("count of flag[-actions] and flag[-counts] not equal") } v, err := strconv.Atoi(elements[0]) if err != nil { return nil, nil, nil, fmt.Errorf("value of -counts must be a legal number(s)") } for range cmdActions { cmdValues = append(cmdValues, int64(v)) } } else { for _, value := range elements { v, err := strconv.Atoi(value) if err != nil { return nil, nil, nil, fmt.Errorf("value of -counts must be a legal number(s)") } cmdValues = append(cmdValues, int64(v)) } } case config.LimitTypeBytes: elements := strings.Split(*values, ",") if len(cmdActions) != len(elements) { if len(elements) != 1 || len(elements) == 0 { return nil, nil, nil, fmt.Errorf("count of flag[-actions] and flag[-counts] not equal") } v, err := units.ParseStrictBytes(elements[0]) if err != nil { return nil, nil, nil, fmt.Errorf("value of -max must be a legal number(s)") } for range cmdActions { cmdValues = append(cmdValues, v) } } else { for _, value := range elements { v, err := units.ParseStrictBytes(value) if err != nil { return nil, nil, nil, fmt.Errorf("value of -max must be a legal number(s)") } cmdValues = append(cmdValues, v) } } default: return nil, nil, nil, fmt.Errorf("type not valid, only 'count' and 'bytes' are allowed") } } else { *limitType = "" } } return cmdBuckets, cmdActions, cmdValues, nil }
|