rflow 0.0.5 → 1.0.0a1

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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-gemset +1 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +21 -0
  5. data/.yardopts +1 -0
  6. data/Gemfile +5 -1
  7. data/Guardfile +8 -0
  8. data/LICENSE +190 -0
  9. data/NOTES +26 -13
  10. data/README.md +448 -0
  11. data/Rakefile +5 -12
  12. data/bin/rflow +23 -20
  13. data/example/basic_config.rb +2 -2
  14. data/example/basic_extensions.rb +8 -8
  15. data/example/http_config.rb +1 -1
  16. data/example/http_extensions.rb +15 -15
  17. data/lib/rflow.rb +15 -387
  18. data/lib/rflow/component.rb +105 -50
  19. data/lib/rflow/component/port.rb +25 -24
  20. data/lib/rflow/components/raw.rb +4 -4
  21. data/lib/rflow/components/raw/extensions.rb +2 -2
  22. data/lib/rflow/configuration.rb +54 -36
  23. data/lib/rflow/configuration/component.rb +2 -3
  24. data/lib/rflow/configuration/connection.rb +9 -10
  25. data/lib/rflow/configuration/migrations/{20010101000001_create_settings.rb → 20010101000000_create_settings.rb} +2 -2
  26. data/lib/rflow/configuration/migrations/20010101000001_create_shards.rb +21 -0
  27. data/lib/rflow/configuration/migrations/20010101000002_create_components.rb +7 -2
  28. data/lib/rflow/configuration/migrations/20010101000003_create_ports.rb +3 -3
  29. data/lib/rflow/configuration/migrations/20010101000004_create_connections.rb +2 -2
  30. data/lib/rflow/configuration/port.rb +3 -4
  31. data/lib/rflow/configuration/ruby_dsl.rb +59 -35
  32. data/lib/rflow/configuration/setting.rb +8 -7
  33. data/lib/rflow/configuration/shard.rb +24 -0
  34. data/lib/rflow/configuration/uuid_keyed.rb +3 -3
  35. data/lib/rflow/connection.rb +21 -10
  36. data/lib/rflow/connections/zmq_connection.rb +45 -44
  37. data/lib/rflow/logger.rb +67 -0
  38. data/lib/rflow/master.rb +127 -0
  39. data/lib/rflow/message.rb +14 -14
  40. data/lib/rflow/pid_file.rb +84 -0
  41. data/lib/rflow/shard.rb +148 -0
  42. data/lib/rflow/version.rb +1 -1
  43. data/rflow.gemspec +22 -28
  44. data/schema/message.avsc +8 -8
  45. data/spec/fixtures/config_ints.rb +4 -4
  46. data/spec/fixtures/config_shards.rb +30 -0
  47. data/spec/fixtures/extensions_ints.rb +8 -8
  48. data/spec/rflow_component_port_spec.rb +58 -0
  49. data/spec/rflow_configuration_ruby_dsl_spec.rb +148 -0
  50. data/spec/rflow_configuration_spec.rb +4 -4
  51. data/spec/rflow_message_data_raw.rb +2 -2
  52. data/spec/rflow_message_data_spec.rb +6 -6
  53. data/spec/rflow_message_spec.rb +13 -13
  54. data/spec/rflow_spec.rb +294 -71
  55. data/spec/schema_spec.rb +2 -2
  56. data/spec/spec_helper.rb +6 -4
  57. data/temp.rb +21 -21
  58. metadata +56 -65
  59. data/.rvmrc +0 -1
  60. data/README +0 -0
data/Rakefile CHANGED
@@ -1,16 +1,9 @@
1
- require 'bundler'
1
+ require 'bundler/gem_tasks'
2
2
  require 'rspec/core/rake_task'
3
- require 'rdoc/task'
4
- Bundler::GemHelper.install_tasks
3
+ require 'yard'
5
4
 
6
- RSpec::Core::RakeTask.new(:spec) do |t|
7
- t.verbose = true
8
- t.rspec_opts = '--tty --color'
9
- end
5
+ RSpec::Core::RakeTask.new(:spec)
10
6
 
11
- RDoc::Task.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
7
+ task :default => :spec
16
8
 
9
+ YARD::Rake::YardocTask.new
data/bin/rflow CHANGED
@@ -27,11 +27,11 @@ EOB
27
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
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
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
@@ -49,12 +49,12 @@ EOB
49
49
  puts RFlow::VERSION
50
50
  exit 0
51
51
  end
52
-
52
+
53
53
  opts.on_tail("-h", "--help", "Show this message and exit") do
54
54
  puts opts
55
55
  exit 0
56
56
  end
57
-
57
+
58
58
  end
59
59
 
60
60
  begin
@@ -73,12 +73,13 @@ require 'rflow'
73
73
  # will always go to STDOUT, as well as to the file specified with the
74
74
  # '-l' parameter
75
75
  startup_logger = Log4r::Logger.new 'startup'
76
- startup_logger.add Log4r::StdoutOutputter.new('startup_stdout', :formatter => RFlow::LOG_PATTERN_FORMATTER)
76
+ startup_logger.add Log4r::StdoutOutputter.new('startup_stdout', :formatter => RFlow::Logger::LOG_PATTERN_FORMATTER)
77
77
  startup_logger.level = Log4r::LNAMES.index options[:startup_log_level].to_s
78
+ Log4r::NDC.push('startup')
78
79
 
79
80
  if options[:startup_log_file_path]
80
81
  begin
81
- startup_logger.add Log4r::FileOutputter.new('startup_file', :filename => options[:startup_log_file_path], :formatter => RFlow::LOG_PATTERN_FORMATTER)
82
+ startup_logger.add Log4r::FileOutputter.new('startup_file', :filename => options[:startup_log_file_path], :formatter => RFlow::Logger::LOG_PATTERN_FORMATTER)
82
83
  rescue Exception => e
83
84
  startup_logger.fatal "Log file '#{options[:startup_log_file_path]}' problem: #{e.message}"
84
85
  exit 1
@@ -120,27 +121,28 @@ when 'load'
120
121
  startup_logger.fatal "Config file '#{options[:config_file_path]}' not found\n#{option_parser.help}"
121
122
  exit 1
122
123
  end
123
-
124
+
124
125
  unless File.readable? options[:config_file_path]
125
126
  startup_logger.fatal "Config file '#{options[:config_file_path]}' not readable\n#{option_parser.help}"
126
127
  exit 1
127
128
  end
128
129
  end
129
-
130
+
130
131
  if File.exist? options[:config_database_path]
131
132
  startup_logger.fatal "Config database '#{options[:config_database_path]}' exists, exiting to prevent accidental overwrite from config file '#{options[:config_file_path]}'"
132
133
  exit 1
133
134
  end
134
-
135
+
135
136
  startup_logger.warn "Config database '#{options[:config_database_path]}' not found, creating"
136
137
  begin
137
- RFlow::Configuration::initialize_database(options[:config_database_path], options[:config_file_path])
138
+ config = RFlow::Configuration::initialize_database(options[:config_database_path], options[:config_file_path])
138
139
  rescue Exception => e
139
140
  startup_logger.fatal "Error initializing configuration database: #{e.message}: #{e.backtrace.join "\n"}"
140
141
  exit 1
141
142
  end
142
143
 
143
144
  startup_logger.warn "Successfully initialized database '#{options[:config_database_path]}' with '#{options[:config_file_path]}'"
145
+ startup_logger.debug config.to_s
144
146
  exit 0
145
147
  end
146
148
 
@@ -155,31 +157,32 @@ end
155
157
 
156
158
  Dir.chdir(File.dirname(options[:config_database_path]))
157
159
  Dir.chdir(config['rflow.application_directory_path'])
158
- pid = RFlow.running_pid_file_path?(config['rflow.pid_file_path'])
160
+ pid_file = RFlow::PIDFile.new(config['rflow.pid_file_path'])
161
+
159
162
 
160
163
  case command
161
164
  when 'stop'
162
- if pid
163
- startup_logger.info "#{config['rflow.application_name']} running, process #{pid} found in #{File.expand_path(config['rflow.pid_file_path'])}, terminating"
165
+ if pid_file.running?
166
+ startup_logger.info "#{config['rflow.application_name']} running, process #{pid_file.read} found in #{pid_file.to_s}, terminating"
164
167
  # TODO: check if it actually shut down
165
- Process.kill 'INT', pid
168
+ pid_file.signal(:INT)
166
169
  else
167
- startup_logger.warn "#{config['rflow.application_name']} process not found in #{File.expand_path(config['rflow.pid_file_path'])}"
170
+ startup_logger.warn "#{config['rflow.application_name']} process not found in #{pid_file.to_s}"
168
171
  exit 1
169
172
  end
170
173
  exit 0
171
174
 
172
175
  when 'status'
173
- unless pid
174
- startup_logger.error "#{config['rflow.application_name']} process not found in #{File.expand_path(config['rflow.pid_file_path'])}"
176
+ unless pid_file.running?
177
+ startup_logger.error "#{config['rflow.application_name']} process not found in #{pid_file.to_s}"
175
178
  exit 1
176
179
  end
177
- startup_logger.info "#{config['rflow.application_name']} running, process #{pid} found in #{File.expand_path(config['rflow.pid_file_path'])}"
180
+ startup_logger.info "#{config['rflow.application_name']} running, process #{pid_file.read} found in #{pid_file.to_s}"
178
181
  exit 0
179
182
 
180
183
  when 'start'
181
- if pid
182
- startup_logger.error "#{config['rflow.application_name']} already running, process #{pid} found in #{File.expand_path(config['rflow.pid_file_path'])}"
184
+ if pid_file.running?
185
+ startup_logger.error "#{config['rflow.application_name']} already running, process #{pid_file.read} found in #{pid_file.to_s}"
183
186
  exit 1
184
187
  end
185
188
  end
@@ -16,7 +16,7 @@ RFlow::Configuration::RubyDSL.configure do |config|
16
16
  # config.component 'replicate', 'RFlow::Components::Replicate'
17
17
  # config.component 'output1', 'RFlow::Components::FileOutput', 'output_file_path' => '/tmp/crap1'
18
18
  # config.component 'output2', 'RFlow::Components::FileOutput', 'output_file_path' => '/tmp/crap2'
19
-
19
+
20
20
  # Hook components together
21
21
  # config.connect 'generate_ints#out' => 'filter#in'
22
22
  # config.connect 'filter#filtered' => 'replicate#in'
@@ -42,7 +42,7 @@ RFlow::Configuration::RubyDSL.configure do |config|
42
42
  config.connect 'generate_ints#even_odd_out' => 'output#in'
43
43
  config.connect 'generate_ints#even_odd_out[even]' => 'output_even#in'
44
44
  config.connect 'generate_ints#even_odd_out[odd]' => 'output_odd#in'
45
-
45
+
46
46
 
47
47
  end
48
48
 
@@ -25,7 +25,7 @@ RFlow::Configuration.add_available_data_type('RFlow::Message::Data::Integer', 'a
25
25
  class RFlow::Components::GenerateIntegerSequence < RFlow::Component
26
26
  output_port :out
27
27
  output_port :even_odd_out
28
-
28
+
29
29
  def configure!(config)
30
30
  @start = config['start'].to_i
31
31
  @finish = config['finish'].to_i
@@ -37,7 +37,7 @@ class RFlow::Components::GenerateIntegerSequence < RFlow::Component
37
37
  # Note that this uses the timer (sometimes with 0 interval) so as
38
38
  # not to block the reactor
39
39
  def run!
40
- timer = EM::PeriodicTimer.new(@interval_seconds) do
40
+ timer = EM::PeriodicTimer.new(@interval_seconds) do
41
41
  message = RFlow::Message.new('RFlow::Message::Data::Integer')
42
42
  message.data.data_object = @start
43
43
  out.send_message message
@@ -47,7 +47,7 @@ class RFlow::Components::GenerateIntegerSequence < RFlow::Component
47
47
  even_odd_out['odd'].send_message message
48
48
  end
49
49
  even_odd_out.send_message message
50
-
50
+
51
51
  @start += @step
52
52
  timer.cancel if @start > @finish
53
53
  end
@@ -59,7 +59,7 @@ class RFlow::Components::Replicate < RFlow::Component
59
59
  input_port :in
60
60
  output_port :out
61
61
  output_port :errored
62
-
62
+
63
63
  def process_message(input_port, input_port_key, connection, message)
64
64
  puts "Processing message in Replicate"
65
65
  out.each do |connections|
@@ -85,7 +85,7 @@ class RFlow::Components::RubyProcFilter < RFlow::Component
85
85
  def configure!(config)
86
86
  @filter_proc = eval("lambda {|message| #{config['filter_proc_string']} }")
87
87
  end
88
-
88
+
89
89
  def process_message(input_port, input_port_key, connection, message)
90
90
  puts "Processing message in RubyProcFilter"
91
91
  begin
@@ -112,18 +112,18 @@ class RFlow::Components::FileOutput < RFlow::Component
112
112
  end
113
113
 
114
114
  #def run!; end
115
-
115
+
116
116
  def process_message(input_port, input_port_key, connection, message)
117
117
  puts "About to output to a file #{output_file_path}"
118
118
  output_file.puts message.data.data_object.inspect
119
119
  output_file.flush
120
120
  end
121
121
 
122
-
122
+
123
123
  def cleanup
124
124
  output_file.close
125
125
  end
126
-
126
+
127
127
  end
128
128
 
129
129
  # TODO: Ensure that all the following methods work as they are
@@ -10,7 +10,7 @@ RFlow::Configuration::RubyDSL.configure do |config|
10
10
  config.component 'replicate', 'RFlow::Components::Replicate'
11
11
  config.component 'file_output', 'RFlow::Components::FileOutput', 'output_file_path' => '/tmp/http_crap'
12
12
  config.component 'http_responder', 'HTTPResponder', 'response_code' => 200, 'content' => 'Hi, this teh awesome'
13
-
13
+
14
14
  # config.connect 'http_server#request_port' => 'filter#in'
15
15
  # config.connect 'filter#filtered' => 'replicate#in'
16
16
  config.connect 'http_server#request_port' => 'replicate#in'
@@ -7,7 +7,7 @@ class RFlow::Components::Replicate < RFlow::Component
7
7
  input_port :in
8
8
  output_port :out
9
9
  output_port :errored
10
-
10
+
11
11
  def process_message(input_port, input_port_key, connection, message)
12
12
  out.each do |connections|
13
13
  begin
@@ -31,7 +31,7 @@ class RFlow::Components::RubyProcFilter < RFlow::Component
31
31
  def configure!(config)
32
32
  @filter_proc = eval("lambda {|message| #{config['filter_proc_string']} }")
33
33
  end
34
-
34
+
35
35
  def process_message(input_port, input_port_key, connection, message)
36
36
  RFlow.logger.debug "Filtering message"
37
37
  begin
@@ -61,7 +61,7 @@ class RFlow::Components::FileOutput < RFlow::Component
61
61
  output_file.puts message.data.data_object.inspect
62
62
  output_file.flush
63
63
  end
64
-
64
+
65
65
  def cleanup!
66
66
  output_file.close
67
67
  end
@@ -110,7 +110,7 @@ RFlow::Configuration.add_available_data_type('HTTPResponse', 'avro', http_respon
110
110
 
111
111
 
112
112
  # Need to be careful when extending to not clobber data already in data_object
113
- module HTTPRequestExtension
113
+ module HTTPRequestExtension
114
114
  def self.extended(base_data)
115
115
  base_data.data_object ||= {'path' => ''}
116
116
  end
@@ -122,14 +122,14 @@ RFlow::Configuration.add_available_data_extension('HTTPRequest', HTTPRequestExte
122
122
 
123
123
 
124
124
  # Need to be careful when extending to not clobber data already in data_object
125
- module HTTPResponseExtension
125
+ module HTTPResponseExtension
126
126
  def self.extended(base_data)
127
127
  base_data.data_object ||= {'status' => 200, 'content' => ''}
128
128
  end
129
129
 
130
130
  def status; data_object['status']; end
131
131
  def status=(new_status); data_object['status'] = new_status; end
132
-
132
+
133
133
  def content; data_object['content']; end
134
134
  def content=(new_content); data_object['content'] = new_content; end
135
135
  end
@@ -144,7 +144,7 @@ class HTTPServer < RFlow::Component
144
144
  output_port :request_port
145
145
 
146
146
  attr_accessor :port, :listen, :server_signature, :connections
147
-
147
+
148
148
  def configure!(config)
149
149
  @listen = config['listen'] ? config['listen'] : '127.0.0.1'
150
150
  @port = config['port'] ? config['port'].to_i : 8000
@@ -179,7 +179,7 @@ class HTTPServer < RFlow::Component
179
179
  end
180
180
  end
181
181
  end
182
-
182
+
183
183
  class Connection < EventMachine::Connection
184
184
  include EventMachine::HttpServer
185
185
 
@@ -192,16 +192,16 @@ class HTTPServer < RFlow::Component
192
192
  no_environment_strings
193
193
  end
194
194
 
195
-
195
+
196
196
  def receive_data(data)
197
197
  RFlow.logger.debug "Received #{data.bytesize} data from #{client_ip}:#{client_port}"
198
198
  super
199
199
  end
200
-
201
-
200
+
201
+
202
202
  def process_http_request
203
203
  RFlow.logger.debug "Received a full HTTP request from #{client_ip}:#{client_port}"
204
-
204
+
205
205
  processing_event = RFlow::Message::ProcessingEvent.new(server.instance_uuid, Time.now.utc)
206
206
 
207
207
  request_message = RFlow::Message.new('HTTPRequest')
@@ -214,7 +214,7 @@ class HTTPServer < RFlow::Component
214
214
  server.request_port.send_message request_message
215
215
  end
216
216
 
217
-
217
+
218
218
  def send_http_response(response_message=nil)
219
219
  RFlow.logger.debug "Sending an HTTP response to #{client_ip}:#{client_port}"
220
220
  resp = EventMachine::DelegatedHttpResponse.new(self)
@@ -224,7 +224,7 @@ class HTTPServer < RFlow::Component
224
224
  resp.content = ""
225
225
  resp.headers["Content-Type"] = "text/html; charset=UTF-8"
226
226
  resp.headers["Server"] = "Apache/2.2.3 (CentOS)"
227
-
227
+
228
228
  if response_message
229
229
  resp.status = response_message.data.status
230
230
  resp.content = response_message.data.content
@@ -234,7 +234,7 @@ class HTTPServer < RFlow::Component
234
234
  close_connection_after_writing
235
235
  end
236
236
 
237
-
237
+
238
238
  # Called when a connection is torn down for whatever reason.
239
239
  # Remove this connection from the server's list
240
240
  def unbind
@@ -5,343 +5,39 @@ require 'time'
5
5
 
6
6
  require 'active_record'
7
7
  require 'eventmachine'
8
- require 'log4r'
9
8
  require 'sqlite3'
10
9
 
11
10
  require 'rflow/configuration'
12
11
 
13
- require 'rflow/component'
12
+ require 'rflow/master'
14
13
  require 'rflow/message'
15
14
 
16
15
  require 'rflow/components'
17
16
  require 'rflow/connections'
18
17
 
18
+ require 'rflow/logger'
19
19
 
20
20
  class RFlow
21
21
  include Log4r
22
22
 
23
23
  class Error < StandardError; end
24
24
 
25
- LOG_PATTERN_FORMAT = '%l [%d] %c (%p) - %M'
26
- DATE_METHOD = 'xmlschema(6)'
27
- LOG_PATTERN_FORMATTER = PatternFormatter.new :pattern => RFlow::LOG_PATTERN_FORMAT, :date_method => DATE_METHOD
28
-
29
- # Might be slightly faster, but also not completely correct XML
30
- #schema timestamps due to %z
31
- #DATE_PATTERN_FORMAT = '%Y-%m-%dT%H:%M:%S.%9N %z'
32
- #LOG_PATTERN_FORMATTER = PatternFormatter.new :pattern => RFlow::LOG_PATTERN_FORMAT, :date_pattern => DATE_PATTERN_FORMAT
33
-
34
25
  class << self
35
26
  attr_accessor :config_database_path
36
27
  attr_accessor :logger
37
28
  attr_accessor :configuration
38
- attr_accessor :components
39
- end
40
-
41
- # def self.initialize_config_database(config_database_path, config_file_path=nil)
42
- # # To handle relative paths in the config (all relative paths are
43
- # # relative to the config database
44
- # Dir.chdir File.dirname(config_database_path)
45
- # Configuration.new(File.basename(config_database_path), config_file_path)
46
- # end
47
-
48
- def self.initialize_logger(log_file_path, log_level='INFO', include_stdout=nil)
49
- rflow_logger = Logger.new((configuration['rflow.application_name'] rescue File.basename(log_file_path)))
50
- rflow_logger.level = LNAMES.index log_level
51
- # TODO: Remove this once all the logging puts in its own
52
- # Class.Method names.
53
- rflow_logger.trace = true
54
- begin
55
- rflow_logger.add FileOutputter.new('rflow.log_file', :filename => log_file_path, :formatter => LOG_PATTERN_FORMATTER)
56
- rescue Exception => e
57
- error_message = "Log file '#{File.expand_path log_file_path}' problem: #{e.message}\b#{e.backtrace.join("\n")}"
58
- RFlow.logger.error error_message
59
- raise ArgumentError, error_message
60
- end
61
-
62
- if include_stdout
63
- rflow_logger.add StdoutOutputter.new('rflow_stdout', :formatter => RFlow::LOG_PATTERN_FORMATTER)
64
- end
65
-
66
-
67
- RFlow.logger.info "Transitioning to running log file #{log_file_path} at level #{log_level}"
68
- RFlow.logger = rflow_logger
69
- end
70
-
71
- def self.reopen_log_file
72
- # TODO: Make this less of a hack, although Log4r doesn't support
73
- # it, so it might be permanent
74
- log_file = Outputter['rflow.log_file'].instance_variable_get(:@out)
75
- File.open(log_file.path, 'a') { |tmp_log_file| log_file.reopen(tmp_log_file) }
76
- end
77
-
78
- def self.close_log_file
79
- Outputter['rflow.log_file'].close
80
- end
81
-
82
- def self.toggle_log_level
83
- original_log_level = LNAMES[logger.level]
84
- new_log_level = (original_log_level == 'DEBUG' ? configuration['rflow.log_level'] : 'DEBUG')
85
- logger.warn "Changing log level from #{original_log_level} to #{new_log_level}"
86
- logger.level = LNAMES.index new_log_level
87
- end
88
-
89
- def self.trap_signals
90
- # Gracefully shutdown on termination signals
91
- ['SIGTERM', 'SIGINT', 'SIGQUIT'].each do |signal|
92
- Signal.trap signal do
93
- logger.warn "Termination signal (#{signal}) received, shutting down"
94
- shutdown
95
- end
96
- end
97
-
98
- # Reload on HUP
99
- ['SIGHUP'].each do |signal|
100
- Signal.trap signal do
101
- logger.warn "Reload signal (#{signal}) received, reloading"
102
- reload
103
- end
104
- end
105
-
106
- # Ignore terminal signals
107
- # TODO: Make sure this is valid for non-daemon (foreground) process
108
- ['SIGTSTP', 'SIGTTOU', 'SIGTTIN'].each do |signal|
109
- Signal.trap signal do
110
- logger.warn "Terminal signal (#{signal}) received, ignoring"
111
- end
112
- end
113
-
114
- # Reopen logs on USR1
115
- ['SIGUSR1'].each do |signal|
116
- Signal.trap signal do
117
- logger.warn "Reopen logs signal (#{signal}) received, reopening #{configuration['rflow.log_file_path']}"
118
- reopen_log_file
119
- end
120
- end
121
-
122
- # Toggle log level on USR2
123
- ['SIGUSR2'].each do |signal|
124
- Signal.trap signal do
125
- logger.warn "Toggle log level signal (#{signal}) received, toggling"
126
- toggle_log_level
127
- end
128
- end
129
-
130
- # TODO: Manage SIGCHLD when spawning other processes
131
- end
132
-
133
-
134
- # returns a PID if a given path contains a non-stale PID file,
135
- # nil otherwise.
136
- def self.running_pid_file_path?(pid_file_path)
137
- return nil unless File.exist? pid_file_path
138
- running_pid? File.read(pid_file_path).to_i
139
- end
140
-
141
- def self.running_pid?(pid)
142
- return if pid <= 0
143
- Process.kill(0, pid)
144
- pid
145
- rescue Errno::ESRCH, Errno::ENOENT
146
- nil
147
- end
148
-
149
- # unlinks a PID file at given if it contains the current PID still
150
- # potentially racy without locking the directory (which is
151
- # non-portable and may interact badly with other programs), but the
152
- # window for hitting the race condition is small
153
- def self.remove_pid_file(pid_file_path)
154
- (File.read(pid_file_path).to_i == $$ and File.unlink(pid_file_path)) rescue nil
155
- logger.debug "Removed PID (#$$) file '#{File.expand_path pid_file_path}'"
156
- end
157
-
158
- # TODO: Handle multiple instances and existing PID file
159
- def self.write_pid_file(pid_file_path)
160
- pid = running_pid_file_path?(pid_file_path)
161
- if pid && pid == $$
162
- logger.warn "Already running (#{pid.to_s}), not writing PID to file '#{File.expand_path pid_file_path}'"
163
- return pid_file_path
164
- elsif pid
165
- error_message = "Already running (#{pid.to_s}), possibly stale PID file '#{File.expand_path pid_file_path}'"
166
- logger.error error_message
167
- raise ArgumentError, error_message
168
- elsif File.exist? pid_file_path
169
- logger.warn "Found stale PID file '#{File.expand_path pid_file_path}', removing"
170
- remove_pid_file pid_file_path
171
- end
172
-
173
- logger.debug "Writing PID (#$$) file '#{File.expand_path pid_file_path}'"
174
- pid_fp = begin
175
- tmp_pid_file_path = File.join(File.dirname(pid_file_path), ".#{File.basename(pid_file_path)}")
176
- File.open(tmp_pid_file_path, File::RDWR|File::CREAT|File::EXCL, 0644)
177
- rescue Errno::EEXIST
178
- retry
179
- end
180
- pid_fp.syswrite("#$$\n")
181
- File.rename(pid_fp.path, pid_file_path)
182
- pid_fp.close
183
-
184
- pid_file_path
185
- end
186
-
187
- # TODO: Refactor this to be cleaner
188
- def self.daemonize!(application_name, pid_file_path)
189
- logger.info "#{application_name} daemonizing"
190
-
191
- # TODO: Drop privileges
192
-
193
- # Daemonize, but don't chdir or close outputs
194
- Process.daemon(true, true)
195
-
196
- # Set the process name
197
- $0 = application_name if application_name
198
-
199
- # Write the PID file
200
- write_pid_file pid_file_path
201
-
202
- # Close standard IO
203
- $stdout.sync = $stderr.sync = true
204
- $stdin.binmode; $stdout.binmode; $stderr.binmode
205
- begin; $stdin.reopen "/dev/null"; rescue ::Exception; end
206
- begin; $stdout.reopen "/dev/null"; rescue ::Exception; end
207
- begin; $stderr.reopen "/dev/null"; rescue ::Exception; end
208
-
209
- $$
210
- end
211
-
212
-
213
- # Iterate through each component config in the configuration
214
- # database and attempt to instantiate each one, storing the
215
- # resulting instantiated components in the 'components' class
216
- # instance attribute. This assumes that the specification of a
217
- # component is a fully qualified Ruby class that has already been
218
- # loaded. It will first attempt to find subclasses of
219
- # RFlow::Component (in the available_components hash) and then
220
- # attempt to constantize the specification into a different class. Future releases will
221
- # support external (i.e. non-managed components), but the current
222
- # stuff only supports Ruby classes
223
- def self.instantiate_components!
224
- logger.info "Instantiating components"
225
- self.components = Hash.new
226
- configuration.components.each do |component_config|
227
- if component_config.managed?
228
- logger.debug "Instantiating component '#{component_config.name}' as '#{component_config.specification}' (#{component_config.uuid})"
229
- begin
230
- logger.debug configuration.available_components.inspect
231
- instantiated_component = if configuration.available_components.include? component_config.specification
232
- logger.debug "Component found in configuration.available_components['#{component_config.specification}']"
233
- configuration.available_components[component_config.specification].new(component_config.uuid, component_config.name)
234
- else
235
- logger.debug "Component not found in configuration.available_components, constantizing component '#{component_config.specification}'"
236
- component_config.specification.constantize.new(component_config.uuid, component_config.name)
237
- end
238
-
239
- components[component_config.uuid] = instantiated_component
240
-
241
- rescue NameError => e
242
- error_message = "Could not instantiate component '#{component_config.name}' as '#{component_config.specification}' (#{component_config.uuid}): the class '#{component_config.specification}' was not found"
243
- logger.error error_message
244
- raise RuntimeError, error_message
245
- rescue Exception => e
246
- error_message = "Could not instantiate component '#{component_config.name}' as '#{component_config.specification}' (#{component_config.uuid}): #{e.class} #{e.message}"
247
- logger.error error_message
248
- raise RuntimeError, error_message
249
- end
250
- else
251
- error_message = "Non-managed components not yet implemented for component '#{component_config.name}' as '#{component_config.specification}' (#{component_config.uuid})"
252
- logger.error error_message
253
- raise NotImplementedError, error_message
254
- end
255
- end
256
- end
257
-
258
-
259
- # Iterate through the instantiated components and send each
260
- # component its soon-to-be connected port names and UUIDs
261
- def self.configure_component_ports!
262
- # Send the port configuration to each component
263
- logger.info "Configuring component ports and assigning UUIDs to port names"
264
- components.each do |component_instance_uuid, component|
265
- RFlow.logger.debug "Configuring ports for component '#{component.name}' (#{component.instance_uuid})"
266
- component_config = configuration.component(component.instance_uuid)
267
- component_config.input_ports.each do |input_port_config|
268
- RFlow.logger.debug "Configuring component '#{component.name}' (#{component.instance_uuid}) with input port '#{input_port_config.name}' (#{input_port_config.uuid})"
269
- component.configure_input_port!(input_port_config.name, input_port_config.uuid)
270
- end
271
- component_config.output_ports.each do |output_port_config|
272
- RFlow.logger.debug "Configuring component '#{component.name}' (#{component.instance_uuid}) with output port '#{output_port_config.name}' (#{output_port_config.uuid})"
273
- component.configure_output_port!(output_port_config.name, output_port_config.uuid)
274
- end
275
- end
276
- end
277
-
278
-
279
- # Iterate through the instantiated components and send each
280
- # component the information necessary to configure a connection on a
281
- # specific port, specifically the port UUID, port key, type of connection, uuid
282
- # of connection, and a configuration specific to the connection type
283
- def self.configure_component_connections!
284
- logger.info "Configuring component port connections"
285
- components.each do |component_instance_uuid, component|
286
- component_config = configuration.component(component.instance_uuid)
287
-
288
- logger.debug "Configuring input connections for component '#{component.name}' (#{component.instance_uuid})"
289
- component_config.input_ports.each do |input_port_config|
290
- input_port_config.input_connections.each do |input_connection_config|
291
- logger.debug "Configuring input port '#{input_port_config.name}' (#{input_port_config.uuid}) key '#{input_connection_config.input_port_key}' with #{input_connection_config.type.to_s} connection '#{input_connection_config.name}' (#{input_connection_config.uuid})"
292
- component.configure_connection!(input_port_config.uuid, input_connection_config.input_port_key,
293
- input_connection_config.type, input_connection_config.uuid, input_connection_config.name, input_connection_config.options)
294
- end
295
- end
296
-
297
- logger.debug "Configuring output connections for '#{component.name}' (#{component.instance_uuid})"
298
- component_config.output_ports.each do |output_port_config|
299
- output_port_config.output_connections.each do |output_connection_config|
300
- logger.debug "Configuring output port '#{output_port_config.name}' (#{output_port_config.uuid}) key '#{output_connection_config.output_port_key}' with #{output_connection_config.type.to_s} connection '#{output_connection_config.name}' (#{output_connection_config.uuid})"
301
- component.configure_connection!(output_port_config.uuid, output_connection_config.output_port_key,
302
- output_connection_config.type, output_connection_config.uuid, output_connection_config.name, output_connection_config.options)
303
- end
304
- end
305
- end
306
- end
307
-
308
-
309
- # Send the component-specific configuration to the component
310
- def self.configure_components!
311
- logger.info "Configuring components with component-specific configurations"
312
- components.each do |component_uuid, component|
313
- component_config = configuration.component(component.instance_uuid)
314
- logger.debug "Configuring component '#{component.name}' (#{component.instance_uuid})"
315
- component.configure!(component_config.options)
316
- end
29
+ attr_accessor :master
317
30
  end
318
31
 
319
- # Send a command to each component to tell them to connect their
320
- # ports via their connections
321
- def self.connect_components!
322
- logger.info "Connecting components"
323
- components.each do |component_uuid, component|
324
- logger.debug "Connecting component '#{component.name}' (#{component.instance_uuid})"
325
- component.connect!
326
- end
327
- end
328
-
329
- # Start each component running
330
- def self.run_components!
331
- logger.info "Running components"
332
- components.each do |component_uuid, component|
333
- logger.debug "Running component '#{component.name}' (#{component.instance_uuid})"
334
- component.run!
335
- end
336
- end
337
-
338
- def self.run(config_database_path, daemonize=nil)
32
+ def self.run(config_database_path=nil, daemonize=nil)
339
33
  self.configuration = Configuration.new(config_database_path)
340
34
 
341
- # First change to the config database directory, which might hold
342
- # relative paths for the other files/directories, such as the
343
- # application_directory_path
344
- Dir.chdir File.dirname(config_database_path)
35
+ if config_database_path
36
+ # First change to the config database directory, which might hold
37
+ # relative paths for the other files/directories, such as the
38
+ # application_directory_path
39
+ Dir.chdir File.dirname(config_database_path)
40
+ end
345
41
 
346
42
  # Bail unless you have some of the basic information. TODO:
347
43
  # rethink this when things get more dynamic
@@ -352,53 +48,15 @@ class RFlow
352
48
  end
353
49
 
354
50
  Dir.chdir configuration['rflow.application_directory_path']
355
-
356
- initialize_logger(configuration['rflow.log_file_path'], configuration['rflow.log_level'], !daemonize)
357
-
358
- application_name = configuration['rflow.application_name']
359
- logger.info "#{application_name} starting"
360
-
361
- logger.info "#{application_name} configured, starting flow"
362
- logger.debug "Available Components: #{RFlow::Configuration.available_components.inspect}"
363
- logger.debug "Available Data Types: #{RFlow::Configuration.available_data_types.inspect}"
364
- logger.debug "Available Data Extensions: #{RFlow::Configuration.available_data_extensions.inspect}"
365
-
366
- # TODO: Start up a FlowManager component and connect it to the
367
- # management interface on all the components.
368
-
369
- instantiate_components!
370
- configure_component_ports!
371
- configure_component_connections!
372
- configure_components!
373
-
374
- # At this point, each component should have their entire
375
- # configuration for the component-specific stuff and all the
376
- # connections and be ready to be connected to the others and start
377
- # running
378
-
379
-
380
- # Daemonize
381
- trap_signals
382
- daemonize!(application_name, configuration['rflow.pid_file_path']) if daemonize
383
- write_pid_file configuration['rflow.pid_file_path']
384
-
385
- # Start the event loop and startup each component
386
- EM.run do
387
- connect_components!
388
51
 
389
- components.each do |component_uuid, component|
390
- RFlow.logger.debug component.to_s
391
- end
52
+ self.logger = RFlow::Logger.new(configuration, !daemonize)
53
+ @master = Master.new(configuration)
392
54
 
393
- run_components!
55
+ master.daemonize! if daemonize
56
+ master.run # Runs EM and doesn't return
394
57
 
395
- # Sit back and relax because everything is running
396
- end
397
-
398
58
  # Should never get here
399
- shutdown
400
-
401
- # TODO: Look into Parallel::ForkManager
59
+ logger.warn "going down"
402
60
  rescue SystemExit => e
403
61
  # Do nothing, just prevent a normal exit from causing an unsightly
404
62
  # error in the logs
@@ -407,34 +65,4 @@ class RFlow
407
65
  exit 1
408
66
  end
409
67
 
410
- def self.shutdown
411
- logger.info "#{configuration['rflow.application_name']} shutting down"
412
-
413
- logger.debug "Shutting down components"
414
- components.each do |component_instance_uuid, component|
415
- logger.debug "Shutting down component '#{component.name}' (#{component.instance_uuid})"
416
- component.shutdown!
417
- end
418
-
419
- # TODO: Ensure that all the components have shut down before
420
- # cleaning up
421
-
422
- logger.debug "Cleaning up components"
423
- components.each do |component_instance_uuid, component|
424
- logger.debug "Cleaning up component '#{component.name}' (#{component.instance_uuid})"
425
- component.cleanup!
426
- end
427
-
428
- remove_pid_file configuration['rflow.pid_file_path']
429
- logger.info "#{configuration['rflow.application_name']} exiting"
430
- exit 0
431
- end
432
-
433
- def self.reload
434
- # TODO: Actually do a real reload
435
- logger.info "#{configuration['rflow.application_name']} reloading"
436
- reload_log_file
437
- logger.info "#{configuration['rflow.application_name']} reloaded"
438
- end
439
-
440
68
  end # class RFlow