def setUp(self): region = "us-east-1" self.session = get_session(region=region) self.provider = Provider(self.session, interactive=True, recreate_failed=True) self.stubber = Stubber(self.provider.cloudformation)
def setUp(self): self.context = self._get_context() self.provider = Provider(None, interactive=False, recreate_failed=False) self.build_action = build.Action(self.context, provider=self.provider) self.stack = mock.MagicMock() self.stack.name = 'vpc' self.stack.fqn = 'vpc' self.stack.locked = False self.stack_status = None plan = self.build_action._generate_plan() _, self.step = plan.list_pending()[0] self.step.stack = self.stack def patch_object(*args, **kwargs): m = mock.patch.object(*args, **kwargs) self.addCleanup(m.stop) m.start() def get_stack(name, *args, **kwargs): if name != self.stack.name or not self.stack_status: raise StackDoesNotExist(name) return {'StackName': self.stack.name, 'StackStatus': self.stack_status, 'Tags': []} patch_object(self.provider, 'get_stack', side_effect=get_stack) patch_object(self.provider, 'update_stack') patch_object(self.provider, 'create_stack') patch_object(self.provider, 'destroy_stack') patch_object(self.build_action, "s3_stack_push")
def _make_provider(self): provider = Provider(self.session, interactive=False, recreate_failed=False) self._patch_object(provider, 'get_stack', side_effect=self._get_stack) self._patch_object(provider, 'update_stack') self._patch_object(provider, 'create_stack') self._patch_object(provider, 'destroy_stack') return provider
def test_ensure_cfn_bucket_exists(self): provider = Provider("us-east-1") action = BaseAction(context=mock_context("mynamespace"), provider=provider) stubber = Stubber(action.s3_conn) stubber.add_response("head_bucket", service_response={}, expected_params={ "Bucket": ANY, }) with stubber: action.ensure_cfn_bucket()
def setUp(self): self.context = self._get_context() self.session = get_session(region=None) self.provider = Provider(self.session, interactive=False, recreate_failed=False) provider_builder = MockProviderBuilder(self.provider) self.build_action = build.Action(self.context, provider_builder=provider_builder, cancel=MockThreadingEvent()) self.stack = mock.MagicMock() self.stack.region = None self.stack.name = 'vpc' self.stack.fqn = 'vpc' self.stack.blueprint.rendered = '{}' self.stack.locked = False self.stack_status = None plan = self.build_action._generate_plan() self.step = plan.steps[0] self.step.stack = self.stack def patch_object(*args, **kwargs): m = mock.patch.object(*args, **kwargs) self.addCleanup(m.stop) m.start() def get_stack(name, *args, **kwargs): if name != self.stack.name or not self.stack_status: raise StackDoesNotExist(name) return { 'StackName': self.stack.name, 'StackStatus': self.stack_status, 'Outputs': [], 'Tags': [] } def get_events(name, *args, **kwargs): return [{ 'ResourceStatus': 'ROLLBACK_IN_PROGRESS', 'ResourceStatusReason': 'CFN fail' }] patch_object(self.provider, 'get_stack', side_effect=get_stack) patch_object(self.provider, 'update_stack') patch_object(self.provider, 'create_stack') patch_object(self.provider, 'destroy_stack') patch_object(self.provider, 'get_events', side_effect=get_events) patch_object(self.build_action, "s3_stack_push")
def test_ensure_cfn_forbidden(self): provider = Provider("us-west-1") action = BaseAction(context=mock_context("mynamespace"), provider=provider) stubber = Stubber(action.s3_conn) stubber.add_client_error( "head_bucket", service_error_code="AccessDenied", service_message="Forbidden", http_status_code=403, ) with stubber: with self.assertRaises(botocore.exceptions.ClientError): action.ensure_cfn_bucket()
def test_stack_template_url(self): test_cases = ( ("us-east-1", "s3.amazonaws.com"), ("us-west-1", "s3-us-west-1.amazonaws.com"), ("eu-west-1", "s3-eu-west-1.amazonaws.com"), ("sa-east-1", "s3-sa-east-1.amazonaws.com"), ) context = mock_context("mynamespace") blueprint = TestBlueprint(name="myblueprint", context=context) for region, endpoint in test_cases: provider = Provider(region) action = BaseAction(context=context, provider=provider) self.assertEqual( action.stack_template_url(blueprint), "https://%s/%s/%s-%s.json" % (endpoint, "stacker-mynamespace", "myblueprint", MOCK_VERSION))
def test_ensure_cfn_bucket_doesnt_exist_us_east(self): provider = Provider("us-east-1") action = BaseAction(context=mock_context("mynamespace"), provider=provider) stubber = Stubber(action.s3_conn) stubber.add_client_error( "head_bucket", service_error_code="NoSuchBucket", service_message="Not Found", http_status_code=404, ) stubber.add_response("create_bucket", service_response={}, expected_params={ "Bucket": ANY, }) with stubber: action.ensure_cfn_bucket()
def test_stack_template_url(self): context = mock_context("mynamespace") blueprint = TestBlueprint(name="myblueprint", context=context) region = "us-east-1" endpoint = "https://example.com" session = get_session(region) provider = Provider(session) action = BaseAction(context=context, provider_builder=MockProviderBuilder( provider, region=region)) with mock.patch('stacker.actions.base.get_s3_endpoint', autospec=True, return_value=endpoint): self.assertEqual( action.stack_template_url(blueprint), "%s/%s/stack_templates/%s/%s-%s.json" % (endpoint, "stacker-mynamespace", "mynamespace-myblueprint", "myblueprint", MOCK_VERSION))
def test_ensure_cfn_bucket_doesnt_exist_us_west(self): session = get_session("us-west-1") provider = Provider(session) action = BaseAction(context=mock_context("mynamespace"), provider_builder=MockProviderBuilder( provider, region="us-west-1")) stubber = Stubber(action.s3_conn) stubber.add_client_error( "head_bucket", service_error_code="NoSuchBucket", service_message="Not Found", http_status_code=404, ) stubber.add_response("create_bucket", service_response={}, expected_params={ "Bucket": ANY, "CreateBucketConfiguration": { "LocationConstraint": "us-west-1", } }) with stubber: action.ensure_cfn_bucket()
def test_successful_init(self): replacements = True p = Provider(self.session, interactive=True, replacements_only=replacements) self.assertEqual(p.replacements_only, replacements)
class TestProviderInteractiveMode(unittest.TestCase): def setUp(self): region = "us-east-1" self.session = get_session(region=region) self.provider = Provider(self.session, interactive=True, recreate_failed=True) self.stubber = Stubber(self.provider.cloudformation) def test_successful_init(self): replacements = True p = Provider(self.session, interactive=True, replacements_only=replacements) self.assertEqual(p.replacements_only, replacements) @patch("stacker.providers.aws.default.ask_for_approval") def test_update_stack_execute_success_no_stack_policy( self, patched_approval): stack_name = "my-fake-stack" self.stubber.add_response("create_change_set", { 'Id': 'CHANGESETID', 'StackId': 'STACKID' }) changes = [] changes.append(generate_change()) self.stubber.add_response( "describe_change_set", generate_change_set_response( status="CREATE_COMPLETE", execution_status="AVAILABLE", changes=changes, )) self.stubber.add_response("execute_change_set", {}) with self.stubber: self.provider.update_stack( fqn=stack_name, template=Template(url="http://fake.template.url.com/"), old_parameters=[], parameters=[], tags=[]) patched_approval.assert_called_with(full_changeset=changes, params_diff=[], include_verbose=True, fqn=stack_name) self.assertEqual(patched_approval.call_count, 1) @patch("stacker.providers.aws.default.ask_for_approval") def test_update_stack_execute_success_with_stack_policy( self, patched_approval): stack_name = "my-fake-stack" self.stubber.add_response("create_change_set", { 'Id': 'CHANGESETID', 'StackId': 'STACKID' }) changes = [] changes.append(generate_change()) self.stubber.add_response( "describe_change_set", generate_change_set_response( status="CREATE_COMPLETE", execution_status="AVAILABLE", changes=changes, )) self.stubber.add_response("set_stack_policy", {}) self.stubber.add_response("execute_change_set", {}) with self.stubber: self.provider.update_stack( fqn=stack_name, template=Template(url="http://fake.template.url.com/"), old_parameters=[], parameters=[], tags=[], stack_policy=Template(body="{}"), ) patched_approval.assert_called_with(full_changeset=changes, params_diff=[], include_verbose=True, fqn=stack_name) self.assertEqual(patched_approval.call_count, 1) def test_select_update_method(self): for i in [[{ 'force_interactive': False, 'force_change_set': False }, self.provider.interactive_update_stack], [{ 'force_interactive': True, 'force_change_set': False }, self.provider.interactive_update_stack], [{ 'force_interactive': False, 'force_change_set': True }, self.provider.interactive_update_stack], [{ 'force_interactive': True, 'force_change_set': True }, self.provider.interactive_update_stack]]: self.assertEquals(self.provider.select_update_method(**i[0]), i[1]) @patch('stacker.providers.aws.default.output_full_changeset') @patch('stacker.providers.aws.default.output_summary') def test_get_stack_changes_interactive(self, mock_output_summary, mock_output_full_cs): stack_name = "MockStack" mock_stack = generate_stack_object(stack_name) self.stubber.add_response( 'describe_stacks', {'Stacks': [generate_describe_stacks_stack(stack_name)]}) self.stubber.add_response('get_template', generate_get_template('cfn_template.yaml')) self.stubber.add_response("create_change_set", { 'Id': 'CHANGESETID', 'StackId': stack_name }) changes = [] changes.append(generate_change()) self.stubber.add_response( "describe_change_set", generate_change_set_response( status="CREATE_COMPLETE", execution_status="AVAILABLE", changes=changes, )) self.stubber.add_response("delete_change_set", {}) self.stubber.add_response( 'describe_stacks', {'Stacks': [generate_describe_stacks_stack(stack_name)]}) with self.stubber: self.provider.get_stack_changes( stack=mock_stack, template=Template(url="http://fake.template.url.com/"), parameters=[], tags=[]) mock_output_summary.assert_called_with(stack_name, 'changes', changes, [], replacements_only=False) mock_output_full_cs.assert_called_with(full_changeset=changes, params_diff=[], fqn=stack_name)
class TestProviderDefaultMode(unittest.TestCase): def setUp(self): region = "us-east-1" self.session = get_session(region=region) self.provider = Provider(self.session, region=region, recreate_failed=False) self.stubber = Stubber(self.provider.cloudformation) def test_get_stack_stack_does_not_exist(self): stack_name = "MockStack" self.stubber.add_client_error( "describe_stacks", service_error_code="ValidationError", service_message="Stack with id %s does not exist" % stack_name, expected_params={"StackName": stack_name}) with self.assertRaises(exceptions.StackDoesNotExist): with self.stubber: self.provider.get_stack(stack_name) def test_get_stack_stack_exists(self): stack_name = "MockStack" stack_response = { "Stacks": [generate_describe_stacks_stack(stack_name)] } self.stubber.add_response("describe_stacks", stack_response, expected_params={"StackName": stack_name}) with self.stubber: response = self.provider.get_stack(stack_name) self.assertEqual(response["StackName"], stack_name) def test_select_update_method(self): for i in [[{ 'force_interactive': True, 'force_change_set': False }, self.provider.interactive_update_stack], [{ 'force_interactive': False, 'force_change_set': False }, self.provider.default_update_stack], [{ 'force_interactive': False, 'force_change_set': True }, self.provider.noninteractive_changeset_update], [{ 'force_interactive': True, 'force_change_set': True }, self.provider.interactive_update_stack]]: self.assertEquals(self.provider.select_update_method(**i[0]), i[1]) def test_prepare_stack_for_update_completed(self): stack_name = "MockStack" stack = generate_describe_stacks_stack(stack_name, stack_status="UPDATE_COMPLETE") with self.stubber: self.assertTrue(self.provider.prepare_stack_for_update(stack, [])) def test_prepare_stack_for_update_in_progress(self): stack_name = "MockStack" stack = generate_describe_stacks_stack( stack_name, stack_status="UPDATE_IN_PROGRESS") with self.assertRaises(exceptions.StackUpdateBadStatus) as raised: with self.stubber: self.provider.prepare_stack_for_update(stack, []) self.assertIn('in-progress', str(raised.exception)) def test_prepare_stack_for_update_non_recreatable(self): stack_name = "MockStack" stack = generate_describe_stacks_stack( stack_name, stack_status="REVIEW_IN_PROGRESS") with self.assertRaises(exceptions.StackUpdateBadStatus) as raised: with self.stubber: self.provider.prepare_stack_for_update(stack, []) self.assertIn('Unsupported state', str(raised.exception)) def test_prepare_stack_for_update_disallowed(self): stack_name = "MockStack" stack = generate_describe_stacks_stack( stack_name, stack_status="ROLLBACK_COMPLETE") with self.assertRaises(exceptions.StackUpdateBadStatus) as raised: with self.stubber: self.provider.prepare_stack_for_update(stack, []) self.assertIn('re-creation is disabled', str(raised.exception)) # Ensure we point out to the user how to enable re-creation self.assertIn('--recreate-failed', str(raised.exception)) def test_prepare_stack_for_update_bad_tags(self): stack_name = "MockStack" stack = generate_describe_stacks_stack( stack_name, stack_status="ROLLBACK_COMPLETE") self.provider.recreate_failed = True with self.assertRaises(exceptions.StackUpdateBadStatus) as raised: with self.stubber: self.provider.prepare_stack_for_update(stack, tags=[{ 'Key': 'stacker_namespace', 'Value': 'test' }]) self.assertIn('tags differ', str(raised.exception).lower()) def test_prepare_stack_for_update_recreate(self): stack_name = "MockStack" stack = generate_describe_stacks_stack( stack_name, stack_status="ROLLBACK_COMPLETE") self.stubber.add_response("delete_stack", {}, expected_params={"StackName": stack_name}) self.provider.recreate_failed = True with self.stubber: self.assertFalse(self.provider.prepare_stack_for_update(stack, [])) def test_noninteractive_changeset_update_no_stack_policy(self): stack_name = "MockStack" self.stubber.add_response("create_change_set", { 'Id': 'CHANGESETID', 'StackId': 'STACKID' }) changes = [] changes.append(generate_change()) self.stubber.add_response( "describe_change_set", generate_change_set_response( status="CREATE_COMPLETE", execution_status="AVAILABLE", changes=changes, )) self.stubber.add_response("execute_change_set", {}) with self.stubber: self.provider.noninteractive_changeset_update( fqn=stack_name, template=Template(url="http://fake.template.url.com/"), old_parameters=[], parameters=[], stack_policy=None, tags=[], ) def test_noninteractive_changeset_update_with_stack_policy(self): stack_name = "MockStack" self.stubber.add_response("create_change_set", { 'Id': 'CHANGESETID', 'StackId': 'STACKID' }) changes = [] changes.append(generate_change()) self.stubber.add_response( "describe_change_set", generate_change_set_response( status="CREATE_COMPLETE", execution_status="AVAILABLE", changes=changes, )) self.stubber.add_response("set_stack_policy", {}) self.stubber.add_response("execute_change_set", {}) with self.stubber: self.provider.noninteractive_changeset_update( fqn=stack_name, template=Template(url="http://fake.template.url.com/"), old_parameters=[], parameters=[], stack_policy=Template(body="{}"), tags=[], ) @patch('stacker.providers.aws.default.output_full_changeset') def test_get_stack_changes_update(self, mock_output_full_cs): stack_name = "MockStack" mock_stack = generate_stack_object(stack_name) self.stubber.add_response( 'describe_stacks', {'Stacks': [generate_describe_stacks_stack(stack_name)]}) self.stubber.add_response('get_template', generate_get_template('cfn_template.yaml')) self.stubber.add_response("create_change_set", { 'Id': 'CHANGESETID', 'StackId': stack_name }) changes = [] changes.append(generate_change()) self.stubber.add_response( "describe_change_set", generate_change_set_response( status="CREATE_COMPLETE", execution_status="AVAILABLE", changes=changes, )) self.stubber.add_response("delete_change_set", {}) self.stubber.add_response( 'describe_stacks', {'Stacks': [generate_describe_stacks_stack(stack_name)]}) with self.stubber: result = self.provider.get_stack_changes( stack=mock_stack, template=Template(url="http://fake.template.url.com/"), parameters=[], tags=[]) mock_output_full_cs.assert_called_with(full_changeset=changes, params_diff=[], fqn=stack_name, answer='y') expected_outputs = { 'FakeOutput': '<inferred-change: MockStack.FakeOutput={}>'.format( str({"Ref": "FakeResource"})) } self.assertEqual(self.provider.get_outputs(stack_name), expected_outputs) self.assertEqual(result, expected_outputs) @patch('stacker.providers.aws.default.output_full_changeset') def test_get_stack_changes_create(self, mock_output_full_cs): stack_name = "MockStack" mock_stack = generate_stack_object(stack_name) self.stubber.add_response( 'describe_stacks', { 'Stacks': [ generate_describe_stacks_stack( stack_name, stack_status='REVIEW_IN_PROGRESS') ] }) self.stubber.add_response("create_change_set", { 'Id': 'CHANGESETID', 'StackId': stack_name }) changes = [] changes.append(generate_change()) self.stubber.add_response( "describe_change_set", generate_change_set_response( status="CREATE_COMPLETE", execution_status="AVAILABLE", changes=changes, )) self.stubber.add_response("delete_change_set", {}) self.stubber.add_response( 'describe_stacks', { 'Stacks': [ generate_describe_stacks_stack( stack_name, stack_status='REVIEW_IN_PROGRESS') ] }) self.stubber.add_response( 'describe_stacks', { 'Stacks': [ generate_describe_stacks_stack( stack_name, stack_status='REVIEW_IN_PROGRESS') ] }) self.stubber.add_response("delete_stack", {}) with self.stubber: self.provider.get_stack_changes( stack=mock_stack, template=Template(url="http://fake.template.url.com/"), parameters=[], tags=[]) mock_output_full_cs.assert_called_with(full_changeset=changes, params_diff=[], fqn=stack_name, answer='y') def test_tail_stack_retry_on_missing_stack(self): stack_name = "SlowToCreateStack" stack = MagicMock(spec=Stack) stack.fqn = "my-namespace-{}".format(stack_name) default.TAIL_RETRY_SLEEP = .01 # Ensure the stack never appears before we run out of retries for i in range(MAX_TAIL_RETRIES + 5): self.stubber.add_client_error( "describe_stack_events", service_error_code="ValidationError", service_message="Stack [{}] does not exist".format(stack_name), http_status_code=400, response_meta={"attempt": i + 1}, ) with self.stubber: try: self.provider.tail_stack(stack, threading.Event()) except ClientError as exc: self.assertEqual(exc.response["ResponseMetadata"]["attempt"], MAX_TAIL_RETRIES) def test_tail_stack_retry_on_missing_stack_eventual_success(self): stack_name = "SlowToCreateStack" stack = MagicMock(spec=Stack) stack.fqn = "my-namespace-{}".format(stack_name) default.TAIL_RETRY_SLEEP = .01 default.GET_EVENTS_SLEEP = .01 rcvd_events = [] def mock_log_func(e): rcvd_events.append(e) def valid_event_response(stack, event_id): return { "StackEvents": [ { "StackId": stack.fqn + "12345", "EventId": event_id, "StackName": stack.fqn, "Timestamp": datetime.now() }, ] } # Ensure the stack never appears before we run out of retries for i in range(3): self.stubber.add_client_error( "describe_stack_events", service_error_code="ValidationError", service_message="Stack [{}] does not exist".format(stack_name), http_status_code=400, response_meta={"attempt": i + 1}, ) self.stubber.add_response("describe_stack_events", valid_event_response(stack, "InitialEvents")) self.stubber.add_response("describe_stack_events", valid_event_response(stack, "Event1")) with self.stubber: try: self.provider.tail_stack(stack, threading.Event(), log_func=mock_log_func) except UnStubbedResponseError: # Eventually we run out of responses - could not happen in # regular execution # normally this would just be dealt with when the threads were # shutdown, but doing so here is a little difficult because # we can't control the `tail_stack` loop pass self.assertEqual(rcvd_events[0]["EventId"], "Event1")
def setUp(self): region = "us-east-1" self.session = get_session(region=region) self.provider = Provider( self.session, interactive=True, recreate_failed=True) self.stubber = Stubber(self.provider.cloudformation)
class TestProviderInteractiveMode(unittest.TestCase): def setUp(self): region = "us-east-1" self.session = get_session(region=region) self.provider = Provider( self.session, interactive=True, recreate_failed=True) self.stubber = Stubber(self.provider.cloudformation) def test_successful_init(self): replacements = True p = Provider(self.session, interactive=True, replacements_only=replacements) self.assertEqual(p.replacements_only, replacements) @patch("stacker.providers.aws.default.ask_for_approval") def test_update_stack_execute_success_no_stack_policy(self, patched_approval): stack_name = "my-fake-stack" self.stubber.add_response( "create_change_set", {'Id': 'CHANGESETID', 'StackId': 'STACKID'} ) changes = [] changes.append(generate_change()) self.stubber.add_response( "describe_change_set", generate_change_set_response( status="CREATE_COMPLETE", execution_status="AVAILABLE", changes=changes, ) ) self.stubber.add_response("execute_change_set", {}) with self.stubber: self.provider.update_stack( fqn=stack_name, template=Template(url="http://fake.template.url.com/"), old_parameters=[], parameters=[], tags=[] ) patched_approval.assert_called_with(full_changeset=changes, params_diff=[], include_verbose=True) self.assertEqual(patched_approval.call_count, 1) @patch("stacker.providers.aws.default.ask_for_approval") def test_update_stack_execute_success_with_stack_policy(self, patched_approval): stack_name = "my-fake-stack" self.stubber.add_response( "create_change_set", {'Id': 'CHANGESETID', 'StackId': 'STACKID'} ) changes = [] changes.append(generate_change()) self.stubber.add_response( "describe_change_set", generate_change_set_response( status="CREATE_COMPLETE", execution_status="AVAILABLE", changes=changes, ) ) self.stubber.add_response("set_stack_policy", {}) self.stubber.add_response("execute_change_set", {}) with self.stubber: self.provider.update_stack( fqn=stack_name, template=Template(url="http://fake.template.url.com/"), old_parameters=[], parameters=[], tags=[], stack_policy=Template(body="{}"), ) patched_approval.assert_called_with(full_changeset=changes, params_diff=[], include_verbose=True) self.assertEqual(patched_approval.call_count, 1) def test_select_update_method(self): for i in [[{'force_interactive': False, 'force_change_set': False}, self.provider.interactive_update_stack], [{'force_interactive': True, 'force_change_set': False}, self.provider.interactive_update_stack], [{'force_interactive': False, 'force_change_set': True}, self.provider.interactive_update_stack], [{'force_interactive': True, 'force_change_set': True}, self.provider.interactive_update_stack]]: self.assertEquals( self.provider.select_update_method(**i[0]), i[1] )
class TestProviderDefaultMode(unittest.TestCase): def setUp(self): region = "us-east-1" self.session = get_session(region=region) self.provider = Provider( self.session, region=region, recreate_failed=False) self.stubber = Stubber(self.provider.cloudformation) def test_get_stack_stack_does_not_exist(self): stack_name = "MockStack" self.stubber.add_client_error( "describe_stacks", service_error_code="ValidationError", service_message="Stack with id %s does not exist" % stack_name, expected_params={"StackName": stack_name} ) with self.assertRaises(exceptions.StackDoesNotExist): with self.stubber: self.provider.get_stack(stack_name) def test_get_stack_stack_exists(self): stack_name = "MockStack" stack_response = { "Stacks": [generate_describe_stacks_stack(stack_name)] } self.stubber.add_response( "describe_stacks", stack_response, expected_params={"StackName": stack_name} ) with self.stubber: response = self.provider.get_stack(stack_name) self.assertEqual(response["StackName"], stack_name) def test_select_update_method(self): for i in [[{'force_interactive': True, 'force_change_set': False}, self.provider.interactive_update_stack], [{'force_interactive': False, 'force_change_set': False}, self.provider.default_update_stack], [{'force_interactive': False, 'force_change_set': True}, self.provider.noninteractive_changeset_update], [{'force_interactive': True, 'force_change_set': True}, self.provider.interactive_update_stack]]: self.assertEquals( self.provider.select_update_method(**i[0]), i[1] ) def test_prepare_stack_for_update_completed(self): stack_name = "MockStack" stack = generate_describe_stacks_stack( stack_name, stack_status="UPDATE_COMPLETE") with self.stubber: self.assertTrue( self.provider.prepare_stack_for_update(stack, [])) def test_prepare_stack_for_update_in_progress(self): stack_name = "MockStack" stack = generate_describe_stacks_stack( stack_name, stack_status="UPDATE_IN_PROGRESS") with self.assertRaises(exceptions.StackUpdateBadStatus) as raised: with self.stubber: self.provider.prepare_stack_for_update(stack, []) self.assertIn('in-progress', str(raised.exception)) def test_prepare_stack_for_update_non_recreatable(self): stack_name = "MockStack" stack = generate_describe_stacks_stack( stack_name, stack_status="REVIEW_IN_PROGRESS") with self.assertRaises(exceptions.StackUpdateBadStatus) as raised: with self.stubber: self.provider.prepare_stack_for_update(stack, []) self.assertIn('Unsupported state', str(raised.exception)) def test_prepare_stack_for_update_disallowed(self): stack_name = "MockStack" stack = generate_describe_stacks_stack( stack_name, stack_status="ROLLBACK_COMPLETE") with self.assertRaises(exceptions.StackUpdateBadStatus) as raised: with self.stubber: self.provider.prepare_stack_for_update(stack, []) self.assertIn('re-creation is disabled', str(raised.exception)) # Ensure we point out to the user how to enable re-creation self.assertIn('--recreate-failed', str(raised.exception)) def test_prepare_stack_for_update_bad_tags(self): stack_name = "MockStack" stack = generate_describe_stacks_stack( stack_name, stack_status="ROLLBACK_COMPLETE") self.provider.recreate_failed = True with self.assertRaises(exceptions.StackUpdateBadStatus) as raised: with self.stubber: self.provider.prepare_stack_for_update( stack, tags=[{'Key': 'stacker_namespace', 'Value': 'test'}]) self.assertIn('tags differ', str(raised.exception).lower()) def test_prepare_stack_for_update_recreate(self): stack_name = "MockStack" stack = generate_describe_stacks_stack( stack_name, stack_status="ROLLBACK_COMPLETE") self.stubber.add_response( "delete_stack", {}, expected_params={"StackName": stack_name} ) self.provider.recreate_failed = True with self.stubber: self.assertFalse( self.provider.prepare_stack_for_update(stack, [])) def test_noninteractive_changeset_update_no_stack_policy(self): stack_name = "MockStack" self.stubber.add_response( "create_change_set", {'Id': 'CHANGESETID', 'StackId': 'STACKID'} ) changes = [] changes.append(generate_change()) self.stubber.add_response( "describe_change_set", generate_change_set_response( status="CREATE_COMPLETE", execution_status="AVAILABLE", changes=changes, ) ) self.stubber.add_response("execute_change_set", {}) with self.stubber: self.provider.noninteractive_changeset_update( fqn=stack_name, template=Template(url="http://fake.template.url.com/"), old_parameters=[], parameters=[], stack_policy=None, tags=[], ) def test_noninteractive_changeset_update_with_stack_policy(self): stack_name = "MockStack" self.stubber.add_response( "create_change_set", {'Id': 'CHANGESETID', 'StackId': 'STACKID'} ) changes = [] changes.append(generate_change()) self.stubber.add_response( "describe_change_set", generate_change_set_response( status="CREATE_COMPLETE", execution_status="AVAILABLE", changes=changes, ) ) self.stubber.add_response("set_stack_policy", {}) self.stubber.add_response("execute_change_set", {}) with self.stubber: self.provider.noninteractive_changeset_update( fqn=stack_name, template=Template(url="http://fake.template.url.com/"), old_parameters=[], parameters=[], stack_policy=Template(body="{}"), tags=[], ) def test_tail_stack_retry_on_missing_stack(self): stack_name = "SlowToCreateStack" stack = MagicMock(spec=Stack) stack.fqn = "my-namespace-{}".format(stack_name) default.TAIL_RETRY_SLEEP = .01 # Ensure the stack never appears before we run out of retries for i in range(MAX_TAIL_RETRIES + 5): self.stubber.add_client_error( "describe_stack_events", service_error_code="ValidationError", service_message="Stack [{}] does not exist".format(stack_name), http_status_code=400, response_meta={"attempt": i + 1}, ) with self.stubber: try: self.provider.tail_stack(stack, threading.Event()) except ClientError as exc: self.assertEqual( exc.response["ResponseMetadata"]["attempt"], MAX_TAIL_RETRIES ) def test_tail_stack_retry_on_missing_stack_eventual_success(self): stack_name = "SlowToCreateStack" stack = MagicMock(spec=Stack) stack.fqn = "my-namespace-{}".format(stack_name) default.TAIL_RETRY_SLEEP = .01 default.GET_EVENTS_SLEEP = .01 rcvd_events = [] def mock_log_func(e): rcvd_events.append(e) def valid_event_response(stack, event_id): return { "StackEvents": [ { "StackId": stack.fqn + "12345", "EventId": event_id, "StackName": stack.fqn, "Timestamp": datetime.now() }, ] } # Ensure the stack never appears before we run out of retries for i in range(3): self.stubber.add_client_error( "describe_stack_events", service_error_code="ValidationError", service_message="Stack [{}] does not exist".format(stack_name), http_status_code=400, response_meta={"attempt": i + 1}, ) self.stubber.add_response( "describe_stack_events", valid_event_response(stack, "InitialEvents") ) self.stubber.add_response( "describe_stack_events", valid_event_response(stack, "Event1") ) with self.stubber: try: self.provider.tail_stack(stack, threading.Event(), log_func=mock_log_func) except UnStubbedResponseError: # Eventually we run out of responses - could not happen in # regular execution # normally this would just be dealt with when the threads were # shutdown, but doing so here is a little difficult because # we can't control the `tail_stack` loop pass self.assertEqual(rcvd_events[0]["EventId"], "Event1")