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.

335 lines
9.3 KiB

  1. package shell
  2. import (
  3. "bytes"
  4. "flag"
  5. "fmt"
  6. "github.com/alecthomas/units"
  7. "github.com/chrislusf/seaweedfs/weed/config"
  8. "github.com/chrislusf/seaweedfs/weed/filer"
  9. "github.com/chrislusf/seaweedfs/weed/pb/s3_pb"
  10. "io"
  11. "strconv"
  12. "strings"
  13. "github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
  14. )
  15. func init() {
  16. Commands = append(Commands, &commandS3CircuitBreaker{})
  17. }
  18. type commandS3CircuitBreaker struct {
  19. }
  20. func (c *commandS3CircuitBreaker) Name() string {
  21. return "s3.circuit.breaker"
  22. }
  23. func (c *commandS3CircuitBreaker) Help() string {
  24. return `configure and apply s3 circuit breaker options for each bucket
  25. # examples
  26. # add
  27. s3.circuit.breaker -actions Read,Write -values 500,200 -global -enable -apply -type count
  28. s3.circuit.breaker -actions Write -values 200MiB -global -enable -apply -type bytes
  29. s3.circuit.breaker -actions Write -values 200MiB -bucket x,y,z -enable -apply -type bytes
  30. #delete
  31. s3.circuit.breaker -actions Write -bucket x,y,z -delete -apply -type bytes
  32. s3.circuit.breaker -actions Write -bucket x,y,z -delete -apply
  33. s3.circuit.breaker -actions Write -delete -apply
  34. `
  35. }
  36. func (c *commandS3CircuitBreaker) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) {
  37. dir := config.CircuitBreakerConfigDir
  38. file := config.CircuitBreakerConfigFile
  39. s3CircuitBreakerCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
  40. buckets := s3CircuitBreakerCommand.String("buckets", "", "comma separated buckets names")
  41. global := s3CircuitBreakerCommand.Bool("global", false, "comma separated buckets names")
  42. actions := s3CircuitBreakerCommand.String("actions", "", "comma separated actions names: Read,Write,List,Tagging,Admin")
  43. limitType := s3CircuitBreakerCommand.String("type", "", "count|bytes simultaneous requests count")
  44. values := s3CircuitBreakerCommand.String("values", "", "comma separated max values,Maximum number of simultaneous requests content length, support byte unit: eg: 1k, 10m, 1g")
  45. enabled := s3CircuitBreakerCommand.Bool("enable", true, "enable or disable circuit breaker")
  46. deleted := s3CircuitBreakerCommand.Bool("delete", false, "delete users, actions or access keys")
  47. apply := s3CircuitBreakerCommand.Bool("apply", false, "update and apply current configuration")
  48. if err = s3CircuitBreakerCommand.Parse(args); err != nil {
  49. return nil
  50. }
  51. var buf bytes.Buffer
  52. if err = commandEnv.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
  53. return filer.ReadEntry(commandEnv.MasterClient, client, dir, file, &buf)
  54. }); err != nil && err != filer_pb.ErrNotFound {
  55. return err
  56. }
  57. cbCfg := &s3_pb.S3CircuitBreakerConfig{
  58. Buckets: make(map[string]*s3_pb.CbOptions),
  59. }
  60. if buf.Len() > 0 {
  61. if err = filer.ParseS3ConfigurationFromBytes(buf.Bytes(), cbCfg); err != nil {
  62. return err
  63. }
  64. }
  65. if *deleted {
  66. cmdBuckets, cmdActions, _, err := c.initActionsAndValues(buckets, actions, limitType, values, true)
  67. if err != nil {
  68. return err
  69. }
  70. if len(cmdBuckets) <= 0 && !*global {
  71. if len(cmdActions) > 0 {
  72. deleteGlobalActions(cbCfg, cmdActions, limitType)
  73. if cbCfg.Buckets != nil {
  74. var allBuckets []string
  75. for bucket, _ := range cbCfg.Buckets {
  76. allBuckets = append(allBuckets, bucket)
  77. }
  78. deleteBucketsActions(allBuckets, cbCfg, cmdActions, limitType)
  79. }
  80. } else {
  81. cbCfg.Global = nil
  82. cbCfg.Buckets = nil
  83. }
  84. } else {
  85. if len(cmdBuckets) > 0 {
  86. deleteBucketsActions(cmdBuckets, cbCfg, cmdActions, limitType)
  87. }
  88. if *global {
  89. deleteGlobalActions(cbCfg, cmdActions, nil)
  90. }
  91. }
  92. } else {
  93. cmdBuckets, cmdActions, cmdValues, err := c.initActionsAndValues(buckets, actions, limitType, values, false)
  94. if err != nil {
  95. return err
  96. }
  97. if len(cmdActions) > 0 && len(*buckets) <= 0 && !*global {
  98. return fmt.Errorf("one of -global and -buckets must be specified")
  99. }
  100. if len(*buckets) > 0 {
  101. for _, bucket := range cmdBuckets {
  102. var cbOptions *s3_pb.CbOptions
  103. var exists bool
  104. if cbOptions, exists = cbCfg.Buckets[bucket]; !exists {
  105. cbOptions = &s3_pb.CbOptions{}
  106. cbCfg.Buckets[bucket] = cbOptions
  107. }
  108. cbOptions.Enabled = *enabled
  109. if len(cmdActions) > 0 {
  110. err = insertOrUpdateValues(cbOptions, cmdActions, cmdValues, limitType)
  111. if err != nil {
  112. return err
  113. }
  114. }
  115. if len(cbOptions.Actions) <= 0 && !cbOptions.Enabled {
  116. delete(cbCfg.Buckets, bucket)
  117. }
  118. }
  119. }
  120. if *global {
  121. globalOptions := cbCfg.Global
  122. if globalOptions == nil {
  123. globalOptions = &s3_pb.CbOptions{Actions: make(map[string]int64, len(cmdActions))}
  124. cbCfg.Global = globalOptions
  125. }
  126. globalOptions.Enabled = *enabled
  127. if len(cmdActions) > 0 {
  128. err = insertOrUpdateValues(globalOptions, cmdActions, cmdValues, limitType)
  129. if err != nil {
  130. return err
  131. }
  132. }
  133. if len(globalOptions.Actions) <= 0 && !globalOptions.Enabled {
  134. cbCfg.Global = nil
  135. }
  136. }
  137. }
  138. buf.Reset()
  139. err = filer.ProtoToText(&buf, cbCfg)
  140. if err != nil {
  141. return err
  142. }
  143. fmt.Fprintf(writer, string(buf.Bytes()))
  144. fmt.Fprintln(writer)
  145. if *apply {
  146. if err := commandEnv.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
  147. return filer.SaveInsideFiler(client, dir, file, buf.Bytes())
  148. }); err != nil {
  149. return err
  150. }
  151. }
  152. return nil
  153. }
  154. func insertOrUpdateValues(cbOptions *s3_pb.CbOptions, cmdActions []string, cmdValues []int64, limitType *string) error {
  155. if len(*limitType) == 0 {
  156. return fmt.Errorf("type not valid, only 'count' and 'bytes' are allowed")
  157. }
  158. if cbOptions.Actions == nil {
  159. cbOptions.Actions = make(map[string]int64, len(cmdActions))
  160. }
  161. if len(cmdValues) > 0 {
  162. for i, action := range cmdActions {
  163. cbOptions.Actions[config.Concat(action, *limitType)] = cmdValues[i]
  164. }
  165. }
  166. return nil
  167. }
  168. func deleteBucketsActions(cmdBuckets []string, cbCfg *s3_pb.S3CircuitBreakerConfig, cmdActions []string, limitType *string) {
  169. if cbCfg.Buckets == nil {
  170. return
  171. }
  172. if len(cmdActions) == 0 {
  173. for _, bucket := range cmdBuckets {
  174. delete(cbCfg.Buckets, bucket)
  175. }
  176. } else {
  177. for _, bucket := range cmdBuckets {
  178. if cbOption, ok := cbCfg.Buckets[bucket]; ok {
  179. if len(cmdActions) > 0 && cbOption.Actions != nil {
  180. for _, action := range cmdActions {
  181. delete(cbOption.Actions, config.Concat(action, *limitType))
  182. }
  183. }
  184. if len(cbOption.Actions) == 0 && !cbOption.Enabled {
  185. delete(cbCfg.Buckets, bucket)
  186. }
  187. }
  188. }
  189. }
  190. if len(cbCfg.Buckets) == 0 {
  191. cbCfg.Buckets = nil
  192. }
  193. }
  194. func deleteGlobalActions(cbCfg *s3_pb.S3CircuitBreakerConfig, cmdActions []string, limitType *string) {
  195. globalOptions := cbCfg.Global
  196. if globalOptions == nil {
  197. return
  198. }
  199. if len(cmdActions) == 0 && globalOptions.Actions != nil {
  200. globalOptions.Actions = nil
  201. return
  202. } else {
  203. for _, action := range cmdActions {
  204. delete(globalOptions.Actions, config.Concat(action, *limitType))
  205. }
  206. }
  207. if len(globalOptions.Actions) == 0 && !globalOptions.Enabled {
  208. cbCfg.Global = nil
  209. }
  210. }
  211. func (c *commandS3CircuitBreaker) initActionsAndValues(buckets, actions, limitType, values *string, deleteOp bool) (cmdBuckets, cmdActions []string, cmdValues []int64, err error) {
  212. if len(*buckets) > 0 {
  213. cmdBuckets = strings.Split(*buckets, ",")
  214. }
  215. if len(*actions) > 0 {
  216. cmdActions = strings.Split(*actions, ",")
  217. //check action valid
  218. for _, action := range cmdActions {
  219. var found bool
  220. for _, allowedAction := range config.AllowedActions {
  221. if allowedAction == action {
  222. found = true
  223. }
  224. }
  225. if !found {
  226. return nil, nil, nil, fmt.Errorf("value(%s) of flag[-action] not valid, allowed actions: %v", *actions, config.AllowedActions)
  227. }
  228. }
  229. }
  230. if !deleteOp {
  231. if len(cmdActions) < 0 {
  232. for _, action := range config.AllowedActions {
  233. cmdActions = append(cmdActions, action)
  234. }
  235. }
  236. if len(*limitType) > 0 {
  237. switch *limitType {
  238. case config.LimitTypeCount:
  239. elements := strings.Split(*values, ",")
  240. if len(cmdActions) != len(elements) {
  241. if len(elements) != 1 || len(elements) == 0 {
  242. return nil, nil, nil, fmt.Errorf("count of flag[-actions] and flag[-counts] not equal")
  243. }
  244. v, err := strconv.Atoi(elements[0])
  245. if err != nil {
  246. return nil, nil, nil, fmt.Errorf("value of -counts must be a legal number(s)")
  247. }
  248. for range cmdActions {
  249. cmdValues = append(cmdValues, int64(v))
  250. }
  251. } else {
  252. for _, value := range elements {
  253. v, err := strconv.Atoi(value)
  254. if err != nil {
  255. return nil, nil, nil, fmt.Errorf("value of -counts must be a legal number(s)")
  256. }
  257. cmdValues = append(cmdValues, int64(v))
  258. }
  259. }
  260. case config.LimitTypeBytes:
  261. elements := strings.Split(*values, ",")
  262. if len(cmdActions) != len(elements) {
  263. if len(elements) != 1 || len(elements) == 0 {
  264. return nil, nil, nil, fmt.Errorf("count of flag[-actions] and flag[-counts] not equal")
  265. }
  266. v, err := units.ParseStrictBytes(elements[0])
  267. if err != nil {
  268. return nil, nil, nil, fmt.Errorf("value of -max must be a legal number(s)")
  269. }
  270. for range cmdActions {
  271. cmdValues = append(cmdValues, v)
  272. }
  273. } else {
  274. for _, value := range elements {
  275. v, err := units.ParseStrictBytes(value)
  276. if err != nil {
  277. return nil, nil, nil, fmt.Errorf("value of -max must be a legal number(s)")
  278. }
  279. cmdValues = append(cmdValues, v)
  280. }
  281. }
  282. default:
  283. return nil, nil, nil, fmt.Errorf("type not valid, only 'count' and 'bytes' are allowed")
  284. }
  285. } else {
  286. *limitType = ""
  287. }
  288. }
  289. return cmdBuckets, cmdActions, cmdValues, nil
  290. }