pipeline_toolkit 1.0.4 → 1.0.6
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.
- data/LICENSE.markdown +20 -0
- data/README.markdown +98 -0
- data/bin/msg_generator +13 -0
- data/bin/msg_probe +13 -0
- data/bin/msg_push +13 -0
- data/bin/msg_sink +14 -0
- data/bin/msg_subscribe +13 -0
- data/lib/pipeline_toolkit.rb +17 -0
- data/lib/pipeline_toolkit/amqp/abstract.rb +95 -0
- data/lib/pipeline_toolkit/amqp/reader.rb +64 -0
- data/lib/pipeline_toolkit/amqp/writer.rb +54 -0
- data/lib/pipeline_toolkit/commands/msg_generator/cli.rb +45 -0
- data/lib/pipeline_toolkit/commands/msg_probe/cli.rb +46 -0
- data/lib/pipeline_toolkit/commands/msg_push/cli.rb +58 -0
- data/lib/pipeline_toolkit/commands/msg_sink/cli.rb +41 -0
- data/lib/pipeline_toolkit/commands/msg_subscribe/cli.rb +58 -0
- data/lib/pipeline_toolkit/default_logger.rb +126 -11
- data/lib/pipeline_toolkit/handlers/message_handler.rb +38 -0
- data/lib/pipeline_toolkit/message_coder.rb +18 -8
- data/lib/pipeline_toolkit/message_command.rb +138 -61
- data/lib/pipeline_toolkit/message_generator.rb +21 -0
- data/lib/pipeline_toolkit/message_probe.rb +6 -6
- data/lib/pipeline_toolkit/message_pusher.rb +51 -54
- data/lib/pipeline_toolkit/message_sink.rb +1 -1
- data/lib/pipeline_toolkit/message_subscriber.rb +182 -201
- data/lib/pipeline_toolkit/monitoring/monitor_server.rb +124 -0
- data/spec/eventmachine_helper.rb +44 -0
- data/spec/message_subscriber_spec.rb +64 -0
- data/spec/spec_helper.rb +15 -0
- metadata +202 -47
- data/.gitignore +0 -5
- data/README.rdoc +0 -70
- data/Rakefile +0 -40
- data/VERSION +0 -1
- data/bin/msg_generator.rb +0 -0
- data/bin/msg_probe.rb +0 -15
- data/bin/msg_push.rb +0 -25
- data/bin/msg_sink.rb +0 -11
- data/bin/msg_subscribe.rb +0 -27
- data/monitor/munin.rb +0 -91
- data/pipeline_toolkit.gemspec +0 -72
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'pp'
|
2
|
+
require 'trollop'
|
3
|
+
require 'eventmachine'
|
4
|
+
|
5
|
+
Signal.trap('INT') do
|
6
|
+
# puts "\nStopping"
|
7
|
+
# TODO: complete signal trap interrupt
|
8
|
+
end
|
9
|
+
|
10
|
+
Signal.trap('TERM') do
|
11
|
+
# puts "\nStopping"
|
12
|
+
# TODO: complete signal trap terminate
|
13
|
+
end
|
14
|
+
|
15
|
+
module Commands # :nodoc:
|
16
|
+
module MsgProbe
|
17
|
+
class CLI
|
18
|
+
def self.execute(stdout, arguments=[])
|
19
|
+
|
20
|
+
opts = Trollop::options do
|
21
|
+
banner <<-EOL
|
22
|
+
Message Probe
|
23
|
+
------------------
|
24
|
+
TODO: description
|
25
|
+
|
26
|
+
Usage:
|
27
|
+
msg_probe [options]
|
28
|
+
|
29
|
+
Examples:
|
30
|
+
msg_probe --name octane_message_formatter
|
31
|
+
msg_probe --name octane_message_formatter --http_port 10001 --interval 1
|
32
|
+
|
33
|
+
Options:
|
34
|
+
EOL
|
35
|
+
opt :interval, "Time in seconds between updates", :short => "i", :default => 2
|
36
|
+
opt :http_port, "The port the HTTP server runs on. Default is a random port between 10000-11000", :type => :integer
|
37
|
+
opt :name, "The name of the probe. Used in monitoring", :type => :string, :short => 'n'
|
38
|
+
opt :dnssd, "Switches on DNSSD (i.e. Bonjour) for the monitoring interface", :short => 'd'
|
39
|
+
opt :env, "The environment to run (development, production)", :default => "development", :short => :e
|
40
|
+
end
|
41
|
+
|
42
|
+
MessageProbe.new(opts).start
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'pp'
|
2
|
+
require 'trollop'
|
3
|
+
require 'eventmachine'
|
4
|
+
|
5
|
+
module Commands # :nodoc:
|
6
|
+
module MsgPush
|
7
|
+
class CLI
|
8
|
+
def self.execute(stdout, arguments=[])
|
9
|
+
|
10
|
+
opts = Trollop::options do
|
11
|
+
banner <<-EOL
|
12
|
+
Message Publisher
|
13
|
+
------------------
|
14
|
+
Publish to a Message Queue server, the received message from the
|
15
|
+
last worker in the pipeline.
|
16
|
+
|
17
|
+
Usage:
|
18
|
+
msg_push [options]
|
19
|
+
|
20
|
+
Examples:
|
21
|
+
msg_push --exchange holding_tank
|
22
|
+
msg_push --exchange holding_tank --type topic \\
|
23
|
+
--queues us_stocks:stock.us.*, dax:stock.de.dax
|
24
|
+
|
25
|
+
Options:
|
26
|
+
EOL
|
27
|
+
|
28
|
+
# Msg exchange
|
29
|
+
opt :exchange, "Exchange name", :type => :string, :required => true, :short => :x
|
30
|
+
opt :type, "Exchange type (direct, fanout, topic)", :default => "fanout", :short => :t
|
31
|
+
opt :passive, "If true, the exchange will not be created if it does not already exist.", :default => false, :short => :s
|
32
|
+
opt :durable, "If true, the exchange will be marked as durable.", :default => false, :short => :d
|
33
|
+
|
34
|
+
# Msg queues
|
35
|
+
opt :queues, "Destination queue(s) (e.g. queue1:routing_key, queue2:routing_key). Routing keys will be ignored if exchange type is not topic.", :type => :strings, :required => false, :short => :q
|
36
|
+
|
37
|
+
# Msg server
|
38
|
+
opt :host, "AMQP message server host", :default => "localhost", :short => :h
|
39
|
+
opt :port, "AMQP message server port", :default => 5672, :short => :p
|
40
|
+
opt :user, "AMQP message server username", :default => "guest", :short => :u
|
41
|
+
opt :pass, "AMQP message server password", :default => "guest", :short => :w
|
42
|
+
opt :vhost, "AMQP message server vhost", :default => "/", :short => :v
|
43
|
+
|
44
|
+
opt :env, "Environment (development, production)", :default => "development", :short => :e
|
45
|
+
end
|
46
|
+
|
47
|
+
# Change ['queue1:routing_key', ...] to [['queue1','routing_key'], ...]
|
48
|
+
queues = []
|
49
|
+
opts.delete(:queues).each do |queue| # trollop gives an array of strings
|
50
|
+
queues << queue.strip.split(":")
|
51
|
+
end
|
52
|
+
opts[:queues] = queues
|
53
|
+
|
54
|
+
MessagePusher.new(opts).start
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'trollop'
|
3
|
+
|
4
|
+
Signal.trap('INT') do
|
5
|
+
# puts "\nStopping"
|
6
|
+
# TODO: complete signal trap interrupt
|
7
|
+
end
|
8
|
+
|
9
|
+
Signal.trap('TERM') do
|
10
|
+
# puts "\nStopping"
|
11
|
+
# TODO: complete signal trap terminate
|
12
|
+
end
|
13
|
+
|
14
|
+
module Commands # :nodoc:
|
15
|
+
module MsgSink
|
16
|
+
class CLI
|
17
|
+
def self.execute(stdout, arguments=[])
|
18
|
+
|
19
|
+
opts = Trollop::options do
|
20
|
+
banner <<-EOL
|
21
|
+
Message Sink
|
22
|
+
------------------
|
23
|
+
TODO: description
|
24
|
+
|
25
|
+
Usage:
|
26
|
+
msg_sink [options]
|
27
|
+
|
28
|
+
Examples:
|
29
|
+
msg_sink
|
30
|
+
|
31
|
+
Options:
|
32
|
+
EOL
|
33
|
+
|
34
|
+
opt :env, "The environment to run (development, production)", :default => "development", :short => :e
|
35
|
+
end
|
36
|
+
|
37
|
+
MessageSink.new(opts).start
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'trollop'
|
2
|
+
require 'eventmachine'
|
3
|
+
|
4
|
+
module Commands # :nodoc:
|
5
|
+
module MsgSubscribe # :nodoc:
|
6
|
+
|
7
|
+
class CLI
|
8
|
+
def self.execute(stdout, arguments=[])
|
9
|
+
|
10
|
+
opts = Trollop::options do
|
11
|
+
banner <<-EOL
|
12
|
+
Message Subscriber
|
13
|
+
------------------
|
14
|
+
Subscribe to a Message Queue server using AMQP and pass messages to the next worker.
|
15
|
+
|
16
|
+
Usage:
|
17
|
+
msg_subscribe [options]
|
18
|
+
|
19
|
+
Examples:
|
20
|
+
msg_subscribe --name octane_message_formatter --port 8000 \\
|
21
|
+
--queue us_stocks
|
22
|
+
msg_subscribe --exchange octane --type topic --queue us_stocks \\
|
23
|
+
--host www.domain.com --port 7000
|
24
|
+
|
25
|
+
Options:
|
26
|
+
EOL
|
27
|
+
|
28
|
+
# Msg exchange
|
29
|
+
opt :exchange, "The exchange name", :type => :string, :required => true, :short => :x
|
30
|
+
opt :type, "The exchange type (direct, fanout or topic)", :default => "fanout", :short => :t
|
31
|
+
opt :passive, "If set to true, the server will not create the exchange if it does not already exist.", :default => false, :short => :s
|
32
|
+
opt :durable, "If set to true, the exchange will be marked as durable.", :default => false, :short => :d
|
33
|
+
|
34
|
+
# Messages
|
35
|
+
opt :queue, "The destination queue (queue:routing_key). Routing keys will be ignored if exchange type is not topic.", :short => :q, :type => :string, :required => true
|
36
|
+
opt :ack, "If this field is set to false the server does not expect acknowledgments for messages.", :short => :a, :type => :boolean, :default => true
|
37
|
+
|
38
|
+
# Msg server
|
39
|
+
opt :host, "The AMQP message server host", :default => "localhost", :short => :h
|
40
|
+
opt :port, "The AMQP message server port", :default => 5672, :short => :p
|
41
|
+
opt :user, "The AMQP message server username", :default => "guest", :short => :u
|
42
|
+
opt :pass, "The AMQP message server username", :default => "guest", :short => :w
|
43
|
+
opt :vhost, "The AMQP message server vhost", :default => "/", :short => :v
|
44
|
+
|
45
|
+
# Monitoring
|
46
|
+
opt :name, "The name used to describe the entire process chain", :type => :string, :short => :n
|
47
|
+
opt :http_port, "The port the HTTP monitoring server runs on. Default is a random port between 10000-11000", :type => :integer, :short => :o
|
48
|
+
opt :content_type, "The type of response we are expecting from the HTTP monitoring server. (http, xml and json supported)", :type => :string, :default => "html", :short => :c
|
49
|
+
opt :dnssd, "Switches on DNSSD (i.e. Bonjour) for the monitoring interface", :type => :boolean, :default => false, :short => :b
|
50
|
+
|
51
|
+
opt :env, "The environment to run (development, production)", :default => "development", :short => :e
|
52
|
+
end
|
53
|
+
|
54
|
+
MessageSubscriber.new(opts).start
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -1,16 +1,131 @@
|
|
1
|
-
|
2
|
-
require
|
1
|
+
begin
|
2
|
+
require "logger"
|
3
|
+
require "fileutils"
|
4
|
+
require 'syslog_logger'
|
5
|
+
rescue LoadError
|
6
|
+
end
|
3
7
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
def
|
9
|
-
|
8
|
+
|
9
|
+
# replace for tidy timestamped logs
|
10
|
+
class Logger
|
11
|
+
remove_method :format_message
|
12
|
+
def format_message(severity, timestamp, progname, msg)
|
13
|
+
"#{timestamp.strftime('%b%d %H:%M:%S')} #{msg}\n"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Default logger supports logging to both syslogger and logger. When running the
|
19
|
+
# logger in 'development' mode will cause the logger to log to the
|
20
|
+
# $HOME/pipeline_toolkit/logs directory. Alternativaly, in 'production' the logger
|
21
|
+
# logs to syslog. You can set the environment by passing the :env option with either
|
22
|
+
# 'development' or 'production' when calling the {.init_logger} method.
|
23
|
+
#
|
24
|
+
class DefaultLogger
|
25
|
+
|
26
|
+
##
|
27
|
+
# Initialize a new logger
|
28
|
+
#
|
29
|
+
# @param options<Hash> Options hash of settings
|
30
|
+
# @option options [String] :env The environment to we initialize the logger for.
|
31
|
+
# @option options [String] :log_name The name to start the log name with. ie. [log_name]_[env].log
|
32
|
+
#
|
33
|
+
def self.init_logger(options = {})
|
34
|
+
options[:env] ||= "development"
|
35
|
+
options[:log_name] ||= "pipeline"
|
36
|
+
|
37
|
+
if options[:env] == "development"
|
38
|
+
@@logger ||= Logger.new("#{log_directories}/#{options[:log_name]}_#{options[:env]}.log", 'daily')
|
39
|
+
@@logger.level = Logger::DEBUG
|
40
|
+
else
|
41
|
+
@@logger ||= SyslogLogger.new("#{options[:log_name]}_#{options[:env]}")
|
42
|
+
@@logger.level = Logger::INFO
|
43
|
+
end
|
44
|
+
|
45
|
+
@@logger.datetime_format = "%Y-%m-%d %H:%M:%S"
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Setter for setting the logger to use
|
50
|
+
#
|
51
|
+
# @param log<Logger> The new logger to use
|
52
|
+
#
|
53
|
+
def self.logger=(log)
|
54
|
+
@@logger = log
|
10
55
|
end
|
11
56
|
|
12
|
-
|
13
|
-
|
57
|
+
##
|
58
|
+
# Getter to return the logger
|
59
|
+
#
|
60
|
+
def self.logger
|
61
|
+
@@logger
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# Setter for setting the log level to use
|
66
|
+
#
|
67
|
+
# @param new_level<Integer> The new log level to use
|
68
|
+
#
|
69
|
+
def self.level=(new_level)
|
70
|
+
logger.level = new_level
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# Getter for the log level
|
75
|
+
#
|
76
|
+
def self.level
|
77
|
+
logger.level
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# Publish a debug log message
|
82
|
+
#
|
83
|
+
# @param message<String> The log message to publish
|
84
|
+
#
|
85
|
+
def self.debug(message)
|
86
|
+
logger.debug(message)
|
14
87
|
end
|
15
88
|
|
16
|
-
|
89
|
+
##
|
90
|
+
# Publish a info log message
|
91
|
+
#
|
92
|
+
# @param message<String> The log message to publish
|
93
|
+
#
|
94
|
+
def self.info(message)
|
95
|
+
logger.info(message)
|
96
|
+
end
|
97
|
+
|
98
|
+
##
|
99
|
+
# Publish a warning log message
|
100
|
+
#
|
101
|
+
# @param message<String> The log message to publish
|
102
|
+
#
|
103
|
+
def self.warn(message)
|
104
|
+
logger.warn(message)
|
105
|
+
end
|
106
|
+
|
107
|
+
##
|
108
|
+
# Publish a error log message
|
109
|
+
#
|
110
|
+
# @param message<String> The log message to publish
|
111
|
+
#
|
112
|
+
def self.error(message)
|
113
|
+
logger.error(message)
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
##
|
119
|
+
# Ensure we have the log directories in the home path
|
120
|
+
#
|
121
|
+
def self.log_directories
|
122
|
+
# Ensure the log directory exist
|
123
|
+
dir = "#{ENV["HOME"]}/pipeline_toolkit/logs"
|
124
|
+
unless File.directory?(dir)
|
125
|
+
# Create log directory
|
126
|
+
FileUtils.makedirs(dir)
|
127
|
+
end
|
128
|
+
dir
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Handlers # :nodoc:
|
2
|
+
|
3
|
+
##
|
4
|
+
# The Message Process handler that is called by Event Machine reactor loop
|
5
|
+
# when it receives a new message from IO watcher
|
6
|
+
#
|
7
|
+
module MessageHandler
|
8
|
+
|
9
|
+
attr_reader :options
|
10
|
+
|
11
|
+
##
|
12
|
+
# Initialize a new instance
|
13
|
+
#
|
14
|
+
# @param msg_command<MessageCommand> An instance of the object to delegate the message handling to
|
15
|
+
# @param options<Hash> An options hash
|
16
|
+
#
|
17
|
+
def initialize(target, options = {})
|
18
|
+
@options = options
|
19
|
+
@target = target
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# The callback method that EventMachine calls as soon as a new message arrives
|
24
|
+
#
|
25
|
+
def notify_readable
|
26
|
+
DefaultLogger.debug("Handlers::MessageHandler#notify_readable") if options[:env] == "development"
|
27
|
+
message_coded = @io.gets
|
28
|
+
unless message_coded.nil?
|
29
|
+
DefaultLogger.debug("Raw message: #{message_coded}") if options[:env] == "development"
|
30
|
+
message = MessageCoder.decode(message_coded.chomp)
|
31
|
+
DefaultLogger.debug("Message: #{message}") if options[:env] == "development"
|
32
|
+
@target.process(message)
|
33
|
+
end
|
34
|
+
rescue StandardError => e # rescued here because main thread does not seem to see it
|
35
|
+
DefaultLogger.error("#{e.class.name}: #{e.message}\n" << e.backtrace.join("\n"))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -1,18 +1,28 @@
|
|
1
|
-
|
2
|
-
#
|
3
|
-
#
|
1
|
+
##
|
2
|
+
# Encode and decode messages using Marshal
|
3
|
+
#
|
4
4
|
class MessageCoder
|
5
|
-
|
6
|
-
|
5
|
+
|
6
|
+
##
|
7
|
+
# Encode the hash message
|
8
|
+
#
|
9
|
+
# @param message<Hash> The message as a hash
|
10
|
+
#
|
11
|
+
def self.encode(message)
|
7
12
|
# NB: Using Marshal here because it's 9-10x faster than to_yaml
|
8
13
|
# See http://gist.github.com/190849
|
9
|
-
str = Marshal.dump(
|
14
|
+
str = Marshal.dump(message)
|
10
15
|
str.gsub!("\n", '--\\n')
|
11
|
-
str
|
16
|
+
str
|
12
17
|
end
|
13
18
|
|
19
|
+
##
|
20
|
+
# Decode the binary string into a hash
|
21
|
+
#
|
22
|
+
# @param str<String> Binary string
|
23
|
+
#
|
14
24
|
def self.decode(str)
|
15
|
-
str.gsub!('--\\n', "\n")
|
25
|
+
str.gsub!('--\\n', "\n")
|
16
26
|
Marshal.load(str)
|
17
27
|
end
|
18
28
|
|
@@ -1,104 +1,181 @@
|
|
1
1
|
require "rubygems"
|
2
2
|
require "eventmachine"
|
3
3
|
|
4
|
+
##
|
5
|
+
# Provide abstract base functionality for pipeline machines.
|
6
|
+
#
|
7
|
+
# Required, To implement a your own machine, override the following methods:
|
8
|
+
#
|
9
|
+
# * include MessageCommand
|
10
|
+
# * {#process_standard} Handle the messages
|
11
|
+
#
|
12
|
+
# Optionally, your machine can implement the following:
|
13
|
+
#
|
14
|
+
# * {#initialize_machine} Setup your machine
|
15
|
+
# * {#report_back} Notify on the acknowledgement pipe about your worker
|
16
|
+
#
|
17
|
+
# @example An example of a pipeline machine
|
18
|
+
#
|
19
|
+
# class DefaultMachine
|
20
|
+
# include MessageCommand
|
21
|
+
#
|
22
|
+
# def report_back
|
23
|
+
#
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# def process_standard(message)
|
27
|
+
# # do some work ie. transform the message or apply some business rules
|
28
|
+
# # raise exception if processing or business rule fails
|
29
|
+
# pass_on(message)
|
30
|
+
# rescue
|
31
|
+
# # handle the storage of the message that could not be processed
|
32
|
+
# # and acknowledge the AMQP server server
|
33
|
+
# acknowledge(message)
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# DefaultMachine.new.start
|
38
|
+
#
|
39
|
+
#
|
40
|
+
# @abstract
|
41
|
+
#
|
4
42
|
module MessageCommand
|
5
|
-
include DefaultLogger
|
6
43
|
|
7
|
-
|
8
|
-
|
44
|
+
##
|
45
|
+
# The options the machine should use
|
46
|
+
#
|
47
|
+
attr_reader :options
|
48
|
+
|
49
|
+
##
|
50
|
+
# Initialize a new instance. A message command can only
|
51
|
+
# be initialized through the implementation instance.
|
52
|
+
#
|
53
|
+
# @param options<Hash> An options hash.
|
54
|
+
#
|
55
|
+
def initialize(options = {})
|
56
|
+
DefaultLogger.init_logger(options)
|
57
|
+
@options = options
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Starts the machine up, ie. creates a new eventmachine reactor loop
|
62
|
+
# and starts watching the STDIN for new messages
|
63
|
+
#
|
9
64
|
def start
|
10
|
-
|
11
|
-
|
12
|
-
Signal.trap('INT') { EM.stop }
|
13
|
-
Signal.trap('TERM'){ EM.stop }
|
65
|
+
DefaultLogger.debug("MessageCommand#start") if options[:env] == "development"
|
14
66
|
|
15
|
-
|
67
|
+
Signal.trap('INT') { EM.stop }
|
68
|
+
Signal.trap('TERM') { EM.stop }
|
16
69
|
|
17
70
|
begin
|
18
71
|
EM.run do
|
19
|
-
conn = EM.watch($stdin,
|
72
|
+
conn = EM.watch($stdin, Handlers::MessageHandler, self, options)
|
73
|
+
# must call this to setup callback to notify_readable
|
20
74
|
conn.notify_readable = true
|
21
|
-
|
75
|
+
|
76
|
+
initialize_machine
|
22
77
|
end
|
23
78
|
rescue StandardError => e
|
24
|
-
|
79
|
+
DefaultLogger.error("#{e.class.name}: #{e.message}\n" << e.backtrace.join("\n"))
|
25
80
|
raise e
|
26
81
|
ensure
|
27
|
-
|
82
|
+
shutdown
|
28
83
|
end
|
29
84
|
end
|
30
85
|
|
86
|
+
##
|
87
|
+
# Stops the machine
|
88
|
+
#
|
31
89
|
def shutdown
|
32
|
-
|
33
|
-
|
90
|
+
DefaultLogger.info("Shutting down #{self.class.name}")
|
91
|
+
DefaultLogger.info "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"
|
92
|
+
@ack_pipe.close if @ack_pipe
|
34
93
|
end
|
35
94
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
case result
|
47
|
-
when :ack
|
48
|
-
self.ack_msg(msg)
|
95
|
+
##
|
96
|
+
# Callback for the Handlers::MessageHandler when it receives a message
|
97
|
+
#
|
98
|
+
# @param message<Hash> The decoded message
|
99
|
+
#
|
100
|
+
def process(message)
|
101
|
+
DefaultLogger.debug("MessageCommand#process(message)") if options[:env] == "development"
|
102
|
+
if message[:msg_type] == :system
|
103
|
+
process_system(message)
|
49
104
|
else
|
50
|
-
|
105
|
+
process_standard(message)
|
51
106
|
end
|
52
107
|
end
|
53
108
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
109
|
+
##
|
110
|
+
# Notify the AMQP server server that we've handled the message
|
111
|
+
#
|
112
|
+
# @param message<Hash> The message to acknowledge
|
113
|
+
#
|
114
|
+
def acknowledge(message)
|
115
|
+
DefaultLogger.debug("MessageCommand#acknowledge(message)") if options[:env] == "development"
|
116
|
+
ack_message = {:msg_type => :ack, :ack_id => message[:ack_id]}
|
117
|
+
write_to_pipe(ack_message, @ack_pipe)
|
58
118
|
end
|
59
119
|
|
60
|
-
|
61
|
-
|
120
|
+
##
|
121
|
+
# Pass the message to the next machine in the pipeline by writing the message to the STDOUT.
|
122
|
+
#
|
123
|
+
# @param message<Hash> The message to pass on
|
124
|
+
#
|
125
|
+
def pass_on(message)
|
126
|
+
DefaultLogger.debug("MessageCommand#pass_on(message)") if options[:env] == "development"
|
127
|
+
write_to_pipe(message)
|
62
128
|
end
|
63
129
|
|
64
|
-
|
65
|
-
#
|
66
|
-
|
130
|
+
##
|
131
|
+
# @abstract Override in the class the MessageCommand is included into. Provides a
|
132
|
+
# chance to initialize any code that needs to take place once the EventMachine loop
|
133
|
+
# has started.
|
134
|
+
#
|
135
|
+
def initialize_machine
|
136
|
+
DefaultLogger.debug("MessageCommand#initialize_machine") if options[:env] == "development"
|
67
137
|
# Implemented in class that includes me
|
68
138
|
end
|
69
139
|
|
70
|
-
|
71
|
-
|
140
|
+
##
|
141
|
+
# @abstract Can be overriden in the class the MessageCommand is included into. Called
|
142
|
+
# when the acknowledgement named pipe has been established.
|
143
|
+
#
|
144
|
+
def report_back
|
145
|
+
DefaultLogger.debug("MessageCommand#report_back") if options[:env] == "development"
|
72
146
|
# Implemented in class that includes me
|
73
147
|
end
|
74
148
|
|
75
|
-
|
76
|
-
#
|
77
|
-
#
|
78
|
-
#
|
79
|
-
|
149
|
+
##
|
150
|
+
# @abstract In your machine implementation you need to override the {#process_standard} method.
|
151
|
+
# Processes a message. This method must call {#pass_on} if the message was handled successfully,
|
152
|
+
# or if the handling of the message fails for whatever reason, you need to decide how you are going
|
153
|
+
# to handle the failure of the message (write message to error log | queue | etc.) and then
|
154
|
+
# acknowledge the message by calling {acknowledge}. Acknowledging the message mean that the
|
155
|
+
# message has been dealt with. All messages must be acknowledged by at least one worker in a pipeline.
|
156
|
+
#
|
157
|
+
def process_standard(message)
|
158
|
+
DefaultLogger.debug("MessageCommand#process_standard(message)") if options[:env] == "development"
|
80
159
|
# Implemented in class that includes me
|
81
|
-
|
160
|
+
pass_on(message)
|
82
161
|
end
|
83
162
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
163
|
+
##
|
164
|
+
# Process the first system message to notify machines down the pipeline which named pipe to
|
165
|
+
# use for acknowledgements.
|
166
|
+
#
|
167
|
+
# @param message<Hash> The system message
|
168
|
+
#
|
169
|
+
def process_system(message)
|
170
|
+
DefaultLogger.debug("MessageCommand#process_system(message)") if options[:env] == "development"
|
171
|
+
options[:ack] = message[:ack] # inherit setting from upstream
|
172
|
+
@ack_pipe = File.open(message[:sys_pipe], "w") # open ack pipe (needs to already exist)
|
173
|
+
report_back
|
174
|
+
pass_on(message)
|
90
175
|
end
|
91
176
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
def initialize(msg_command)
|
96
|
-
@msg_command = msg_command
|
97
|
-
end
|
98
|
-
|
99
|
-
def notify_readable
|
100
|
-
@msg_command.process_line(@io.gets)
|
101
|
-
end
|
177
|
+
def write_to_pipe(message, pipe=$stdout)
|
178
|
+
pipe.syswrite(MessageCoder.encode(message) << "\n")
|
102
179
|
end
|
103
180
|
|
104
181
|
end
|