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,53 +1,56 @@
1
1
  require 'pipeline_toolkit/amqp/abstract'
2
2
  require 'pipeline_toolkit/message_coder'
3
3
 
4
- module Amqp # :nodoc:
4
+ module PipelineToolkit
5
+ module Amqp # :nodoc:
5
6
 
6
- ##
7
- # The Writer provides functionality specific to asynchronous message
8
- # publishing to the AMQP server.
9
- #
10
- module Writer
11
- include Amqp::Abstract
12
-
13
7
  ##
14
- # Initialize the AMQP server specific to handling
15
- # of messages publishing to the server.
8
+ # The Writer provides functionality specific to asynchronous message
9
+ # publishing to the AMQP server.
16
10
  #
17
- def initialize_writer
18
- DefaultLogger.debug("Amqp::Writer#initialize_writer") if options[:env] == "development"
19
- initialize_connection
20
- initialize_channel
21
- initialize_exchange
22
- end
11
+ module Writer
12
+ include Amqp::Abstract
13
+
14
+ ##
15
+ # Initialize the AMQP server specific to handling
16
+ # of messages publishing to the server.
17
+ #
18
+ def initialize_writer
19
+ DefaultLogger.debug("Amqp::Writer#initialize_writer") if options[:env] == "development"
20
+ initialize_connection
21
+ initialize_channel
22
+ initialize_exchange
23
+ end
23
24
 
24
- ##
25
- # Initializes the queues to publish the messages too.
26
- #
27
- def initialize_queues
28
- DefaultLogger.debug("Amqp::Writer#initialize_queues") if options[:env] == "development"
29
- options[:queues].each do |name, routing_key|
30
- initialize_queue(name)
31
- bind_queue(routing_key)
25
+ ##
26
+ # Initializes the queues to publish the messages too.
27
+ #
28
+ def initialize_queues
29
+ DefaultLogger.debug("Amqp::Writer#initialize_queues") if options[:env] == "development"
30
+ options[:queues].each do |name, routing_key|
31
+ initialize_queue(name)
32
+ bind_queue(routing_key)
33
+ end
32
34
  end
33
- end
34
35
 
35
- ##
36
- # Handles the publishing of messages into the AMQP server.
37
- #
38
- # @param message<Hash> The message to publish
39
- #
40
- def publish(message)
41
- DefaultLogger.debug("Amqp::Writer#publish(message)") if options[:env] == "development"
42
- if message.has_key?(:routing_key)
43
- @exchange.publish(MessageCoder.encode(message), :key => message[:routing_key])
44
- else
45
- @exchange.publish(MessageCoder.encode(message))
36
+ ##
37
+ # Handles the publishing of messages into the AMQP server.
38
+ #
39
+ # @param message<Hash> The message to publish
40
+ #
41
+ def publish(message)
42
+ DefaultLogger.debug("Amqp::Writer#publish(message)") if options[:env] == "development"
43
+ if message.has_key?(:routing_key)
44
+ @exchange.publish(MessageCoder.encode(message), :key => message[:routing_key])
45
+ else
46
+ @exchange.publish(MessageCoder.encode(message))
47
+ end
46
48
  end
47
- end
48
49
 
49
- def queue_names
50
- options[:queues].map { |name, type| name }
50
+ def queue_names
51
+ options[:queues].map { |name, type| name }
52
+ end
53
+
51
54
  end
52
55
 
53
56
  end
@@ -1,4 +1,3 @@
1
- require 'pp'
2
1
  require 'trollop'
3
2
  require 'eventmachine'
4
3
  require 'pipeline_toolkit'
@@ -14,10 +13,11 @@ Signal.trap('TERM') do
14
13
  exit(0)
15
14
  # TODO: complete signal trap terminate
16
15
  end
17
-
18
- module Commands # :nodoc:
19
- module MsgGenerate
20
- class CLI
16
+
17
+ module PipelineToolkit # :nodoc:
18
+ module CLI
19
+
20
+ class MsgGeneratorCLI
21
21
 
22
22
  def self.execute(stdout, arguments=[])
23
23
 
@@ -44,5 +44,6 @@ Options:
44
44
  MessageGenerator.new(opts).start
45
45
  end
46
46
  end
47
+
47
48
  end
48
49
  end
@@ -1,10 +1,10 @@
1
- require 'pp'
2
1
  require 'trollop'
3
2
  require 'eventmachine'
3
+ require 'pipeline_toolkit'
4
4
 
5
- module Commands # :nodoc:
6
- module MsgPush
7
- class CLI
5
+ module PipelineToolkit # :nodoc:
6
+ module CLI
7
+ class MsgPushCLI
8
8
  def self.execute(stdout, arguments=[])
9
9
 
10
10
  opts = Trollop::options do
@@ -25,22 +25,30 @@ Examples:
25
25
  Options:
26
26
  EOL
27
27
 
28
- # Msg exchange
28
+ banner <<-EOL
29
+
30
+ Queue/exchange:
31
+ EOL
29
32
  opt :exchange, "Exchange name", :type => :string, :required => true, :short => :x
30
33
  opt :type, "Exchange type (direct, fanout, topic)", :default => "fanout", :short => :t
31
34
  opt :passive, "If true, the exchange will not be created if it does not already exist.", :default => false, :short => :s
32
35
  opt :durable, "If true, the exchange will be marked as durable.", :default => false, :short => :d
33
-
34
- # Msg queues
35
36
  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
 
37
- # Msg server
38
+ banner <<-EOL
39
+
40
+ AMQP server:
41
+ EOL
38
42
  opt :host, "AMQP message server host", :default => "localhost", :short => :h
39
43
  opt :port, "AMQP message server port", :default => 5672, :short => :p
40
44
  opt :user, "AMQP message server username", :default => "guest", :short => :u
41
45
  opt :pass, "AMQP message server password", :default => "guest", :short => :w
42
46
  opt :vhost, "AMQP message server vhost", :default => "/", :short => :v
43
47
 
48
+ banner <<-EOL
49
+
50
+ Misc:
51
+ EOL
44
52
  opt :env, "Environment (development, production)", :default => "development", :short => :e
45
53
  end
46
54
 
@@ -0,0 +1,28 @@
1
+ require 'trollop'
2
+ require 'pipeline_toolkit'
3
+
4
+ module PipelineToolkit # :nodoc:
5
+ module CLI
6
+
7
+ class MsgSinkCLI
8
+ def self.execute(stdout, arguments=[])
9
+
10
+ opts = Trollop::options do
11
+ banner <<-EOL
12
+ Message Sink
13
+ ------------
14
+ Swallows messages but still acknowledges them (like a /dev/null, but with acks)
15
+
16
+ Usage:
17
+ msg_sink
18
+ EOL
19
+
20
+ opt :env, "The environment to run (development, production)", :default => "development", :short => :e
21
+ end
22
+
23
+ MessageSink.new(opts).start
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -1,10 +1,11 @@
1
1
  require 'trollop'
2
2
  require 'eventmachine'
3
+ require 'pipeline_toolkit'
3
4
 
4
- module Commands # :nodoc:
5
- module MsgSubscribe # :nodoc:
5
+ module PipelineToolkit # :nodoc:
6
+ module CLI # :nodoc:
6
7
 
7
- class CLI
8
+ class MsgSubscribeCLI
8
9
  def self.execute(stdout, arguments=[])
9
10
 
10
11
  opts = Trollop::options do
@@ -23,31 +24,39 @@ Examples:
23
24
  --host www.domain.com --port 7000
24
25
 
25
26
  Options:
26
- EOL
27
+ EOL
27
28
 
28
- # Msg exchange
29
+ banner <<-EOL
30
+
31
+ Queue/exchange:
32
+ EOL
29
33
  opt :exchange, "The exchange name", :type => :string, :required => true, :short => :x
30
34
  opt :type, "The exchange type (direct, fanout or topic)", :default => "fanout", :short => :t
31
35
  opt :passive, "If set to true, the server will not create the exchange if it does not already exist.", :default => false, :short => :s
32
36
  opt :durable, "If set to true, the exchange will be marked as durable.", :default => false, :short => :d
33
-
34
- # Messages
35
37
  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
38
+ opt :no_acks, "If flag is set then the server does not use acknowledgments for messages.", :type => :flag
39
+
40
+ banner <<-EOL
37
41
 
38
- # Msg server
42
+ AMQP server:
43
+ EOL
39
44
  opt :host, "The AMQP message server host", :default => "localhost", :short => :h
40
45
  opt :port, "The AMQP message server port", :default => 5672, :short => :p
41
46
  opt :user, "The AMQP message server username", :default => "guest", :short => :u
42
47
  opt :pass, "The AMQP message server username", :default => "guest", :short => :w
43
48
  opt :vhost, "The AMQP message server vhost", :default => "/", :short => :v
44
49
 
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
+ banner <<-EOL
51
+
52
+ Monitoring:
53
+ EOL
54
+ opt :http_port, "The port the HTTP monitoring interface runs on. A value must be given to enable the monitoring interface - by default the monitoring interface isn't enabled. An HTML or JSON response can be obtained by suffixing either .html or .json to the request", :type => :integer, :short => :o
50
55
 
56
+ banner <<-EOL
57
+
58
+ Misc:
59
+ EOL
51
60
  opt :env, "The environment to run (development, production)", :default => "development", :short => :e
52
61
  end
53
62
 
@@ -1,6 +1,8 @@
1
-
1
+ require 'pipeline_toolkit/open_hash'
2
2
  require 'pipeline_toolkit/cucumber/amqp'
3
- require 'pipeline_toolkit/cucumber/amqp_steps'
3
+ require 'pipeline_toolkit/cucumber/in_out_process'
4
4
  require 'pipeline_toolkit/cucumber/machine'
5
+ require 'pipeline_toolkit/cucumber/msg_sub_process'
6
+
5
7
  require 'pipeline_toolkit/cucumber/machine_steps'
6
- require 'pipeline_toolkit/cucumber/msg_sub_machine'
8
+ require 'pipeline_toolkit/cucumber/amqp_steps'
@@ -19,7 +19,7 @@ module PipelineToolkit
19
19
  exchange = @mq.exchange(exchange, :type => :fanout)
20
20
  messages.each do |message|
21
21
  message.default = nil
22
- exchange.publish(MessageCoder.encode(message))
22
+ exchange.publish(MessageCoder.encode(message))
23
23
  end
24
24
  end
25
25
 
@@ -0,0 +1,50 @@
1
+ require 'pipeline_toolkit'
2
+ require 'background_process'
3
+
4
+ module PipelineToolkit
5
+ module Cucumber
6
+
7
+ # Provides an interface for running and interacting with a stdin/stdout process which
8
+ # talks messages.
9
+ class InOutProcess
10
+
11
+ def run(command)
12
+ @process = BackgroundProcess.run("ruby -rubygems bin/#{command}")
13
+ # wait for process to start
14
+ sleep(0.5)
15
+ raise @process.stderr.gets unless @process.running?
16
+ end
17
+
18
+ def kill
19
+ @process.kill
20
+ @process.wait
21
+ end
22
+
23
+ def get_messages(number, timeout = 5)
24
+ output_msgs = []
25
+ get_lines(@process.stdout, number, timeout) do |line|
26
+ msg = MessageCoder.decode(line)
27
+ output_msgs << msg
28
+ end
29
+ output_msgs
30
+ end
31
+
32
+ private
33
+
34
+ def get_lines(io, number, timeout = 5)
35
+ count = 0
36
+ Timeout::timeout(timeout) do
37
+ while (count < number)
38
+ line = io.gets
39
+ raise "The machine quit before I received all the messages!!" if line.nil?
40
+ yield line if block_given?
41
+ count += 1
42
+ end
43
+ end
44
+ rescue Timeout::Error
45
+ raise "Timed out waiting for messages. Received #{count} of #{number}"
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -4,53 +4,71 @@ require 'background_process'
4
4
  module PipelineToolkit
5
5
  module Cucumber
6
6
 
7
- # Provides an interface for running and interacting with a machine process.
8
- class Machine
9
-
10
- def run(command)
11
- @process = BackgroundProcess.run("ruby -rubygems bin/#{command}")
12
- # wait for process to start, and check if running
13
- sleep(0.5)
14
- raise @process.stderr.gets unless @process.running?
7
+ # Provides an interface for running and interacting with a pipeline_toolkit machine.
8
+ class Machine < InOutProcess
9
+
10
+ attr_reader :use_acknowledgements
11
+
12
+ def run(command, use_acknowledgements = true)
13
+ super(command)
14
+
15
+ @use_acknowledgements = use_acknowledgements
16
+ setup_system
15
17
  end
16
18
 
17
19
  def kill
18
- @process.kill
19
- @process.wait
20
+ destroy_sys_pipe
21
+ super
20
22
  end
21
23
 
22
24
  def input_messages(messages)
23
25
  messages.each do |message|
24
- # NB: required otherwise marshal complains about the Hash's default proc
25
- message.default = nil
26
+ store_acknowledgement(message) if use_acknowledgements
26
27
  @process.stdin.puts(MessageCoder.encode(message))
27
28
  @process.stdin.flush
28
29
  end
29
30
  end
30
-
31
- def get_messages(number, timeout = 5)
31
+
32
+ def get_acknowledged_messages(number, timeout = 5)
32
33
  output_msgs = []
33
- output_lines(number, timeout) do |line|
34
+ get_lines(@sys_pipe, number, timeout) do |line|
34
35
  msg = MessageCoder.decode(line)
35
36
  output_msgs << msg
36
37
  end
37
38
  output_msgs
39
+ @messages_to_ack.values_at( *output_msgs.map { |msg| msg[:ack_id] } )
38
40
  end
39
41
 
40
42
  private
41
43
 
42
- def output_lines(number, timeout = 5)
43
- count = 0
44
- Timeout::timeout(timeout) do
45
- while (count < number)
46
- line = @process.stdout.gets
47
- yield line
48
- count += 1
49
- end
50
- end
51
- rescue Timeout::Error
52
- raise "Timed out waiting for messages. Received #{count} of #{number}"
44
+ def setup_system
45
+ create_sys_pipe
46
+ message = { :msg_type => "system",
47
+ :sys_pipe => @sys_pipe.path,
48
+ :ack => use_acknowledgements }
49
+
50
+ # send sys_message
51
+ input_messages([message])
52
+
53
+ # get pipe_desc off sys_pipe
54
+ get_lines(@sys_pipe, 1, 5)
53
55
  end
56
+
57
+ def destroy_sys_pipe
58
+ `rm #{@sys_pipe.path}`
59
+ end
60
+
61
+ def create_sys_pipe
62
+ name = File.join("/tmp", "test_sys_pipe_#{Time.now.to_i}_#{rand(9999)}")
63
+ `mkfifo #{name}`
64
+ @sys_pipe = File.new(name, "r+")
65
+ end
66
+
67
+ def store_acknowledgement(message)
68
+ @messages_to_ack ||= {}
69
+ message[:ack_id] = "#{Time.now.to_i}_#{rand(9999)}"
70
+ @messages_to_ack[message[:ack_id]] = message
71
+ end
54
72
 
55
73
  end
56
74
  end
@@ -1,23 +1,29 @@
1
- # ---------
2
- # Machine lifecycle
3
1
 
4
2
  Given /^I run this machine:$/ do |command|
5
- @machine = PipelineToolkit::Cucumber::Machine.new
6
- @machine.run(command)
3
+ @process = PipelineToolkit::Cucumber::Machine.new
4
+ @process.run(command)
7
5
  end
8
6
 
9
7
  # Clean up backround processes after each scenario
10
8
  After do
11
- @machine.kill unless @machine.nil?
9
+ @process.kill unless @process.nil?
12
10
  end
13
11
 
14
12
  When /^I input these messages:$/ do |messages|
15
- @machine.input_messages(messages.hashes)
13
+ @process.input_messages(messages.hashes)
16
14
  end
17
15
 
18
- Then /^these messages are output:$/ do |expected_msgs|
19
- received_messages = @machine.get_messages(expected_msgs.rows.size)
16
+ Then /^these messages are passed on:$/ do |expected_msgs|
17
+ received_messages = @process.get_messages(expected_msgs.rows.size)
20
18
  # turn everything into str
21
19
  received_messages.map! { |h| h.each { |k,v| h[k] = v.to_s }}
22
20
  expected_msgs.diff!(received_messages)
23
- end
21
+ end
22
+
23
+ Then /^these messages are acknowledged:$/ do |expected_msgs|
24
+ received_messages = @process.get_acknowledged_messages(expected_msgs.rows.size)
25
+ # turn everything into str
26
+ received_messages.map! { |h| h.each { |k,v| h[k] = v.to_s }}
27
+ expected_msgs.diff!(received_messages)
28
+ end
29
+