Un-mock heat and remaining mocked calls

This also updates Stack.resources to deal with the nested
stacks introduced by the updated tuskar-api.  The tests
should eventually be updated to include nested stacks.

There are some new Plan properties that have 'todos'
indicating that they will be updated once the tuskar-api
pythonclient is updated.  Testing was done with hardcoded
values there.

Change-Id: I8ba071bf586bb2350ed4838a9cf9199f195cbe17
This commit is contained in:
Tzu-Mainn Chen 2014-08-27 06:13:25 +02:00
parent da896c2241
commit 919678da84
13 changed files with 98 additions and 201 deletions

View File

@ -21,20 +21,14 @@ from horizon.utils import memoized
from openstack_dashboard.api import base
from openstack_dashboard.api import heat
from openstack_dashboard.api import keystone
from openstack_dashboard.test.test_data import utils as test_utils
from tuskar_ui.api import node
from tuskar_ui.api import tuskar
from tuskar_ui.cached_property import cached_property # noqa
from tuskar_ui.handle_errors import handle_errors # noqa
from tuskar_ui.test.test_data import heat_data
from tuskar_ui.test.test_driver import heat_driver as mock_heat
from tuskar_ui.utils import utils
TEST_DATA = test_utils.TestDataContainer()
heat_data.data(TEST_DATA)
LOG = logging.getLogger(__name__)
@ -86,11 +80,16 @@ class Stack(base.APIResourceWrapper):
@classmethod
@handle_errors(_("Unable to create Heat stack"), [])
def create(cls, request, stack_name, template, parameters):
stack = mock_heat.Stack.create(
stack_name=stack_name,
template=template,
parameters=parameters)
def create(cls, request, stack_name, template, environment,
provider_resource_templates):
fields = {
'stack_name': stack_name,
'template': template,
'environment': environment,
'files': provider_resource_templates,
'password': 'password',
}
stack = heat.stack_create(request, **fields)
return cls(stack, request=request)
@classmethod
@ -146,11 +145,11 @@ class Stack(base.APIResourceWrapper):
@classmethod
@handle_errors(_("Unable to delete Heat stack"), [])
def delete(cls, request, stack_id):
mock_heat.Stack.delete(stack_id)
heat.stack_delete(request, stack_id)
@memoized.memoized
def resources(self, with_joins=True):
"""Return a list of all Resources associated with the Stack
def resources(self, with_joins=True, role=None):
"""Return list of OS::Nova::Server Resources associated with the Stack
:param with_joins: should we also retrieve objects associated with each
retrieved Resource?
@ -159,48 +158,49 @@ class Stack(base.APIResourceWrapper):
:return: list of all Resources or an empty list if there are none
:rtype: list of tuskar_ui.api.heat.Resource
"""
resources = [r for r in TEST_DATA.heatclient_resources.list() if
r.stack_id == self.id]
top_level_resources = heat.resources_list(self._request,
self.stack_name)
resource_dicts = []
for resource in top_level_resources:
if resource.resource_type == 'OS::Nova::Server':
if role is None:
resource_dicts.append({"resource": resource, "role": None})
elif resource.resource_type == 'OS::Heat::ResourceGroup':
# we need to dig through the resource group to reach the nova
# resources
group_resources = heat.resources_list(
self._request, resource.physical_resource_id)
for group_resource in group_resources:
tuskar_role = tuskar.OvercloudRole.get_by_resource_type(
self._request, group_resource.resource_type)
nova_resources = heat.resources_list(
self._request,
group_resource.physical_resource_id)
if role is None or role.uuid == tuskar_role.uuid:
resource_dicts.extend([{"resource": resource,
"role": tuskar_role}
for resource in nova_resources])
if not with_joins:
return [Resource(r, request=self._request, stack=self)
for r in resources]
return [Resource(rd['resource'], request=self._request,
stack=self, role=rd['role'])
for rd in resource_dicts]
nodes_dict = utils.list_to_dict(node.Node.list(self._request,
associated=True),
key_attribute='instance_uuid')
joined_resources = []
for r in resources:
for rd in resource_dicts:
resource = rd['resource']
joined_resources.append(
Resource(r, node=nodes_dict.get(r.physical_resource_id, None),
request=self._request, stack=self))
Resource(resource,
node=nodes_dict.get(resource.physical_resource_id,
None),
request=self._request, stack=self, role=rd['role']))
# TODO(lsmola) I want just resources with nova instance
# this could be probably filtered a better way, investigate
return [r for r in joined_resources if r.node is not None]
@memoized.memoized
def resources_by_role(self, overcloud_role, with_joins=True):
"""Return a list of Resources that match an OvercloudRole
:param overcloud_role: role of resources to be returned
:type overcloud_role: tuskar_ui.api.tuskar.OvercloudRole
:param with_joins: should we also retrieve objects associated with each
retrieved Resource?
:type with_joins: bool
:return: list of Resources that match the OvercloudRole, or an empty
list if there are none
:rtype: list of tuskar_ui.api.heat.Resource
"""
# FIXME(lsmola) with_joins is not necessary here, I need at least
# nova instance
resources = self.resources(with_joins)
filtered_resources = [resource for resource in resources if
(resource.has_role(overcloud_role))]
return filtered_resources
@memoized.memoized
def resources_count(self, overcloud_role=None):
"""Return count of associated Resources
@ -218,7 +218,7 @@ class Stack(base.APIResourceWrapper):
if overcloud_role is None:
resources = self.resources()
else:
resources = self.resources_by_role(overcloud_role)
resources = self.resources(role=overcloud_role)
return len(resources)
@cached_property
@ -384,27 +384,8 @@ class Resource(base.APIResourceWrapper):
self._node = kwargs['node']
if 'stack' in kwargs:
self._stack = kwargs['stack']
@classmethod
def get(cls, request, stack, resource_name):
"""Return the specified Heat Resource within a Stack
:param request: request object
:type request: django.http.HttpRequest
:param overcloud: the Stack from which to retrieve the resource
:type overcloud: tuskar_ui.api.heat.OvercloudStack
:param resource_name: name of the Resource to retrieve
:type resource_name: str
:return: matching Resource, or None if no Resource in the Stack
matches the resource name
:rtype: tuskar_ui.api.heat.Resource
"""
for r in TEST_DATA.heatclient_resources.list():
if r.stack_id == stack.id and r.resource_name == resource_name:
return cls(r, request=request, stack=stack)
if 'role' in kwargs:
self._role = kwargs['role']
@classmethod
def get_by_node(cls, request, node):
@ -438,21 +419,8 @@ class Resource(base.APIResourceWrapper):
OvercloudRole is associated
:rtype: tuskar_ui.api.tuskar.OvercloudRole
"""
roles = tuskar.OvercloudRole.list(self._request)
for role in roles:
if self.has_role(role):
return role
def has_role(self, role):
"""Determine whether a resources matches an overcloud role
:param role: role to check against
:type role: tuskar_ui.api.tuskar.OvercloudRole
:return: does this resource match the overcloud_role?
:rtype: bool
"""
return self.resource_type == role.provider_resource_type
if hasattr(self, '_role'):
return self._role
@cached_property
def node(self):

View File

@ -19,19 +19,12 @@ from novaclient.v1_1.contrib import baremetal
from openstack_dashboard.api import base
from openstack_dashboard.api import glance
from openstack_dashboard.api import nova
from openstack_dashboard.test.test_data import utils as test_utils
from tuskar_ui.cached_property import cached_property # noqa
from tuskar_ui.handle_errors import handle_errors # noqa
from tuskar_ui.test.test_data import heat_data
from tuskar_ui.test.test_data import node_data
from tuskar_ui.utils import utils
TEST_DATA = test_utils.TestDataContainer()
node_data.data(TEST_DATA)
heat_data.data(TEST_DATA)
ERROR_STATES = set(['deploy failed', 'error'])
POWER_ON_STATES = set(['on', 'power on'])
@ -432,25 +425,17 @@ class Node(base.APIResourceWrapper):
def get(cls, request, uuid):
node = NodeClient(request).node_class.get(request, uuid)
if node.instance_uuid is not None:
for server in TEST_DATA.novaclient_servers.list():
if server.id == node.instance_uuid:
break
else:
server = None
return cls(node, instance=server, request=request)
return cls(node)
server = nova.server_get(request, node.instance_uuid)
else:
server = None
return cls(node, instance=server, request=request)
@classmethod
@handle_errors(_("Unable to retrieve node"))
def get_by_instance_uuid(cls, request, instance_uuid):
node = NodeClient(request).node_class.get_by_instance_uuid(
request, instance_uuid)
for server in TEST_DATA.novaclient_servers.list():
if server.id == node.instance_uuid:
break
else:
server = None
server = nova.server_get(request, instance_uuid)
return cls(node, instance=server, request=request)
@classmethod
@ -459,7 +444,7 @@ class Node(base.APIResourceWrapper):
nodes = NodeClient(request).node_class.list(
request, associated=associated)
if associated is None or associated:
servers = TEST_DATA.novaclient_servers.list()
servers = nova.server_list(request)[0]
servers_dict = utils.list_to_dict(servers)
nodes_with_instance = []
for n in nodes:
@ -511,9 +496,8 @@ class Node(base.APIResourceWrapper):
"""
if self.instance is None:
return
for image in TEST_DATA.glanceclient_images.list():
if image.id == self.instance.image['id']:
return image.name
image = image_get(self._request, self.instance.image['id'])
return image.name
@cached_property
def instance_status(self):

View File

@ -36,7 +36,7 @@ def tuskarclient(request):
class OvercloudPlan(base.APIResourceWrapper):
_attrs = ('id', 'name', 'description', 'created_at', 'modified_at',
'roles', 'parameters', 'template')
'roles', 'parameters')
def __init__(self, apiresource, request=None):
super(OvercloudPlan, self).__init__(apiresource)
@ -151,6 +151,24 @@ class OvercloudPlan(base.APIResourceWrapper):
return [OvercloudRole.get(self._request, role['uuid'])
for role in self.roles]
@cached_property
def template(self):
#TODO(tzumainn): replace with actual call to tuskar api
#once tuskar pythonclient is updated
return ""
@cached_property
def environment(self):
#TODO(tzumainn): replace with actual call to tuskar api
#once tuskar pythonclient is updated
return ""
@cached_property
def provider_resource_templates(self):
#TODO(tzumainn): replace with actual call to tuskar api
#once tuskar pythonclient is updated
return {}
def parameter_list(self, include_key_parameters=True):
params = self.parameters
if not include_key_parameters:
@ -240,11 +258,18 @@ class OvercloudRole(base.APIResourceWrapper):
if image_id_from_plan == image.id:
return role
@classmethod
@handle_errors(_("Unable to retrieve overcloud role"))
def get_by_resource_type(cls, request, resource_type):
for role in OvercloudRole.list(request):
if role.provider_resource_type == resource_type:
return role
# TODO(tzumainn): fix this once we know how a role corresponds to
# its provider resource type
@property
def provider_resource_type(self):
return self.name
return 'Tuskar::' + self.name + '-' + str(self.version)
@property
def parameter_prefix(self):

View File

@ -50,7 +50,6 @@ class NodesTests(test.BaseAdminViewTests, helpers.APITestCase):
raise horizon_exceptions.NotFound
def test_index_get(self):
with patch('tuskar_ui.api.node.Node', **{
'spec_set': ['list'], # Only allow these attributes
'list.return_value': [],

View File

@ -67,7 +67,8 @@ class DeployOvercloud(horizon.forms.SelfHandlingForm):
api.heat.Stack.create(request,
plan.name,
plan.template,
plan.parameters)
plan.environment,
plan.provider_resource_templates)
except Exception:
LOG.exception()
return False

View File

@ -126,6 +126,8 @@ class OverviewTests(test.BaseAdminViewTests):
return_value=True),
patch('tuskar_ui.api.heat.Stack.is_deployed',
return_value=False),
patch('tuskar_ui.api.heat.Stack.resources',
return_value=[]),
patch('tuskar_ui.api.heat.Stack.events',
return_value=[]),
):

View File

@ -41,7 +41,7 @@ def _get_role_data(plan, stack, role, field):
}
if stack:
resources = stack.resources_by_role(role, with_joins=True)
resources = stack.resources(role=role, with_joins=True)
nodes = [r.node for r in resources]
node_count = len(nodes)

View File

@ -74,9 +74,8 @@ class DetailView(horizon_tables.DataTableView, OvercloudRoleMixin, StackMixin):
@memoized.memoized
def _get_nodes(self, stack, role):
resources = stack.resources_by_role(role, with_joins=True)
resources = stack.resources(role=role, with_joins=True)
nodes = [r.node for r in resources]
for node in nodes:
# TODO(tzumainn): this could probably be done more efficiently
# by getting the resource for all nodes at once

View File

@ -40,7 +40,6 @@ class HeatAPITests(test.APITestCase):
with patch('openstack_dashboard.api.heat.stack_get',
return_value=stack):
ret_val = api.heat.Stack.get(self.request, stack.id)
self.assertIsInstance(ret_val, api.heat.Stack)
def test_stack_plan(self):
@ -70,8 +69,8 @@ class HeatAPITests(test.APITestCase):
self.assertFalse(ret_val)
def test_stack_resources(self):
stack = api.heat.Stack(self.heatclient_stacks.first())
stack = api.heat.Stack(self.heatclient_stacks.first(),
request=self.request)
resources = self.heatclient_resources.list()
nodes = self.baremetalclient_nodes.list()
instances = []
@ -89,26 +88,7 @@ class HeatAPITests(test.APITestCase):
for i in ret_val:
self.assertIsInstance(i, api.heat.Resource)
self.assertEqual(3, len(ret_val))
def test_stack_resources_no_ironic(self):
stack = api.heat.Stack(self.heatclient_stacks.first())
role = api.tuskar.OvercloudRole(
self.tuskarclient_roles.first())
nodes = self.baremetalclient_nodes.list()
# FIXME(lsmola) only resources and image_name should be tested
# here, anybody has idea how to do that?
with patch('openstack_dashboard.api.base.is_service_enabled',
return_value=False):
with patch('novaclient.v1_1.contrib.baremetal.'
'BareMetalNodeManager.list',
return_value=nodes):
ret_val = stack.resources_by_role(role)
for i in ret_val:
self.assertIsInstance(i, api.heat.Resource)
self.assertEqual(1, len(ret_val))
self.assertEqual(4, len(ret_val))
def test_stack_keystone_ip(self):
stack = api.heat.Stack(self.heatclient_stacks.first())
@ -142,26 +122,6 @@ class HeatAPITests(test.APITestCase):
stack.dashboard_urls)
self.assertEqual(client_get.call_count, 1)
def test_resource_get(self):
stack = self.heatclient_stacks.first()
resource = self.heatclient_resources.first()
ret_val = api.heat.Resource.get(None, stack,
resource.resource_name)
self.assertIsInstance(ret_val, api.heat.Resource)
def test_resource_role(self):
# The api needs to get the role, and getting the role means listing
# all roles, so we mock that.
roles = self.tuskarclient_roles.list()
with patch('tuskarclient.v2.roles.RoleManager.list',
return_value=roles):
resource = api.heat.Resource(self.heatclient_resources.first(),
self.request)
ret_val = resource.role
self.assertIsInstance(ret_val, api.tuskar.OvercloudRole)
self.assertEqual('Compute', ret_val.name)
def test_resource_node_no_ironic(self):
resource = self.heatclient_resources.first()
nodes = self.baremetalclient_nodes.list()

View File

@ -125,7 +125,7 @@ class NodeAPITests(test.APITestCase):
with patch('openstack_dashboard.api.nova.server_list',
return_value=([instance], False)):
ret_val = api.node.Node(node).image_name
self.assertEqual(ret_val, 'overcloud-compute')
self.assertEqual(ret_val, 'overcloud-control')
def test_node_addresses_no_ironic(self):
node = self.baremetalclient_nodes.first()

View File

@ -118,7 +118,7 @@ def data(TEST):
'logical_resource_id': 'Compute0',
'physical_resource_id': 'aa',
'resource_status': 'CREATE_COMPLETE',
'resource_type': 'Compute'})
'resource_type': 'OS::Nova::Server'})
resource_2 = resources.Resource(
resources.ResourceManager(None),
{'id': '2-resource-id',
@ -127,7 +127,7 @@ def data(TEST):
'logical_resource_id': 'Controller',
'physical_resource_id': 'bb',
'resource_status': 'CREATE_COMPLETE',
'resource_type': 'Controller'})
'resource_type': 'OS::Nova::Server'})
resource_3 = resources.Resource(
resources.ResourceManager(None),
{'id': '3-resource-id',
@ -136,7 +136,7 @@ def data(TEST):
'logical_resource_id': 'Compute1',
'physical_resource_id': 'cc',
'resource_status': 'CREATE_COMPLETE',
'resource_type': 'Compute'})
'resource_type': 'OS::Nova::Server'})
resource_4 = resources.Resource(
resources.ResourceManager(None),
{'id': '4-resource-id',
@ -145,7 +145,7 @@ def data(TEST):
'logical_resource_id': 'Compute2',
'physical_resource_id': 'dd',
'resource_status': 'CREATE_COMPLETE',
'resource_type': 'Compute'})
'resource_type': 'OS::Nova::Server'})
TEST.heatclient_resources.add(resource_1,
resource_2,
resource_3,

View File

@ -1,41 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from openstack_dashboard.test.test_data import utils
from tuskar_ui.test.test_data import heat_data
TEST_DATA = utils.TestDataContainer()
heat_data.data(TEST_DATA)
class Stack:
_stacks = {}
@classmethod
def create(cls, **kwargs):
stack = TEST_DATA.heatclient_stacks.first()
cls._stacks[stack.id] = stack
return stack
@classmethod
def list(cls):
return cls._stacks.values()
@classmethod
def get(cls, stack_id):
return cls._stacks.get(stack_id, None)
@classmethod
def delete(cls, stack_id):
cls._stacks.pop(stack_id, None)