smith 0.5.12 → 0.5.13.1

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 (46) hide show
  1. data/bin/smithctl +16 -19
  2. data/lib/smith.rb +25 -41
  3. data/lib/smith/agent.rb +96 -66
  4. data/lib/smith/agent_monitoring.rb +3 -4
  5. data/lib/smith/agent_process.rb +16 -9
  6. data/lib/smith/amqp_errors.rb +53 -0
  7. data/lib/smith/application/agency.rb +23 -20
  8. data/lib/smith/bootstrap.rb +3 -3
  9. data/lib/smith/command.rb +2 -2
  10. data/lib/smith/command_base.rb +4 -0
  11. data/lib/smith/commands/agency/agents.rb +19 -19
  12. data/lib/smith/commands/agency/kill.rb +6 -2
  13. data/lib/smith/commands/agency/list.rb +2 -4
  14. data/lib/smith/commands/agency/logger.rb +27 -28
  15. data/lib/smith/commands/agency/metadata.rb +1 -5
  16. data/lib/smith/commands/agency/object_count.rb +13 -11
  17. data/lib/smith/commands/agency/restart.rb +18 -9
  18. data/lib/smith/commands/agency/start.rb +34 -25
  19. data/lib/smith/commands/agency/stop.rb +58 -41
  20. data/lib/smith/commands/agency/version.rb +10 -10
  21. data/lib/smith/commands/common.rb +7 -4
  22. data/lib/smith/commands/smithctl/acl.rb +46 -37
  23. data/lib/smith/commands/smithctl/commands.rb +1 -1
  24. data/lib/smith/commands/smithctl/firehose.rb +30 -0
  25. data/lib/smith/commands/smithctl/pop.rb +39 -32
  26. data/lib/smith/commands/smithctl/push.rb +70 -51
  27. data/lib/smith/commands/smithctl/rm.rb +32 -9
  28. data/lib/smith/commands/smithctl/subscribe.rb +36 -0
  29. data/lib/smith/commands/smithctl/top.rb +1 -1
  30. data/lib/smith/exceptions.rb +2 -0
  31. data/lib/smith/messaging/acl/agency_command.proto +4 -0
  32. data/lib/smith/messaging/acl/default.rb +8 -1
  33. data/lib/smith/messaging/amqp_options.rb +2 -2
  34. data/lib/smith/messaging/message_counter.rb +21 -0
  35. data/lib/smith/messaging/payload.rb +47 -49
  36. data/lib/smith/messaging/queue.rb +50 -0
  37. data/lib/smith/messaging/queue_definition.rb +18 -0
  38. data/lib/smith/messaging/queue_factory.rb +20 -29
  39. data/lib/smith/messaging/receiver.rb +211 -173
  40. data/lib/smith/messaging/requeue.rb +91 -0
  41. data/lib/smith/messaging/sender.rb +184 -28
  42. data/lib/smith/messaging/util.rb +72 -0
  43. data/lib/smith/queue_definitions.rb +11 -0
  44. data/lib/smith/version.rb +1 -1
  45. metadata +18 -10
  46. data/lib/smith/messaging/endpoint.rb +0 -116
@@ -0,0 +1,50 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Smith
3
+ module Messaging
4
+ class Queue
5
+
6
+ extend Util
7
+
8
+ class << self
9
+ def messages?(queue_name, &blk)
10
+ number_of_messages(queue_name) do |n|
11
+ yield n > 0
12
+ end
13
+ end
14
+
15
+ def consumers?(queue_name)
16
+ number_of_consumers(queue_name) do |n|
17
+ yield n > 0
18
+ end
19
+ end
20
+
21
+ def number_of_consumers(queue_name)
22
+ status(queue_name) do |_, number_consumers|
23
+ yield number_consumers
24
+ end
25
+ end
26
+
27
+ def number_of_messages(queue_name)
28
+ status(queue_name) do |number_messages, _|
29
+ yield number_messages
30
+ end
31
+ end
32
+
33
+ def status(queue_name)
34
+ open_channel do |channel, ok|
35
+ channel.queue(normalise(queue_name), :passive => true).status do |number_messages, number_consumers|
36
+ yield number_messages, number_consumers
37
+ channel.close
38
+ end
39
+ end
40
+ end
41
+
42
+ def channel
43
+ open_channel do |channel, ok|
44
+ yield channel
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,18 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module Smith
4
+ class QueueDefinition
5
+
6
+ attr_reader :name, :options
7
+
8
+ def initialize(name, options)
9
+ @name = name
10
+ @options = options
11
+ end
12
+
13
+ # to_a is defined to make the splat operator work.
14
+ def to_a()
15
+ return @name, @options
16
+ end
17
+ end
18
+ end
@@ -6,39 +6,14 @@ module Smith
6
6
  @cache = Cache.new
7
7
  end
8
8
 
9
- def create(queue_name, type, opts={})
10
- key = "#{type}:#{queue_name}"
11
- if @cache[key]
12
- @cache[key]
13
- else
14
- update_cache(key, opts) do |o|
15
- case type
16
- when :receiver
17
- Messaging::Receiver.new(queue_name, o)
18
- when :sender
19
- Messaging::Sender.new(queue_name, o)
20
- else
21
- raise ArgumentError, "unknown queue type"
22
- end
23
- end
24
- end
25
- end
26
-
27
- # Simple wrapper around create that runs Endpoint#ready and calls the block
28
- def queue(queue_name, type, opts={}, &blk)
29
- create(queue_name, type, opts).ready { |queue| blk.call(queue) }
30
- end
31
-
32
- # Convenience method that returns a Sender object. #ready is called by
33
- # this method.
9
+ # Convenience method that returns a Sender object.
34
10
  def sender(queue_name, opts={}, &blk)
35
- queue(queue_name, :sender, opts) { |sender| blk.call(sender) }
11
+ create(queue_name, :sender, opts, &blk)
36
12
  end
37
13
 
38
- # Convenience method that returns a Receiver object. #ready is called by
39
- # this method.
14
+ # Convenience method that returns a Receiver object.
40
15
  def receiver(queue_name, opts={}, &blk)
41
- queue(queue_name, :receiver, opts) { |receiver| blk.call(receiver) }
16
+ create(queue_name, :receiver, opts, &blk)
42
17
  end
43
18
 
44
19
  # Passes each queue to the supplied block.
@@ -55,6 +30,22 @@ module Smith
55
30
 
56
31
  private
57
32
 
33
+ def create(queue_name, type, opts={}, &blk)
34
+ key = "#{type}:#{queue_name}"
35
+ if @cache[key]
36
+ blk.call(@cache[key])
37
+ else
38
+ update_cache(key, opts) do |o|
39
+ case type
40
+ when :receiver
41
+ Messaging::Receiver.new(queue_name, o, &blk)
42
+ when :sender
43
+ Messaging::Sender.new(queue_name, o, &blk)
44
+ end
45
+ end
46
+ end
47
+ end
48
+
58
49
  def update_cache(queue_name, opts, &blk)
59
50
  dont_cache = (opts.has_key?(:dont_cache)) ? opts.delete(:dont_cache) : false
60
51
  if dont_cache
@@ -1,236 +1,274 @@
1
1
  # -*- encoding: utf-8 -*-
2
+
3
+ require_relative 'message_counter'
4
+ require_relative 'requeue'
5
+
2
6
  module Smith
3
7
  module Messaging
4
- class Receiver < Endpoint
8
+ class Receiver
5
9
 
6
10
  include Logger
11
+ include Util
7
12
 
8
- attr_accessor :options
13
+ attr_accessor :queue_name
9
14
 
10
- def initialize(queue_name, opts={})
11
- @auto_ack = (opts.has_key?(:auto_ack)) ? opts.delete(:auto_ack) : true
12
- @threading = (opts.has_key?(:threading)) ? opts.delete(:threading) : false
13
- @payload_type = (opts.key?(:type)) ? [opts.delete(:type)].flatten : []
15
+ def initialize(queue_definition, opts={}, &blk)
14
16
 
15
- super(queue_name, AmqpOptions.new(opts))
16
- end
17
+ @queue_name, opts = get_queue_name_and_options(queue_definition, opts)
17
18
 
18
- # Subscribes to a queue and passes the headers and payload into the
19
- # block. +subscribe+ will automatically acknowledge the message unless
20
- # the options sets :ack to false.
21
- def subscribe(&block)
22
- if !@queue.subscribed?
23
- opts = options.subscribe
24
- logger.verbose { "Subscribing to: [queue]:#{denormalized_queue_name} [options]:#{opts}" }
25
- queue.subscribe(opts) do |metadata,payload|
26
- if payload
27
- if @payload_type.empty? || @payload_type.include?(metadata.type.to_sym)
28
- thread(Reply.new(self, metadata, payload)) do |reply|
29
- increment_counter
30
- block.call(reply)
31
- end
32
- else
33
- raise IncorrectPayloadType, "This queue can only accept the following payload types: #{@payload_type.to_a.to_s}"
34
- end
35
- else
36
- logger.verbose { "Received null message on: #{denormalized_queue_name} [options]:#{opts}" }
37
- end
19
+ @normalised_queue_name = normalise(@queue_name)
20
+
21
+ @foo_options = {
22
+ :auto_ack => option_or_default(opts, :auto_ack, true),
23
+ :threading => option_or_default(opts, :threading, false)}
24
+
25
+ @payload_type = option_or_default(opts, :type, []) {|v| [v].flatten }
26
+
27
+ prefetch = option_or_default(opts, :prefetch, Smith.config.agent.prefetch)
28
+
29
+ @options = AmqpOptions.new(opts)
30
+ @options.routing_key = @normalised_queue_name
31
+
32
+ @message_counter = MessageCounter.new(@queue_name)
33
+
34
+ @channel_completion = EM::Completion.new
35
+ @queue_completion = EM::Completion.new
36
+ @exchange_completion = EM::Completion.new
37
+ @requeue_options_completion = EM::Completion.new
38
+
39
+ @reply_queues = {}
40
+
41
+ open_channel(:prefetch => prefetch) do |channel|
42
+ @channel_completion.succeed(channel)
43
+ channel.direct(@normalised_queue_name, @options.exchange) do |exchange|
44
+ @exchange_completion.succeed(exchange)
38
45
  end
39
- else
40
- logger.error { "Queue is already subscribed too. Not listening on: #{denormalise_queue_name}" }
41
46
  end
42
- end
43
47
 
44
- # pops a message off the queue and passes the headers and payload
45
- # into the block. +pop+ will automatically acknowledge the message
46
- # unless the options sets :ack to false.
47
- def pop(&block)
48
- opts = options.pop
49
- @queue.pop(opts) do |metadata, payload|
50
- thread(Reply.new(self, metadata, (payload.nil?) ? nil : payload)) do |reply|
51
- block.call(reply)
48
+ open_channel(:prefetch => prefetch) do |channel|
49
+ channel.queue(@normalised_queue_name, @options.queue) do |queue|
50
+ @exchange_completion.completion do |exchange|
51
+ queue.bind(exchange, :routing_key => @queue_name)
52
+ @queue_completion.succeed(queue)
53
+ @requeue_options_completion.succeed(:exchange => exchange, :queue => queue)
54
+ end
52
55
  end
53
56
  end
54
- end
55
57
 
56
- def threading?
57
- @threading
58
- end
58
+ start_garbage_collection
59
59
 
60
- def auto_ack?
61
- @auto_ack
60
+ blk.call(self) if blk
62
61
  end
63
62
 
64
- private
65
-
66
- # Controls whether to use threads or not. Given that I need to ack in the
67
- # thread (TODO check this) I also need to pass in a flag to say whether
68
- # to auto ack or not. This is because it can get called twice and we don't
69
- # want to ack more than once or an error will be thrown.
70
- def thread(reply, &block)
71
- logger.verbose { "Threads: [queue]: #{denormalized_queue_name}: #{threading?}" }
72
- logger.verbose { "auto_ack: [queue]: #{denormalized_queue_name}: #{auto_ack?}" }
73
- if threading?
74
- EM.defer do
75
- block.call(reply)
76
- reply.ack if auto_ack?
63
+ def start_garbage_collection
64
+ logger.debug { "Starting the garbage collector." }
65
+ EM.add_periodic_timer(5) do
66
+ @reply_queues.each do |queue_name,queue|
67
+ queue.status do |number_of_messages, number_of_consumers|
68
+ if number_of_messages == 0 && number_of_consumers == 0
69
+ queue.delete do |delete_ok|
70
+ @reply_queues.delete(queue_name)
71
+ logger.debug { "Unused reply queue deleted: #{queue_name}" }
72
+ end
73
+ end
74
+ end
77
75
  end
78
- else
79
- block.call(reply)
80
- reply.ack if auto_ack?
81
76
  end
82
77
  end
83
78
 
84
- # I'm not terribly happy about this class. It's publicly visible and it contains
85
- # some gross violations of Ruby's protection mechanism. I suspect it's an indication
86
- # of a more fundamental design flaw. I will leave it as is for the time being but
87
- # this really needs to be reviewed. FIXME review this class.
88
- class Reply
89
-
90
- include Logger
91
-
92
- attr_reader :metadata, :payload, :time
79
+ def ack(multiple=false)
80
+ @channel_completion.completion {|channel| channel.ack(multiple) }
81
+ end
93
82
 
94
- def initialize(receiver, metadata, undecoded_payload)
95
- @undecoded_payload = undecoded_payload
96
- @receiver = receiver
97
- @exchange = receiver.send(:exchange)
98
- @metadata = metadata
99
- @time = Time.now
83
+ private :start_garbage_collection
100
84
 
101
- if undecoded_payload
102
- @payload = ACL::Payload.decode(undecoded_payload, metadata.type)
103
- logger.verbose { "Received content on: [queue]: #{denormalized_queue_name}." }
104
- logger.verbose { "Payload content: [queue]: #{denormalized_queue_name}, [metadata type]: #{metadata.type}, [message]: #{payload.inspect}" }
105
- else
106
- logger.verbose { "Received nil content on: [queue]: #{denormalized_queue_name}." }
107
- @payload = nil
108
- @nil_message = true
85
+ def setup_reply_queue(reply_queue_name, &blk)
86
+ if @reply_queues[reply_queue_name]
87
+ blk.call(@reply_queues[reply_queue_name])
88
+ else
89
+ @exchange_completion.completion do |exchange|
90
+ logger.debug { "Attaching to reply queue: #{reply_queue_name}" }
91
+ Smith::Messaging::Sender.new(reply_queue_name, :auto_delete => false, :immediate => true, :mandatory => true) do |sender|
92
+ @reply_queues[reply_queue_name] = sender
93
+ blk.call(sender)
94
+ end
109
95
  end
110
96
  end
97
+ end
111
98
 
112
- # Reply to a message. If reply_to header is not set a error will be logged
113
- def reply(&block)
114
- responder = Responder.new
115
- if reply_to
116
- responder.callback do |return_value|
117
- Sender.new(@metadata.reply_to, :auto_delete => true).ready do |sender|
118
- logger.verbose { "Replying on: #{@metadata.reply_to}" } if logger.level == 0
119
- sender.publish(ACL::Payload.new(:default).content(return_value), sender.options.publish(:correlation_id => @metadata.message_id))
99
+ # Subscribes to a queue and passes the headers and payload into the
100
+ # block. +subscribe+ will automatically acknowledge the message unless
101
+ # the options sets :ack to false.
102
+ def subscribe(&blk)
103
+ @queue_completion.completion do |queue|
104
+ @requeue_options_completion.completion do |requeue_options|
105
+ if !queue.subscribed?
106
+ opts = @options.subscribe
107
+ logger.debug { "Subscribing to: [queue]:#{@queue_name} [options]:#{opts}" }
108
+ queue.subscribe(opts) do |metadata,payload|
109
+ if payload
110
+ on_message(metadata, payload, requeue_options, &blk)
111
+ else
112
+ logger.verbose { "Received null message on: #{@queue_name} [options]:#{opts}" }
113
+ end
120
114
  end
121
- end
122
- else
123
- # Null responder. If a call on the responder is made log a warning. Something is wrong.
124
- responder.callback do |return_value|
125
- logger.error { "You are responding to a message that has no reply_to on queue: #{denormalized_queue_name}." }
126
- logger.verbose { "Queue options: #{@metadata.exchange}." }
115
+ else
116
+ logger.error { "Queue is already subscribed too. Not listening on: #{@queue_name}" }
127
117
  end
128
118
  end
129
-
130
- block.call(responder)
131
- end
132
-
133
- # acknowledge the message.
134
- def ack(multiple=false)
135
- @metadata.ack(multiple) unless @nil_message
136
119
  end
120
+ end
137
121
 
138
- # reject the message. Optionally requeuing it.
139
- def reject(opts={})
140
- @metadata.reject(opts) unless @nil_message
122
+ # pops a message off the queue and passes the headers and payload
123
+ # into the block. +pop+ will automatically acknowledge the message
124
+ # unless the options sets :ack to false.
125
+ def pop(&blk)
126
+ opts = @options.pop
127
+ @queue_completion.completion do |queue|
128
+ @requeue_options_completion.completion do |requeue_options|
129
+ queue.pop(opts) do |metadata, payload|
130
+ if payload
131
+ on_message(metadata, payload, requeue_options, &blk)
132
+ else
133
+ blk.call(nil,nil)
134
+ end
135
+ end
136
+ end
141
137
  end
138
+ end
142
139
 
143
- def reply_to
144
- @metadata.reply_to
140
+ # Define a channel error handler.
141
+ def on_error(chain=false, &blk)
142
+ # TODO Check that this chains callbacks
143
+ @channel_completion.completion do |channel|
144
+ channel.on_error(&blk)
145
145
  end
146
+ end
146
147
 
147
- # Republish the message to the end of the same queue. This is useful
148
- # for when the agent encounters an error and needs to requeue the message.
149
- def requeue(delay, count, strategy, &block)
150
- requeue_with_strategy(delay, count, strategy) do
151
-
152
- # Sort out the options. Receiver#queue is private hence the send. I know, I know.
153
- opts = @receiver.send(:queue).opts.tap do |o|
154
- o.delete(:queue)
155
- o.delete(:exchange)
156
-
157
- o[:headers] = increment_requeue_count(metadata.headers)
158
- o[:routing_key] = normalised_queue_name
159
- o[:type] = metadata.type
148
+ def on_message(metadata, payload, requeue_options, &blk)
149
+ if @payload_type.empty? || @payload_type.include?(metadata.type.to_sym)
150
+ @message_counter.increment_counter
151
+ if metadata.reply_to
152
+ setup_reply_queue(metadata.reply_to) do |queue|
153
+ Foo.new(metadata, payload, @foo_options.merge(:reply_queue => queue), requeue_options, &blk)
160
154
  end
161
-
162
- logger.verbose { "Requeuing to: #{denormalized_queue_name}. [options]: #{opts}" }
163
- logger.verbose { "Requeuing to: #{denormalized_queue_name}. [message]: #{ACL::Payload.decode(@undecoded_payload, metadata.type)}" }
164
-
165
- @receiver.send(:exchange).publish(@undecoded_payload, opts)
155
+ else
156
+ Foo.new(metadata, payload, @foo_options, requeue_options, &blk)
166
157
  end
158
+ else
159
+ raise IncorrectPayloadType, "This queue can only accept the following payload types: #{@payload_type.to_a.to_s}"
167
160
  end
161
+ end
168
162
 
169
- def on_requeue_error(&block)
170
- @on_requeue_error = block
163
+ private :on_message
164
+
165
+ # def delete(&blk)
166
+ # @queue_completion.completion do |queue|
167
+ # queue.delete do
168
+ # @exchange_completion.completion do |exchange|
169
+ # exchange.delete do
170
+ # @channel_completion.completion do |channel|
171
+ # channel.close(&blk)
172
+ # end
173
+ # end
174
+ # end
175
+ # end
176
+ # end
177
+ # end
178
+
179
+
180
+ # I've a feeling this should be replaced by the above code.
181
+ # TODO Check this is correct.
182
+ def delete(&blk)
183
+ @queue_completion.completion do |queue|
184
+ queue.delete(&blk)
171
185
  end
186
+ end
172
187
 
173
- def on_requeue(&block)
174
- @on_requeue = block
188
+ def status(&blk)
189
+ @queue_completion.completion do |queue|
190
+ queue.status do |num_messages, num_consumers|
191
+ blk.call(num_messages, num_consumers)
192
+ end
175
193
  end
194
+ end
176
195
 
177
- def current_requeue_number
178
- metadata.headers['requeue'] || 0
196
+ def requeue_parameters(opts={})
197
+ @requeue_options_completion.completion do |requeue_options|
198
+ requeue_options.merge!(opts)
179
199
  end
200
+ end
180
201
 
181
- # The payload type. This returns the protocol buffers class name as a string.
182
- def payload_type
183
- @metadata.type
202
+ def on_requeue(&blk)
203
+ @requeue_options_completion.completion do |requeue_options|
204
+ requeue_options.merge!(:on_requeue => blk)
184
205
  end
206
+ end
185
207
 
186
- def queue_name
187
- denormalized_queue_name
208
+ def on_requeue_limit(&blk)
209
+ @requeue_options_completion.completion do |requeue_options|
210
+ requeue_options.merge!(:on_requeue_limit => blk)
188
211
  end
212
+ end
213
+ end
189
214
 
190
- private
191
215
 
192
- def requeue_with_strategy(delay, count, strategy, &block)
193
- if current_requeue_number < count
194
- method = "#{strategy}_strategy".to_sym
195
- if respond_to?(method, true)
196
- cumulative_delay = send(method, delay)
197
- @on_requeue.call(cumulative_delay, current_requeue_number + 1)
198
- EM.add_timer(cumulative_delay) do
199
- block.call(cumulative_delay, current_requeue_number + 1)
200
- end
201
- else
202
- raise RuntimeError, "Unknown requeue strategy. #{method}"
203
- end
204
- else
205
- @on_requeue_error.call(cumulative_delay, current_requeue_number)
216
+ class Foo
217
+ include Smith::Logger
218
+
219
+ attr_accessor :metadata
220
+
221
+ def initialize(metadata, data, opts={}, requeue_opts, &blk)
222
+ @metadata = metadata
223
+ @reply_queue = opts[:reply_queue]
224
+ @requeue_opts = requeue_opts
225
+
226
+ @time = Time.now
227
+ @message = ACL::Payload.decode(data, metadata.type)
228
+
229
+ if opts[:threading]
230
+ EM.defer do
231
+ blk.call(@message, self)
232
+ ack if opts[:auto_ack]
206
233
  end
234
+ else
235
+ blk.call(@message, self)
236
+ ack if opts[:auto_ack]
207
237
  end
238
+ end
208
239
 
209
- def exponential_no_initial_delay_strategy(delay)
210
- delay * (2 ** current_requeue_number - 1)
211
- end
240
+ # Send a message to the reply_to queue as specified in the message header.
241
+ def reply(acl=nil, &blk)
242
+ raise ArgumentError, "you cannot supply an ACL and a blcok." if acl && blk
243
+ raise ArgumentError, "you must supply either an ACL or a blcok." if acl.nil? && blk.nil?
212
244
 
213
- def exponential_strategy(delay)
214
- delay * (2 ** current_requeue_number)
245
+ if @metadata.reply_to
246
+ @reply_queue.publish((blk) ? blk.call : acl, :correlation_id => @metadata.message_id)
247
+ else
248
+ logger.error { "You are replying to a message that has no reply_to: #{@metadata.exchange}." }
215
249
  end
250
+ end
216
251
 
217
- def linear_strategy(delay)
218
- delay * (current_requeue_number + 1)
219
- end
252
+ # Requeue the current mesage on the current queue. A requeue number is
253
+ # added to the message header which is used to ensure the correct number
254
+ # of requeues.
255
+ def requeue
256
+ Requeue.new(@message, @metadata, @requeue_opts).requeue
257
+ end
220
258
 
221
- def denormalized_queue_name
222
- @receiver.denormalized_queue_name
223
- end
259
+ # acknowledge the message.
260
+ def ack(multiple=false)
261
+ @metadata.ack(multiple)
262
+ end
224
263
 
225
- def normalised_queue_name
226
- @receiver.queue_name
227
- end
264
+ # reject the message. Optionally requeuing it.
265
+ def reject(opts={})
266
+ @metadata.reject(opts)
267
+ end
228
268
 
229
- def increment_requeue_count(headers)
230
- headers.tap do |m|
231
- m['requeue'] = (m['requeue']) ? m['requeue'] + 1 : 1
232
- end
233
- end
269
+ # the correlation_id
270
+ def correlation_id
271
+ @metadata.correlation_id
234
272
  end
235
273
  end
236
274
  end