pushyd 0.0.2 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/Gemfile.lock +3 -3
- data/bin/pushyd.rb +25 -18
- data/lib/pushyd/config.rb +53 -37
- data/lib/pushyd/constants.rb +13 -0
- data/lib/pushyd/daemon.rb +4 -6
- data/lib/pushyd/endpoint.rb +126 -0
- data/lib/pushyd/proxy.rb +38 -168
- data/lib/pushyd/shouter.rb +101 -0
- data/lib/pushyd.rb +4 -2
- data/pushyd.gemspec +3 -3
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 838aaf0ca927c20acf4d53714ff5eaa11499716a
|
4
|
+
data.tar.gz: e481c674723737cf9b8b6f84b38e57c8e6b39804
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e7cad3480998b54851e30573b0e1f8229c9c2ccd50359436b11b236ccccbd6fc2817e7a7bcecbcedbff0538366efa5814c61aeefccedc3d1d2408c2e07104fcb
|
7
|
+
data.tar.gz: 37cc4b0233a4da19f28d3c2a52cce679e4e12129b282bdb3d514ceee0a1ee5dac3a40b051bb5f783f20aee425eab2a0fa4b06003234030ad037e16dec4fe9813
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
pushyd (0.
|
4
|
+
pushyd (0.1.1)
|
5
5
|
bunny
|
6
6
|
chamber
|
7
7
|
daemons
|
@@ -10,14 +10,14 @@ PATH
|
|
10
10
|
terminal-table
|
11
11
|
|
12
12
|
GEM
|
13
|
-
remote:
|
13
|
+
remote: https://rubygems.org/
|
14
14
|
specs:
|
15
15
|
addressable (2.4.0)
|
16
16
|
amq-protocol (2.0.1)
|
17
17
|
ast (2.2.0)
|
18
18
|
astrolabe (1.3.1)
|
19
19
|
parser (~> 2.2)
|
20
|
-
bunny (2.
|
20
|
+
bunny (2.3.1)
|
21
21
|
amq-protocol (>= 2.0.1)
|
22
22
|
chamber (2.9.0)
|
23
23
|
hashie (~> 3.3)
|
data/bin/pushyd.rb
CHANGED
@@ -10,15 +10,18 @@ begin
|
|
10
10
|
rescue LoadError
|
11
11
|
raise "EXITING: some basic libs were not found"
|
12
12
|
end
|
13
|
+
include PushyDaemon
|
13
14
|
|
14
|
-
# Guess app root
|
15
|
-
APP_ROOT = File.expand_path(File.dirname(__FILE__) + "/../")
|
16
15
|
|
17
|
-
#
|
18
|
-
|
19
|
-
cmd_env = "production"
|
20
|
-
cmd_dump = false
|
16
|
+
# Handle configuration
|
17
|
+
APP_ROOT = File.expand_path(File.dirname(__FILE__) + "/../")
|
21
18
|
begin
|
19
|
+
# Defaults
|
20
|
+
cmd_config = nil
|
21
|
+
cmd_env = "production"
|
22
|
+
cmd_dump = false
|
23
|
+
|
24
|
+
# Parse options and check compliance
|
22
25
|
OptionParser.new do |opts|
|
23
26
|
opts.banner = "Usage: #{File.basename $PROGRAM_NAME} [options] start|stop"
|
24
27
|
opts.on("-c", "--config CONFIGFILE") { |config| cmd_config = File.expand_path(config)}
|
@@ -26,21 +29,25 @@ begin
|
|
26
29
|
opts.on("-d", "--dump") { cmd_dump = true }
|
27
30
|
opts.on("", "--dev") { cmd_env = "development" }
|
28
31
|
end.order!(ARGV)
|
29
|
-
rescue OptionParser::InvalidOption => e
|
30
|
-
abort "EXITING: option parser: #{e.message}"
|
31
|
-
end
|
32
32
|
|
33
|
+
# Build Chamber-based configuration from Gemspec with initial context
|
34
|
+
Config.prepare root: APP_ROOT, gemspec: "pushyd", env: cmd_env, config: cmd_config
|
33
35
|
|
34
|
-
#
|
35
|
-
Config.
|
36
|
+
# Display final configuration
|
37
|
+
puts "--- #{Config.name} #{Config.version}"
|
38
|
+
puts "Environment \t #{Config.env}"
|
39
|
+
puts "Config files \t #{Config.files}"
|
40
|
+
puts
|
41
|
+
puts "Log file \t #{Config[:log]}"
|
42
|
+
puts Config.dump if cmd_dump
|
36
43
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
+
rescue OptionParser::InvalidOption => e
|
45
|
+
abort "EXITING: option parser: #{e.message}"
|
46
|
+
rescue PushyDaemon::ConfigParseError => e
|
47
|
+
abort "EXITING: ConfigParseError: #{e.message}"
|
48
|
+
rescue Exception => e
|
49
|
+
abort "EXITING: Exception: #{e.message}"
|
50
|
+
end
|
44
51
|
|
45
52
|
|
46
53
|
# Run daemon
|
data/lib/pushyd/config.rb
CHANGED
@@ -1,43 +1,59 @@
|
|
1
1
|
require "chamber"
|
2
2
|
|
3
|
-
|
4
|
-
extend Chamber
|
5
|
-
|
6
|
-
class << self
|
7
|
-
attr_reader :name
|
8
|
-
attr_reader :spec
|
9
|
-
attr_reader :files
|
10
|
-
attr_reader :version
|
11
|
-
attr_reader :env
|
12
|
-
end
|
3
|
+
module PushyDaemon
|
13
4
|
|
14
|
-
|
15
|
-
|
16
|
-
raise "config: missing root" unless (@root = args[:root])
|
17
|
-
raise "config: missing env" unless (@env = args[:env])
|
18
|
-
|
19
|
-
# Gemspec parameter
|
20
|
-
gemspec_path = "#{args[:root]}/#{args[:gemspec]}.gemspec"
|
21
|
-
raise "config: missing gemspec" unless args[:gemspec]
|
22
|
-
raise "config: missing gemspec file at #{gemspec_path}" unless File.exist?(gemspec_path)
|
23
|
-
|
24
|
-
# Load Gemspec
|
25
|
-
@spec = Gem::Specification::load gemspec_path
|
26
|
-
@name = @spec.name
|
27
|
-
@version = @spec.version
|
28
|
-
raise "config: missing name" unless @name
|
29
|
-
|
30
|
-
# Init Chamber (defaults, etc, cmdline)
|
31
|
-
@files = ["#{args[:root]}/defaults.yml"]
|
32
|
-
@files << File.expand_path("/etc/#{@name}.yml")
|
33
|
-
@files << args[:config].to_s if args[:config]
|
34
|
-
|
35
|
-
# Load configuration files
|
36
|
-
load files: @files, namespaces: { environment: @env }
|
37
|
-
end
|
5
|
+
class ConfigMissingParameter < StandardError; end
|
6
|
+
class ConfigParseError < StandardError; end
|
38
7
|
|
39
|
-
|
40
|
-
|
41
|
-
|
8
|
+
class Config
|
9
|
+
extend Chamber
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_reader :name
|
13
|
+
attr_reader :spec
|
14
|
+
attr_reader :files
|
15
|
+
attr_reader :version
|
16
|
+
attr_reader :env
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.prepare args = {}
|
20
|
+
# Context parameters
|
21
|
+
raise PushyDaemon::ConfigMissingParameter, "missing root" unless (@root = args[:root])
|
22
|
+
raise PushyDaemon::ConfigMissingParameter, "missing env" unless (@env = args[:env])
|
23
|
+
|
24
|
+
# Gemspec parameter
|
25
|
+
gemspec_path = "#{args[:root]}/#{args[:gemspec]}.gemspec"
|
26
|
+
raise PushyDaemon::ConfigMissingParameter, "missing gemspec" unless args[:gemspec]
|
27
|
+
raise PushyDaemon::ConfigMissingParameter, "gemspec file not found: #{gemspec_path}" unless File.exist?(gemspec_path)
|
28
|
+
|
29
|
+
# Load Gemspec
|
30
|
+
@spec = Gem::Specification::load gemspec_path
|
31
|
+
@name = @spec.name
|
32
|
+
@version = @spec.version
|
33
|
+
raise PushyDaemon::ConfigMissingParameter, "missing name" unless @name
|
34
|
+
|
35
|
+
# Init Chamber (defaults, etc, cmdline)
|
36
|
+
@files = ["#{args[:root]}/defaults.yml"]
|
37
|
+
@files << File.expand_path("/etc/#{@name}.yml")
|
38
|
+
@files << args[:config].to_s if args[:config]
|
42
39
|
|
40
|
+
# Load configuration files
|
41
|
+
load files: @files, namespaces: { environment: @env }
|
42
|
+
|
43
|
+
# Try to access any key to force parsing of the files
|
44
|
+
self[:dummy]
|
45
|
+
|
46
|
+
rescue Psych::SyntaxError => e
|
47
|
+
raise PushyDaemon::ConfigParseError, e.message
|
48
|
+
|
49
|
+
rescue Exception => e
|
50
|
+
raise PushyDaemon::ConfigParseError, e.message
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.dump
|
55
|
+
self.to_hash.to_yaml
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
43
59
|
end
|
data/lib/pushyd/daemon.rb
CHANGED
@@ -5,16 +5,14 @@ module PushyDaemon
|
|
5
5
|
# Create a new proxy
|
6
6
|
p = Proxy.new(logger)
|
7
7
|
|
8
|
-
# Prepare subscriptions
|
9
|
-
p.prepare
|
10
|
-
|
11
|
-
# Make it listen
|
12
|
-
|
13
8
|
# Dump config table
|
14
9
|
puts p.table.to_s
|
15
10
|
|
11
|
+
# Create a new shouter
|
12
|
+
s = Shouter.new(logger)
|
13
|
+
|
16
14
|
# Start infinite loop
|
17
|
-
|
15
|
+
s.shout
|
18
16
|
end
|
19
17
|
|
20
18
|
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'bunny'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
module PushyDaemon
|
5
|
+
|
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
|
+
|
13
|
+
def initialize(logger)
|
14
|
+
@logger = logger
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def abort message
|
20
|
+
@logger.error "ABORT #{self.class}: #{message}"
|
21
|
+
raise "ABORT #{self.class}: #{message}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def info message
|
25
|
+
@logger.info "#{self.class}: #{message}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def message params = {}
|
29
|
+
# Indenting
|
30
|
+
indent = " " * (params[:way].length)
|
31
|
+
|
32
|
+
# Header
|
33
|
+
@logger.info sprintf(
|
34
|
+
"%3s %-15s %s",
|
35
|
+
params[:way],
|
36
|
+
params[:exchange],
|
37
|
+
params[:key]
|
38
|
+
)
|
39
|
+
|
40
|
+
# Attributes
|
41
|
+
if (params[:attrs].is_a? Hash)
|
42
|
+
params[:attrs].each do |name, value|
|
43
|
+
@logger.info sprintf("%s %-15s %s", indent, name, value)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Body (split in lines to log them separately)
|
48
|
+
unless (params[:body].nil? || params[:body].empty?)
|
49
|
+
JSON.pretty_generate(params[:body]).each_line do |line|
|
50
|
+
@logger.info sprintf("%s %s", indent, line.rstrip)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Start connexion to RabbitMQ
|
56
|
+
def connect busconf
|
57
|
+
raise PushyDaemon::EndpointConnexionContext, "invalid bus host/port" unless (busconf.is_a? Hash) &&
|
58
|
+
busconf[:host] && busconf[:port]
|
59
|
+
|
60
|
+
puts "connecting to #{busconf[:host]} port #{busconf[:port]}"
|
61
|
+
conn = Bunny.new host: (busconf[:host].to_s || "localhost").to_s,
|
62
|
+
port: busconf[:port].to_i,
|
63
|
+
user: busconf[:user].to_s,
|
64
|
+
pass: busconf[:pass].to_s,
|
65
|
+
heartbeat: :server
|
66
|
+
conn.start
|
67
|
+
rescue Bunny::TCPConnectionFailedForAllHosts, Bunny::AuthenticationFailureError, AMQ::Protocol::EmptyResponseError => e
|
68
|
+
raise PushyDaemon::EndpointConnectionError, "error connecting (#{e.class})"
|
69
|
+
rescue Exception => e
|
70
|
+
raise PushyDaemon::EndpointConnectionError, "unknow (#{e.inspect})"
|
71
|
+
else
|
72
|
+
return conn
|
73
|
+
end
|
74
|
+
|
75
|
+
# Declare or return the exchange for this topic
|
76
|
+
def channel_exchange topic
|
77
|
+
@exchanges ||= {}
|
78
|
+
@exchanges[topic] ||= @channel.topic(topic, durable: true, persistent: true)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Subscribe to interesting topic/routes and bind a listenner
|
82
|
+
def channel_subscribe rule
|
83
|
+
# Check information
|
84
|
+
rule_name = rule[:name].to_s
|
85
|
+
rule_topic = rule[:topic].to_s
|
86
|
+
rule_routes = rule[:routes].to_s.split(' ')
|
87
|
+
rule_queue = "#{Config.name}-#{PROXY_SCOPE}-#{rule[:name]}"
|
88
|
+
raise PushyDaemon::EndpointSubscribeContext, "rule [#{rule_name}] lacking topic" unless rule_topic
|
89
|
+
raise PushyDaemon::EndpointSubscribeContext, "rule [#{rule_name}] lacking routes" if rule_routes.empty?
|
90
|
+
|
91
|
+
# Create queue for this rule (remove it beforehand)
|
92
|
+
#conn.create_channel.queue_delete(rule_queue_name)
|
93
|
+
queue = @channel.queue(rule_queue, auto_delete: false, durable: true)
|
94
|
+
|
95
|
+
# Bind each route from this topic-exchange
|
96
|
+
topic_exchange = channel_exchange(rule_topic)
|
97
|
+
rule_routes.each do |route|
|
98
|
+
# Bind exchange to queue
|
99
|
+
queue.bind topic_exchange, routing_key: route
|
100
|
+
info "subscribe: bind [#{rule_topic}/#{route}] \t> #{rule_queue}"
|
101
|
+
|
102
|
+
# Add row to config table
|
103
|
+
@table.add_row [rule_name, rule_topic, route, rule[:relay].to_s, rule[:title].to_s ]
|
104
|
+
end
|
105
|
+
|
106
|
+
# Subscribe to our new queue
|
107
|
+
queue.subscribe(block: false, manual_ack: PROXY_USE_ACK, message_max: PROXY_MESSAGE_MAX) do |delivery_info, metadata, payload|
|
108
|
+
|
109
|
+
# Handle the message
|
110
|
+
handle_message rule, delivery_info, metadata, payload
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
rescue Bunny::PreconditionFailed => e
|
115
|
+
raise PushyDaemon::EndpointSubscribeError, "PreconditionFailed: [#{rule_topic}] code(#{e.channel_close.reply_code}) message(#{e.channel_close.reply_text})"
|
116
|
+
|
117
|
+
rescue Exception => e
|
118
|
+
raise PushyDaemon::EndpointSubscribeError, "unhandled (#{e.inspect})"
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
def handle_message rule, delivery_info, metadata, payload
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
data/lib/pushyd/proxy.rb
CHANGED
@@ -1,25 +1,18 @@
|
|
1
1
|
require 'rest_client'
|
2
|
-
require 'bunny'
|
3
2
|
require 'yaml'
|
4
3
|
require 'json'
|
5
4
|
require 'terminal-table'
|
6
5
|
|
7
|
-
|
8
|
-
PROXY_MESSAGE_MAX = 1
|
9
|
-
PROXY_USE_ACK = false
|
10
|
-
PROXY_SCOPE = "dev"
|
11
|
-
# PROXY_IDENT = "proxy"
|
12
|
-
# QUEUE_HOST = `hostname`.to_s.chomp
|
13
|
-
# SEPARATOR = "="*160
|
14
|
-
# ACK_PERCENT = 50
|
6
|
+
module PushyDaemon
|
15
7
|
|
8
|
+
# class ProxyConnexionContext < StandardError; end
|
16
9
|
|
17
|
-
|
18
|
-
class Proxy
|
10
|
+
class Proxy < Endpoint
|
19
11
|
|
20
12
|
attr_accessor :table
|
21
13
|
|
22
14
|
def initialize(logger)
|
15
|
+
# Init
|
23
16
|
@exchanges = {}
|
24
17
|
@logger = logger
|
25
18
|
|
@@ -28,123 +21,41 @@ module PushyDaemon
|
|
28
21
|
@table.title = "Propagation rules"
|
29
22
|
@table.headings = ["queue binding", "topic", "route", "relay", "title"]
|
30
23
|
@table.align_column(5, :right)
|
31
|
-
end
|
32
24
|
|
33
|
-
def prepare
|
34
25
|
# Start connexion to RabbitMQ and create channel
|
35
26
|
conn = connect Config.bus
|
36
27
|
@channel = conn.create_channel
|
37
|
-
info "
|
28
|
+
info "connected on a channel"
|
38
29
|
|
39
|
-
# Check
|
40
|
-
|
30
|
+
# Check config
|
31
|
+
config_rules = Config[:rules]
|
32
|
+
unless (config_rules.is_a? Enumerable) && !config_rules.empty?
|
41
33
|
abort "prepare: empty [rules] section"
|
42
34
|
end
|
43
|
-
info "
|
35
|
+
info "found rules: #{config_rules.keys.join(', ')}"
|
44
36
|
|
45
37
|
# Subsribe for each and every rule/route
|
46
|
-
|
38
|
+
config_rules.each do |name, rule|
|
47
39
|
rule[:name] = name
|
48
40
|
channel_subscribe rule
|
49
41
|
#abort "prepare: OK"
|
50
42
|
end
|
51
43
|
|
52
44
|
# Send config table to logs
|
53
|
-
info "
|
54
|
-
end
|
45
|
+
info "dumping configuration\n#{@table.to_s}"
|
55
46
|
|
56
|
-
|
57
|
-
|
58
|
-
info "ping"
|
59
|
-
sleep(1)
|
60
|
-
end
|
47
|
+
rescue Bunny::TCPConnectionFailedForAllHosts => e
|
48
|
+
abort "ERROR: cannot connect to RabbitMQ hosts (#{e.inspect})"
|
61
49
|
end
|
62
50
|
|
63
51
|
private
|
64
52
|
|
65
|
-
def abort message
|
66
|
-
@logger.error "ABORT: #{message}"
|
67
|
-
raise "ABORT: #{message}"
|
68
|
-
end
|
69
|
-
|
70
|
-
def info message
|
71
|
-
@logger.info message
|
72
|
-
end
|
73
|
-
|
74
|
-
def dump_rules rules
|
75
|
-
|
76
|
-
end
|
77
|
-
|
78
|
-
# Start connexion to RabbitMQ
|
79
|
-
def connect busconf
|
80
|
-
abort "connect: bus host/port not found" unless busconf.is_a? Hash
|
81
|
-
|
82
|
-
puts "connecting to #{busconf[:host]} port #{busconf[:port]}"
|
83
|
-
conn = Bunny.new host: (busconf[:host].to_s || "localhost").to_s,
|
84
|
-
port: busconf[:port].to_i,
|
85
|
-
user: busconf[:user].to_s,
|
86
|
-
pass: busconf[:pass].to_s,
|
87
|
-
heartbeat: :server
|
88
|
-
conn.start
|
89
|
-
rescue Bunny::TCPConnectionFailedForAllHosts, Bunny::AuthenticationFailureError, AMQ::Protocol::EmptyResponseError => e
|
90
|
-
abort "connect: error connecting to RabbitMQ (#{e.class})"
|
91
|
-
rescue Exception => e
|
92
|
-
abort "connect: unknow connection error (#{e.inspect})"
|
93
|
-
else
|
94
|
-
return conn
|
95
|
-
end
|
96
|
-
|
97
|
-
# Declare or return the exchange for this topic
|
98
|
-
def channel_exchange topic
|
99
|
-
@exchanges ||= {}
|
100
|
-
@exchanges[topic] ||= @channel.topic(topic, durable: true, persistent: true)
|
101
|
-
end
|
102
|
-
|
103
|
-
# Subscribe to interesting topic/routes and bind a listenner
|
104
|
-
def channel_subscribe rule
|
105
|
-
# Check information
|
106
|
-
rule_name = rule[:name].to_s
|
107
|
-
rule_topic = rule[:topic].to_s
|
108
|
-
rule_routes = rule[:routes].to_s.split(' ')
|
109
|
-
rule_queue = "#{Config.name}-#{PROXY_SCOPE}-#{rule[:name]}"
|
110
|
-
abort "subscribe: rule [#{rule_name}] lacking topic" unless rule_topic
|
111
|
-
abort "subscribe: rule [#{rule_name}] lacking routes" if rule_routes.empty?
|
112
|
-
|
113
|
-
# Create queue for this rule (remove it beforehand)
|
114
|
-
#conn.create_channel.queue_delete(rule_queue_name)
|
115
|
-
queue = @channel.queue(rule_queue, auto_delete: false, durable: true)
|
116
|
-
|
117
|
-
# Bind each route from this topic-exchange
|
118
|
-
topic_exchange = channel_exchange(rule_topic)
|
119
|
-
rule_routes.each do |route|
|
120
|
-
# Bind exchange to queue
|
121
|
-
queue.bind topic_exchange, routing_key: route
|
122
|
-
info "subscribe: bind: \t[#{rule_topic}] \t[#{route}] \t> [#{rule_queue}]"
|
123
|
-
|
124
|
-
# Add row to config table
|
125
|
-
@table.add_row [rule_name, rule_topic, route, rule[:relay].to_s, rule[:title].to_s ]
|
126
|
-
end
|
127
|
-
|
128
|
-
# Subscribe to our new queue
|
129
|
-
queue.subscribe(block: false, manual_ack: PROXY_USE_ACK, message_max: PROXY_MESSAGE_MAX) do |delivery_info, metadata, payload|
|
130
|
-
|
131
|
-
# Handle the message
|
132
|
-
handle_message rule[:name], rule, delivery_info, metadata, payload
|
133
|
-
|
134
|
-
end
|
135
|
-
|
136
|
-
rescue Bunny::PreconditionFailed => e
|
137
|
-
abort "subscribe: PreconditionFailed: [#{rule_topic}] code(#{e.channel_close.reply_code}) message(#{e.channel_close.reply_text})"
|
138
|
-
rescue Exception => e
|
139
|
-
abort "subscribe: unhandled (#{e.inspect})"
|
140
|
-
|
141
|
-
end
|
142
|
-
|
143
53
|
# Handle the reception of a message on a queue
|
144
54
|
def handle_message rule, delivery_info, metadata, payload
|
145
55
|
# Prepare data
|
146
56
|
rule_name = rule[:name]
|
147
|
-
|
57
|
+
rule_relay = rule[:relay]
|
58
|
+
msg_exchange = delivery_info.exchange
|
148
59
|
msg_rkey = delivery_info.routing_key.force_encoding('UTF-8')
|
149
60
|
msg_headers = metadata.headers || {}
|
150
61
|
|
@@ -152,38 +63,43 @@ module PushyDaemon
|
|
152
63
|
data = parse payload, metadata.content_type #, rule
|
153
64
|
|
154
65
|
# Announce match
|
155
|
-
|
66
|
+
message way: WAY_IN,
|
67
|
+
exchange: msg_exchange,
|
68
|
+
key: msg_rkey,
|
69
|
+
body: data,
|
70
|
+
attrs: {
|
71
|
+
'rule' => rule_name,
|
72
|
+
'app-id' => metadata.app_id,
|
73
|
+
'content-type' => metadata.content_type,
|
74
|
+
}
|
156
75
|
|
157
76
|
# Build notification payload
|
158
|
-
|
159
|
-
|
160
|
-
exchange: msg_topic,
|
77
|
+
post_body = {
|
78
|
+
exchange: msg_exchange,
|
161
79
|
route: msg_rkey,
|
162
|
-
#headers: msg_headers,
|
163
80
|
sent_at: msg_headers['sent_at'],
|
164
81
|
sent_by: msg_headers['sent_by'],
|
165
82
|
data: data,
|
166
83
|
}
|
167
|
-
pretty_body = JSON.pretty_generate(body)
|
168
|
-
|
169
|
-
# Dump body data
|
170
|
-
puts "RULE: #{rule.inspect}"
|
171
|
-
puts "APP-ID: #{metadata.app_id}"
|
172
|
-
puts "CONTENT-TYPE: #{metadata.content_type}"
|
173
|
-
puts pretty_body
|
174
84
|
|
175
85
|
# Propagate data if needed
|
176
|
-
|
86
|
+
propagate rule_relay, post_body if rule_relay
|
177
87
|
end
|
178
88
|
|
179
|
-
def propagate
|
89
|
+
def propagate relay_url, post_body
|
180
90
|
# Nothing more to do if no relay
|
181
|
-
return if
|
91
|
+
return if relay_url.nil? || relay_url.empty?
|
92
|
+
id = SecureRandom.random_number(100)
|
93
|
+
|
94
|
+
# Log message details
|
95
|
+
message way: WAY_POST,
|
96
|
+
exchange: id,
|
97
|
+
key: relay_url,
|
98
|
+
body: post_body
|
182
99
|
|
183
100
|
# Push message to URL
|
184
|
-
|
185
|
-
|
186
|
-
puts "< #{response.body}"
|
101
|
+
response = RestClient.post relay_url.to_s, JSON.pretty_generate(post_body), :content_type => :json
|
102
|
+
info "#{id}: #{response.body}"
|
187
103
|
|
188
104
|
rescue Exception => e
|
189
105
|
abort "propagate: #{e.message}"
|
@@ -220,49 +136,3 @@ module PushyDaemon
|
|
220
136
|
|
221
137
|
end
|
222
138
|
|
223
|
-
|
224
|
-
# def prepare_shout
|
225
|
-
|
226
|
-
|
227
|
-
# # Prepare shout config
|
228
|
-
# shout_config = config[:shout]
|
229
|
-
# shout_exchange = nil
|
230
|
-
# shout_keys = []
|
231
|
-
|
232
|
-
# if shout_config.is_a? Hash
|
233
|
-
# shout_exchange = topic(channel, shout_config[:topic])
|
234
|
-
# shout_keys = shout_config[:keys] if shout_config[:keys].is_a? Array
|
235
|
-
# end
|
236
|
-
|
237
|
-
# end
|
238
|
-
|
239
|
-
# def endlessly
|
240
|
-
# # Endless loop with shout config
|
241
|
-
# begin
|
242
|
-
# loop do
|
243
|
-
# if shout_exchange
|
244
|
-
# random_string = SecureRandom.hex
|
245
|
-
# random_key = shout_keys.sample || "random"
|
246
|
-
# shout shout_exchange, [:ping, random_key, random_string], {}
|
247
|
-
# end
|
248
|
-
# sleep 1
|
249
|
-
# end
|
250
|
-
# rescue AMQ::Protocol::EmptyResponseError => e
|
251
|
-
# abort "ERROR: AMQ::Protocol::EmptyResponseError (#{e.inspect})"
|
252
|
-
# rescue Bunny::TCPConnectionFailedForAllHosts => e
|
253
|
-
# abort "ERROR: cannot connect to RabbitMQ hosts (#{e.inspect})"
|
254
|
-
# rescue Bunny::ChannelAlreadyClosed => e
|
255
|
-
# abort "ERROR: channel unexpectedly closed (#{e.inspect})"
|
256
|
-
# # sleep 1
|
257
|
-
# # retry
|
258
|
-
# rescue Bunny::PreconditionFailed => e
|
259
|
-
# abort "ERROR: precondition failed (#{e.inspect})"
|
260
|
-
# rescue Interrupt => e
|
261
|
-
# channel.close
|
262
|
-
# conn.close
|
263
|
-
# abort "QUITTING"
|
264
|
-
# end
|
265
|
-
# end
|
266
|
-
|
267
|
-
# Dump configuration
|
268
|
-
# Hashie.symbolize_keys! config
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module PushyDaemon
|
5
|
+
|
6
|
+
class ShouterResponseError < StandardError; end
|
7
|
+
class ShouterChannelClosed < StandardError; end
|
8
|
+
class ShouterPreconditionFailed < StandardError; end
|
9
|
+
class ShouterInterrupted < StandardError; end
|
10
|
+
class EndpointTopicContext < StandardError; end
|
11
|
+
|
12
|
+
class Shouter < Endpoint
|
13
|
+
|
14
|
+
attr_accessor :table
|
15
|
+
|
16
|
+
def initialize(logger)
|
17
|
+
# Init
|
18
|
+
@logger = logger
|
19
|
+
@keys = []
|
20
|
+
|
21
|
+
# Start connexion to RabbitMQ and create channel
|
22
|
+
conn = connect Config.bus
|
23
|
+
@channel = conn.create_channel
|
24
|
+
info "connected on a channel"
|
25
|
+
|
26
|
+
# Check config
|
27
|
+
config_shout = Config[:shout]
|
28
|
+
if (config_shout.is_a? Enumerable) && !config_shout.empty?
|
29
|
+
@keys = config_shout[:keys] if config_shout[:keys].is_a? Array
|
30
|
+
@topic = config_shout[:topic]
|
31
|
+
|
32
|
+
info "found topic: #{@topic}"
|
33
|
+
info "found keys: #{@keys.join(', ')}"
|
34
|
+
else
|
35
|
+
abort "prepare: empty [shout] section"
|
36
|
+
end
|
37
|
+
|
38
|
+
# Create exchange
|
39
|
+
raise PushyDaemon::EndpointTopicContext unless @topic
|
40
|
+
@exchange = @channel.topic(@topic, durable: true, persistent: true)
|
41
|
+
|
42
|
+
# if shout_config.is_a? Hash
|
43
|
+
# shout_keys = shout_config[:keys] if config_shout[:keys].is_a? Array
|
44
|
+
# end
|
45
|
+
|
46
|
+
rescue Bunny::TCPConnectionFailedForAllHosts => e
|
47
|
+
abort "ERROR: cannot connect to RabbitMQ hosts (#{e.inspect})"
|
48
|
+
end
|
49
|
+
|
50
|
+
def shout
|
51
|
+
# Prepare exchange
|
52
|
+
loop do
|
53
|
+
if true # shout_exchange
|
54
|
+
random_string = SecureRandom.hex
|
55
|
+
random_key = @keys.sample || "random"
|
56
|
+
channel_shout [:ping, random_key, random_string], {}
|
57
|
+
end
|
58
|
+
sleep 1
|
59
|
+
end
|
60
|
+
rescue AMQ::Protocol::EmptyResponseError => e
|
61
|
+
raise PushyDaemon::ShouterResponseError, "#{e.class} (#{e.inspect})"
|
62
|
+
rescue Bunny::ChannelAlreadyClosed => e
|
63
|
+
raise PushyDaemon::ShouterChannelClosed, "#{e.class} (#{e.inspect})"
|
64
|
+
rescue Bunny::PreconditionFailed => e
|
65
|
+
raise PushyDaemon::ShouterPreconditionFailed, "#{e.class} (#{e.inspect})"
|
66
|
+
|
67
|
+
rescue Interrupt => e
|
68
|
+
@channel.close
|
69
|
+
# conn.close
|
70
|
+
raise PushyDaemon::ShouterInterrupted, "#{e.class} (#{e.inspect})"
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def channel_shout keys, body = {}
|
76
|
+
# Add timestamp
|
77
|
+
headers = {
|
78
|
+
sent_at: DateTime.now.iso8601,
|
79
|
+
sent_by: Config.name
|
80
|
+
}
|
81
|
+
exchange_name = @exchange.name
|
82
|
+
|
83
|
+
# Prepare key and data
|
84
|
+
routing_key = keys.unshift(exchange_name).join('.')
|
85
|
+
|
86
|
+
# Announce shout
|
87
|
+
message way: WAY_OUT, exchange: exchange_name, key: routing_key, body: nil, attrs: {}
|
88
|
+
|
89
|
+
# Publish
|
90
|
+
@exchange.publish(body.to_json,
|
91
|
+
routing_key: routing_key,
|
92
|
+
headers: headers,
|
93
|
+
app_id: Config.name,
|
94
|
+
content_type: "application/json",
|
95
|
+
)
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
data/lib/pushyd.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
# Global libs
|
3
2
|
require "rubygems"
|
4
3
|
require "json"
|
@@ -6,8 +5,11 @@ require "thread"
|
|
6
5
|
require "singleton"
|
7
6
|
# require "newrelic_rpm"
|
8
7
|
|
9
|
-
# Project
|
8
|
+
# Project libs
|
10
9
|
require_relative "pushyd/config"
|
10
|
+
require_relative "pushyd/constants"
|
11
|
+
require_relative "pushyd/endpoint"
|
11
12
|
require_relative "pushyd/proxy"
|
13
|
+
require_relative "pushyd/shouter"
|
12
14
|
require_relative "pushyd/daemon"
|
13
15
|
|
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.
|
4
|
+
spec.version = "0.1.1"
|
5
5
|
|
6
6
|
# Project description
|
7
7
|
spec.name = "pushyd"
|
@@ -14,10 +14,10 @@ Gem::Specification.new do |spec|
|
|
14
14
|
spec.date = Time.now.strftime("%Y-%m-%d")
|
15
15
|
|
16
16
|
# List files and executables
|
17
|
-
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.files = `git ls-files -z`.split("\x0")
|
18
18
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
19
|
spec.require_paths = ["lib"]
|
20
|
-
spec.required_ruby_version = ">= 2.
|
20
|
+
spec.required_ruby_version = ">= 2.1"
|
21
21
|
|
22
22
|
# Development dependencies
|
23
23
|
spec.add_development_dependency "bundler", "~> 1.6"
|
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.
|
4
|
+
version: 0.1.1
|
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-06-
|
11
|
+
date: 2016-06-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -180,8 +180,11 @@ files:
|
|
180
180
|
- defaults.yml
|
181
181
|
- lib/pushyd.rb
|
182
182
|
- lib/pushyd/config.rb
|
183
|
+
- lib/pushyd/constants.rb
|
183
184
|
- lib/pushyd/daemon.rb
|
185
|
+
- lib/pushyd/endpoint.rb
|
184
186
|
- lib/pushyd/proxy.rb
|
187
|
+
- lib/pushyd/shouter.rb
|
185
188
|
- pushyd.gemspec
|
186
189
|
homepage: http://github.com/bmedici/pushyd
|
187
190
|
licenses:
|
@@ -195,7 +198,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
195
198
|
requirements:
|
196
199
|
- - ">="
|
197
200
|
- !ruby/object:Gem::Version
|
198
|
-
version: '2.
|
201
|
+
version: '2.1'
|
199
202
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
200
203
|
requirements:
|
201
204
|
- - ">="
|