right_agent 0.5.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 (147) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +78 -0
  3. data/Rakefile +86 -0
  4. data/lib/right_agent.rb +66 -0
  5. data/lib/right_agent/actor.rb +163 -0
  6. data/lib/right_agent/actor_registry.rb +76 -0
  7. data/lib/right_agent/actors/agent_manager.rb +189 -0
  8. data/lib/right_agent/agent.rb +735 -0
  9. data/lib/right_agent/agent_config.rb +403 -0
  10. data/lib/right_agent/agent_identity.rb +209 -0
  11. data/lib/right_agent/agent_tags_manager.rb +213 -0
  12. data/lib/right_agent/audit_formatter.rb +107 -0
  13. data/lib/right_agent/broker_client.rb +683 -0
  14. data/lib/right_agent/command.rb +30 -0
  15. data/lib/right_agent/command/agent_manager_commands.rb +134 -0
  16. data/lib/right_agent/command/command_client.rb +136 -0
  17. data/lib/right_agent/command/command_constants.rb +42 -0
  18. data/lib/right_agent/command/command_io.rb +128 -0
  19. data/lib/right_agent/command/command_parser.rb +87 -0
  20. data/lib/right_agent/command/command_runner.rb +105 -0
  21. data/lib/right_agent/command/command_serializer.rb +63 -0
  22. data/lib/right_agent/console.rb +65 -0
  23. data/lib/right_agent/core_payload_types.rb +42 -0
  24. data/lib/right_agent/core_payload_types/cookbook.rb +61 -0
  25. data/lib/right_agent/core_payload_types/cookbook_position.rb +46 -0
  26. data/lib/right_agent/core_payload_types/cookbook_repository.rb +116 -0
  27. data/lib/right_agent/core_payload_types/cookbook_sequence.rb +70 -0
  28. data/lib/right_agent/core_payload_types/dev_repositories.rb +90 -0
  29. data/lib/right_agent/core_payload_types/event_categories.rb +38 -0
  30. data/lib/right_agent/core_payload_types/executable_bundle.rb +138 -0
  31. data/lib/right_agent/core_payload_types/login_policy.rb +72 -0
  32. data/lib/right_agent/core_payload_types/login_user.rb +62 -0
  33. data/lib/right_agent/core_payload_types/planned_volume.rb +94 -0
  34. data/lib/right_agent/core_payload_types/recipe_instantiation.rb +60 -0
  35. data/lib/right_agent/core_payload_types/repositories_bundle.rb +50 -0
  36. data/lib/right_agent/core_payload_types/right_script_attachment.rb +95 -0
  37. data/lib/right_agent/core_payload_types/right_script_instantiation.rb +73 -0
  38. data/lib/right_agent/core_payload_types/secure_document.rb +66 -0
  39. data/lib/right_agent/core_payload_types/secure_document_location.rb +63 -0
  40. data/lib/right_agent/core_payload_types/software_repository_instantiation.rb +61 -0
  41. data/lib/right_agent/daemonize.rb +35 -0
  42. data/lib/right_agent/dispatcher.rb +348 -0
  43. data/lib/right_agent/enrollment_result.rb +217 -0
  44. data/lib/right_agent/exceptions.rb +30 -0
  45. data/lib/right_agent/ha_broker_client.rb +1278 -0
  46. data/lib/right_agent/idempotent_request.rb +140 -0
  47. data/lib/right_agent/log.rb +418 -0
  48. data/lib/right_agent/monkey_patches.rb +29 -0
  49. data/lib/right_agent/monkey_patches/amqp_patch.rb +274 -0
  50. data/lib/right_agent/monkey_patches/ruby_patch.rb +49 -0
  51. data/lib/right_agent/monkey_patches/ruby_patch/array_patch.rb +29 -0
  52. data/lib/right_agent/monkey_patches/ruby_patch/darwin_patch.rb +24 -0
  53. data/lib/right_agent/monkey_patches/ruby_patch/linux_patch.rb +24 -0
  54. data/lib/right_agent/monkey_patches/ruby_patch/linux_patch/file_patch.rb +30 -0
  55. data/lib/right_agent/monkey_patches/ruby_patch/object_patch.rb +49 -0
  56. data/lib/right_agent/monkey_patches/ruby_patch/singleton_patch.rb +46 -0
  57. data/lib/right_agent/monkey_patches/ruby_patch/string_patch.rb +107 -0
  58. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch.rb +32 -0
  59. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/file_patch.rb +90 -0
  60. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/process_patch.rb +63 -0
  61. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/stdio_patch.rb +27 -0
  62. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/time_patch.rb +55 -0
  63. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/win32ole_patch.rb +34 -0
  64. data/lib/right_agent/multiplexer.rb +91 -0
  65. data/lib/right_agent/operation_result.rb +270 -0
  66. data/lib/right_agent/packets.rb +637 -0
  67. data/lib/right_agent/payload_formatter.rb +104 -0
  68. data/lib/right_agent/pid_file.rb +159 -0
  69. data/lib/right_agent/platform.rb +319 -0
  70. data/lib/right_agent/platform/darwin.rb +227 -0
  71. data/lib/right_agent/platform/linux.rb +268 -0
  72. data/lib/right_agent/platform/windows.rb +1204 -0
  73. data/lib/right_agent/scripts/agent_controller.rb +522 -0
  74. data/lib/right_agent/scripts/agent_deployer.rb +379 -0
  75. data/lib/right_agent/scripts/common_parser.rb +153 -0
  76. data/lib/right_agent/scripts/log_level_manager.rb +193 -0
  77. data/lib/right_agent/scripts/stats_manager.rb +256 -0
  78. data/lib/right_agent/scripts/usage.rb +58 -0
  79. data/lib/right_agent/secure_identity.rb +92 -0
  80. data/lib/right_agent/security.rb +32 -0
  81. data/lib/right_agent/security/cached_certificate_store_proxy.rb +63 -0
  82. data/lib/right_agent/security/certificate.rb +102 -0
  83. data/lib/right_agent/security/certificate_cache.rb +89 -0
  84. data/lib/right_agent/security/distinguished_name.rb +56 -0
  85. data/lib/right_agent/security/encrypted_document.rb +84 -0
  86. data/lib/right_agent/security/rsa_key_pair.rb +76 -0
  87. data/lib/right_agent/security/signature.rb +86 -0
  88. data/lib/right_agent/security/static_certificate_store.rb +69 -0
  89. data/lib/right_agent/sender.rb +937 -0
  90. data/lib/right_agent/serialize.rb +29 -0
  91. data/lib/right_agent/serialize/message_pack.rb +102 -0
  92. data/lib/right_agent/serialize/secure_serializer.rb +131 -0
  93. data/lib/right_agent/serialize/secure_serializer_initializer.rb +47 -0
  94. data/lib/right_agent/serialize/serializable.rb +135 -0
  95. data/lib/right_agent/serialize/serializer.rb +149 -0
  96. data/lib/right_agent/stats_helper.rb +731 -0
  97. data/lib/right_agent/subprocess.rb +38 -0
  98. data/lib/right_agent/tracer.rb +124 -0
  99. data/right_agent.gemspec +60 -0
  100. data/spec/actor_registry_spec.rb +81 -0
  101. data/spec/actor_spec.rb +99 -0
  102. data/spec/agent_config_spec.rb +226 -0
  103. data/spec/agent_identity_spec.rb +75 -0
  104. data/spec/agent_spec.rb +571 -0
  105. data/spec/broker_client_spec.rb +961 -0
  106. data/spec/command/agent_manager_commands_spec.rb +51 -0
  107. data/spec/command/command_io_spec.rb +93 -0
  108. data/spec/command/command_parser_spec.rb +79 -0
  109. data/spec/command/command_runner_spec.rb +72 -0
  110. data/spec/command/command_serializer_spec.rb +51 -0
  111. data/spec/core_payload_types/dev_repositories_spec.rb +64 -0
  112. data/spec/core_payload_types/executable_bundle_spec.rb +59 -0
  113. data/spec/core_payload_types/login_user_spec.rb +98 -0
  114. data/spec/core_payload_types/right_script_attachment_spec.rb +65 -0
  115. data/spec/core_payload_types/spec_helper.rb +23 -0
  116. data/spec/dispatcher_spec.rb +372 -0
  117. data/spec/enrollment_result_spec.rb +53 -0
  118. data/spec/ha_broker_client_spec.rb +1673 -0
  119. data/spec/idempotent_request_spec.rb +136 -0
  120. data/spec/log_spec.rb +177 -0
  121. data/spec/monkey_patches/amqp_patch_spec.rb +100 -0
  122. data/spec/monkey_patches/eventmachine_spec.rb +62 -0
  123. data/spec/monkey_patches/string_patch_spec.rb +99 -0
  124. data/spec/multiplexer_spec.rb +48 -0
  125. data/spec/operation_result_spec.rb +171 -0
  126. data/spec/packets_spec.rb +418 -0
  127. data/spec/platform/platform_spec.rb +60 -0
  128. data/spec/results_mock.rb +45 -0
  129. data/spec/secure_identity_spec.rb +50 -0
  130. data/spec/security/cached_certificate_store_proxy_spec.rb +56 -0
  131. data/spec/security/certificate_cache_spec.rb +71 -0
  132. data/spec/security/certificate_spec.rb +49 -0
  133. data/spec/security/distinguished_name_spec.rb +46 -0
  134. data/spec/security/encrypted_document_spec.rb +55 -0
  135. data/spec/security/rsa_key_pair_spec.rb +55 -0
  136. data/spec/security/signature_spec.rb +66 -0
  137. data/spec/security/static_certificate_store_spec.rb +52 -0
  138. data/spec/sender_spec.rb +887 -0
  139. data/spec/serialize/message_pack_spec.rb +131 -0
  140. data/spec/serialize/secure_serializer_spec.rb +102 -0
  141. data/spec/serialize/serializable_spec.rb +90 -0
  142. data/spec/serialize/serializer_spec.rb +174 -0
  143. data/spec/spec.opts +2 -0
  144. data/spec/spec_helper.rb +77 -0
  145. data/spec/stats_helper_spec.rb +681 -0
  146. data/spec/tracer_spec.rb +114 -0
  147. metadata +320 -0
@@ -0,0 +1,213 @@
1
+ #
2
+ # Copyright (c) 2009 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ require 'singleton'
24
+
25
+ module RightScale
26
+
27
+ # Agent tags management
28
+ class AgentTagsManager
29
+ include Singleton
30
+
31
+ # (Agent) Agent being managed
32
+ attr_accessor :agent
33
+
34
+ # Retrieve current agent tags and give result to block
35
+ #
36
+ # === Block
37
+ # Given block should take one argument which will be set with an array
38
+ # initialized with the tags of this instance
39
+ #
40
+ # === Return
41
+ # true:: Always return true
42
+ def tags
43
+ do_query do |result|
44
+ if result.kind_of?(Hash)
45
+ yield(result.size == 1 ? result.values.first['tags'] : [])
46
+ else
47
+ yield result
48
+ end
49
+ end
50
+ end
51
+
52
+ # Queries a list of servers in the current deployment which have one or more
53
+ # of the given tags.
54
+ #
55
+ # === Parameters
56
+ # tags(Array):: tags to query or empty
57
+ #
58
+ # === Block
59
+ # Given block should take one argument which will be set with an array
60
+ # initialized with the tags of this instance
61
+ #
62
+ # === Return
63
+ # true:: Always return true
64
+ def query_tags(*tags)
65
+ do_query(tags) { |result| yield result }
66
+ end
67
+
68
+ # Queries a list of servers in the current deployment which have one or more
69
+ # of the given tags. Yields the raw response (for responding locally).
70
+ #
71
+ # === Parameters
72
+ # tags(Array):: tags to query or empty
73
+ # agent_ids(Array):: agent IDs to query or empty or nil
74
+ #
75
+ # === Block
76
+ # Given block should take one argument which will be set with the raw response
77
+ #
78
+ # === Return
79
+ # true:: Always return true
80
+ def query_tags_raw(tags, agent_ids = nil)
81
+ do_query(tags, agent_ids, true) { |raw_response| yield raw_response }
82
+ end
83
+
84
+ # Add given tags to agent
85
+ #
86
+ # === Parameters
87
+ # new_tags(Array):: Tags to be added
88
+ #
89
+ # === Block
90
+ # A block is optional. If provided, should take one argument which will be set with the
91
+ # raw response
92
+ #
93
+ # === Return
94
+ # true always return true
95
+ def add_tags(*new_tags)
96
+ update_tags(new_tags, []) { |raw_response| yield raw_response if block_given? }
97
+ end
98
+
99
+ # Remove given tags from agent
100
+ #
101
+ # === Parameters
102
+ # old_tags(Array):: Tags to be removed
103
+ #
104
+ # === Block
105
+ # A block is optional. If provided, should take one argument which will be set with the
106
+ # raw response
107
+ #
108
+ # === Return
109
+ # true always return true
110
+ def remove_tags(*old_tags)
111
+ update_tags([], old_tags) { |raw_response| yield raw_response if block_given? }
112
+ end
113
+
114
+ # Runs a tag update with a list of new and old tags.
115
+ #
116
+ # === Parameters
117
+ # new_tags(Array):: new tags to add or empty
118
+ # old_tags(Array):: old tags to remove or empty
119
+ # block(Block):: optional callback for update response
120
+ #
121
+ # === Block
122
+ # A block is optional. If provided, should take one argument which will be set with the
123
+ # raw response
124
+ #
125
+ # === Return
126
+ # true:: Always return true
127
+ def update_tags(new_tags, old_tags, &block)
128
+ agent_check
129
+ tags = @agent.tags
130
+ tags += (new_tags || [])
131
+ tags -= (old_tags || [])
132
+ tags.uniq!
133
+
134
+ payload = {:new_tags => new_tags, :obsolete_tags => old_tags}
135
+ request = RightScale::IdempotentRequest.new("/mapper/update_tags", payload)
136
+ if block
137
+ # always yield raw response
138
+ request.callback do |_|
139
+ # refresh agent's copy of tags on successful update
140
+ @agent.tags = tags
141
+ block.call(request.raw_response)
142
+ end
143
+ request.errback { |message| block.call(request.raw_response || message) }
144
+ end
145
+ request.run
146
+ true
147
+ end
148
+
149
+ # Clear all agent tags
150
+ #
151
+ # === Block
152
+ # Given block should take one argument which will be set with the raw response
153
+ #
154
+ # === Return
155
+ # true::Always return true
156
+ def clear
157
+ update_tags([], tags) { |raw_response| yield raw_response }
158
+ end
159
+
160
+ private
161
+
162
+ def agent_check
163
+ raise ArgumentError, "Must set agent= before using tag manager" unless @agent
164
+ end
165
+
166
+ # Runs a tag query with an optional list of tags.
167
+ #
168
+ # === Parameters
169
+ # tags(Array):: tags to query or empty or nil
170
+ # agent_ids(Array):: agent IDs to query or empty or nil
171
+ # raw(Boolean):: true to yield raw tag response instead of deserialized tags
172
+ #
173
+ # === Block
174
+ # Given block should take one argument which will be set with an array
175
+ # initialized with the tags of this instance
176
+ #
177
+ # === Return
178
+ # true:: Always return true
179
+ def do_query(tags = nil, agent_ids = nil, raw = false)
180
+ agent_check
181
+ payload = {:agent_ids => [@agent.identity]}
182
+ payload[:tags] = ensure_flat_array_value(tags) unless tags.nil? || tags.empty?
183
+ payload[:agent_ids] = ensure_flat_array_value(agent_ids) unless agent_ids.nil? || agent_ids.empty?
184
+ request = RightScale::IdempotentRequest.new("/mapper/query_tags", payload)
185
+ request.callback { |result| yield raw ? request.raw_response : result }
186
+ request.errback do |message|
187
+ RightScale::RightLinkLog.error("Failed to query tags: #{message}")
188
+ yield((raw ? request.raw_response : nil) || message)
189
+ end
190
+ request.run
191
+ true
192
+ end
193
+
194
+ # Ensures value is a flat array, making an array from the single value if
195
+ # necessary.
196
+ #
197
+ # === Parameters
198
+ # value(Object):: any kind of value
199
+ #
200
+ # === Return
201
+ # result(Array):: flat array value
202
+ def ensure_flat_array_value(value)
203
+ if value.kind_of?(Array)
204
+ value.flatten!
205
+ else
206
+ value = [value]
207
+ end
208
+ value
209
+ end
210
+
211
+ end # AgentTagsManager
212
+
213
+ end # RightScale
@@ -0,0 +1,107 @@
1
+ #
2
+ # Copyright (c) 2009-2011 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ module RightScale
24
+
25
+ # Standard formatter for audit entries
26
+ # Each method return a hash of two elements:
27
+ # - :summary contains the updated summary of the audit entry
28
+ # - :detail contains the details to be appended to the audit entry
29
+ class AuditFormatter
30
+
31
+ # Start new audit section
32
+ #
33
+ # === Parameters
34
+ # title(String):: New section title
35
+ #
36
+ # === Return
37
+ # entry(Hash):: Hash containing new audit entry summary and detail
38
+ def self.new_section(title)
39
+ title = '' unless title
40
+ entry = { :summary => title, :detail => "#{ '****' * 20 }\n*RS>#{ title.center(72) }****\n" }
41
+ end
42
+
43
+ # Update audit summary
44
+ #
45
+ # === Parameters
46
+ # status(String):: Updated audit status
47
+ #
48
+ # === Return
49
+ # entry(Hash):: Hash containing new audit entry summary and detail
50
+ def self.status(status)
51
+ entry = { :summary => status, :detail => wrap_text(status) }
52
+ end
53
+
54
+ # Append output to current audit section
55
+ #
56
+ # === Parameters
57
+ # text(String):: Output to be appended
58
+ #
59
+ # === Return
60
+ # entry(Hash):: Hash containing new audit entry detail
61
+ def self.output(text)
62
+ text += "\n" unless text[-1, 1] == "\n"
63
+ entry = { :detail => text }
64
+ end
65
+
66
+ # Append info text to current audit section
67
+ #
68
+ # === Parameters
69
+ # info(String):: Information to be appended
70
+ #
71
+ # === Return
72
+ # entry(Hash):: Hash containing new audit entry detail
73
+ def self.info(text)
74
+ entry = { :detail => wrap_text(text) }
75
+ end
76
+
77
+ # Append error text to current audit section
78
+ #
79
+ # === Parameters
80
+ # text(String):: Error message to be appended
81
+ #
82
+ # === Return
83
+ # entry(Hash):: Hash containing new audit entry detail
84
+ def self.error(text)
85
+ entry = { :detail => "*ERROR> #{text}\n" }
86
+ end
87
+
88
+ protected
89
+
90
+ # Wrap text to given number of columns
91
+ # Tries to be smart and only wrap when there is a space
92
+ #
93
+ # === Parameters
94
+ # txt(String):: Text to be wrapped
95
+ # prefix(String>:: Prefix for each wrapped line, default to '*RS) '
96
+ # col(Integer):: Maximum number of columns for each line, default to 80
97
+ #
98
+ # === Return
99
+ # wrapped_text(String):: Wrapped text
100
+ def self.wrap_text(txt, prefix = '*RS> ', col = 80)
101
+ txt = '' unless txt
102
+ wrapped_text = txt.gsub(/(.{1,#{col - prefix.size}})( +|$\n?)|(.{1,#{col - prefix.size}})/, "#{prefix}\\1\\3\n").rstrip + "\n"
103
+ end
104
+
105
+ end
106
+
107
+ end
@@ -0,0 +1,683 @@
1
+ #
2
+ # Copyright (c) 2009-2011 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ module RightScale
24
+
25
+ # Client for accessing AMQP broker
26
+ class BrokerClient
27
+
28
+ include StatsHelper
29
+
30
+ # Set of possible broker connection status values
31
+ STATUS = [
32
+ :connecting, # Initiated AMQP connection but not yet confirmed that connected
33
+ :connected, # Confirmed AMQP connection
34
+ :stopping, # Broker is stopping service and, although still connected, is no longer usable
35
+ :disconnected, # Notified by AMQP that connection has been lost and attempting to reconnect
36
+ :closed, # AMQP connection closed explicitly or because of too many failed connect attempts
37
+ :failed # Failed to connect due to internal failure or AMQP failure to connect
38
+ ]
39
+
40
+ # (MQ) AMQP client
41
+ attr_reader :mq
42
+
43
+ # (String) Broker identity
44
+ attr_reader :identity
45
+
46
+ # (String) Broker alias, used in logs
47
+ attr_reader :alias
48
+
49
+ # (String) Host name
50
+ attr_reader :host
51
+
52
+ # (Integer) Port number
53
+ attr_reader :port
54
+
55
+ # (Integer) Unique index for broker within given island, used in alias
56
+ attr_reader :index
57
+
58
+ # (Symbol) AMQP connection STATUS value
59
+ attr_reader :status
60
+
61
+ # (Array) List of MQ::Queue queues currently subscribed
62
+ attr_reader :queues
63
+
64
+ # (Boolean) Whether last connect attempt failed
65
+ attr_reader :last_failed
66
+
67
+ # (ActivityStats) AMQP lost connection statistics
68
+ attr_reader :disconnects
69
+
70
+ # (ActivityStats) AMQP connection failure statistics
71
+ attr_reader :failures
72
+
73
+ # (Integer) Number of attempts to connect after failure
74
+ attr_reader :retries
75
+
76
+ # (Integer) Identifier for island containing this broker
77
+ attr_reader :island_id
78
+
79
+ # (Integer) Island alias, used in logs
80
+ attr_reader :island_alias
81
+
82
+ # (Boolean) Whether this broker is in the same island as the creator of this client
83
+ attr_reader :in_home_island
84
+
85
+ # Create broker client
86
+ #
87
+ # === Parameters
88
+ # identity(String):: Broker identity
89
+ # address(Hash):: Broker address
90
+ # :host(String:: IP host name or address
91
+ # :port(Integer):: TCP port number for individual broker
92
+ # :index(String):: Unique index for broker within given island for use in forming alias
93
+ # serializer(Serializer):: Serializer used for marshaling packets being published; if nil,
94
+ # has same effect as setting options :no_serialize and :no_unserialize
95
+ # exceptions(ExceptionStats):: Exception statistics container
96
+ # options(Hash):: AMQP connection configuration options
97
+ # :user(String):: User name
98
+ # :pass(String):: Password
99
+ # :vhost(String):: Virtual host path name
100
+ # :insist(Boolean):: Whether to suppress redirection of connection
101
+ # :reconnect_interval(Integer):: Number of seconds between reconnect attempts
102
+ # :prefetch(Integer):: Maximum number of messages the AMQP broker is to prefetch for the agent
103
+ # before it receives an ack. Value 1 ensures that only last unacknowledged gets redelivered
104
+ # if the agent crashes. Value 0 means unlimited prefetch.
105
+ # :home_island(Integer):: Identifier for home island of creator of this client
106
+ # :exception_on_receive_callback(Proc):: Callback activated on a receive exception with parameters
107
+ # message(Object):: Message received
108
+ # exception(Exception):: Exception raised
109
+ # :update_status_callback(Proc):: Callback activated on a connection status change with parameters
110
+ # broker(BrokerClient):: Broker client
111
+ # connected_before(Boolean):: Whether was connected prior to this status change
112
+ # island(IslandData|nil):: Island containing this broker, or nil if unknown
113
+ # existing(BrokerClient|nil):: Existing broker client for this address, or nil if none
114
+ def initialize(identity, address, serializer, exceptions, options, island = nil, existing = nil)
115
+ @options = options
116
+ @identity = identity
117
+ @island_id = island && island.id
118
+ @island_alias = island ? "i#{island.id}" : ""
119
+ @in_home_island = @island_id == @options[:home_island]
120
+ @host = address[:host]
121
+ @port = address[:port].to_i
122
+ @index = address[:index].to_i
123
+ @alias = (@in_home_island ? "" : @island_alias) + "b#{@index}"
124
+ @serializer = serializer
125
+ @exceptions = exceptions
126
+ @queues = []
127
+ @last_failed = false
128
+ @disconnects = ActivityStats.new(measure_rate = false)
129
+ @failures = ActivityStats.new(measure_rate = false)
130
+ @retries = 0
131
+
132
+ connect(address, @options[:reconnect_interval])
133
+
134
+ if existing
135
+ @disconnects = existing.disconnects
136
+ @failures = existing.failures
137
+ @last_failed = existing.last_failed
138
+ @retries = existing.retries
139
+ update_failure if @status == :failed
140
+ end
141
+ end
142
+
143
+ # Determine whether the broker connection is usable, i.e., connecting or confirmed connected
144
+ #
145
+ # === Return
146
+ # (Boolean):: true if usable, otherwise false
147
+ def usable?
148
+ [:connected, :connecting].include?(@status)
149
+ end
150
+
151
+ # Determine whether this client is currently connected to the broker
152
+ #
153
+ # === Return
154
+ # (Boolean):: true if connected, otherwise false
155
+ def connected?
156
+ @status == :connected
157
+ end
158
+
159
+ # Determine whether the broker connection has failed
160
+ #
161
+ # === Return
162
+ # (Boolean):: true if failed, otherwise false
163
+ def failed?(backoff = false)
164
+ @status == :failed
165
+ end
166
+
167
+ # Subscribe an AMQP queue to an AMQP exchange
168
+ # Do not wait for confirmation from broker that subscription is complete
169
+ # When a message is received, acknowledge, unserialize, and log it as specified
170
+ # If the message is unserialized and it is not of the right type, it is dropped after logging a warning
171
+ #
172
+ # === Parameters
173
+ # queue(Hash):: AMQP queue being subscribed with keys :name and :options,
174
+ # which are the standard AMQP ones plus
175
+ # :no_declare(Boolean):: Whether to skip declaring this queue on the broker
176
+ # to cause its creation; for use when client does not have permission to create or
177
+ # knows the queue already exists and wants to avoid declare overhead
178
+ # exchange(Hash|nil):: AMQP exchange to subscribe to with keys :type, :name, and :options,
179
+ # nil means use empty exchange by directly subscribing to queue; the :options are the
180
+ # standard AMQP ones plus
181
+ # :no_declare(Boolean):: Whether to skip declaring this exchange on the broker
182
+ # to cause its creation; for use when client does not have create permission or
183
+ # knows the exchange already exists and wants to avoid declare overhead
184
+ # options(Hash):: Subscribe options:
185
+ # :ack(Boolean):: Explicitly acknowledge received messages to AMQP
186
+ # :no_unserialize(Boolean):: Do not unserialize message, this is an escape for special
187
+ # situations like enrollment, also implicitly disables receive filtering and logging;
188
+ # this option is implicitly invoked if initialize without a serializer
189
+ # (packet class)(Array(Symbol)):: Filters to be applied in to_s when logging packet to :info,
190
+ # only packet classes specified are accepted, others are not processed but are logged with error
191
+ # :category(String):: Packet category description to be used in error messages
192
+ # :log_data(String):: Additional data to display at end of log entry
193
+ # :no_log(Boolean):: Disable receive logging unless debug level
194
+ # :exchange2(Hash):: Additional exchange to which same queue is to be bound
195
+ # :brokers(Array):: Identity of brokers for which to subscribe, defaults to all usable if nil or empty
196
+ #
197
+ # === Block
198
+ # Block with following parameters to be called each time exchange matches a message to the queue:
199
+ # identity(String):: Serialized identity of broker delivering the message
200
+ # message(Packet|String):: Message received, which is unserialized unless :no_unserialize was specified
201
+ #
202
+ # === Return
203
+ # (Boolean):: true if subscribe successfully or if already subscribed, otherwise false
204
+ def subscribe(queue, exchange = nil, options = {}, &blk)
205
+ return false unless usable?
206
+ return true unless @queues.select { |q| q.name == queue[:name] }.empty?
207
+
208
+ to_exchange = if exchange
209
+ if options[:exchange2]
210
+ " to exchanges #{exchange[:name]} and #{options[:exchange2][:name]}"
211
+ else
212
+ " to exchange #{exchange[:name]}"
213
+ end
214
+ end
215
+ queue_options = queue[:options] || {}
216
+ exchange_options = (exchange && exchange[:options]) || {}
217
+
218
+ begin
219
+ Log.info("[setup] Subscribing queue #{queue[:name]}#{to_exchange} on broker #{@alias}")
220
+ q = @mq.queue(queue[:name], queue_options)
221
+ @queues << q
222
+ if exchange
223
+ x = @mq.__send__(exchange[:type], exchange[:name], exchange_options)
224
+ binding = q.bind(x, options[:key] ? {:key => options[:key]} : {})
225
+ if exchange2 = options[:exchange2]
226
+ q.bind(@mq.__send__(exchange2[:type], exchange2[:name], exchange2[:options] || {}))
227
+ end
228
+ q = binding
229
+ end
230
+ if options[:ack]
231
+ q.subscribe(:ack => true) do |info, message|
232
+ begin
233
+ # Ack now before processing to avoid risk of duplication after a crash
234
+ info.ack
235
+ if options[:no_unserialize] || @serializer.nil?
236
+ execute_callback(blk, @identity, message)
237
+ elsif message == "nil"
238
+ # This happens as part of connecting an instance agent to a broker prior to version 13
239
+ Log.debug("RECV #{@alias} nil message ignored")
240
+ elsif
241
+ packet = receive(queue[:name], message, options)
242
+ execute_callback(blk, @identity, packet) if packet
243
+ end
244
+ true
245
+ rescue Exception => e
246
+ Log.error("Failed executing block for message from queue #{queue.inspect}#{to_exchange} " +
247
+ "on broker #{@alias}", e, :trace)
248
+ @exceptions.track("receive", e)
249
+ false
250
+ end
251
+ end
252
+ else
253
+ q.subscribe do |message|
254
+ begin
255
+ if options[:no_unserialize] || @serializer.nil?
256
+ execute_callback(blk, @identity, message)
257
+ elsif message == "nil"
258
+ # This happens as part of connecting an instance agent to a broker
259
+ Log.debug("RECV #{@alias} nil message ignored")
260
+ elsif
261
+ packet = receive(queue[:name], message, options)
262
+ execute_callback(blk, @identity, packet) if packet
263
+ end
264
+ true
265
+ rescue Exception => e
266
+ Log.error("Failed executing block for message from queue #{queue.inspect}#{to_exchange} " +
267
+ "on broker #{@alias}", e, :trace)
268
+ @exceptions.track("receive", e)
269
+ false
270
+ end
271
+ end
272
+ end
273
+ rescue Exception => e
274
+ Log.error("Failed subscribing queue #{queue.inspect}#{to_exchange} on broker #{@alias}", e, :trace)
275
+ @exceptions.track("subscribe", e)
276
+ false
277
+ end
278
+ end
279
+
280
+ # Unsubscribe from the specified queues
281
+ # Silently ignore unknown queues
282
+ #
283
+ # === Parameters
284
+ # queue_names(Array):: Names of queues previously subscribed to
285
+ #
286
+ # === Block
287
+ # Optional block to be called with no parameters when each unsubscribe completes
288
+ #
289
+ # === Return
290
+ # true:: Always return true
291
+ def unsubscribe(queue_names, &blk)
292
+ if usable?
293
+ @queues.each do |q|
294
+ if queue_names.include?(q.name)
295
+ begin
296
+ Log.info("[stop] Unsubscribing queue #{q.name} on broker #{@alias}")
297
+ q.unsubscribe { blk.call if blk }
298
+ rescue Exception => e
299
+ Log.error("Failed unsubscribing queue #{q.name} on broker #{@alias}", e, :trace)
300
+ @exceptions.track("unsubscribe", e)
301
+ blk.call if blk
302
+ end
303
+ end
304
+ end
305
+ end
306
+ true
307
+ end
308
+
309
+ # Declare queue or exchange object but do not subscribe to it
310
+ #
311
+ # === Parameters
312
+ # type(Symbol):: Type of object: :queue, :direct, :fanout or :topic
313
+ # name(String):: Name of object
314
+ # options(Hash):: Standard AMQP declare options
315
+ #
316
+ # === Return
317
+ # (Boolean):: true if declare successfully, otherwise false
318
+ def declare(type, name, options = {})
319
+ return false unless usable?
320
+ begin
321
+ Log.info("[setup] Declaring #{name} #{type.to_s} on broker #{@alias}")
322
+ delete_from_cache(:queue, name)
323
+ @mq.__send__(type, name, options)
324
+ true
325
+ rescue Exception => e
326
+ Log.error("Failed declaring #{type.to_s} #{name} on broker #{@alias}", e, :trace)
327
+ @exceptions.track("declare", e)
328
+ false
329
+ end
330
+ end
331
+
332
+ # Publish message to AMQP exchange
333
+ #
334
+ # === Parameters
335
+ # exchange(Hash):: AMQP exchange to subscribe to with keys :type, :name, and :options,
336
+ # which are the standard AMQP ones plus
337
+ # :no_declare(Boolean):: Whether to skip declaring this exchange or queue on the broker
338
+ # to cause its creation; for use when client does not have create permission or
339
+ # knows the object already exists and wants to avoid declare overhead
340
+ # :declare(Boolean):: Whether to delete this exchange or queue from the AMQP cache
341
+ # to force it to be declared on the broker and thus be created if it does not exist
342
+ # packet(Packet):: Message to serialize and publish
343
+ # message(String):: Serialized message to be published
344
+ # options(Hash):: Publish options -- standard AMQP ones plus
345
+ # :no_serialize(Boolean):: Do not serialize packet because it is already serialized
346
+ # :log_filter(Array(Symbol)):: Filters to be applied in to_s when logging packet to :info
347
+ # :log_data(String):: Additional data to display at end of log entry
348
+ # :no_log(Boolean):: Disable publish logging unless debug level
349
+ #
350
+ # === Return
351
+ # (Boolean):: true if publish successfully, otherwise false
352
+ def publish(exchange, packet, message, options = {})
353
+ return false unless connected?
354
+ begin
355
+ exchange_options = exchange[:options] || {}
356
+ unless (options[:no_log] && Log.level != :debug) || options[:no_serialize]
357
+ re = "RE-" if packet.respond_to?(:tries) && !packet.tries.empty?
358
+ log_filter = options[:log_filter] unless Log.level == :debug
359
+ Log.info("#{re}SEND #{@alias} #{packet.to_s(log_filter, :send_version)} " +
360
+ "#{options[:log_data]}")
361
+ end
362
+ Log.debug("... publish options #{options.inspect}, exchange #{exchange[:name]}, " +
363
+ "type #{exchange[:type]}, options #{exchange[:options].inspect}")
364
+ delete_from_cache(exchange[:type], exchange[:name]) if exchange_options[:declare]
365
+ @mq.__send__(exchange[:type], exchange[:name], exchange_options).publish(message, options)
366
+ true
367
+ rescue Exception => e
368
+ Log.error("Failed publishing to exchange #{exchange.inspect} on broker #{@alias}", e, :trace)
369
+ @exceptions.track("publish", e)
370
+ false
371
+ end
372
+ end
373
+
374
+ # Provide callback to be activated when broker returns a message that could not be delivered
375
+ # A message published with :mandatory => true is returned if the exchange does not have any associated queues
376
+ # or if all the associated queues do not have any consumers
377
+ # A message published with :immediate => true is returned for the same reasons as :mandatory plus if all
378
+ # of the queues associated with the exchange are not immediately ready to consume the message
379
+ #
380
+ # === Block
381
+ # Optional block with following parameters to be called when a message is returned
382
+ # to(String):: Queue to which message was published
383
+ # reason(String):: Reason for return
384
+ # "NO_ROUTE" - queue does not exist
385
+ # "NO_CONSUMERS" - queue exists but it has no consumers, or if :immediate was specified,
386
+ # all consumers are not immediately ready to consume
387
+ # "ACCESS_REFUSED" - queue not usable because broker is in the process of stopping service
388
+ # message(String):: Returned serialized message
389
+ #
390
+ # === Return
391
+ # true:: Always return true
392
+ def return_message
393
+ @mq.return_message do |info, message|
394
+ begin
395
+ to = if info.exchange && !info.exchange.empty? then info.exchange else info.routing_key end
396
+ reason = info.reply_text
397
+ Log.debug("RETURN #{@alias} because #{reason} for #{to}")
398
+ yield(to, reason, message) if block_given?
399
+ rescue Exception => e
400
+ Log.error("Failed return #{info.inspect} of message from broker #{@alias}", e, :trace)
401
+ @exceptions.track("return", e)
402
+ end
403
+ end
404
+ true
405
+ end
406
+
407
+ # Delete queue
408
+ #
409
+ # === Parameters
410
+ # name(String):: Queue name
411
+ # options(Hash):: Queue declare options
412
+ #
413
+ # === Return
414
+ # (Boolean):: true if queue was successfully deleted, otherwise false
415
+ def delete(name, options = {})
416
+ deleted = false
417
+ if usable?
418
+ begin
419
+ @queues.reject! do |q|
420
+ if q.name == name
421
+ @mq.queue(name, options.merge(:no_declare => true)).delete
422
+ deleted = true
423
+ end
424
+ end
425
+ unless deleted
426
+ # Allowing declare to happen since queue may not exist and do not want NOT_FOUND
427
+ # failure to cause AMQP channel to close
428
+ @mq.queue(name, options).delete
429
+ deleted = true
430
+ end
431
+ rescue Exception => e
432
+ Log.error("Failed deleting queue #{name.inspect} on broker #{@alias}", e, :trace)
433
+ @exceptions.track("delete", e)
434
+ end
435
+ end
436
+ deleted
437
+ end
438
+
439
+ # Close broker connection
440
+ #
441
+ # === Parameters
442
+ # propagate(Boolean):: Whether to propagate connection status updates, defaults to true
443
+ # normal(Boolean):: Whether this is a normal close vs. a failed connection, defaults to true
444
+ # log(Boolean):: Whether to log that closing, defaults to true
445
+ #
446
+ # === Block
447
+ # Optional block with no parameters to be called after connection closed
448
+ #
449
+ # === Return
450
+ # true:: Always return true
451
+ def close(propagate = true, normal = true, log = true, &blk)
452
+ final_status = normal ? :closed : :failed
453
+ if ![:closed, :failed].include?(@status)
454
+ begin
455
+ Log.info("[stop] Closed connection to broker #{@alias}") if log
456
+ update_status(final_status) if propagate
457
+ @connection.close do
458
+ @status = final_status
459
+ yield if block_given?
460
+ end
461
+ rescue Exception => e
462
+ Log.error("Failed to close broker #{@alias}", e, :trace)
463
+ @exceptions.track("close", e)
464
+ @status = final_status
465
+ yield if block_given?
466
+ end
467
+ else
468
+ @status = final_status
469
+ yield if block_given?
470
+ end
471
+ true
472
+ end
473
+
474
+ # Get broker client information summarizing its status
475
+ #
476
+ # === Return
477
+ # (Hash):: Status of broker with keys
478
+ # :identity(String):: Serialized identity
479
+ # :alias(String):: Alias used in logs
480
+ # :status(Symbol):: Status of connection
481
+ # :disconnects(Integer):: Number of times lost connection
482
+ # :failures(Integer):: Number of times connect failed
483
+ # :retries(Integer):: Number of attempts to connect after failure
484
+ def summary
485
+ {
486
+ :identity => @identity,
487
+ :alias => @alias,
488
+ :status => @status,
489
+ :retries => @retries,
490
+ :disconnects => @disconnects.total,
491
+ :failures => @failures.total,
492
+ }
493
+ end
494
+
495
+ # Get broker client statistics
496
+ #
497
+ # === Return
498
+ # (Hash):: Broker client stats with keys
499
+ # "alias"(String):: Broker alias
500
+ # "identity"(String):: Broker identity
501
+ # "status"(Status):: Status of connection
502
+ # "disconnect last"(Hash|nil):: Last disconnect information with key "elapsed", or nil if none
503
+ # "disconnects"(Integer|nil):: Number of times lost connection, or nil if none
504
+ # "failure last"(Hash|nil):: Last connect failure information with key "elapsed", or nil if none
505
+ # "failures"(Integer|nil):: Number of failed attempts to connect to broker, or nil if none
506
+ def stats
507
+ {
508
+ "alias" => @alias,
509
+ "identity" => @identity,
510
+ "status" => @status.to_s,
511
+ "disconnect last" => @disconnects.last,
512
+ "disconnects" => nil_if_zero(@disconnects.total),
513
+ "failure last" => @failures.last,
514
+ "failures" => nil_if_zero(@failures.total),
515
+ "retries" => nil_if_zero(@retries)
516
+ }
517
+ end
518
+
519
+ # Callback from AMQP with connection status or from HABrokerClient
520
+ # Makes client callback with :connected or :disconnected status if boundary crossed
521
+ #
522
+ # === Parameters
523
+ # status(Symbol):: Status of connection (:connected, :ready, :disconnected, :stopping, :failed, :closed)
524
+ #
525
+ # === Return
526
+ # true:: Always return true
527
+ def update_status(status)
528
+ # Do not let closed connection regress to failed
529
+ return true if status == :failed && @status == :closed
530
+
531
+ # Wait until connection is ready (i.e. handshake with broker is completed) before
532
+ # changing our status to connected
533
+ return true if status == :connected
534
+ status = :connected if status == :ready
535
+
536
+ before = @status
537
+ @status = status
538
+
539
+ if status == :connected
540
+ update_success
541
+ elsif status == :failed
542
+ update_failure
543
+ elsif status == :disconnected && before != :disconnected
544
+ @disconnects.update
545
+ end
546
+
547
+ unless status == before || @options[:update_status_callback].nil?
548
+ @options[:update_status_callback].call(self, before == :connected)
549
+ end
550
+ true
551
+ end
552
+
553
+ protected
554
+
555
+ # Connect to broker and register for status updates
556
+ # Also set prefetch value if specified
557
+ #
558
+ # === Parameters
559
+ # address(Hash):: Broker address
560
+ # :host(String:: IP host name or address
561
+ # :port(Integer):: TCP port number for individual broker
562
+ # :index(String):: Unique index for broker within given island for use in forming alias
563
+ # reconnect_interval(Integer):: Number of seconds between reconnect attempts
564
+ #
565
+ # === Return
566
+ # true:: Always return true
567
+ def connect(address, reconnect_interval)
568
+ begin
569
+ Log.info("[setup] Connecting to broker #{@identity}, alias #{@alias}")
570
+ @status = :connecting
571
+ @connection = AMQP.connect(:user => @options[:user],
572
+ :pass => @options[:pass],
573
+ :vhost => @options[:vhost],
574
+ :host => address[:host],
575
+ :port => address[:port],
576
+ :insist => @options[:insist] || false,
577
+ :reconnect_delay => lambda { rand(reconnect_interval) },
578
+ :reconnect_interval => reconnect_interval)
579
+ @mq = MQ.new(@connection)
580
+ @mq.__send__(:connection).connection_status { |status| update_status(status) }
581
+ @mq.prefetch(@options[:prefetch]) if @options[:prefetch]
582
+ rescue Exception => e
583
+ @status = :failed
584
+ @failures.update
585
+ Log.error("Failed connecting to broker #{@alias}", e, :trace)
586
+ @exceptions.track("connect", e)
587
+ @connection.close if @connection
588
+ end
589
+ end
590
+
591
+ # Receive message by unserializing it, checking that it is an acceptable type, and logging accordingly
592
+ #
593
+ # === Parameters
594
+ # queue(String):: Name of queue
595
+ # message(String):: Serialized packet
596
+ # options(Hash):: Subscribe options:
597
+ # (packet class)(Array(Symbol)):: Filters to be applied in to_s when logging packet to :info,
598
+ # only packet classes specified are accepted, others are not processed but are logged with error
599
+ # :category(String):: Packet category description to be used in error messages
600
+ # :log_data(String):: Additional data to display at end of log entry
601
+ # :no_log(Boolean):: Disable receive logging unless debug level
602
+ #
603
+ # === Return
604
+ # (Packet|nil):: Unserialized packet or nil if not of right type or if there is an exception
605
+ def receive(queue, message, options = {})
606
+ begin
607
+ packet = @serializer.load(message)
608
+ if options.key?(packet.class)
609
+ unless options[:no_log] && Log.level != :debug
610
+ re = "RE-" if packet.respond_to?(:tries) && !packet.tries.empty?
611
+ log_filter = options[packet.class] unless Log.level == :debug
612
+ Log.info("#{re}RECV #{@alias} #{packet.to_s(log_filter, :recv_version)} " +
613
+ "#{options[:log_data]}")
614
+ end
615
+ packet
616
+ else
617
+ category = options[:category] + " " if options[:category]
618
+ Log.warning("Received invalid #{category}packet type from queue #{queue} on broker #{@alias}: #{packet.class}\n" + caller.join("\n"))
619
+ nil
620
+ end
621
+ rescue Exception => e
622
+ trace = e.is_a?(Serializer::SerializationError) ? :caller : :trace
623
+ Log.error("Failed receiving from queue #{queue} on #{@alias}", e, trace)
624
+ @exceptions.track("receive", e)
625
+ @options[:exception_on_receive_callback].call(message, e) if @options[:exception_on_receive_callback]
626
+ nil
627
+ end
628
+ end
629
+
630
+ # Make status updates for connect success
631
+ #
632
+ # === Return
633
+ # true:: Always return true
634
+ def update_success
635
+ @last_failed = false
636
+ @retries = 0
637
+ true
638
+ end
639
+
640
+ # Make status updates for connect failure
641
+ #
642
+ # === Return
643
+ # true:: Always return true
644
+ def update_failure
645
+ Log.error("Failed to connect to broker #{@alias}")
646
+ if @last_failed
647
+ @retries += 1
648
+ else
649
+ @last_failed = true
650
+ @retries = 0
651
+ @failures.update
652
+ end
653
+ true
654
+ end
655
+
656
+ # Delete object from local AMQP cache in case it is no longer consistent with broker
657
+ #
658
+ # === Parameters
659
+ # type(Symbol):: Type of AMQP object
660
+ # name(String):: Name of object
661
+ #
662
+ # === Return
663
+ # true:: Always return true
664
+ def delete_from_cache(type, name)
665
+ @mq.__send__(type == :queue ? :queues : :exchanges).delete(name)
666
+ true
667
+ end
668
+
669
+ # Execute packet receive callback, make it a separate method to ease instrumentation
670
+ #
671
+ # === Parameters
672
+ # callback(Proc):: Proc to run
673
+ # args(Array):: Array of pass-through arguments
674
+ #
675
+ # === Return
676
+ # (Object):: Callback return value
677
+ def execute_callback(callback, *args)
678
+ callback.call(*args) if callback
679
+ end
680
+
681
+ end # BrokerClient
682
+
683
+ end # RightScale