pushyd 0.9.4 → 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0f6680e8b9c8639d2230c50187affeda2816b658
4
- data.tar.gz: c896cb6178136eb5293a8cf0922b06364bb82f27
3
+ metadata.gz: f28f30eb8e374a96c94d9db2e3211cb96e130805
4
+ data.tar.gz: 2b3f74b4cf520b66847c1626c44e3257e6310a49
5
5
  SHA512:
6
- metadata.gz: 0d9a4132921a47b2f909bd915213c9bf48606769ba8bd0d854da8c6936fc982e9bb5fb6c9e072be7ad63a0a3fc4172e0eeffc1fbc65cff8e61ecc9f53716d673
7
- data.tar.gz: 9ecfa2313ed02c2df6c8285b4da4b9adc5905ef10200aad5bb62ec30272fdb71a9b3808c59308de94178848a33e3fe4041897c0556fecbfa32310c56675a3805
6
+ metadata.gz: 20bb1cc397e0d112988e0f8988645a553eefb19c288c81a49d531e4fc644607a45a160c3db0ff77c76a2e2e13666d877a44072e585baaf4553120ab6b56def2d
7
+ data.tar.gz: 167aa4659b654a46195671d00d46d6a205b19fb528aac492d6c0b8cc12ad4b8580b02975b9365eee082780785e574e8601dd26a01f8c386629a42a3f69ad9b92
data/Gemfile.lock CHANGED
@@ -1,14 +1,15 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pushyd (0.9.4)
4
+ pushyd (0.20.0)
5
5
  api-auth
6
- bmc-daemon-lib (~> 0.2)
6
+ bmc-daemon-lib (~> 0.3.14)
7
7
  bunny (~> 2.3)
8
8
  daemons
9
9
  json
10
10
  newrelic_rpm
11
11
  rest-client (~> 1.8)
12
+ rollbar
12
13
  terminal-table
13
14
 
14
15
  GEM
@@ -16,21 +17,21 @@ GEM
16
17
  specs:
17
18
  addressable (2.4.0)
18
19
  amq-protocol (2.0.1)
19
- api-auth (2.0.0)
20
+ api-auth (2.0.1)
20
21
  ast (2.3.0)
21
- bmc-daemon-lib (0.2.0)
22
+ bmc-daemon-lib (0.3.14)
22
23
  chamber (~> 2.9)
23
- bunny (2.5.0)
24
+ bunny (2.5.1)
24
25
  amq-protocol (>= 2.0.1)
25
- chamber (2.9.0)
26
+ chamber (2.9.1)
26
27
  hashie (~> 3.3)
27
28
  thor (~> 0.19.1)
28
- daemons (1.2.3)
29
+ daemons (1.2.4)
29
30
  diff-lcs (1.2.5)
30
- domain_name (0.5.20160615)
31
+ domain_name (0.5.20160826)
31
32
  unf (>= 0.0.5, < 1.0.0)
32
33
  hashie (3.4.4)
33
- http (2.0.2)
34
+ http (2.0.3)
34
35
  addressable (~> 2.3)
35
36
  http-cookie (~> 1.0)
36
37
  http-form_data (~> 1.0.1)
@@ -39,10 +40,11 @@ GEM
39
40
  domain_name (~> 0.5)
40
41
  http-form_data (1.0.1)
41
42
  http_parser.rb (0.6.0)
42
- json (2.0.1)
43
- mime-types (2.99.2)
43
+ json (2.0.2)
44
+ mime-types (2.99.3)
45
+ multi_json (1.12.1)
44
46
  netrc (0.11.0)
45
- newrelic_rpm (3.16.0.318)
47
+ newrelic_rpm (3.16.2.321)
46
48
  parser (2.3.1.2)
47
49
  ast (~> 2.2)
48
50
  powerpack (0.1.1)
@@ -52,11 +54,13 @@ GEM
52
54
  http-cookie (>= 1.0.2, < 2.0)
53
55
  mime-types (>= 1.16, < 3.0)
54
56
  netrc (~> 0.7)
57
+ rollbar (2.12.0)
58
+ multi_json
55
59
  rspec (3.5.0)
56
60
  rspec-core (~> 3.5.0)
57
61
  rspec-expectations (~> 3.5.0)
58
62
  rspec-mocks (~> 3.5.0)
59
- rspec-core (3.5.1)
63
+ rspec-core (3.5.3)
60
64
  rspec-support (~> 3.5.0)
61
65
  rspec-expectations (3.5.0)
62
66
  diff-lcs (>= 1.2.0, < 2.0)
@@ -65,19 +69,20 @@ GEM
65
69
  diff-lcs (>= 1.2.0, < 2.0)
66
70
  rspec-support (~> 3.5.0)
67
71
  rspec-support (3.5.0)
68
- rubocop (0.41.2)
72
+ rubocop (0.42.0)
69
73
  parser (>= 2.3.1.1, < 3.0)
70
74
  powerpack (~> 0.1)
71
75
  rainbow (>= 1.99.1, < 3.0)
72
76
  ruby-progressbar (~> 1.7)
73
77
  unicode-display_width (~> 1.0, >= 1.0.1)
74
78
  ruby-progressbar (1.8.1)
75
- terminal-table (1.6.0)
79
+ terminal-table (1.7.2)
80
+ unicode-display_width (~> 1.1.1)
76
81
  thor (0.19.1)
77
82
  unf (0.1.4)
78
83
  unf_ext
79
84
  unf_ext (0.0.7.2)
80
- unicode-display_width (1.1.0)
85
+ unicode-display_width (1.1.1)
81
86
 
82
87
  PLATFORMS
83
88
  ruby
@@ -91,4 +96,4 @@ DEPENDENCIES
91
96
  rubocop
92
97
 
93
98
  BUNDLED WITH
94
- 1.11.2
99
+ 1.12.5
data/bin/pushyd CHANGED
@@ -17,6 +17,7 @@ begin
17
17
  # Defaults
18
18
  cmd_config = nil
19
19
  cmd_logfile = nil
20
+ cmd_dump = nil
20
21
 
21
22
  # Init Chamber-based configuration from Gemspec
22
23
  Conf.init File.dirname(__FILE__) + "/../"
@@ -25,10 +26,11 @@ begin
25
26
  # Parse options and check compliance
26
27
  parser = OptionParser.new do |opts|
27
28
  opts.banner = "Usage: #{File.basename $PROGRAM_NAME} [options] start|stop"
28
- opts.on("-l", "--log LOGFILE") { |path| cmd_logfile = File.expand_path(path.to_s)}
29
- opts.on("-c", "--config CONFIGFILE") { |path| cmd_config = File.expand_path(path.to_s)}
30
- opts.on("-e", "--environment ENV") { |env| Conf.app_env = env }
31
- opts.on("", "--dev") { Conf.app_env = "development" }
29
+ opts.on("-l", "--log LOGFILE") { |path| cmd_logfile = File.expand_path(path.to_s)}
30
+ opts.on("-c", "--config CONFIGFILE") { |path| cmd_config = File.expand_path(path.to_s)}
31
+ opts.on("-e", "--environment ENV") { |env| Conf.app_env = env }
32
+ opts.on("", "--dev") { Conf.app_env = "development" }
33
+ opts.on("", "--dump", "Dump config as seen by the process") { |value| cmd_dump = true }
32
34
  end
33
35
  parser.order!(ARGV)
34
36
 
@@ -55,8 +57,12 @@ puts "Config files \t #{Conf.files}"
55
57
  puts "Started at \t #{Conf.app_started}"
56
58
  puts "Loging to file \t #{Conf[:log][:file]}" if Conf[:log].is_a? Enumerable
57
59
  puts "Process name \t #{Conf.generate(:process_name)}"
58
- puts
59
- puts Conf.dump
60
+ puts "Newrelic \t #{Conf.feature?(:newrelic) || '-'}"
61
+ puts "Rollbar \t #{Conf.feature?(:rollbar) || '-'}"
62
+ if cmd_dump
63
+ puts
64
+ puts Conf.dump
65
+ end
60
66
 
61
67
  # Run daemon
62
68
  run_options = {
data/defaults.yml CHANGED
@@ -1,10 +1,14 @@
1
1
  # common defaults
2
2
  broker: amqp://guest:guest@localhost:5672/%2F
3
+
3
4
  logs:
4
- path: '/tmp/'
5
- file: pushyd.log
6
- newrelic: newrelic.log
7
- level: debug
5
+ path: "/tmp/"
6
+ level: debug
7
+ default: "pushyd-default.log"
8
+ shouter: "pushyd-shouter.log"
9
+ consumer: "pushyd-consumer.log"
10
+ # newrelic: "pushyd-newrelic.log"
11
+ # rollbar: "pushyd-rollbar.log"
8
12
 
9
13
  shout:
10
14
  topic: pushyd
@@ -1,29 +1,25 @@
1
1
  # Constants: global
2
2
  MSG_SEND = "SEND"
3
3
  MSG_RECV = "RECV"
4
- WAY_PROP = "PROP"
4
+ MSG_RLAY = "RLAY"
5
5
 
6
- # Constants: proxy
7
- PROXY_MESSAGE_MAX = 1
8
- PROXY_USE_ACK = false
6
+ # Constants: AMQP protocol
7
+ AMQP_HEARTBEAT_INTERVAL = 30
8
+ AMQP_RECOVERY_INTERVAL = 5
9
+ AMQP_PREFETCH = 3
10
+ AMQP_MANUAL_ACK = false
9
11
 
10
12
  # Constants: shouter
11
13
  SHOUTER_SENTAT_DECIMALS = 6
12
14
 
13
15
  # Constants: logger
14
- LOG_ROTATION = "daily"
15
-
16
16
  LOG_HEADER_TIME = "%Y-%m-%d %H:%M:%S"
17
17
  LOG_HEADER_FORMAT = "%s \t%d\t%-8s %-15s "
18
- LOG_MESSAGE_TRIM = 200
18
+ LOG_MESSAGE_TRIM = 250
19
19
  LOG_MESSAGE_TEXT = "%s%s"
20
20
  LOG_MESSAGE_ARRAY = "%s %s"
21
21
  LOG_MESSAGE_HASH = "%s %-20s %s\n"
22
22
 
23
23
  # Constants: logger app-specific prefix
24
- LOG_PREFIX_FORMAT = nil
25
-
26
- # Constants: AMQP protocol
27
- AMQP_HEARTBEAT_INTERVAL = 30
28
- AMQP_RECOVERY_INTERVAL = 5
29
- AMQP_PREFETCH = 2
24
+ #LOG_PREFIX_FORMAT = "(%-12s) (%-12s)"
25
+ LOG_PREFIX_FORMAT = "%-20s "
@@ -0,0 +1,144 @@
1
+ module PushyDaemon
2
+ # class ShouterResponseError < StandardError; end
3
+ # class ShouterChannelClosed < StandardError; end
4
+ # class ShouterPreconditionFailed < StandardError; end
5
+ # class ShouterInterrupted < StandardError; end
6
+ class ConsumerError < StandardError; end
7
+ class ConsumerRuleMissing < StandardError; end
8
+
9
+ class Consumer < BmcDaemonLib::MqConsumer
10
+ #include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
11
+ include Shared::HmacSignature
12
+ attr_accessor :logger
13
+
14
+ def initialize(conn, rule_name, rule)
15
+ # Init
16
+ @queue = nil
17
+ @conn = conn
18
+ @rule = rule
19
+ @rule_name = rule_name
20
+
21
+ # Prepare logger
22
+ @logger = BmcDaemonLib::LoggerPool.instance.get :consumer
23
+
24
+ # Create channel, prefetch only one message at a time
25
+ @channel = @conn.create_channel
26
+ @channel.prefetch(AMQP_PREFETCH)
27
+
28
+ # OK
29
+ log_info "Consumer initialized"
30
+ end
31
+
32
+ protected
33
+
34
+ def log_prefix
35
+ [@rule_name]
36
+ end
37
+
38
+ # Handle the reception of a message on a queue
39
+ def handle_message context, metadata, delivery_info, message = {}
40
+ # Prepare data
41
+ headers = metadata.headers || {}
42
+
43
+ # Relay data if needed
44
+ handle_relay context, message, headers
45
+
46
+ # Handle errors and acknowledgments
47
+ # log_debug "handle_message : channel[#{@channel.inspect}]"
48
+ rescue StandardError => e
49
+ log_error "handle_message: unknown: #{e.message}, #{e.inspect}", e.backtrace
50
+ channel_ackit(message[:tag], false)
51
+ else
52
+ channel_ackit(message[:tag], true)
53
+ end
54
+
55
+ private
56
+
57
+ def handle_relay context, message, headers
58
+ # Check we have a valid @rule
59
+ raise ConsumerRuleMissing unless @rule.is_a? Hash
60
+
61
+ # Check if we need to relay anything
62
+ unless @rule[:relay]
63
+ log_debug "handle_relay: no [relay] URL"
64
+ return
65
+ end
66
+
67
+ # Prepare stuff
68
+ relay_auth = @rule[:auth].to_s
69
+ relay_url = URI(@rule[:relay]).to_s
70
+ request_id = identifier(6)
71
+ request_prefix = "handle_relay [#{request_id}] "
72
+
73
+ # Build payload
74
+ request_infos = {
75
+ topic: message[:topic],
76
+ route: message[:rkey],
77
+ sent_at: headers['sent_at'],
78
+ sent_by: headers['sent_by'],
79
+ context: context,
80
+ data: message[:data],
81
+ }
82
+ request_body = JSON.pretty_generate(request_infos)
83
+
84
+ # Build request headers
85
+ headers = {
86
+ content_type: :json,
87
+ accept: :json,
88
+ user_agent: BmcDaemonLib::Conf.generate(:user_agent),
89
+ }
90
+
91
+ # Compute: payload MD5, HMAC signature
92
+ headers_md5 headers, request_body
93
+ headers_sign headers, @rule[:sign], [:date]
94
+
95
+ # Build final request
96
+ request = RestClient::Request.new url: relay_url,
97
+ method: :post,
98
+ payload: request_body,
99
+ headers: headers
100
+
101
+ # Execute request
102
+ log_message MSG_RLAY, request_id, relay_url, request_infos, request.processed_headers
103
+ response = request.execute
104
+
105
+ # Handle exceptions
106
+ rescue RestClient::ExceptionWithResponse, URI::InvalidURIError, RestClient::InternalServerError => e
107
+ log_error "#{request_prefix} rest-client exception: #{e.message}"
108
+ rescue ApiAuth::ApiAuthError, ApiAuth::UnknownHTTPRequest => e
109
+ log_error "#{request_prefix} api-auth: #{e.message}"
110
+ rescue Errno::ECONNREFUSED => e
111
+ log_error "#{request_prefix} connection refused: #{e.message}"
112
+ rescue StandardError => e
113
+ log_error "#{request_prefix} unknown: #{e.message}, #{e.inspect}", e.backtrace
114
+ else
115
+ log_info "#{request_prefix} received [#{response.body}]"
116
+ end
117
+
118
+ def channel_ackit tag, success=true
119
+ # log_debug "channel_ackit[#{channel}.#{tag}] #{@channel.inspect}"
120
+ # if success
121
+ # log_debug "channel_ackit[#{@channel.id}.#{tag}]: ACK"
122
+ # @channel.ack(tag)
123
+ # else
124
+ # log_debug "channel_ackit[#{@channel.id}.#{tag}]: NACK"
125
+ # @channel.nack(tag)
126
+ # end
127
+
128
+ # rescue Bunny::ChannelAlreadyClosed => ex
129
+ # error "channel_ackit[#{@channel.id}.#{tag}]: exception: ChannelAlreadyClosed"
130
+
131
+ # rescue StandardError => ex
132
+ # log_debug "channel_ackit[#{@channel.id}.#{tag}]: exception: #{ex.inspect}"
133
+ # # fail PushyDaemon::EndpointSubscribeError, "unhandled (#{e.inspect})"
134
+
135
+ # else
136
+ # log_debug "channel_ackit[#{@channel.id}.#{tag}]: done"
137
+ end
138
+
139
+ # NewRelic instrumentation
140
+ #add_transaction_tracer :receive, category: :task
141
+ #add_transaction_tracer :propagate, category: :task
142
+
143
+ end
144
+ end
data/lib/pushyd/daemon.rb CHANGED
@@ -2,20 +2,12 @@ module PushyDaemon
2
2
  class Daemon
3
3
 
4
4
  def self.run
5
- # Create a new proxy
6
- p = Proxy.new
7
-
8
- # Dump config table
9
- puts p.table.to_s
10
-
11
- # Create a new shouter
12
- s = Shouter.new
13
-
14
- # Start shout loop
15
- s.shout
5
+ # Create a new proxy, and dump its configuration
6
+ Proxy.new
16
7
 
17
8
  # Backup infinite loop in case shout does nothing
18
9
  loop do
10
+ sleep 1
19
11
  end
20
12
 
21
13
  rescue EndpointConnectionError, ShouterInterrupted => e
@@ -0,0 +1,3 @@
1
+ # Init Rollbar and Newrelic
2
+ Conf.prepare_newrelic
3
+ Conf.prepare_rollbar
data/lib/pushyd/proxy.rb CHANGED
@@ -1,19 +1,19 @@
1
+ require "bunny"
1
2
  require 'api_auth'
2
3
  require 'rest_client'
3
4
  require 'terminal-table'
4
5
 
5
6
  module PushyDaemon
6
- class Proxy < Endpoint
7
- include Shared::HmacSignature
8
- include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
7
+ class Proxy < BmcDaemonLib::MqEndpoint
8
+ #include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
9
9
 
10
10
  # Class options
11
11
  attr_accessor :table
12
12
 
13
13
  def initialize
14
14
  # Init
15
- super
16
- @exchanges = {}
15
+ @shouters = []
16
+ @consumers = []
17
17
 
18
18
  # Init ASCII table
19
19
  @table = Terminal::Table.new
@@ -21,155 +21,87 @@ module PushyDaemon
21
21
  @table.headings = ["rule", "topic", "route", "relay", "created queue", "description"]
22
22
  @table.align_column(5, :right)
23
23
 
24
- # Start connexion to RabbitMQ and create channel
25
- @channel = connect_channel Conf[:broker]
26
- log_info "channel connected"
24
+ # Prepare logger
25
+ @logger = BmcDaemonLib::LoggerPool.instance.get
27
26
 
28
- # Check config
29
- config_rules = Conf[:rules]
30
- if config_rules.nil? || !config_rules.is_a?(Hash)
31
- log_error "prepare: empty [rules] section"
32
- else
33
- log_info "found rules: #{config_rules.keys.join(', ')}"
34
-
35
- # Subsribe for each and every rule/route
36
- config_rules.each do |name, rule|
37
- rule[:name] = name
38
- channel_subscribe rule
39
- end
40
- end
27
+ # Start connexion to RabbitMQ
28
+ @conn = connect_to BmcDaemonLib::Conf[:broker]
29
+ log_info "Proxy connected"
30
+
31
+ # Check config and subscribe rules
32
+ create_consumers
33
+
34
+ # Create a new shouter, and start its loop
35
+ create_shouter
41
36
 
42
37
  # Send config table to logs
43
- log_info "proxy initialized", @table.to_s.lines
38
+ log_info "Proxy initialized", @table.to_s.lines
44
39
  end
45
40
 
46
41
  protected
47
42
 
48
- private
43
+ def log_prefix
44
+ [nil]
45
+ # [self.class.name.split('::').last, :proxy]
46
+ end
49
47
 
50
- def extract_delay msg_headers
51
- return unless msg_headers['sent_at']
48
+ def create_shouter
49
+ # Get config
50
+ config_shouter = BmcDaemonLib::Conf[:shout]
52
51
 
53
- # Extract sent_at header
54
- sent_at = Time.iso8601(msg_headers['sent_at']) rescue nil
55
- # log_info "sent_at : #{sent_at.to_f}"
56
- # log_info "timenow : #{Time.now.to_f}"
52
+ # Create the shouter
53
+ shouter = Shouter.new(@conn, config_shouter)
54
+ @shouters << shouter
57
55
 
58
- # Compute delay
59
- return ((Time.now - sent_at)*1000).round(2)
56
+ # Now make it loop
57
+ shouter.start_loop
60
58
  end
61
59
 
60
+ def create_consumers
61
+ # Get config
62
+ config_rules = BmcDaemonLib::Conf[:rules]
63
+ log_info "create_consumers: #{config_rules.keys.join(', ')}"
62
64
 
63
- # Handle the reception of a message on a queue
64
- def handle_message rule, delivery_info, metadata, payload
65
- # Prepare data
66
- rule_name = rule[:name]
67
- msg_exchange = delivery_info.exchange
68
- msg_rkey = delivery_info.routing_key.force_encoding('UTF-8')
69
- msg_headers = metadata.headers || {}
70
-
71
- # Compute delay and size
72
- delay = extract_delay(msg_headers)
73
- size = format_bytes(payload.bytesize, "B")
74
-
75
- # Extract payload
76
- data = parse payload, metadata.content_type
77
-
78
- # Announce match
79
- log_message MSG_RECV, msg_exchange, msg_rkey, data, {
80
- 'matched rule' => rule_name,
81
- 'app-id' => metadata.app_id,
82
- 'content-type' => metadata.content_type,
83
- 'delay (ms)' => delay,
84
- 'body size' => size,
85
- }
86
-
87
- # Build notification payload
88
- propagate_data = {
89
- exchange: msg_exchange,
90
- route: msg_rkey,
91
- sent_at: msg_headers['sent_at'],
92
- sent_by: msg_headers['sent_by'],
93
- data: data,
94
- }
95
-
96
- # Propagate data if needed
97
- propagate rule, propagate_data
98
- end
65
+ if config_rules.nil? || !config_rules.is_a?(Hash)
66
+ log_error "prepare: no rules"
67
+ return
68
+ end
99
69
 
100
- def propagate rule, data
101
- # Nothing more to do if no relay
102
- return if rule[:relay].nil? || rule[:relay].empty?
103
-
104
- # Prepare stuff
105
- relay_auth = rule[:auth].to_s
106
- relay_uri = URI(rule[:relay])
107
- relay_url = relay_uri.to_s
108
- id = identifier(6)
109
- # log_info "propagate: user[#{relay_uri.user}] url[#{relay_url}]"
110
-
111
- # Build POST body and log message
112
- post_body = JSON.pretty_generate(data)
113
- log_message WAY_PROP, id, relay_url, data
114
-
115
- # Prepare request
116
- request = RestClient::Request.new url: relay_url,
117
- method: :post,
118
- payload: post_body,
119
- headers: {
120
- content_type: :json,
121
- accept: :json,
122
- user_agent: Conf.generate(:user_agent),
123
- }
124
-
125
- # Compute payload MD5
126
- headers_md5 request
127
-
128
- # Compute HMAC signature
129
- headers_sign request, rule['hmac-method'], rule['hmac-user'], rule['hmac-secret'], [:date]
130
-
131
- # Send request
132
- log_info "propagate: #{relay_url}", request.headers
133
- response = request.execute
134
-
135
- # Handle exceptions
136
- rescue RestClient::ExceptionWithResponse, URI::InvalidURIError => e
137
- log_error "propagate: rest-client: #{e.message}"
138
- rescue RestClient::InternalServerError => e
139
- log_error "propagate: rest-client: #{e.message}"
140
- rescue ApiAuth::ApiAuthError, ApiAuth::UnknownHTTPRequest => e
141
- log_error "propagate: api-auth: #{e.message}"
142
- rescue Errno::ECONNREFUSED => e
143
- log_error "propagate: connection refused: #{e.message}"
144
- rescue StandardError => e
145
- log_error "propagate: unknown: #{e.message}, #{e.inspect}", e.backtrace
146
- else
147
- log_info "propagate: #{response.body}"
70
+ # Subscribe for each and every rule/route
71
+ config_rules.each do |name, rule|
72
+ rule[:name] = name
73
+ create_consumer rule
74
+ end
148
75
  end
149
76
 
150
- def parse payload, content_type #, fields = []
151
- # Force encoding (pftop...)
152
- utf8payload = payload.to_s.force_encoding('UTF-8')
153
-
154
- # Parse payload if content-type provided
155
- case content_type
156
- when "application/json"
157
- return JSON.parse utf8payload rescue nil
158
- when "text/plain"
159
- return utf8payload.to_s
160
- else
161
- return utf8payload
77
+ # Subscribe to interesting topic/routes and bind a listenner
78
+ def create_consumer rule
79
+ # Check information
80
+ rule_name = rule[:name].to_s
81
+ rule_topic = rule[:topic].to_s
82
+ rule_routes = rule[:routes].to_s.split(' ')
83
+ rule_queue = sprintf('%s-%s', BmcDaemonLib::Conf.app_name, rule_name.gsub('_', '-'))
84
+
85
+ fail PushyDaemon::EndpointSubscribeContext, "rule [#{rule_name}] lacking topic" unless rule_topic
86
+ fail PushyDaemon::EndpointSubscribeContext, "rule [#{rule_name}] lacking routes" if rule_routes.empty?
87
+
88
+ # Build a new consumer
89
+ consumer = Consumer.new(@conn, rule_name, rule)
90
+
91
+ # Create its own queue
92
+ consumer.subscribe_to_queue rule_queue, "rule:#{rule_name}"
93
+
94
+ # Bind each route to exchange
95
+ rule_routes.each do |route|
96
+ consumer.listen_to rule_topic, route
97
+
98
+ # Add row to config table
99
+ @table.add_row [rule_name, rule_topic, route, rule[:relay].to_s, rule_queue, rule[:title].to_s ]
162
100
  end
163
101
 
164
- # Handle body parse errors
165
- rescue Encoding::UndefinedConversionError => e
166
- log_error "parse: JSON PARSE ERROR: #{e.inspect}"
167
- return {}
102
+ # Return it
103
+ @consumers << consumer
168
104
  end
169
105
 
170
- # NewRelic instrumentation
171
- add_transaction_tracer :handle_message, category: :task
172
- add_transaction_tracer :propagate, category: :task
173
-
174
106
  end
175
107
  end
@@ -5,63 +5,55 @@ module PushyDaemon
5
5
  class ShouterInterrupted < StandardError; end
6
6
  class EndpointTopicContext < StandardError; end
7
7
 
8
- class Shouter < Endpoint
8
+ class Shouter < BmcDaemonLib::MqEndpoint
9
9
  include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
10
+ include BmcDaemonLib::LoggerHelper
10
11
 
11
12
  # Class options
12
13
  attr_accessor :table
14
+ attr_accessor :logger
13
15
 
14
- def initialize
16
+ def initialize(conn, config_shout)
15
17
  # Init
16
- super
17
- @keys = []
18
+ @shouter_keys = []
19
+ @channel = nil
20
+ @exchange = nil
21
+
22
+ # Prepare logger
23
+ @logger = BmcDaemonLib::LoggerPool.instance.get :shouter
18
24
 
19
25
  # Check config
20
- config_shout = Conf[:shout]
21
26
  unless config_shout && config_shout.any? && config_shout.is_a?(Enumerable)
22
27
  log_error "prepare: empty [shout] section"
23
28
  return
24
29
  end
25
30
 
26
31
  # Extract information
27
- @keys = config_shout[:keys] if config_shout[:keys].is_a? Array
28
- @topic = config_shout[:topic]
29
- @period = config_shout[:period] || 0
32
+ @shouter_keys = config_shout[:keys] if config_shout[:keys].is_a? Array
33
+ @shouter_topic = config_shout[:topic]
34
+ @shouter_period = config_shout[:period].to_i
35
+ @shouter_period = 1 unless (@shouter_period > 0)
30
36
 
31
- # Start connexion to RabbitMQ and create channel
32
- @channel = connect_channel Conf[:broker]
33
- log_info "channel connected"
37
+ fail PushyDaemon::EndpointTopicContext unless @shouter_topic
34
38
 
35
- # Create exchange
36
- fail PushyDaemon::EndpointTopicContext unless @topic
37
- @exchange = @channel.topic(@topic, durable: true, persistent: true)
39
+ # Create channel and exchange
40
+ @channel = conn.create_channel
41
+ @exchange = @channel.topic(@shouter_topic, durable: true, persistent: true)
38
42
 
39
- # Send shouter info to logs
40
- shouter_info = { topic: @topic, period: @period, keys: @keys }
41
- log_info "shouter initialized", shouter_info
43
+ # Start working, now
44
+ log_info "Shouter initialized, starting loop now", { topic: @shouter_topic, period: @shouter_period, keys: @shouter_keys }
42
45
  end
43
46
 
44
- def shout
45
- return unless @exchange
46
-
47
+ def start_loop
47
48
  # Prepare exchange
48
49
  loop do
49
- # Generate key and fake id
50
- if @keys.is_a?(Array) && @keys.any?
51
- random_key = @keys.sample
52
- else
53
- random_key = "random"
54
- end
55
- # random_key = @keys.class
56
- random_string = SecureRandom.hex
57
-
58
50
  # Generate payload
59
- #payload = {time: Time.now.to_f, host: Conf.host}
60
- payload = nil
51
+ payload = {time: Time.now.to_f, host: BmcDaemonLib::Conf.host}
52
+ # payload = nil
61
53
 
62
54
  # Shout it !
63
- channel_shout [:ping, random_key, random_string], payload
64
- sleep @period
55
+ exchange_shout @exchange, payload
56
+ sleep @shouter_period
65
57
  end
66
58
  rescue AMQ::Protocol::EmptyResponseError => e
67
59
  fail PushyDaemon::ShouterResponseError, "#{e.class} (#{e.inspect})"
@@ -76,35 +68,43 @@ module PushyDaemon
76
68
 
77
69
  protected
78
70
 
71
+ def log_prefix
72
+ [nil]
73
+ # [self.class.name.split('::').last, :shouter]
74
+ end
75
+
79
76
  private
80
77
 
81
- def channel_shout keys, body = {}
82
- # Prepare exchange_name and routing_key
83
- exchange_name = @exchange.name
84
- keys.unshift(exchange_name)
78
+ def exchange_shout exchange, body = {}
79
+ # Prepare routing_key
80
+ keys = []
81
+ keys << @shouter_topic
82
+ keys << SecureRandom.hex
83
+ keys << @shouter_keys.sample if (@shouter_keys.is_a?(Array) && @shouter_keys.any?)
85
84
  routing_key = keys.join('.')
86
85
 
87
86
  # Announce shout
88
- log_message MSG_SEND, exchange_name, routing_key, body
87
+ log_message MSG_SEND, @shouter_topic, routing_key, body
89
88
 
90
89
  # Prepare headers
90
+ app_id = "#{BmcDaemonLib::Conf.app_name}/#{BmcDaemonLib::Conf.app_ver}"
91
91
  headers = {
92
92
  sent_at: DateTime.now.iso8601(SHOUTER_SENTAT_DECIMALS),
93
- sent_by: Conf.app_name,
93
+ sent_by: app_id,
94
94
  }
95
95
 
96
96
  # Publish
97
- @exchange.publish(body.to_json,
97
+ exchange.publish(body.to_json,
98
98
  routing_key: routing_key,
99
99
  headers: headers,
100
- app_id: Conf.app_name,
100
+ app_id: app_id,
101
101
  content_type: "application/json",
102
102
  )
103
103
  end
104
104
 
105
105
  # NewRelic instrumentation
106
- add_transaction_tracer :channel_shout, category: :task
107
- add_transaction_tracer :shout, category: :task
106
+ #add_transaction_tracer :exchange_shout, category: :task
107
+ # add_transaction_tracer :shout, category: :task
108
108
 
109
109
  end
110
110
  end
data/lib/pushyd.rb CHANGED
@@ -4,13 +4,18 @@ require 'bmc-daemon-lib'
4
4
  require "yaml"
5
5
  require "json"
6
6
  require "newrelic_rpm"
7
+ require "rollbar"
8
+
7
9
 
8
10
  # Shared libs
9
11
  require_relative "shared/hmac_signature"
10
12
 
11
13
  # Project libs
12
14
  require_relative "pushyd/constants"
13
- require_relative "pushyd/endpoint"
14
15
  require_relative "pushyd/proxy"
16
+ require_relative "pushyd/consumer"
15
17
  require_relative "pushyd/shouter"
16
18
  require_relative "pushyd/daemon"
19
+
20
+ # Init
21
+ require_relative "pushyd/initialize"
@@ -4,21 +4,33 @@ require 'base64'
4
4
  module Shared
5
5
  module HmacSignature
6
6
 
7
- def headers_sign request, hmac_method, hmac_user, hmac_secret, names = ['date']
8
- return unless hmac_user
9
- unless hmac_secret && hmac_method
10
- log_error "headers_sign: hmac: missing secret or method"
7
+ def headers_sign headers, config, names = ['date']
8
+ # Extract and check
9
+ return unless config.is_a? Hash
10
+ hmac_method = config[:method]
11
+ hmac_user = config[:user]
12
+ hmac_secret = config[:secret]
13
+ #log_debug "headers_sign config", config
14
+
15
+ # Check params
16
+ unless config[:method] && config[:user] && config[:secret]
17
+ log_error "headers_sign: missing method/user/secret"
18
+ return
19
+ end
20
+
21
+ # Check params
22
+ unless config[:method] == 'hmac-kong'
23
+ log_error "headers_sign: only [hmac-kong] method is supported"
11
24
  return
12
25
  end
13
26
 
14
27
  # OK, lets go
15
- log_info "headers_sign: before: user[#{hmac_user}] secret[#{hmac_secret}] method[#{hmac_method}]", request.headers
16
- hmac_sign_kong request.headers, hmac_user, hmac_secret, names
17
- log_info "headers_sign: after:", request.headers
28
+ hmac_sign_kong headers, config[:user], config[:secret], names
29
+ # log_info "headers_sign: after signing", headers
18
30
  end
19
31
 
20
- def headers_md5 request
21
- request.headers['Content-MD5'] = Digest::MD5.hexdigest(request.payload.to_s)
32
+ def headers_md5 headers, payload
33
+ headers['Content-MD5'] = Digest::MD5.hexdigest(payload.to_s)
22
34
  end
23
35
 
24
36
  private
@@ -33,18 +45,17 @@ module Shared
33
45
  myheaders = hmac_headers_filter headers, names
34
46
 
35
47
  # Signe string of headers
36
- headers_signature = hmac_headers_hash myheaders, client_secret
37
- log_debug "hmac_sign_kong #{myheaders.keys.inspect} #{headers_signature}"
48
+ signature = hmac_headers_hash myheaders, client_secret
49
+ log_debug "hmac_sign_kong signed [#{signature}] from headers #{myheaders.keys.inspect}"
38
50
 
39
51
  # Add auth header
40
- # headers['Authorization'] = hmac_build_header(client_id, myheaders, headers_signature)
41
- headers['test'] = "testing123"
52
+ headers['Authorization'] = hmac_build_header(client_id, myheaders, signature)
53
+ #headers['test'] = "testing123"
42
54
 
43
55
  # That's OK
44
56
  return headers
45
57
  end
46
58
 
47
-
48
59
  def hmac_build_header client_id, myheaders, signature
49
60
  sprintf 'hmac username="%s", algorithm="hmac-sha1", headers="%s", signature="%s"',
50
61
  client_id,
data/pushyd.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  # coding: utf-8
2
2
  Gem::Specification.new do |spec|
3
3
  # Project version
4
- spec.version = "0.9.4"
4
+ spec.version = "0.20.0"
5
5
 
6
6
  # Project description
7
7
  spec.name = "pushyd"
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
28
28
  # spec.add_development_dependency "pry"
29
29
 
30
30
  # Runtime dependencies
31
- spec.add_runtime_dependency "bmc-daemon-lib", "~> 0.2"
31
+ spec.add_runtime_dependency "bmc-daemon-lib", "~> 0.3.14"
32
32
  spec.add_runtime_dependency "daemons"
33
33
  spec.add_runtime_dependency "json"
34
34
  spec.add_runtime_dependency "bunny", "~> 2.3"
@@ -36,4 +36,5 @@ Gem::Specification.new do |spec|
36
36
  spec.add_runtime_dependency "api-auth"
37
37
  spec.add_runtime_dependency "terminal-table"
38
38
  spec.add_runtime_dependency "newrelic_rpm"
39
+ spec.add_runtime_dependency "rollbar"
39
40
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pushyd
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.4
4
+ version: 0.20.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bruno MEDICI
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-21 00:00:00.000000000 Z
11
+ date: 2016-09-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -86,14 +86,14 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '0.2'
89
+ version: 0.3.14
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '0.2'
96
+ version: 0.3.14
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: daemons
99
99
  requirement: !ruby/object:Gem::Requirement
@@ -192,6 +192,20 @@ dependencies:
192
192
  - - ">="
193
193
  - !ruby/object:Gem::Version
194
194
  version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: rollbar
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ type: :runtime
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
195
209
  description: A nice proxy listenning to a RabbitMQ bus, repeating selected messages
196
210
  in POST requests when filters match routing patterns
197
211
  email: pushyd@bmconseil.com
@@ -210,8 +224,9 @@ files:
210
224
  - defaults.yml
211
225
  - lib/pushyd.rb
212
226
  - lib/pushyd/constants.rb
227
+ - lib/pushyd/consumer.rb
213
228
  - lib/pushyd/daemon.rb
214
- - lib/pushyd/endpoint.rb
229
+ - lib/pushyd/initialize.rb
215
230
  - lib/pushyd/proxy.rb
216
231
  - lib/pushyd/shouter.rb
217
232
  - lib/shared/hmac_signature.rb
@@ -1,137 +0,0 @@
1
- require 'bunny'
2
- require "securerandom"
3
-
4
- module PushyDaemon
5
- # Class exceptions
6
- class EndpointConnexionContext < StandardError; end
7
- class EndpointConnectionError < StandardError; end
8
- class EndpointSubscribeContext < StandardError; end
9
- class EndpointSubscribeError < StandardError; end
10
-
11
- class Endpoint
12
- include BmcDaemonLib::LoggerHelper
13
- attr_reader :logger
14
-
15
- def initialize
16
- # Prepare logger
17
- @logger = BmcDaemonLib::LoggerPool.instance.get :file
18
-
19
- # Done
20
- log_info "endpoint initialized"
21
- end
22
-
23
- protected
24
-
25
- def log_message msg_way, msg_exchange, msg_key, msg_body = [], msg_attrs = {}
26
- # Message header
27
- info sprintf("%3s %-15s %s", msg_way, msg_exchange, msg_key)
28
-
29
- # Body lines
30
- if msg_body.is_a?(Enumerable) && !msg_body.empty?
31
- body_json = JSON.pretty_generate(msg_body)
32
- log_debug nil, body_json.lines
33
- end
34
-
35
- # Attributes lines
36
- log_debug nil, msg_attrs if msg_attrs
37
- end
38
-
39
- # Start connexion to RabbitMQ
40
- def connect_channel busconf
41
- fail PushyDaemon::EndpointConnexionContext, "invalid bus host/port" unless busconf
42
- info "connecting to bus", {
43
- broker: busconf,
44
- recover: AMQP_RECOVERY_INTERVAL,
45
- heartbeat: AMQP_HEARTBEAT_INTERVAL,
46
- prefetch: AMQP_PREFETCH
47
- }
48
- conn = Bunny.new busconf.to_s,
49
- logger: @logger,
50
- # heartbeat: :server,
51
- automatically_recover: true,
52
- network_recovery_interval: AMQP_RECOVERY_INTERVAL,
53
- heartbeat_interval: AMQP_HEARTBEAT_INTERVAL
54
- conn.start
55
-
56
- # Create channel, prefetch only one message at a time
57
- channel = conn.create_channel
58
- channel.prefetch(AMQP_PREFETCH)
59
-
60
- rescue Bunny::TCPConnectionFailedForAllHosts, Bunny::AuthenticationFailureError, AMQ::Protocol::EmptyResponseError => e
61
- fail PushyDaemon::EndpointConnectionError, "error connecting (#{e.class})"
62
- rescue StandardError => e
63
- fail PushyDaemon::EndpointConnectionError, "unknow (#{e.inspect})"
64
- else
65
- return channel
66
- end
67
-
68
- # Declare or return the exchange for this topic
69
- def channel_exchange topic
70
- @exchanges ||= {}
71
- @exchanges[topic] ||= @channel.topic(topic, durable: true, persistent: true)
72
- end
73
-
74
- # Subscribe to interesting topic/routes and bind a listenner
75
- def channel_subscribe rule
76
- # Check information
77
- rule_name = rule[:name].to_s
78
- rule_topic = rule[:topic].to_s
79
- rule_routes = rule[:routes].to_s.split(' ')
80
- rule_queue = "#{Conf.app_name}-#{rule[:name]}"
81
- fail PushyDaemon::EndpointSubscribeContext, "rule [#{rule_name}] lacking topic" unless rule_topic
82
- fail PushyDaemon::EndpointSubscribeContext, "rule [#{rule_name}] lacking routes" if rule_routes.empty?
83
-
84
- # Create queue for this rule (remove it beforehand)
85
- #conn.create_channel.queue_delete(rule_queue_name)
86
- queue = @channel.queue(rule_queue, auto_delete: false, durable: true)
87
-
88
- # Bind each route from this topic-exchange
89
- topic_exchange = channel_exchange(rule_topic)
90
- rule_routes.each do |route|
91
- # Bind exchange to queue
92
- queue.bind topic_exchange, routing_key: route
93
- info "subscribe: bind [#{rule_topic}/#{route}] \t> #{rule_queue}"
94
-
95
- # Add row to config table
96
- # ["rule", "topic", "route", "relay", "queue", "description"]
97
- @table.add_row [rule_name, rule_topic, route, rule[:relay].to_s, rule_queue, rule[:title].to_s ]
98
- end
99
-
100
- # Subscribe to our new queue
101
- queue.subscribe(block: false, manual_ack: PROXY_USE_ACK, message_max: PROXY_MESSAGE_MAX) do |delivery_info, metadata, payload|
102
-
103
- # Handle the message
104
- handle_message rule, delivery_info, metadata, payload
105
-
106
- end
107
-
108
- rescue Bunny::PreconditionFailed => e
109
- fail PushyDaemon::EndpointSubscribeError, "PreconditionFailed: [#{rule_topic}] code(#{e.channel_close.reply_code}) message(#{e.channel_close.reply_text})"
110
-
111
- rescue StandardError => e
112
- fail PushyDaemon::EndpointSubscribeError, "unhandled (#{e.inspect})"
113
-
114
- end
115
-
116
- def handle_message rule, delivery_info, metadata, payload
117
- end
118
-
119
- def identifier len
120
- rand(36**len).to_s(36)
121
- end
122
-
123
- def format_bytes number, unit="", decimals = 0
124
- return "&Oslash;" if number.nil? || number.to_f.zero?
125
-
126
- units = ["", "k", "M", "G", "T", "P" ]
127
- index = ( Math.log(number) / Math.log(2) ).to_i / 10
128
- converted = number.to_f / (1024 ** index)
129
-
130
- truncated = converted.round(decimals)
131
- return "#{truncated} #{units[index]}#{unit}"
132
- end
133
-
134
- private
135
-
136
- end
137
- end