]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
heat API : Add more unit tests
authorSteven Hardy <shardy@redhat.com>
Mon, 23 Jul 2012 16:43:39 +0000 (17:43 +0100)
committerSteven Hardy <shardy@redhat.com>
Mon, 23 Jul 2012 16:44:25 +0000 (17:44 +0100)
Add more unit tests to improve API test coverage

Change-Id: I5f3a8933a9a09acb6b904ccc76ec20ad210a5974
Signed-off-by: Steven Hardy <shardy@redhat.com>
heat/tests/test_api_v1.py

index 7a2fb6c0d53176ef624d50d4c9a63be21be0db63..6bb0227ffe4869df2d4f72bbaf9f72f5ca5d9f28 100644 (file)
 #    under the License.
 
 
+import sys
+import socket
 import nose
+import mox
+import json
 import unittest
 from nose.plugins.attrib import attr
 
-from heat.common import config
-from heat.openstack.common import cfg
-import heat.common.config
+import httplib
+import json
+import urlparse
+
+from heat.common import context
+from heat.engine import auth
+from heat.openstack.common import rpc
+from heat.common.wsgi import Request
+from heat.api.v1 import exception
 import heat.api.v1.stacks as stacks
 
 
@@ -30,6 +40,42 @@ class StackControllerTest(unittest.TestCase):
     Tests the API class which acts as the WSGI controller,
     the endpoint processing API requests after they are routed
     '''
+    # Utility functions
+    def _create_context(self, user='api_test_user'):
+        ctx = context.get_admin_context()
+        self.m.StubOutWithMock(ctx, 'username')
+        ctx.username = user
+        self.m.StubOutWithMock(auth, 'authenticate')
+        return ctx
+
+    def _dummy_GET_request(self, params={}):
+        # Mangle the params dict into a query string
+        qs = "&".join(["=".join([k, str(params[k])]) for k in params])
+        environ = {'REQUEST_METHOD': 'GET', 'QUERY_STRING': qs}
+        req = Request(environ)
+        req.context = self._create_context()
+        return req
+
+    # The tests
+    def test_format_response(self):
+        response = self.controller._format_response("Foo", "Bar")
+        expected = {'FooResponse': {'FooResult': 'Bar'}}
+        self.assert_(response == expected)
+
+    def test_stackid_addprefix(self):
+
+        # Stub socket.gethostname so it returns "ahostname"
+        self.m.StubOutWithMock(socket, 'gethostname')
+        socket.gethostname().AndReturn("ahostname")
+
+        self.m.ReplayAll()
+
+        response = self.controller._stackid_addprefix({'StackName': 'Foo',
+                                                       'StackId': str(123)})
+        expected = {'StackName': 'Foo',
+                    'StackId': 'ahostname:8000:stack/Foo/123'}
+        self.assert_(response == expected)
+
     def test_params_extract(self):
         p = {'Parameters.member.Foo.ParameterKey': 'foo',
              'Parameters.member.Foo.ParameterValue': 'bar',
@@ -76,15 +122,497 @@ class StackControllerTest(unittest.TestCase):
         params = self.controller._extract_user_params(p)
         self.assertFalse(params)
 
-    # TODO : lots more StackController tests..
+    def test_reformat_dict_keys(self):
+        keymap = {"foo": "bar"}
+        data = {"foo": 123}
+        expected = {"bar": 123}
+        result = self.controller._reformat_dict_keys(keymap, data)
+        self.assertEqual(result, expected)
+
+    def test_list(self):
+        # Format a dummy GET request to pass into the WSGI handler
+        params = {'Action': 'ListStacks'}
+        dummy_req = self._dummy_GET_request(params)
+
+        # Stub out the RPC call to the engine with a pre-canned response
+        engine_resp = {u'stacks': [
+                        {u'stack_id': u'1',
+                        u'updated_time': u'2012-07-09T09:13:11Z',
+                        u'template_description': u'blah',
+                        u'stack_status_reason': u'Stack successfully created',
+                        u'creation_time': u'2012-07-09T09:12:45Z',
+                        u'stack_name': u'wordpress',
+                        u'stack_status': u'CREATE_COMPLETE'}]}
+        self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, 'engine', {'method': 'show_stack',
+                                    'args': {'stack_name': None,
+                                    'params': dict(dummy_req.params)}}
+                ).AndReturn(engine_resp)
+
+        # Stub socket.gethostname so it returns "ahostname"
+        self.m.StubOutWithMock(socket, 'gethostname')
+        socket.gethostname().AndReturn("ahostname")
+
+        self.m.ReplayAll()
+
+        # Call the list controller function and compare the response
+        result = self.controller.list(dummy_req)
+        expected = {'ListStacksResponse': {'ListStacksResult':
+            {'StackSummaries': [
+            {u'StackId': u'ahostname:8000:stack/wordpress/1',
+            u'LastUpdatedTime': u'2012-07-09T09:13:11Z',
+            u'TemplateDescription': u'blah',
+            u'StackStatusReason': u'Stack successfully created',
+            u'CreationTime': u'2012-07-09T09:12:45Z',
+            u'StackName': u'wordpress', u'StackStatus': u'CREATE_COMPLETE'}]}}}
+        self.assertEqual(result, expected)
+
+    def test_describe(self):
+        # Format a dummy GET request to pass into the WSGI handler
+        stack_name = "wordpress"
+        params = {'Action': 'DescribeStacks', 'StackName': stack_name}
+        dummy_req = self._dummy_GET_request(params)
+
+        # Stub out the RPC call to the engine with a pre-canned response
+        # Note the engine returns a load of keys we don't actually use
+        # so this is a subset of the real response format
+        engine_resp = {u'stacks': [
+            {u'stack_id': u'6',
+            u'updated_time': u'2012-07-09T09:13:11Z',
+            u'parameters':{
+            u'DBUsername': {u'Default': u'admin'},
+            u'LinuxDistribution': {u'Default': u'F16'},
+            u'InstanceType': {u'Default': u'm1.large'},
+            u'DBRootPassword': {u'Default': u'admin'},
+            u'DBPassword': {u'Default': u'admin'},
+            u'DBName': {u'Default': u'wordpress'}},
+            u'outputs':
+                [{u'output_key': u'WebsiteURL',
+                u'description': u'URL for Wordpress wiki',
+                u'output_value': u'http://10.0.0.8/wordpress'}],
+            u'stack_status_reason': u'Stack successfully created',
+            u'creation_time': u'2012-07-09T09:12:45Z',
+            u'stack_name': u'wordpress',
+            u'notification_topics': [],
+            u'stack_status': u'CREATE_COMPLETE',
+            u'description': u'blah',
+            u'disable_rollback': True,
+            u'timeout_mins':60,
+            u'capabilities':[]}]}
+
+        self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, 'engine', {'method': 'show_stack', 'args':
+            {'stack_name': stack_name,
+            'params': dict(dummy_req.params)}}).AndReturn(engine_resp)
+
+        # Stub socket.gethostname so it returns "ahostname"
+        self.m.StubOutWithMock(socket, 'gethostname')
+        socket.gethostname().AndReturn("ahostname")
+
+        self.m.ReplayAll()
+
+        # Call the list controller function and compare the response
+        response = self.controller.describe(dummy_req)
+
+        expected = {'DescribeStacksResponse': {'DescribeStacksResult':
+                {'Stacks':
+                [{'StackId': u'ahostname:8000:stack/wordpress/6',
+                'StackStatusReason': u'Stack successfully created',
+                'Description': u'blah',
+                'Parameters':
+                    [{'ParameterValue': u'admin',
+                    'ParameterKey': u'DBUsername'},
+                    {'ParameterValue': u'F16',
+                    'ParameterKey': u'LinuxDistribution'},
+                    {'ParameterValue': u'm1.large',
+                    'ParameterKey': u'InstanceType'},
+                    {'ParameterValue': u'admin',
+                    'ParameterKey': u'DBRootPassword'},
+                    {'ParameterValue': u'admin',
+                    'ParameterKey': u'DBPassword'},
+                    {'ParameterValue': u'wordpress',
+                    'ParameterKey': u'DBName'}],
+                'Outputs':
+                    [{'OutputKey': u'WebsiteURL',
+                    'OutputValue': u'http://10.0.0.8/wordpress',
+                    'Description': u'URL for Wordpress wiki'}],
+                'TimeoutInMinutes': 60,
+                'CreationTime': u'2012-07-09T09:12:45Z',
+                'Capabilities': [],
+                'StackName': u'wordpress',
+                'NotificationARNs': [],
+                'StackStatus': u'CREATE_COMPLETE',
+                'DisableRollback': True,
+                'LastUpdatedTime': u'2012-07-09T09:13:11Z'}]}}}
+
+        self.assert_(response == expected)
+
+    def test_get_template_int_body(self):
+        ''' Test the internal _get_template function '''
+        params = {'TemplateBody': "abcdef"}
+        dummy_req = self._dummy_GET_request(params)
+        result = self.controller._get_template(dummy_req)
+        expected = "abcdef"
+        self.assert_(result == expected)
+
+    # TODO : test the _get_template TemplateUrl case
+
+    def test_create(self):
+        # Format a dummy request
+        stack_name = "wordpress"
+        template = {u'Foo': u'bar'}
+        json_template = json.dumps(template)
+        params = {'Action': 'CreateStack', 'StackName': stack_name,
+                  'TemplateBody': '%s' % json_template,
+                  'TimeoutInMinutes': 30,
+                  'Parameters.member.1.ParameterKey': 'InstanceType',
+                  'Parameters.member.1.ParameterValue': 'm1.xlarge'}
+        engine_parms = {u'InstanceType': u'm1.xlarge'}
+        engine_args = {'timeout_mins': u'30'}
+        dummy_req = self._dummy_GET_request(params)
+
+        # Stub out the RPC call to the engine with a pre-canned response
+        engine_resp = {u'StackName': u'wordpress', u'StackId': 1}
+
+        self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, 'engine', {'method': 'create_stack',
+            'args':
+            {'stack_name': stack_name,
+            'template': template,
+            'params': engine_parms,
+            'args': engine_args}}).AndReturn(engine_resp)
+
+        # Stub socket.gethostname so it returns "ahostname"
+        self.m.StubOutWithMock(socket, 'gethostname')
+        socket.gethostname().AndReturn("ahostname")
+
+        self.m.ReplayAll()
+
+        response = self.controller.create(dummy_req)
+
+        expected = {'CreateStackResponse': {'CreateStackResult':
+                        {u'StackName': u'wordpress',
+                        u'StackId': u'ahostname:8000:stack/wordpress/1'}}}
+
+        self.assert_(response == expected)
+
+    def test_update(self):
+        # Format a dummy request
+        stack_name = "wordpress"
+        template = {u'Foo': u'bar'}
+        json_template = json.dumps(template)
+        params = {'Action': 'UpdateStack', 'StackName': stack_name,
+                  'TemplateBody': '%s' % json_template,
+                  'Parameters.member.1.ParameterKey': 'InstanceType',
+                  'Parameters.member.1.ParameterValue': 'm1.xlarge'}
+        engine_parms = {u'InstanceType': u'm1.xlarge'}
+        engine_args = {}
+        dummy_req = self._dummy_GET_request(params)
+
+        # Stub out the RPC call to the engine with a pre-canned response
+        engine_resp = {u'StackName': u'wordpress', u'StackId': 1}
+
+        self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, 'engine', {'method': 'update_stack',
+            'args':
+            {'stack_name': stack_name,
+            'template': template,
+            'params': engine_parms,
+            'args': engine_args}}).AndReturn(engine_resp)
+
+        # Stub socket.gethostname so it returns "ahostname"
+        self.m.StubOutWithMock(socket, 'gethostname')
+        socket.gethostname().AndReturn("ahostname")
+
+        self.m.ReplayAll()
+
+        response = self.controller.update(dummy_req)
+
+        expected = {'UpdateStackResponse': {'UpdateStackResult':
+                        {u'StackName': u'wordpress',
+                        u'StackId': u'ahostname:8000:stack/wordpress/1'}}}
+
+        self.assert_(response == expected)
+
+    def test_get_template(self):
+        # Format a dummy request
+        stack_name = "wordpress"
+        template = {u'Foo': u'bar'}
+        params = {'Action': 'GetTemplate', 'StackName': stack_name}
+        dummy_req = self._dummy_GET_request(params)
+
+        # Stub out the RPC call to the engine with a pre-canned response
+        engine_resp = template
+
+        self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, 'engine', {'method': 'get_template',
+            'args':
+            {'stack_name': stack_name,
+            'params': dict(dummy_req.params)}}).AndReturn(engine_resp)
+
+        self.m.ReplayAll()
+
+        response = self.controller.get_template(dummy_req)
+
+        expected = {'GetTemplateResponse': {'GetTemplateResult':
+                        {'TemplateBody': template}}}
+
+        self.assert_(response == expected)
+
+    def test_delete(self):
+        # Format a dummy request
+        stack_name = "wordpress"
+        params = {'Action': 'DeleteStack', 'StackName': stack_name}
+        dummy_req = self._dummy_GET_request(params)
+
+        # Stub out the RPC call to the engine with a pre-canned response
+        # Engine returns None when delete successful
+        engine_resp = None
+
+        self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, 'engine', {'method': 'delete_stack',
+            'args':
+            {'stack_name': stack_name,
+            'params': dict(dummy_req.params)}}).AndReturn(engine_resp)
+
+        self.m.ReplayAll()
+
+        response = self.controller.delete(dummy_req)
+
+        expected = {'DeleteStackResponse': {'DeleteStackResult': ''}}
+
+        self.assert_(response == expected)
+
+    def test_events_list(self):
+        # Format a dummy request
+        stack_name = "wordpress"
+        params = {'Action': 'DescribeStackEvents', 'StackName': stack_name}
+        dummy_req = self._dummy_GET_request(params)
+
+        # Stub out the RPC call to the engine with a pre-canned response
+        engine_resp = {u'events': [{u'stack_name': u'wordpress',
+                        u'event_time': u'2012-07-23T13:05:39Z',
+                        u'stack_id': 6,
+                        u'logical_resource_id': u'WikiDatabase',
+                        u'resource_status_reason': u'state changed',
+                        u'event_id': 42,
+                        u'resource_status': u'IN_PROGRESS',
+                        u'physical_resource_id': None,
+                        u'resource_properties':
+                            {u'UserData': u'blah'},
+                        u'resource_type': u'AWS::EC2::Instance'}]}
+
+        self.m.StubOutWithMock(rpc, 'call')
+        rpc.call(dummy_req.context, 'engine', {'method': 'list_events',
+            'args':
+            {'stack_name': stack_name,
+            'params': dict(dummy_req.params)}}).AndReturn(engine_resp)
+
+        # Stub socket.gethostname so it returns "ahostname"
+        self.m.StubOutWithMock(socket, 'gethostname')
+        socket.gethostname().AndReturn("ahostname")
+
+        self.m.ReplayAll()
+
+        response = self.controller.events_list(dummy_req)
+
+        expected = {'DescribeStackEventsResponse':
+            {'DescribeStackEventsResult':
+            {'StackEvents':
+                [{'EventId': 42,
+                'StackId': u'ahostname:8000:stack/wordpress/6',
+                'ResourceStatus': u'IN_PROGRESS',
+                'ResourceType': u'AWS::EC2::Instance',
+                'Timestamp': u'2012-07-23T13:05:39Z',
+                'StackName': u'wordpress',
+                'ResourceProperties': {u'UserData': u'blah'},
+                'PhysicalResourceId': None,
+                'ResourceStatusData': u'state changed',
+                'LogicalResourceId': u'WikiDatabase'}]}}}
+
+        self.assert_(response == expected)
+
+    def test_describe_stack_resource(self):
+        # Format a dummy request
+        stack_name = "wordpress"
+        params = {'Action': 'DescribeStackResource',
+                  'StackName': stack_name,
+                  'LogicalResourceId': "WikiDatabase"}
+        dummy_req = self._dummy_GET_request(params)
+
+        # Stub out the RPC call to the engine with a pre-canned response
+        engine_resp = {u'description': u'',
+                       u'stack_name': u'wordpress',
+                       u'logical_resource_id': u'WikiDatabase',
+                       u'resource_status_reason': None,
+                       u'updated_time': u'2012-07-23T13:06:00Z',
+                       u'stack_id': 6,
+                       u'resource_status': u'CREATE_COMPLETE',
+                       u'physical_resource_id':
+                            u'a3455d8c-9f88-404d-a85b-5315293e67de',
+                       u'resource_type': u'AWS::EC2::Instance',
+                       u'metadata': {u'wordpress': []}}
+
+        self.m.StubOutWithMock(rpc, 'call')
+        args = {
+            'stack_name': dummy_req.params.get('StackName'),
+            'resource_name': dummy_req.params.get('LogicalResourceId'),
+        }
+        rpc.call(dummy_req.context, 'engine',
+            {'method': 'describe_stack_resource',
+            'args': args}).AndReturn(engine_resp)
+
+        # Stub socket.gethostname so it returns "ahostname"
+        self.m.StubOutWithMock(socket, 'gethostname')
+        socket.gethostname().AndReturn("ahostname")
+
+        self.m.ReplayAll()
+
+        response = self.controller.describe_stack_resource(dummy_req)
+
+        expected = {'DescribeStackResourceResponse':
+                    {'DescribeStackResourceResult':
+                    {'StackResourceDetail':
+                    {'StackId': u'ahostname:8000:stack/wordpress/6',
+                    'ResourceStatus': u'CREATE_COMPLETE',
+                    'Description': u'',
+                    'ResourceType': u'AWS::EC2::Instance',
+                    'ResourceStatusReason': None,
+                    'LastUpdatedTimestamp': u'2012-07-23T13:06:00Z',
+                    'StackName': u'wordpress',
+                    'PhysicalResourceId':
+                        u'a3455d8c-9f88-404d-a85b-5315293e67de',
+                    'Metadata': {u'wordpress': []},
+                    'LogicalResourceId': u'WikiDatabase'}}}}
+
+        self.assert_(response == expected)
+
+    def test_describe_stack_resources(self):
+        # Format a dummy request
+        stack_name = "wordpress"
+        params = {'Action': 'DescribeStackResources',
+                  'StackName': stack_name,
+                  'LogicalResourceId': "WikiDatabase"}
+        dummy_req = self._dummy_GET_request(params)
+
+        # Stub out the RPC call to the engine with a pre-canned response
+        engine_resp = [{u'description': u'',
+                        u'stack_name': u'wordpress',
+                        u'logical_resource_id': u'WikiDatabase',
+                        u'resource_status_reason': None,
+                        u'updated_time': u'2012-07-23T13:06:00Z',
+                        u'stack_id': 6,
+                        u'resource_status': u'CREATE_COMPLETE',
+                        u'physical_resource_id':
+                            u'a3455d8c-9f88-404d-a85b-5315293e67de',
+                        u'resource_type': u'AWS::EC2::Instance',
+                        u'metadata': {u'ensureRunning': u'true''true'}}]
+
+        self.m.StubOutWithMock(rpc, 'call')
+        args = {
+            'stack_name': dummy_req.params.get('StackName'),
+            'physical_resource_id': None,
+            'logical_resource_id': dummy_req.params.get('LogicalResourceId'),
+        }
+        rpc.call(dummy_req.context, 'engine',
+            {'method': 'describe_stack_resources',
+            'args': args}).AndReturn(engine_resp)
+
+        # Stub socket.gethostname so it returns "ahostname"
+        self.m.StubOutWithMock(socket, 'gethostname')
+        socket.gethostname().AndReturn("ahostname")
+
+        self.m.ReplayAll()
+
+        response = self.controller.describe_stack_resources(dummy_req)
+
+        expected = {'DescribeStackResourcesResponse':
+                    {'DescribeStackResourcesResult':
+                    {'StackResources':
+                        [{'StackId': u'ahostname:8000:stack/wordpress/6',
+                        'ResourceStatus': u'CREATE_COMPLETE',
+                        'Description': u'',
+                        'ResourceType': u'AWS::EC2::Instance',
+                        'Timestamp': u'2012-07-23T13:06:00Z',
+                        'ResourceStatusReason': None,
+                        'StackName': u'wordpress',
+                        'PhysicalResourceId':
+                            u'a3455d8c-9f88-404d-a85b-5315293e67de',
+                        'LogicalResourceId': u'WikiDatabase'}]}}}
+
+        self.assert_(response == expected)
+
+    def test_describe_stack_resources_err_inval(self):
+        # Format a dummy request containing both StackName and
+        # PhysicalResourceId, which is invalid and should throw a
+        # HeatInvalidParameterCombinationError
+        stack_name = "wordpress"
+        params = {'Action': 'DescribeStackResources',
+                  'StackName': stack_name,
+                  'PhysicalResourceId': "123456"}
+        dummy_req = self._dummy_GET_request(params)
+        ret = self.controller.describe_stack_resources(dummy_req)
+        self.assert_(type(ret) ==
+            exception.HeatInvalidParameterCombinationError)
+
+    def test_list_stack_resources(self):
+        # Format a dummy request
+        stack_name = "wordpress"
+        params = {'Action': 'ListStackResources',
+                  'StackName': stack_name}
+        dummy_req = self._dummy_GET_request(params)
+
+        # Stub out the RPC call to the engine with a pre-canned response
+        engine_resp = [{u'description': u'',
+                        u'stack_name': u'wordpress',
+                        u'logical_resource_id': u'WikiDatabase',
+                        u'resource_status_reason': None,
+                        u'updated_time': u'2012-07-23T13:06:00Z',
+                        u'stack_id': 6,
+                        u'resource_status': u'CREATE_COMPLETE',
+                        u'physical_resource_id':
+                            u'a3455d8c-9f88-404d-a85b-5315293e67de',
+                        u'resource_type': u'AWS::EC2::Instance',
+                        u'metadata': {}}]
+
+        self.m.StubOutWithMock(rpc, 'call')
+        args = {
+            'stack_name': dummy_req.params.get('StackName'),
+        }
+        rpc.call(dummy_req.context, 'engine',
+            {'method': 'list_stack_resources',
+            'args': args}).AndReturn(engine_resp)
+
+        self.m.ReplayAll()
+
+        response = self.controller.list_stack_resources(dummy_req)
+
+        expected = {'ListStackResourcesResponse': {'ListStackResourcesResult':
+                    {'StackResourceSummaries':
+                    [{'ResourceStatus': u'CREATE_COMPLETE',
+                    'ResourceType': u'AWS::EC2::Instance',
+                    'ResourceStatusReason': None,
+                    'LastUpdatedTimestamp': u'2012-07-23T13:06:00Z',
+                    'PhysicalResourceId':
+                        u'a3455d8c-9f88-404d-a85b-5315293e67de',
+                     'LogicalResourceId': u'WikiDatabase'}]}}}
+
+        self.assert_(response == expected)
 
     def setUp(self):
+        self.maxDiff = None
+        self.m = mox.Mox()
+
         # Create WSGI controller instance
-        config.register_api_opts()
-        self.controller = stacks.StackController(cfg.CONF)
+        class DummyConfig():
+            bind_port = 8000
+        cfgopts = DummyConfig()
+        self.controller = stacks.StackController(options=cfgopts)
         print "setup complete"
 
     def tearDown(self):
+        self.m.UnsetStubs()
+        self.m.VerifyAll()
         print "teardown complete"