emque-producing 0.0.2

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: cc3013b37568f755a5f258d0c18b3a8e346ac219
4
+ data.tar.gz: 28aad21a17a39be148e7d22611df8b4d992c85f6
5
+ SHA512:
6
+ metadata.gz: 997635efddaa40fe279c246a3862917666b4c4d154b6809c9c69e7fe6e3a4281ffac27e8a1638c8556467f764be2cdb62a9b8d67edaf90331541e2d581b733da
7
+ data.tar.gz: 5cebfd302c30b90c1863555cd5b7fce581d67d1278310a05d6ad13f4bf71e573bf9a50311204da6a74e7108de3d7f735654db85905639bf621b14f982e68dfda
data/.gitignore ADDED
@@ -0,0 +1,34 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+
12
+ ## Specific to RubyMotion:
13
+ .dat*
14
+ .repl_history
15
+ build/
16
+
17
+ ## Documentation cache and generated files:
18
+ /.yardoc/
19
+ /_yardoc/
20
+ /doc/
21
+ /rdoc/
22
+
23
+ ## Environment normalisation:
24
+ /.bundle/
25
+ /lib/bundler/man/
26
+
27
+ # for a library or gem, you might want to ignore these files since the code is
28
+ # intended to run in multiple environments; otherwise, check them in:
29
+ # Gemfile.lock
30
+ # .ruby-version
31
+ # .ruby-gemset
32
+
33
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
34
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,64 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ emque-producing (0.0.2)
5
+ oj (~> 2.10.2)
6
+ virtus (~> 1.0.3)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ amq-protocol (1.9.2)
12
+ axiom-types (0.1.1)
13
+ descendants_tracker (~> 0.0.4)
14
+ ice_nine (~> 0.11.0)
15
+ thread_safe (~> 0.3, >= 0.3.1)
16
+ bunny (1.4.1)
17
+ amq-protocol (>= 1.9.2)
18
+ coderay (1.1.0)
19
+ coercible (1.0.0)
20
+ descendants_tracker (~> 0.0.1)
21
+ descendants_tracker (0.0.4)
22
+ thread_safe (~> 0.3, >= 0.3.1)
23
+ diff-lcs (1.2.5)
24
+ equalizer (0.0.9)
25
+ ice_nine (0.11.0)
26
+ method_source (0.8.2)
27
+ oj (2.10.2)
28
+ poseidon (0.0.4)
29
+ pry (0.9.12.4)
30
+ coderay (~> 1.0)
31
+ method_source (~> 0.8)
32
+ slop (~> 3.4)
33
+ rake (10.1.1)
34
+ rspec (3.1.0)
35
+ rspec-core (~> 3.1.0)
36
+ rspec-expectations (~> 3.1.0)
37
+ rspec-mocks (~> 3.1.0)
38
+ rspec-core (3.1.5)
39
+ rspec-support (~> 3.1.0)
40
+ rspec-expectations (3.1.2)
41
+ diff-lcs (>= 1.2.0, < 2.0)
42
+ rspec-support (~> 3.1.0)
43
+ rspec-mocks (3.1.2)
44
+ rspec-support (~> 3.1.0)
45
+ rspec-support (3.1.1)
46
+ slop (3.4.7)
47
+ thread_safe (0.3.4)
48
+ virtus (1.0.3)
49
+ axiom-types (~> 0.1)
50
+ coercible (~> 1.0)
51
+ descendants_tracker (~> 0.0, >= 0.0.3)
52
+ equalizer (~> 0.0, >= 0.0.9)
53
+
54
+ PLATFORMS
55
+ ruby
56
+
57
+ DEPENDENCIES
58
+ bundler (~> 1.0)
59
+ bunny (~> 1.4.1)
60
+ emque-producing!
61
+ poseidon (= 0.0.4)
62
+ pry
63
+ rake
64
+ rspec (~> 3.1)
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 TeamSnap
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,98 @@
1
+ [ ![Codeship Status for
2
+ teamsnap/emque-producing](https://www.codeship.io/projects/2ca7fd90-1785-0132-5f9d-7ab39a5c8237/status)](https://www.codeship.io/projects/34115)
3
+
4
+ # Emque Producing
5
+
6
+ Define and send messages with Ruby to a variety of [message brokers](http://en.wikipedia.org/wiki/Message_broker).
7
+ Currently supported message brokers are [RabbitMQ](https://www.rabbitmq.com) and
8
+ [Kafka](http://kafka.apache.org/).
9
+
10
+ This is a library that pairs nicely with [Emque
11
+ Consuming](https://www.github.com/teamsnap/emque-consuming), a framework for
12
+ consuming and routing messages to your code.
13
+
14
+ ## Installation
15
+
16
+ Add these lines to your application's Gemfile, depending on your message broker:
17
+
18
+ # for RabbitMQ, bunny is used
19
+ gem "emque-producing"
20
+ gem "bunny", "~> 1.4.1"
21
+
22
+ or
23
+
24
+ # for Kafka, poseidon is used
25
+ gem "emque-producing"
26
+ gem "poseidon", "0.0.4"
27
+
28
+ And then execute:
29
+
30
+ $ bundle
31
+
32
+ Or install it yourself as:
33
+
34
+ $ gem install emque-producing
35
+
36
+ ## Usage
37
+
38
+ # configure (likely in a Rails initializer)
39
+ require 'emque-producing'
40
+ Emque::Producing.configure do |c|
41
+ c.app_name = "app"
42
+ c.publishing_adapter = :rabbitmq
43
+ c.rabbitmq_options = { :url => "amqp://guest:guest@localhost:5672" }
44
+ #c.kafka_options = { :seed_brokers => [localhost:9092],
45
+ # :producer_options => {} }
46
+ c.error_handlers << Proc.new {|ex,context|
47
+ # notify/log
48
+ }
49
+ end
50
+
51
+ # create a message class
52
+ class MyMessage
53
+ include Emque::Producing::Message
54
+ topic "topic1"
55
+ message_type "mymessage.new"
56
+
57
+ attribute :first_property, Integer, :required => true
58
+ attribute :another_property, String, :required => true
59
+ end
60
+
61
+ # produce message
62
+ message = MyMessage.new({:first_property => 1, :another_property => "another"})
63
+ message.publish
64
+
65
+ ## Requirements
66
+
67
+ * Ruby 1.9.3 or higher
68
+ * RabbitMQ 3.x
69
+ * Bunny 1.4.x
70
+ * Kafka 0.8.1
71
+ * Poseidon 0.0.4
72
+
73
+ ## Tests
74
+
75
+ To run tests...
76
+
77
+ ```
78
+ rspec
79
+ ```
80
+
81
+ If you would like to test the gem as part of your client, you can update the
82
+ configuration option `publish_messages` to false like so:
83
+ ```ruby
84
+ Emque::Producing.configure do |c|
85
+ c.publish_messages = false
86
+ ...other options
87
+ end
88
+ ```
89
+ This will prevent Emque from actually attempting to make the connection to your
90
+ adapter which may be convenient in the case of CI environments.
91
+
92
+ ## Contributing
93
+
94
+ 1. Fork it ( http://github.com/teamsnap/emquemessages/fork )
95
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
96
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
97
+ 4. Push to the branch (`git push origin my-new-feature`)
98
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ begin
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ task :default => :spec
7
+ rescue LoadError
8
+ # no rspec available
9
+ # end
10
+ end
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib/', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'emque/producing/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "emque-producing"
8
+ spec.version = Emque::Producing::VERSION
9
+ spec.platform = Gem::Platform::RUBY
10
+ spec.authors = ["Emily Dobervich", "Ryan Williams"]
11
+ spec.email = ["emily@teamsnap.com", "ryan.williams@teamsnap.com"]
12
+ spec.summary = %q{Define and send messages to a variety of message brokers}
13
+ spec.description = %q{Define and send messages to a variety of message brokers}
14
+ spec.homepage = ""
15
+ spec.license = "MIT"
16
+ spec.required_ruby_version = '>= 1.9.3'
17
+
18
+ # Manifest
19
+ spec.files = `git ls-files`.split("\n")
20
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_dependency "oj", "~> 2.10.2"
25
+ spec.add_dependency "virtus", "~> 1.0.3"
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.0"
28
+ spec.add_development_dependency "rake"
29
+ spec.add_development_dependency "rspec", "~> 3.1"
30
+ spec.add_development_dependency "pry"
31
+ spec.add_development_dependency "poseidon", "0.0.4"
32
+ spec.add_development_dependency "bunny", "~> 1.4.1"
33
+ end
@@ -0,0 +1 @@
1
+ require "emque/producing"
@@ -0,0 +1,8 @@
1
+ require "virtus"
2
+ require "oj"
3
+ require "emque/producing/version"
4
+ require "emque/producing/producing"
5
+ require "emque/producing/configuration"
6
+ require "emque/producing/logging"
7
+ require "emque/producing/publisher/base"
8
+ require "emque/producing/message/message"
@@ -0,0 +1,24 @@
1
+ module Emque
2
+ module Producing
3
+ class Configuration
4
+ attr_accessor :app_name
5
+ attr_accessor :publishing_adapter
6
+ attr_accessor :kafka_options
7
+ attr_accessor :rabbitmq_options
8
+ attr_accessor :error_handlers
9
+ attr_accessor :log_publish_message
10
+ attr_accessor :publish_messages
11
+
12
+ def initialize
13
+ @app_name = ""
14
+ @publishing_adapter = :rabbitmq
15
+ @error_handlers = []
16
+ @log_publish_message = false
17
+ @publish_messages = true
18
+ @kafka_options = { :seed_brokers => ["localhost:9092"],
19
+ :producer_options => {} }
20
+ @rabbitmq_options = { :url => "amqp://guest:guest@localhost:5672" }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ require "logger"
2
+
3
+ module Emque
4
+ module Producing
5
+ class Logging
6
+ def self.initialize_logger(log_target = STDOUT)
7
+ @logger = Logger.new(log_target)
8
+ @logger.level = Logger::INFO
9
+ @logger
10
+ end
11
+
12
+ def self.logger
13
+ defined?(@logger) ? @logger : initialize_logger
14
+ end
15
+
16
+ def self.logger=(log)
17
+ @logger = log || Logger.new("/dev/null")
18
+ end
19
+
20
+ def logger
21
+ Emque::Producing::Logging.logger
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,146 @@
1
+ require 'securerandom'
2
+
3
+ module Emque
4
+ module Producing
5
+ module Message
6
+ InvalidMessageError = Class.new(StandardError)
7
+ MessagesNotSentError = Class.new(StandardError)
8
+
9
+ module ClassMethods
10
+ def topic(name)
11
+ @topic = name
12
+ end
13
+
14
+ def read_topic
15
+ @topic
16
+ end
17
+
18
+ def message_type(name)
19
+ @message_type = name
20
+ end
21
+
22
+ def read_message_type
23
+ @message_type
24
+ end
25
+
26
+ def private_attribute(name, coercion=nil, opts={})
27
+ @private_attrs ||= []
28
+ @private_attrs << name
29
+ attribute(name, coercion, opts)
30
+ end
31
+
32
+ def private_attrs
33
+ Array(@private_attrs)
34
+ end
35
+ end
36
+
37
+ def self.included(base)
38
+ base.extend(ClassMethods)
39
+ base.send(:include, Virtus.model)
40
+ base.send(:attribute, :partition_key, String, :default => nil, :required => false)
41
+ end
42
+
43
+ def add_metadata
44
+ {
45
+ :metadata =>
46
+ {
47
+ :host => host_name,
48
+ :app => app_name,
49
+ :topic => topic,
50
+ :created_at => formatted_time,
51
+ :uuid => uuid,
52
+ :type => message_type,
53
+ :partition_key => partition_key
54
+ }
55
+ }.merge(public_attributes)
56
+ end
57
+
58
+ def topic
59
+ self.class.read_topic
60
+ end
61
+
62
+ def message_type
63
+ self.class.read_message_type
64
+ end
65
+
66
+ def valid?
67
+ invalid_attributes.empty? && topic && message_type
68
+ end
69
+
70
+ def invalid_attributes
71
+ invalid_attrs = self.class.attribute_set.inject([]) do |attrs, attr|
72
+ attrs << attr.name if attr.required? && self.attributes.fetch(attr.name).nil?
73
+ attrs
74
+ end
75
+ Array(invalid_attrs) - self.class.private_attrs
76
+ end
77
+
78
+ def to_json
79
+ data = self.add_metadata
80
+ Oj.dump(data, :mode => :compat)
81
+ end
82
+
83
+ def publish(publisher=Emque::Producing.publisher)
84
+ log "publishing...", true
85
+ if valid?
86
+ log "valid...", true
87
+ if Emque::Producing.configuration.publish_messages
88
+ sent = publisher.publish(topic, message_type, to_json, partition_key)
89
+ log "sent #{sent}"
90
+ raise MessagesNotSentError.new unless sent
91
+ end
92
+ else
93
+ log "failed...", true
94
+ raise InvalidMessageError.new(invalid_message)
95
+ end
96
+ end
97
+
98
+ private
99
+
100
+ def invalid_message
101
+ if !topic
102
+ "A topic is required"
103
+ elsif !message_type
104
+ "A message type is required"
105
+ else
106
+ "Required attributes #{invalid_attributes} are missing."
107
+ end
108
+ end
109
+
110
+ def host_name
111
+ Socket.gethostbyname(Socket.gethostname).first
112
+ end
113
+
114
+ def formatted_time
115
+ DateTime.now.new_offset(0).to_time.utc.iso8601
116
+ end
117
+
118
+ def uuid
119
+ SecureRandom.uuid
120
+ end
121
+
122
+ def app_name
123
+ Emque::Producing.configuration.app_name || raise("Messages must have an app name configured.")
124
+ end
125
+
126
+ def log(message, include_message = false)
127
+ if Emque::Producing.configuration.log_publish_message
128
+ message = "#{message} #{to_json}" if include_message
129
+ Emque::Producing.logger.info("MESSAGE LOG: #{message}")
130
+ end
131
+ end
132
+
133
+ def public_attributes
134
+ public = self.class.attribute_set.select do |attr|
135
+ attr && !self.class.private_attrs.include?(attr.name)
136
+ end.map(&:name)
137
+ slice_attributes(*public)
138
+ end
139
+
140
+ def slice_attributes(*keys)
141
+ keys.map!(&:to_sym)
142
+ attributes.select { |key, value| keys.include?(key) }
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,43 @@
1
+ module Emque
2
+ module Producing
3
+ class << self
4
+ attr_accessor :publisher
5
+ attr_writer :configuration
6
+
7
+ def configure
8
+ yield(configuration)
9
+ end
10
+
11
+ def configuration
12
+ @configuration ||= Emque::Producing::Configuration.new
13
+ end
14
+
15
+ def host_name
16
+ Socket.gethostbyname(Socket.gethostname).first
17
+ end
18
+
19
+ def publisher
20
+ return @publisher unless @publisher.nil?
21
+
22
+ if (configuration.publishing_adapter == :kafka)
23
+ require "emque/producing/publisher/kafka"
24
+ @publisher = Emque::Producing::Publisher::Kafka.new
25
+ elsif (configuration.publishing_adapter == :rabbitmq)
26
+ require "emque/producing/publisher/rabbitmq"
27
+ @publisher = Emque::Producing::Publisher::RabbitMq.new
28
+ else
29
+ raise "No publisher configured"
30
+ end
31
+ @publisher
32
+ end
33
+
34
+ def logger
35
+ Emque::Producing::Logging.logger
36
+ end
37
+
38
+ def logger=(log)
39
+ Emque::Producing::Logging.logger = log
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,23 @@
1
+ module Emque
2
+ module Producing
3
+ module Publisher
4
+ class Base
5
+ def host_name
6
+ Socket.gethostbyname(Socket.gethostname).first
7
+ end
8
+
9
+ def handle_error(e)
10
+ Emque::Producing.configuration.error_handlers.each do |handler|
11
+ begin
12
+ handler.call(e, nil)
13
+ rescue => ex
14
+ Emque::Producing.logger.error "Producer error hander raised an error"
15
+ Emque::Producing.logger.error ex
16
+ Emque::Producing.logger.error Array(ex.backtrace).join("\n")
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ require "poseidon"
2
+
3
+ module Emque
4
+ module Producing
5
+ module Publisher
6
+ class Kafka < Emque::Producing::Publisher::Base
7
+ def initialize
8
+ @producer = Poseidon::Producer.new(
9
+ Emque::Producing.configuration.kafka_options[:seed_brokers],
10
+ "producer_#{host_name}_#{Process.pid}",
11
+ Emque::Producing.configuration.kafka_options[:producer_options])
12
+ end
13
+
14
+ def publish(topic, message_type, message, key = nil)
15
+ begin
16
+ msg = Poseidon::MessageToSend.new(topic, message, key)
17
+ @producer.send_messages([msg])
18
+ rescue => e
19
+ handle_error(e)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,58 @@
1
+ require "bunny"
2
+ require "thread"
3
+
4
+ module Emque
5
+ module Producing
6
+ module Publisher
7
+ class RabbitMq < Emque::Producing::Publisher::Base
8
+ CONN = Bunny
9
+ .new(Emque::Producing.configuration.rabbitmq_options[:url])
10
+ .tap { |conn|
11
+ conn.start
12
+ }
13
+
14
+ CHANNEL_POOL = Queue
15
+ .new
16
+ .tap { |queue|
17
+ 20.times { |i| queue << CONN.create_channel }
18
+ }
19
+
20
+ def publish(topic, message_type, message, key = nil)
21
+ ch = CHANNEL_POOL.pop
22
+ ch.open if ch.closed?
23
+ begin
24
+ exchange = ch.fanout(topic, :durable => true, :auto_delete => false)
25
+
26
+ # Assumes all messages are mandatory in order to let callers know if
27
+ # the message was not sent. Uses publisher confirms to wait.
28
+ ch.confirm_select
29
+ sent = true
30
+ exchange.on_return do |return_info, properties, content|
31
+ sent = false
32
+ end
33
+
34
+ exchange.publish(
35
+ message,
36
+ :mandatory => true,
37
+ :persistent => true,
38
+ :type => message_type,
39
+ :app_id => Emque::Producing.configuration.app_name,
40
+ :content_type => "application/json")
41
+
42
+ success = ch.wait_for_confirms
43
+ unless success
44
+ Emque::Producing.logger.warn("RabbitMQ Publisher: message was nacked")
45
+ ch.nacked_set.each do |n|
46
+ Emque::Producing.logger.warn("message id: #{n}")
47
+ end
48
+ end
49
+
50
+ return sent
51
+ ensure
52
+ CHANNEL_POOL << ch unless ch.nil?
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,5 @@
1
+ module Emque
2
+ module Producing
3
+ VERSION = "0.0.2"
4
+ end
5
+ end
@@ -0,0 +1,67 @@
1
+ #!/bin/bash
2
+ # Licensed to the Apache Software Foundation (ASF) under one or more
3
+ # contributor license agreements. See the NOTICE file distributed with
4
+ # this work for additional information regarding copyright ownership.
5
+ # The ASF licenses this file to You under the Apache License, Version 2.0
6
+ # (the "License"); you may not use this file except in compliance with
7
+ # the License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ if [ $# -lt 1 ];
18
+ then
19
+ echo "USAGE: $0 classname [opts]"
20
+ exit 1
21
+ fi
22
+
23
+ if [-z "$SCALA_VERSION" ]; then
24
+ SCALA_VERSION=2.8.0
25
+ fi
26
+
27
+ # assume all dependencies have been packaged into one jar with sbt-assembly's task "assembly-package-dependency"
28
+ for file in $KAFKA_PATH/core/target/scala-$SCALA_VERSION/*.jar;
29
+ do
30
+ CLASSPATH=$CLASSPATH:$file
31
+ done
32
+
33
+ for file in $KAFKA_PATH/perf/target/scala-$SCALA_VERSION/kafka*.jar;
34
+ do
35
+ CLASSPATH=$CLASSPATH:$file
36
+ done
37
+
38
+ # classpath addition for release
39
+ for file in $KAFKA_PATH/libs/*.jar;
40
+ do
41
+ CLASSPATH=$CLASSPATH:$file
42
+ done
43
+
44
+ for file in $KAFKA_PATH/kafka*.jar;
45
+ do
46
+ CLASSPATH=$CLASSPATH:$file
47
+ done
48
+
49
+ if [ -z "$KAFKA_JMX_OPTS" ]; then
50
+ KAFKA_JMX_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false "
51
+ fi
52
+
53
+ if [ -z "$KAFKA_OPTS" ]; then
54
+ KAFKA_OPTS="-Xmx512M -server -Dlog4j.configuration=file:$KAFKA_PATH/config/log4j.properties"
55
+ fi
56
+
57
+ if [ $JMX_PORT ]; then
58
+ KAFKA_JMX_OPTS="$KAFKA_JMX_OPTS -Dcom.sun.management.jmxremote.port=$JMX_PORT "
59
+ fi
60
+
61
+ if [ -z "$JAVA_HOME" ]; then
62
+ JAVA="java"
63
+ else
64
+ JAVA="$JAVA_HOME/bin/java"
65
+ fi
66
+
67
+ exec $JAVA $KAFKA_OPTS $KAFKA_JMX_OPTS -cp $CLASSPATH "$@"
@@ -0,0 +1,33 @@
1
+ ROOT_DIRECTORY = File.absolute_path(File.dirname(__FILE__) + "/../")
2
+
3
+ require "test_cluster"
4
+ require "spec_helper"
5
+
6
+ unless File.directory?(File.join(ROOT_DIRECTORY, "kafka_2.8.0-0.8.1"))
7
+ puts "\033[0;32m"
8
+ puts "*" * 83
9
+ puts "Downloading kafka"
10
+ puts "*" * 83
11
+ puts "\033[0;0m"
12
+
13
+ system(
14
+ "cd #{ROOT_DIRECTORY} && curl https://archive.apache.org/dist/kafka/0.8.1/kafka_2.8.0-0.8.1.tgz | tar xz"
15
+ )
16
+ end
17
+
18
+ ENV["KAFKA_PATH"] = File.join(ROOT_DIRECTORY, "kafka_2.8.0-0.8.1")
19
+ ENV["SCALA_VERSION"] = "2.8.0"
20
+
21
+ RSpec.configure do |config|
22
+ config.before(:suite) do
23
+ JavaRunner.remove_tmp
24
+ JavaRunner.set_kafka_path!
25
+ $tc = TestCluster.new
26
+ $tc.start
27
+ sleep 5
28
+ end
29
+
30
+ config.after(:suite) do
31
+ $tc.stop
32
+ end
33
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe Emque::Producing::Configuration do
4
+ subject { Emque::Producing::Configuration.new }
5
+
6
+ it "provides default values" do
7
+ expect(subject.app_name).to eq ""
8
+ expect(subject.error_handlers).to eq []
9
+ expect(subject.kafka_options[:seed_brokers]).to eq ["localhost:9092"]
10
+ end
11
+
12
+ it "allows app_name to be overwritten" do
13
+ subject.app_name = "my app"
14
+ expect(subject.app_name).to eq "my app"
15
+ end
16
+
17
+ it "allows seed_brokers to be overwritten" do
18
+ subject.kafka_options[:seed_brokers] = ["kafka1:9092", "kafka2:9092"]
19
+ expect(subject.kafka_options[:seed_brokers]).to eq ["kafka1:9092", "kafka2:9092"]
20
+ end
21
+ end
@@ -0,0 +1,88 @@
1
+ require "spec_helper"
2
+ require "virtus"
3
+ require "emque/producing/message/message"
4
+
5
+ class TestMessage
6
+ include Emque::Producing::Message
7
+
8
+ topic "queue"
9
+ message_type "queue.new"
10
+
11
+ attribute :test_id, Integer, :required => true
12
+ private_attribute :extra, String, :default => "value"
13
+ end
14
+
15
+ class MessageNoTopic
16
+ include Emque::Producing::Message
17
+ message_type "testing"
18
+ end
19
+
20
+ class MessageNoType
21
+ include Emque::Producing::Message
22
+ topic "testing"
23
+ end
24
+
25
+ describe Emque::Producing::Message do
26
+ before do
27
+ Emque::Producing.configure do |c|
28
+ c.app_name = "apiv3"
29
+ end
30
+ end
31
+
32
+ describe "#to_json" do
33
+ it "creates the metadata" do
34
+ message = TestMessage.new(:test_id => 1)
35
+ metadata = message.add_metadata[:metadata]
36
+ expect(metadata[:app]).to eql("apiv3")
37
+ end
38
+
39
+ it "can be transformed to json" do
40
+ message = Oj.load(TestMessage.new().to_json)
41
+ expect(message["metadata"]["app"]).to eql("apiv3")
42
+ end
43
+
44
+ it "includes valid attributes in json" do
45
+ produced_message = TestMessage.new(:test_id => 1)
46
+ json = produced_message.to_json
47
+ consumed_message = Oj.load(json)
48
+ expect(consumed_message["test_id"]).to eql(1)
49
+ end
50
+ end
51
+
52
+ describe "#retry" do
53
+ pending
54
+ end
55
+
56
+ it "validates the message for missing attributes" do
57
+ message = TestMessage.new()
58
+ expect(message).to_not be_valid
59
+ end
60
+
61
+ it "raises a useful message when trying to send an invalid message" do
62
+ message = TestMessage.new()
63
+ expected_error = Emque::Producing::Message::InvalidMessageError
64
+ expect{message.publish(->{})}.to raise_error(expected_error)
65
+ end
66
+
67
+ it "validates that the message has a topic" do
68
+ message = MessageNoTopic.new
69
+ expected_error = Emque::Producing::Message::InvalidMessageError
70
+ expect{message.publish(->{})}.to raise_error(expected_error, "A topic is required")
71
+ end
72
+
73
+ it "validates that the message has a message type" do
74
+ message = MessageNoType.new
75
+ expected_error = Emque::Producing::Message::InvalidMessageError
76
+ expect{message.publish(->{})}.to raise_error(expected_error, "A message type is required")
77
+ end
78
+
79
+ it "applys a uuid per message" do
80
+ message = TestMessage.new()
81
+ expect(message.add_metadata[:metadata][:uuid]).to_not be_nil
82
+ end
83
+
84
+ it "has the sub type in the metadata" do
85
+ message = TestMessage.new()
86
+ expect(message.add_metadata[:metadata][:type]).to eql("queue.new")
87
+ end
88
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe Emque::Producing do
4
+
5
+ end
@@ -0,0 +1,20 @@
1
+ require "spec_helper"
2
+ require "emque/producing/publisher/kafka"
3
+
4
+ describe Emque::Producing::Publisher do
5
+ describe "#publish" do
6
+ context "when error handler raises an exception" do
7
+ it "handles the exception" do
8
+ expect_any_instance_of(Poseidon::Producer). to receive(:send_messages).and_raise
9
+ Emque::Producing.configure do |c|
10
+ c.error_handlers << Proc.new {|ex,context|
11
+ raise "something"
12
+ }
13
+ end
14
+ Emque::Producing.logger = nil
15
+ publisher = Emque::Producing::Publisher::Kafka.new
16
+ publisher.publish("mytopic", "message.type", "mymessage")
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ $TESTING = true
2
+
3
+ require "pry"
4
+ require "emque-producing"
5
+
6
+ ENV["EMQUE_ENV"] = "test"
7
+
8
+ module VerifyAndResetHelpers
9
+ def verify(object)
10
+ RSpec::Mocks.proxy_for(object).verify
11
+ end
12
+
13
+ def reset(object)
14
+ RSpec::Mocks.proxy_for(object).reset
15
+ end
16
+ end
17
+
18
+ RSpec.configure do |config|
19
+ config.order = "random"
20
+
21
+ config.include VerifyAndResetHelpers
22
+ end
@@ -0,0 +1,201 @@
1
+ # from https://github.com/bpot/poseidon
2
+
3
+ require 'daemon_controller'
4
+
5
+ class TestCluster
6
+ attr_reader :broker, :zookeeper
7
+ def initialize
8
+ @zookeeper = ZookeeperRunner.new
9
+ @broker = BrokerRunner.new(0, 9092)
10
+ end
11
+
12
+ def start
13
+ @zookeeper.start
14
+ @broker.start
15
+ end
16
+
17
+ def stop
18
+ @zookeeper.stop
19
+ @broker.stop
20
+ end
21
+ end
22
+
23
+ class JavaRunner
24
+ def self.remove_tmp
25
+ FileUtils.rm_rf("#{ROOT_DIRECTORY}/tmp")
26
+ end
27
+
28
+ def self.set_kafka_path!
29
+ JavaRunner.kafka_path = File.join(ROOT_DIRECTORY, "kafka-0.8.0-src")
30
+ end
31
+
32
+ def self.kafka_path=(kafka_path)
33
+ @kafka_path = kafka_path
34
+ end
35
+
36
+ def self.kafka_path
37
+ @kafka_path
38
+ end
39
+
40
+ attr_reader :pid
41
+ def initialize(id, start_cmd, port, properties = {})
42
+ @id = id
43
+ @properties = properties
44
+ @pid = nil
45
+ @start_cmd = start_cmd
46
+ @port = port
47
+ end
48
+
49
+ def start
50
+ write_properties
51
+ run
52
+ end
53
+
54
+ def stop
55
+ daemon_controller.stop
56
+ end
57
+
58
+ def without_process
59
+ stop
60
+ begin
61
+ yield
62
+ ensure
63
+ start
64
+ sleep 5
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def daemon_controller
71
+ @dc ||= DaemonController.new(
72
+ :identifier => @id,
73
+ :start_command => "#{@start_cmd} #{config_path} >>#{log_path} 2>&1 & echo $! > #{pid_path}",
74
+ :ping_command => [:tcp, '127.0.0.1', @port],
75
+ :pid_file => pid_path,
76
+ :log_file => log_path,
77
+ :start_timeout => 25
78
+ )
79
+ end
80
+
81
+ def run
82
+ FileUtils.mkdir_p(log_dir)
83
+ FileUtils.mkdir_p(pid_dir)
84
+ daemon_controller.start
85
+ end
86
+
87
+ def write_properties
88
+ FileUtils.mkdir_p(config_dir)
89
+ File.open(config_path, "w+") do |f|
90
+ @properties.each do |k,v|
91
+ f.puts "#{k}=#{v}"
92
+ end
93
+ end
94
+ end
95
+
96
+ def pid_path
97
+ "#{pid_dir}/#{@id}.pid"
98
+ end
99
+
100
+ def pid_dir
101
+ "#{file_path}/pid"
102
+ end
103
+
104
+ def log_path
105
+ "#{log_dir}/#{@id}.log"
106
+ end
107
+
108
+ def log_dir
109
+ "#{file_path}/log"
110
+ end
111
+
112
+ def config_path
113
+ "#{config_dir}/#{@id}.properties"
114
+ end
115
+
116
+ def config_dir
117
+ "#{file_path}/config"
118
+ end
119
+
120
+ def file_path
121
+ ROOT_DIRECTORY + "/tmp/"
122
+ end
123
+ end
124
+
125
+ class BrokerRunner
126
+ DEFAULT_PROPERTIES = {
127
+ "broker.id" => 0,
128
+ "port" => 9092,
129
+ "num.network.threads" => 2,
130
+ "num.io.threads" => 2,
131
+ "socket.send.buffer.bytes" => 1048576,
132
+ "socket.receive.buffer.bytes" => 1048576,
133
+ "socket.request.max.bytes" => 104857600,
134
+ "log.dir" => "#{ROOT_DIRECTORY}/tmp/kafka-logs",
135
+ "num.partitions" => 1,
136
+ "log.flush.interval.messages" => 10000,
137
+ "log.flush.interval.ms" => 1000,
138
+ "log.retention.hours" => 168,
139
+ "log.segment.bytes" => 536870912,
140
+ "log.cleanup.interval.mins" => 1,
141
+ "zookeeper.connect" => "localhost:2181",
142
+ "zookeeper.connection.timeout.ms" => 1000000,
143
+ "kafka.metrics.polling.interval.secs" => 5,
144
+ "kafka.metrics.reporters" => "kafka.metrics.KafkaCSVMetricsReporter",
145
+ "kafka.csv.metrics.dir" => "#{ROOT_DIRECTORY}/tmp/kafka_metrics",
146
+ "kafka.csv.metrics.reporter.enabled" => "false",
147
+ }
148
+
149
+ def initialize(id, port, partition_count = 1)
150
+ @id = id
151
+ @port = port
152
+ @jr = JavaRunner.new("broker_#{id}",
153
+ "#{ROOT_DIRECTORY}/spec/bin/kafka-run-class.sh kafka.Kafka",
154
+ port,
155
+ DEFAULT_PROPERTIES.merge(
156
+ "broker.id" => id,
157
+ "port" => port,
158
+ "log.dir" => "#{ROOT_DIRECTORY}/tmp/kafka-logs_#{id}",
159
+ "num.partitions" => partition_count
160
+ ))
161
+ end
162
+
163
+ def pid
164
+ @jr.pid
165
+ end
166
+
167
+ def start
168
+ @jr.start
169
+ end
170
+
171
+ def stop
172
+ @jr.stop
173
+ end
174
+
175
+ def without_process
176
+ @jr.without_process { yield }
177
+ end
178
+ end
179
+
180
+ class ZookeeperRunner
181
+ def initialize
182
+ @jr = JavaRunner.new("zookeeper",
183
+ "#{ROOT_DIRECTORY}/spec/bin/kafka-run-class.sh org.apache.zookeeper.server.quorum.QuorumPeerMain",
184
+ 2181,
185
+ :dataDir => "#{ROOT_DIRECTORY}/tmp/zookeeper",
186
+ :clientPort => 2181,
187
+ :maxClientCnxns => 0)
188
+ end
189
+
190
+ def pid
191
+ @jr.pid
192
+ end
193
+
194
+ def start
195
+ @jr.start
196
+ end
197
+
198
+ def stop
199
+ @jr.stop
200
+ end
201
+ end
metadata ADDED
@@ -0,0 +1,191 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: emque-producing
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Emily Dobervich
8
+ - Ryan Williams
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-02-06 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: oj
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: 2.10.2
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: 2.10.2
28
+ - !ruby/object:Gem::Dependency
29
+ name: virtus
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: 1.0.3
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: 1.0.3
42
+ - !ruby/object:Gem::Dependency
43
+ name: bundler
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '1.0'
49
+ type: :development
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: rake
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: rspec
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '3.1'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '3.1'
84
+ - !ruby/object:Gem::Dependency
85
+ name: pry
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: poseidon
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - '='
103
+ - !ruby/object:Gem::Version
104
+ version: 0.0.4
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - '='
110
+ - !ruby/object:Gem::Version
111
+ version: 0.0.4
112
+ - !ruby/object:Gem::Dependency
113
+ name: bunny
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: 1.4.1
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - "~>"
124
+ - !ruby/object:Gem::Version
125
+ version: 1.4.1
126
+ description: Define and send messages to a variety of message brokers
127
+ email:
128
+ - emily@teamsnap.com
129
+ - ryan.williams@teamsnap.com
130
+ executables: []
131
+ extensions: []
132
+ extra_rdoc_files: []
133
+ files:
134
+ - ".gitignore"
135
+ - Gemfile
136
+ - Gemfile.lock
137
+ - LICENSE.txt
138
+ - README.md
139
+ - Rakefile
140
+ - emque-producing.gemspec
141
+ - lib/emque-producing.rb
142
+ - lib/emque/producing.rb
143
+ - lib/emque/producing/configuration.rb
144
+ - lib/emque/producing/logging.rb
145
+ - lib/emque/producing/message/message.rb
146
+ - lib/emque/producing/producing.rb
147
+ - lib/emque/producing/publisher/base.rb
148
+ - lib/emque/producing/publisher/kafka.rb
149
+ - lib/emque/producing/publisher/rabbitmq.rb
150
+ - lib/emque/producing/version.rb
151
+ - spec/bin/kafka-run-class.sh
152
+ - spec/kafka_spec_helper.rb
153
+ - spec/producing/configuration_spec.rb
154
+ - spec/producing/message/message_spec.rb
155
+ - spec/producing/producing_spec.rb
156
+ - spec/producing/publisher_spec.rb
157
+ - spec/spec_helper.rb
158
+ - spec/test_cluster.rb
159
+ homepage: ''
160
+ licenses:
161
+ - MIT
162
+ metadata: {}
163
+ post_install_message:
164
+ rdoc_options: []
165
+ require_paths:
166
+ - lib
167
+ required_ruby_version: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - ">="
170
+ - !ruby/object:Gem::Version
171
+ version: 1.9.3
172
+ required_rubygems_version: !ruby/object:Gem::Requirement
173
+ requirements:
174
+ - - ">="
175
+ - !ruby/object:Gem::Version
176
+ version: '0'
177
+ requirements: []
178
+ rubyforge_project:
179
+ rubygems_version: 2.2.2
180
+ signing_key:
181
+ specification_version: 4
182
+ summary: Define and send messages to a variety of message brokers
183
+ test_files:
184
+ - spec/bin/kafka-run-class.sh
185
+ - spec/kafka_spec_helper.rb
186
+ - spec/producing/configuration_spec.rb
187
+ - spec/producing/message/message_spec.rb
188
+ - spec/producing/producing_spec.rb
189
+ - spec/producing/publisher_spec.rb
190
+ - spec/spec_helper.rb
191
+ - spec/test_cluster.rb