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.

345 lines
9.6 KiB

  1. package shell
  2. import (
  3. "bytes"
  4. "flag"
  5. "fmt"
  6. "github.com/alecthomas/units"
  7. "github.com/chrislusf/seaweedfs/weed/filer"
  8. "github.com/chrislusf/seaweedfs/weed/pb/s3_pb"
  9. "github.com/chrislusf/seaweedfs/weed/s3api/s3_config"
  10. "io"
  11. "strconv"
  12. "strings"
  13. "github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
  14. )
  15. var LoadConfig = loadConfig
  16. func init() {
  17. Commands = append(Commands, &commandS3CircuitBreaker{})
  18. }
  19. type commandS3CircuitBreaker struct {
  20. }
  21. func (c *commandS3CircuitBreaker) Name() string {
  22. return "s3.circuitBreaker"
  23. }
  24. func (c *commandS3CircuitBreaker) Help() string {
  25. return `configure and apply s3 circuit breaker options for each bucket
  26. # examples
  27. # add
  28. s3.circuitBreaker -actions Read,Write -values 500,200 -global -enable -apply -type count
  29. s3.circuitBreaker -actions Write -values 200MiB -global -enable -apply -type bytes
  30. s3.circuitBreaker -actions Write -values 200MiB -bucket x,y,z -enable -apply -type bytes
  31. #delete
  32. s3.circuitBreaker -actions Write -bucket x,y,z -delete -apply -type bytes
  33. s3.circuitBreaker -actions Write -bucket x,y,z -delete -apply
  34. s3.circuitBreaker -actions Write -delete -apply
  35. `
  36. }
  37. func (c *commandS3CircuitBreaker) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) {
  38. dir := s3_config.CircuitBreakerConfigDir
  39. file := s3_config.CircuitBreakerConfigFile
  40. s3CircuitBreakerCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
  41. buckets := s3CircuitBreakerCommand.String("buckets", "", "the bucket name(s) to configure, eg: -buckets x,y,z")
  42. global := s3CircuitBreakerCommand.Bool("global", false, "configure global circuit breaker")
  43. actions := s3CircuitBreakerCommand.String("actions", "", "comma separated actions names: Read,Write,List,Tagging,Admin")
  44. limitType := s3CircuitBreakerCommand.String("type", "", "count|bytes simultaneous requests count")
  45. values := s3CircuitBreakerCommand.String("values", "", "comma separated max values,Maximum number of simultaneous requests content length, support byte unit: eg: 1k, 10m, 1g")
  46. disabled := s3CircuitBreakerCommand.Bool("disable", false, "disable global or buckets circuit breaker")
  47. deleted := s3CircuitBreakerCommand.Bool("delete", false, "delete circuit breaker config")
  48. apply := s3CircuitBreakerCommand.Bool("apply", false, "update and apply current configuration")
  49. if err = s3CircuitBreakerCommand.Parse(args); err != nil {
  50. return nil
  51. }
  52. var buf bytes.Buffer
  53. err = LoadConfig(commandEnv, dir, file, &buf)
  54. if err != nil {
  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, *disabled)
  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 = !*disabled
  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 = !*disabled
  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 loadConfig(commandEnv *CommandEnv, dir string, file string, buf *bytes.Buffer) error {
  155. if err := commandEnv.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
  156. return filer.ReadEntry(commandEnv.MasterClient, client, dir, file, buf)
  157. }); err != nil && err != filer_pb.ErrNotFound {
  158. return err
  159. }
  160. return nil
  161. }
  162. func insertOrUpdateValues(cbOptions *s3_pb.CbOptions, cmdActions []string, cmdValues []int64, limitType *string) error {
  163. if len(*limitType) == 0 {
  164. return fmt.Errorf("type not valid, only 'count' and 'bytes' are allowed")
  165. }
  166. if cbOptions.Actions == nil {
  167. cbOptions.Actions = make(map[string]int64, len(cmdActions))
  168. }
  169. if len(cmdValues) > 0 {
  170. for i, action := range cmdActions {
  171. cbOptions.Actions[s3_config.Concat(action, *limitType)] = cmdValues[i]
  172. }
  173. }
  174. return nil
  175. }
  176. func deleteBucketsActions(cmdBuckets []string, cbCfg *s3_pb.S3CircuitBreakerConfig, cmdActions []string, limitType *string) {
  177. if cbCfg.Buckets == nil {
  178. return
  179. }
  180. if len(cmdActions) == 0 {
  181. for _, bucket := range cmdBuckets {
  182. delete(cbCfg.Buckets, bucket)
  183. }
  184. } else {
  185. for _, bucket := range cmdBuckets {
  186. if cbOption, ok := cbCfg.Buckets[bucket]; ok {
  187. if len(cmdActions) > 0 && cbOption.Actions != nil {
  188. for _, action := range cmdActions {
  189. delete(cbOption.Actions, s3_config.Concat(action, *limitType))
  190. }
  191. }
  192. if len(cbOption.Actions) == 0 && !cbOption.Enabled {
  193. delete(cbCfg.Buckets, bucket)
  194. }
  195. }
  196. }
  197. }
  198. if len(cbCfg.Buckets) == 0 {
  199. cbCfg.Buckets = nil
  200. }
  201. }
  202. func deleteGlobalActions(cbCfg *s3_pb.S3CircuitBreakerConfig, cmdActions []string, limitType *string) {
  203. globalOptions := cbCfg.Global
  204. if globalOptions == nil {
  205. return
  206. }
  207. if len(cmdActions) == 0 && globalOptions.Actions != nil {
  208. globalOptions.Actions = nil
  209. return
  210. } else {
  211. for _, action := range cmdActions {
  212. delete(globalOptions.Actions, s3_config.Concat(action, *limitType))
  213. }
  214. }
  215. if len(globalOptions.Actions) == 0 && !globalOptions.Enabled {
  216. cbCfg.Global = nil
  217. }
  218. }
  219. func (c *commandS3CircuitBreaker) initActionsAndValues(buckets, actions, limitType, values *string, parseValues bool) (cmdBuckets, cmdActions []string, cmdValues []int64, err error) {
  220. if len(*buckets) > 0 {
  221. cmdBuckets = strings.Split(*buckets, ",")
  222. }
  223. if len(*actions) > 0 {
  224. cmdActions = strings.Split(*actions, ",")
  225. //check action valid
  226. for _, action := range cmdActions {
  227. var found bool
  228. for _, allowedAction := range s3_config.AllowedActions {
  229. if allowedAction == action {
  230. found = true
  231. }
  232. }
  233. if !found {
  234. return nil, nil, nil, fmt.Errorf("value(%s) of flag[-action] not valid, allowed actions: %v", *actions, s3_config.AllowedActions)
  235. }
  236. }
  237. }
  238. if !parseValues {
  239. if len(cmdActions) < 0 {
  240. for _, action := range s3_config.AllowedActions {
  241. cmdActions = append(cmdActions, action)
  242. }
  243. }
  244. if len(*limitType) > 0 {
  245. switch *limitType {
  246. case s3_config.LimitTypeCount:
  247. elements := strings.Split(*values, ",")
  248. if len(cmdActions) != len(elements) {
  249. if len(elements) != 1 || len(elements) == 0 {
  250. return nil, nil, nil, fmt.Errorf("count of flag[-actions] and flag[-counts] not equal")
  251. }
  252. v, err := strconv.Atoi(elements[0])
  253. if err != nil {
  254. return nil, nil, nil, fmt.Errorf("value of -values must be a legal number(s)")
  255. }
  256. for range cmdActions {
  257. cmdValues = append(cmdValues, int64(v))
  258. }
  259. } else {
  260. for _, value := range elements {
  261. v, err := strconv.Atoi(value)
  262. if err != nil {
  263. return nil, nil, nil, fmt.Errorf("value of -values must be a legal number(s)")
  264. }
  265. cmdValues = append(cmdValues, int64(v))
  266. }
  267. }
  268. case s3_config.LimitTypeBytes:
  269. elements := strings.Split(*values, ",")
  270. if len(cmdActions) != len(elements) {
  271. if len(elements) != 1 || len(elements) == 0 {
  272. return nil, nil, nil, fmt.Errorf("values count of -actions and -values not equal")
  273. }
  274. v, err := units.ParseStrictBytes(elements[0])
  275. if err != nil {
  276. return nil, nil, nil, fmt.Errorf("value of -max must be a legal number(s)")
  277. }
  278. for range cmdActions {
  279. cmdValues = append(cmdValues, v)
  280. }
  281. } else {
  282. for _, value := range elements {
  283. v, err := units.ParseStrictBytes(value)
  284. if err != nil {
  285. return nil, nil, nil, fmt.Errorf("value of -max must be a legal number(s)")
  286. }
  287. cmdValues = append(cmdValues, v)
  288. }
  289. }
  290. default:
  291. return nil, nil, nil, fmt.Errorf("type not valid, only 'count' and 'bytes' are allowed")
  292. }
  293. } else {
  294. *limitType = ""
  295. }
  296. }
  297. return cmdBuckets, cmdActions, cmdValues, nil
  298. }