You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							363 lines
						
					
					
						
							10 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							363 lines
						
					
					
						
							10 KiB
						
					
					
				| package shell | |
| 
 | |
| import ( | |
| 	"bytes" | |
| 	"flag" | |
| 	"fmt" | |
| 	"io" | |
| 	"strconv" | |
| 	"strings" | |
| 
 | |
| 	"github.com/seaweedfs/seaweedfs/weed/filer" | |
| 	"github.com/seaweedfs/seaweedfs/weed/pb/s3_pb" | |
| 	"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" | |
| 
 | |
| 	"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" | |
| ) | |
| 
 | |
| var LoadConfig = loadConfig | |
| 
 | |
| func init() { | |
| 	Commands = append(Commands, &commandS3CircuitBreaker{}) | |
| } | |
| 
 | |
| type commandS3CircuitBreaker struct { | |
| } | |
| 
 | |
| func (c *commandS3CircuitBreaker) Name() string { | |
| 	return "s3.circuitBreaker" | |
| } | |
| 
 | |
| func (c *commandS3CircuitBreaker) Help() string { | |
| 	return `configure and apply s3 circuit breaker options for each bucket | |
|  | |
| 	# examples | |
| 	# add circuit breaker config for global | |
| 	s3.circuitBreaker -global -type count -actions Read,Write -values 500,200 -apply | |
|  | |
| 	# disable global config | |
| 	s3.circuitBreaker -global -disable -apply | |
|  | |
| 	# add circuit breaker config for buckets x,y,z | |
| 	s3.circuitBreaker -buckets x,y,z -type count -actions Read,Write -values 200,100 -apply | |
|  | |
| 	# disable circuit breaker config of x | |
| 	s3.circuitBreaker -buckets x -disable -apply | |
|  | |
| 	# delete circuit breaker config of x | |
| 	s3.circuitBreaker -buckets x -delete -apply | |
|  | |
| 	# clear all circuit breaker config | |
| 	s3.circuitBreaker -delete -apply | |
| 	` | |
| } | |
| 
 | |
| func (c *commandS3CircuitBreaker) HasTag(CommandTag) bool { | |
| 	return false | |
| } | |
| 
 | |
| func (c *commandS3CircuitBreaker) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) { | |
| 	dir := s3_constants.CircuitBreakerConfigDir | |
| 	file := s3_constants.CircuitBreakerConfigFile | |
| 
 | |
| 	s3CircuitBreakerCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError) | |
| 	buckets := s3CircuitBreakerCommand.String("buckets", "", "the bucket name(s) to configure, eg: -buckets x,y,z") | |
| 	global := s3CircuitBreakerCommand.Bool("global", false, "configure global circuit breaker") | |
| 
 | |
| 	actions := s3CircuitBreakerCommand.String("actions", "", "comma separated actions names: Read,Write,List,Tagging,Admin") | |
| 	limitType := s3CircuitBreakerCommand.String("type", "", "'Count' or 'MB'; Count represents the number of simultaneous requests, and MB represents the content size of all simultaneous requests") | |
| 	values := s3CircuitBreakerCommand.String("values", "", "comma separated values") | |
| 
 | |
| 	disabled := s3CircuitBreakerCommand.Bool("disable", false, "disable global or buckets circuit breaker") | |
| 	deleted := s3CircuitBreakerCommand.Bool("delete", false, "delete circuit breaker config") | |
| 
 | |
| 	apply := s3CircuitBreakerCommand.Bool("apply", false, "update and apply current configuration") | |
| 
 | |
| 	if err = s3CircuitBreakerCommand.Parse(args); err != nil { | |
| 		return nil | |
| 
 | |
| 	} | |
| 
 | |
| 	var buf bytes.Buffer | |
| 	err = LoadConfig(commandEnv, dir, file, &buf) | |
| 	if err != nil { | |
| 		return err | |
| 	} | |
| 
 | |
| 	cbCfg := &s3_pb.S3CircuitBreakerConfig{ | |
| 		Buckets: make(map[string]*s3_pb.S3CircuitBreakerOptions), | |
| 	} | |
| 	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, *disabled) | |
| 		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.S3CircuitBreakerOptions | |
| 				var exists bool | |
| 				if cbOptions, exists = cbCfg.Buckets[bucket]; !exists { | |
| 					cbOptions = &s3_pb.S3CircuitBreakerOptions{} | |
| 					cbCfg.Buckets[bucket] = cbOptions | |
| 				} | |
| 				cbOptions.Enabled = !*disabled | |
| 
 | |
| 				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.S3CircuitBreakerOptions{Actions: make(map[string]int64, len(cmdActions))} | |
| 				cbCfg.Global = globalOptions | |
| 			} | |
| 			globalOptions.Enabled = !*disabled | |
| 
 | |
| 			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.Fprint(writer, buf.String()) | |
| 	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 loadConfig(commandEnv *CommandEnv, dir string, file string, buf *bytes.Buffer) error { | |
| 	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 | |
| 	} | |
| 	return nil | |
| } | |
| 
 | |
| func insertOrUpdateValues(cbOptions *s3_pb.S3CircuitBreakerOptions, 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[s3_constants.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, s3_constants.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, s3_constants.Concat(action, *limitType)) | |
| 		} | |
| 	} | |
| 
 | |
| 	if len(globalOptions.Actions) == 0 && !globalOptions.Enabled { | |
| 		cbCfg.Global = nil | |
| 	} | |
| } | |
| 
 | |
| func (c *commandS3CircuitBreaker) initActionsAndValues(buckets, actions, limitType, values *string, parseValues 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 s3_constants.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, s3_constants.AllowedActions) | |
| 			} | |
| 		} | |
| 	} | |
| 
 | |
| 	if !parseValues { | |
| 		if len(cmdActions) < 0 { | |
| 			for _, action := range s3_constants.AllowedActions { | |
| 				cmdActions = append(cmdActions, action) | |
| 			} | |
| 		} | |
| 
 | |
| 		if len(*limitType) > 0 { | |
| 			switch *limitType { | |
| 			case s3_constants.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 -values 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 -values must be a legal number(s)") | |
| 						} | |
| 						cmdValues = append(cmdValues, int64(v)) | |
| 					} | |
| 				} | |
| 			case s3_constants.LimitTypeBytes: | |
| 				elements := strings.Split(*values, ",") | |
| 				if len(cmdActions) != len(elements) { | |
| 					if len(elements) != 1 || len(elements) == 0 { | |
| 						return nil, nil, nil, fmt.Errorf("values count of -actions and -values not equal") | |
| 					} | |
| 					v, err := parseMBToBytes(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 := parseMBToBytes(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 | |
| } | |
| 
 | |
| func parseMBToBytes(valStr string) (int64, error) { | |
| 	v, err := strconv.Atoi(valStr) | |
| 	v *= 1024 * 1024 | |
| 	return int64(v), err | |
| }
 |