rflow 0.0.5 → 1.0.0a1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +21 -0
- data/.yardopts +1 -0
- data/Gemfile +5 -1
- data/Guardfile +8 -0
- data/LICENSE +190 -0
- data/NOTES +26 -13
- data/README.md +448 -0
- data/Rakefile +5 -12
- data/bin/rflow +23 -20
- data/example/basic_config.rb +2 -2
- data/example/basic_extensions.rb +8 -8
- data/example/http_config.rb +1 -1
- data/example/http_extensions.rb +15 -15
- data/lib/rflow.rb +15 -387
- data/lib/rflow/component.rb +105 -50
- data/lib/rflow/component/port.rb +25 -24
- data/lib/rflow/components/raw.rb +4 -4
- data/lib/rflow/components/raw/extensions.rb +2 -2
- data/lib/rflow/configuration.rb +54 -36
- data/lib/rflow/configuration/component.rb +2 -3
- data/lib/rflow/configuration/connection.rb +9 -10
- data/lib/rflow/configuration/migrations/{20010101000001_create_settings.rb → 20010101000000_create_settings.rb} +2 -2
- data/lib/rflow/configuration/migrations/20010101000001_create_shards.rb +21 -0
- data/lib/rflow/configuration/migrations/20010101000002_create_components.rb +7 -2
- data/lib/rflow/configuration/migrations/20010101000003_create_ports.rb +3 -3
- data/lib/rflow/configuration/migrations/20010101000004_create_connections.rb +2 -2
- data/lib/rflow/configuration/port.rb +3 -4
- data/lib/rflow/configuration/ruby_dsl.rb +59 -35
- data/lib/rflow/configuration/setting.rb +8 -7
- data/lib/rflow/configuration/shard.rb +24 -0
- data/lib/rflow/configuration/uuid_keyed.rb +3 -3
- data/lib/rflow/connection.rb +21 -10
- data/lib/rflow/connections/zmq_connection.rb +45 -44
- data/lib/rflow/logger.rb +67 -0
- data/lib/rflow/master.rb +127 -0
- data/lib/rflow/message.rb +14 -14
- data/lib/rflow/pid_file.rb +84 -0
- data/lib/rflow/shard.rb +148 -0
- data/lib/rflow/version.rb +1 -1
- data/rflow.gemspec +22 -28
- data/schema/message.avsc +8 -8
- data/spec/fixtures/config_ints.rb +4 -4
- data/spec/fixtures/config_shards.rb +30 -0
- data/spec/fixtures/extensions_ints.rb +8 -8
- data/spec/rflow_component_port_spec.rb +58 -0
- data/spec/rflow_configuration_ruby_dsl_spec.rb +148 -0
- data/spec/rflow_configuration_spec.rb +4 -4
- data/spec/rflow_message_data_raw.rb +2 -2
- data/spec/rflow_message_data_spec.rb +6 -6
- data/spec/rflow_message_spec.rb +13 -13
- data/spec/rflow_spec.rb +294 -71
- data/spec/schema_spec.rb +2 -2
- data/spec/spec_helper.rb +6 -4
- data/temp.rb +21 -21
- metadata +56 -65
- data/.rvmrc +0 -1
- 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 '
|
4
|
-
Bundler::GemHelper.install_tasks
|
3
|
+
require 'yard'
|
5
4
|
|
6
|
-
RSpec::Core::RakeTask.new(:spec)
|
7
|
-
t.verbose = true
|
8
|
-
t.rspec_opts = '--tty --color'
|
9
|
-
end
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
10
6
|
|
11
|
-
|
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
|
-
|
160
|
+
pid_file = RFlow::PIDFile.new(config['rflow.pid_file_path'])
|
161
|
+
|
159
162
|
|
160
163
|
case command
|
161
164
|
when 'stop'
|
162
|
-
if
|
163
|
-
startup_logger.info "#{config['rflow.application_name']} running, process #{
|
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
|
-
|
168
|
+
pid_file.signal(:INT)
|
166
169
|
else
|
167
|
-
startup_logger.warn "#{config['rflow.application_name']} process not found in #{
|
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
|
174
|
-
startup_logger.error "#{config['rflow.application_name']} process not found in #{
|
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 #{
|
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
|
182
|
-
startup_logger.error "#{config['rflow.application_name']} already running, process #{
|
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
|
data/example/basic_config.rb
CHANGED
@@ -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
|
|
data/example/basic_extensions.rb
CHANGED
@@ -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
|
data/example/http_config.rb
CHANGED
@@ -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'
|
data/example/http_extensions.rb
CHANGED
@@ -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
|
data/lib/rflow.rb
CHANGED
@@ -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/
|
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 :
|
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
|
-
|
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
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
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
|
-
|
390
|
-
|
391
|
-
end
|
52
|
+
self.logger = RFlow::Logger.new(configuration, !daemonize)
|
53
|
+
@master = Master.new(configuration)
|
392
54
|
|
393
|
-
|
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
|
-
|
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
|