sc4ry 0.1.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,11 @@
1
+ # rubygems depends
1
2
  require 'rest-client'
2
3
  require 'prometheus/client'
3
4
  require 'prometheus/client/push'
5
+ require 'redis'
6
+ require 'version'
4
7
 
8
+ # Stdlibs depends
5
9
  require 'logger'
6
10
  require 'timeout'
7
11
  require 'forwardable'
@@ -11,9 +15,10 @@ require 'openssl'
11
15
  require 'net/http'
12
16
  require 'uri'
13
17
  require 'json'
18
+ require 'yaml'
14
19
 
15
20
 
16
-
21
+ # Sc4ry internal depends
17
22
  require_relative 'helpers'
18
23
  require_relative 'exceptions'
19
24
  require_relative 'logger'
@@ -1,5 +1,12 @@
1
+ # Sc4ry module
2
+ # @note namespace
1
3
  module Sc4ry
4
+
5
+ # Sc4ry::Exceptions module
6
+ # @note namespace
2
7
  module Exceptions
8
+
9
+ # Exception use in {Sc4ry::Circuits} when running circuit {Sc4ry::Circuits::run}
3
10
  class CircuitBreaked < StandardError
4
11
  def initialize(msg="Circuit just opened")
5
12
  super(msg)
@@ -7,15 +14,33 @@ module Sc4ry
7
14
 
8
15
  end
9
16
 
17
+ # Generic Exception use in {Sc4ry::Circuits}
10
18
  class Sc4ryGenericError < StandardError
11
- def initialize(msg)
19
+ def initialize(msg="")
20
+ super(msg)
21
+ end
22
+
23
+ end
24
+
25
+ # Exception use in {Sc4ry::Store} or/and {Sc4ry::Backend} on data string issues
26
+ class Sc4ryBackendError < StandardError
27
+ def initialize(msg="")
12
28
  super(msg)
13
29
  end
14
30
 
15
31
  end
16
32
 
33
+ # Exception use in {Sc4ry::Notifiers} on notification issues
34
+ class Sc4ryNotifierError < StandardError
35
+ def initialize(msg="")
36
+ super(msg)
37
+ end
38
+
39
+ end
40
+
41
+ # Exception use in {Sc4ry::Circuits} on config management issues
17
42
  class ConfigError < StandardError
18
- def initialize(msg)
43
+ def initialize(msg="")
19
44
  super(msg)
20
45
  end
21
46
 
data/lib/sc4ry/helpers.rb CHANGED
@@ -1,10 +1,22 @@
1
-
1
+ # Sc4ry module
2
+ # @note namespace
2
3
  module Sc4ry
3
- module Helpers
4
4
 
5
- def Helpers.log(options)
6
- Sc4ry::Logger.current = options[:target] if options[:target]
7
- Sc4ry::Logger.get.send options[:level], "Sc4ry : #{options[:message]}"
5
+ # Sc4ry::Helpers module
6
+ # @note namespace
7
+ module Helpers
8
+
9
+ # class method (module) to help logging messages
10
+ # @param [Symbol] target a specific logger, restored old after
11
+ # @param [Symbol] level (default :info) a logging level (see Logger Stdlib)
12
+ # @param [String] message your message
13
+ # @return [Boolean]
14
+ def Helpers.log(target: nil, level: :info, message: )
15
+ save = Sc4ry::Loggers.current
16
+ Sc4ry::Loggers.current = target if target
17
+ Sc4ry::Loggers.get.send level, "Sc4ry : #{message}"
18
+ Sc4ry::Loggers.current = save
19
+ return true
8
20
  end
9
21
 
10
22
  # TCP/IP service checker
@@ -37,7 +49,9 @@ module Sc4ry
37
49
  end
38
50
  end
39
51
 
40
-
52
+ # class method (module) to help send notifiesby Sc4ry::Notifiers
53
+ # @param [Hash] options a Notifying structure
54
+ # @return [Boolean]
41
55
  def Helpers.notify(options = {})
42
56
  Sc4ry::Notifiers.list.each do |record|
43
57
  notifier = Sc4ry::Notifiers.get name: record
data/lib/sc4ry/logger.rb CHANGED
@@ -1,29 +1,65 @@
1
-
1
+ # Sc4ry module
2
+ # @note namespace
2
3
  module Sc4ry
3
- class Logger
4
+
5
+ # Sc4ry loggers Factory/provider
6
+ # @note must be accessed by [Sc4ry::Circuits.loggers]
7
+ class Loggers
4
8
 
5
9
  @@loggers = {:stdout => ::Logger.new(STDOUT)}
6
10
  @@current = :stdout
7
11
 
8
- def Logger.list_avaible
9
- return @@loggers
12
+ # give the list of available loggers (initially internal Sc4ry logger )
13
+ # @return [Array] of [symbol] the list of defined loggers
14
+ # @note default :stdout => ::Logger(STDOUT) from Ruby Stdlib
15
+ # @example usage
16
+ # include Sc4ry
17
+ # Circuits.loggers.list_available.each {|logger| puts logger }
18
+ def Loggers.list_available
19
+ return @@loggers.keys
10
20
  end
11
21
 
12
- def Logger.current
22
+ # return the current logger name (initially :stdtout )
23
+ # @return [symbol] the name of the logger
24
+ # @example usage
25
+ # include Sc4ry
26
+ # puts Circuits.loggers.current
27
+ def Loggers.current
13
28
  return @@current
14
29
  end
15
30
 
16
- def Logger.get
31
+ # return the current logger Object (initially internal Sc4ry Stdlib Logger on STDOUT )
32
+ # @return [symbol] the name of the logger
33
+ # @example usage
34
+ # include Sc4ry
35
+ # Circuits.loggers.get :stdout
36
+ def Loggers.get
17
37
  return @@loggers[@@current]
18
38
  end
19
39
 
20
- def Logger.current=(sym)
40
+ # Set the current logger
41
+ # @param [Symbol] sym the name of the logger
42
+ # @return [symbol] the name of the logger updated
43
+ # @example usage
44
+ # include Sc4ry
45
+ # Circuits.loggers.current = :newlogger
46
+ def Loggers.current=(sym)
21
47
  raise "Logger not define : #{sym}" unless @@loggers.keys.include? sym
22
48
  @@current = sym
49
+ return @@current
23
50
  end
24
51
 
25
- def Logger.register(options = {})
26
- @@loggers[options[:name]] = options[:instance]
52
+ # register un new logger
53
+ # @param [Symbol] name the name of the new logger
54
+ # @param [Object] instance the new logger object
55
+ # raise Sc4ry::Exceptions::Sc4ryGenericError if name is not a Symbol
56
+ # @example usage
57
+ # include Sc4ry
58
+ # Circuits.loggers.register name: :newlogger, instance: Logger::new('/path/to/my.log')
59
+ def Loggers.register(name: , instance: )
60
+ raise Sc4ry::Exceptions::Sc4ryGenericError, "name: keyword must be a Symbol" unless name.class == Symbol
61
+ @@loggers[name] = instance
62
+ return name
27
63
  end
28
64
 
29
65
  end
@@ -1,32 +1,56 @@
1
1
  Dir[File.dirname(__FILE__) + '/*.rb'].sort.each { |file| require file unless File.basename(file) == 'init.rb' }
2
2
 
3
+ # Sc4ry module
4
+ # @note namespace
3
5
  module Sc4ry
4
- module Notifiers
5
6
 
7
+ # Sc4ry::Notifiers module
8
+ # @note namespace
9
+ module Notifiers
10
+
11
+ # default notifiers specifications
6
12
  DEFAULT_NOTIFIERS = {:prometheus => {:class => Sc4ry::Notifiers::Prometheus, :config => {:url => 'http://localhost:9091'}},
7
13
  :mattermost => {:class => Sc4ry::Notifiers::Mattermost, :config => {:url => 'http://localhost:9999', :token => "<CHANGE_ME>"}}
8
14
  }
9
15
  @@notifiers_list = DEFAULT_NOTIFIERS.dup
10
16
 
17
+ # class method how display a specific notifier config
18
+ # @param notifier [Symbol] a notifier name
19
+ # @return [Hash] the config
20
+ def Notifiers.display_config(notifier: )
21
+ raise Sc4ry::Exceptions::Sc4ryNotifierError, "Notifier #{notifier} not found" unless @@notifiers_list.include? notifier
22
+ return @@notifiers_list[notifier][:config]
23
+ end
11
24
 
25
+ # class method how return the list of known notifiers
26
+ # @return [Array] a list of [Symbol] notifiers name
12
27
  def Notifiers.list
13
28
  return @@notifiers_list.keys
14
29
  end
15
30
 
16
- def Notifiers.get(options ={})
17
- return @@notifiers_list[options[:name]]
31
+ # class method how return a specific notifier by name
32
+ # @param name [Symbol] a notifier name
33
+ # @return [Hash] the notifier structure
34
+ def Notifiers.get(name: )
35
+ return @@notifiers_list[name]
18
36
  end
19
37
 
20
- def Notifiers.register(options)
21
- raise ":name is mandatory" unless options[:name]
22
- raise ":definition is mandatory" unless options[:definition]
23
- @@notifiers_list[options[:name]] = options[:definition]
38
+ # class method how register a specific notifier
39
+ # @param name [Symbol] a notifier name
40
+ # @param definition [Hash] a notifier definition
41
+ # @return [Hash] the notifier structure
42
+ def Notifiers.register(name: , definition: )
43
+ @@notifiers_list[name] = definition
24
44
  end
25
45
 
26
- def Notifiers.config(options)
27
- raise ":name is mandatory" unless options[:name]
28
- raise ":config is mandatory" unless options[:config]
29
- @@notifiers_list[options[:name]][:config] = options[:config]
46
+
47
+ # class method how configure a specific notifier
48
+ # @param name [Symbol] a notifier name
49
+ # @param config [Hash] a notifier config
50
+ # @return [Hash] the notifier structure
51
+ def Notifiers.config(name:, config: )
52
+ @@notifiers_list[name][:config] = config
53
+ return config
30
54
  end
31
55
  end
32
56
  end
@@ -1,12 +1,17 @@
1
+ # Sc4ry module
2
+ # @note namespace
1
3
  module Sc4ry
4
+ # Sc4ry::Notifiers module
5
+ # @note namespace
2
6
  module Notifiers
3
7
 
8
+ # Mattermost Notifier class
4
9
  class Mattermost
5
10
 
6
11
  # send metrics to Prometheus PushGateway
7
12
  # @return [Bool]
8
13
  def Mattermost.notify(options = {})
9
- config = Sc4ry::Notifiers.get({name: :mattermost})[:config]
14
+ config = Sc4ry::Notifiers.get(name: :mattermost)[:config]
10
15
  status = options[:config][:status][:general]
11
16
  circuit = options[:circuit]
12
17
  status_map = {:open => 0, :half_open => 1, :closed => 2}
@@ -20,7 +25,7 @@ module Sc4ry
20
25
  use_ssl: uri.scheme == "https",
21
26
  }
22
27
  payload = { "text" => "message : #{message } from #{Socket.gethostname}", "username" => "Sc4ry" }
23
- Sc4ry::Helpers.log level: :debug, message: "Mattermost Notifier : #{message}"
28
+ Sc4ry::Helpers.log level: :debug, message: "Mattermost Notifying : #{message}"
24
29
  request.body = ::JSON.dump(payload)
25
30
  response = ::Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
26
31
  http.request(request)
@@ -1,7 +1,13 @@
1
+ # Sc4ry module
2
+ # @note namespace
1
3
  module Sc4ry
4
+
5
+ # Sc4ry::Notifiers module
6
+ # @note namespace
2
7
  module Notifiers
3
8
 
4
9
 
10
+ # Prometheus notifier class
5
11
  class Prometheus
6
12
 
7
13
  @@registry = ::Prometheus::Client::Registry::new
@@ -14,7 +20,7 @@ module Sc4ry
14
20
  # send metrics to Prometheus PushGateway
15
21
  # @return [Bool]
16
22
  def Prometheus.notify(options = {})
17
- @config = Sc4ry::Notifiers.get({name: :prometheus})[:config]
23
+ @config = Sc4ry::Notifiers.get(name: :prometheus)[:config]
18
24
  status = options[:config][:status][:general]
19
25
  circuit = options[:circuit]
20
26
  status_map = {:open => 0, :half_open => 1, :closed => 2}
@@ -1,9 +1,16 @@
1
1
 
2
+ # Sc4ry module
3
+ # @note namespace
2
4
  module Sc4ry
5
+
6
+ # class Facility to run and update values/status for a circuit Proc
3
7
  class RunController
4
8
 
9
+ # return the execution time of the proc
5
10
  attr_reader :execution_time
6
11
 
12
+ # constructor
13
+ # @param [Hash] circuit the data of the circuit
7
14
  def initialize(circuit = {})
8
15
  @circuit = circuit
9
16
  @execution_time = 0
@@ -12,38 +19,47 @@ module Sc4ry
12
19
  @overtime = false
13
20
  end
14
21
 
22
+ # return if the Proc failed on a covered exception by this circuit
23
+ # @return [Boolean]
15
24
  def failed?
16
25
  return @failure
17
26
  end
18
-
27
+
28
+ # return if the Proc overtime the specified time of the circuit
29
+ # @return [Boolean]
19
30
  def overtimed?
20
31
  return @overtime
21
32
  end
22
33
 
34
+ # return if the Proc timeout the timeout defined value of the circuit, if timeout is active
35
+ # @return [Boolean]
23
36
  def timeout?
24
37
  return @timeout
25
38
  end
26
39
 
27
-
28
- def run(options = {})
40
+ # run and update values for the bloc given by keyword
41
+ # @param [Proc] block a block to run and calculate
42
+ # @return [Hash] a result Hash
43
+ def run(block: )
29
44
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
30
45
  begin
31
46
  if @circuit[:timeout] == true
32
47
  Timeout::timeout(@circuit[:timeout_value]) do
33
- options[:block].call
48
+ block.call
34
49
  end
35
50
  @timeout = false
36
51
  else
37
- options[:block].call
52
+ block.call
38
53
  end
39
54
  rescue Exception => e
40
- @last_exception = e.class
55
+ @last_exception = e.class.to_s
41
56
  if e.class == Timeout::Error then
42
57
  @timeout = true
43
58
  elsif @circuit[:exceptions].include? e.class
44
59
  @failure = true
45
60
  else
46
61
  if @circuit[:forward_unknown_exceptions] then
62
+
47
63
  raise e.class, "Sc4ry forward: #{e.message}"
48
64
  else
49
65
  Sc4ry::Helpers.log level: :debug, message: "skipped : #{@last_exception}"
data/lib/sc4ry/store.rb CHANGED
@@ -1,4 +1,10 @@
1
+ # Sc4ry module
2
+ # @note namespace
1
3
  module Sc4ry
4
+
5
+ # Sc4ry::Store class
6
+ # Store Class Provider/manager singleton Forwarder on {Sc4ry::Backends::Memory} or {Sc4ry::Backends::Redis}
7
+ # @note must be accessed by {Sc4ry::Circuits.store}
2
8
  class Store
3
9
 
4
10
  @@current = :memory
@@ -6,35 +12,92 @@ module Sc4ry
6
12
  extend Forwardable
7
13
  include Singleton
8
14
 
9
- @@backends = {:memory => {:class => Sc4ry::Backends::Memory},
15
+ @@backends = {:memory => {:class => Sc4ry::Backends::Memory, config: {}},
10
16
  :redis => {:class => Sc4ry::Backends::Redis, :config => {:host => 'localhost', :port => 6379, :db => 10 }}}
11
17
 
18
+ # accessor on current backend (default :memory)
12
19
  attr_reader :be
20
+
13
21
  def_delegators :@be, :put, :get, :flush, :exist?, :del, :list
14
22
 
23
+ # constructor pointing on :memory backend
15
24
  def initialize
16
25
  change_backend name: @@current
17
26
  end
18
27
 
28
+ # return the current backend
29
+ # @return [Object] in {Sc4ry::Backends::Memory} or {Sc4ry::Backends::Redis}
30
+ # @example usage
31
+ # include Sc4ry
32
+ # puts Circuits.store.current
19
33
  def current
20
34
  return @@current
21
35
  end
22
36
 
23
- def change_backend(options)
24
- @@current = options[:name]
37
+ # return the config of a specific backend
38
+ # @param [Symbol] backend the name the backend
39
+ # @return [Hash] the config of the backend
40
+ # @raise Sc4ry::Exceptions::Sc4ryBackendError if backend is not found
41
+ # @example usage
42
+ # include Sc4ry
43
+ # puts Circuits.store.get_config backend: :redis
44
+ def get_config(backend: )
45
+ raise Sc4ry::Exceptions::Sc4ryBackendError, "backend #{backend} not found" unless @@backends.include? backend
46
+ return @@backends[backend][:config]
47
+ end
48
+
49
+ # list backend available
50
+ # @return [Array] of Symbol the list of backend name
51
+ # @example usage
52
+ # include Sc4ry
53
+ # puts Circuits.store.list_backend
54
+ def list_backend
55
+ return @@backends.keys
56
+ end
57
+
58
+ # change the current backend
59
+ # @note if changing form :memory to :redis => all values and result are lost and circuits will be lost
60
+ # @note if changing to :redis, get all the define circuits with values and status (ideal) for distributed worker/instance/runner/services
61
+ # @param [Symbol] name the name of the target backend
62
+ # @return [Symbol] the name of the new current backend
63
+ # @raise Sc4ry::Exceptions::Sc4ryBackendError if backend is not found
64
+ def change_backend(name: )
65
+ raise Sc4ry::Exceptions::Sc4ryBackendError, "backend #{name} not found" unless @@backends.include? name
66
+ @@current = name
25
67
  @be = @@backends[@@current][:class]::new(@@backends[@@current][:config])
68
+ return name
26
69
  end
27
70
 
28
- def register_backend(options)
29
- raise ":name is mandatory" unless options[:name]
30
- raise ":definition is mandatory" unless options[:definition]
31
- @@backends[options[:name]] = options[:definition]
71
+ # register a new backend
72
+ # @param [Symbol] name the name of the backend
73
+ # @param [Hash] config the config for this backend
74
+ # @param [Class] backend_class the class name of the new backend
75
+ # @raise Sc4ry::Exceptions::Sc4ryBackendError if backend already exist
76
+ # @return [Symbol] the name of the backend
77
+ def register_backend(name:, config: {}, backend_class:)
78
+ raise Sc4ry::Exceptions::Sc4ryBackendError , "backend #{name} already exist" if @@backends.include? name
79
+ @@backends[name] = {config: config, class: backend_class}
80
+ return name
32
81
  end
33
82
 
34
- def config_backend(options)
35
- raise ":name is mandatory" unless options[:name]
36
- raise ":config is mandatory" unless options[:config]
37
- @@backends[options[:name]][:config] = options[:config]
83
+ # delete the specified backend reference
84
+ # @param [Symbol] name the name of the target backend
85
+ # @raise Sc4ry::Exceptions::Sc4ryBackendError if backend is not found, or name == :memory or :redis
86
+ # @return [Boolean]
87
+ def delete_backend(name: )
88
+ raise Sc4ry::Exceptions::Sc4ryBackendError , "Delete forbidden for backend in [:redis,:memory]" if [:memory,:redis].include? name
89
+ raise Sc4ry::Exceptions::Sc4ryBackendError , "backend #{name} not found" unless @@backends.include? name
90
+ return @@backends.delete(name)
91
+ end
92
+
93
+ # change the specified backend config
94
+ # @param [Symbol] name the name of the target backend
95
+ # @param [Hash] config the config of the specified backend
96
+ # @raise Sc4ry::Exceptions::Sc4ryBackendError if backend is not found, or name == :memory
97
+ def config_backend(name:, config:)
98
+ raise Sc4ry::Exceptions::Sc4ryBackendError, "backend #{name} not found" unless @@backends.include? name
99
+ raise Sc4ry::Exceptions::Sc4ryBackendError, "backend :memory not need config" if name == :memory
100
+ @@backends[name][:config] = config
38
101
  end
39
102
 
40
103
 
data/lib/sc4ry/version.rb CHANGED
@@ -1,3 +1,9 @@
1
+ require 'version'
2
+
3
+ # Sc4ry module
4
+ # @note namespace
1
5
  module Sc4ry
2
- VERSION = "0.1.8"
6
+
7
+ # the version of Sc4ry
8
+ VERSION = Version.current
3
9
  end
data/sc4ry.gemspec CHANGED
@@ -1,8 +1,7 @@
1
- require_relative 'lib/sc4ry/version'
2
1
 
3
2
  Gem::Specification.new do |spec|
4
3
  spec.name = "sc4ry"
5
- spec.version = Sc4ry::VERSION
4
+ spec.version = `cat VERSION`.chomp
6
5
  spec.authors = ["Romain GEORGES"]
7
6
  spec.email = ["romain.georges@orange.com"]
8
7
 
@@ -22,6 +21,11 @@ Gem::Specification.new do |spec|
22
21
 
23
22
  spec.add_dependency "prometheus-client", "~> 3.0"
24
23
  spec.add_dependency "rest-client", "~> 2.1"
24
+ spec.add_dependency "redis", "~> 4.6"
25
25
 
26
-
26
+ spec.add_development_dependency "yard", "~> 0.9.27"
27
+ spec.add_development_dependency 'code_statistics', '~> 0.2.13'
28
+ spec.add_development_dependency "yard-rspec", "~> 0.1"
29
+ spec.add_development_dependency "roodi", "~> 5.0"
30
+ spec.add_dependency "version", "~> 1.1"
27
31
  end
@@ -0,0 +1,25 @@
1
+ AssignmentInConditionalCheck:
2
+ CaseMissingElseCheck:
3
+ ClassLineCountCheck:
4
+ line_count: 300 # including comments yard
5
+ ClassNameCheck:
6
+ pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/
7
+ #ClassVariableCheck:
8
+ CyclomaticComplexityBlockCheck:
9
+ complexity: 5
10
+ CyclomaticComplexityMethodCheck:
11
+ complexity: 10
12
+ EmptyRescueBodyCheck:
13
+ ForLoopCheck:
14
+ MethodLineCountCheck:
15
+ line_count: 30
16
+ MethodNameCheck:
17
+ pattern: !ruby/regexp /^[_a-z<>=\[|+-\/\*`]+[_a-z0-9_<>=~@\[\]]*[=!\?]?$/
18
+ # MissingForeignKeyIndexCheck:
19
+ ModuleLineCountCheck:
20
+ line_count: 300
21
+ ModuleNameCheck:
22
+ pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/
23
+ ParameterNumberCheck:
24
+ parameter_count: 5
25
+