rflow 0.0.5 → 1.0.0a1

Sign up to get free protection for your applications and to get access to all the features.
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