rflow 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b52acbd7c200a903c0e3c17982a220a2a9ee0169
4
- data.tar.gz: 6f16e7be6007f0de84de616bffaa82279bf90a59
3
+ metadata.gz: 325fd6dd2c2d5409dc692a641c36da3b2139f7e8
4
+ data.tar.gz: d71bb8ce7e17c7e86fc1e2de7f299e73efdaeb5a
5
5
  SHA512:
6
- metadata.gz: ffcde5466c777d5e36c815142a1b54b8da90fab13f6e7188519148482fdfc961250149a21f59d1998b674ce520f8436dd1a6185750af76063403742767aa4201
7
- data.tar.gz: 15ea4a44084a9d8fa11691f45788ec9205b442cbabd4084b18e13d7ad94016e2f9ef37fb6d6e22bd6eeabee392bcf2fbef585cd3cc991c70c47abc87c3bc20e1
6
+ metadata.gz: e5a602dc11534aa9a48b81bfc5d2035103b048b67d96684587f84c03152f90b415a43f6e19050616cbdccd37cfbad3ddf67e5c45ecfb4690c2dd40cfaa1b5883
7
+ data.tar.gz: e7d78c206614c6df0824b4d87f50a1c48edb54468defc498f5fa2319b05c707a4a4ee440514a8a11058998202b76ab93885859a8a50b1dbe85bc33bc82a5c041
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ruby-2.1.2
1
+ ruby-2.3.0
data/.travis.yml CHANGED
@@ -4,6 +4,8 @@ rvm:
4
4
  - 1.9.3
5
5
  - 2.0.0
6
6
  - 2.1.0
7
+ - 2.2.0
8
+ - 2.3.0
7
9
 
8
10
  before_install:
9
11
  - sudo apt-get install libtool autoconf automake uuid-dev build-essential
@@ -12,6 +14,7 @@ before_install:
12
14
  # - sudo add-apt-repository -y ppa:chris-lea/zeromq
13
15
  # - sudo apt-get update
14
16
  # - sudo apt-get install libzmq3 libzmq3-dev
17
+ - gem update bundler
15
18
 
16
19
  script: bundle exec rspec spec
17
20
 
data/Gemfile CHANGED
@@ -1,8 +1,9 @@
1
- source "https://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
5
  group :development do
6
6
  gem 'guard'
7
7
  gem 'guard-rspec'
8
+ gem 'listen', '<3.1'
8
9
  end
data/Guardfile CHANGED
@@ -4,5 +4,5 @@
4
4
  guard :rspec do
5
5
  watch(%r{^spec/.+_spec\.rb$})
6
6
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
- watch('spec/spec_helper.rb') { "spec" }
7
+ watch('spec/spec_helper.rb') { 'spec' }
8
8
  end
data/README.md CHANGED
@@ -13,7 +13,7 @@ RFlow is a Ruby framework inspired by
13
13
 
14
14
  In short, components communicate with each other by sending/receiving
15
15
  messages via their output/input ports over connections. Ports are
16
- "wired" together output->input with connections, and messages are
16
+ 'wired' together output->input with connections, and messages are
17
17
  explicitly serialized before being sent over the connection. RFlow
18
18
  supports generalized connection types and message serialization,
19
19
  however only two are in current use, namely ZeroMQ connections and
@@ -58,7 +58,7 @@ work. (You will probably get errors saying arcane things like
58
58
 
59
59
  * __Port__ - a named entity on each component that is responsible for
60
60
  receiving data (and input port) or sending data (and output port).
61
- Ports can be "keyed" or "indexed" to allow better multiplexing of
61
+ Ports can be 'keyed' or 'indexed' to allow better multiplexing of
62
62
  messages out/in a single port, as well as allow a single port to be
63
63
  accessed by an array.
64
64
 
@@ -70,7 +70,7 @@ work. (You will probably get errors saying arcane things like
70
70
  * __Message__ - a bit of serialized data that is sent out an output
71
71
  port and received on an input port. Due to the serialization,
72
72
  message types and schemas are explicitly defined. In a departure
73
- from "pure" FBP, RFlow supports sending multiple message types via a
73
+ from 'pure' FBP, RFlow supports sending multiple message types via a
74
74
  single connection.
75
75
 
76
76
  * __Workflow__ - the common name for the digraph created when the
@@ -126,7 +126,7 @@ end
126
126
  component to clean up any external resources that it might have
127
127
  outstanding, such as file handles or network sockets.
128
128
 
129
- "Source" components will often do all of their work within the `run!`
129
+ 'Source' components will often do all of their work within the `run!`
130
130
  method, and often gather message data from an external source, such as
131
131
  file, database, or network socket. The following component generates a
132
132
  set of integers between a configured start/finish, incrementing by a
@@ -158,7 +158,7 @@ class RFlow::Components::GenerateIntegerSequence < RFlow::Component
158
158
  end
159
159
  ```
160
160
 
161
- "Middle" components receive messages on input port(s), perform a bit
161
+ 'Middle' components receive messages on input port(s), perform a bit
162
162
  of computation, and then send a message out the output port(s). The
163
163
  following component accepts a Ruby expression string via its config,
164
164
  and then uses that as an expression to determine what port to send an
@@ -189,7 +189,7 @@ class RFlow::Components::RubyProcFilter < RFlow::Component
189
189
  end
190
190
  ```
191
191
 
192
- "Sink" components accept messages on an input port and do not have an
192
+ 'Sink' components accept messages on an input port and do not have an
193
193
  output port. They often operate on external sinks, such as writing
194
194
  messages to a file, database, or network socket. The following
195
195
  component writes the inspected message to a file (defined via the
@@ -220,10 +220,10 @@ end
220
220
 
221
221
  RFlow messages are instances of
222
222
  [`RFlow::Message`](lib/rflow/message.rb), which are ultimately
223
- serialized via an Avro [schema](schema/message.zvsc).
223
+ serialized via an Avro [schema](schema/message.avsc).
224
224
 
225
- There are two parts of the message "envelope": a provenance and the
226
- embedded data object "payload".
225
+ There are two parts of the message 'envelope': a provenance and the
226
+ embedded data object 'payload'.
227
227
 
228
228
  The `provenance` is a way for a component to annotate a message with a
229
229
  bit of data that should (by convention) be carried through the
data/Vagrantfile CHANGED
@@ -1,16 +1,16 @@
1
1
  # -*- mode: ruby -*-
2
2
  # vi: set ft=ruby :
3
- VAGRANTFILE_API_VERSION = "2"
3
+ VAGRANTFILE_API_VERSION = '2'
4
4
 
5
5
  Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
6
6
  config.vm.define 'centos62' do |c|
7
- c.vm.box = "jstoneham/rflow-centos62"
7
+ c.vm.box = 'jstoneham/rflow-centos62'
8
8
  end
9
9
  config.vm.define 'centos64' do |c|
10
- c.vm.box = "box-cutter/centos64"
10
+ c.vm.box = 'box-cutter/centos64'
11
11
  end
12
12
  config.vm.define 'centos65' do |c|
13
- c.vm.box = "chef/centos-6.5"
13
+ c.vm.box = 'chef/centos-6.5'
14
14
  end
15
15
 
16
16
  config.vm.synced_folder '.', '/rflow'
@@ -21,28 +21,28 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
21
21
  config.vm.synced_folder '../rflow-components-http', '/rflow-components-http'
22
22
 
23
23
  # forward http for rflow testing
24
- config.vm.network "forwarded_port", guest: 8000, host: 8000
24
+ config.vm.network 'forwarded_port', guest: 8000, host: 8000
25
25
 
26
26
  # install RPM dependencies for rflow and zeromq
27
- config.vm.provision "shell", privileged: true, inline: <<-EOS
27
+ config.vm.provision 'shell', privileged: true, inline: <<-EOS
28
28
  curl -O https://dl.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm
29
29
  rpm -ivh epel-release-6-8.noarch.rpm
30
30
  yum -y install libyaml-devel patch libffi-devel glibc-headers autoconf gcc-c++ glibc-devel readline-devel zlib-devel openssl-devel automake libtool bison git sqlite-devel rpm-build libuuid-devel vim
31
31
  EOS
32
32
 
33
33
  # build zeromq as vagrant user
34
- config.vm.provision "shell", privileged: false, inline: <<-EOS
34
+ config.vm.provision 'shell', privileged: false, inline: <<-EOS
35
35
  curl -O http://download.zeromq.org/zeromq-3.2.4.tar.gz
36
36
  rpmbuild -tb zeromq-3.2.4.tar.gz
37
37
  EOS
38
38
 
39
39
  # install zeromq
40
- config.vm.provision "shell", privileged: true, inline: <<-EOS
40
+ config.vm.provision 'shell', privileged: true, inline: <<-EOS
41
41
  rpm -ivh ~vagrant/rpmbuild/RPMS/x86_64/zeromq-*
42
42
  EOS
43
43
 
44
44
  # set up RVM and bundler
45
- config.vm.provision "shell", privileged: false, inline: <<-EOS
45
+ config.vm.provision 'shell', privileged: false, inline: <<-EOS
46
46
  rm -f .profile
47
47
  curl -sSL https://get.rvm.io | bash -s stable
48
48
  source .rvm/scripts/rvm
data/bin/rflow CHANGED
@@ -16,41 +16,41 @@ option_parser = OptionParser.new do |opts|
16
16
  Usage: #{File.basename $0} [options] (start|stop|status|load)
17
17
  EOB
18
18
 
19
- opts.on("-d", "--database DB", "Config database (sqlite) path (GENERALLY REQUIRED)") do |db|
19
+ opts.on('-d', '--database DB', 'Config database (sqlite) path (GENERALLY REQUIRED)') do |db|
20
20
  options[:config_database_path] = File.expand_path(db)
21
21
  end
22
22
 
23
- opts.on("-c", "--config CONFIG", "Config file path (only valid for load)") do |config|
23
+ opts.on('-c', '--config CONFIG', 'Config file path (only valid for load)') do |config|
24
24
  options[:config_file_path] = File.expand_path(config)
25
25
  end
26
26
 
27
- opts.on("-e", "--extensions FILE1[,FILE_N]", Array, "Extension file paths (will load)") do |extensions|
27
+ opts.on('-e', '--extensions FILE1[,FILE_N]', Array, 'Extension file paths (will load)') do |extensions|
28
28
  options[:extensions_file_paths] += extensions.map {|extension| File.expand_path(extension)}
29
29
  end
30
30
 
31
- opts.on("-g", "--gems GEM1[,GEM_N]", Array, "Extension gems (will require)") do |gems|
31
+ opts.on('-g', '--gems GEM1[,GEM_N]', Array, 'Extension gems (will require)') do |gems|
32
32
  options[:gems] += gems
33
33
  end
34
34
 
35
- opts.on("-l", "--log LOGFILE", "Initial startup log file (in addition to stdout)") do |log|
35
+ opts.on('-l', '--log LOGFILE', 'Initial startup log file (in addition to stdout)') do |log|
36
36
  options[:startup_log_file_path] = File.expand_path(log)
37
37
  end
38
38
 
39
- opts.on("-v", "--verbose [LEVEL]", [:DEBUG, :INFO, :WARN], "Control the startup log (and stdout) verbosity (DEBUG, INFO, WARN) defaults to INFO") do |level|
39
+ opts.on('-v', '--verbose [LEVEL]', [:DEBUG, :INFO, :WARN], 'Control the startup log (and stdout) verbosity (DEBUG, INFO, WARN) defaults to INFO') do |level|
40
40
  options[:startup_log_level] = level || :DEBUG
41
41
  end
42
42
 
43
- opts.on("-f", "Run in the foreground") do |f|
43
+ opts.on('-f', 'Run in the foreground') do |f|
44
44
  options[:daemonize] = false
45
45
  end
46
46
 
47
- opts.on_tail("--version", "Show RFlow version and exit") do
47
+ opts.on_tail('--version', 'Show RFlow version and exit') do
48
48
  require 'rflow/version'
49
49
  puts RFlow::VERSION
50
50
  exit 0
51
51
  end
52
52
 
53
- opts.on_tail("-h", "--help", "Show this message and exit") do
53
+ opts.on_tail('-h', '--help', 'Show this message and exit') do
54
54
  puts opts
55
55
  exit 0
56
56
  end
@@ -98,10 +98,10 @@ class HTTPServer < RFlow::Component
98
98
  # This is done by inspecting the provenance, specifically the
99
99
  # context attribute that we stored originally
100
100
  def process_message(input_port, input_port_key, connection, message)
101
- RFlow.logger.debug "Received a message"
101
+ RFlow.logger.debug 'Received a message'
102
102
  return unless message.data_type_name == 'HTTPResponse'
103
103
 
104
- RFlow.logger.debug "Received a HTTPResponse message, determining if its mine"
104
+ RFlow.logger.debug 'Received a HTTPResponse message, determining if its mine'
105
105
  my_events = message.provenance.find_all {|processing_event| processing_event.component_instance_uuid == instance_uuid}
106
106
  RFlow.logger.debug "Found #{my_events.size} processing events from me"
107
107
 
@@ -122,7 +122,7 @@ class HTTPServer < RFlow::Component
122
122
  attr_accessor :server, :client_ip, :client_port
123
123
 
124
124
  def post_init
125
- @client_port, @client_ip = Socket.unpack_sockaddr_in(get_peername) rescue ["?", "?.?.?.?"]
125
+ @client_port, @client_ip = Socket.unpack_sockaddr_in(get_peername) rescue ['?', '?.?.?.?']
126
126
  RFlow.logger.debug "Connection from #{@client_ip}:#{@client_port}"
127
127
  super
128
128
  no_environment_strings
@@ -154,9 +154,9 @@ class HTTPServer < RFlow::Component
154
154
 
155
155
  # Default values
156
156
  resp.status = 200
157
- resp.content = ""
158
- resp.headers["Content-Type"] = "text/html; charset=UTF-8"
159
- resp.headers["Server"] = "Apache/2.2.3 (CentOS)"
157
+ resp.content = ''
158
+ resp.headers['Content-Type'] = 'text/html; charset=UTF-8'
159
+ resp.headers['Server'] = 'Apache/2.2.3 (CentOS)'
160
160
 
161
161
  if response_message
162
162
  resp.status = response_message.data.status
@@ -170,7 +170,7 @@ class HTTPServer < RFlow::Component
170
170
  # Called when a connection is torn down for whatever reason.
171
171
  # Remove this connection from the server's list
172
172
  def unbind
173
- RFlow.logger.debug "Connection lost"
173
+ RFlow.logger.debug 'Connection lost'
174
174
  server.connections.delete(self.signature)
175
175
  end
176
176
  end
data/lib/rflow.rb CHANGED
@@ -1,5 +1,5 @@
1
- require "rubygems"
2
- require "bundler/setup"
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
3
  require 'time'
4
4
  require 'active_record'
5
5
  require 'eventmachine'
@@ -38,7 +38,7 @@ class RFlow
38
38
  def self.establish_configuration
39
39
  @configuration = Configuration.new(@config_database_path)
40
40
  unless configuration['rflow.application_directory_path']
41
- raise ArgumentError, "Empty configuration database! Use a view/controller (such as the RubyDSL) to create a configuration"
41
+ raise ArgumentError, 'Empty configuration database! Use a view/controller (such as the RubyDSL) to create a configuration'
42
42
  end
43
43
  end
44
44
 
@@ -60,4 +60,25 @@ class RFlow
60
60
  master.daemonize! if @daemonize
61
61
  master.run! # blocks until EventMachine stops
62
62
  end
63
+
64
+ # This ought to be in EM, but we'll put it here instead of monkey-patching
65
+ def self.next_tick_and_wait
66
+ mutex = Mutex.new
67
+ condition = ConditionVariable.new
68
+
69
+ mutex.synchronize do # while locked...
70
+ EM.next_tick do # schedule a job that will...
71
+ mutex.synchronize do # grab the lock
72
+ begin
73
+ yield # do its thing...
74
+ condition.signal # then wake us up when it's done...
75
+ rescue
76
+ condition.signal # even if the thing fails
77
+ raise
78
+ end
79
+ end
80
+ end
81
+ condition.wait(mutex) # drop the mutex to allow the job to run
82
+ end
83
+ end
63
84
  end
@@ -1,3 +1,5 @@
1
+ require 'fcntl'
2
+
1
3
  class RFlow
2
4
  class ChildProcess
3
5
  attr_reader :pid, :name
@@ -91,11 +91,11 @@ class RFlow
91
91
 
92
92
  # Returns a list of connected input ports. Each port will have
93
93
  # one or more keys associated with a particular connection.
94
- def input_ports; ports.by_type["RFlow::Component::InputPort"]; end
94
+ def input_ports; ports.by_type['RFlow::Component::InputPort']; end
95
95
 
96
96
  # Returns a list of connected output ports. Each port will have
97
97
  # one or more keys associated with the particular connection.
98
- def output_ports; ports.by_type["RFlow::Component::OutputPort"]; end
98
+ def output_ports; ports.by_type['RFlow::Component::OutputPort']; end
99
99
 
100
100
  def configure_input_port!(port_name, options = {})
101
101
  RFlow.logger.debug "Configuring component '#{name}' (#{uuid}) input port '#{port_name}' (#{options[:uuid]})"
@@ -1,7 +1,7 @@
1
1
  class RFlow
2
2
  class Component
3
3
  # TODO: make this into a class to limit the amount of extensions
4
- # that we have to do when operating on these "Arrays", i.e. when
4
+ # that we have to do when operating on these 'Arrays', i.e. when
5
5
  # adding two together
6
6
  module ConnectionCollection
7
7
  def send_message(message)
@@ -44,6 +44,30 @@ class RFlow
44
44
  def connected?; connected; end
45
45
  end
46
46
 
47
+ # Stateless class to help with a nicer API
48
+ class HashSubPort
49
+ def initialize(hash_port, key)
50
+ @hash_port = hash_port
51
+ @key = key
52
+ end
53
+
54
+ def send_message(message)
55
+ connections.each {|connection| connection.send_message(message) }
56
+ end
57
+
58
+ def connections
59
+ @hash_port.connections_for(@key)
60
+ end
61
+
62
+ def direct_connect(other_port)
63
+ @hash_port.direct_connect(@key, other_port)
64
+ end
65
+
66
+ def each
67
+ connections.each {|connection| yield connection }
68
+ end
69
+ end
70
+
47
71
  # Allows for a list of connections to be assigned to each port/key
48
72
  # combination. Note that binding an input port to an un-indexed
49
73
  # output port will result in messages from all indexed connections
@@ -53,41 +77,44 @@ class RFlow
53
77
  class HashPort < Port
54
78
  attr_accessor :name, :uuid
55
79
 
56
- protected
57
- attr_reader :connections_for
58
-
59
80
  public
60
81
  def initialize(component, args = {})
61
82
  super(component)
62
83
  self.uuid = args[:uuid]
63
84
  self.name = args[:name]
64
- @connections_for = Hash.new {|hash, key| hash[key] = [].extend(ConnectionCollection)}
85
+ @connections_for = Hash.new {|hash, key| hash[key] = []}
65
86
  end
66
87
 
67
- # Returns an extended Array of all the connections that should
68
- # be sent/received on this port. Merges the nil-keyed port
88
+ # Get the subport for a given key, which can be used to send messages
89
+ # or direct connection
90
+ def [](key)
91
+ HashSubPort.new(self, key)
92
+ end
93
+
94
+ # Returns an Array of all the connections that should
95
+ # be sent/received on this subport. Merges the nil-keyed port
69
96
  # (i.e. any connections for a port without a key) to those
70
97
  # specific for the key, so should only be used to read a list of
71
98
  # connections, not to add new ones. Use add_connection to add a
72
99
  # new connection for a given key.
73
- def [](key)
100
+ def connections_for(key)
74
101
  case key
75
- when nil; connections_for[nil]
76
- else connections_for[key] + connections_for[nil]
77
- end.extend(ConnectionCollection)
102
+ when nil; @connections_for[nil]
103
+ else @connections_for[key] + @connections_for[nil]
104
+ end
78
105
  end
79
106
 
80
107
  # Adds a connection for a given key
81
108
  def add_connection(key, connection)
82
109
  RFlow.logger.debug "Attaching #{connection.class.name} connection '#{connection.name}' (#{connection.uuid}) to port '#{name}' (#{uuid}), key '#{connection.input_port_key}'"
83
- connections_for[key] << connection
110
+ @connections_for[key] << connection
84
111
  @all_connections = nil
85
112
  end
86
113
 
87
114
  # Removes a connection from a given key
88
115
  def remove_connection(key, connection)
89
116
  RFlow.logger.debug "Removing #{connection.class.name} connection '#{connection.name}' (#{connection.uuid}) from port '#{name}' (#{uuid}), key '#{connection.input_port_key}'"
90
- connections_for[key].delete(connection)
117
+ @connections_for[key].delete(connection)
91
118
  @all_connections = nil
92
119
  end
93
120
 
@@ -105,41 +132,39 @@ class RFlow
105
132
  end
106
133
  end
107
134
 
108
- def direct_connect(other_port)
135
+ def direct_connect(key = nil, other_port)
109
136
  case other_port
110
- when InputPort; add_connection nil, ForwardToInputPort.new(other_port)
111
- when OutputPort; add_connection nil, ForwardToOutputPort.new(other_port)
137
+ when InputPort; add_connection key, ForwardToInputPort.new(other_port)
138
+ when OutputPort; add_connection key, ForwardToOutputPort.new(other_port)
112
139
  else raise ArgumentError, "Unknown port type #{other_port.class.name}"
113
140
  end
114
141
  end
115
142
 
116
143
  # Return a list of connected keys
117
144
  def keys
118
- connections_for.keys
145
+ @connections_for.keys
119
146
  end
120
147
 
121
- # Enumerate through all the ConnectionCollections
122
- # TODO: simplify with enumerators and procs
123
148
  def each
124
- connections_for.values.each {|connections| yield connections }
149
+ @connections_for.values.each {|connections| yield connections }
125
150
  end
126
151
 
127
152
  def send_message(message)
128
- def connect!; raise NotImplementedError, "Raw ports do not know how to send messages"; end
153
+ raise NotImplementedError, 'Raw ports do not know how to send messages'
129
154
  end
130
155
 
131
156
  # Should be overridden. Called when it is time to actually
132
157
  # establish the connection
133
- def connect!; raise NotImplementedError, "Raw ports do not know which direction to connect"; end
158
+ def connect!; raise NotImplementedError, 'Raw ports do not know which direction to connect'; end
134
159
 
135
160
  def all_connections
136
- @all_connections ||= connections_for.values.flatten.uniq.extend(ConnectionCollection)
161
+ @all_connections ||= @connections_for.values.flatten.uniq.extend(ConnectionCollection)
137
162
  end
138
163
  end
139
164
 
140
165
  class InputPort < HashPort
141
166
  def connect!
142
- connections_for.each {|key, conns| conns.each {|c| c.connect_input! } }
167
+ @connections_for.each {|key, conns| conns.each {|c| c.connect_input! } }
143
168
  @connected = true
144
169
  end
145
170
 
@@ -149,7 +174,7 @@ class RFlow
149
174
  end
150
175
 
151
176
  def recv_callback=(callback)
152
- connections_for.each do |key, connections|
177
+ @connections_for.each do |key, connections|
153
178
  connections.each do |connection|
154
179
  connection.recv_callback = Proc.new do |message|
155
180
  callback.call self, key, connection, message
@@ -161,7 +186,7 @@ class RFlow
161
186
 
162
187
  class OutputPort < HashPort
163
188
  def connect!
164
- connections_for.each {|key, conns| conns.each {|c| c.connect_output! } }
189
+ @connections_for.each {|key, conns| conns.each {|c| c.connect_output! } }
165
190
  @connected = true
166
191
  end
167
192