pipeline_toolkit 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
+