rflow 1.0.0a2 → 1.0.0a3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.ruby-version +1 -1
- data/README.VAGRANT +63 -0
- data/README.md +118 -33
- data/Vagrantfile +53 -0
- data/bin/rflow +6 -1
- data/example/basic_extensions.rb +7 -8
- data/example/http_extensions.rb +7 -8
- data/lib/rflow/broker.rb +18 -0
- data/lib/rflow/child_process.rb +3 -1
- data/lib/rflow/component.rb +51 -61
- data/lib/rflow/component/port.rb +24 -15
- data/lib/rflow/configuration.rb +1 -0
- data/lib/rflow/configuration/connection.rb +35 -17
- data/lib/rflow/configuration/ruby_dsl.rb +47 -9
- data/lib/rflow/connection.rb +13 -9
- data/lib/rflow/connections/zmq_connection.rb +46 -3
- data/lib/rflow/daemon_process.rb +1 -1
- data/lib/rflow/master.rb +8 -1
- data/lib/rflow/shard.rb +8 -2
- data/lib/rflow/version.rb +1 -1
- data/rflow.gemspec +6 -6
- data/spec/fixtures/extensions_ints.rb +7 -8
- data/spec/rflow/component/port_spec.rb +16 -22
- data/spec/rflow/components/clock_spec.rb +12 -17
- data/spec/rflow/configuration/ruby_dsl_spec.rb +234 -46
- data/spec/rflow/configuration_spec.rb +5 -5
- data/spec/rflow/forward_to_input_port_spec.rb +10 -18
- data/spec/rflow/forward_to_output_port_spec.rb +6 -13
- data/spec/rflow/logger_spec.rb +6 -6
- data/spec/rflow/message/data/raw_spec.rb +3 -3
- data/spec/rflow/message_spec.rb +16 -16
- data/spec/rflow_spec.rb +37 -37
- data/spec/spec_helper.rb +3 -5
- metadata +20 -17
data/lib/rflow/connection.rb
CHANGED
@@ -7,6 +7,8 @@ class RFlow
|
|
7
7
|
case config.type
|
8
8
|
when 'RFlow::Configuration::ZMQConnection'
|
9
9
|
RFlow::Connections::ZMQConnection.new(config)
|
10
|
+
when 'RFlow::Configuration::BrokeredZMQConnection'
|
11
|
+
RFlow::Connections::BrokeredZMQConnection.new(config)
|
10
12
|
else
|
11
13
|
raise ArgumentError, "Only ZMQConnections currently supported"
|
12
14
|
end
|
@@ -18,6 +20,7 @@ class RFlow
|
|
18
20
|
protected
|
19
21
|
attr_reader :recv_callback
|
20
22
|
|
23
|
+
public
|
21
24
|
def initialize(config)
|
22
25
|
@config = config
|
23
26
|
@uuid = config.uuid
|
@@ -56,6 +59,9 @@ class RFlow
|
|
56
59
|
def recv_callback
|
57
60
|
@recv_callback ||= Proc.new {|message|}
|
58
61
|
end
|
62
|
+
|
63
|
+
def input_port_key; config.input_port_key; end
|
64
|
+
def output_port_key; config.output_port_key; end
|
59
65
|
end
|
60
66
|
|
61
67
|
# Primarily for testing purposes. Captures whatever messages are sent on it.
|
@@ -77,14 +83,13 @@ class RFlow
|
|
77
83
|
# contain other components within it, shuttling messages in and out without
|
78
84
|
# making the internal component visible to the larger RFlow network.
|
79
85
|
class ForwardToOutputPort < Connection
|
80
|
-
def initialize(
|
86
|
+
def initialize(target_port)
|
81
87
|
super(RFlow::Configuration::NullConfiguration.new)
|
82
|
-
@
|
83
|
-
@port_name = port_name.to_sym
|
88
|
+
@target_port = target_port
|
84
89
|
end
|
85
90
|
|
86
91
|
def send_message(message)
|
87
|
-
@
|
92
|
+
@target_port.send_message(message)
|
88
93
|
end
|
89
94
|
end
|
90
95
|
|
@@ -93,15 +98,14 @@ class RFlow
|
|
93
98
|
# contain other components within it, shuttling messages in and out without
|
94
99
|
# making the internal component visible to the larger RFlow network.
|
95
100
|
class ForwardToInputPort < Connection
|
96
|
-
def initialize(
|
101
|
+
def initialize(target_port)
|
97
102
|
super(RFlow::Configuration::NullConfiguration.new)
|
98
|
-
@receiver =
|
99
|
-
@
|
100
|
-
@port_key = port_key
|
103
|
+
@receiver = target_port.component
|
104
|
+
@target_port = target_port
|
101
105
|
end
|
102
106
|
|
103
107
|
def send_message(message)
|
104
|
-
@receiver.process_message(@
|
108
|
+
@receiver.process_message(@target_port, nil, self, message)
|
105
109
|
end
|
106
110
|
end
|
107
111
|
end
|
@@ -1,6 +1,11 @@
|
|
1
|
-
|
1
|
+
begin
|
2
|
+
require 'em-zeromq'
|
3
|
+
rescue Exception => e
|
4
|
+
raise LoadError, 'Error loading ZeroMQ; perhaps the wrong system library version is installed?'
|
5
|
+
end
|
2
6
|
require 'rflow/connection'
|
3
7
|
require 'rflow/message'
|
8
|
+
require 'rflow/broker'
|
4
9
|
|
5
10
|
class RFlow
|
6
11
|
module Connections
|
@@ -9,7 +14,8 @@ class RFlow
|
|
9
14
|
attr_accessor :zmq_context
|
10
15
|
|
11
16
|
def create_zmq_context
|
12
|
-
|
17
|
+
version = LibZMQ::version
|
18
|
+
RFlow.logger.debug { "Creating a new ZeroMQ context; ZeroMQ version is #{version[:major]}.#{version[:minor]}.#{version[:patch]}" }
|
13
19
|
if EM.reactor_running?
|
14
20
|
raise RuntimeError, "EventMachine reactor is running when attempting to create a ZeroMQ context"
|
15
21
|
end
|
@@ -22,7 +28,7 @@ class RFlow
|
|
22
28
|
end
|
23
29
|
end
|
24
30
|
|
25
|
-
def zmq_context;
|
31
|
+
def zmq_context; ZMQConnection.zmq_context; end
|
26
32
|
|
27
33
|
private
|
28
34
|
attr_accessor :input_socket, :output_socket
|
@@ -94,5 +100,42 @@ class RFlow
|
|
94
100
|
true
|
95
101
|
end
|
96
102
|
end
|
103
|
+
|
104
|
+
class BrokeredZMQConnection < ZMQConnection
|
105
|
+
end
|
106
|
+
|
107
|
+
# The broker process responsible for shuttling messages back and forth on a
|
108
|
+
# many-to-many pipeline link. (Solutions without a broker only allow a
|
109
|
+
# 1-to-many or many-to-1 connection.)
|
110
|
+
class ZMQStreamer < Broker
|
111
|
+
private
|
112
|
+
attr_reader :connection, :context, :back, :front
|
113
|
+
|
114
|
+
public
|
115
|
+
def initialize(config)
|
116
|
+
@connection = config.connection
|
117
|
+
super("broker-#{connection.name}", 'Broker')
|
118
|
+
end
|
119
|
+
|
120
|
+
def run_process
|
121
|
+
version = LibZMQ::version
|
122
|
+
RFlow.logger.debug { "Creating a new ZeroMQ context; ZeroMQ version is #{version[:major]}.#{version[:minor]}.#{version[:patch]}" }
|
123
|
+
@context = ZMQ::Context.new
|
124
|
+
RFlow.logger.debug { "Connecting message broker to route from #{connection.options['output_address']} to #{connection.options['input_address']}" }
|
125
|
+
@back = context.socket(ZMQ::PULL)
|
126
|
+
back.bind(connection.options['output_address'])
|
127
|
+
@front = context.socket(ZMQ::PUSH)
|
128
|
+
front.bind(connection.options['input_address'])
|
129
|
+
ZMQ::Proxy.new(back, front)
|
130
|
+
back.close
|
131
|
+
front.close
|
132
|
+
rescue Exception => e
|
133
|
+
RFlow.logger.error "Error running message broker: #{e.class}: #{e.message}, because: #{e.backtrace.inspect}"
|
134
|
+
ensure
|
135
|
+
back.close if back
|
136
|
+
front.close if front
|
137
|
+
context.terminate if context
|
138
|
+
end
|
139
|
+
end
|
97
140
|
end
|
98
141
|
end
|
data/lib/rflow/daemon_process.rb
CHANGED
data/lib/rflow/master.rb
CHANGED
@@ -1,15 +1,18 @@
|
|
1
1
|
require 'rflow/daemon_process'
|
2
2
|
require 'rflow/pid_file'
|
3
3
|
require 'rflow/shard'
|
4
|
+
require 'rflow/broker'
|
4
5
|
|
5
6
|
class RFlow
|
6
7
|
class Master < DaemonProcess
|
7
8
|
attr_reader :shards
|
9
|
+
attr_reader :brokers
|
8
10
|
|
9
11
|
def initialize(config)
|
10
12
|
super(config['rflow.application_name'], 'Master')
|
11
13
|
@pid_file = PIDFile.new(config['rflow.pid_file_path'])
|
12
14
|
@shards = config.shards.map {|config| Shard.new(config) }
|
15
|
+
@brokers = config.connections.flat_map(&:brokers).map {|config| Broker.build(config) }
|
13
16
|
end
|
14
17
|
|
15
18
|
def run!
|
@@ -20,11 +23,15 @@ class RFlow
|
|
20
23
|
end
|
21
24
|
|
22
25
|
def spawn_subprocesses
|
26
|
+
RFlow.logger.debug "Running #{brokers.count} brokers" if brokers.count > 0
|
27
|
+
brokers.each(&:spawn!)
|
28
|
+
RFlow.logger.debug "#{brokers.count} brokers started: #{brokers.map { |w| "#{w.name} (#{w.pid})" }.join(", ")}" if brokers.count > 0
|
29
|
+
|
23
30
|
shards.each(&:run!)
|
24
31
|
end
|
25
32
|
|
26
33
|
def subprocesses
|
27
|
-
shards.flat_map(&:workers)
|
34
|
+
brokers + shards.flat_map(&:workers)
|
28
35
|
end
|
29
36
|
|
30
37
|
def run_process
|
data/lib/rflow/shard.rb
CHANGED
@@ -38,11 +38,17 @@ class RFlow
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
+
# Connect all inputs before all outputs, so connection types that require a 'server'
|
42
|
+
# to be established before a 'client' can connect can get themselves ready.
|
41
43
|
def connect_components!
|
42
44
|
RFlow.logger.debug "Connecting components"
|
43
45
|
@components.each do |component|
|
44
|
-
RFlow.logger.debug "Connecting component '#{component.name}' (#{component.uuid})"
|
45
|
-
component.
|
46
|
+
RFlow.logger.debug "Connecting inputs for component '#{component.name}' (#{component.uuid})"
|
47
|
+
component.connect_inputs!
|
48
|
+
end
|
49
|
+
@components.each do |component|
|
50
|
+
RFlow.logger.debug "Connecting outputs for component '#{component.name}' (#{component.uuid})"
|
51
|
+
component.connect_outputs!
|
46
52
|
end
|
47
53
|
end
|
48
54
|
|
data/lib/rflow/version.rb
CHANGED
data/rflow.gemspec
CHANGED
@@ -26,11 +26,11 @@ Gem::Specification.new do |s|
|
|
26
26
|
s.add_dependency "activerecord", "~> 3.2"
|
27
27
|
|
28
28
|
s.add_dependency "avro", "~> 1.7.5"
|
29
|
-
s.add_dependency "em-zeromq", "
|
29
|
+
s.add_dependency "em-zeromq", "0.5.0"
|
30
30
|
|
31
|
-
s.add_development_dependency "bundler", "~> 1.
|
32
|
-
s.add_development_dependency "rspec", "~>
|
33
|
-
s.add_development_dependency "rspec-collection_matchers", "~>
|
34
|
-
s.add_development_dependency "rake", ">=
|
35
|
-
s.add_development_dependency "yard", "~> 0.8
|
31
|
+
s.add_development_dependency "bundler", "~> 1.6"
|
32
|
+
s.add_development_dependency "rspec", "~> 3.0"
|
33
|
+
s.add_development_dependency "rspec-collection_matchers", "~> 1.0"
|
34
|
+
s.add_development_dependency "rake", ">= 10.3"
|
35
|
+
s.add_development_dependency "yard", "~> 0.8"
|
36
36
|
end
|
@@ -13,21 +13,20 @@ end
|
|
13
13
|
RFlow::Configuration.add_available_data_extension('RFlow::Message::Data::Integer', SimpleDataExtension)
|
14
14
|
|
15
15
|
class RFlow::Components::FileOutput < RFlow::Component
|
16
|
-
attr_accessor :output_file_path
|
16
|
+
attr_accessor :output_file_path
|
17
17
|
input_port :in
|
18
18
|
|
19
19
|
def configure!(config)
|
20
20
|
self.output_file_path = config['output_file_path']
|
21
|
-
self.output_file = File.new output_file_path, 'w+'
|
22
21
|
end
|
23
22
|
|
24
23
|
def process_message(input_port, input_port_key, connection, message)
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
24
|
+
File.open(output_file_path, 'a') do |f|
|
25
|
+
f.flock(File::LOCK_EX)
|
26
|
+
f.puts message.data.data_object.inspect
|
27
|
+
f.flush
|
28
|
+
f.flock(File::LOCK_UN)
|
29
|
+
end
|
31
30
|
end
|
32
31
|
end
|
33
32
|
|
@@ -4,17 +4,13 @@ class RFlow
|
|
4
4
|
class Component
|
5
5
|
describe Port do
|
6
6
|
it "should not be connected" do
|
7
|
-
described_class.new.
|
7
|
+
expect(described_class.new(nil)).not_to be_connected
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
11
|
describe HashPort do
|
12
12
|
it "should not be connected" do
|
13
|
-
|
14
|
-
config.stub(:name).and_return('port')
|
15
|
-
config.stub(:uuid).and_return('1')
|
16
|
-
|
17
|
-
described_class.new(config).should_not be_connected
|
13
|
+
expect(described_class.new(nil)).not_to be_connected
|
18
14
|
end
|
19
15
|
end
|
20
16
|
|
@@ -22,17 +18,16 @@ class RFlow
|
|
22
18
|
context "#connect!" do
|
23
19
|
it "should be connected" do
|
24
20
|
connection = double('connection')
|
25
|
-
connection.
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
config.stub(:uuid).and_return('1')
|
21
|
+
allow(connection).to receive(:name)
|
22
|
+
allow(connection).to receive(:uuid)
|
23
|
+
allow(connection).to receive(:input_port_key)
|
24
|
+
expect(connection).to receive(:connect_input!)
|
30
25
|
|
31
|
-
described_class.new(
|
26
|
+
described_class.new(nil).tap do |port|
|
32
27
|
port.add_connection(nil, connection)
|
33
|
-
port.
|
28
|
+
expect(port).not_to be_connected
|
34
29
|
port.connect!
|
35
|
-
port.
|
30
|
+
expect(port).to be_connected
|
36
31
|
end
|
37
32
|
end
|
38
33
|
end
|
@@ -42,17 +37,16 @@ class RFlow
|
|
42
37
|
context "#connect!" do
|
43
38
|
it "should be connected" do
|
44
39
|
connection = double('connection')
|
45
|
-
connection.
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
port_config.stub(:uuid).and_return('1')
|
40
|
+
allow(connection).to receive(:name)
|
41
|
+
allow(connection).to receive(:uuid)
|
42
|
+
allow(connection).to receive(:input_port_key)
|
43
|
+
expect(connection).to receive(:connect_output!)
|
50
44
|
|
51
|
-
described_class.new(
|
45
|
+
described_class.new(nil).tap do |port|
|
52
46
|
port.add_connection(nil, connection)
|
53
|
-
port.
|
47
|
+
expect(port).not_to be_connected
|
54
48
|
port.connect!
|
55
|
-
port.
|
49
|
+
expect(port).to be_connected
|
56
50
|
end
|
57
51
|
end
|
58
52
|
end
|
@@ -7,15 +7,10 @@ class RFlow
|
|
7
7
|
ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
|
8
8
|
Configuration.migrate_database
|
9
9
|
end
|
10
|
-
let(:config) do
|
11
|
-
RFlow::Configuration::Component.new.tap do |c|
|
12
|
-
c.output_ports << RFlow::Configuration::OutputPort.new(name: 'tick_port')
|
13
|
-
end
|
14
|
-
end
|
15
10
|
let(:message_connection) { RFlow::MessageCollectingConnection.new }
|
16
11
|
|
17
12
|
def clock(args = {})
|
18
|
-
Clock.new
|
13
|
+
Clock.new.tap do |c|
|
19
14
|
c.configure! args
|
20
15
|
c.tick_port.connect!
|
21
16
|
c.tick_port.add_connection nil, message_connection
|
@@ -26,44 +21,44 @@ class RFlow
|
|
26
21
|
|
27
22
|
it 'defaults configuration nicely' do
|
28
23
|
clock.tap do |c|
|
29
|
-
c.clock_name.
|
30
|
-
c.tick_interval.
|
24
|
+
expect(c.clock_name).to eq('Clock')
|
25
|
+
expect(c.tick_interval).to eq(1)
|
31
26
|
end
|
32
27
|
end
|
33
28
|
|
34
29
|
it 'supports name overrides' do
|
35
30
|
clock('name' => 'testname').tap do |c|
|
36
|
-
c.clock_name.
|
31
|
+
expect(c.clock_name).to eq('testname')
|
37
32
|
end
|
38
33
|
end
|
39
34
|
|
40
35
|
it 'supports interval overrides for floats' do
|
41
36
|
clock('tick_interval' => 1.5).tap do |c|
|
42
|
-
c.tick_interval.
|
37
|
+
expect(c.tick_interval).to eq(1.5)
|
43
38
|
end
|
44
39
|
end
|
45
40
|
|
46
41
|
it 'supports interval overrides for strings' do
|
47
42
|
clock('tick_interval' => '1.5').tap do |c|
|
48
|
-
c.tick_interval.
|
43
|
+
expect(c.tick_interval).to eq(1.5)
|
49
44
|
end
|
50
45
|
end
|
51
46
|
|
52
47
|
it 'should register a timer' do
|
53
|
-
EventMachine::PeriodicTimer.
|
48
|
+
expect(EventMachine::PeriodicTimer).to receive(:new).with(1)
|
54
49
|
clock.run!
|
55
50
|
end
|
56
51
|
|
57
52
|
it 'should generate a tick message when asked' do
|
58
53
|
clock.tap do |c|
|
59
54
|
now = Integer(Time.now.to_f * 1000)
|
60
|
-
messages.
|
55
|
+
expect(messages).to be_empty
|
61
56
|
c.tick
|
62
|
-
messages.
|
57
|
+
expect(messages).to have(1).message
|
63
58
|
messages.first.tap do |m|
|
64
|
-
m.data_type_name.
|
65
|
-
m.data.name.
|
66
|
-
m.data.timestamp.
|
59
|
+
expect(m.data_type_name).to eq('RFlow::Message::Clock::Tick')
|
60
|
+
expect(m.data.name).to eq('Clock')
|
61
|
+
expect(m.data.timestamp).to be >= now
|
67
62
|
end
|
68
63
|
end
|
69
64
|
end
|
@@ -12,10 +12,10 @@ class RFlow
|
|
12
12
|
it "should correctly process an empty DSL" do
|
13
13
|
described_class.configure {}
|
14
14
|
|
15
|
-
Shard.
|
16
|
-
Component.
|
17
|
-
Port.
|
18
|
-
Connection.
|
15
|
+
expect(Shard).to have(0).shards
|
16
|
+
expect(Component).to have(0).components
|
17
|
+
expect(Port).to have(0).ports
|
18
|
+
expect(Connection).to have(0).connections
|
19
19
|
end
|
20
20
|
|
21
21
|
it "should correctly process a component declaration" do
|
@@ -23,15 +23,15 @@ class RFlow
|
|
23
23
|
c.component 'boom', 'town', 'opt1' => 'OPT1', 'opt2' => 'OPT2'
|
24
24
|
end
|
25
25
|
|
26
|
-
Shard.
|
27
|
-
Component.
|
28
|
-
Port.
|
29
|
-
Connection.
|
26
|
+
expect(Shard).to have(1).shard
|
27
|
+
expect(Component).to have(1).component
|
28
|
+
expect(Port).to have(0).ports
|
29
|
+
expect(Connection).to have(0).connections
|
30
30
|
|
31
31
|
Component.first.tap do |c|
|
32
|
-
c.name.
|
33
|
-
c.specification.
|
34
|
-
c.options.
|
32
|
+
expect(c.name).to eq('boom')
|
33
|
+
expect(c.specification).to eq('town')
|
34
|
+
expect(c.options).to eq('opt1' => 'OPT1', 'opt2' => 'OPT2')
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
@@ -45,38 +45,38 @@ class RFlow
|
|
45
45
|
c.connect 'first#out[outkey]' => 'second#in[inkey]'
|
46
46
|
end
|
47
47
|
|
48
|
-
Shard.
|
49
|
-
Component.
|
50
|
-
Port.
|
51
|
-
Connection.
|
48
|
+
expect(Shard).to have(1).shard
|
49
|
+
expect(Component).to have(2).components
|
50
|
+
expect(Port).to have(2).ports
|
51
|
+
expect(Connection).to have(4).connections
|
52
52
|
|
53
53
|
first_component = Component.where(name: 'first').first.tap do |component|
|
54
|
-
component.specification.
|
55
|
-
component.
|
56
|
-
component.
|
57
|
-
component.output_ports.first.name.
|
54
|
+
expect(component.specification).to eq('First')
|
55
|
+
expect(component).to have(0).input_ports
|
56
|
+
expect(component).to have(1).output_port
|
57
|
+
expect(component.output_ports.first.name).to eq('out')
|
58
58
|
|
59
|
-
component.output_ports.first.
|
59
|
+
expect(component.output_ports.first).to have(4).connections
|
60
60
|
component.output_ports.first.connections.tap do |connections|
|
61
|
-
connections[0].input_port_key.
|
62
|
-
connections[0].output_port_key.
|
63
|
-
connections[1].input_port_key.
|
64
|
-
connections[1].output_port_key.
|
65
|
-
connections[2].input_port_key.
|
66
|
-
connections[2].output_port_key.
|
67
|
-
connections[3].input_port_key.
|
68
|
-
connections[3].output_port_key.
|
61
|
+
expect(connections[0].input_port_key).to be_nil
|
62
|
+
expect(connections[0].output_port_key).to be_nil
|
63
|
+
expect(connections[1].input_port_key).to eq('inkey')
|
64
|
+
expect(connections[1].output_port_key).to be_nil
|
65
|
+
expect(connections[2].input_port_key).to be_nil
|
66
|
+
expect(connections[2].output_port_key).to eq('outkey')
|
67
|
+
expect(connections[3].input_port_key).to eq('inkey')
|
68
|
+
expect(connections[3].output_port_key).to eq('outkey')
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
72
72
|
Component.where(name: 'second').first.tap do |component|
|
73
|
-
component.specification.
|
74
|
-
component.
|
75
|
-
component.input_ports.first.name.
|
76
|
-
component.
|
73
|
+
expect(component.specification).to eq('Second')
|
74
|
+
expect(component).to have(1).input_port
|
75
|
+
expect(component.input_ports.first.name).to eq('in')
|
76
|
+
expect(component).to have(0).output_ports
|
77
77
|
|
78
|
-
component.input_ports.first.
|
79
|
-
component.input_ports.first.connections.
|
78
|
+
expect(component.input_ports.first).to have(4).connections
|
79
|
+
expect(component.input_ports.first.connections).to eq(first_component.output_ports.first.connections)
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
@@ -93,39 +93,227 @@ class RFlow
|
|
93
93
|
s.component 'fourth', 'Fourth'
|
94
94
|
end
|
95
95
|
|
96
|
+
c.process "s3", :count => 10 do |s|
|
97
|
+
s.component 'fifth', 'Fifth'
|
98
|
+
end
|
99
|
+
|
96
100
|
c.shard "s-ignored", :type => :process, :count => 10 do
|
97
101
|
# ignored because there are no components
|
98
102
|
end
|
99
103
|
|
100
|
-
c.
|
104
|
+
c.thread "s4", :count => 10 do |s|
|
105
|
+
s.component 'sixth', 'Sixth'
|
106
|
+
end
|
107
|
+
|
108
|
+
c.shard "s5", :type => :thread, :count => 10 do |s|
|
109
|
+
s.component 'seventh', 'Seventh'
|
110
|
+
end
|
111
|
+
|
112
|
+
c.component 'eighth', 'Eighth'
|
101
113
|
|
102
114
|
c.connect 'first#out' => 'second#in'
|
103
115
|
c.connect 'second#out[outkey]' => 'third#in[inkey]'
|
104
116
|
c.connect 'second#out' => 'third#in2'
|
105
117
|
c.connect 'third#out' => 'fourth#in'
|
106
118
|
c.connect 'third#out' => 'fifth#in'
|
119
|
+
c.connect 'third#out' => 'sixth#in'
|
107
120
|
end
|
108
121
|
|
109
|
-
Shard.
|
110
|
-
Component.
|
111
|
-
Port.
|
112
|
-
Connection.
|
122
|
+
expect(Shard).to have(6).shards
|
123
|
+
expect(Component).to have(8).components
|
124
|
+
expect(Port).to have(9).ports
|
125
|
+
expect(Connection).to have(6).connections
|
113
126
|
|
114
127
|
Shard.all.tap do |shards|
|
115
|
-
shards.map(&:name).
|
116
|
-
shards.
|
117
|
-
shards.
|
118
|
-
shards.
|
128
|
+
expect(shards.map(&:name)).to eq(['DEFAULT', 's1', 's2', 's3', 's4', 's5'])
|
129
|
+
expect(shards.map(&:type)).to eq((['RFlow::Configuration::ProcessShard'] * 4) + (['RFlow::Configuration::ThreadShard'] * 2))
|
130
|
+
expect(shards.first.components.all.map(&:name)).to eq(['first', 'eighth'])
|
131
|
+
expect(shards.second.components.all.map(&:name)).to eq(['second'])
|
132
|
+
expect(shards.third.components.all.map(&:name)).to eq(['third', 'fourth'])
|
133
|
+
expect(shards.fourth.components.all.map(&:name)).to eq(['fifth'])
|
119
134
|
end
|
120
135
|
|
121
|
-
Port.all.map(&:name).
|
136
|
+
expect(Port.all.map(&:name)).to eq(['out', 'in', 'out', 'in', 'in2', 'out', 'in', 'in', 'in'])
|
122
137
|
|
123
|
-
Connection.all.map(&:name).
|
138
|
+
expect(Connection.all.map(&:name)).to eq(
|
124
139
|
['first#out=>second#in',
|
125
140
|
'second#out[outkey]=>third#in[inkey]',
|
126
141
|
'second#out=>third#in2',
|
127
142
|
'third#out=>fourth#in',
|
128
|
-
'third#out=>fifth#in'
|
143
|
+
'third#out=>fifth#in',
|
144
|
+
'third#out=>sixth#in'])
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should generate PUSH-PULL inproc ZeroMQ connections for in-shard connections" do
|
148
|
+
described_class.configure do |c|
|
149
|
+
|
150
|
+
c.shard "s1", :process => 1 do |s|
|
151
|
+
s.component 'first', 'First', :opt1 => 'opt1'
|
152
|
+
s.component 'second', 'Second', :opt1 => 'opt1', "opt2" => "opt2"
|
153
|
+
end
|
154
|
+
|
155
|
+
c.connect 'first#out' => 'second#in'
|
156
|
+
end
|
157
|
+
|
158
|
+
expect(Shard).to have(1).shards
|
159
|
+
expect(Component).to have(2).components
|
160
|
+
expect(Port).to have(2).ports
|
161
|
+
expect(Connection).to have(1).connections
|
162
|
+
|
163
|
+
Connection.first.tap do |conn|
|
164
|
+
expect(conn.type).to eq('RFlow::Configuration::ZMQConnection')
|
165
|
+
expect(conn.name).to eq('first#out=>second#in')
|
166
|
+
expect(conn.output_port_key).to be_nil
|
167
|
+
expect(conn.input_port_key).to be_nil
|
168
|
+
conn.options.tap do |opts|
|
169
|
+
expect(opts['output_socket_type']).to eq('PUSH')
|
170
|
+
expect(opts['output_address']).to eq("inproc://rflow.#{conn.uuid}")
|
171
|
+
expect(opts['output_responsibility']).to eq('connect')
|
172
|
+
expect(opts['input_socket_type']).to eq('PULL')
|
173
|
+
expect(opts['input_address']).to eq("inproc://rflow.#{conn.uuid}")
|
174
|
+
expect(opts['input_responsibility']).to eq('bind')
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
it "should generate PUSH-PULL ipc ZeroMQ connections for one-to-one inter-shard connections" do
|
180
|
+
described_class.configure do |c|
|
181
|
+
|
182
|
+
c.shard "s1", :process => 1 do |s|
|
183
|
+
s.component 'first', 'First', :opt1 => 'opt1'
|
184
|
+
end
|
185
|
+
|
186
|
+
c.shard "s2", :process => 1 do |s|
|
187
|
+
s.component 'second', 'Second', :opt1 => 'opt1', "opt2" => "opt2"
|
188
|
+
end
|
189
|
+
|
190
|
+
c.connect 'first#out' => 'second#in'
|
191
|
+
end
|
192
|
+
|
193
|
+
expect(Shard).to have(2).shards
|
194
|
+
expect(Component).to have(2).components
|
195
|
+
expect(Port).to have(2).ports
|
196
|
+
expect(Connection).to have(1).connections
|
197
|
+
|
198
|
+
Connection.first.tap do |conn|
|
199
|
+
expect(conn.type).to eq('RFlow::Configuration::ZMQConnection')
|
200
|
+
expect(conn.name).to eq('first#out=>second#in')
|
201
|
+
expect(conn.output_port_key).to be_nil
|
202
|
+
expect(conn.input_port_key).to be_nil
|
203
|
+
conn.options.tap do |opts|
|
204
|
+
expect(opts['output_socket_type']).to eq('PUSH')
|
205
|
+
expect(opts['output_address']).to eq("ipc://rflow.#{conn.uuid}")
|
206
|
+
expect(opts['output_responsibility']).to eq('connect')
|
207
|
+
expect(opts['input_socket_type']).to eq('PULL')
|
208
|
+
expect(opts['input_address']).to eq("ipc://rflow.#{conn.uuid}")
|
209
|
+
expect(opts['input_responsibility']).to eq('bind')
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
it "should generate PUSH-PULL ipc ZeroMQ connections for one-to-many inter-shard connections" do
|
215
|
+
described_class.configure do |c|
|
216
|
+
|
217
|
+
c.shard "s1", :process => 1 do |s|
|
218
|
+
s.component 'first', 'First', :opt1 => 'opt1'
|
219
|
+
end
|
220
|
+
|
221
|
+
c.shard "s2", :process => 3 do |s|
|
222
|
+
s.component 'second', 'Second', :opt1 => 'opt1', "opt2" => "opt2"
|
223
|
+
end
|
224
|
+
|
225
|
+
c.connect 'first#out' => 'second#in'
|
226
|
+
end
|
227
|
+
|
228
|
+
expect(Shard).to have(2).shards
|
229
|
+
expect(Component).to have(2).components
|
230
|
+
expect(Port).to have(2).ports
|
231
|
+
expect(Connection).to have(1).connections
|
232
|
+
|
233
|
+
Connection.first.tap do |conn|
|
234
|
+
expect(conn.type).to eq('RFlow::Configuration::ZMQConnection')
|
235
|
+
expect(conn.name).to eq('first#out=>second#in')
|
236
|
+
expect(conn.output_port_key).to be_nil
|
237
|
+
expect(conn.input_port_key).to be_nil
|
238
|
+
conn.options.tap do |opts|
|
239
|
+
expect(opts['output_socket_type']).to eq('PUSH')
|
240
|
+
expect(opts['output_address']).to eq("ipc://rflow.#{conn.uuid}")
|
241
|
+
expect(opts['output_responsibility']).to eq('bind')
|
242
|
+
expect(opts['input_socket_type']).to eq('PULL')
|
243
|
+
expect(opts['input_address']).to eq("ipc://rflow.#{conn.uuid}")
|
244
|
+
expect(opts['input_responsibility']).to eq('connect')
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
it "should generate PUSH-PULL ipc ZeroMQ connections for many-to-one inter-shard connections" do
|
250
|
+
described_class.configure do |c|
|
251
|
+
|
252
|
+
c.shard "s1", :process => 3 do |s|
|
253
|
+
s.component 'first', 'First', :opt1 => 'opt1'
|
254
|
+
end
|
255
|
+
|
256
|
+
c.shard "s2", :process => 1 do |s|
|
257
|
+
s.component 'second', 'Second', :opt1 => 'opt1', "opt2" => "opt2"
|
258
|
+
end
|
259
|
+
|
260
|
+
c.connect 'first#out' => 'second#in'
|
261
|
+
end
|
262
|
+
|
263
|
+
expect(Shard).to have(2).shards
|
264
|
+
expect(Component).to have(2).components
|
265
|
+
expect(Port).to have(2).ports
|
266
|
+
expect(Connection).to have(1).connections
|
267
|
+
|
268
|
+
Connection.first.tap do |conn|
|
269
|
+
expect(conn.type).to eq('RFlow::Configuration::ZMQConnection')
|
270
|
+
expect(conn.name).to eq('first#out=>second#in')
|
271
|
+
expect(conn.output_port_key).to be_nil
|
272
|
+
expect(conn.input_port_key).to be_nil
|
273
|
+
conn.options.tap do |opts|
|
274
|
+
expect(opts['output_socket_type']).to eq('PUSH')
|
275
|
+
expect(opts['output_address']).to eq("ipc://rflow.#{conn.uuid}")
|
276
|
+
expect(opts['output_responsibility']).to eq('connect')
|
277
|
+
expect(opts['input_socket_type']).to eq('PULL')
|
278
|
+
expect(opts['input_address']).to eq("ipc://rflow.#{conn.uuid}")
|
279
|
+
expect(opts['input_responsibility']).to eq('bind')
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
it "should generate PUSH-PULL brokered ZeroMQ connections for many-to-many inter-shard connections" do
|
285
|
+
described_class.configure do |c|
|
286
|
+
|
287
|
+
c.shard "s1", :process => 3 do |s|
|
288
|
+
s.component 'first', 'First', :opt1 => 'opt1'
|
289
|
+
end
|
290
|
+
|
291
|
+
c.shard "s2", :process => 3 do |s|
|
292
|
+
s.component 'second', 'Second', :opt1 => 'opt1', "opt2" => "opt2"
|
293
|
+
end
|
294
|
+
|
295
|
+
c.connect 'first#out' => 'second#in'
|
296
|
+
end
|
297
|
+
|
298
|
+
expect(Shard).to have(2).shards
|
299
|
+
expect(Component).to have(2).components
|
300
|
+
expect(Port).to have(2).ports
|
301
|
+
expect(Connection).to have(1).connections
|
302
|
+
|
303
|
+
Connection.first.tap do |conn|
|
304
|
+
expect(conn.type).to eq('RFlow::Configuration::BrokeredZMQConnection')
|
305
|
+
expect(conn.name).to eq('first#out=>second#in')
|
306
|
+
expect(conn.output_port_key).to be_nil
|
307
|
+
expect(conn.input_port_key).to be_nil
|
308
|
+
conn.options.tap do |opts|
|
309
|
+
expect(opts['output_socket_type']).to eq('PUSH')
|
310
|
+
expect(opts['output_address']).to eq("ipc://rflow.#{conn.uuid}.in")
|
311
|
+
expect(opts['output_responsibility']).to eq('connect')
|
312
|
+
expect(opts['input_socket_type']).to eq('PULL')
|
313
|
+
expect(opts['input_address']).to eq("ipc://rflow.#{conn.uuid}.out")
|
314
|
+
expect(opts['input_responsibility']).to eq('connect')
|
315
|
+
end
|
316
|
+
end
|
129
317
|
end
|
130
318
|
|
131
319
|
it "should not allow two components with the same name" do
|