prometheus-splash 0.5.3 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -1
  3. data/README.md +58 -60
  4. data/bin/splash +3 -0
  5. data/config/splash.yml +110 -11
  6. data/lib/splash/backends.rb +7 -0
  7. data/lib/splash/backends/file.rb +36 -0
  8. data/lib/splash/backends/redis.rb +44 -1
  9. data/lib/splash/cli.rb +11 -4
  10. data/lib/splash/cli/commands.rb +136 -9
  11. data/lib/splash/cli/config.rb +9 -1
  12. data/lib/splash/cli/daemon.rb +49 -2
  13. data/lib/splash/cli/documentation.rb +5 -2
  14. data/lib/splash/cli/logs.rb +116 -47
  15. data/lib/splash/cli/process.rb +121 -52
  16. data/lib/splash/cli/sequences.rb +114 -0
  17. data/lib/splash/cli/transfers.rb +213 -0
  18. data/lib/splash/cli/webadmin.rb +46 -0
  19. data/lib/splash/commands.rb +111 -23
  20. data/lib/splash/config.rb +174 -23
  21. data/lib/splash/config/flush.rb +9 -1
  22. data/lib/splash/config/sanitycheck.rb +7 -2
  23. data/lib/splash/config/service.rb +8 -1
  24. data/lib/splash/config/setup.rb +8 -3
  25. data/lib/splash/constants.rb +37 -8
  26. data/lib/splash/daemon.rb +5 -1
  27. data/lib/splash/daemon/controller.rb +31 -11
  28. data/lib/splash/daemon/metrics.rb +29 -14
  29. data/lib/splash/daemon/orchestrator.rb +114 -35
  30. data/lib/splash/daemon/orchestrator/grammar.rb +40 -5
  31. data/lib/splash/daemon/orchestrator/hooks.rb +10 -0
  32. data/lib/splash/dependencies.rb +18 -1
  33. data/lib/splash/exiter.rb +14 -0
  34. data/lib/splash/helpers.rb +88 -46
  35. data/lib/splash/loggers.rb +30 -4
  36. data/lib/splash/loggers/cli.rb +18 -11
  37. data/lib/splash/loggers/daemon.rb +14 -3
  38. data/lib/splash/loggers/dual.rb +14 -1
  39. data/lib/splash/loggers/web.rb +51 -0
  40. data/lib/splash/logs.rb +105 -20
  41. data/lib/splash/processes.rb +104 -21
  42. data/lib/splash/sequences.rb +105 -0
  43. data/lib/splash/templates.rb +10 -0
  44. data/lib/splash/transfers.rb +229 -0
  45. data/lib/splash/transports.rb +9 -0
  46. data/lib/splash/transports/rabbitmq.rb +33 -4
  47. data/lib/splash/webadmin.rb +122 -0
  48. data/lib/splash/webadmin/api/routes/commands.rb +28 -0
  49. data/lib/splash/webadmin/api/routes/config.rb +10 -0
  50. data/lib/splash/webadmin/api/routes/init.rb +2 -0
  51. data/lib/splash/webadmin/api/routes/logs.rb +59 -0
  52. data/lib/splash/webadmin/api/routes/process.rb +60 -0
  53. data/lib/splash/webadmin/api/routes/sequences.rb +28 -0
  54. data/lib/splash/webadmin/main.rb +27 -0
  55. data/lib/splash/webadmin/portal/controllers/commands.rb +7 -0
  56. data/lib/splash/webadmin/portal/controllers/documentation.rb +6 -0
  57. data/lib/splash/webadmin/portal/controllers/home.rb +31 -0
  58. data/lib/splash/webadmin/portal/controllers/logs.rb +14 -0
  59. data/lib/splash/webadmin/portal/controllers/notfound.rb +5 -0
  60. data/lib/splash/webadmin/portal/controllers/processes.rb +14 -0
  61. data/lib/splash/webadmin/portal/controllers/proxy.rb +36 -0
  62. data/lib/splash/webadmin/portal/controllers/restclient.rb +19 -0
  63. data/lib/splash/webadmin/portal/controllers/sequences.rb +7 -0
  64. data/lib/splash/webadmin/portal/init.rb +11 -0
  65. data/lib/splash/webadmin/portal/public/css/ultragreen.css +8544 -0
  66. data/lib/splash/webadmin/portal/public/fonts/FontAwesome.otf +0 -0
  67. data/lib/splash/webadmin/portal/public/fonts/fontawesome-webfont.ttf +0 -0
  68. data/lib/splash/webadmin/portal/public/fonts/fontawesome-webfont.woff +0 -0
  69. data/lib/splash/webadmin/portal/public/fonts/fontawesome-webfont.woff2 +0 -0
  70. data/lib/splash/webadmin/portal/public/images/logo_splash.png +0 -0
  71. data/lib/splash/webadmin/portal/public/images/logo_splash_reduce.png +0 -0
  72. data/lib/splash/webadmin/portal/public/images/logo_splash_tiny.png +0 -0
  73. data/lib/splash/webadmin/portal/views/commands.slim +49 -0
  74. data/lib/splash/webadmin/portal/views/documentation.slim +3 -0
  75. data/lib/splash/webadmin/portal/views/home.slim +123 -0
  76. data/lib/splash/webadmin/portal/views/layout.slim +43 -0
  77. data/lib/splash/webadmin/portal/views/logs.slim +32 -0
  78. data/lib/splash/webadmin/portal/views/nav.slim +17 -0
  79. data/lib/splash/webadmin/portal/views/not_found.slim +3 -0
  80. data/lib/splash/webadmin/portal/views/processes.slim +29 -0
  81. data/lib/splash/webadmin/portal/views/proxy.slim +16 -0
  82. data/lib/splash/webadmin/portal/views/restclient.slim +41 -0
  83. data/lib/splash/webadmin/portal/views/restclient_result.slim +29 -0
  84. data/lib/splash/webadmin/portal/views/sequences.slim +50 -0
  85. data/prometheus-splash.gemspec +11 -2
  86. data/spec/helpers_spec.rb +119 -0
  87. metadata +191 -5
@@ -0,0 +1,105 @@
1
+ # coding: utf-8
2
+
3
+ # base Splash module
4
+ module Splash
5
+
6
+ # Splash Commands module/namespace
7
+ module Sequences
8
+
9
+ include Splash::Commands
10
+ include Splash::Config
11
+ include Splash::Loggers
12
+ include Splash::Exiter
13
+ include Splash::Transports
14
+
15
+ def run_seq(params = {})
16
+ list = get_config.sequences
17
+ name = params[:name].to_sym
18
+ acase = {}
19
+ seq_res = []
20
+ log = get_logger
21
+ log.info "beginnning Sequence execution : #{name}"
22
+ session = (params[:session])? params[:session] : get_session
23
+ if list.include? name then
24
+ seq_options = (list[name][:options].nil?)? {} : list[name][:options]
25
+ list[name][:definition].each do |step|
26
+ log.info "STEP : #{step[:step]}",session
27
+ if step[:on_host].nil? then
28
+ command = CommandWrapper::new(step[:command].to_s)
29
+ step[:callback] = true if step[:callback].nil?
30
+ step[:trace] = true if step[:trace].nil?
31
+ step[:notify] = true if step[:notify].nil?
32
+ step[:session] = session
33
+ acase = command.call_and_notify step
34
+ else
35
+ log.info "Remote execution of #{step[:command]} on #{step[:on_host]}", session
36
+ begin
37
+ transport = get_default_client
38
+ if transport.class == Hash and transport.include? :case then
39
+ return transport
40
+ else
41
+ acase = transport.execute({ :verb => :execute_command,
42
+ payload: {:name => step[:command].to_s},
43
+ :return_to => "splash.#{Socket.gethostname}.return",
44
+ :queue => "splash.#{step[:on_host]}.input" })
45
+ log.receive "return with exitcode #{acase[:exit_code]}", session
46
+ end
47
+ rescue Interrupt
48
+ acase = { case: :interrupt, more: "Remote command exection", exit_code: 33}
49
+ end
50
+
51
+ end
52
+ seq_res.push acase[:exit_code]
53
+ continue = (seq_options[:continue].nil?)? true : seq_options[:continue]
54
+ if acase[:exit_code] > 0 and continue == false then
55
+ acase[:more] = "Break execution on error, continue = false"
56
+ break
57
+ end
58
+ end
59
+ else
60
+ return { :case => :not_found, :more => "Sequence #{name} not configured" }
61
+ end
62
+
63
+ if seq_res.select {|res| res > 0}.count == seq_res.count then
64
+ acase = { case: :status_ko, more: "all execution failed" }
65
+ elsif seq_res.select {|res| res > 0}.count > 0 then
66
+ acase = { case: :status_ko, more: "some execution failed" }
67
+ else
68
+ acase = { case: :status_ok, more: "all execution successed" }
69
+ end
70
+ acase[:more] << " with remote call interrupt" if seq_res.include?(33)
71
+ return acase
72
+ end
73
+
74
+
75
+ def list_seq(options = {})
76
+ list = get_config.sequences
77
+ unless list.empty?
78
+ acase = { :case => :quiet_exit , :more => "List configured sequences"}
79
+ acase[:data] = list
80
+ return acase
81
+ else
82
+ return { :case => :not_found, :more => 'No sequences configured' }
83
+ end
84
+ end
85
+
86
+ def show_seq(options = {})
87
+ list = get_config.sequences
88
+ name = options[:name].to_sym
89
+ if list.include? name then
90
+ acase = { :case => :quiet_exit, :more => "Show specific sequence : #{name}"}
91
+ acase[:data] = list[name]
92
+ else
93
+ return { :case => :not_found, :more => "Sequence #{name} not configured" }
94
+ end
95
+ return acase
96
+ end
97
+
98
+ def schedule_seq(options = {})
99
+ acase = { :case => :quiet_exit, :more => "schedule" }
100
+ return acase
101
+
102
+ end
103
+
104
+ end
105
+ end
@@ -1,12 +1,19 @@
1
1
  # coding: utf-8
2
+
3
+ # base Splash Module
2
4
  module Splash
5
+
6
+ # Templates namespace
3
7
  module Templates
4
8
 
5
9
  # KISS template Engine
6
10
  class Template
7
11
 
12
+ # getter of the list of token
8
13
  attr_reader :list_token
14
+ # getter of the template file
9
15
  attr_reader :template_file
16
+ # getter of the flat content of the template
10
17
  attr_reader :content
11
18
 
12
19
  # constructor : generate the pseudo accessor for template Class from token list
@@ -72,8 +79,11 @@ module Splash
72
79
 
73
80
  end
74
81
 
82
+ # Exception for an invalid Token list
75
83
  class InvalidTokenList < Exception; end
84
+ # Exception for an malformed token
76
85
  class NotAToken < Exception; end
86
+ # Exception for an invalid template file
77
87
  class NoTemplateFile < Exception; end
78
88
 
79
89
  end
@@ -0,0 +1,229 @@
1
+ # coding: utf-8
2
+
3
+ # base Splash Module
4
+ module Splash
5
+
6
+ # Transfers module
7
+ module Transfers
8
+
9
+ include Splash::Config
10
+ include Splash::Loggers
11
+ include Splash::Helpers
12
+
13
+
14
+
15
+ class TxNotifier
16
+ @@registry = Prometheus::Client::Registry::new
17
+ @@metric_nbfiles = Prometheus::Client::Gauge.new(:txnbfiles, docstring: 'SPLASH metric transfer number of files')
18
+ @@metric_nbfiles_failed = Prometheus::Client::Gauge.new(:txnbfilesfailed, docstring: 'SPLASH metric transfer number of failed files')
19
+ @@metric_time = Prometheus::Client::Gauge.new(:txtime, docstring: 'SPLASH metric transfer execution time')
20
+ @@registry.register(@@metric_nbfiles)
21
+ @@registry.register(@@metric_nbfiles_failed)
22
+ @@registry.register(@@metric_time)
23
+
24
+ def initialize(options={})
25
+ @config = get_config
26
+ @url = @config.prometheus_pushgateway_url
27
+ @name = "tx_#{options[:name].to_s}"
28
+ @nbfiles = options[:nbfiles]
29
+ @nbfiles_failed = options[:nbfiles_failed]
30
+ @time = options[:time]
31
+ end
32
+
33
+ # send metrics to Prometheus PushGateway
34
+ # @return [Bool]
35
+ def notify
36
+ unless verify_service url: @url then
37
+ return { :case => :service_dependence_missing, :more => "Prometheus Notification not send."}
38
+ end
39
+ @@metric_nbfiles.set(@nbfiles)
40
+ @@metric_nbfiles_failed.set(@nbfiles_failed)
41
+ @@metric_time.set(@time)
42
+ hostname = Socket.gethostname
43
+ return Prometheus::Client::Push.new(@name, hostname, @url).add(@@registry)
44
+ end
45
+
46
+ end
47
+
48
+
49
+
50
+ class TxRecords
51
+ include Splash::Backends
52
+ include Splash::Constants
53
+
54
+ def initialize(name)
55
+ @name = name
56
+ @backend = get_backend :transfers_trace
57
+ end
58
+
59
+ def purge(retention)
60
+ retention = {} if retention.nil?
61
+ if retention.include? :hours then
62
+ adjusted_datetime = DateTime.now - retention[:hours].to_f / 24
63
+ elsif retention.include? :hours then
64
+ adjusted_datetime = DateTime.now - retention[:days].to_i
65
+ else
66
+ adjusted_datetime = DateTime.now - DEFAULT_RETENTION
67
+ end
68
+
69
+ data = get_all_records
70
+
71
+ data.delete_if { |item|
72
+ DateTime.parse(item.keys.first) <= (adjusted_datetime)}
73
+ @backend.put key: @name, value: data.to_yaml
74
+ end
75
+
76
+ def add_record(record)
77
+ data = get_all_records
78
+ data.push({ DateTime.now.to_s => record })
79
+ @backend.put key: @name, value: data.to_yaml
80
+ end
81
+
82
+ def get_all_records(options={})
83
+ return (@backend.exist?({key: @name}))? YAML::load(@backend.get({key: @name})) : []
84
+ end
85
+
86
+ def check_prepared
87
+ return :never_run_prepare unless @backend.exist?({key: @name})
88
+ return :never_prepare unless YAML::load(@backend.get({key: @name})).select {|item|
89
+ record =item.keys.first
90
+ value=item[record]
91
+ value[:status] == :prepared
92
+ }.count > 0
93
+ return :prepared
94
+ end
95
+
96
+ end
97
+
98
+ def run_txs(options = {})
99
+ log = get_logger
100
+ log.info 'Running Transfers'
101
+ count=0
102
+ get_config.transfers.each do |record|
103
+ txrec = TxRecords::new record[:name]
104
+ txrec.purge(record[:retention])
105
+ log.item "Execute : #{record[:name]}, #{record[:desc]}"
106
+ case txrec.check_prepared
107
+ when :prepared
108
+ if record[:type] == :push then
109
+ unless push record
110
+ count += 1
111
+ end
112
+ elsif record[:type] == :pull then
113
+ unless pull record
114
+ count += 1
115
+ end
116
+ else
117
+ log.ko "Transfer type unkown"
118
+ count += 1
119
+ end
120
+ when :never_prepare
121
+ log.ko "#{record[:name]} : Never prepared, ignored"
122
+ txrec.add_record :status => :never_prepared
123
+ count += 1
124
+ when :never_run_prepare
125
+ log.ko "#{record[:name]} : Never Executed and never prepared, ignored"
126
+ txrec.add_record :status => :never_prepared
127
+ count += 1
128
+ end
129
+ end
130
+ return {:case => :error_exit, :more => "#{count} Transfer(s) failed"} if count > 0
131
+ return {:case => :quiet_exit }
132
+ end
133
+
134
+
135
+
136
+ def prepare_tx(name)
137
+ log = get_logger
138
+ record = get_config.transfers.select { |item| item[:name] == name.to_sym }.first
139
+ home = Etc.getpwnam(record[:local][:user]).dir
140
+ identity = ::File::readlines("#{home}/.ssh/id_rsa.pub").first.chomp
141
+ folder = {:mode => "755",
142
+ :owner => record[:local][:user] ,
143
+ :group => Etc.getgrgid(Etc.getpwnam(record[:local][:user]).gid).name,
144
+ :name => record[:local][:path],
145
+ :path => record[:local][:path]}
146
+ log.info "Ensure local folder : #{record[:local][:path]}"
147
+ make_folder(folder) unless verify_folder(folder).empty?
148
+ begin
149
+ log.info "Ensure RSA Key sharing for local user : #{record[:local][:user]} to remote user : #{record[:remote][:user]}@#{record[:remote][:host]}"
150
+ ssh = Net::SSH.start(record[:remote][:host],record[:remote][:user])
151
+ output = ssh.exec!(%[
152
+ /bin/bash -cl '
153
+ umask 077;
154
+ mkdir #{record[:remote][:path]};
155
+ test -d ~/.ssh || mkdir ~/.ssh;
156
+ if [ ! -f ~/.ssh/authorized_keys -o `grep "#{identity}" ~/.ssh/authorized_keys 2> /dev/null | wc -l` -eq 0 ]; then echo "#{identity}" >> ~/.ssh/authorized_keys
157
+ fi'])
158
+ log.info "Prepare remote folder : #{record[:remote][:path]}"
159
+ log.info "Prepare data file for transfer : #{record[:name]}"
160
+ txrec = TxRecords::new record[:name]
161
+ txrec.add_record :status => :prepared
162
+ log.ok "Transfer : #{record[:name]} prepared successfully"
163
+ return {:case => :quiet_exit }
164
+ rescue Interrupt
165
+ splash_exit case: :interrupt, more: "Remote command exection"
166
+ rescue TTY::Reader::InputInterrupt
167
+ splash_exit case: :interrupt, more: "Remote command exection"
168
+ end
169
+ end
170
+
171
+
172
+
173
+ def save_data
174
+
175
+ end
176
+
177
+
178
+
179
+ def push(record)
180
+ config = get_config
181
+ log = get_logger
182
+ txrec = TxRecords::new record[:name]
183
+ start = Time.now
184
+ res = true
185
+ count = 0
186
+ done =[]
187
+ start_date = DateTime.now.to_s
188
+ list = Dir.glob("#{record[:local][:path]}/#{record[:pattern]}")
189
+ count = list.count
190
+ log.arrow "Transfering #{count} file(s)"
191
+
192
+ begin
193
+ scp = Net::SCP.start(record[:remote][:host],record[:remote][:user])
194
+ list.each do|f|
195
+ log.arrow "Copy file : #{f} to #{record[:remote][:user]}@#{record[:remote][:host]}:#{record[:remote][:path]}"
196
+ scp.upload! f, record[:remote][:path]
197
+ done.push f
198
+ if record[:backup] then
199
+ log.arrow "File #{f} backuped"
200
+ FileUtils::mv(f, "#{f}.#{Time.now.getutc.to_i}")
201
+ else
202
+ FileUtils::unlink(f)
203
+ end
204
+ end
205
+
206
+ rescue
207
+ res = false
208
+ end
209
+
210
+ end_date = DateTime.now.to_s
211
+ time = Time.now - start
212
+ status = (res)? :success : :failure
213
+ txrec.add_record :status => status,
214
+ :end_date => end_date,
215
+ :time => time,
216
+ :count => count,
217
+ :wanted => list,
218
+ :done => done
219
+ count_failed = list.count - done.count
220
+ txmonitor = TxNotifier::new({name: record[:name], nbfiles: count,nbfiles_failed: count_failed, time: time})
221
+ if txmonitor.notify then
222
+ log.ok "Sending metrics to Prometheus Pushgateway"
223
+ else
224
+ log.ko "Failed to send metrics to Prometheus Pushgateway"
225
+ end
226
+ return res
227
+ end
228
+ end
229
+ end
@@ -1,9 +1,16 @@
1
1
  # coding: utf-8
2
2
 
3
+ # base Splash module
3
4
  module Splash
5
+
6
+ # Splash Transports namespace
4
7
  module Transports
5
8
  include Splash::Config
6
9
 
10
+
11
+ # factory for Splash::Transports::Rabbitmq::Subscriber
12
+ # @param [Hash] options
13
+ # @return [Splash::Transports::Rabbitmq::Subscriber|Hash] Subscriber or Exiter case :configuration_error
7
14
  def get_default_subscriber(options)
8
15
  config = get_config.transports
9
16
  transport = config[:active]
@@ -20,6 +27,8 @@ module Splash
20
27
  end
21
28
  end
22
29
 
30
+ # factory for Splash::Transports::Rabbitmq::Client
31
+ # @return [Splash::Transports::Rabbitmq::Client|Hash] Client or Exiter case :configuration_error
23
32
  def get_default_client
24
33
  config = get_config.transports
25
34
  transport = config[:active]
@@ -1,14 +1,24 @@
1
1
  # coding: utf-8
2
+
3
+ # base Splash module
2
4
  module Splash
5
+
6
+ # Splash Transports namespace
3
7
  module Transports
8
+
9
+ # RabbitMQ Transport
4
10
  module Rabbitmq
5
11
 
12
+ # Subscriber Mode RabbitMQ Client
6
13
  class Subscriber
7
14
  include Splash::Config
8
15
  extend Forwardable
9
16
 
10
17
  def_delegators :@queue, :subscribe
11
18
 
19
+ # Constructor Forward subscribe method and initialize a Bunny Client atribute @queue
20
+ # @param [Hash] options
21
+ # @option options [String] :queue the name of the subscribed queue
12
22
  def initialize(options = {})
13
23
  @config = get_config.transports
14
24
 
@@ -32,12 +42,13 @@ module Splash
32
42
 
33
43
  end
34
44
 
35
-
45
+ # publish / get Mode RabbitMQ Client
36
46
  class Client
37
47
  include Splash::Config
38
48
  include Splash::Transports
39
49
  include Splash::Loggers
40
50
 
51
+ # Constructor initialize a Bunny Client
41
52
  def initialize
42
53
  @config = get_config.transports
43
54
  host = @config[:rabbitmq][:host]
@@ -48,7 +59,7 @@ module Splash
48
59
  conf = { :host => host, :vhost => vhost, :user => user, :password => passwd, :port => port.to_i}
49
60
 
50
61
  begin
51
- @connection = Bunny.new conf
62
+ @connection = Bunny.new conf
52
63
  @connection.start
53
64
  @channel = @connection.create_channel
54
65
  rescue Bunny::Exception
@@ -56,19 +67,32 @@ module Splash
56
67
  end
57
68
  end
58
69
 
59
-
70
+ # purge a queue
71
+ # @param [Hash] options
72
+ # @option options [String] :queue the name of the queue to purge
60
73
  def purge(options)
61
74
  @channel.queue(options[:queue]).purge
62
75
  end
63
76
 
77
+ # publish to a queue
78
+ # @param [Hash] options
79
+ # @option options [String] :queue the name of the queue to purge
80
+ # @option options [String] :message the message to send
64
81
  def publish(options ={})
65
82
  return @channel.default_exchange.publish(options[:message], :routing_key => options[:queue])
66
83
  end
67
84
 
85
+ # ack a specific message for manual ack with a delivery tag to a queue
86
+ # @param [String] ack
87
+ # @return [Boolean]
68
88
  def ack(ack)
69
89
  return @channel.acknowledge(ack, false)
70
90
  end
71
91
 
92
+
93
+ # send an execution order message (verb+payload) via RabbitMQ to an slash input queue
94
+ # @param [Hash] order
95
+ # @return [Void] unserialized Void object from YAML
72
96
  def execute(order)
73
97
  queue = order[:return_to]
74
98
  lock = Mutex.new
@@ -76,7 +100,6 @@ module Splash
76
100
  condition = ConditionVariable.new
77
101
  get_default_subscriber(queue: queue).subscribe do |delivery_info, properties, payload|
78
102
  res = YAML::load(payload)
79
-
80
103
  lock.synchronize { condition.signal }
81
104
  end
82
105
  get_logger.send "Verb : #{order[:verb].to_s} to queue : #{order[:queue]}."
@@ -85,6 +108,11 @@ module Splash
85
108
  return res
86
109
  end
87
110
 
111
+ # Get a message from a RabbitMQ queue
112
+ # @param [Hash] options
113
+ # @option options [String] :queue the name of the queue to query
114
+ # @option options [String] :manual_ack flag to inhibit ack
115
+ # @return [Hash] Payload + ack tag if :manual_ack
88
116
  def get(options ={})
89
117
  queue = @channel.queue(options[:queue])
90
118
  opt = {}; opt[:manual_ack] = (options[:manual_ack])? true : false
@@ -94,6 +122,7 @@ module Splash
94
122
  return res
95
123
  end
96
124
 
125
+ # close the RabbitMQ connection
97
126
  def close
98
127
  @connection.close
99
128
  end