right_agent 1.0.1 → 2.0.7

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 (67) hide show
  1. data/README.rdoc +10 -8
  2. data/Rakefile +31 -5
  3. data/lib/right_agent.rb +6 -1
  4. data/lib/right_agent/actor.rb +4 -20
  5. data/lib/right_agent/actors/agent_manager.rb +1 -1
  6. data/lib/right_agent/agent.rb +357 -144
  7. data/lib/right_agent/agent_config.rb +7 -6
  8. data/lib/right_agent/agent_identity.rb +13 -11
  9. data/lib/right_agent/agent_tag_manager.rb +60 -64
  10. data/{spec/results_mock.rb → lib/right_agent/clients.rb} +10 -24
  11. data/lib/right_agent/clients/api_client.rb +383 -0
  12. data/lib/right_agent/clients/auth_client.rb +247 -0
  13. data/lib/right_agent/clients/balanced_http_client.rb +369 -0
  14. data/lib/right_agent/clients/base_retry_client.rb +495 -0
  15. data/lib/right_agent/clients/right_http_client.rb +279 -0
  16. data/lib/right_agent/clients/router_client.rb +493 -0
  17. data/lib/right_agent/command/command_io.rb +4 -4
  18. data/lib/right_agent/command/command_parser.rb +2 -2
  19. data/lib/right_agent/command/command_runner.rb +1 -1
  20. data/lib/right_agent/connectivity_checker.rb +179 -0
  21. data/lib/right_agent/core_payload_types/secure_document_location.rb +2 -2
  22. data/lib/right_agent/dispatcher.rb +12 -10
  23. data/lib/right_agent/enrollment_result.rb +16 -12
  24. data/lib/right_agent/exceptions.rb +34 -20
  25. data/lib/right_agent/history.rb +10 -5
  26. data/lib/right_agent/log.rb +5 -5
  27. data/lib/right_agent/minimal.rb +1 -0
  28. data/lib/right_agent/multiplexer.rb +1 -1
  29. data/lib/right_agent/offline_handler.rb +270 -0
  30. data/lib/right_agent/packets.rb +7 -7
  31. data/lib/right_agent/payload_formatter.rb +1 -1
  32. data/lib/right_agent/pending_requests.rb +128 -0
  33. data/lib/right_agent/platform.rb +1 -1
  34. data/lib/right_agent/protocol_version_mixin.rb +69 -0
  35. data/lib/right_agent/{idempotent_request.rb → retryable_request.rb} +7 -7
  36. data/lib/right_agent/scripts/agent_controller.rb +28 -26
  37. data/lib/right_agent/scripts/agent_deployer.rb +37 -22
  38. data/lib/right_agent/scripts/common_parser.rb +10 -3
  39. data/lib/right_agent/secure_identity.rb +1 -1
  40. data/lib/right_agent/sender.rb +299 -785
  41. data/lib/right_agent/serialize/secure_serializer.rb +3 -1
  42. data/lib/right_agent/serialize/secure_serializer_initializer.rb +2 -2
  43. data/lib/right_agent/serialize/serializable.rb +8 -3
  44. data/right_agent.gemspec +49 -18
  45. data/spec/agent_config_spec.rb +7 -7
  46. data/spec/agent_identity_spec.rb +7 -4
  47. data/spec/agent_spec.rb +43 -7
  48. data/spec/agent_tag_manager_spec.rb +72 -83
  49. data/spec/clients/api_client_spec.rb +423 -0
  50. data/spec/clients/auth_client_spec.rb +272 -0
  51. data/spec/clients/balanced_http_client_spec.rb +576 -0
  52. data/spec/clients/base_retry_client_spec.rb +635 -0
  53. data/spec/clients/router_client_spec.rb +594 -0
  54. data/spec/clients/spec_helper.rb +111 -0
  55. data/spec/command/command_io_spec.rb +1 -1
  56. data/spec/command/command_parser_spec.rb +1 -1
  57. data/spec/connectivity_checker_spec.rb +83 -0
  58. data/spec/dispatcher_spec.rb +3 -2
  59. data/spec/enrollment_result_spec.rb +2 -2
  60. data/spec/history_spec.rb +51 -39
  61. data/spec/offline_handler_spec.rb +340 -0
  62. data/spec/pending_requests_spec.rb +136 -0
  63. data/spec/{idempotent_request_spec.rb → retryable_request_spec.rb} +73 -73
  64. data/spec/sender_spec.rb +835 -1052
  65. data/spec/serialize/secure_serializer_spec.rb +3 -2
  66. data/spec/spec_helper.rb +54 -1
  67. metadata +71 -12
@@ -76,11 +76,11 @@ module RightScale
76
76
  # true:: Always return true
77
77
  #
78
78
  # === Raise
79
- # (RightScale::Exceptions::Argument):: If block is missing
80
- # (RightScale::Exceptions::Application):: If +listen+ has already been called and +stop+ hasn't since
81
- # (RightScale::Exceptions::Application):: If port is already bound
79
+ # (ArgumentError):: If block is missing
80
+ # (Exceptions::Application):: If +listen+ has already been called and +stop+ hasn't since
81
+ # (Exceptions::Application):: If port is already bound
82
82
  def listen(socket_port, &block)
83
- raise Exceptions::Argument, 'Missing listener block' unless block_given?
83
+ raise ArgumentError, 'Missing listener block' unless block_given?
84
84
  raise Exceptions::Application, 'Already listening' if listening
85
85
  begin
86
86
  @conn = EM.start_server('127.0.0.1', socket_port, ServerInputHandler, block)
@@ -31,9 +31,9 @@ module RightScale
31
31
  # Block that will get called back whenever a command is successfully parsed
32
32
  #
33
33
  # === Raise
34
- # (RightScale::Exceptions::Argument): If block is missing
34
+ # (ArgumentError): If block is missing
35
35
  def initialize &block
36
- raise RightScale::Exceptions::Argument, 'Missing handler block' unless block
36
+ raise ArgumentError, 'Missing handler block' unless block
37
37
  @callback = block
38
38
  @buildup = ''
39
39
  end
@@ -55,7 +55,7 @@ module RightScale
55
55
  # cmd_options[:listen_port](Integer):: Command server listen port
56
56
  #
57
57
  # === Raise
58
- # (RightScale::Exceptions::Application):: If +start+ has already been called and +stop+ hasn't since
58
+ # (Exceptions::Application):: If +start+ has already been called and +stop+ hasn't since
59
59
  def self.start(socket_port, identity, commands, fiber_pool = nil)
60
60
  cmd_options = nil
61
61
  @listen_port = socket_port
@@ -0,0 +1,179 @@
1
+ #
2
+ # Copyright (c) 2009-2013 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
+ # Broker connectivity checker
26
+ # Checks connectivity when requested
27
+ class ConnectivityChecker
28
+
29
+ # Minimum number of seconds between restarts of the inactivity timer
30
+ MIN_RESTART_INACTIVITY_TIMER_INTERVAL = 60
31
+
32
+ # Number of seconds to wait for ping response from a RightNet router when checking connectivity
33
+ PING_TIMEOUT = 30
34
+
35
+ # Default maximum number of consecutive ping timeouts before attempt to reconnect
36
+ MAX_PING_TIMEOUTS = 3
37
+
38
+ # (EM::Timer) Timer while waiting for RightNet router ping response
39
+ attr_accessor :ping_timer
40
+
41
+ def initialize(sender, check_interval, ping_stats, exception_stats)
42
+ @sender = sender
43
+ @check_interval = check_interval
44
+ @ping_timeouts = {}
45
+ @ping_timer = nil
46
+ @ping_stats = ping_stats
47
+ @exception_stats = exception_stats
48
+ @last_received = Time.now
49
+ @message_received_callbacks = []
50
+ restart_inactivity_timer if @check_interval > 0
51
+ end
52
+
53
+ # Update the time this agent last received a request or response message
54
+ # and restart the inactivity timer thus deferring the next connectivity check
55
+ # Also forward this message receipt notification to any callbacks that have registered
56
+ #
57
+ # === Block
58
+ # Optional block without parameters that is activated when a message is received
59
+ #
60
+ # === Return
61
+ # true:: Always return true
62
+ def message_received(&callback)
63
+ if block_given?
64
+ @message_received_callbacks << callback
65
+ else
66
+ @message_received_callbacks.each { |c| c.call }
67
+ if @check_interval > 0
68
+ now = Time.now
69
+ if (now - @last_received) > MIN_RESTART_INACTIVITY_TIMER_INTERVAL
70
+ @last_received = now
71
+ restart_inactivity_timer
72
+ end
73
+ end
74
+ end
75
+ true
76
+ end
77
+
78
+ # Check whether broker connection is usable by pinging a router via that broker
79
+ # Attempt to reconnect if ping does not respond in PING_TIMEOUT seconds and
80
+ # if have reached timeout limit
81
+ # Ignore request if already checking a connection
82
+ #
83
+ # === Parameters
84
+ # id(String):: Identity of specific broker to use to send ping, defaults to any
85
+ # currently connected broker
86
+ # max_ping_timeouts(Integer):: Maximum number of ping timeouts before attempt
87
+ # to reconnect, defaults to MAX_PING_TIMEOUTS
88
+ #
89
+ # === Return
90
+ # true:: Always return true
91
+ def check(id = nil, max_ping_timeouts = MAX_PING_TIMEOUTS)
92
+ unless @terminating || @ping_timer || (id && !@sender.client.connected?(id))
93
+ @ping_id = id
94
+ @ping_timer = EM::Timer.new(PING_TIMEOUT) do
95
+ if @ping_id
96
+ begin
97
+ @ping_stats.update("timeout")
98
+ @ping_timer = nil
99
+ @ping_timeouts[@ping_id] = (@ping_timeouts[@ping_id] || 0) + 1
100
+ if @ping_timeouts[@ping_id] >= max_ping_timeouts
101
+ Log.error("Mapper ping via broker #{@ping_id} timed out after #{PING_TIMEOUT} seconds and now " +
102
+ "reached maximum of #{max_ping_timeouts} timeout#{max_ping_timeouts > 1 ? 's' : ''}, " +
103
+ "attempting to reconnect")
104
+ host, port, index, priority = @sender.client.identity_parts(@ping_id)
105
+ @sender.agent.connect(host, port, index, priority, force = true)
106
+ else
107
+ Log.warning("Mapper ping via broker #{@ping_id} timed out after #{PING_TIMEOUT} seconds")
108
+ end
109
+ rescue Exception => e
110
+ Log.error("Failed to reconnect to broker #{@ping_id}", e, :trace)
111
+ @exception_stats.track("ping timeout", e)
112
+ end
113
+ else
114
+ @ping_timer = nil
115
+ end
116
+ end
117
+
118
+ handler = lambda do |_|
119
+ begin
120
+ if @ping_timer
121
+ @ping_stats.update("success")
122
+ @ping_timer.cancel
123
+ @ping_timer = nil
124
+ @ping_timeouts[@ping_id] = 0
125
+ @ping_id = nil
126
+ end
127
+ rescue Exception => e
128
+ Log.error("Failed to cancel router ping", e, :trace)
129
+ @exception_stats.track("cancel ping", e)
130
+ end
131
+ end
132
+ request = Request.new("/router/ping", nil, {:from => @sender.identity, :token => AgentIdentity.generate})
133
+ @sender.pending_requests[request.token] = PendingRequest.new(Request, Time.now, handler)
134
+ ids = [@ping_id] if @ping_id
135
+ @ping_id = @sender.send(:publish, request, ids).first
136
+ end
137
+ true
138
+ end
139
+
140
+ # Prepare for agent termination
141
+ #
142
+ # === Return
143
+ # true:: Always return true
144
+ def terminate
145
+ @terminating = true
146
+ @check_interval = 0
147
+ if @ping_timer
148
+ @ping_timer.cancel
149
+ @ping_timer = nil
150
+ end
151
+ if @inactivity_timer
152
+ @inactivity_timer.cancel
153
+ @inactivity_timer = nil
154
+ end
155
+ true
156
+ end
157
+
158
+ protected
159
+
160
+ # Start timer that waits for inactive messaging period to end before checking connectivity
161
+ #
162
+ # === Return
163
+ # true:: Always return true
164
+ def restart_inactivity_timer
165
+ @inactivity_timer.cancel if @inactivity_timer
166
+ @inactivity_timer = EM::Timer.new(@check_interval) do
167
+ begin
168
+ check(id = nil, max_ping_timeouts = 1)
169
+ rescue Exception => e
170
+ Log.error("Failed connectivity check", e, :trace)
171
+ @exception_stats.track("check connectivity", e)
172
+ end
173
+ end
174
+ true
175
+ end
176
+
177
+ end # ConnectivityChecker
178
+
179
+ end # RightScale
@@ -40,10 +40,10 @@ module RightScale
40
40
  # (String):: Access token that should be used to fetch the document
41
41
  attr_accessor :ticket
42
42
 
43
- # Array(String):: names of RightNet agents capable of providing the document.
43
+ # Array(String):: Identities of RightNet agents capable of providing the document.
44
44
  # If nil, then no target should be specified when sending RightNet requests;
45
45
  # this is used to route requests to "trusted infrastructure" nodes at the
46
- # discretion of the mapper.
46
+ # discretion of the RightNet router.
47
47
  attr_accessor :targets
48
48
 
49
49
  # Initialize fields from given arguments
@@ -25,6 +25,8 @@ module RightScale
25
25
  # Dispatching of payload to specified actor
26
26
  class Dispatcher
27
27
 
28
+ include ProtocolVersionMixin
29
+
28
30
  class InvalidRequestType < Exception; end
29
31
  class DuplicateRequest < Exception; end
30
32
 
@@ -85,10 +87,10 @@ module RightScale
85
87
  token = request.token
86
88
  actor, method, idempotent = route(request)
87
89
  received_at = @request_stats.update(method, (token if request.is_a?(Request)))
88
- if dup = duplicate?(request, method, idempotent)
90
+ if (dup = duplicate?(request, method, idempotent))
89
91
  raise DuplicateRequest.new(dup)
90
92
  end
91
- unless result = expired?(request, method)
93
+ unless (result = expired?(request, method))
92
94
  result = perform(request, actor, method, idempotent)
93
95
  end
94
96
  if request.is_a?(Request)
@@ -140,7 +142,7 @@ module RightScale
140
142
  @reject_stats = RightSupport::Stats::Activity.new
141
143
  @request_stats = RightSupport::Stats::Activity.new
142
144
  @dispatch_failure_stats = RightSupport::Stats::Activity.new
143
- @exception_stats = RightSupport::Stats::Exceptions.new(@agent)
145
+ @exception_stats = RightSupport::Stats::Exceptions.new(@agent, @agent.exception_callback)
144
146
  true
145
147
  end
146
148
 
@@ -157,10 +159,10 @@ module RightScale
157
159
  @reject_stats.update("expired (#{method})")
158
160
  Log.info("REJECT EXPIRED <#{request.token}> from #{request.from} TTL #{RightSupport::Stats.elapsed(now - expires_at)} ago")
159
161
  # For agents that do not know about non-delivery, use error result
160
- if request.recv_version < 13
161
- OperationResult.error("Could not deliver request (#{OperationResult::TTL_EXPIRATION})")
162
- else
162
+ if can_handle_non_delivery_result?(request.recv_version)
163
163
  OperationResult.non_delivery(OperationResult::TTL_EXPIRATION)
164
+ else
165
+ OperationResult.error("Could not deliver request (#{OperationResult::TTL_EXPIRATION})")
164
166
  end
165
167
  end
166
168
  end
@@ -176,11 +178,11 @@ module RightScale
176
178
  # (String|nil):: Messaging describing who already serviced request if it is a duplicate, otherwise nil
177
179
  def duplicate?(request, method, idempotent)
178
180
  if !idempotent && @dispatched_cache
179
- if serviced_by = @dispatched_cache.serviced_by(request.token)
181
+ if (serviced_by = @dispatched_cache.serviced_by(request.token))
180
182
  from_retry = ""
181
183
  else
182
184
  from_retry = "retry "
183
- request.tries.each { |t| break if serviced_by = @dispatched_cache.serviced_by(t) }
185
+ request.tries.each { |t| break if (serviced_by = @dispatched_cache.serviced_by(t)) }
184
186
  end
185
187
  if serviced_by
186
188
  @reject_stats.update("#{from_retry}duplicate (#{method})")
@@ -225,9 +227,9 @@ module RightScale
225
227
  def perform(request, actor, method, idempotent)
226
228
  @dispatched_cache.store(request.token) if @dispatched_cache && !idempotent
227
229
  if actor.method(method).arity.abs == 1
228
- actor.__send__(method, request.payload)
230
+ actor.send(method, request.payload)
229
231
  else
230
- actor.__send__(method, request.payload, request)
232
+ actor.send(method, request.payload, request)
231
233
  end
232
234
  rescue Exception => e
233
235
  @dispatch_failure_stats.update("#{request.type}->#{e.class.name}")
@@ -6,7 +6,7 @@ require 'openssl'
6
6
  require 'base64'
7
7
  require 'json'
8
8
 
9
- # A response to a RightNet enrollment request containing the mapper's X509 cert,
9
+ # A response to a RightNet enrollment request containing the router's X509 cert,
10
10
  # the instance (or other) agent's identity cert, and the agent's private key.
11
11
  # Responses are encrypted using a secret key shared between the instance and
12
12
  # the Certifying Authority (aka the RightScale core site) and integrity-protected
@@ -14,35 +14,39 @@ require 'json'
14
14
  module RightScale
15
15
 
16
16
  class EnrollmentResult
17
+
18
+ include ProtocolVersionMixin
19
+
17
20
  # Versions 5 and above use an identical format for the enrollment result
18
21
  SUPPORTED_VERSIONS = 5..AgentConfig.protocol_version
19
22
 
20
23
  class IntegrityFailure < Exception; end
21
24
  class VersionError < Exception; end
22
25
 
23
- attr_reader :r_s_version, :timestamp, :mapper_cert, :id_cert, :id_key
26
+ attr_reader :r_s_version, :timestamp, :router_cert, :id_cert, :id_key
24
27
 
25
28
  # Create a new instance of this class
26
29
  #
27
30
  # === Parameters
28
31
  # timestamp(Time):: Timestamp associated with this result
29
- # mapper_cert(String):: Arbitrary string
32
+ # router_cert(String):: Arbitrary string
30
33
  # id_cert(String):: Arbitrary string
31
34
  # id_key(String):: Arbitrary string
32
35
  # secret(String):: Shared secret with which the result is encrypted
33
36
  #
34
- def initialize(r_s_version, timestamp, mapper_cert, id_cert, id_key, secret)
37
+ def initialize(r_s_version, timestamp, router_cert, id_cert, id_key, secret)
35
38
  @r_s_version = r_s_version
36
39
  @timestamp = timestamp.utc
37
- @mapper_cert = mapper_cert
40
+ @router_cert = router_cert
38
41
  @id_cert = id_cert
39
42
  @id_key = id_key
40
- @serializer = Serializer.new((:json if r_s_version < 12))
43
+ @serializer = Serializer.new(can_handle_msgpack_result?(r_s_version) ? :msgpack : :json)
41
44
 
45
+ cert_name = can_handle_http?(r_s_version) ? 'router_cert' : 'mapper_cert'
42
46
  msg = @serializer.dump({
43
- 'mapper_cert' => @mapper_cert.to_s,
44
- 'id_cert' => @id_cert,
45
- 'id_key' => @id_key
47
+ cert_name => @router_cert.to_s,
48
+ 'id_cert' => @id_cert,
49
+ 'id_key' => @id_key
46
50
  })
47
51
 
48
52
  key = EnrollmentResult.derive_key(secret, @timestamp.to_i.to_s)
@@ -90,7 +94,7 @@ module RightScale
90
94
  # true|false:: Whether the objects' pertinent fields are identical
91
95
  #
92
96
  def ==(o)
93
- self.mapper_cert == o.mapper_cert &&
97
+ self.router_cert == o.router_cert &&
94
98
  self.id_cert == o.id_cert &&
95
99
  self.id_key == o.id_key &&
96
100
  self.timestamp.to_i == o.timestamp.to_i
@@ -164,11 +168,11 @@ module RightScale
164
168
  raise IntegrityFailure.new("MAC mismatch: expected #{my_mac}, got #{mac}") unless (mac == my_mac)
165
169
 
166
170
  msg = serializer.load(plaintext)
167
- mapper_cert = msg['mapper_cert']
171
+ router_cert = msg['router_cert'] || msg['mapper_cert']
168
172
  id_cert = msg['id_cert']
169
173
  id_key = msg['id_key']
170
174
 
171
- self.new(r_s_version, timestamp, mapper_cert, id_cert, id_key, secret)
175
+ self.new(r_s_version, timestamp, router_cert, id_cert, id_key, secret)
172
176
  end
173
177
 
174
178
  protected
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2009-2011 RightScale Inc
2
+ # Copyright (c) 2009-2013 RightScale Inc
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining
5
5
  # a copy of this software and associated documentation files (the
@@ -21,21 +21,22 @@
21
21
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
 
23
23
  module RightScale
24
+
24
25
  class Exceptions
25
- # Capability not currently supported
26
- class NotSupported < Exception; end
27
26
 
28
- # Internal application error
29
- class Application < RuntimeError
27
+ # Base exception for use in nesting exceptions
28
+ class NestedException < StandardError
30
29
  attr_reader :nested_exception
30
+
31
+ # Exception message and optional nested exception or string
31
32
  def initialize(message, nested_exception = nil)
32
33
  @nested_exception = nested_exception
33
34
  super(message)
34
35
  end
35
36
  end
36
37
 
37
- # Invalid command or method argument
38
- class Argument < RuntimeError; end
38
+ # Internal application error
39
+ class Application < StandardError; end
39
40
 
40
41
  # Agent command IO error
41
42
  class IO < RuntimeError; end
@@ -43,30 +44,43 @@ module RightScale
43
44
  # Agent compute platform error
44
45
  class PlatformError < StandardError; end
45
46
 
46
- # Cannot connect or lost connection to external resource
47
- class ConnectivityFailure < RuntimeError
48
- attr_reader :nested_exception
47
+ # Terminating service
48
+ class Terminating < RuntimeError; end
49
+
50
+ # Not authorized to make request
51
+ class Unauthorized < NestedException
49
52
  def initialize(message, nested_exception = nil)
50
- @nested_exception = nested_exception
51
- super(message)
53
+ super(message, nested_exception)
54
+ end
55
+ end
56
+
57
+ # Cannot connect to service, lost connection to it, or it is out of service or too busy to respond
58
+ class ConnectivityFailure < NestedException
59
+ def initialize(message, nested_exception = nil)
60
+ super(message, nested_exception)
52
61
  end
53
62
  end
54
63
 
55
64
  # Request failed but potentially will succeed if retried
56
- class RetryableError < RuntimeError
57
- attr_reader :nested_exception
65
+ class RetryableError < NestedException
58
66
  def initialize(message, nested_exception = nil)
59
- @nested_exception = nested_exception
60
- super(message)
67
+ super(message, nested_exception)
61
68
  end
62
69
  end
63
70
 
64
71
  # Database query failed
65
- class QueryFailure < RuntimeError
66
- attr_reader :nested_exception
72
+ class QueryFailure < NestedException
67
73
  def initialize(message, nested_exception = nil)
68
- @nested_exception = nested_exception
69
- super(message)
74
+ super(message, nested_exception)
75
+ end
76
+ end
77
+
78
+ # Error internal to specified server
79
+ class InternalServerError < NestedException
80
+ attr_reader :server
81
+ def initialize(message, server, nested_exception = nil)
82
+ @server = server
83
+ super(message, nested_exception)
70
84
  end
71
85
  end
72
86
  end