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.
- 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
|