rflow-components-amqp 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.
- data/.gitignore +4 -0
- data/Gemfile +3 -0
- data/Rakefile +15 -0
- data/lib/rflow/components/amqp/extensions.rb +27 -0
- data/lib/rflow/components/amqp/subscriber.rb +113 -0
- data/lib/rflow/components/amqp/version.rb +7 -0
- data/lib/rflow/components/amqp.rb +25 -0
- data/lib/rflow-components-amqp.rb +2 -0
- data/rflow-components-amqp.gemspec +28 -0
- data/schema/amqp_message.avsc +15 -0
- data/spec/extensions_spec.rb +47 -0
- data/spec/schema_spec.rb +29 -0
- data/spec/spec_helper.rb +21 -0
- metadata +126 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'rdoc/task'
|
4
|
+
Bundler::GemHelper.install_tasks
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
7
|
+
t.verbose = true
|
8
|
+
t.rspec_opts = '--tty --color'
|
9
|
+
end
|
10
|
+
|
11
|
+
Rake::RDocTask.new do |rd|
|
12
|
+
rd.main = "README"
|
13
|
+
rd.rdoc_files.include("README", "lib/**/*.rb")
|
14
|
+
rd.rdoc_dir = File.join('doc', 'html')
|
15
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class RFlow
|
2
|
+
module Components
|
3
|
+
module AMQP
|
4
|
+
|
5
|
+
# The set of extensions to add capability to AMQP data types
|
6
|
+
module Extensions
|
7
|
+
|
8
|
+
module AMQPMessageExtension
|
9
|
+
def self.extended(base_data)
|
10
|
+
base_data.data_object ||= {'header' => {}, 'payload' => ''}
|
11
|
+
end
|
12
|
+
|
13
|
+
# Default accessors
|
14
|
+
['header', 'payload'].each do |name|
|
15
|
+
define_method name do |*args|
|
16
|
+
data_object[name]
|
17
|
+
end
|
18
|
+
define_method :"#{name}=" do |*args|
|
19
|
+
data_object[name] = args.first
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'amqp'
|
3
|
+
require 'rflow'
|
4
|
+
|
5
|
+
class RFlow
|
6
|
+
module Components
|
7
|
+
module AMQP
|
8
|
+
|
9
|
+
# By default will use and exclusive, non-durable, auto-deleting,
|
10
|
+
# randomly named queue for subscribing to topic messages with an
|
11
|
+
# empty-string pattern
|
12
|
+
class Subscriber < RFlow::Component
|
13
|
+
output_port :amqp_port
|
14
|
+
output_port :raw_port
|
15
|
+
|
16
|
+
attr_accessor :config, :queue_config
|
17
|
+
|
18
|
+
DEFAULT_CONFIG = {
|
19
|
+
'server' => '127.0.0.1',
|
20
|
+
'port' => 5672,
|
21
|
+
'username' => 'guest',
|
22
|
+
'password' => 'guest',
|
23
|
+
'vhost' => '/',
|
24
|
+
|
25
|
+
'queue_name' => 'asdf',
|
26
|
+
'queue_passive' => false,
|
27
|
+
'queue_durable' => false,
|
28
|
+
'queue_exclusive' => true,
|
29
|
+
'queue_auto_delete' => true,
|
30
|
+
|
31
|
+
'binding_pattern' => '',
|
32
|
+
}
|
33
|
+
|
34
|
+
def configure!(config)
|
35
|
+
@config = DEFAULT_CONFIG.merge config
|
36
|
+
@config['port'] = @config['port'].to_i
|
37
|
+
|
38
|
+
['durable', 'passive', 'exclusive', 'auto_delete'].each do |queue_bool_opt|
|
39
|
+
@config["queue_#{queue_bool_opt}"] = to_boolean(@config["queue_#{queue_bool_opt}"])
|
40
|
+
end
|
41
|
+
|
42
|
+
# Convert the queue parameters into AMQP-friendly sym-keyed
|
43
|
+
# Hash that can be passed directly to underlying AMQP gem
|
44
|
+
# methods
|
45
|
+
@queue_config = {}
|
46
|
+
@config.each do |key, value|
|
47
|
+
md = /queue_(.*)/.match(key.to_s)
|
48
|
+
unless md.nil?
|
49
|
+
@queue_config[md[1].to_sym] = value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
def run!
|
56
|
+
::AMQP.connect(:host => @config['server'], :port => @config['port'], :vhost => @config['vhost'], :username => @config['username'], :password => @config['password']) do |conn|
|
57
|
+
@amqp_connection = conn
|
58
|
+
|
59
|
+
::AMQP::Channel.new(@amqp_connection) do |channel|
|
60
|
+
@amqp_channel = channel
|
61
|
+
@amqp_exchange = @amqp_channel.topic
|
62
|
+
|
63
|
+
::AMQP::Queue.new(@amqp_channel, @config['queue_name'], @queue_config) do |queue|
|
64
|
+
@amqp_queue = queue
|
65
|
+
@amqp_queue.bind(@amqp_exchange, :routing_key => @config['binding_pattern']).subscribe(:ack => true) do |header, payload|
|
66
|
+
RFlow.logger.debug "AMQP message received"
|
67
|
+
processing_event = RFlow::Message::ProcessingEvent.new(instance_uuid, Time.now.utc)
|
68
|
+
|
69
|
+
amqp_message = RFlow::Message.new('RFlow::Message::Data::AMQP::Message')
|
70
|
+
header.to_hash.each {|k,v| amqp_message.data.header[k.to_s] = v}
|
71
|
+
amqp_message.data.payload = payload
|
72
|
+
|
73
|
+
# TODO: Optimize out if not connected
|
74
|
+
raw_message = RFlow::Message.new('RFlow::Message::Data::Raw')
|
75
|
+
raw_message.data.raw = payload
|
76
|
+
|
77
|
+
processing_event.completed_at = Time.now.utc
|
78
|
+
amqp_message.provenance << processing_event
|
79
|
+
raw_message.provenance << processing_event
|
80
|
+
|
81
|
+
amqp_port.send_message amqp_message
|
82
|
+
raw_port.send_message raw_message
|
83
|
+
|
84
|
+
header.ack
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# EM.add_timer(2) do
|
92
|
+
# EM.add_periodic_timer(0) do
|
93
|
+
# @amqp_exchange.publish Array.new(rand(1000)) { rand(256) }.pack('c*'), :routing_key => ""
|
94
|
+
# end
|
95
|
+
# end
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_boolean(string)
|
99
|
+
case string
|
100
|
+
when /^true$/i, '1', true
|
101
|
+
true
|
102
|
+
when /^false/i, '0', false
|
103
|
+
false
|
104
|
+
else
|
105
|
+
raise ArgumentError, "'#{string}' cannot be coerced to a boolean value"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rflow/components/amqp/extensions'
|
2
|
+
require 'rflow/components/amqp/subscriber'
|
3
|
+
|
4
|
+
class RFlow
|
5
|
+
module Components
|
6
|
+
module AMQP
|
7
|
+
# Load the schemas
|
8
|
+
SCHEMA_DIRECTORY = ::File.expand_path(::File.join(::File.dirname(__FILE__), '..', '..', '..', 'schema'))
|
9
|
+
|
10
|
+
SCHEMA_FILES = {
|
11
|
+
'amqp_message.avsc' => 'RFlow::Message::Data::AMQP::Message',
|
12
|
+
}
|
13
|
+
|
14
|
+
SCHEMA_FILES.each do |file_name, data_type_name|
|
15
|
+
schema_string = ::File.read(::File.join(SCHEMA_DIRECTORY, file_name))
|
16
|
+
RFlow::Configuration.add_available_data_type data_type_name, 'avro', schema_string
|
17
|
+
end
|
18
|
+
|
19
|
+
# Load the data extensions
|
20
|
+
RFlow::Configuration.add_available_data_extension('RFlow::Message::Data::AMQP::Message',
|
21
|
+
RFlow::Components::AMQP::Extensions::AMQPMessageExtension)
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "rflow/components/amqp/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "rflow-components-amqp"
|
7
|
+
s.version = RFlow::Components::AMQP::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.required_ruby_version = '~> 1.9'
|
10
|
+
s.authors = ["Michael L. Artz"]
|
11
|
+
s.email = ["michael.artz@redjack.com"]
|
12
|
+
s.homepage = "https://github.com/redjack/rflow-components-amqp"
|
13
|
+
s.summary = %q{AMQP publisher and subscriber component for the RFlow FBP framework}
|
14
|
+
s.description = %q{AMQP publisher and subscriber component for the RFlow FBP framework. Also includes the necessary AMQP::Message message type}
|
15
|
+
|
16
|
+
s.rubyforge_project = "rflow-components-amqp"
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
|
23
|
+
s.add_dependency 'rflow', '~> 0.0.1'
|
24
|
+
s.add_dependency 'amqp', '>= 0.8.0.rc12'
|
25
|
+
|
26
|
+
s.add_development_dependency 'rspec', '~> 2.5.0'
|
27
|
+
s.add_development_dependency 'rake', '~> 0.8.7'
|
28
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
{
|
2
|
+
"type": "record",
|
3
|
+
"name": "Message",
|
4
|
+
"namespace": "org.rflow.message.data.amqp",
|
5
|
+
"aliases": [],
|
6
|
+
"fields": [
|
7
|
+
{"name": "client_ip", "type": ["string", "null"]},
|
8
|
+
{"name": "client_port", "type": ["int", "null"]},
|
9
|
+
{"name": "server_ip", "type": ["string", "null"]},
|
10
|
+
{"name": "server_port", "type": ["int", "null"]},
|
11
|
+
|
12
|
+
{"name": "header", "type": {"type": "map", "values": ["string", "int", "null"]}},
|
13
|
+
{"name": "payload", "type": "bytes"}
|
14
|
+
]
|
15
|
+
}
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper.rb'
|
2
|
+
|
3
|
+
# describe RFlow::Components::File::Extensions::FileExtension do
|
4
|
+
# before(:each) do
|
5
|
+
# @schema_string = RFlow::Configuration.available_data_types['RFlow::Message::Data::File']['avro']
|
6
|
+
# end
|
7
|
+
#
|
8
|
+
# it "should add the extension to RFlow::Configuration" do
|
9
|
+
# RFlow::Configuration.available_data_extensions['RFlow::Message::Data::File'].should include(described_class)
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# it "should set the defaults" do
|
13
|
+
# file = RFlow::Message.new('RFlow::Message::Data::File')
|
14
|
+
#
|
15
|
+
# file.data.path.should == '/'
|
16
|
+
# file.data.size.should == 0
|
17
|
+
# file.data.content.should == ''
|
18
|
+
# file.data.creation_timestamp.should == nil
|
19
|
+
# file.data.modification_timestamp.should == nil
|
20
|
+
# file.data.accessed_timestamp.should == nil
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# it "should correctly use integers or strings for size field" do
|
24
|
+
# file = RFlow::Message.new('RFlow::Message::Data::File')
|
25
|
+
#
|
26
|
+
# file.data.size.should == 0
|
27
|
+
# file.data.size = 10
|
28
|
+
# file.data.size.should == 10
|
29
|
+
# file.data.size = '20'
|
30
|
+
# file.data.size == 20
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# it "should correctly use Time or xmlschema strings for timestamp fields" do
|
34
|
+
# file = RFlow::Message.new('RFlow::Message::Data::File')
|
35
|
+
#
|
36
|
+
# file.data.creation_timestamp.should == nil
|
37
|
+
# now = Time.now
|
38
|
+
#
|
39
|
+
# file.data.creation_timestamp = now
|
40
|
+
# file.data.creation_timestamp.should == Time.xmlschema(now.xmlschema(9))
|
41
|
+
#
|
42
|
+
# file.data.creation_timestamp = now.xmlschema
|
43
|
+
# file.data.creation_timestamp.should == Time.xmlschema(now.xmlschema)
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
#
|
47
|
+
# end
|
data/spec/schema_spec.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper.rb'
|
2
|
+
|
3
|
+
describe 'RFlow::Message::Data::AMQP::Message Avro Schema' do
|
4
|
+
before(:each) do
|
5
|
+
@schema_string = RFlow::Configuration.available_data_types['RFlow::Message::Data::AMQP::Message']['avro']
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should encode and decode an object" do
|
9
|
+
amqp_message = {
|
10
|
+
'header' => {
|
11
|
+
'content_type' => 'application/octet-stream',
|
12
|
+
'delivery_mode' => 2,
|
13
|
+
'priority' => 0,
|
14
|
+
},
|
15
|
+
'payload' =>'PAYLOAD',
|
16
|
+
}
|
17
|
+
|
18
|
+
expect {encode_avro(@schema_string, amqp_message)}.to_not raise_error
|
19
|
+
avro_encoded_amqp_message = encode_avro(@schema_string, amqp_message)
|
20
|
+
|
21
|
+
expect {decode_avro(@schema_string, avro_encoded_amqp_message)}.to_not raise_error
|
22
|
+
decoded_amqp_message = decode_avro(@schema_string, avro_encoded_amqp_message)
|
23
|
+
|
24
|
+
decoded_amqp_message['payload'].should == amqp_message['payload']
|
25
|
+
decoded_amqp_message['header'].should == amqp_message['header']
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'rflow-components-amqp'))
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
RFlow.logger = Logger.new STDOUT
|
6
|
+
|
7
|
+
def decode_avro(schema_string, serialized_object)
|
8
|
+
schema = Avro::Schema.parse(schema_string)
|
9
|
+
serialized_object.force_encoding 'BINARY'
|
10
|
+
sio = StringIO.new(serialized_object)
|
11
|
+
Avro::IO::DatumReader.new(schema, schema).read Avro::IO::BinaryDecoder.new(sio)
|
12
|
+
end
|
13
|
+
|
14
|
+
def encode_avro(schema_string, object)
|
15
|
+
encoded_string = ''
|
16
|
+
encoded_string.force_encoding 'BINARY'
|
17
|
+
schema = Avro::Schema.parse(schema_string)
|
18
|
+
sio = StringIO.new(encoded_string)
|
19
|
+
Avro::IO::DatumWriter.new(schema).write object, Avro::IO::BinaryEncoder.new(sio)
|
20
|
+
encoded_string
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rflow-components-amqp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Michael L. Artz
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-03-17 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rflow
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.0.1
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.0.1
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: amqp
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.8.0.rc12
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.8.0.rc12
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 2.5.0
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.5.0
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rake
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 0.8.7
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 0.8.7
|
78
|
+
description: AMQP publisher and subscriber component for the RFlow FBP framework. Also
|
79
|
+
includes the necessary AMQP::Message message type
|
80
|
+
email:
|
81
|
+
- michael.artz@redjack.com
|
82
|
+
executables: []
|
83
|
+
extensions: []
|
84
|
+
extra_rdoc_files: []
|
85
|
+
files:
|
86
|
+
- .gitignore
|
87
|
+
- Gemfile
|
88
|
+
- Rakefile
|
89
|
+
- lib/rflow-components-amqp.rb
|
90
|
+
- lib/rflow/components/amqp.rb
|
91
|
+
- lib/rflow/components/amqp/extensions.rb
|
92
|
+
- lib/rflow/components/amqp/subscriber.rb
|
93
|
+
- lib/rflow/components/amqp/version.rb
|
94
|
+
- rflow-components-amqp.gemspec
|
95
|
+
- schema/amqp_message.avsc
|
96
|
+
- spec/extensions_spec.rb
|
97
|
+
- spec/schema_spec.rb
|
98
|
+
- spec/spec_helper.rb
|
99
|
+
homepage: https://github.com/redjack/rflow-components-amqp
|
100
|
+
licenses: []
|
101
|
+
post_install_message:
|
102
|
+
rdoc_options: []
|
103
|
+
require_paths:
|
104
|
+
- lib
|
105
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.9'
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
none: false
|
113
|
+
requirements:
|
114
|
+
- - ! '>='
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
requirements: []
|
118
|
+
rubyforge_project: rflow-components-amqp
|
119
|
+
rubygems_version: 1.8.24
|
120
|
+
signing_key:
|
121
|
+
specification_version: 3
|
122
|
+
summary: AMQP publisher and subscriber component for the RFlow FBP framework
|
123
|
+
test_files:
|
124
|
+
- spec/extensions_spec.rb
|
125
|
+
- spec/schema_spec.rb
|
126
|
+
- spec/spec_helper.rb
|