pipeline_toolkit 1.1.0 → 1.2.0

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 (34) hide show
  1. data/LICENSE.markdown +1 -1
  2. data/README.markdown +38 -19
  3. data/bin/msg_generator +3 -4
  4. data/bin/msg_push +3 -4
  5. data/bin/msg_sink +3 -7
  6. data/bin/msg_subscribe +3 -4
  7. data/lib/pipeline_toolkit.rb +5 -5
  8. data/lib/pipeline_toolkit/amqp/abstract.rb +81 -78
  9. data/lib/pipeline_toolkit/amqp/reader.rb +55 -52
  10. data/lib/pipeline_toolkit/amqp/writer.rb +42 -39
  11. data/lib/pipeline_toolkit/{commands/msg_generator/cli.rb → cli/msg_generator_cli.rb} +6 -5
  12. data/lib/pipeline_toolkit/{commands/msg_push/cli.rb → cli/msg_push_cli.rb} +16 -8
  13. data/lib/pipeline_toolkit/cli/msg_sink_cli.rb +28 -0
  14. data/lib/pipeline_toolkit/{commands/msg_subscribe/cli.rb → cli/msg_subscribe_cli.rb} +23 -14
  15. data/lib/pipeline_toolkit/cucumber.rb +5 -3
  16. data/lib/pipeline_toolkit/cucumber/amqp.rb +1 -1
  17. data/lib/pipeline_toolkit/cucumber/in_out_process.rb +50 -0
  18. data/lib/pipeline_toolkit/cucumber/machine.rb +44 -26
  19. data/lib/pipeline_toolkit/cucumber/machine_steps.rb +15 -9
  20. data/lib/pipeline_toolkit/cucumber/{msg_sub_machine.rb → msg_sub_process.rb} +5 -5
  21. data/lib/pipeline_toolkit/handlers/message_handler.rb +43 -39
  22. data/lib/pipeline_toolkit/message_coder.rb +38 -25
  23. data/lib/pipeline_toolkit/message_command.rb +167 -157
  24. data/lib/pipeline_toolkit/message_generator.rb +22 -18
  25. data/lib/pipeline_toolkit/message_pusher.rb +46 -47
  26. data/lib/pipeline_toolkit/message_sink.rb +9 -5
  27. data/lib/pipeline_toolkit/message_subscriber.rb +190 -183
  28. data/lib/pipeline_toolkit/monitoring/monitor_server.rb +64 -109
  29. data/lib/pipeline_toolkit/util/hash_ext.rb +8 -0
  30. data/lib/pipeline_toolkit/util/indifferent_hash.rb +10 -0
  31. data/lib/pipeline_toolkit/{socket_util.rb → util/socket_util.rb} +1 -1
  32. metadata +25 -115
  33. data/lib/pipeline_toolkit/commands/msg_sink/cli.rb +0 -41
  34. data/lib/pipeline_toolkit/open_hash.rb +0 -24
@@ -1,27 +1,31 @@
1
1
  require 'json'
2
2
 
3
- class MessageGenerator
3
+ module PipelineToolkit
4
4
 
5
- def initialize(options)
6
- @delay = options.delay
7
- @msg = JSON.parse(options.msg)
8
- @limit = options.limit
9
- end
5
+ class MessageGenerator
6
+
7
+ def initialize(options)
8
+ @delay = options.delay
9
+ @msg = JSON.parse(options.msg)
10
+ @limit = options.limit
11
+ end
10
12
 
11
- def start
12
- @count = 0
13
- loop do
14
- @count += 1
15
- break if @limit && @count > @limit
13
+ def start
14
+ @count = 0
15
+ loop do
16
+ @count += 1
17
+ break if @limit && @count > @limit
16
18
 
17
- self.send_msg(@msg)
18
- sleep(@delay) unless @delay.nil?
19
+ self.send_msg(@msg)
20
+ sleep(@delay) unless @delay.nil?
21
+ end
19
22
  end
20
- end
21
23
 
22
- def send_msg(msg)
23
- puts MessageCoder.encode(@msg)
24
- $stdout.flush
25
- end
24
+ def send_msg(msg)
25
+ puts MessageCoder.encode(@msg)
26
+ $stdout.flush
27
+ end
26
28
 
29
+ end
30
+
27
31
  end
@@ -1,58 +1,57 @@
1
1
  require "mq"
2
2
 
3
- ##
4
- # A Message Queue machine to handle messages that has to be published
5
- # back into a message queue.
6
- #
7
- class MessagePusher
8
- include MessageCommand
9
- include Amqp::Writer
3
+ module PipelineToolkit
10
4
 
11
5
  ##
12
- # Initializes a new intance
6
+ # A Message Queue machine to handle messages that has to be published
7
+ # back into a message queue.
13
8
  #
14
- # @param options<Hash> Options hash for the message pusher.
15
- #
16
- def initialize(options = {})
17
- super(options)
18
- DefaultLogger.debug("MessagePusher#initialize(options = {})") if options[:env] == "development"
19
-
20
- queues_string = options[:queues].map { |name, type| "#{name} (key:#{type})" }.join(",")
21
- DefaultLogger.info "================================================================"
22
- DefaultLogger.info "Booting #{self.class.name} (#{options[:env]})"
23
- DefaultLogger.info "Exchange: #{options[:exchange]} (#{options[:type]} passive:#{options[:passive]} durable:#{options[:durable]})"
24
- DefaultLogger.info "Queues: #{queues_string}"
25
- DefaultLogger.info "amqp://#{options[:user]}:#{options[:pass]}@#{options[:host]}:#{options[:port]}#{options[:vhost]}"
26
- DefaultLogger.info ""
27
- end
9
+ class MessagePusher
10
+ include MessageCommand
11
+ include Amqp::Writer
28
12
 
29
- ##
30
- # Initialize the AMQP server connection and exchange so we can write messages to the queue.
31
- #
32
- def initialize_machine
33
- DefaultLogger.debug("MessagePusher#initialize_machine") if options[:env] == "development"
34
- initialize_writer
35
- initialize_queues
36
- end
13
+ ##
14
+ # Initializes a new intance
15
+ #
16
+ # @param options<Hash> Options hash for the message pusher.
17
+ #
18
+ def initialize(options = {})
19
+ super(options)
20
+ DefaultLogger.debug("MessagePusher#initialize(options = {})") if options[:env] == "development"
37
21
 
38
- ##
39
- # Notify back down the pipeline information about this machine.
40
- #
41
- def report_back
42
- DefaultLogger.debug("MessagePusher#open_acknowledgement_pipe") if options[:env] == "development"
43
- message = { :msg_type => :pipe_desc, :queues => queue_names.join(",") }
44
- write_to_pipe(message, @ack_pipe)
45
- end
22
+ queues_string = options[:queues].map { |name, type| "#{name} (key:#{type})" }.join(",")
23
+ DefaultLogger.info "================================================================"
24
+ DefaultLogger.info "Booting #{self.class.name} (#{options[:env]})"
25
+ DefaultLogger.info "Exchange: #{options[:exchange]} (#{options[:type]} passive:#{options[:passive]} durable:#{options[:durable]})"
26
+ DefaultLogger.info "Queues: #{queues_string}"
27
+ DefaultLogger.info "amqp://#{options[:user]}:#{options[:pass]}@#{options[:host]}:#{options[:port]}#{options[:vhost]}"
28
+ DefaultLogger.info ""
29
+ end
30
+
31
+ ##
32
+ # Initialize the AMQP server connection and exchange so we can write messages to the queue.
33
+ #
34
+ def initialize_machine
35
+ DefaultLogger.debug("MessagePusher#initialize_machine") if options[:env] == "development"
36
+ initialize_writer
37
+ initialize_queues
38
+ end
39
+
40
+ def description
41
+ "exchange:#{options[:exchange]}"
42
+ end
43
+
44
+ ##
45
+ # Handle the messages by writing them into the AMQP server exchange.
46
+ #
47
+ # @param message<Hash> The message to write to the exchange.
48
+ #
49
+ def process_standard(message)
50
+ DefaultLogger.debug("MessagePusher#process_standard(message)") if options[:env] == "development"
51
+ publish(message)
52
+ acknowledge(message) if options[:ack]
53
+ end
46
54
 
47
- ##
48
- # Handle the messages by writing them into the AMQP server exchange.
49
- #
50
- # @param message<Hash> The message to write to the exchange.
51
- #
52
- def process_standard(message)
53
- DefaultLogger.debug("MessagePusher#process_standard(message)") if options[:env] == "development"
54
- publish(message)
55
- acknowledge(message) if options[:ack]
56
55
  end
57
56
 
58
57
  end
@@ -1,8 +1,12 @@
1
- class MessageSink
2
- include MessageCommand
1
+ module PipelineToolkit
2
+
3
+ class MessageSink
4
+ include MessageCommand
3
5
 
4
- def process_standard(msg)
5
- :ack
6
- end
6
+ def process_standard(message)
7
+ acknowledge(message)
8
+ end
7
9
 
10
+ end
11
+
8
12
  end
@@ -3,228 +3,235 @@ require "mq"
3
3
  require "time"
4
4
  require "socket"
5
5
 
6
- ##
7
- # The message subscriber is used to subscribe to a AMQP server queue
8
- # and handle a single message at a time.
9
- #
10
- class MessageSubscriber
6
+ module PipelineToolkit
7
+ ##
8
+ # The message subscriber is used to subscribe to a AMQP server queue
9
+ # and handle a single message at a time.
10
+ #
11
+ class MessageSubscriber
11
12
 
12
- include Amqp::Reader
13
+ include Amqp::Reader
13
14
 
14
- attr_reader :start_time
15
- attr_reader :mps
16
- attr_reader :options
17
- attr_accessor :queues_out
15
+ attr_reader :start_time
16
+ attr_reader :mps
17
+ attr_reader :options
18
+ attr_accessor :structure
18
19
 
19
- PIPE_PATH = "/tmp"
20
+ PIPE_PATH = "/tmp"
20
21
 
21
- ##
22
- # Initializes a new instance
23
- #
24
- # @param options<Hash> An options hash, see command line interface.
25
- #
26
- def initialize(options = {})
27
-
28
- DefaultLogger.init_logger(options)
29
-
30
- @options = options
22
+ ##
23
+ # Initializes a new instance
24
+ #
25
+ # @param options<Hash> An options hash, see command line interface.
26
+ #
27
+ def initialize(options = {})
31
28
 
32
- @options[:http_port] ||= Socket.select_random_port(10_000, 11_000)
33
- @options[:name] ||= (Socket.gethostname + '_' + Process.pid.to_s)
34
-
35
- DefaultLogger.info "================================================================"
36
- DefaultLogger.info "Booting #{self.class.name} (#{options[:env]})"
37
- DefaultLogger.info "Exchange: #{options[:exchange]} (#{options[:type]} passive:#{options[:passive]} durable:#{options[:durable]})"
38
- DefaultLogger.info "Queue: #{options[:queue]} (ack:#{options[:ack]})"
39
- DefaultLogger.info "amqp://#{options[:user]}:#{options[:pass]}@#{options[:host]}:#{options[:port]}#{options[:vhost]}"
40
- DefaultLogger.info "Monitoring: http://localhost:#{options[:http_port]}/ (#{options[:content_type]} name:#{options[:name]} DNS-SD:#{options[:dnssd]})"
41
- DefaultLogger.info ""
42
-
43
- @queues_out = ''
29
+ DefaultLogger.init_logger(options)
44
30
 
45
- initialize_dnssd
31
+ @options = options
32
+ options[:ack] = !options[:no_acks]
46
33
 
47
- reset_message_statistics
48
- end
34
+ DefaultLogger.info "================================================================"
35
+ DefaultLogger.info "Booting #{self.class.name} (#{options[:env]})"
36
+ DefaultLogger.info "Exchange: #{options[:exchange]} (#{options[:type]} passive:#{options[:passive]} durable:#{options[:durable]})"
37
+ DefaultLogger.info "Queue: #{options[:queue]} (ack:#{options[:ack]})"
38
+ DefaultLogger.info "amqp://#{options[:user]}:#{options[:pass]}@#{options[:host]}:#{options[:port]}#{options[:vhost]}"
39
+ DefaultLogger.info "Monitoring: http://localhost:#{options[:http_port]}/ (#{options[:content_type]})"
40
+ DefaultLogger.info ""
41
+
42
+ @structure = [description]
43
+
44
+ reset_message_statistics
45
+ end
49
46
 
50
- ##
51
- # Starts the subscriber reactor loop
52
- #
53
- def start
47
+ ##
48
+ # Starts the subscriber reactor loop
49
+ #
50
+ def start
54
51
 
55
- Signal.trap('INT') { EM.stop }
56
- Signal.trap('TERM') { EM.stop }
52
+ Signal.trap('INT') { EM.stop }
53
+ Signal.trap('TERM') { EM.stop }
57
54
 
58
- DefaultLogger.debug("MessageSubscriber#start")
59
- begin
60
- create_ack_pipe
55
+ DefaultLogger.debug("MessageSubscriber#start")
56
+ begin
57
+ create_sys_pipe
61
58
 
62
- EM.run do
63
- @start_time = Time.now
59
+ EM.run do
60
+ @start_time = Time.now
64
61
 
65
- initialize_reader
66
- queue_subscribe
62
+ initialize_reader
63
+ queue_subscribe
67
64
 
68
- conn = EM.watch(@ack_pipe, Handlers::MessageHandler, self, options)
69
- # must call this to setup callback to notify_readable
70
- conn.notify_readable = true
71
-
72
- EM.start_server('0.0.0.0', options[:http_port], Monitoring::MonitorServer, self, options)
73
- EM.add_periodic_timer(5) { calculate_message_statistics }
65
+ conn = EM.watch(@sys_pipe, Handlers::MessageHandler, self, options)
66
+ # must call this to setup callback to notify_readable
67
+ conn.notify_readable = true
68
+
69
+ if monitoring_enabled?
70
+ EM.start_server('0.0.0.0', options[:http_port], Monitoring::MonitorServer, self, options)
71
+ EM.add_periodic_timer(5) { calculate_message_statistics }
72
+ end
73
+ end
74
+ rescue StandardError => e
75
+ DefaultLogger.error "#{e.class.name}: #{e.message}\n" << e.backtrace.join("\n")
76
+ raise e
77
+ ensure
78
+ shutdown
74
79
  end
75
- rescue StandardError => e
76
- DefaultLogger.error "#{e.class.name}: #{e.message}\n" << e.backtrace.join("\n")
77
- raise e
78
- ensure
79
- shutdown
80
80
  end
81
- end
82
81
 
83
- ##
84
- # Stop the subscriber
85
- #
86
- def shutdown
87
- DefaultLogger.info("Shutting down #{self.class.name}")
88
- DefaultLogger.info "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"
89
- destroy_ack_pipe
90
- end
91
-
92
- ##
93
- # Callback for the Handlers::MessageHandler when it receives a message
94
- #
95
- # @param message<Hash> The decoded message
96
- #
97
- def process(message)
98
- case message[:msg_type]
99
- when :ack
100
- perform_acknowledgement(message[:ack_id])
101
- when :pipe_desc
102
- DefaultLogger.debug("AcknowledgementHandler#queues_out")
103
- @queues_out = message[:queues]
104
- else
105
- raise Exception.new("Unknown control message received: #{message}")
82
+ ##
83
+ # Stop the subscriber
84
+ #
85
+ def shutdown
86
+ DefaultLogger.info("Shutting down #{self.class.name}")
87
+ DefaultLogger.info "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"
88
+ destroy_sys_pipe
106
89
  end
107
- end
108
-
109
- ##
110
- # Support templating of member data.
111
- #
112
- def get_binding
113
- binding
114
- end
115
-
116
- # :nodoc:
117
- def name
118
- options[:name] || ''
119
- end
120
-
121
- # :nodoc:
122
- def queue_name
123
- options[:name] || ''
124
- end
125
-
126
- protected
127
90
 
128
- ##
129
- # Process the message received from AMQP server
130
- #
131
- # @param header<MQ::Header> The header of the message
132
- # @param body Description
133
- #
134
- def process_queue_message(header, body)
135
- DefaultLogger.debug("MessageSubscriber#process_queue_message(header, body)")
136
- body = MessageCoder.decode(body)
137
- unless body.is_a?(Hash)
138
- message = { :msg_type => :standard, :raw => body }
139
- else
140
- message = { :msg_type => :standard }.merge(body)
91
+ def monitoring_enabled?
92
+ !options[:http_port].nil?
141
93
  end
142
- store_acknowledgement(message, header) if options[:ack]
143
- write(message)
144
- end
145
-
146
- private
147
-
94
+
148
95
  ##
149
- # Reset the instance variables used to store message statistics
96
+ # Callback for the Handlers::MessageHandler when it receives a message
150
97
  #
151
- def reset_message_statistics
152
- @count = 0
153
- @prev_time = Time.now
98
+ # @param message<Hash> The decoded message
99
+ #
100
+ def process(message)
101
+ case message[:msg_type]
102
+ when "ack"
103
+ DefaultLogger.debug("Acknowledging message #{message[:ack_id]}")
104
+ perform_acknowledgement(message[:ack_id])
105
+ when "pipe_desc"
106
+ @structure << message[:description].to_s
107
+ else
108
+ raise "Unknown control message received: #{message.inspect}"
109
+ end
154
110
  end
155
-
111
+
156
112
  ##
157
- # Calculate the statistics on message handling
113
+ # Support templating of member data.
158
114
  #
159
- def calculate_message_statistics
160
- @time_delta = Time.now - @prev_time
161
- @mps = @count / @time_delta
162
- reset_message_statistics
115
+ def get_binding
116
+ binding
163
117
  end
164
-
118
+
119
+ # :nodoc:
120
+ def process_id
121
+ Process.pid.to_s
122
+ end
123
+
165
124
  ##
166
- # Create the acknowledgement named pipe
125
+ # The uptime of the subscriber in mins
167
126
  #
168
- def create_ack_pipe
169
- name = File.join(PIPE_PATH, "sys_pipe_#{generate_guid}")
170
- `mkfifo #{name}`
171
- @ack_pipe = File.new(name, "r+")
172
- message = { :msg_type => :system,
173
- :sys_pipe => name,
174
- :ack => options[:ack] }
175
- write_to_pipe(message)
127
+ def uptime
128
+ (Time.now - self.start_time).to_i
176
129
  end
177
130
 
178
131
  ##
179
- # Destroy the acknowledgement named pipe
132
+ # The hostname of the server the subscriber is running on
180
133
  #
181
- def destroy_ack_pipe
182
- `rm #{@ack_pipe.path}`
134
+ def hostname
135
+ Socket.gethostname
183
136
  end
184
137
 
138
+ def description
139
+ "queue:#{options[:queue]}"
140
+ end
141
+
142
+ protected
143
+
185
144
  ##
186
- # Generates a unique guid to use as part of the acknowledgement named pipe name.
187
- # Stolen from EM.
145
+ # Process the message received from AMQP server
188
146
  #
189
- def generate_guid
190
- # Cache uuidgen seed for better performance
191
- if @ix and @ix >= 10_000
192
- @ix = nil
193
- @seed = nil
147
+ # @param header<MQ::Header> The header of the message
148
+ # @param body Description
149
+ #
150
+ def process_queue_message(header, body)
151
+ DefaultLogger.debug("MessageSubscriber#process_queue_message(header, body)")
152
+ body = MessageCoder.decode(body)
153
+ DefaultLogger.debug("--------------- #{body.inspect}")
154
+ unless body.is_a?(Hash)
155
+ message = { :msg_type => 'standard', :raw => body }
156
+ else
157
+ message = { :msg_type => 'standard' }.merge(body)
158
+ end
159
+ store_acknowledgement(message, header) if options[:ack]
160
+ write(message)
161
+ end
162
+
163
+ private
164
+
165
+ ##
166
+ # Reset the instance variables used to store message statistics
167
+ #
168
+ def reset_message_statistics
169
+ @count = 0
170
+ @prev_time = Time.now
171
+ end
172
+
173
+ ##
174
+ # Calculate the statistics on message handling
175
+ #
176
+ def calculate_message_statistics
177
+ @time_delta = Time.now - @prev_time
178
+ @mps = @count / @time_delta
179
+ reset_message_statistics
194
180
  end
195
181
 
196
- # NB. This will only work on *nix platforms
197
- @seed ||= `uuidgen`.chomp.gsub(/-/,"")
198
- @ix ||= 0
182
+ ##
183
+ # Create the acknowledgement named pipe
184
+ #
185
+ def create_sys_pipe
186
+ name = File.join(PIPE_PATH, "sys_pipe_#{generate_guid}")
187
+ `mkfifo #{name}`
188
+ @sys_pipe = File.new(name, "r+")
189
+ message = { :msg_type => "system",
190
+ :sys_pipe => name,
191
+ :ack => options[:ack] }
192
+ write_to_pipe(message)
193
+ end
194
+
195
+ ##
196
+ # Destroy the acknowledgement named pipe
197
+ #
198
+ def destroy_sys_pipe
199
+ `rm #{@sys_pipe.path}`
200
+ end
199
201
 
200
- "#{@seed}#{@ix += 1}"
201
- end
202
+ ##
203
+ # Generates a unique guid to use as part of the acknowledgement named pipe name.
204
+ # Stolen from EM.
205
+ #
206
+ def generate_guid
207
+ # Cache uuidgen seed for better performance
208
+ if @ix and @ix >= 10_000
209
+ @ix = nil
210
+ @seed = nil
211
+ end
212
+
213
+ # NB. This will only work on *nix platforms
214
+ @seed ||= `uuidgen`.chomp.gsub(/-/,"")
215
+ @ix ||= 0
216
+
217
+ "#{@seed}#{@ix += 1}"
218
+ end
202
219
 
203
- ##
204
- # Write the message to STDOUT
205
- #
206
- # @param message<Hash> The message to write
207
- #
208
- def write(message)
209
- DefaultLogger.debug("MessageSubscriber#write(message)")
210
- @count += 1
211
- write_to_pipe(message)
212
- end
220
+ ##
221
+ # Write the message to STDOUT
222
+ #
223
+ # @param message<Hash> The message to write
224
+ #
225
+ def write(message)
226
+ DefaultLogger.debug("MessageSubscriber#write(#{message.inspect})")
227
+ @count += 1
228
+ write_to_pipe(message)
229
+ end
213
230
 
214
- ##
215
- # Initialize the DNS Service Discovery (aka Bonjour, MDNS).
216
- #
217
- def initialize_dnssd
218
- return unless options[:dnssd]
219
- # FIXME. DNS-SD breaks when under load. Not sure what problem is yet, have raised it with
220
- # gem author:
221
- # http://github.com/tenderlove/dnssd/issues#issue/3
222
- require 'dnssd'
223
- DNSSD.register!("#{options[:name]}_pipe", "_http._tcp", nil, options[:http_port])
224
- end
231
+ def write_to_pipe(message, pipe=$stdout)
232
+ pipe.syswrite(MessageCoder.encode(message) << "\n")
233
+ end
225
234
 
226
- def write_to_pipe(message, pipe=$stdout)
227
- pipe.syswrite(MessageCoder.encode(message) << "\n")
228
- end
235
+ end
229
236
 
230
- end
237
+ end