brown 1.0.0

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 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: []