Skip to content

Commit

Permalink
Merge 877e06d into f9e33b4
Browse files Browse the repository at this point in the history
  • Loading branch information
danielsiwiec committed Mar 20, 2015
2 parents f9e33b4 + 877e06d commit a8baa6f
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 7 deletions.
86 changes: 79 additions & 7 deletions aws-sdk-resources/lib/aws-sdk-resources/resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,92 @@ def initialize(*args)
# @return [Hash<Symbol,String>]
attr_reader :identifiers

# @option options [Integer] :max_attempts (10)
# @option options [Integer] :delay (10)
# @option options [Proc] :before_attempt (nil)
# @option options [Proc] :before_wait (nil)
# Waiter polls an API operation until a resource enters a desired
# state.
#
# @note The waiting operation is performed on a copy. The original resource remains unchanged
#
# ## Basic Usage
#
# Waiter will polls until it is succesful, it fails by
# entering a terminal state, or until a maximum number of attempts
# are made.
#
# # polls in a loop until condition is true
# resource.wait_until(options) {|resource| condition}
#
# ## Example
#
# instance.wait_until(max_attempts:10, delay:5) {|instance| instance.state.name == 'running' }
#
# ## Configuration
#
# You can configure the maximum number of polling attempts, and the
# delay (in seconds) between each polling attempt. The waiting condition is set
# by passing a block to {#wait_until}:
#
# # poll for ~25 seconds
# resource.wait_until(max_attempts:5,delay:5) {|resource|...}
#
# ## Callbacks
#
# You can be notified before each polling attempt and before each
# delay. If you throw `:success` or `:failure` from these callbacks,
# it will terminate the waiter.
#
# started_at = Time.now
# # poll for 1 hour, instead of a number of attempts
# proc = Proc.new do |attempts, response|
# throw :failure if Time.now - started_at > 3600
# end
#
# # disable max attempts
# instance.wait_until(before_wait:proc, max_attempts:nil) {...}
#
# ## Handling Errors
#
# When a waiter is successful, it returns the Resource. When a waiter
# fails, it raises an error.
#
# begin
# resource.wait_until(...)
# rescue Aws::Waiters::Errors::WaiterFailed
# # resource did not enter the desired state in time
# end
#
#
# @yieldparam [Resource] resource to be used in the waiting condition
#
# @raise [Errors::FailureStateError] Raised when the waiter terminates
# because the waiter has entered a state that it will not transition
# out of, preventing success.
#
# @raise [Errors::TooManyAttemptsError] Raised when the configured
# maximum number of attempts have been made, and the waiter is not
# yet successful.
#
# @raise [Errors::UnexpectedError] Raised when an error is encounted
# while polling for a resource that is not expected.
#
# @raise [NotImplementedError] Raised when the resource does not
# support #reload operation
#
# @option options [Integer] :max_attempts (10) Maximum number of attempts
# @option options [Integer] :delay (10) Delay between each attempt in seconds
# @option options [Proc] :before_attempt (nil) Callback invoked before each attempt
# @option options [Proc] :before_wait (nil) Callback invoked before each wait
# @return [Resource] if the waiter was successful
def wait_until(options = {}, &block)
resource_copy = self.dup
attempts = 0
options[:max_attempts] ||= 10
options[:delay] ||= 10
options[:poller] = Proc.new do
attempts += 1
if block.call(self)
[:success, self]
if block.call(resource_copy)
[:success, resource_copy]
else
reload unless attempts == options[:max_attempts]
resource_copy.reload unless attempts == options[:max_attempts]
:retry
end
end
Expand Down
69 changes: 69 additions & 0 deletions aws-sdk-resources/spec/resource_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,78 @@ module Resources
end

end
end

describe '#wait_until' do

let(:data) { double('data') }

let(:data2) { double('data-2') }

let(:data3) { double('data-3') }

let(:resource) { resource_class.new(data:data) }

let(:datas) { [data2, data3] }

let(:load_operation) { double('load-operation') }

before(:each) do
allow(load_operation).to receive(:call).
and_return(*datas)
resource_class.load_operation = load_operation
end

let(:proc) { double('proc') }

it 'does not reload if waiting condition already met' do
expect(load_operation).not_to receive(:call)
resource.wait_until {true}
end

it 'does not modify the resource if waiting condition already met' do
response = resource.wait_until {true}
expect(resource.data).to be(data)
end

it 'reloads up to maximum attempts and raises an error and does not modify the resource' do
expect{
expect(load_operation).to receive(:call).exactly(4).times
resource.wait_until(max_attempts:5, delay:0) {false}
}.to raise_error(Aws::Waiters::Errors::TooManyAttemptsError, /stopped waiting after 5 attempts without success/)
expect(resource.data).to be(data)
end

it 'reloads until condition met' do
allow(proc).to receive(:call).and_return(false,false, true)
expect(load_operation).to receive(:call).exactly(2).times
resource.wait_until(delay:0, max_attempts:10) {proc.call}
end

it 'returns last reloaded resource if successful' do
allow(proc).to receive(:call).and_return(false,false, true)
expect(load_operation).to receive(:call).exactly(2).times
response = resource.wait_until(delay:0) {proc.call}
expect(response.data).to be(data3)
end

it 'does not modify the resource if waiting if waiting condition not met' do
allow(proc).to receive(:call).and_return(false,false, true)
response = resource.wait_until(delay:0) {proc.call}
expect(resource.data).to be(data)
end

it 'raises a NotImplementedError when load_operation is not defined' do
resource_class.load_operation = nil
msg = "#load not defined for #{resource_name}"
expect {
resource_class.new.wait_until {false}
}.to raise_error(NotImplementedError, msg)
end

end


describe 'class methods' do

describe 'identifiers' do
Expand Down

0 comments on commit a8baa6f

Please sign in to comment.