From: Steven Hardy Date: Mon, 23 Jul 2012 16:43:39 +0000 (+0100) Subject: heat API : Add more unit tests X-Git-Tag: 2014.1~1570 X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=64f9b72c8e41d736cf2b8be447a940333b37149a;p=openstack-build%2Fheat-build.git heat API : Add more unit tests Add more unit tests to improve API test coverage Change-Id: I5f3a8933a9a09acb6b904ccc76ec20ad210a5974 Signed-off-by: Steven Hardy --- diff --git a/heat/tests/test_api_v1.py b/heat/tests/test_api_v1.py index 7a2fb6c0..6bb0227f 100644 --- a/heat/tests/test_api_v1.py +++ b/heat/tests/test_api_v1.py @@ -13,13 +13,23 @@ # 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"