emque-producing 0.0.2

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