brown 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 484e391eba640c3d0556ff4e9ad183427615015b
4
+ data.tar.gz: 8942004eb6a02b16be0918798311d2796b284539
5
+ SHA512:
6
+ metadata.gz: d46574e93ee7268e3bfb0d5cfe5be84b8820012682c58e4fd592ea4a85953069da27c4fac289ec7fe362608fb9ee040c4cfd9dd6b8a5536e03046beef69c60ef
7
+ data.tar.gz: 290cda8b17a65fc9b76fce0439d1e76e02b5f112f82fcc766bc66efb8c9440e39f6c4940fa37b37d8fef5811a7865588cf990cbc487f5495cb5c4c169f8bebd1
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ /Gemfile.lock
2
+ /pkg
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ exec(*(["bundle", "exec", $PROGRAM_NAME] + ARGV)) if ENV['BUNDLE_GEMFILE'].nil?
2
+
3
+ Bundler.setup(:default, :development)
4
+
5
+ task :default => :test
6
+
7
+ begin
8
+ Bundler.setup(:default, :development)
9
+ rescue Bundler::BundlerError => e
10
+ $stderr.puts e.message
11
+ $stderr.puts "Run `bundle install` to install missing gems"
12
+ exit e.status_code
13
+ end
14
+
15
+ Bundler::GemHelper.install_tasks
16
+
17
+ task :release do
18
+ sh "git release"
19
+ end
data/bin/brown ADDED
@@ -0,0 +1,30 @@
1
+ # Run an agent. Any agent.
2
+
3
+ require 'envied'
4
+
5
+ envied_config = (ENVied.config || ENVied::Configuration.new).tap do |cfg|
6
+ cfg.enable_defaults!
7
+ cfg.variable :BROWN_ACL_PATH
8
+ cfg.variable :RABBITMQ_SERVER
9
+ cfg.variable :BROWN_LOG_LEVEL, :string, :default => "info"
10
+ end
11
+
12
+ ENVied.require(:default, :config => envied_config)
13
+
14
+ # Trick smith-using agents into loving us -- since we have a `smith.rb` and
15
+ # we should be the only thing in $LOAD_PATH at the moment, our version will
16
+ # be loaded and the real smith will never darken our door.
17
+ require 'smith'
18
+
19
+ load *ARGV
20
+
21
+ agent_classes = ObjectSpace.each_object(Class).select do |k|
22
+ k != Brown::Agent and k.ancestors.include?(Brown::Agent)
23
+ end
24
+
25
+ Brown::ACLLoader.load_all(ENVied.BROWN_ACL_PATH.split(":"))
26
+
27
+ Brown.start(:server_url => ENVied.RABBITMQ_SERVER,
28
+ :log_level => ENVied.BROWN_LOG_LEVEL) do
29
+ agent_classes.each { |k| k.new.run }
30
+ end
data/brown.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ require "git-version-bump"
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "brown"
5
+ s.version = GVB.version
6
+ s.date = GVB.date
7
+
8
+ s.summary = "Run individual smith agents directly from the command line"
9
+
10
+ s.licenses = ["GPL-3"]
11
+
12
+ s.authors = ["Richard Heycock", "Matt Palmer"]
13
+
14
+ s.files = `git ls-files -z`.split("\0")
15
+ s.executables = %w{brown}
16
+
17
+ s.has_rdoc = false
18
+
19
+ s.add_runtime_dependency "amqp", "~> 1.4"
20
+ s.add_runtime_dependency "envied", "~> 0.8"
21
+ s.add_runtime_dependency "eventmachine-le", "~> 1.0"
22
+ s.add_runtime_dependency "extlib", "~> 0.9"
23
+ s.add_runtime_dependency "murmurhash3", "~> 0.1"
24
+ s.add_runtime_dependency "protobuf", "~> 3.0"
25
+
26
+ s.add_development_dependency "bundler"
27
+ s.add_development_dependency "git-version-bump", "~> 0.10"
28
+ s.add_development_dependency "rake", "~> 10.4", ">= 10.4.2"
29
+ end
data/lib/brown.rb ADDED
@@ -0,0 +1,5 @@
1
+ module Brown; end
2
+
3
+ Dir["#{__dir__}/brown/*.rb"].each { |f| require f }
4
+
5
+ Brown.extend Brown::ModuleMethods
@@ -0,0 +1,57 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'tmpdir'
4
+ require 'protobuf'
5
+
6
+ require "brown/logger"
7
+
8
+ class Brown::ACLLoader
9
+ include Brown::Logger
10
+
11
+ def self.load_all(*dirs)
12
+ dirs.flatten!
13
+ pfiles = dirs.each_with_object([]) do |dir, list|
14
+ list << Dir["#{dir}/*.proto"]
15
+ end.flatten
16
+
17
+ load_proto_files(pfiles, dirs)
18
+ end
19
+
20
+ def self.load_proto_files(pfiles, dirs)
21
+ orig_load_path = $LOAD_PATH
22
+
23
+ Dir.mktmpdir do |tmpdir|
24
+ dirs = pfiles.map { |f| File.dirname(f) }.uniq + [tmpdir]
25
+ dirs.each { |d| $LOAD_PATH.unshift(d) }
26
+
27
+ compiles = []
28
+
29
+ pfiles.each do |f|
30
+ pbrbfile = f.gsub(/\.proto$/, ".pb.rb")
31
+ unless File.exists?(pbrbfile) and File.stat(pfile).mtime <= File.stat(pbrbfile).mtime
32
+ compiles << f
33
+ end
34
+ end
35
+
36
+ includes = dirs.map { |d| "-I '#{d}'" }.join(" ")
37
+
38
+ unless compiles.empty?
39
+ cmd = "protoc --ruby_out='#{tmpdir}' #{includes} #{compiles.map { |f| "'#{f}'" }.join(' ')} 2>&1"
40
+ output = nil
41
+
42
+ IO.popen(cmd) { |fd| output = fd.read }
43
+
44
+ if $?.exitstatus != 0
45
+ logger.fatal { "protoc failed: #{output}" }
46
+ raise RuntimeError, output
47
+ end
48
+ end
49
+
50
+ pfiles.each do |f|
51
+ require "#{File.basename(f, '.proto')}.pb"
52
+ end
53
+ end
54
+ ensure
55
+ $LOAD_PATH.replace(orig_load_path)
56
+ end
57
+ end
@@ -0,0 +1,52 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'extlib'
4
+ require 'murmurhash3'
5
+
6
+ module Brown::ACLLookup
7
+ def get_by_hash(type)
8
+ hashes[type]
9
+ end
10
+ module_function :get_by_hash
11
+
12
+ def get_by_type(type)
13
+ to_murmur32(type)
14
+ end
15
+ module_function :get_by_type
16
+
17
+ # Look the key up in the cache. This defaults to the key being the hash.
18
+ # If :by_type => true is passed in as the second argument then it will
19
+ # perform the lookup in the type hash.
20
+ #
21
+ def include?(key, opts={})
22
+ if opts[:by_type]
23
+ !get_by_type(key).nil?
24
+ else
25
+ !get_by_hash(key).nil?
26
+ end
27
+ end
28
+ module_function :include?
29
+
30
+ def clear!
31
+ @hashes = nil
32
+ end
33
+ module_function :clear!
34
+
35
+ def hashes
36
+ @hashes ||= begin
37
+ map = ObjectSpace.each_object(Class).map do |k|
38
+ [[to_murmur32(k), k], [k.to_s.split(/::/).last.snake_case, k]]
39
+ end.flatten(1)
40
+ Hash[map]
41
+ end
42
+ end
43
+ module_function :hashes
44
+
45
+ private
46
+
47
+ # Convert the name to a base 36 murmur hash
48
+ def to_murmur32(type)
49
+ MurmurHash3::V32.murmur3_32_str_hash(type.to_s).to_s(36)
50
+ end
51
+ module_function :to_murmur32
52
+ end
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require "brown/logger"
4
+
5
+ class Brown::Agent
6
+ include Brown::Logger
7
+
8
+ # Override this method to implement your own agent.
9
+ def run
10
+ raise ArgumentError, "You must override this method"
11
+ end
12
+
13
+ def receiver(queue_name, opts={}, &blk)
14
+ queues.receiver(queue_name, opts, &blk)
15
+ end
16
+
17
+ def sender(queue_name, opts={}, &blk)
18
+ queues.sender(queue_name, opts, &blk)
19
+ end
20
+
21
+ class << self
22
+ # I care not for your opts... this is just here for Smith compatibility
23
+ def options(opts)
24
+ end
25
+ end
26
+
27
+ protected
28
+
29
+ def queues
30
+ @queues ||= Brown::QueueFactory.new
31
+ end
32
+ end
@@ -0,0 +1,148 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module Brown::AmqpErrors
4
+ def error_message(code, text, &blk)
5
+ details = error_lookup(code)
6
+ message = "#{details[:error_class]} exception: #{code} " +
7
+ "(#{details[:name]}). #{details[:description]}"
8
+
9
+ case code
10
+ when 404
11
+ diagnosis = "looks like the queue has been deleted."
12
+ when 406
13
+ case text
14
+ when /.*(unknown delivery tag [0-9]+).*/
15
+ diagnosis = "#{$1} - you've probably already acknowledged the message."
16
+ end
17
+ else
18
+ end
19
+ blk.call(message, diagnosis)
20
+ end
21
+
22
+ def error_lookup(code)
23
+ errors[code]
24
+ end
25
+
26
+ def errors
27
+ @errors ||= {
28
+ 311 => {
29
+ :name => "content-too-large",
30
+ :error_class => "Channel",
31
+ :description => "The client attempted to transfer content larger " +
32
+ "than the server could accept at the present time. " +
33
+ "The client may retry at a later time."
34
+ },
35
+ 313 => {
36
+ :name => "no-consumers",
37
+ :error_class => "Channel",
38
+ :description => "When the exchange cannot deliver to a consumer " +
39
+ "when the immediate flag is set. As a result of " +
40
+ "pending data on the queue or the absence of any " +
41
+ "consumers of the queue."
42
+ },
43
+ 320 => {
44
+ :name => "connection-forced",
45
+ :error_class => "Connection",
46
+ :description => "An operator intervened to close the Connection " +
47
+ "for some reason. The client may retry at some " +
48
+ "later date."
49
+ },
50
+ 402 => {
51
+ :name => "invalid-path",
52
+ :error_class => "Connection",
53
+ :description => "The client tried to work with an unknown virtual host."
54
+ },
55
+ 403 => {
56
+ :name => "access-refused",
57
+ :error_class => "Channel",
58
+ :description => "The client attempted to work with a server " +
59
+ "entity to which it has no access due to security " +
60
+ "settings."
61
+ },
62
+ 404 => {
63
+ :name => "not-found",
64
+ :error_class => "Channel",
65
+ :description => "The client attempted to work with a server " +
66
+ "entity that does not exist."
67
+ },
68
+ 405 => {
69
+ :name => "resource-locked",
70
+ :error_class => "Channel",
71
+ :description => "The client attempted to work with a server " +
72
+ "entity to which it has no access because " +
73
+ "another client is working with it."
74
+ },
75
+ 406 => {
76
+ :name => "precondition-failed",
77
+ :error_class => "Channel",
78
+ :description => "The client requested a method that was not " +
79
+ "allowed because some precondition failed."
80
+ },
81
+ 501 => {
82
+ :name => "frame-error",
83
+ :error_class => "Connection",
84
+ :description => "The sender sent a malformed frame that the " +
85
+ "recipient could not decode. This strongly " +
86
+ "implies a programming error in the sending peer."
87
+ },
88
+ 502 => {
89
+ :name => "syntax-error",
90
+ :error_class => "Connection",
91
+ :description => "The sender sent a frame that contained illegal " +
92
+ "values for one or more fields. This strongly " +
93
+ "implies a programming error in the sending peer."
94
+ },
95
+ 503 => {
96
+ :name => "command-invalid",
97
+ :error_class => "Connection",
98
+ :description => "The client sent an invalid sequence of frames, " +
99
+ "attempting to perform an operation that was " +
100
+ "considered invalid by the server. This usually " +
101
+ "implies a programming error in the client."
102
+ },
103
+ 504 => {
104
+ :name => "channel-error",
105
+ :error_class => "Connection",
106
+ :description => "The client attempted to work with a Channel that " +
107
+ "had not been correctly opened. This most likely " +
108
+ "indicates a fault in the client layer."
109
+ },
110
+ 505 => {
111
+ :name => "unexpected-frame",
112
+ :error_class => "Connection",
113
+ :description => "The peer sent a frame that was not expected, " +
114
+ "usually in the context of a content header and " +
115
+ "body. This strongly indicates a fault in the " +
116
+ "peer's content processing."
117
+ },
118
+ 506 => {
119
+ :name => "resource-error",
120
+ :error_class => "Connection",
121
+ :description => "The server could not complete the method because " +
122
+ "it lacked sufficient resources. This may be due " +
123
+ "to the client creating too many of some type of entity."
124
+ },
125
+ 530 => {
126
+ :name => "not-allowed",
127
+ :error_class => "Connection",
128
+ :description => "The client tried to work with some entity in a " +
129
+ "manner that is prohibited by the server, due to " +
130
+ "security settings or by some other criteria."
131
+ },
132
+ 540 => {
133
+ :name => "not-implemented",
134
+ :error_class => "Connection",
135
+ :description => "The client tried to use functionality that is " +
136
+ "not implemented in the server."
137
+ },
138
+ 541 => {
139
+ :name => "internal-error",
140
+ :error_class => "Connection",
141
+ :description => "The server could not complete the method " +
142
+ "because of an internal error. The server may " +
143
+ "require intervention by an operator in order " +
144
+ "to resume normal operations."
145
+ }
146
+ }
147
+ end
148
+ end
@@ -0,0 +1,51 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'logger'
4
+
5
+ class Logger
6
+ VERBOSE = 0.5
7
+ TRACE = -1
8
+
9
+ # Lack of prior planning, peeps!
10
+ remove_const(:SEV_LABEL)
11
+ SEV_LABEL = {
12
+ TRACE => "TRACE",
13
+ DEBUG => "DEBUG",
14
+ VERBOSE => "VERB",
15
+ INFO => "INFO",
16
+ WARN => "WARN",
17
+ ERROR => "ERROR",
18
+ FATAL => "FATAL"
19
+ }
20
+
21
+ def verbose(progname = nil, &block)
22
+ add(VERBOSE, nil, progname, &block)
23
+ end
24
+
25
+ def trace(progname = nil, &block)
26
+ add(TRACE, nil, progname, &block)
27
+ end
28
+ end
29
+
30
+ module Brown::Logger
31
+ def logger
32
+ @logger ||= begin
33
+ Logger.new($stderr).tap do |l|
34
+ l.formatter = proc { |s,dt,n,msg| "#{$$} [#{s[0]}] #{msg}\n" }
35
+ l.level = Logger.const_get(Brown.log_level.upcase.to_sym)
36
+ end
37
+ end
38
+ end
39
+
40
+ def log_level(level=nil)
41
+ if level
42
+ logger.level = Logger.const_get(level.upcase.to_sym)
43
+ end
44
+ end
45
+
46
+ def backtrace(ex)
47
+ if ex.respond_to?(:backtrace) and ex.backtrace
48
+ self.debug { ex.backtrace.map { |l| " #{l}" }.join("\n") }
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,73 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'brown/logger'
4
+
5
+ class Brown::Message
6
+ include Brown::Logger
7
+
8
+ attr_reader :payload, :metadata
9
+
10
+ def initialize(payload, metadata, requeue_queue, requeue_options, opts = {}, &blk)
11
+ @metadata = metadata
12
+
13
+ @requeue_queue = requeue_queue
14
+ @requeue_options = requeue_options
15
+
16
+ @requeue_options[:strategy] ||= :linear
17
+
18
+ @requeue_options[:on_requeue] ||= ->(count, total_count, cumulative_delay) {
19
+ logger.info { "Requeuing (#{@requeue_options[:strategy]}) message on queue: #{@requeue_queue.name}, count: #{count} of #{total_count}." }
20
+ }
21
+
22
+ @requeue_options[:on_requeue_limit] ||= ->(message, count, total_count, cumulative_delay) {
23
+ logger.info { "Not attempting any more requeues, requeue limit reached: #{total_count} for queue: #{@requeue_queue.name}, cummulative delay: #{cumulative_delay}s." }
24
+ }
25
+
26
+ klass = Brown::ACLLookup.get_by_hash(metadata.type)
27
+ raise RuntimeError, "Unknown ACL: #{metadata.type}" if klass.nil?
28
+
29
+ @payload = klass.new.parse_from_string(payload)
30
+
31
+ blk.call(@payload, self)
32
+ ack if opts[:auto_ack]
33
+ end
34
+
35
+ def ack(multiple = false)
36
+ @metadata.ack(multiple)
37
+ end
38
+
39
+ def nak(opts = {})
40
+ @metadata.reject(opts)
41
+ end
42
+
43
+ alias_method :reject, :nak
44
+
45
+ def requeue
46
+ if current_requeue_number < @requeue_options[:count]
47
+ cumulative_delay = case @requeue_options[:strategy].to_sym
48
+ when :linear
49
+ @requeue_options[:delay] * (current_requeue_number + 1)
50
+ when :exponential
51
+ @requeue_options[:delay] * (2 ** current_requeue_number)
52
+ when :exponential_no_initial_delay
53
+ @requeue_options[:delay] * (2 ** current_requeue_number - 1)
54
+ else
55
+ raise RuntimeError, "Unknown requeue strategy #{@requeue_options[:strategy].to_sym.inspect}"
56
+ end
57
+
58
+ EM.add_timer(cumulative_delay) do
59
+ new_headers = (@metadata.headers || {}).merge('requeue' => current_requeue_number + 1)
60
+ @requeue_queue.publish(@payload, @metadata.to_hash.merge(:headers => new_headers))
61
+ end
62
+
63
+ @requeue_options[:on_requeue].call(current_requeue_number + 1, @requeue_options[:count], cumulative_delay)
64
+ else
65
+ @requeue_options[:on_requeue_limit].call(@payload, current_requeue_number + 1, @requeue_options[:count], @requeue_options[:delay] * current_requeue_number)
66
+ end
67
+ end
68
+
69
+ private
70
+ def current_requeue_number
71
+ (@metadata.headers['requeue'] rescue nil) || 0
72
+ end
73
+ end
@@ -0,0 +1,120 @@
1
+ # Remove any pre-existing activation of eventmachine, so that `eventmachine-le`
2
+ # takes priority
3
+ $:.delete_if { |d| d =~ /\/eventmachine-\d/ }
4
+
5
+ require 'eventmachine-le'
6
+ require 'amqp'
7
+ require 'uri'
8
+
9
+ require 'brown/logger'
10
+
11
+ module Brown::ModuleMethods
12
+ include Brown::Logger
13
+
14
+ attr_reader :connection, :log_level
15
+
16
+ def compile_acls
17
+ @compiler = ACLCompiler.new
18
+ @compiler.compile
19
+ end
20
+
21
+ def running?
22
+ EM.reactor_running?
23
+ end
24
+
25
+ def start(opts={})
26
+ @log_level = opts[:log_level] || "info"
27
+
28
+ connection_settings = {
29
+ :on_tcp_connection_failure => method(:tcp_connection_failure_handler),
30
+ :on_possible_authentication_failure => method(:authentication_failure_handler)
31
+ }
32
+
33
+ AMQP.start(opts[:server_url], connection_settings) do |connection|
34
+ EM.threadpool_size = 1
35
+ @connection = connection
36
+
37
+ connection.on_connection do
38
+ logger.info { "Connected to: AMQP Broker: #{broker_identifier(connection)}" }
39
+ end
40
+
41
+ connection.on_tcp_connection_loss do |connection, settings|
42
+ logger.info { "Reconnecting to AMQP Broker: #{broker_identifier(connection)} in 5s" }
43
+ connection.reconnect(false, 5)
44
+ end
45
+
46
+ connection.after_recovery do |connection|
47
+ logger.info { "Connection with AMQP Broker restored: #{broker_identifier(connection)}" }
48
+ end
49
+
50
+ connection.on_error do |connection, connection_close|
51
+ # If the broker is gracefully shutdown we get a 320. Log a nice message.
52
+ if connection_close.reply_code == 320
53
+ logger.info { "AMQP Broker shutdown: #{broker_identifier(connection)}" }
54
+ else
55
+ logger.warn { connection_close.reply_text }
56
+ end
57
+ end
58
+
59
+ # This will be the last thing run by the reactor.
60
+ shutdown_hook { logger.debug { "Reactor Stopped" } }
61
+
62
+ yield if block_given?
63
+ end
64
+ end
65
+
66
+ def shutdown_hook(&block)
67
+ EM.add_shutdown_hook(&block)
68
+ end
69
+
70
+ def stop(immediately=false, &blk)
71
+ shutdown_hook(&blk) if blk
72
+
73
+ if running?
74
+ if immediately
75
+ EM.next_tick do
76
+ @connection.close { EM.stop_event_loop }
77
+ end
78
+ else
79
+ EM.add_timer(1) do
80
+ @connection.close { EM.stop_event_loop }
81
+ end
82
+ end
83
+ else
84
+ logger.fatal { "Eventmachine is not running, exiting with prejudice" }
85
+ exit!
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ def tcp_connection_failure_handler(settings)
92
+ # Only display the following settings.
93
+ s = settings.select { |k,v| ([:user, :pass, :vhost, :host, :port, :ssl].include?(k)) }
94
+
95
+ logger.fatal { "Cannot connect to the AMQP server." }
96
+ logger.fatal { "Is the server running and are the connection details correct?" }
97
+ logger.info { "Details:" }
98
+ s.each do |k,v|
99
+ logger.info { " Setting: %-7s%s" % [k, v] }
100
+ end
101
+ EM.stop
102
+ end
103
+
104
+ def authentication_failure_handler(settings)
105
+ # Only display the following settings.
106
+ s = settings.select { |k,v| [:user, :pass, :vhost, :host].include?(k) }
107
+
108
+ logger.fatal { "Authentication failure." }
109
+ logger.info { "Details:" }
110
+ s.each do |k,v|
111
+ logger.info { " Setting: %-7s%s" % [k, v] }
112
+ end
113
+ EM.stop
114
+ end
115
+
116
+ def broker_identifier(connection)
117
+ broker = connection.broker.properties
118
+ "#{connection.broker_endpoint}, (#{broker['product']}/v#{broker['version']})"
119
+ end
120
+ end
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ class Brown::QueueDefinition
4
+ attr_reader :options
5
+
6
+ def initialize(name, options)
7
+ @normalised_queue = "smith.#{name}"
8
+ @denormalised_queue = "#{name}"
9
+ @options = options
10
+ end
11
+
12
+ def denormalise
13
+ @denormalised_queue
14
+ end
15
+
16
+ def name
17
+ @normalised_queue
18
+ end
19
+
20
+ def normalise
21
+ @normalised_queue
22
+ end
23
+
24
+ # to_a is defined to make the splat operator work.
25
+ def to_a
26
+ return @normalised_queue, @options
27
+ end
28
+
29
+ def to_s
30
+ "<#{self.class}: #{@denormalised_queue}, #{@options.inspect}>"
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ class Brown::QueueFactory
4
+ def initialize
5
+ @cache = {}
6
+ end
7
+
8
+ # Convenience method that returns a Sender object.
9
+ def sender(queue_name, opts={}, &blk)
10
+ k = "sender:#{queue_name}"
11
+ @cache[k] ||= Brown::Sender.new(queue_name, opts)
12
+ blk.call(@cache[k])
13
+ end
14
+
15
+ # Convenience method that returns a Receiver object.
16
+ def receiver(queue_name, opts={}, &blk)
17
+ k = "receiver:#{queue_name}"
18
+ @cache[k] ||= Brown::Receiver.new(queue_name, opts)
19
+ blk.call(@cache[k])
20
+ end
21
+
22
+ # Passes each queue to the supplied block.
23
+ def each_queue
24
+ @cache.values.each do |queue|
25
+ yield queue if block_given?
26
+ end
27
+ end
28
+
29
+ # Returns all queues as a hash, with the queue name being the key.
30
+ def queues
31
+ @cache
32
+ end
33
+ end
@@ -0,0 +1,143 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'brown/logger'
4
+ require 'brown/util'
5
+
6
+ class Brown::Receiver
7
+ include Brown::Logger
8
+ include Brown::Util
9
+
10
+ def initialize(queue_def, opts={}, &blk)
11
+ @queue_def = queue_def.is_a?(Brown::QueueDefinition) ? queue_def : Brown::QueueDefinition.new(queue_def, opts)
12
+
13
+ @acl_type_cache = Brown::ACLLookup
14
+
15
+ @options = opts
16
+
17
+ @requeue_options = {}
18
+ @requeue_queue = Brown::Sender.new(@queue_def, opts)
19
+
20
+ @payload_type = Array(option_or_default(@queue_def.options, :type, []))
21
+
22
+ prefetch = option_or_default(@queue_def.options, :prefetch, 1)
23
+
24
+ @channel_completion = EM::Completion.new
25
+ @queue_completion = EM::Completion.new
26
+
27
+ open_channel(:prefetch => prefetch) do |channel|
28
+ logger.debug { "channel open for receiver on #{@queue_def.denormalise}" }
29
+ channel.on_error do |ch, close|
30
+ logger.fatal { "Channel error: #{close.inspect}" }
31
+ end
32
+
33
+ channel.queue(@queue_def.normalise) do |queue|
34
+ logger.debug { "Registered queue #{@queue_def.denormalise} on channel" }
35
+ @queue_completion.succeed(queue)
36
+ end
37
+
38
+ @channel_completion.succeed(channel)
39
+ end
40
+
41
+ blk.call(self) if blk
42
+ end
43
+
44
+ def ack(multiple=false)
45
+ @channel_completion.completion {|channel| channel.ack(multiple) }
46
+ end
47
+
48
+ # Subscribes to a queue and passes the headers and payload into the
49
+ # block. +subscribe+ will automatically acknowledge the message unless
50
+ # the options sets :ack to false.
51
+ def subscribe(opts = {}, &blk)
52
+ @queue_completion.completion do |queue|
53
+ logger.debug { "Subscribing to: [queue]:#{@queue_def.denormalise} [options]:#{@queue_def.options}" }
54
+ queue.subscribe(opts.merge(:ack => true)) do |metadata,payload|
55
+ logger.debug { "Received a message on #{@queue_def.denormalise}: #{metadata.to_hash.inspect}" }
56
+ if payload
57
+ on_message(metadata, payload, &blk)
58
+ else
59
+ logger.debug { "Received null message on: #{@queue_def.denormalise} [options]:#{@queue_def.options}" }
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ def unsubscribe(&blk)
66
+ @queue_completion.completion do |queue|
67
+ queue.unsubscribe(&blk)
68
+ end
69
+ end
70
+
71
+ def requeue_parameters(opts)
72
+ @requeue_options.merge!(opts)
73
+ end
74
+
75
+ def on_requeue(&blk)
76
+ @requeue_options[:on_requeue] = blk
77
+ end
78
+
79
+ def on_requeue_limit(&blk)
80
+ @requeue_options[:on_requeue_limit] = blk
81
+ end
82
+
83
+ # pops a message off the queue and passes the headers and payload
84
+ # into the block. +pop+ will automatically acknowledge the message
85
+ # unless the options sets :ack to false.
86
+ def pop(&blk)
87
+ @queue_completion.completion do |queue|
88
+ queue.pop({}) do |metadata, payload|
89
+ if payload
90
+ on_message(metadata, payload, &blk)
91
+ else
92
+ blk.call(nil,nil)
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ # Define a channel error handler.
99
+ def on_error(chain=false, &blk)
100
+ # TODO Check that this chains callbacks
101
+ @channel_completion.completion do |channel|
102
+ channel.on_error(&blk)
103
+ end
104
+ end
105
+
106
+ def queue_name
107
+ @queue_def.denormalise
108
+ end
109
+
110
+ def delete(&blk)
111
+ @queue_completion.completion do |queue|
112
+ @channel_completion.completion do |channel|
113
+ queue.unbind(exchange) do
114
+ queue.delete do
115
+ exchange.delete do
116
+ channel.close(&blk)
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ def status(&blk)
125
+ @queue_completion.completion do |queue|
126
+ queue.status do |num_messages, num_consumers|
127
+ blk.call(num_messages, num_consumers)
128
+ end
129
+ end
130
+ end
131
+
132
+ private
133
+
134
+ def on_message(metadata, payload, &blk)
135
+ if @payload_type.empty? || @payload_type.include?(@acl_type_cache.get_by_hash(metadata.type))
136
+ Brown::Message.new(payload, metadata, @requeue_queue, @requeue_options, @options, &blk)
137
+ else
138
+ allowable_acls = @payload_type.join(", ")
139
+ received_acl = @acl_type_cache.get_by_hash(metadata.type)
140
+ raise ACL::IncorrectTypeError, "Received ACL: #{received_acl} on queue: #{@queue_def.denormalise}. This queue can only accept the following ACLs: #{allowable_acls}"
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,92 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'brown/logger'
4
+ require 'brown/util'
5
+
6
+ class Brown::Sender
7
+ include Brown::Logger
8
+ include Brown::Util
9
+
10
+ attr_reader :name
11
+
12
+ def initialize(queue_def, opts={})
13
+ @queue_def = queue_def.is_a?(Brown::QueueDefinition) ? queue_def : Brown::QueueDefinition.new(queue_def, opts)
14
+ @name = @queue_def.denormalise
15
+
16
+ @reply_container = {}
17
+
18
+ @message_count = 0
19
+
20
+ @channel_completion = EM::Completion.new
21
+
22
+ open_channel do |channel|
23
+ logger.debug { "Opening a channel for sending" }
24
+ @channel_completion.succeed(channel)
25
+ end
26
+ end
27
+
28
+ def publish(payload, opts={}, &blk)
29
+ logger.debug { "Publishing to: [queue]: #{@queue_def.denormalise}. [options]: #{opts}" }
30
+ logger.debug { "ACL content: [queue]: #{@queue_def.denormalise}, [metadata type]: #{payload.class}, [message]: #{payload.inspect}" }
31
+
32
+ increment_counter
33
+
34
+ type = Brown::ACLLookup.get_by_type(payload.class)
35
+
36
+ @channel_completion.completion do |channel|
37
+ logger.debug { "Publishing #{payload.inspect} to queue #{@queue_def.denormalise}" }
38
+ AMQP::Exchange.default(channel).publish(payload.to_s, opts.merge(:type => type, :routing_key => @queue_def.normalise), &blk)
39
+ end
40
+ end
41
+
42
+ def delete(&blk)
43
+ queue.delete do
44
+ @channel_completion.completion do |channel|
45
+ channel.close(&blk)
46
+ end
47
+ end
48
+ end
49
+
50
+ =begin
51
+ def status(&blk)
52
+ @queue_completion.completion do |queue|
53
+ queue.status do |num_messages, num_consumers|
54
+ blk.call(num_messages, num_consumers) if blk
55
+ end
56
+ end
57
+ end
58
+
59
+ def message_count(&blk)
60
+ status do |messages|
61
+ blk.call(messages) if blk
62
+ end
63
+ end
64
+
65
+ def consumer_count(&blk)
66
+ status do |_, consumers|
67
+ blk.call(consumers) if blk
68
+ end
69
+ end
70
+ =end
71
+
72
+ def counter
73
+ @message_count
74
+ end
75
+
76
+ # Define a channel error handler.
77
+ def on_error(chain=false, &blk)
78
+ @channel_completion.completion do |channel|
79
+ channel.on_error(&blk)
80
+ end
81
+ end
82
+
83
+ def queue_name
84
+ @queue_def.denormalise
85
+ end
86
+
87
+ private
88
+
89
+ def increment_counter(value=1)
90
+ @message_count += value
91
+ end
92
+ end
data/lib/brown/util.rb ADDED
@@ -0,0 +1,58 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require "brown/amqp_errors"
4
+ require "brown/logger"
5
+
6
+ module Brown::Util
7
+ include Brown::AmqpErrors
8
+ include Brown::Logger
9
+
10
+ def number_of_messages
11
+ status do |num_messages, _|
12
+ yield num_messages
13
+ end
14
+ end
15
+
16
+ def number_of_consumers
17
+ status do |_, num_consumers|
18
+ yield num_consumers
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def open_channel(opts={}, &blk)
25
+ AMQP::Channel.new(Brown.connection) do |channel,_|
26
+ logger.debug { "Opened channel: #{"%#x" % channel.object_id}" }
27
+
28
+ channel.auto_recovery = true
29
+ logger.debug { "Channel auto recovery enabled" }
30
+
31
+ # Set up QoS. If you do not do this then any subscribes will get
32
+ # overwhelmed if there are too many messages.
33
+ prefetch = opts[:prefetch] || 1
34
+
35
+ channel.prefetch(prefetch)
36
+ logger.debug { "AMQP prefetch set to: #{prefetch}" }
37
+
38
+ blk.call(channel)
39
+ end
40
+ end
41
+
42
+ def random(prefix = '', suffix = '')
43
+ "#{prefix}#{SecureRandom.hex(8)}#{suffix}"
44
+ end
45
+
46
+ def option_or_default(options, key, default, &blk)
47
+ if options.is_a?(Hash)
48
+ if options.key?(key)
49
+ v = options.delete(key)
50
+ (blk) ? blk.call(v) : v
51
+ else
52
+ default
53
+ end
54
+ else
55
+ raise ArguementError, "Options must be a Hash."
56
+ end
57
+ end
58
+ end
data/lib/smith.rb ADDED
@@ -0,0 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'brown'
3
+
4
+ Smith = Brown
metadata ADDED
@@ -0,0 +1,196 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: brown
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Richard Heycock
8
+ - Matt Palmer
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-02-08 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: amqp
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '1.4'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '1.4'
28
+ - !ruby/object:Gem::Dependency
29
+ name: envied
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '0.8'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '0.8'
42
+ - !ruby/object:Gem::Dependency
43
+ name: eventmachine-le
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '1.0'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '1.0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: extlib
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '0.9'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '0.9'
70
+ - !ruby/object:Gem::Dependency
71
+ name: murmurhash3
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '0.1'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '0.1'
84
+ - !ruby/object:Gem::Dependency
85
+ name: protobuf
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '3.0'
91
+ type: :runtime
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '3.0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: bundler
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: git-version-bump
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: '0.10'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - "~>"
124
+ - !ruby/object:Gem::Version
125
+ version: '0.10'
126
+ - !ruby/object:Gem::Dependency
127
+ name: rake
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - "~>"
131
+ - !ruby/object:Gem::Version
132
+ version: '10.4'
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: 10.4.2
136
+ type: :development
137
+ prerelease: false
138
+ version_requirements: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - "~>"
141
+ - !ruby/object:Gem::Version
142
+ version: '10.4'
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: 10.4.2
146
+ description:
147
+ email:
148
+ executables:
149
+ - brown
150
+ extensions: []
151
+ extra_rdoc_files: []
152
+ files:
153
+ - ".gitignore"
154
+ - Gemfile
155
+ - Rakefile
156
+ - bin/brown
157
+ - brown.gemspec
158
+ - lib/brown.rb
159
+ - lib/brown/acl_loader.rb
160
+ - lib/brown/acl_lookup.rb
161
+ - lib/brown/agent.rb
162
+ - lib/brown/amqp_errors.rb
163
+ - lib/brown/logger.rb
164
+ - lib/brown/message.rb
165
+ - lib/brown/module_methods.rb
166
+ - lib/brown/queue_definition.rb
167
+ - lib/brown/queue_factory.rb
168
+ - lib/brown/receiver.rb
169
+ - lib/brown/sender.rb
170
+ - lib/brown/util.rb
171
+ - lib/smith.rb
172
+ homepage:
173
+ licenses:
174
+ - GPL-3
175
+ metadata: {}
176
+ post_install_message:
177
+ rdoc_options: []
178
+ require_paths:
179
+ - lib
180
+ required_ruby_version: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ version: '0'
185
+ required_rubygems_version: !ruby/object:Gem::Requirement
186
+ requirements:
187
+ - - ">="
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ requirements: []
191
+ rubyforge_project:
192
+ rubygems_version: 2.2.2
193
+ signing_key:
194
+ specification_version: 4
195
+ summary: Run individual smith agents directly from the command line
196
+ test_files: []