pushyd 0.0.2 → 0.1.1
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.
- 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
|
- - ">="
|