def test_waiter_handles_retry_state(self): acceptor_with_retry_state = [ { 'state': 'success', 'matcher': 'status', 'expected': 200 }, { 'state': 'retry', 'matcher': 'error', 'expected': 'RetryMe' }, ] config = self.create_waiter_config(acceptors=acceptor_with_retry_state) operation_method = mock.Mock() self.client_responses_are( {'Nothing': 'foo'}, {'Error': { 'Code': 'RetryMe', 'Message': 'foo' }}, { 'Success': True, 'ResponseMetadata': { 'HTTPStatusCode': 200 } }, {'NeverCalled': True}, for_operation=operation_method) waiter = Waiter('MyWaiter', config, operation_method) waiter.wait() self.assertEqual(operation_method.call_count, 3)
def test_waiter_transitions_to_retry_but_max_attempts_exceeded(self): acceptors = [ { 'state': 'success', 'matcher': 'status', 'expected': 200 }, { 'state': 'retry', 'matcher': 'error', 'expected': 'RetryMe' }, ] config = self.create_waiter_config(acceptors=acceptors) operation_method = mock.Mock() self.client_responses_are({ 'Success': False }, {'Error': { 'Code': 'RetryMe', 'Message': 'foo' }}, {'Success': False}, {'Success': False}, for_operation=operation_method) waiter = Waiter('MyWaiter', config, operation_method) with self.assertRaises(WaiterError): waiter.wait()
def test_waiter_transitions_to_retry_but_max_attempts_exceeded(self): acceptors = [ { 'state': 'success', 'matcher': 'status', 'expected': 200 }, { 'state': 'retry', 'matcher': 'error', 'expected': 'RetryMe' }, ] config = self.create_waiter_config(acceptors=acceptors) operation_method = mock.Mock() self.client_responses_are( {'Success': False}, {'Error': { 'Code': 'RetryMe', 'Message': 'foo' }}, {'Success': False}, {'Success': False}, for_operation=operation_method) waiter = Waiter('MyWaiter', config, operation_method) with self.assertRaises(WaiterError): waiter.wait()
def test_waiter_transitions_to_failure_state(self): acceptors = [ # A success state that will never be hit. { 'state': 'success', 'matcher': 'status', 'expected': 1000 }, { 'state': 'failure', 'matcher': 'error', 'expected': 'FailError' }, ] config = self.create_waiter_config(acceptors=acceptors) operation_method = mock.Mock() self.client_responses_are( {'Nothing': 'foo'}, # And on the second attempt, a FailError is seen, which # causes the waiter to fail fast. {'Error': { 'Code': 'FailError', 'Message': 'foo' }}, {'WillNeverGetCalled': True}, for_operation=operation_method) waiter = Waiter('MyWaiter', config, operation_method) with self.assertRaises(WaiterError): waiter.wait() # Not only should we raise an exception, but we should have # only called the operation_method twice because the second # response triggered a fast fail. self.assertEqual(operation_method.call_count, 2)
def test_waiter_transitions_to_failure_state(self): acceptors = [ # A success state that will never be hit. { 'state': 'success', 'matcher': 'status', 'expected': 1000 }, { 'state': 'failure', 'matcher': 'error', 'expected': 'FailError' }, ] config = self.create_waiter_config(acceptors=acceptors) operation_method = mock.Mock() self.client_responses_are( { 'Nothing': 'foo' }, # And on the second attempt, a FailError is seen, which # causes the waiter to fail fast. {'Error': { 'Code': 'FailError', 'Message': 'foo' }}, {'WillNeverGetCalled': True}, for_operation=operation_method) waiter = Waiter('MyWaiter', config, operation_method) with self.assertRaises(WaiterError): waiter.wait() # Not only should we raise an exception, but we should have # only called the operation_method twice because the second # response triggered a fast fail. self.assertEqual(operation_method.call_count, 2)
def test_waiter_handles_retry_state(self): acceptor_with_retry_state = [ { 'state': 'success', 'matcher': 'status', 'expected': 200 }, { 'state': 'retry', 'matcher': 'error', 'expected': 'RetryMe' }, ] config = self.create_waiter_config(acceptors=acceptor_with_retry_state) operation_method = mock.Mock() self.client_responses_are({ 'Nothing': 'foo' }, {'Error': { 'Code': 'RetryMe', 'Message': 'foo' }}, { 'Success': True, 'ResponseMetadata': { 'HTTPStatusCode': 200 } }, {'NeverCalled': True}, for_operation=operation_method) waiter = Waiter('MyWaiter', config, operation_method) waiter.wait() self.assertEqual(operation_method.call_count, 3)
def _assert_failure_state_error_raised(self, acceptors, responses, expected_msg): config = self.create_waiter_config(acceptors=acceptors) operation_method = mock.Mock() waiter = Waiter('MyWaiter', config, operation_method) self.client_responses_are(*responses, for_operation=operation_method) with self.assertRaisesRegex(WaiterError, expected_msg): waiter.wait()
def test_last_response_available_on_waiter_error(self): last_response = {'Error': {'Code': 'UnknownError', 'Message': 'bad error'}} config = self.create_waiter_config() operation_method = mock.Mock() self.client_responses_are(last_response, for_operation=operation_method) waiter = Waiter('MyWaiter', config, operation_method) with self.assertRaises(WaiterError) as e: waiter.wait() self.assertEqual(e.exception.last_response, last_response)
def test_waiter_invocation_config_honors_max_attempts(self): config = self.create_waiter_config() operation_method = mock.Mock() self.client_responses_are({'Success': False}, {'Success': False}, for_operation=operation_method) waiter = Waiter('MyWaiter', config, operation_method) custom_max = 2 with self.assertRaises(WaiterError): waiter.wait(WaiterConfig={'MaxAttempts': custom_max}) self.assertEqual(operation_method.call_count, 2)
def test_waiter_never_matches(self): # Verify that a matcher will fail after max_attempts # is exceeded. config = self.create_waiter_config(max_attempts=3) operation_method = mock.Mock() self.client_responses_are({'Foo': 'FAILURE'}, {'Foo': 'FAILURE'}, {'Foo': 'FAILURE'}, for_operation=operation_method) waiter = Waiter('MyWaiter', config, operation_method) with self.assertRaises(WaiterError): waiter.wait()
def test_waiter_never_matches(self): # Verify that a matcher will fail after max_attempts # is exceeded. config = self.create_waiter_config(max_attempts=3) operation_method = mock.Mock() self.client_responses_are({ 'Foo': 'FAILURE' }, {'Foo': 'FAILURE'}, {'Foo': 'FAILURE'}, for_operation=operation_method) waiter = Waiter('MyWaiter', config, operation_method) with self.assertRaises(WaiterError): waiter.wait()
def test_kwargs_are_passed_through(self): acceptors = [ {'state': 'success', 'matcher': 'error', 'expected': 'MyError'}, ] config = self.create_waiter_config(acceptors=acceptors) operation_method = mock.Mock() self.client_responses_are( {'Error': {'Code': 'MyError'}}, for_operation=operation_method) waiter = Waiter('MyWaiter', config, operation_method) waiter.wait(Foo='foo', Bar='bar', Baz='baz') operation_method.assert_called_with(Foo='foo', Bar='bar', Baz='baz')
def test_waiter_invocation_config_honors_max_attempts(self): config = self.create_waiter_config() operation_method = mock.Mock() self.client_responses_are( {'Success': False}, {'Success': False}, for_operation=operation_method ) waiter = Waiter('MyWaiter', config, operation_method) custom_max = 2 with self.assertRaises(WaiterError): waiter.wait(WaiterConfig={'MaxAttempts': custom_max}) self.assertEqual(operation_method.call_count, 2)
def test_waiter_invocation_config_honors_delay(self, sleep_mock): config = self.create_waiter_config() operation_method = mock.Mock() self.client_responses_are({'Success': False}, {'Success': False}, {'Success': False}, for_operation=operation_method) waiter = Waiter('MyWaiter', config, operation_method) custom_delay = 3 with self.assertRaises(WaiterError): waiter.wait(WaiterConfig={'Delay': custom_delay}) # We attempt three times, which means we need to sleep # twice, once before each subsequent request. self.assertEqual(sleep_mock.call_count, 2) sleep_mock.assert_called_with(custom_delay)
def test_last_response_available_on_waiter_error(self): last_response = { 'Error': { 'Code': 'UnknownError', 'Message': 'bad error' } } config = self.create_waiter_config() operation_method = mock.Mock() self.client_responses_are(last_response, for_operation=operation_method) waiter = Waiter('MyWaiter', config, operation_method) with self.assertRaises(WaiterError) as e: waiter.wait() self.assertEqual(e.exception.last_response, last_response)
def test_unspecified_errors_stops_waiter(self): # If a waiter receives an error response, then the # waiter immediately stops. config = self.create_waiter_config() operation_method = mock.Mock() self.client_responses_are( # This is an unknown error that's not called out # in any of the waiter config, so when the # waiter encounters this response it will transition # to the failure state. {'Error': {'Code': 'UnknownError', 'Message': 'bad error'}}, for_operation=operation_method ) waiter = Waiter('MyWaiter', config, operation_method) with self.assertRaises(WaiterError): waiter.wait()
def test_waiter_waits_until_acceptor_matches(self): config = self.create_waiter_config(max_attempts=3, acceptors=[{ 'state': 'success', 'matcher': 'path', 'argument': 'Foo', 'expected': 'SUCCESS' }]) # Simulate the client having two calls that don't # match followed by a third call that matches the # acceptor. operation_method = mock.Mock() waiter = Waiter('MyWaiter', config, operation_method) self.client_responses_are({'Foo': 'FAILURE'}, {'Foo': 'FAILURE'}, {'Foo': 'SUCCESS'}, for_operation=operation_method) waiter.wait() self.assertEqual(operation_method.call_count, 3)
def test_waiter_invocation_config_honors_delay(self, sleep_mock): config = self.create_waiter_config() operation_method = mock.Mock() self.client_responses_are( {'Success': False}, {'Success': False}, {'Success': False}, for_operation=operation_method ) waiter = Waiter('MyWaiter', config, operation_method) custom_delay = 3 with self.assertRaises(WaiterError): waiter.wait(WaiterConfig={'Delay': custom_delay}) # We attempt three times, which means we need to sleep # twice, once before each subsequent request. self.assertEqual(sleep_mock.call_count, 2) sleep_mock.assert_called_with(custom_delay)
def test_waiter_waits_until_acceptor_matches(self): config = self.create_waiter_config( max_attempts=3, acceptors=[{'state': 'success', 'matcher': 'path', 'argument': 'Foo', 'expected': 'SUCCESS'}]) # Simulate the client having two calls that don't # match followed by a third call that matches the # acceptor. operation_method = mock.Mock() waiter = Waiter('MyWaiter', config, operation_method) self.client_responses_are( {'Foo': 'FAILURE'}, {'Foo': 'FAILURE'}, {'Foo': 'SUCCESS'}, for_operation=operation_method ) waiter.wait() self.assertEqual(operation_method.call_count, 3)
def test_unspecified_errors_propagate_error_code(self): # If a waiter receives an error response, then the # waiter should pass along the error code config = self.create_waiter_config() operation_method = mock.Mock() error_code = 'error_message' error_message = 'error_message' self.client_responses_are( # This is an unknown error that's not called out # in any of the waiter config, so when the # waiter encounters this response it will transition # to the failure state. {'Error': {'Code': error_code, 'Message': error_message}}, for_operation=operation_method ) waiter = Waiter('MyWaiter', config, operation_method) with self.assertRaisesRegexp(WaiterError, error_message): waiter.wait()
def test_waiter_matches_with_invalid_error_response(self): # Verify that the call will not raise WaiterError # because of 'Error' key in success response. config = self.create_waiter_config(max_attempts=3, acceptors=[{ 'state': 'success', 'matcher': 'path', 'argument': 'Foo', 'expected': 'SUCCESS' }]) operation_method = mock.Mock() waiter = Waiter('MyWaiter', config, operation_method) self.client_responses_are({ 'Foo': 'SUCCESS', 'Error': 'foo' }, for_operation=operation_method) waiter.wait() self.assertEqual(operation_method.call_count, 1)
def make_waiter(op, path, expected, matcher="path", delay=1, max_attempts=30): from botocore.waiter import Waiter, SingleWaiterConfig acceptor = dict(matcher=matcher, argument=path, expected=expected, state="success") waiter_cfg = dict(operation=op.__name__, delay=delay, maxAttempts=max_attempts, acceptors=[acceptor]) return Waiter(op.__name__, SingleWaiterConfig(waiter_cfg), op)
def test_waiter_honors_delay_time_between_retries(self, sleep_mock): delay_time = 5 config = self.create_waiter_config(delay=delay_time) operation_method = mock.Mock() self.client_responses_are( # This is an unknown error that's not called out # in any of the waiter config, so when the # waiter encounters this response it will transition # to the failure state. {'Success': False}, {'Success': False}, {'Success': False}, for_operation=operation_method) waiter = Waiter('MyWaiter', config, operation_method) with self.assertRaises(WaiterError): waiter.wait() # We attempt three times, which means we need to sleep # twice, once before each subsequent request. self.assertEqual(sleep_mock.call_count, 2) sleep_mock.assert_called_with(delay_time)
TemplateBody=cfn, Capabilities=["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"], PermissionModel='SERVICE_MANAGED', #AdministrationRoleARN='AWSControlTowerStackSetRole', #ExecutionRoleName='AWSControlTowerExecution', AutoDeployment={ 'Enabled': True, 'RetainStacksOnAccountRemoval': False }) stack_set_id = response['StackSetId'] print("Stack Set Id is {}".format((stack_set_id))) organizations_client = boto3.client('organizations') root_id = organizations_client.list_roots()['Roots'][0]['Id'] waiter = Waiter('StackSetOperationComplete', waiter_config, cloudformation_client.describe_stack_set_operation) ec2_client = boto3.client('ec2') all_regions = [ region['RegionName'] for region in ec2_client.describe_regions()['Regions'] ] response = cloudformation_client.create_stack_instances( StackSetName=StackSetName, DeploymentTargets={'OrganizationalUnitIds': [root_id]}, Regions=all_regions, OperationPreferences={ 'FailureToleranceCount': 1, 'MaxConcurrentCount': 10 })