Mitch Garnaat

Adding some unit tests.

...@@ -60,6 +60,7 @@ class Stack(object): ...@@ -60,6 +60,7 @@ class Stack(object):
60 try: 60 try:
61 resources = self._cfn.list_stack_resources( 61 resources = self._cfn.list_stack_resources(
62 StackName=self.name) 62 StackName=self.name)
63 + LOG.debug(resources)
63 except Exception: 64 except Exception:
64 LOG.exception('Unable to find role ARN: %s', role_name) 65 LOG.exception('Unable to find role ARN: %s', role_name)
65 for resource in resources['StackResourceSummaries']: 66 for resource in resources['StackResourceSummaries']:
...@@ -88,6 +89,7 @@ class Stack(object): ...@@ -88,6 +89,7 @@ class Stack(object):
88 while not done: 89 while not done:
89 time.sleep(1) 90 time.sleep(1)
90 response = self._cfn.describe_stacks(StackName=self.name) 91 response = self._cfn.describe_stacks(StackName=self.name)
92 + LOG.debug(response)
91 status = response['Stacks'][0]['StackStatus'] 93 status = response['Stacks'][0]['StackStatus']
92 LOG.debug('Stack status is: %s', status) 94 LOG.debug('Stack status is: %s', status)
93 if status in self.completed_states: 95 if status in self.completed_states:
...@@ -100,9 +102,10 @@ class Stack(object): ...@@ -100,9 +102,10 @@ class Stack(object):
100 LOG.debug('create_stack: stack_name=%s', self.name) 102 LOG.debug('create_stack: stack_name=%s', self.name)
101 template_body = open(self.template_path).read() 103 template_body = open(self.template_path).read()
102 try: 104 try:
103 - self._cfn.create_stack( 105 + response = self._cfn.create_stack(
104 StackName=self.name, TemplateBody=template_body, 106 StackName=self.name, TemplateBody=template_body,
105 Capabilities=['CAPABILITY_IAM']) 107 Capabilities=['CAPABILITY_IAM'])
108 + LOG.debug(response)
106 except Exception: 109 except Exception:
107 LOG.exception('Unable to create stack') 110 LOG.exception('Unable to create stack')
108 self.wait() 111 self.wait()
...@@ -111,9 +114,10 @@ class Stack(object): ...@@ -111,9 +114,10 @@ class Stack(object):
111 LOG.debug('create_stack: stack_name=%s', self.name) 114 LOG.debug('create_stack: stack_name=%s', self.name)
112 template_body = open(self.template_path).read() 115 template_body = open(self.template_path).read()
113 try: 116 try:
114 - self._cfn.update_stack( 117 + response = self._cfn.update_stack(
115 StackName=self.name, TemplateBody=template_body, 118 StackName=self.name, TemplateBody=template_body,
116 Capabilities=['CAPABILITY_IAM']) 119 Capabilities=['CAPABILITY_IAM'])
120 + LOG.debug(response)
117 except Exception, e: 121 except Exception, e:
118 if 'ValidationError' in str(e): 122 if 'ValidationError' in str(e):
119 LOG.info('No Updates Required') 123 LOG.info('No Updates Required')
...@@ -124,6 +128,7 @@ class Stack(object): ...@@ -124,6 +128,7 @@ class Stack(object):
124 def delete(self): 128 def delete(self):
125 LOG.debug('delete_stack: stack_name=%s', self.name) 129 LOG.debug('delete_stack: stack_name=%s', self.name)
126 try: 130 try:
127 - self._cfn.delete_stack(StackName=self.name) 131 + response = self._cfn.delete_stack(StackName=self.name)
132 + LOG.debug(response)
128 except Exception: 133 except Exception:
129 LOG.exception('Unable to delete stack: %s', self.name) 134 LOG.exception('Unable to delete stack: %s', self.name)
......
1 botocore==0.80.0 1 botocore==0.80.0
2 click==3.3 2 click==3.3
3 PyYAML>=3.11 3 PyYAML>=3.11
4 +mock>=1.0.1
4 nose==1.3.1 5 nose==1.3.1
5 tox==1.7.1 6 tox==1.7.1
......
1 +# Copyright (c) 2014 Mitch Garnaat http://garnaat.org/
2 +#
3 +# Licensed under the Apache License, Version 2.0 (the "License"). You
4 +# may not use this file except in compliance with the License. A copy of
5 +# the License is located at
6 +#
7 +# http://aws.amazon.com/apache2.0/
8 +#
9 +# or in the "license" file accompanying this file. This file is
10 +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11 +# ANY KIND, either express or implied. See the License for the specific
12 +# language governing permissions and limitations under the License.
1 +# Copyright (c) 2014 Mitch Garnaat http://garnaat.org/
2 +#
3 +# Licensed under the Apache License, Version 2.0 (the "License"). You
4 +# may not use this file except in compliance with the License. A copy of
5 +# the License is located at
6 +#
7 +# http://aws.amazon.com/apache2.0/
8 +#
9 +# or in the "license" file accompanying this file. This file is
10 +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11 +# ANY KIND, either express or implied. See the License for the specific
12 +# language governing permissions and limitations under the License.
File mode changed
1 +import mock
2 +
3 +import tests.unit.responses as responses
4 +
5 +
6 +class MockAWS(object):
7 +
8 + def __init__(self, profile=None, region=None):
9 + pass
10 +
11 + def create_client(self, client_name):
12 + client = None
13 + if client_name == 'logs':
14 + client = mock.Mock()
15 + choices = responses.logs_describe_log_streams
16 + client.describe_log_streams = mock.Mock(
17 + side_effect=choices)
18 + choices = responses.logs_get_log_events
19 + client.get_log_events = mock.Mock(
20 + side_effect=choices)
21 + if client_name == 'cloudformation':
22 + client = mock.Mock()
23 + choices = responses.cfn_list_stack_resources
24 + client.list_stack_resources = mock.Mock(
25 + side_effect=choices)
26 + choices = responses.cfn_describe_stacks
27 + client.describe_stacks = mock.Mock(
28 + side_effect=choices)
29 + choices = responses.cfn_create_stack
30 + client.create_stack = mock.Mock(
31 + side_effect=choices)
32 + choices = responses.cfn_delete_stack
33 + client.delete_stack = mock.Mock(
34 + side_effect=choices)
35 + if client_name == 'iam':
36 + client = mock.Mock()
37 + choices = responses.iam_get_role
38 + client.get_role = mock.Mock(
39 + side_effect=choices)
40 + return client
41 +
42 +
43 +def get_aws(context):
44 + return MockAWS()
1 +import datetime
2 +from dateutil.tz import tzutc
3 +
4 +cfn_list_stack_resources = [{'ResponseMetadata': {'HTTPStatusCode': 200, 'RequestId': 'dd35f0ef-9699-11e4-ba38-c355c9515dbc'}, u'StackResourceSummaries': [{u'ResourceStatus': 'CREATE_COMPLETE', u'ResourceType': 'AWS::IAM::Role', u'ResourceStatusReason': None, u'LastUpdatedTimestamp': datetime.datetime(2015, 1, 6, 17, 37, 54, 861000, tzinfo=tzutc()), u'PhysicalResourceId': 'TestKinesis-InvokeRole-IF6VUXY9MBJN', u'LogicalResourceId': 'InvokeRole'}, {u'ResourceStatus': 'CREATE_COMPLETE', u'ResourceType': 'AWS::IAM::Role', u'ResourceStatusReason': None, u'LastUpdatedTimestamp': datetime.datetime(2015, 1, 6, 17, 37, 55, 18000, tzinfo=tzutc()), u'PhysicalResourceId': 'TestKinesis-ExecRole-567SAV6TZOET', u'LogicalResourceId': 'ExecRole'}, {u'ResourceStatus': 'CREATE_COMPLETE', u'ResourceType': 'AWS::IAM::Policy', u'ResourceStatusReason': None, u'LastUpdatedTimestamp': datetime.datetime(2015, 1, 6, 17, 37, 58, 120000, tzinfo=tzutc()), u'PhysicalResourceId': 'TestK-Invo-OMW5SDLQM8FM', u'LogicalResourceId': 'InvokeRolePolicies'}, {u'ResourceStatus': 'CREATE_COMPLETE', u'ResourceType': 'AWS::IAM::Policy', u'ResourceStatusReason': None, u'LastUpdatedTimestamp': datetime.datetime(2015, 1, 6, 17, 37, 58, 454000, tzinfo=tzutc()), u'PhysicalResourceId': 'TestK-Exec-APWRVKTBPPPT', u'LogicalResourceId': 'ExecRolePolicies'}]}]
5 +
6 +iam_get_role = [{u'Role': {u'AssumeRolePolicyDocument': {u'Version': u'2012-10-17', u'Statement': [{u'Action': u'sts:AssumeRole', u'Principal': {u'Service': u's3.amazonaws.com'}, u'Effect': u'Allow', u'Condition': {u'ArnLike': {u'sts:ExternalId': u'arn:aws:s3:::*'}}, u'Sid': u''}, {u'Action': u'sts:AssumeRole', u'Principal': {u'Service': u'lambda.amazonaws.com'}, u'Effect': u'Allow', u'Sid': u''}]}, u'RoleId': 'AROAIEVJHUJG2I4MG5PSC', u'CreateDate': datetime.datetime(2015, 1, 6, 17, 37, 44, tzinfo=tzutc()), u'RoleName': 'TestKinesis-InvokeRole-IF6VUXY9MBJN', u'Path': '/', u'Arn': 'arn:aws:iam::0123456789012:role/TestKinesis-InvokeRole-FOO'}, 'ResponseMetadata': {'HTTPStatusCode': 200, 'RequestId': 'dd6e8d42-9699-11e4-afe6-d3625e8b365b'}}]
7 +
8 +logs_describe_log_streams = [{u'logStreams': [{u'firstEventTimestamp': 1417042749449, u'lastEventTimestamp': 1417042749547, u'creationTime': 1417042748263, u'uploadSequenceToken': u'49540114640150833041490484409222729829873988799393975922', u'logStreamName': u'1cc48e4e613246b7974094323259d600', u'lastIngestionTime': 1417042750483, u'arn': u'arn:aws:logs:us-east-1:0123456789012:log-group:/aws/lambda/KinesisSample:log-stream:1cc48e4e613246b7974094323259d600', u'storedBytes': 712}, {u'firstEventTimestamp': 1417272406988, u'lastEventTimestamp': 1417272407088, u'creationTime': 1417272405690, u'uploadSequenceToken': u'49540113907504451034164105858363493278561872472363261986', u'logStreamName': u'2782a5ff88824c85a9639480d1ed7bbe', u'lastIngestionTime': 1417272408043, u'arn': u'arn:aws:logs:us-east-1:0123456789012:log-group:/aws/lambda/KinesisSample:log-stream:2782a5ff88824c85a9639480d1ed7bbe', u'storedBytes': 712}, {u'firstEventTimestamp': 1420569035842, u'lastEventTimestamp': 1420569035941, u'creationTime': 1420569034614, u'uploadSequenceToken': u'49540113907883563702539166025438885323514410026454245426', u'logStreamName': u'2d62991a479b4ebf9486176122b72a55', u'lastIngestionTime': 1420569036909, u'arn': u'arn:aws:logs:us-east-1:0123456789012:log-group:/aws/lambda/KinesisSample:log-stream:2d62991a479b4ebf9486176122b72a55', u'storedBytes': 709}, {u'firstEventTimestamp': 1418244027421, u'lastEventTimestamp': 1418244027541, u'creationTime': 1418244026907, u'uploadSequenceToken': u'49540113964795065449189116778452984186276757901477438642', u'logStreamName': u'4f44ffa128d6405591ca83b2b0f9dd2d', u'lastIngestionTime': 1418244028484, u'arn': u'arn:aws:logs:us-east-1:0123456789012:log-group:/aws/lambda/KinesisSample:log-stream:4f44ffa128d6405591ca83b2b0f9dd2d', u'storedBytes': 1010}, {u'firstEventTimestamp': 1418242565524, u'lastEventTimestamp': 1418242565641, u'creationTime': 1418242564196, u'uploadSequenceToken': u'49540113095132904942090446312687285178819573422397343074', u'logStreamName': u'69c5ac87e7e6415985116e8cb44e538e', u'lastIngestionTime': 1418242566558, u'arn': u'arn:aws:logs:us-east-1:0123456789012:log-group:/aws/lambda/KinesisSample:log-stream:69c5ac87e7e6415985116e8cb44e538e', u'storedBytes': 713}, {u'firstEventTimestamp': 1417213193378, u'lastEventTimestamp': 1417213193478, u'creationTime': 1417213192095, u'uploadSequenceToken': u'49540113336360065754596187770479764234792559857643841394', u'logStreamName': u'f68e3d87b8a14cdba338f6926f7cf50a', u'lastIngestionTime': 1417213194421, u'arn': u'arn:aws:logs:us-east-1:0123456789012:log-group:/aws/lambda/KinesisSample:log-stream:f68e3d87b8a14cdba338f6926f7cf50a', u'storedBytes': 711}], 'ResponseMetadata': {'HTTPStatusCode': 200, 'RequestId': '2a6d4941-969b-11e4-947f-19d1c72ede7e'}}]
9 +
10 +logs_get_log_events = [{'ResponseMetadata': {'HTTPStatusCode': 200, 'RequestId': '2a7deb71-969b-11e4-914b-8f1f3d7b023d'}, u'nextForwardToken': u'f/31679748107442531967654742688057700554200447759088287749', u'events': [{u'ingestionTime': 1420569036909, u'timestamp': 1420569035842, u'message': u'2015-01-06T18:30:35.841Z\tko2sss03iq7l2pdk\tLoading event\n'}, {u'ingestionTime': 1420569036909, u'timestamp': 1420569035899, u'message': u'START RequestId: 23007242-95d2-11e4-a10e-7b2ab60a7770\n'}, {u'ingestionTime': 1420569036909, u'timestamp': 1420569035940, u'message': u'2015-01-06T18:30:35.940Z\t23007242-95d2-11e4-a10e-7b2ab60a7770\t{\n "Records": [\n {\n "kinesis": {\n "partitionKey": "partitionKey-3",\n "kinesisSchemaVersion": "1.0",\n "data": "SGVsbG8sIHRoaXMgaXMgYSB0ZXN0IDEyMy4=",\n "sequenceNumber": "49545115243490985018280067714973144582180062593244200961"\n },\n "eventSource": "aws:kinesis",\n "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961",\n "invokeIdentityArn": "arn:aws:iam::0123456789012:role/testLEBRole",\n "eventVersion": "1.0",\n "eventName": "aws:kinesis:record",\n "eventSourceARN": "arn:aws:kinesis:us-east-1:35667example:stream/examplestream",\n "awsRegion": "us-east-1"\n }\n ]\n}\n'}, {u'ingestionTime': 1420569036909, u'timestamp': 1420569035940, u'message': u'2015-01-06T18:30:35.940Z\t23007242-95d2-11e4-a10e-7b2ab60a7770\tDecoded payload: Hello, this is a test 123.\n'}, {u'ingestionTime': 1420569036909, u'timestamp': 1420569035941, u'message': u'END RequestId: 23007242-95d2-11e4-a10e-7b2ab60a7770\n'}, {u'ingestionTime': 1420569036909, u'timestamp': 1420569035941, u'message': u'REPORT RequestId: 23007242-95d2-11e4-a10e-7b2ab60a7770\tDuration: 98.51 ms\tBilled Duration: 100 ms \tMemory Size: 128 MB\tMax Memory Used: 26 MB\t\n'}], u'nextBackwardToken': u'b/31679748105234758193000210997045664445208259969996226560'}]
11 +
12 +cfn_describe_stacks = [
13 + {u'Stacks': [{u'StackId': 'arn:aws:cloudformation:us-east-1:084307701560:stack/TestKinesis/7c4ae730-96b8-11e4-94cc-5001dc3ed8d2', u'Description': None, u'Tags': [], u'StackStatusReason': 'User Initiated', u'CreationTime': datetime.datetime(2015, 1, 7, 21, 59, 43, 208000, tzinfo=tzutc()), u'Capabilities': ['CAPABILITY_IAM'], u'StackName': 'TestKinesis', u'NotificationARNs': [], u'StackStatus': 'CREATE_IN_PROGRESS', u'DisableRollback': False}], 'ResponseMetadata': {'HTTPStatusCode': 200, 'RequestId': '7d66debd-96b8-11e4-a647-4f4741ffff69'}},
14 + {u'Stacks': [{u'StackId': 'arn:aws:cloudformation:us-east-1:084307701560:stack/TestKinesis/7c4ae730-96b8-11e4-94cc-5001dc3ed8d2', u'Description': None, u'Tags': [], u'StackStatusReason': 'User Initiated', u'CreationTime': datetime.datetime(2015, 1, 7, 21, 59, 43, 208000, tzinfo=tzutc()), u'Capabilities': ['CAPABILITY_IAM'], u'StackName': 'TestKinesis', u'NotificationARNs': [], u'StackStatus': 'CREATE_IN_PROGRESS', u'DisableRollback': False}], 'ResponseMetadata': {'HTTPStatusCode': 200, 'RequestId': '7e36fff7-96b8-11e4-af44-6350f4f8c2ae'}},
15 + {u'Stacks': [{u'StackId': 'arn:aws:cloudformation:us-east-1:084307701560:stack/TestKinesis/7c4ae730-96b8-11e4-94cc-5001dc3ed8d2', u'Description': None, u'Tags': [], u'StackStatusReason': 'User Initiated', u'CreationTime': datetime.datetime(2015, 1, 7, 21, 59, 43, 208000, tzinfo=tzutc()), u'Capabilities': ['CAPABILITY_IAM'], u'StackName': 'TestKinesis', u'NotificationARNs': [], u'StackStatus': 'CREATE_IN_PROGRESS', u'DisableRollback': False}], 'ResponseMetadata': {'HTTPStatusCode': 200, 'RequestId': '7ef03e10-96b8-11e4-bc86-7f67e11abcfa'}},
16 + {u'Stacks': [{u'StackId': 'arn:aws:cloudformation:us-east-1:084307701560:stack/TestKinesis/7c4ae730-96b8-11e4-94cc-5001dc3ed8d2', u'Description': None, u'Tags': [], u'StackStatusReason': None, u'CreationTime': datetime.datetime(2015, 1, 7, 21, 59, 43, 208000, tzinfo=tzutc()), u'Capabilities': ['CAPABILITY_IAM'], u'StackName': 'TestKinesis', u'NotificationARNs': [], u'StackStatus': 'CREATE_COMPLETE', u'DisableRollback': False}], 'ResponseMetadata': {'HTTPStatusCode': 200, 'RequestId': '8c2bff8e-96b8-11e4-be70-c5ad82c32f2d'}}]
17 +
18 +cfn_create_stack = [{u'StackId': 'arn:aws:cloudformation:us-east-1:084307701560:stack/TestKinesis/7c4ae730-96b8-11e4-94cc-5001dc3ed8d2', 'ResponseMetadata': {'HTTPStatusCode': 200, 'RequestId': '7c2f2260-96b8-11e4-be70-c5ad82c32f2d'}}]
19 +
20 +cfn_delete_stack = [{'ResponseMetadata': {'HTTPStatusCode': 200, 'RequestId': 'f19af5b8-96bc-11e4-860e-11ba752b58a9'}}]
1 +# Copyright (c) 2014 Mitch Garnaat http://garnaat.org/
2 +#
3 +# Licensed under the Apache License, Version 2.0 (the "License"). You
4 +# may not use this file except in compliance with the License. A copy of
5 +# the License is located at
6 +#
7 +# http://aws.amazon.com/apache2.0/
8 +#
9 +# or in the "license" file accompanying this file. This file is
10 +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11 +# ANY KIND, either express or implied. See the License for the specific
12 +# language governing permissions and limitations under the License.
13 +
14 +import unittest
15 +
16 +import mock
17 +
18 +from kappa.log import Log
19 +from mock_aws import get_aws
20 +
21 +
22 +class TestLog(unittest.TestCase):
23 +
24 + def setUp(self):
25 + self.aws_patch = mock.patch('kappa.aws.get_aws', get_aws)
26 + self.mock_aws = self.aws_patch.start()
27 +
28 + def tearDown(self):
29 + self.aws_patch.stop()
30 +
31 + def test_streams(self):
32 + mock_context = mock.Mock()
33 + log = Log(mock_context, 'foo/bar')
34 + streams = log.streams()
35 + self.assertEqual(len(streams), 6)
36 +
37 + def test_tail(self):
38 + mock_context = mock.Mock()
39 + log = Log(mock_context, 'foo/bar')
40 + events = log.tail()
41 + self.assertEqual(len(events), 6)
42 + self.assertEqual(events[0]['ingestionTime'], 1420569036909)
43 + self.assertIn('RequestId: 23007242-95d2-11e4-a10e-7b2ab60a7770',
44 + events[-1]['message'])
1 +# Copyright (c) 2015 Mitch Garnaat http://garnaat.org/
2 +#
3 +# Licensed under the Apache License, Version 2.0 (the "License"). You
4 +# may not use this file except in compliance with the License. A copy of
5 +# the License is located at
6 +#
7 +# http://aws.amazon.com/apache2.0/
8 +#
9 +# or in the "license" file accompanying this file. This file is
10 +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11 +# ANY KIND, either express or implied. See the License for the specific
12 +# language governing permissions and limitations under the License.
13 +
14 +import unittest
15 +import os
16 +
17 +import mock
18 +
19 +from kappa.stack import Stack
20 +from mock_aws import get_aws
21 +
22 +Config = {
23 + 'template': 'roles.cf',
24 + 'stack_name': 'FooBar',
25 + 'exec_role': 'ExecRole',
26 + 'invoke_role': 'InvokeRole'}
27 +
28 +
29 +def path(filename):
30 + return os.path.join(os.path.dirname(__file__), 'data', filename)
31 +
32 +
33 +class TestStack(unittest.TestCase):
34 +
35 + def setUp(self):
36 + self.aws_patch = mock.patch('kappa.aws.get_aws', get_aws)
37 + self.mock_aws = self.aws_patch.start()
38 + Config['template'] = path(Config['template'])
39 +
40 + def tearDown(self):
41 + self.aws_patch.stop()
42 +
43 + def test_properties(self):
44 + mock_context = mock.Mock()
45 + stack = Stack(mock_context, Config)
46 + self.assertEqual(stack.name, Config['stack_name'])
47 + self.assertEqual(stack.template_path, Config['template'])
48 + self.assertEqual(stack.exec_role, Config['exec_role'])
49 + self.assertEqual(stack.invoke_role, Config['invoke_role'])
50 + self.assertEqual(
51 + stack.invoke_role_arn,
52 + 'arn:aws:iam::0123456789012:role/TestKinesis-InvokeRole-FOO')
53 +
54 + def test_exists(self):
55 + mock_context = mock.Mock()
56 + stack = Stack(mock_context, Config)
57 + self.assertTrue(stack.exists())
58 +
59 + def test_create(self):
60 + mock_context = mock.Mock()
61 + stack = Stack(mock_context, Config)
62 + stack.create()
63 +
64 + def test_delete(self):
65 + mock_context = mock.Mock()
66 + stack = Stack(mock_context, Config)
67 + stack.delete()