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.
Files changed (41) hide show
  1. data/LICENSE.markdown +20 -0
  2. data/README.markdown +98 -0
  3. data/bin/msg_generator +13 -0
  4. data/bin/msg_probe +13 -0
  5. data/bin/msg_push +13 -0
  6. data/bin/msg_sink +14 -0
  7. data/bin/msg_subscribe +13 -0
  8. data/lib/pipeline_toolkit.rb +17 -0
  9. data/lib/pipeline_toolkit/amqp/abstract.rb +95 -0
  10. data/lib/pipeline_toolkit/amqp/reader.rb +64 -0
  11. data/lib/pipeline_toolkit/amqp/writer.rb +54 -0
  12. data/lib/pipeline_toolkit/commands/msg_generator/cli.rb +45 -0
  13. data/lib/pipeline_toolkit/commands/msg_probe/cli.rb +46 -0
  14. data/lib/pipeline_toolkit/commands/msg_push/cli.rb +58 -0
  15. data/lib/pipeline_toolkit/commands/msg_sink/cli.rb +41 -0
  16. data/lib/pipeline_toolkit/commands/msg_subscribe/cli.rb +58 -0
  17. data/lib/pipeline_toolkit/default_logger.rb +126 -11
  18. data/lib/pipeline_toolkit/handlers/message_handler.rb +38 -0
  19. data/lib/pipeline_toolkit/message_coder.rb +18 -8
  20. data/lib/pipeline_toolkit/message_command.rb +138 -61
  21. data/lib/pipeline_toolkit/message_generator.rb +21 -0
  22. data/lib/pipeline_toolkit/message_probe.rb +6 -6
  23. data/lib/pipeline_toolkit/message_pusher.rb +51 -54
  24. data/lib/pipeline_toolkit/message_sink.rb +1 -1
  25. data/lib/pipeline_toolkit/message_subscriber.rb +182 -201
  26. data/lib/pipeline_toolkit/monitoring/monitor_server.rb +124 -0
  27. data/spec/eventmachine_helper.rb +44 -0
  28. data/spec/message_subscriber_spec.rb +64 -0
  29. data/spec/spec_helper.rb +15 -0
  30. metadata +202 -47
  31. data/.gitignore +0 -5
  32. data/README.rdoc +0 -70
  33. data/Rakefile +0 -40
  34. data/VERSION +0 -1
  35. data/bin/msg_generator.rb +0 -0
  36. data/bin/msg_probe.rb +0 -15
  37. data/bin/msg_push.rb +0 -25
  38. data/bin/msg_sink.rb +0 -11
  39. data/bin/msg_subscribe.rb +0 -27
  40. data/monitor/munin.rb +0 -91
  41. 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
- require "logger"
2
- require 'syslog_logger'
1
+ begin
2
+ require "logger"
3
+ require "fileutils"
4
+ require 'syslog_logger'
5
+ rescue LoadError
6
+ end
3
7
 
4
- # OPTIMIZE. Is there a better way to handle default/common/shared logging?
5
- module DefaultLogger
6
-
7
- # Return the logger for this class
8
- def log
9
- @logger ||= SyslogLogger.new(self.syslog_name)
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
- def syslog_name
13
- "pipeline"
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
- end
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
- # Encodes messages
3
- #
1
+ ##
2
+ # Encode and decode messages using Marshal
3
+ #
4
4
  class MessageCoder
5
-
6
- def self.encode(msg)
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(msg)
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
- attr_reader :sys_pipe
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
- log.info("starting")
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
- @ack_buffer ||= ""
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, ProcessLine, self)
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
- self.init_loop
75
+
76
+ initialize_machine
22
77
  end
23
78
  rescue StandardError => e
24
- log.info e
79
+ DefaultLogger.error("#{e.class.name}: #{e.message}\n" << e.backtrace.join("\n"))
25
80
  raise e
26
81
  ensure
27
- self.shutdown
82
+ shutdown
28
83
  end
29
84
  end
30
85
 
86
+ ##
87
+ # Stops the machine
88
+ #
31
89
  def shutdown
32
- log.info("shutting down")
33
- @sys_pipe && @sys_pipe.close
90
+ DefaultLogger.info("Shutting down #{self.class.name}")
91
+ DefaultLogger.info "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"
92
+ @ack_pipe.close if @ack_pipe
34
93
  end
35
94
 
36
- def process_line(line)
37
- msg = MessageCoder.decode(line)
38
-
39
- case msg[:msg_type]
40
- when :system
41
- result = process_system(msg)
42
- else
43
- result = process_message(msg)
44
- end
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
- self.pass_on_msg(result)
105
+ process_standard(message)
51
106
  end
52
107
  end
53
108
 
54
- def ack_msg(msg)
55
- return unless @use_ack
56
- msg = {:msg_type => :ack, :ack_id => msg.ack_id}
57
- @sys_pipe.syswrite(MessageCoder.encode(msg) << "\n")
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
- def pass_on_msg(msg)
61
- $stdout.syswrite(MessageCoder.encode(msg) << "\n")
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
- # Override in included class. Provides a chance to initialize any
65
- # code that needs to take place once the EM loop has started.
66
- def init_loop
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
- # Can be overriden in included class. Called when the sys_pipe has been established.
71
- def syspipe_open
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
- # Override in included class. Processes a message. This method
76
- # must return either a msg object -- which may or may not have been modified -- or the symbol :ack.
77
- # Returning :ack mean that the message has been dealt with and can be acknowledged back to the queue
78
- # server. All messages must be acknowledged by at least one message_command.
79
- def process_message(msg)
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
- msg
160
+ pass_on(message)
82
161
  end
83
162
 
84
- def process_system(msg)
85
- @sys_pipe = File.open(msg.sys_pipe, "w")
86
- @use_ack = msg.use_ack
87
- @max_unackd = msg.max_unackd
88
- msg
89
- self.syspipe_open
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
- module ProcessLine
93
- include DefaultLogger
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