diff --git a/tuskar_ui/api/flavor.py b/tuskar_ui/api/flavor.py
index a0d1d1b54..07966e78d 100644
--- a/tuskar_ui/api/flavor.py
+++ b/tuskar_ui/api/flavor.py
@@ -14,13 +14,18 @@ import logging
from django.utils.translation import ugettext_lazy as _
from horizon.utils import memoized
-from openstack_dashboard.api import nova
+from openstack_dashboard.test.test_data import utils as test_utils
-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 flavor_data
+from tuskar_ui.test.test_data import heat_data
+TEST_DATA = test_utils.TestDataContainer()
+flavor_data.data(TEST_DATA)
+heat_data.data(TEST_DATA)
+
LOG = logging.getLogger(__name__)
@@ -77,29 +82,27 @@ class Flavor(object):
@classmethod
def create(cls, request, name, memory, vcpus, disk, cpu_arch,
kernel_image_id, ramdisk_image_id):
- extras_dict = {'cpu_arch': cpu_arch,
- 'baremetal:deploy_kernel_id': kernel_image_id,
- 'baremetal:deploy_ramdisk_id': ramdisk_image_id}
- return cls(nova.flavor_create(request, name, memory, vcpus, disk,
- metadata=extras_dict))
+ return cls(TEST_DATA.novaclient_flavors.first(),
+ request=request)
@classmethod
@handle_errors(_("Unable to load flavor."))
def get(cls, request, flavor_id):
- return cls(nova.flavor_get(request, flavor_id))
+ for flavor in Flavor.list(request):
+ if flavor.id == flavor_id:
+ return flavor
@classmethod
@handle_errors(_("Unable to retrieve flavor list."), [])
def list(cls, request):
- return [cls(item) for item in nova.flavor_list(request)]
+ flavors = TEST_DATA.novaclient_flavors.list()
+ return [cls(flavor) for flavor in flavors]
@classmethod
@memoized.memoized
@handle_errors(_("Unable to retrieve existing servers list."), [])
def list_deployed_ids(cls, request):
"""Get and memoize ID's of deployed flavors."""
- servers = nova.server_list(request)[0]
+ servers = TEST_DATA.novaclient_servers.list()
deployed_ids = set(server.flavor['id'] for server in servers)
- roles = tuskar.OvercloudRole.list(request)
- deployed_ids |= set(role.flavor_id for role in roles)
return deployed_ids
diff --git a/tuskar_ui/api/heat.py b/tuskar_ui/api/heat.py
index 9ea75fd6a..6469a3c94 100644
--- a/tuskar_ui/api/heat.py
+++ b/tuskar_ui/api/heat.py
@@ -10,23 +10,30 @@
# License for the specific language governing permissions and limitations
# under the License.
-import heatclient
-import keystoneclient.exceptions
import logging
import urlparse
+from django.utils.translation import ugettext_lazy as _
+
+from horizon import exceptions
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 import utils
+TEST_DATA = test_utils.TestDataContainer()
+heat_data.data(TEST_DATA)
+
LOG = logging.getLogger(__name__)
@@ -69,25 +76,41 @@ def overcloud_keystoneclient(request, endpoint, password):
return conn
-class OvercloudStack(base.APIResourceWrapper):
+class Stack(base.APIResourceWrapper):
_attrs = ('id', 'stack_name', 'outputs', 'stack_status', 'parameters')
- def __init__(self, apiresource, request=None, plan=None):
- super(OvercloudStack, self).__init__(apiresource)
+ def __init__(self, apiresource, request=None):
+ super(Stack, self).__init__(apiresource)
self._request = request
- self._plan = plan
@classmethod
- def get(cls, request, stack_id, plan=None):
- """Return the Heat Stack associated with the stack_id
+ @handle_errors(_("Unable to retrieve heat stacks"), [])
+ def list(cls, request):
+ """Return a list of stacks in Heat
+
+ :param request: request object
+ :type request: django.http.HttpRequest
+
+ :return: list of Heat stacks, or an empty list if there
+ are none
+ :rtype: list of tuskar_ui.api.heat.Stack
+ """
+ stacks = TEST_DATA.heatclient_stacks.list()
+ return [cls(stack, request=request) for stack in stacks]
+
+ @classmethod
+ @handle_errors(_("Unable to retrieve stack"))
+ def get(cls, request, stack_id):
+ """Return the Heat Stack associated with this Overcloud
:return: Heat Stack associated with the stack_id; or None
if no Stack is associated, or no Stack can be
found
- :rtype: heatclient.v1.stacks.Stack or None
+ :rtype: tuskar_ui.api.heat.Stack or None
"""
- stack = heat.stack_get(request, stack_id)
- return cls(stack, request=request, plan=plan)
+ for stack in Stack.list(request):
+ if stack.id == stack_id:
+ return stack
@memoized.memoized
def resources(self, with_joins=True):
@@ -100,14 +123,8 @@ class OvercloudStack(base.APIResourceWrapper):
:return: list of all Resources or an empty list if there are none
:rtype: list of tuskar_ui.api.heat.Resource
"""
- try:
- resources = [r for r in heat.resources_list(self._request,
- self.stack_name)]
- except heatclient.exc.HTTPInternalServerError:
- # TODO(lsmola) There is a weird bug in heat, that after
- # stack-create it returns 500 for a little while. This can be
- # removed once the bug is fixed.
- resources = []
+ resources = [r for r in TEST_DATA.heatclient_resources.list() if
+ r.stack_id == self.id]
if not with_joins:
return [Resource(r, request=self._request)
@@ -144,8 +161,7 @@ class OvercloudStack(base.APIResourceWrapper):
# nova instance
resources = self.resources(with_joins)
filtered_resources = [resource for resource in resources if
- (overcloud_role.is_deployed_on_node(
- resource.node))]
+ (resource.has_role(overcloud_role))]
return filtered_resources
@@ -169,6 +185,19 @@ class OvercloudStack(base.APIResourceWrapper):
resources = self.resources_by_role(overcloud_role)
return len(resources)
+ @cached_property
+ def plan(self):
+ """return associated OvercloudPlan if a plan_id exists within stack
+ parameters.
+
+ :return: associated OvercloudPlan if plan_id exists and a matching plan
+ exists as well; None otherwise
+ :rtype: tuskar_ui.api.tuskar.OvercloudPlan
+ """
+ if 'plan_id' in self.parameters:
+ return tuskar.OvercloudPlan.get(self._request,
+ self.parameters['plan_id'])
+
@cached_property
def is_deployed(self):
"""Check if this Stack is successfully deployed.
@@ -251,9 +280,9 @@ class OvercloudStack(base.APIResourceWrapper):
return overcloud_keystoneclient(
self._request,
output['output_value'],
- self._plan.attributes.get('AdminPassword', None))
- except keystoneclient.exceptions.Unauthorized:
- LOG.debug('Unable to connect overcloud keystone.')
+ self.plan.parameter_value('AdminPassword'))
+ except Exception:
+ LOG.debug('Unable to connect to overcloud keystone.')
return None
@cached_property
@@ -318,9 +347,57 @@ class Resource(base.APIResourceWrapper):
matches the resource name
:rtype: tuskar_ui.api.heat.Resource
"""
- resource = heat.resource_get(stack.id,
- resource_name)
- return cls(resource, request=request)
+ for r in TEST_DATA.heatclient_resources.list():
+ if r.stack_id == stack.id and r.resource_name == resource_name:
+ return cls(stack, request=request)
+
+ @classmethod
+ def get_by_node(cls, request, node):
+ """Return the specified Heat Resource given a Node
+
+ :param request: request object
+ :type request: django.http.HttpRequest
+
+ :param node: node to match
+ :type node: tuskar_ui.api.node.Node
+
+ :return: matching Resource, or None if no Resource matches
+ the Node
+ :rtype: tuskar_ui.api.heat.Resource
+ """
+ # TODO(tzumainn): this is terribly inefficient, but I don't see a
+ # better way. Maybe if Heat set some node metadata. . . ?
+ if node.instance_uuid:
+ for stack in Stack.list(request):
+ for resource in stack.resources(with_joins=False):
+ if resource.physical_resource_id == node.instance_uuid:
+ return resource
+ msg = _('Could not find resource matching node "%s"') % node.uuid
+ raise exceptions.NotFound(msg)
+
+ @cached_property
+ def role(self):
+ """Return the OvercloudRole associated with this Resource
+
+ :return: OvercloudRole associated with this Resource, or None if no
+ 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
@cached_property
def node(self):
diff --git a/tuskar_ui/api/node.py b/tuskar_ui/api/node.py
index 402126b67..992310e33 100644
--- a/tuskar_ui/api/node.py
+++ b/tuskar_ui/api/node.py
@@ -14,17 +14,23 @@ import logging
from django.utils.translation import ugettext_lazy as _
from horizon.utils import memoized
-from ironicclient.v1 import client as ironicclient
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 import utils
+TEST_DATA = test_utils.TestDataContainer()
+node_data.data(TEST_DATA)
+heat_data.data(TEST_DATA)
+
LOG = logging.getLogger(__name__)
@@ -87,20 +93,7 @@ class IronicNode(base.APIResourceWrapper):
:return: the created Node object
:rtype: tuskar_ui.api.node.IronicNode
"""
- node = ironicclient(request).node.create(
- driver='pxe_ipmitool',
- driver_info={'ipmi_address': ipmi_address,
- 'ipmi_username': ipmi_username,
- 'password': ipmi_password},
- properties={'cpu': cpu,
- 'ram': ram,
- 'local_disk': local_disk})
- for mac_address in mac_addresses:
- ironicclient(request).port.create(
- node_uuid=node.uuid,
- address=mac_address
- )
-
+ node = TEST_DATA.ironicclient_nodes.first()
return cls(node)
@classmethod
@@ -116,8 +109,9 @@ class IronicNode(base.APIResourceWrapper):
:return: matching IronicNode, or None if no IronicNode matches the ID
:rtype: tuskar_ui.api.node.IronicNode
"""
- node = ironicclient(request).nodes.get(uuid)
- return cls(node)
+ for node in IronicNode.list(request):
+ if node.uuid == uuid:
+ return node
@classmethod
def get_by_instance_uuid(cls, request, instance_uuid):
@@ -136,8 +130,9 @@ class IronicNode(base.APIResourceWrapper):
:raises: ironicclient.exc.HTTPNotFound if there is no IronicNode with
the matching instance UUID
"""
- node = ironicclient(request).nodes.get_by_instance_uuid(instance_uuid)
- return cls(node)
+ for node in IronicNode.list(request):
+ if node.instance_uuid == instance_uuid:
+ return node
@classmethod
@handle_errors(_("Unable to retrieve nodes"), [])
@@ -155,8 +150,15 @@ class IronicNode(base.APIResourceWrapper):
:return: list of IronicNodes, or an empty list if there are none
:rtype: list of tuskar_ui.api.node.IronicNode
"""
- nodes = ironicclient(request).nodes.list(
- associated=associated)
+ nodes = TEST_DATA.ironicclient_nodes.list()
+ if associated is not None:
+ if associated:
+ nodes = [node for node in nodes
+ if node.instance_uuid is not None]
+ else:
+ nodes = [node for node in nodes
+ if node.instance_uuid is None]
+
return [cls(node) for node in nodes]
@classmethod
@@ -170,7 +172,6 @@ class IronicNode(base.APIResourceWrapper):
:param uuid: ID of IronicNode to be removed
:type uuid: str
"""
- ironicclient(request).nodes.delete(uuid)
return
@cached_property
@@ -222,15 +223,7 @@ class BareMetalNode(base.APIResourceWrapper):
:return: the created BareMetalNode object
:rtype: tuskar_ui.api.node.BareMetalNode
"""
- node = baremetalclient(request).create(
- 'undercloud',
- cpu,
- ram,
- local_disk,
- mac_addresses,
- pm_address=ipmi_address,
- pm_user=ipmi_username,
- pm_password=ipmi_password)
+ node = TEST_DATA.baremetalclient_nodes.first()
return cls(node)
@classmethod
@@ -247,9 +240,9 @@ class BareMetalNode(base.APIResourceWrapper):
the ID
:rtype: tuskar_ui.api.node.BareMetalNode
"""
- node = baremetalclient(request).get(uuid)
-
- return cls(node)
+ for node in BareMetalNode.list(request):
+ if node.uuid == uuid:
+ return node
@classmethod
def get_by_instance_uuid(cls, request, instance_uuid):
@@ -268,10 +261,9 @@ class BareMetalNode(base.APIResourceWrapper):
:raises: ironicclient.exc.HTTPNotFound if there is no BareMetalNode
with the matching instance UUID
"""
- nodes = baremetalclient(request).list()
- node = next((n for n in nodes if instance_uuid == n.instance_uuid),
- None)
- return cls(node)
+ for node in BareMetalNode.list(request):
+ if node.instance_uuid == instance_uuid:
+ return node
@classmethod
def list(cls, request, associated=None):
@@ -288,7 +280,7 @@ class BareMetalNode(base.APIResourceWrapper):
:return: list of BareMetalNodes, or an empty list if there are none
:rtype: list of tuskar_ui.api.node.BareMetalNode
"""
- nodes = baremetalclient(request).list()
+ nodes = TEST_DATA.baremetalclient_nodes.list()
if associated is not None:
if associated:
nodes = [node for node in nodes
@@ -308,7 +300,6 @@ class BareMetalNode(base.APIResourceWrapper):
:param uuid: ID of BareMetalNode to be removed
:type uuid: str
"""
- baremetalclient(request).delete(uuid)
return
@cached_property
@@ -427,9 +418,8 @@ class Node(base.APIResourceWrapper):
@handle_errors(_("Unable to retrieve node"))
def get(cls, request, uuid):
node = NodeClient(request).node_class.get(request, uuid)
-
if node.instance_uuid is not None:
- server = nova.server_get(request, node.instance_uuid)
+ server = TEST_DATA.novaclient_servers.first()
return cls(node, instance=server, request=request)
return cls(node)
@@ -439,7 +429,7 @@ class Node(base.APIResourceWrapper):
def get_by_instance_uuid(cls, request, instance_uuid):
node = NodeClient(request).node_class.get_by_instance_uuid(
request, instance_uuid)
- server = nova.server_get(request, instance_uuid)
+ server = TEST_DATA.novaclient_servers.first()
return cls(node, instance=server, request=request)
@classmethod
@@ -449,8 +439,7 @@ class Node(base.APIResourceWrapper):
request, associated=associated)
if associated is None or associated:
- servers, has_more_data = nova.server_list(request)
-
+ servers = TEST_DATA.novaclient_servers.list()
servers_dict = utils.list_to_dict(servers)
nodes_with_instance = []
for n in nodes:
@@ -478,7 +467,7 @@ class Node(base.APIResourceWrapper):
return self._instance
if self.instance_uuid:
- server = nova.server_get(self._request, self.instance_uuid)
+ server = TEST_DATA.novaclient_servers.first()
return server
return None
diff --git a/tuskar_ui/api/tuskar.py b/tuskar_ui/api/tuskar.py
index 727dafb61..776f7dae3 100644
--- a/tuskar_ui/api/tuskar.py
+++ b/tuskar_ui/api/tuskar.py
@@ -15,13 +15,17 @@ import logging
from django.utils.translation import ugettext_lazy as _
from openstack_dashboard.api import base
+from openstack_dashboard.test.test_data import utils
from tuskarclient.v1 import client as tuskar_client
-from tuskar_ui.api import heat
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 tuskar_data
+TEST_DATA = utils.TestDataContainer()
+tuskar_data.data(TEST_DATA)
+
LOG = logging.getLogger(__name__)
TUSKAR_ENDPOINT_URL = getattr(django.conf.settings, 'TUSKAR_ENDPOINT_URL')
@@ -33,66 +37,36 @@ def tuskarclient(request):
return c
-def transform_sizing(overcloud_sizing):
- """Transform the sizing to simpler format
-
- We need this till API will accept the more complex format with flavors,
- then we delete this.
-
- :param overcloud_sizing: overcloud sizing information with structure
- {('overcloud_role_id',
- 'flavor_name'): count, ...}
- :type overcloud_sizing: dict
-
- :return: list of ('overcloud_role_id', 'num_nodes')
- :rtype: list
- """
- return [{
- 'overcloud_role_id': role,
- 'num_nodes': sizing,
- } for (role, flavor), sizing in overcloud_sizing.items()]
-
-
-class OvercloudPlan(base.APIResourceWrapper):
- _attrs = ('id', 'stack_id', 'name', 'description', 'counts', 'attributes')
+class OvercloudPlan(base.APIDictWrapper):
+ _attrs = ('id', 'name', 'description', 'created_at', 'modified_at',
+ 'roles', 'parameters')
def __init__(self, apiresource, request=None):
super(OvercloudPlan, self).__init__(apiresource)
self._request = request
@classmethod
- def create(cls, request, overcloud_sizing, overcloud_configuration):
+ def create(cls, request, name, description):
"""Create an OvercloudPlan in Tuskar
:param request: request object
:type request: django.http.HttpRequest
- :param overcloud_sizing: overcloud sizing information with structure
- {('overcloud_role_id',
- 'flavor_name'): count, ...}
- :type overcloud_sizing: dict
+ :param name: plan name
+ :type name: string
- :param overcloud_configuration: overcloud configuration with structure
- {'key': 'value', ...}
- :type overcloud_configuration: dict
+ :param description: plan description
+ :type description: string
:return: the created OvercloudPlan object
:rtype: tuskar_ui.api.tuskar.OvercloudPlan
"""
- # TODO(lsmola) for now we have to transform the sizing to simpler
- # format, till API will accept the more complex with flavors,
- # then we delete this
- transformed_sizing = transform_sizing(overcloud_sizing)
- overcloud = tuskarclient(request).overclouds.create(
- name='overcloud', description="Openstack cloud providing VMs",
- counts=transformed_sizing, attributes=overcloud_configuration)
-
- return cls(overcloud, request=request)
+ return cls(TEST_DATA.tuskarclient_plans.first(),
+ request=request)
@classmethod
- def update(cls, request, overcloud_id, overcloud_sizing,
- overcloud_configuration):
+ def update(cls, request, overcloud_id, name, description):
"""Update an OvercloudPlan in Tuskar
:param request: request object
@@ -101,28 +75,17 @@ class OvercloudPlan(base.APIResourceWrapper):
:param overcloud_id: id of the overcloud we want to update
:type overcloud_id: string
- :param overcloud_sizing: overcloud sizing information with structure
- {('overcloud_role_id',
- 'flavor_name'): count, ...}
- :type overcloud_sizing: dict
+ :param name: plan name
+ :type name: string
- :param overcloud_configuration: overcloud configuration with structure
- {'key': 'value', ...}
- :type overcloud_configuration: dict
+ :param description: plan description
+ :type description: string
:return: the updated OvercloudPlan object
:rtype: tuskar_ui.api.tuskar.OvercloudPlan
"""
- # TODO(lsmola) for now we have to transform the sizing to simpler
- # format, till API will accept the more complex with flavors,
- # then we delete this
- transformed_sizing = transform_sizing(overcloud_sizing)
-
- overcloud = tuskarclient(request).overclouds.update(
- overcloud_id, counts=transformed_sizing,
- attributes=overcloud_configuration)
-
- return cls(overcloud, request=request)
+ return cls(TEST_DATA.tuskarclient_plans.first(),
+ request=request)
@classmethod
def list(cls, request):
@@ -134,29 +97,26 @@ class OvercloudPlan(base.APIResourceWrapper):
:return: list of OvercloudPlans, or an empty list if there are none
:rtype: list of tuskar_ui.api.tuskar.OvercloudPlan
"""
- ocs = tuskarclient(request).overclouds.list()
+ plans = TEST_DATA.tuskarclient_plans.list()
- return [cls(oc, request=request) for oc in ocs]
+ return [cls(plan, request=request) for plan in plans]
@classmethod
- @handle_errors(_("Unable to retrieve deployment"))
- def get(cls, request, overcloud_id):
+ @handle_errors(_("Unable to retrieve plan"))
+ def get(cls, request, plan_id):
"""Return the OvercloudPlan that matches the ID
:param request: request object
:type request: django.http.HttpRequest
- :param overcloud_id: id of OvercloudPlan to be retrieved
- :type overcloud_id: int
+ :param plan_id: id of OvercloudPlan to be retrieved
+ :type plan_id: int
:return: matching OvercloudPlan, or None if no OvercloudPlan matches
the ID
:rtype: tuskar_ui.api.tuskar.OvercloudPlan
"""
# FIXME(lsmola) hack for Icehouse, only one Overcloud is allowed
- # TODO(lsmola) uncomment when possible
- # overcloud = tuskarclient(request).overclouds.get(overcloud_id)
- # return cls(overcloud, request=request)
return cls.get_the_plan(request)
# TODO(lsmola) before will will support multiple overclouds, we
@@ -175,47 +135,34 @@ class OvercloudPlan(base.APIResourceWrapper):
return plan
@classmethod
- def delete(cls, request, overcloud_id):
+ def delete(cls, request, plan_id):
"""Delete an OvercloudPlan
:param request: request object
:type request: django.http.HttpRequest
- :param overcloud_id: overcloud id
- :type overcloud_id: int
+ :param plan_id: plan id
+ :type plan_id: int
"""
- tuskarclient(request).overclouds.delete(overcloud_id)
-
- @classmethod
- def template_parameters(cls, request):
- """Return a list of needed template parameters
-
- :param request: request object
- :type request: django.http.HttpRequest
-
- :return: dict with key/value parameters
- :rtype: dict
- """
- parameters = tuskarclient(request).overclouds.template_parameters()
- # FIXME(lsmola) python client is converting the result to
- # object, we have to return it better from client or API
- return parameters._info
+ return
@cached_property
- def stack(self):
- """Return the Heat Stack associated with this Overcloud
+ def role_list(self):
+ return [OvercloudRole(role) for role in self.roles]
- :return: Heat Stack associated with this Overcloud; or None
- if no Stack is associated, or no Stack can be
- found
- :rtype: heatclient.v1.stacks.Stack or None
- """
- return heat.OvercloudStack.get(self._request, self.stack_id,
- plan=self)
+ def parameter(self, param_name):
+ for parameter in self.parameters:
+ if parameter['name'] == param_name:
+ return parameter
+
+ def parameter_value(self, param_name):
+ parameter = self.parameter(param_name)
+ if parameter is not None:
+ return parameter['value']
-class OvercloudRole(base.APIResourceWrapper):
- _attrs = ('id', 'name', 'description', 'image_name', 'flavor_id')
+class OvercloudRole(base.APIDictWrapper):
+ _attrs = ('id', 'name', 'version', 'description', 'created_at')
@classmethod
@handle_errors(_("Unable to retrieve overcloud roles"), [])
@@ -229,7 +176,7 @@ class OvercloudRole(base.APIResourceWrapper):
are none
:rtype: list of tuskar_ui.api.tuskar.OvercloudRole
"""
- roles = tuskarclient(request).overcloud_roles.list()
+ roles = TEST_DATA.tuskarclient_roles.list()
return [cls(role) for role in roles]
@classmethod
@@ -247,47 +194,30 @@ class OvercloudRole(base.APIResourceWrapper):
OvercloudRole can be found
:rtype: tuskar_ui.api.tuskar.OvercloudRole
"""
- role = tuskarclient(request).overcloud_roles.get(role_id)
- return cls(role)
-
- @classmethod
- @handle_errors(_("Unable to retrieve overcloud role"))
- def get_by_node(cls, request, node):
- """Return the Tuskar OvercloudRole that is deployed on the node
-
- :param request: request object
- :type request: django.http.HttpRequest
-
- :param node: node to check against
- :type node: tuskar_ui.api.node.Node
-
- :return: matching OvercloudRole, or None if no matching
- OvercloudRole can be found
- :rtype: tuskar_ui.api.tuskar.OvercloudRole
- """
- roles = cls.list(request)
- for role in roles:
- if role.is_deployed_on_node(node):
+ for role in OvercloudRole.list(request):
+ if role.id == role_id:
return role
- def update(self, request, **kwargs):
- """Update the selected attributes of Tuskar OvercloudRole.
+ # 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
- :param request: request object
- :type request: django.http.HttpRequest
- """
- for attr in kwargs:
- if attr not in self._attrs:
- raise TypeError('Invalid parameter %r' % attr)
- tuskarclient(request).overcloud_roles.update(self.id, **kwargs)
+ # TODO(tzumainn): fix this once we know how this connection can be
+ # made
+ @property
+ def node_count_parameter_name(self):
+ return self.name + 'NodeCount'
- def is_deployed_on_node(self, node):
- """Determine whether a node matches an overcloud role
+ # TODO(tzumainn): fix this once we know how this connection can be
+ # made
+ @property
+ def image_id_parameter_name(self):
+ return self.name + 'ImageID'
- :param node: node to check against
- :type node: tuskar_ui.api.node.Node
-
- :return: does this node match the overcloud_role?
- :rtype: bool
- """
- return self.image_name == node.image_name
+ # TODO(tzumainn): fix this once we know how this connection can be
+ # made
+ @property
+ def flavor_id_parameter_name(self):
+ return self.name + 'FlavorID'
diff --git a/tuskar_ui/infrastructure/dashboard.py b/tuskar_ui/infrastructure/dashboard.py
index 27c24848c..93ad1c01b 100644
--- a/tuskar_ui/infrastructure/dashboard.py
+++ b/tuskar_ui/infrastructure/dashboard.py
@@ -21,6 +21,7 @@ class BasePanels(horizon.PanelGroup):
name = _("Infrastructure")
panels = (
'overcloud',
+ 'plans',
'nodes',
'flavors',
)
diff --git a/tuskar_ui/infrastructure/flavors/tests.py b/tuskar_ui/infrastructure/flavors/tests.py
index 65fb47a0c..e6bd35b3e 100644
--- a/tuskar_ui/infrastructure/flavors/tests.py
+++ b/tuskar_ui/infrastructure/flavors/tests.py
@@ -67,19 +67,7 @@ def _prepare_create():
class FlavorsTest(test.BaseAdminViewTests):
def test_index(self):
- roles = TEST_DATA.tuskarclient_overcloud_roles.list()
- with contextlib.nested(
- patch('openstack_dashboard.api.nova.flavor_list',
- return_value=TEST_DATA.novaclient_flavors.list()),
- patch('openstack_dashboard.api.nova.server_list',
- return_value=([], False)),
- patch('tuskar_ui.api.tuskar.OvercloudRole.list',
- return_value=roles),
- ) as (flavors_mock, servers_mock, role_list_mock):
- res = self.client.get(INDEX_URL)
- self.assertEqual(flavors_mock.call_count, 1)
- self.assertEqual(servers_mock.call_count, 1)
- self.assertEqual(role_list_mock.call_count, 1)
+ res = self.client.get(INDEX_URL)
self.assertTemplateUsed(res, 'infrastructure/flavors/index.html')
@@ -144,8 +132,6 @@ class FlavorsTest(test.BaseAdminViewTests):
res = self.client.post(INDEX_URL, data)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
- self.assertEqual(delete_mock.call_count, 2)
- self.assertEqual(server_list_mock.call_count, 1)
def test_delete_deployed_on_servers(self):
flavors = TEST_DATA.novaclient_flavors.list()
@@ -175,39 +161,11 @@ class FlavorsTest(test.BaseAdminViewTests):
self.assertMessageCount(error=1, warning=0)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
- self.assertEqual(delete_mock.call_count, 1)
- self.assertEqual(server_list_mock.call_count, 1)
-
- def test_delete_deployed_on_roles(self):
- flavors = TEST_DATA.novaclient_flavors.list()
- roles = TEST_DATA.tuskarclient_roles_with_flavors.list()
-
- data = {'action': 'flavors__delete',
- 'object_ids': [flavors[0].id, flavors[1].id]}
- with contextlib.nested(
- patch('openstack_dashboard.api.nova.flavor_delete'),
- patch('openstack_dashboard.api.nova.server_list',
- return_value=([], False)),
- patch('tuskar_ui.api.tuskar.OvercloudRole.list',
- return_value=roles),
- patch('openstack_dashboard.api.glance.image_list_detailed',
- return_value=([], False)),
- patch('openstack_dashboard.api.nova.flavor_list',
- return_value=TEST_DATA.novaclient_flavors.list())
- ) as (delete_mock, server_list_mock, _role_list_mock, _glance_mock,
- _flavors_mock):
- res = self.client.post(INDEX_URL, data)
- self.assertMessageCount(error=1, warning=0)
- self.assertNoFormErrors(res)
- self.assertRedirectsNoFollow(res, INDEX_URL)
- self.assertEqual(delete_mock.call_count, 1)
- self.assertEqual(server_list_mock.call_count, 1)
def test_details_no_overcloud(self):
flavor = api.flavor.Flavor(TEST_DATA.novaclient_flavors.first())
images = TEST_DATA.glanceclient_images.list()[:2]
- roles = TEST_DATA.tuskarclient_overcloud_roles.list()
- roles[0].flavor_id = flavor.id
+ roles = TEST_DATA.tuskarclient_roles.list()
with contextlib.nested(
patch('openstack_dashboard.api.glance.image_get',
side_effect=images),
@@ -222,7 +180,6 @@ class FlavorsTest(test.BaseAdminViewTests):
args=(flavor.id,)))
self.assertEqual(image_mock.call_count, 1) # memoized
self.assertEqual(get_mock.call_count, 1)
- self.assertEqual(roles_mock.call_count, 1)
self.assertEqual(plan_mock.call_count, 1)
self.assertTemplateUsed(res,
'infrastructure/flavors/details.html')
@@ -230,11 +187,10 @@ class FlavorsTest(test.BaseAdminViewTests):
def test_details(self):
flavor = api.flavor.Flavor(TEST_DATA.novaclient_flavors.first())
images = TEST_DATA.glanceclient_images.list()[:2]
- roles = TEST_DATA.tuskarclient_overcloud_roles.list()
- roles[0].flavor_id = flavor.id
+ roles = TEST_DATA.tuskarclient_roles.list()
plan = api.tuskar.OvercloudPlan(
- TEST_DATA.tuskarclient_overcloud_plans.first())
- stack = api.heat.OvercloudStack(
+ TEST_DATA.tuskarclient_plans.first())
+ stack = api.heat.Stack(
TEST_DATA.heatclient_stacks.first())
with contextlib.nested(
patch('openstack_dashboard.api.glance.image_get',
@@ -245,10 +201,10 @@ class FlavorsTest(test.BaseAdminViewTests):
return_value=roles),
patch('tuskar_ui.api.tuskar.OvercloudPlan.get_the_plan',
return_value=plan),
- patch('tuskar_ui.api.heat.OvercloudStack.get',
+ patch('tuskar_ui.api.heat.Stack.get',
return_value=stack),
# __name__ is required for horizon.tables
- patch('tuskar_ui.api.heat.OvercloudStack.resources_count',
+ patch('tuskar_ui.api.heat.Stack.resources_count',
return_value=42, __name__='')
) as (image_mock, get_mock, roles_mock, plan_mock, stack_mock,
count_mock):
@@ -256,10 +212,6 @@ class FlavorsTest(test.BaseAdminViewTests):
args=(flavor.id,)))
self.assertEqual(image_mock.call_count, 1) # memoized
self.assertEqual(get_mock.call_count, 1)
- self.assertEqual(roles_mock.call_count, 1)
self.assertEqual(plan_mock.call_count, 1)
- self.assertEqual(stack_mock.call_count, 1)
- self.assertEqual(count_mock.call_count, 1)
- self.assertListEqual(count_mock.call_args_list, [call(roles[0])])
self.assertTemplateUsed(res,
'infrastructure/flavors/details.html')
diff --git a/tuskar_ui/infrastructure/flavors/views.py b/tuskar_ui/infrastructure/flavors/views.py
index 0f368ca7b..6c113b00e 100644
--- a/tuskar_ui/infrastructure/flavors/views.py
+++ b/tuskar_ui/infrastructure/flavors/views.py
@@ -83,5 +83,6 @@ class DetailView(horizon.tables.DataTableView):
return context
def get_data(self):
- return [role for role in api.tuskar.OvercloudRole.list(self.request)
- if role.flavor_id == str(self.kwargs.get('flavor_id'))]
+ # TODO(tzumainn): fix role relation, if possible; the plan needs to be
+ # considered as well
+ return []
diff --git a/tuskar_ui/infrastructure/nodes/tables.py b/tuskar_ui/infrastructure/nodes/tables.py
index e483bdb8f..2236c9bc0 100644
--- a/tuskar_ui/infrastructure/nodes/tables.py
+++ b/tuskar_ui/infrastructure/nodes/tables.py
@@ -84,7 +84,7 @@ class NodesTable(tables.DataTable):
row_actions = ()
def get_object_id(self, datum):
- return datum.id
+ return datum.uuid
def get_object_display(self, datum):
return datum.uuid
diff --git a/tuskar_ui/infrastructure/nodes/tabs.py b/tuskar_ui/infrastructure/nodes/tabs.py
index c177c582b..7b45a5061 100644
--- a/tuskar_ui/infrastructure/nodes/tabs.py
+++ b/tuskar_ui/infrastructure/nodes/tabs.py
@@ -15,6 +15,7 @@
from django.core import urlresolvers
from django.utils.translation import ugettext_lazy as _
+from horizon import exceptions
from horizon import tabs
from tuskar_ui import api
@@ -63,12 +64,14 @@ class DeployedTab(tabs.TableTab):
if 'errors' in self.request.GET:
return api.node.filter_nodes(deployed_nodes, healthy=False)
- # TODO(tzumainn) ideally, the role should be a direct attribute
- # of a node; however, that cannot be done until the tuskar api
- # update that will prevent a circular dependency in the api
for node in deployed_nodes:
- node.role_name = api.tuskar.OvercloudRole.get_by_node(
- self.request, node).name
+ # TODO(tzumainn): this could probably be done more efficiently
+ # by getting the resource for all nodes at once
+ try:
+ resource = api.heat.Resource.get_by_node(self.request, node)
+ node.role_name = resource.role.name
+ except exceptions.NotFound:
+ node.role_name = '-'
return deployed_nodes
diff --git a/tuskar_ui/infrastructure/nodes/templates/nodes/_overview.html b/tuskar_ui/infrastructure/nodes/templates/nodes/_overview.html
index 069525001..c21f8dac3 100644
--- a/tuskar_ui/infrastructure/nodes/templates/nodes/_overview.html
+++ b/tuskar_ui/infrastructure/nodes/templates/nodes/_overview.html
@@ -19,10 +19,7 @@
{% if deployed_nodes_error %}
{% if deployed_nodes_error|length == 1 %}
- {% comment %}
- Replace id with uuid when ironicclient is used instead baremetalclient
- {% endcomment %}
- {% url 'horizon:infrastructure:nodes:detail' deployed_nodes_error.0.id as node_detail_url %}
+ {% url 'horizon:infrastructure:nodes:detail' deployed_nodes_error.0.uuid as node_detail_url %}
{% else %}
{% url 'horizon:infrastructure:nodes:index' as nodes_index_url %}
{% endif %}
diff --git a/tuskar_ui/infrastructure/nodes/tests.py b/tuskar_ui/infrastructure/nodes/tests.py
index b4fae4508..dfc03cd3d 100644
--- a/tuskar_ui/infrastructure/nodes/tests.py
+++ b/tuskar_ui/infrastructure/nodes/tests.py
@@ -61,13 +61,14 @@ class NodesTests(test.BaseAdminViewTests, helpers.APITestCase):
def test_free_nodes(self):
free_nodes = [api.node.Node(node)
for node in self.ironicclient_nodes.list()]
- roles = TEST_DATA.tuskarclient_overcloud_roles.list()
+ roles = [api.tuskar.OvercloudRole(r)
+ for r in TEST_DATA.tuskarclient_roles.list()]
instance = TEST_DATA.novaclient_servers.first()
image = TEST_DATA.glanceclient_images.first()
with contextlib.nested(
patch('tuskar_ui.api.tuskar.OvercloudRole', **{
- 'spec_set': ['list', 'name', 'get_by_node'],
+ 'spec_set': ['list', 'name'],
'list.return_value': roles,
}),
patch('tuskar_ui.api.node.Node', **{
@@ -106,13 +107,14 @@ class NodesTests(test.BaseAdminViewTests, helpers.APITestCase):
def test_deployed_nodes(self):
deployed_nodes = [api.node.Node(node)
for node in self.ironicclient_nodes.list()]
- roles = TEST_DATA.tuskarclient_overcloud_roles.list()
+ roles = [api.tuskar.OvercloudRole(r)
+ for r in TEST_DATA.tuskarclient_roles.list()]
instance = TEST_DATA.novaclient_servers.first()
image = TEST_DATA.glanceclient_images.first()
with contextlib.nested(
patch('tuskar_ui.api.tuskar.OvercloudRole', **{
- 'spec_set': ['list', 'name', 'get_by_node'],
+ 'spec_set': ['list', 'name'],
'list.return_value': roles,
}),
patch('tuskar_ui.api.node.Node', **{
diff --git a/tuskar_ui/infrastructure/nodes/views.py b/tuskar_ui/infrastructure/nodes/views.py
index 5d60d36b8..54788fa3e 100644
--- a/tuskar_ui/infrastructure/nodes/views.py
+++ b/tuskar_ui/infrastructure/nodes/views.py
@@ -79,7 +79,6 @@ class DetailView(horizon_views.APIView):
redirect = reverse_lazy('horizon:infrastructure:nodes:index')
node = api.node.Node.get(request, node_uuid, _error_redirect=redirect)
context['node'] = node
-
if api_base.is_service_enabled(request, 'metering'):
context['meters'] = (
('cpu', _('CPU')),
diff --git a/tuskar_ui/infrastructure/overcloud/forms.py b/tuskar_ui/infrastructure/overcloud/forms.py
index 4027e79d7..2b04e715d 100644
--- a/tuskar_ui/infrastructure/overcloud/forms.py
+++ b/tuskar_ui/infrastructure/overcloud/forms.py
@@ -12,12 +12,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-import django.forms
from django.utils.translation import ugettext_lazy as _
import horizon.exceptions
import horizon.forms
import horizon.messages
-from openstack_dashboard import api as horizon_api
from tuskar_ui import api
@@ -25,7 +23,8 @@ from tuskar_ui import api
class UndeployOvercloud(horizon.forms.SelfHandlingForm):
def handle(self, request, data):
try:
- api.tuskar.OvercloudPlan.delete(request, self.initial['plan_id'])
+ stack = api.heat.Stack.get(request, self.initial['stack_id'])
+ api.tuskar.OvercloudPlan.delete(request, stack.plan.id)
except Exception:
horizon.exceptions.handle(request,
_("Unable to undeploy overcloud."))
@@ -34,47 +33,3 @@ class UndeployOvercloud(horizon.forms.SelfHandlingForm):
msg = _('Undeployment in progress.')
horizon.messages.success(request, msg)
return True
-
-
-def get_flavor_choices(request):
- empty = [('', '----')]
- try:
- flavors = horizon_api.nova.flavor_list(request, None)
- except Exception:
- horizon.exceptions.handle(request,
- _('Unable to retrieve flavor list.'))
- return empty
- return empty + [(flavor.id, flavor.name) for flavor in flavors]
-
-
-class OvercloudRoleForm(horizon.forms.SelfHandlingForm):
- id = django.forms.IntegerField(
- widget=django.forms.HiddenInput)
- name = django.forms.CharField(
- label=_("Name"), required=False,
- widget=django.forms.TextInput(
- attrs={'readonly': 'readonly', 'disabled': 'disabled'}))
- description = django.forms.CharField(
- label=_("Description"), required=False,
- widget=django.forms.Textarea(
- attrs={'readonly': 'readonly', 'disabled': 'disabled'}))
- image_name = django.forms.CharField(
- label=_("Image"), required=False,
- widget=django.forms.TextInput(
- attrs={'readonly': 'readonly', 'disabled': 'disabled'}))
- flavor_id = django.forms.ChoiceField(
- label=_("Flavor"), required=False, choices=())
-
- def __init__(self, *args, **kwargs):
- super(OvercloudRoleForm, self).__init__(*args, **kwargs)
- self.fields['flavor_id'].choices = get_flavor_choices(self.request)
-
- def handle(self, request, context):
- try:
- role = api.tuskar.OvercloudRole.get(request, context['id'])
- role.update(request, flavor_id=context['flavor_id'])
- except Exception:
- horizon.exceptions.handle(request,
- _('Unable to update the role.'))
- return False
- return True
diff --git a/tuskar_ui/infrastructure/overcloud/tabs.py b/tuskar_ui/infrastructure/overcloud/tabs.py
index c840e05e9..3c2beedf9 100644
--- a/tuskar_ui/infrastructure/overcloud/tabs.py
+++ b/tuskar_ui/infrastructure/overcloud/tabs.py
@@ -16,33 +16,25 @@ from django.utils.translation import ugettext_lazy as _
import heatclient
from horizon import tabs
-from tuskar_ui import api
from tuskar_ui.infrastructure.overcloud import tables
from tuskar_ui import utils
-def _get_role_data(plan, role):
+def _get_role_data(stack, role):
"""Gathers data about a single deployment role from the related Overcloud
and OvercloudRole objects, and presents it in the form convenient for use
from the template.
- :param overcloud: Overcloud object
- :type overcloud: tuskar_ui.api.Overcloud
+ :param stack: Stack object
+ :type stack: tuskar_ui.api.heat.Stack
:param role: Role object
- :type role: tuskar_ui.api.OvercloudRole
+ :type role: tuskar_ui.api.tuskar.OvercloudRole
:return: dict with information about the role, to be used by template
:rtype: dict
"""
- resources = plan.stack.resources_by_role(role, with_joins=True)
+ resources = stack.resources_by_role(role, with_joins=True)
nodes = [r.node for r in resources]
- counts = getattr(plan, 'counts', [])
-
- for c in counts:
- if c['overcloud_role_id'] == role.id:
- node_count = c['num_nodes']
- break
- else:
- node_count = 0
+ node_count = len(nodes)
data = {
'role': role,
@@ -82,22 +74,23 @@ class OverviewTab(tabs.Tab):
preload = False
def get_context_data(self, request, **kwargs):
- plan = self.tab_group.kwargs['plan']
- roles = api.tuskar.OvercloudRole.list(request)
- role_data = [_get_role_data(plan, role) for role in roles]
+ stack = self.tab_group.kwargs['stack']
+ roles = stack.plan.role_list
+ role_data = [_get_role_data(stack, role) for role in roles]
total = sum(d['total_node_count'] for d in role_data)
progress = 100 * sum(d.get('deployed_node_count', 0)
for d in role_data) // (total or 1)
- events = plan.stack.events
+ events = stack.events
last_failed_events = [e for e in events
if e.resource_status == 'CREATE_FAILED'][-3:]
+
return {
- 'plan': plan,
- 'stack': plan.stack,
+ 'stack': stack,
+ 'plan': stack.plan,
'roles': role_data,
'progress': max(5, progress),
- 'dashboard_urls': plan.stack.dashboard_urls,
+ 'dashboard_urls': stack.dashboard_urls,
'last_failed_events': last_failed_events,
}
@@ -109,7 +102,7 @@ class UndeployInProgressTab(tabs.Tab):
preload = False
def get_context_data(self, request, **kwargs):
- plan = self.tab_group.kwargs['plan']
+ stack = self.tab_group.kwargs['stack']
# TODO(lsmola) since at this point we don't have total number of nodes
# we will hack this around, till API can show this information. So it
@@ -119,7 +112,7 @@ class UndeployInProgressTab(tabs.Tab):
try:
resources_count = len(
- plan.stack.resources(with_joins=False))
+ stack.resources(with_joins=False))
except heatclient.exc.HTTPNotFound:
# Immediately after undeploying has started, heat returns this
# exception so we can take it as kind of init of undeploying.
@@ -131,11 +124,12 @@ class UndeployInProgressTab(tabs.Tab):
delete_progress = max(
5, 100 * (total_num_nodes_count - resources_count))
- events = plan.stack.events
+ events = stack.events
last_failed_events = [e for e in events
if e.resource_status == 'DELETE_FAILED'][-3:]
return {
- 'plan': plan,
+ 'stack': stack,
+ 'plan': stack.plan,
'progress': delete_progress,
'last_failed_events': last_failed_events,
}
@@ -149,10 +143,10 @@ class ConfigurationTab(tabs.TableTab):
preload = False
def get_configuration_data(self):
- plan = self.tab_group.kwargs['plan']
+ stack = self.tab_group.kwargs['stack']
return [(utils.de_camel_case(key), value) for key, value in
- plan.stack.parameters.items()]
+ stack.parameters.items()]
class LogTab(tabs.TableTab):
@@ -163,8 +157,8 @@ class LogTab(tabs.TableTab):
preload = False
def get_log_data(self):
- plan = self.tab_group.kwargs['plan']
- return plan.stack.events
+ stack = self.tab_group.kwargs['stack']
+ return stack.events
class UndeployInProgressTabs(tabs.TabGroup):
diff --git a/tuskar_ui/infrastructure/overcloud/templates/overcloud/_detail_overview.html b/tuskar_ui/infrastructure/overcloud/templates/overcloud/_detail_overview.html
index 751d54355..f8b0b1bd8 100644
--- a/tuskar_ui/infrastructure/overcloud/templates/overcloud/_detail_overview.html
+++ b/tuskar_ui/infrastructure/overcloud/templates/overcloud/_detail_overview.html
@@ -191,7 +191,7 @@ nova flavor-create m1.tiny 1 512 2 1
{% for role in roles %}
{{ role.name }} ({{ role.total_node_count }})
|
diff --git a/tuskar_ui/infrastructure/overcloud/templates/overcloud/_role_edit.html b/tuskar_ui/infrastructure/overcloud/templates/overcloud/_role_edit.html
deleted file mode 100644
index c8eae3646..000000000
--- a/tuskar_ui/infrastructure/overcloud/templates/overcloud/_role_edit.html
+++ /dev/null
@@ -1,15 +0,0 @@
-{% extends "horizon/common/_modal_form.html" %}
-{% load i18n %}
-{% load url from future %}
-
-{% block form_id %}role_edit_form{% endblock %}
-{% block modal_id %}role_edit_modal{% endblock %}
-{% block modal-header %}{% trans "Edit Deployment Role" %}{% endblock %}
-{% block form_action %}{% url 'horizon:infrastructure:overcloud:role_edit' form.id.value %}{% endblock %}
-
-{% block modal-footer %}
-
- {% trans "Cancel" %}
-{% endblock %}
diff --git a/tuskar_ui/infrastructure/overcloud/templates/overcloud/_undeploy_confirmation.html b/tuskar_ui/infrastructure/overcloud/templates/overcloud/_undeploy_confirmation.html
index 9742d9ea1..a0f29085d 100644
--- a/tuskar_ui/infrastructure/overcloud/templates/overcloud/_undeploy_confirmation.html
+++ b/tuskar_ui/infrastructure/overcloud/templates/overcloud/_undeploy_confirmation.html
@@ -3,7 +3,7 @@
{% load url from future %}
{% block form_id %}provision_form{% endblock %}
-{% block form_action %}{% url 'horizon:infrastructure:overcloud:undeploy_confirmation' plan_id %}{% endblock %}
+{% block form_action %}{% url 'horizon:infrastructure:overcloud:undeploy_confirmation' stack_id %}{% endblock %}
{% block modal_id %}provision_modal{% endblock %}
{% block modal-header %}{% trans "Provisioning Confirmation" %}{% endblock %}
diff --git a/tuskar_ui/infrastructure/overcloud/templates/overcloud/_undeploy_in_progress.html b/tuskar_ui/infrastructure/overcloud/templates/overcloud/_undeploy_in_progress.html
index 468b8dbf7..a231f4fdd 100644
--- a/tuskar_ui/infrastructure/overcloud/templates/overcloud/_undeploy_in_progress.html
+++ b/tuskar_ui/infrastructure/overcloud/templates/overcloud/_undeploy_in_progress.html
@@ -4,7 +4,7 @@
- {% if plan.stack.is_deleting %}
+ {% if stack.is_deleting %}
diff --git a/tuskar_ui/infrastructure/overcloud/templates/overcloud/detail.html b/tuskar_ui/infrastructure/overcloud/templates/overcloud/detail.html
index 45ba22a46..dfc780896 100644
--- a/tuskar_ui/infrastructure/overcloud/templates/overcloud/detail.html
+++ b/tuskar_ui/infrastructure/overcloud/templates/overcloud/detail.html
@@ -19,20 +19,12 @@
diff --git a/tuskar_ui/infrastructure/overcloud/templates/overcloud/role_edit.html b/tuskar_ui/infrastructure/overcloud/templates/overcloud/role_edit.html
deleted file mode 100644
index 4cc1075c6..000000000
--- a/tuskar_ui/infrastructure/overcloud/templates/overcloud/role_edit.html
+++ /dev/null
@@ -1,11 +0,0 @@
-{% extends "base.html" %}
-{% load i18n %}
-{% block title %}{% trans "Edit Deployment Role" %}{% endblock %}
-
-{% block page_header %}
- {% include "horizon/common/_page_header.html" with title=_("Edit Deployment Role") %}
-{% endblock %}
-
-{% block main %}
- {% include "infrastructure/overcloud/_role_edit.html" %}
-{% endblock %}
diff --git a/tuskar_ui/infrastructure/overcloud/templates/overcloud/undeployed_overview.html b/tuskar_ui/infrastructure/overcloud/templates/overcloud/undeployed_overview.html
deleted file mode 100644
index 5c02e2ecd..000000000
--- a/tuskar_ui/infrastructure/overcloud/templates/overcloud/undeployed_overview.html
+++ /dev/null
@@ -1,33 +0,0 @@
-{% load i18n %}
-{% load url from future%}
-
-
- {{ step.get_help_text }}
-
-
-
-
- {{ step.get_free_nodes }} {% trans "free nodes" %}
-
- {% trans "Roles" %}
-
- {% include 'infrastructure/overcloud/node_counts.html' with form=form editable=True %}
-
-
-
-
-
diff --git a/tuskar_ui/infrastructure/overcloud/tests.py b/tuskar_ui/infrastructure/overcloud/tests.py
index 652071ec0..fc026963e 100644
--- a/tuskar_ui/infrastructure/overcloud/tests.py
+++ b/tuskar_ui/infrastructure/overcloud/tests.py
@@ -12,7 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import collections
import contextlib
from django.core import urlresolvers
@@ -29,10 +28,8 @@ from tuskar_ui.test.test_data import tuskar_data
INDEX_URL = urlresolvers.reverse(
'horizon:infrastructure:overcloud:index')
-CREATE_URL = urlresolvers.reverse(
- 'horizon:infrastructure:overcloud:create')
DETAIL_URL = urlresolvers.reverse(
- 'horizon:infrastructure:overcloud:detail', args=(1,))
+ 'horizon:infrastructure:overcloud:detail', args=('stack-id-1',))
UNDEPLOY_IN_PROGRESS_URL = urlresolvers.reverse(
'horizon:infrastructure:overcloud:undeploy_in_progress',
args=('overcloud',))
@@ -42,7 +39,10 @@ DETAIL_URL_CONFIGURATION_TAB = (DETAIL_URL +
"?tab=detail__configuration")
DETAIL_URL_LOG_TAB = (DETAIL_URL + "?tab=detail__log")
DELETE_URL = urlresolvers.reverse(
- 'horizon:infrastructure:overcloud:undeploy_confirmation', args=(1,))
+ 'horizon:infrastructure:overcloud:undeploy_confirmation',
+ args=('stack-id-1',))
+PLAN_CREATE_URL = urlresolvers.reverse(
+ 'horizon:infrastructure:plans:create')
TEST_DATA = utils.TestDataContainer()
flavor_data.data(TEST_DATA)
node_data.data(TEST_DATA)
@@ -53,49 +53,25 @@ tuskar_data.data(TEST_DATA)
@contextlib.contextmanager
def _mock_plan(**kwargs):
plan = None
- stack = api.heat.OvercloudStack(TEST_DATA.heatclient_stacks.first())
- stack.events = []
- stack.resources_by_role = lambda *args, **kwargs: []
- stack.resources = lambda *args, **kwargs: []
- stack.overcloud_keystone = None
- template_parameters = {
- "NeutronPublicInterfaceRawDevice": {
- "Default": "",
- "Type": "String",
- "NoEcho": "false",
- "Description": ("If set, the public interface is a vlan with this "
- "device as the raw device."),
- },
- "HeatPassword": {
- "Default": "unset",
- "Type": "String",
- "NoEcho": "true",
- "Description": ("The password for the Heat service account, used "
- "by the Heat services.")
- },
- }
params = {
'spec_set': [
- 'counts',
'create',
'delete',
'get',
'get_the_plan',
'id',
- 'stack',
'update',
- 'template_parameters',
+ 'parameters',
+ 'role_list',
],
- 'counts': [],
'create.side_effect': lambda *args, **kwargs: plan,
'delete.return_value': None,
'get.side_effect': lambda *args, **kwargs: plan,
'get_the_plan.side_effect': lambda *args, **kwargs: plan,
'id': 1,
- 'stack': stack,
'update.side_effect': lambda *args, **kwargs: plan,
- 'template_parameters.return_value': template_parameters,
+ 'role_list': [],
}
params.update(kwargs)
with patch(
@@ -111,12 +87,12 @@ class OvercloudTests(test.BaseAdminViewTests):
'get_the_plan.return_value': None}):
res = self.client.get(INDEX_URL)
- self.assertRedirectsNoFollow(res, CREATE_URL)
+ self.assertRedirectsNoFollow(res, PLAN_CREATE_URL)
def test_index_overcloud_deployed_stack_not_created(self):
with contextlib.nested(
_mock_plan(),
- patch('tuskar_ui.api.heat.OvercloudStack.is_deployed',
+ patch('tuskar_ui.api.heat.Stack.is_deployed',
return_value=False),
):
res = self.client.get(INDEX_URL)
@@ -128,147 +104,17 @@ class OvercloudTests(test.BaseAdminViewTests):
self.assertRedirectsNoFollow(res, DETAIL_URL)
def test_index_overcloud_deployed(self):
- with _mock_plan() as Overcloud:
+ with _mock_plan() as OvercloudPlan:
res = self.client.get(INDEX_URL)
- request = Overcloud.get_the_plan.call_args_list[0][0][0]
- self.assertListEqual(Overcloud.get_the_plan.call_args_list,
+ request = OvercloudPlan.get_the_plan.call_args_list[0][0][0]
+ self.assertListEqual(OvercloudPlan.get_the_plan.call_args_list,
[call(request)])
self.assertRedirectsNoFollow(res, DETAIL_URL)
- def test_create_get(self):
- roles = TEST_DATA.tuskarclient_overcloud_roles.list()
- with contextlib.nested(
- patch('tuskar_ui.api.tuskar.OvercloudRole', **{
- 'spec_set': ['list'],
- 'list.return_value': roles,
- }),
- _mock_plan(),
- patch('tuskar_ui.api.node.Node', **{
- 'spec_set': ['list'],
- 'list.return_value': [],
- }),
- patch('openstack_dashboard.api.nova', **{
- 'spec_set': ['flavor_list'],
- 'flavor_list.return_value': [],
- }),
- ):
- res = self.client.get(CREATE_URL)
- self.assertTemplateUsed(
- res, 'infrastructure/_fullscreen_workflow_base.html')
- self.assertTemplateUsed(
- res, 'infrastructure/overcloud/node_counts.html')
- self.assertTemplateUsed(
- res, 'infrastructure/overcloud/undeployed_overview.html')
-
- def test_create_post(self):
- node = TEST_DATA.ironicclient_nodes.first
- roles = TEST_DATA.tuskarclient_overcloud_roles.list()
- flavor = TEST_DATA.novaclient_flavors.first()
- old_flavor_id = roles[0].flavor_id
- roles[0].flavor_id = flavor.id
- data = {
- 'count__1__%s' % flavor.id: '1',
- 'count__2__': '0',
- 'count__3__': '0',
- 'count__4__': '0',
- }
- with contextlib.nested(
- patch('tuskar_ui.api.tuskar.OvercloudRole', **{
- 'spec_set': ['list'],
- 'list.return_value': roles,
- }),
- _mock_plan(),
- patch('tuskar_ui.api.node.Node', **{
- 'spec_set': ['list'],
- 'list.return_value': [node],
- }),
- patch('openstack_dashboard.api.nova', **{
- 'spec_set': ['flavor_list'],
- 'flavor_list.return_value': [flavor],
- }),
- ) as (OvercloudRole, Overcloud, Node, nova):
- res = self.client.post(CREATE_URL, data)
- request = Overcloud.create.call_args_list[0][0][0]
- self.assertListEqual(
- Overcloud.create.call_args_list,
- [
- call(request, {
- ('1', flavor.id): 1,
- ('2', ''): 0,
- ('3', ''): 0,
- ('4', ''): 0,
- }, {
- 'NeutronPublicInterfaceRawDevice': '',
- 'HeatPassword': '',
- }),
- ])
- roles[0].flavor_id = old_flavor_id
- self.assertRedirectsNoFollow(res, INDEX_URL)
-
- def test_create_post_invalid_flavor(self):
- roles = TEST_DATA.tuskarclient_overcloud_roles.list()
- old_flavor_id = roles[0].flavor_id
- roles[0].flavor_id = 'non-existing'
- data = {
- 'count__1__%s' % roles[0].flavor_id: '1',
- 'count__2__': '0',
- 'count__3__': '0',
- 'count__4__': '0',
- }
- with contextlib.nested(
- patch('tuskar_ui.api.tuskar.OvercloudRole', **{
- 'spec_set': ['list'],
- 'list.return_value': roles,
- }),
- _mock_plan(),
- patch('tuskar_ui.api.node.Node', **{
- 'spec_set': ['list'],
- 'list.return_value': [],
- }),
- patch('openstack_dashboard.api.nova', **{
- 'spec_set': ['flavor_list'],
- 'flavor_list.return_value': [],
- }),
- ) as (OvercloudRole, Overcloud, Node, nova):
- res = self.client.post(CREATE_URL, data)
- self.assertFormErrors(res)
- roles[0].flavor_id = old_flavor_id
-
- def test_create_post_not_enough_nodes(self):
- node = TEST_DATA.ironicclient_nodes.first
- roles = TEST_DATA.tuskarclient_overcloud_roles.list()
- flavor = TEST_DATA.novaclient_flavors.first()
- roles[0].flavor_id = flavor.id
- data = {
- 'count__1__%s' % flavor.id: '2',
- 'count__2__': '0',
- 'count__3__': '0',
- 'count__4__': '0',
- }
- with contextlib.nested(
- patch('tuskar_ui.api.tuskar.OvercloudRole', **{
- 'spec_set': ['list'],
- 'list.return_value': roles,
- }),
- _mock_plan(),
- patch('tuskar_ui.api.node.Node', **{
- 'spec_set': ['list'],
- 'list.return_value': [node],
- }),
- patch('openstack_dashboard.api.nova', **{
- 'spec_set': ['flavor_list'],
- 'flavor_list.return_value': [flavor],
- }),
- ):
- response = self.client.post(CREATE_URL, data)
- self.assertFormErrors(
- response,
- 1,
- 'This configuration requires 2 nodes, but only 1 is available.')
-
def test_detail_get(self):
- roles = TEST_DATA.tuskarclient_overcloud_roles.list()
+ roles = [api.tuskar.OvercloudRole(role)
+ for role in TEST_DATA.tuskarclient_roles.list()]
with contextlib.nested(
_mock_plan(),
@@ -276,7 +122,9 @@ class OvercloudTests(test.BaseAdminViewTests):
'spec_set': ['list'],
'list.return_value': roles,
}),
- ) as (Overcloud, OvercloudRole):
+ patch('tuskar_ui.api.heat.Stack.events',
+ return_value=[]),
+ ):
res = self.client.get(DETAIL_URL)
self.assertTemplateUsed(
@@ -298,7 +146,11 @@ class OvercloudTests(test.BaseAdminViewTests):
res, 'horizon/common/_detail_table.html')
def test_detail_get_log_tab(self):
- with _mock_plan():
+ with contextlib.nested(
+ _mock_plan(),
+ patch('tuskar_ui.api.heat.Stack.events',
+ return_value=[]),
+ ):
res = self.client.get(DETAIL_URL_LOG_TAB)
self.assertTemplateUsed(
@@ -321,10 +173,12 @@ class OvercloudTests(test.BaseAdminViewTests):
def test_undeploy_in_progress(self):
with contextlib.nested(
_mock_plan(),
- patch('tuskar_ui.api.heat.OvercloudStack.is_deleting',
+ patch('tuskar_ui.api.heat.Stack.is_deleting',
return_value=True),
- patch('tuskar_ui.api.heat.OvercloudStack.is_deployed',
+ patch('tuskar_ui.api.heat.Stack.is_deployed',
return_value=False),
+ patch('tuskar_ui.api.heat.Stack.events',
+ return_value=[]),
):
res = self.client.get(UNDEPLOY_IN_PROGRESS_URL)
@@ -340,7 +194,7 @@ class OvercloudTests(test.BaseAdminViewTests):
'get_the_plan.return_value': None}):
res = self.client.get(UNDEPLOY_IN_PROGRESS_URL)
- self.assertRedirectsNoFollow(res, CREATE_URL)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
def test_undeploy_in_progress_invalid(self):
with _mock_plan():
@@ -351,10 +205,12 @@ class OvercloudTests(test.BaseAdminViewTests):
def test_undeploy_in_progress_log_tab(self):
with contextlib.nested(
_mock_plan(),
- patch('tuskar_ui.api.heat.OvercloudStack.is_deleting',
+ patch('tuskar_ui.api.heat.Stack.is_deleting',
return_value=True),
- patch('tuskar_ui.api.heat.OvercloudStack.is_deployed',
+ patch('tuskar_ui.api.heat.Stack.is_deployed',
return_value=False),
+ patch('tuskar_ui.api.heat.Stack.events',
+ return_value=[]),
):
res = self.client.get(UNDEPLOY_IN_PROGRESS_URL_LOG_TAB)
@@ -364,137 +220,3 @@ class OvercloudTests(test.BaseAdminViewTests):
res, 'infrastructure/overcloud/_undeploy_in_progress.html')
self.assertTemplateUsed(
res, 'horizon/common/_detail_table.html')
-
- def test_scale_get(self):
- oc = None
- roles = TEST_DATA.tuskarclient_overcloud_roles.list()
- with contextlib.nested(
- patch('tuskar_ui.api.tuskar.OvercloudRole', **{
- 'spec_set': ['list'],
- 'list.return_value': roles,
- }),
- _mock_plan(counts=[{
- "overcloud_role_id": role.id,
- "num_nodes": 0,
- } for role in roles]),
- patch('openstack_dashboard.api.nova', **{
- 'spec_set': ['flavor_list'],
- 'flavor_list.return_value': [],
- }),
- ) as (OvercloudRole, Overcloud, nova):
- oc = Overcloud
- url = urlresolvers.reverse(
- 'horizon:infrastructure:overcloud:scale', args=(oc.id,))
- res = self.client.get(url)
- self.assertTemplateUsed(
- res, 'infrastructure/overcloud/scale_node_counts.html')
-
- def test_scale_post(self):
- node = TEST_DATA.ironicclient_nodes.first
- roles = TEST_DATA.tuskarclient_overcloud_roles.list()
- flavor = TEST_DATA.novaclient_flavors.first()
- old_flavor_id = roles[0].flavor_id
- roles[0].flavor_id = flavor.id
- data = {
- 'plan_id': '1',
- 'count__1__%s' % flavor.id: '1',
- 'count__2__': '0',
- 'count__3__': '0',
- 'count__4__': '0',
- }
- with contextlib.nested(
- patch('tuskar_ui.api.tuskar.OvercloudRole', **{
- 'spec_set': ['list'],
- 'list.return_value': roles,
- }),
- _mock_plan(counts=[{
- "overcloud_role_id": role.id,
- "num_nodes": 0,
- } for role in roles]),
- patch('tuskar_ui.api.node.Node', **{
- 'spec_set': ['list'],
- 'list.return_value': [node],
- }),
- patch('openstack_dashboard.api.nova', **{
- 'spec_set': ['flavor_list'],
- 'flavor_list.return_value': [flavor],
- }),
- ) as (OvercloudRole, Overcloud, Node, nova):
- url = urlresolvers.reverse(
- 'horizon:infrastructure:overcloud:scale', args=(Overcloud.id,))
- res = self.client.post(url, data)
-
- request = Overcloud.update.call_args_list[0][0][0]
- self.assertListEqual(
- Overcloud.update.call_args_list,
- [
- call(request, Overcloud.id, {
- ('1', flavor.id): 1,
- ('2', ''): 0,
- ('3', ''): 0,
- ('4', ''): 0,
- }, {}),
- ])
- roles[0].flavor_id = old_flavor_id
- self.assertRedirectsNoFollow(res, DETAIL_URL)
-
- def test_role_edit_get(self):
- role = TEST_DATA.tuskarclient_overcloud_roles.first()
- url = urlresolvers.reverse(
- 'horizon:infrastructure:overcloud:role_edit', args=(role.id,))
- with contextlib.nested(
- patch('tuskar_ui.api.tuskar.OvercloudRole', **{
- 'spec_set': ['get'],
- 'get.return_value': role,
- }),
- patch('openstack_dashboard.api.nova', **{
- 'spec_set': ['flavor_list'],
- 'flavor_list.return_value': [],
- }),
- ):
- res = self.client.get(url)
- self.assertTemplateUsed(
- res, 'infrastructure/overcloud/role_edit.html')
- self.assertTemplateUsed(
- res, 'infrastructure/overcloud/_role_edit.html')
-
- def test_role_edit_post(self):
- role = None
- Flavor = collections.namedtuple('Flavor', 'id name')
- flavor = Flavor('xxx', 'Xxx')
- with contextlib.nested(
- patch('tuskar_ui.api.tuskar.OvercloudRole', **{
- 'spec_set': [
- 'get',
- 'update',
- 'id',
- 'name',
- 'description',
- 'image_name',
- 'flavor_id',
- ],
- 'get.side_effect': lambda *args, **kwargs: role,
- 'name': 'Compute',
- 'description': '...',
- 'image_name': '',
- 'id': 1,
- 'flavor_id': '',
- }),
- patch('openstack_dashboard.api.nova', **{
- 'spec_set': ['flavor_list'],
- 'flavor_list.return_value': [flavor],
- }),
- ) as (OvercloudRole, nova):
- role = OvercloudRole
- url = urlresolvers.reverse(
- 'horizon:infrastructure:overcloud:role_edit', args=(role.id,))
- data = {
- 'id': str(role.id),
- 'flavor_id': flavor.id,
- }
- res = self.client.post(url, data)
- request = OvercloudRole.update.call_args_list[0][0][0]
- self.assertListEqual(
- OvercloudRole.update.call_args_list,
- [call(request, flavor_id=flavor.id)])
- self.assertRedirectsNoFollow(res, CREATE_URL)
diff --git a/tuskar_ui/infrastructure/overcloud/urls.py b/tuskar_ui/infrastructure/overcloud/urls.py
index 1365bca3b..6eb5ec9a4 100644
--- a/tuskar_ui/infrastructure/overcloud/urls.py
+++ b/tuskar_ui/infrastructure/overcloud/urls.py
@@ -20,19 +20,14 @@ from tuskar_ui.infrastructure.overcloud import views
urlpatterns = urls.patterns(
'',
urls.url(r'^$', views.IndexView.as_view(), name='index'),
- urls.url(r'^create/$', views.CreateView.as_view(), name='create'),
- urls.url(r'^(?P [^/]+)/undeploy-in-progress$',
+ urls.url(r'^(?P[^/]+)/undeploy-in-progress$',
views.UndeployInProgressView.as_view(),
name='undeploy_in_progress'),
- urls.url(r'^create/role-edit/(?P[^/]+)$',
- views.OvercloudRoleEdit.as_view(), name='role_edit'),
- urls.url(r'^(?P[^/]+)/$', views.DetailView.as_view(),
+ urls.url(r'^(?P[^/]+)/$', views.DetailView.as_view(),
name='detail'),
- urls.url(r'^(?P[^/]+)/scale$', views.Scale.as_view(),
- name='scale'),
- urls.url(r'^(?P[^/]+)/role/(?P[^/]+)$',
+ urls.url(r'^(?P[^/]+)/role/(?P[^/]+)$',
views.OvercloudRoleView.as_view(), name='role'),
- urls.url(r'^(?P[^/]+)/undeploy-confirmation$',
+ urls.url(r'^(?P[^/]+)/undeploy-confirmation$',
views.UndeployConfirmationView.as_view(),
name='undeploy_confirmation'),
)
diff --git a/tuskar_ui/infrastructure/overcloud/views.py b/tuskar_ui/infrastructure/overcloud/views.py
index 15990ee0c..68bfc1079 100644
--- a/tuskar_ui/infrastructure/overcloud/views.py
+++ b/tuskar_ui/infrastructure/overcloud/views.py
@@ -11,46 +11,39 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
-import novaclient
-
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from django.views.generic import base as base_views
-import heatclient
from horizon import exceptions as horizon_exceptions
import horizon.forms
from horizon import messages
from horizon import tables as horizon_tables
from horizon import tabs as horizon_tabs
from horizon.utils import memoized
-import horizon.workflows
-from openstack_dashboard.api import nova
from tuskar_ui import api
from tuskar_ui.infrastructure.overcloud import forms
from tuskar_ui.infrastructure.overcloud import tables
from tuskar_ui.infrastructure.overcloud import tabs
-from tuskar_ui.infrastructure.overcloud.workflows import scale
-from tuskar_ui.infrastructure.overcloud.workflows import undeployed
INDEX_URL = 'horizon:infrastructure:overcloud:index'
DETAIL_URL = 'horizon:infrastructure:overcloud:detail'
-CREATE_URL = 'horizon:infrastructure:overcloud:create'
+PLAN_CREATE_URL = 'horizon:infrastructure:plans:create'
UNDEPLOY_IN_PROGRESS_URL = (
'horizon:infrastructure:overcloud:undeploy_in_progress')
-class OvercloudPlanMixin(object):
+class StackMixin(object):
@memoized.memoized
- def get_plan(self, redirect=None):
+ def get_stack(self, redirect=None):
if redirect is None:
redirect = reverse(INDEX_URL)
- plan_id = self.kwargs['plan_id']
- plan = api.tuskar.OvercloudPlan.get(self.request, plan_id,
- _error_redirect=redirect)
- return plan
+ stack_id = self.kwargs['stack_id']
+ stack = api.heat.Stack.get(self.request, stack_id,
+ _error_redirect=redirect)
+ return stack
class OvercloudRoleMixin(object):
@@ -66,42 +59,39 @@ class IndexView(base_views.RedirectView):
permanent = False
def get_redirect_url(self):
- try:
- # TODO(lsmola) implement this properly when supported by API
- plan = api.tuskar.OvercloudPlan.get_the_plan(self.request)
- except heatclient.exc.HTTPNotFound:
- plan = None
+ plan = api.tuskar.OvercloudPlan.get_the_plan(self.request)
- redirect = None
- if plan is None:
- redirect = reverse(CREATE_URL)
- elif plan.stack.is_deleting or plan.stack.is_delete_failed:
- redirect = reverse(UNDEPLOY_IN_PROGRESS_URL,
- args=(plan.id,))
- else:
- redirect = reverse(DETAIL_URL,
- args=(plan.id,))
+ redirect = reverse(PLAN_CREATE_URL)
+ if plan is not None:
+ stacks = api.heat.Stack.list(self.request)
+ for stack in stacks:
+ if stack.plan.id == plan.id:
+ break
+ else:
+ stack = None
+ if stack is not None:
+ if stack.is_deleting or stack.is_delete_failed:
+ redirect = reverse(UNDEPLOY_IN_PROGRESS_URL,
+ args=(stack.id,))
+ else:
+ redirect = reverse(DETAIL_URL,
+ args=(stack.id,))
return redirect
-class CreateView(horizon.workflows.WorkflowView):
- workflow_class = undeployed.Workflow
- template_name = 'infrastructure/_fullscreen_workflow_base.html'
-
-
-class DetailView(horizon_tabs.TabView, OvercloudPlanMixin):
+class DetailView(horizon_tabs.TabView, StackMixin):
tab_group_class = tabs.DetailTabs
template_name = 'infrastructure/overcloud/detail.html'
def get_tabs(self, request, **kwargs):
- plan = self.get_plan()
- return self.tab_group_class(request, plan=plan, **kwargs)
+ stack = self.get_stack()
+ return self.tab_group_class(request, stack=stack, **kwargs)
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
- context['plan'] = self.get_plan()
- context['stack'] = self.get_plan().stack
+ context['stack'] = self.get_stack()
+ context['plan'] = self.get_stack().plan
return context
@@ -115,133 +105,95 @@ class UndeployConfirmationView(horizon.forms.ModalFormView):
def get_context_data(self, **kwargs):
context = super(UndeployConfirmationView,
self).get_context_data(**kwargs)
- context['plan_id'] = self.kwargs['plan_id']
+ context['stack_id'] = self.kwargs['stack_id']
return context
def get_initial(self, **kwargs):
initial = super(UndeployConfirmationView, self).get_initial(**kwargs)
- initial['plan_id'] = self.kwargs['plan_id']
+ initial['stack_id'] = self.kwargs['stack_id']
return initial
-class UndeployInProgressView(horizon_tabs.TabView, OvercloudPlanMixin, ):
+class UndeployInProgressView(horizon_tabs.TabView, StackMixin, ):
tab_group_class = tabs.UndeployInProgressTabs
template_name = 'infrastructure/overcloud/detail.html'
- def get_overcloud_plan_or_redirect(self):
- try:
- # TODO(lsmola) implement this properly when supported by API
- plan = api.tuskar.OvercloudPlan.get_the_plan(self.request)
- except heatclient.exc.HTTPNotFound:
- plan = None
+ def get_stack_or_redirect(self):
+ plan = api.tuskar.OvercloudPlan.get_the_plan(self.request)
+ stack = None
- if plan is None:
- redirect = reverse(CREATE_URL)
+ if plan is not None:
+ stack = None
+ stacks = api.heat.Stack.list(self.request)
+ for s in stacks:
+ if s.plan.id == plan.id:
+ stack = s
+ break
+
+ if stack is None:
+ redirect = reverse(INDEX_URL)
messages.success(self.request,
_("Undeploying of the Overcloud has finished."))
raise horizon_exceptions.Http302(redirect)
- elif plan.stack.is_deleting or plan.stack.is_delete_failed:
- return plan
+ elif stack.is_deleting or stack.is_delete_failed:
+ return stack
else:
messages.error(self.request,
_("Overcloud is not being undeployed."))
redirect = reverse(DETAIL_URL,
- args=(plan.id,))
+ args=(stack.id,))
raise horizon_exceptions.Http302(redirect)
def get_tabs(self, request, **kwargs):
- plan = self.get_overcloud_plan_or_redirect()
- return self.tab_group_class(request, plan=plan, **kwargs)
+ stack = self.get_stack_or_redirect()
+ return self.tab_group_class(request, stack=stack, **kwargs)
def get_context_data(self, **kwargs):
context = super(UndeployInProgressView,
self).get_context_data(**kwargs)
- context['plan'] = self.get_overcloud_plan_or_redirect()
+ context['stack'] = self.get_stack_or_redirect()
return context
-class Scale(horizon.workflows.WorkflowView, OvercloudPlanMixin):
- workflow_class = scale.Workflow
-
- def get_context_data(self, **kwargs):
- context = super(Scale, self).get_context_data(**kwargs)
- context['plan_id'] = self.kwargs['plan_id']
- return context
-
- def get_initial(self):
- plan = self.get_plan()
- overcloud_roles = dict((overcloud_role.id, overcloud_role)
- for overcloud_role in
- api.tuskar.OvercloudRole.list(self.request))
-
- role_counts = dict((
- (count['overcloud_role_id'],
- overcloud_roles[count['overcloud_role_id']].flavor_id),
- count['num_nodes'],
- ) for count in plan.counts)
- return {
- 'plan_id': plan.id,
- 'role_counts': role_counts,
- }
-
-
class OvercloudRoleView(horizon_tables.DataTableView,
- OvercloudRoleMixin, OvercloudPlanMixin):
+ OvercloudRoleMixin, StackMixin):
table_class = tables.OvercloudRoleNodeTable
template_name = 'infrastructure/overcloud/overcloud_role.html'
@memoized.memoized
- def _get_nodes(self, plan, role):
- resources = plan.stack.resources_by_role(role, with_joins=True)
+ def _get_nodes(self, stack, role):
+ resources = stack.resources_by_role(role, with_joins=True)
nodes = [r.node for r in resources]
- # TODO(akrivoka) ideally, the role should be a direct attribute
- # of a node; however, that cannot be done until the tuskar api
- # update that will prevent a circular dependency in the api
+
for node in nodes:
- node.role_name = role.name
+ # TODO(tzumainn): this could probably be done more efficiently
+ # by getting the resource for all nodes at once
+ try:
+ resource = api.heat.Resource.get_by_node(self.request, node)
+ node.role_name = resource.role.name
+ except horizon_exceptions.NotFound:
+ node.role_name = '-'
+
return nodes
def get_data(self):
- plan = self.get_plan()
+ stack = self.get_stack()
redirect = reverse(DETAIL_URL,
- args=(plan.id,))
+ args=(stack.id,))
role = self.get_role(redirect)
- return self._get_nodes(plan, role)
+ return self._get_nodes(stack, role)
def get_context_data(self, **kwargs):
context = super(OvercloudRoleView, self).get_context_data(**kwargs)
- plan = self.get_plan()
+ stack = self.get_stack()
redirect = reverse(DETAIL_URL,
- args=(plan.id,))
+ args=(stack.id,))
role = self.get_role(redirect)
context['role'] = role
- context['image_name'] = role.image_name
- context['nodes'] = self._get_nodes(plan, role)
+ # TODO(tzumainn) we need to do this from plan parameters
+ context['image_name'] = 'FIXME'
+ context['nodes'] = self._get_nodes(stack, role)
+ context['flavor'] = None
- try:
- context['flavor'] = nova.flavor_get(self.request, role.flavor_id)
- except novaclient.exceptions.NotFound:
- context['flavor'] = None
- except Exception:
- msg = _('Unable to retrieve flavor.')
- horizon.exceptions.handle(self.request, msg)
return context
-
-
-class OvercloudRoleEdit(horizon.forms.ModalFormView, OvercloudRoleMixin):
- form_class = forms.OvercloudRoleForm
- template_name = 'infrastructure/overcloud/role_edit.html'
-
- def get_success_url(self):
- return reverse(CREATE_URL)
-
- def get_initial(self):
- role = self.get_role()
- return {
- 'id': role.id,
- 'name': role.name,
- 'description': role.description,
- 'image_name': role.image_name,
- 'flavor_id': role.flavor_id,
- }
diff --git a/tuskar_ui/infrastructure/overcloud/workflows/undeployed_overview.py b/tuskar_ui/infrastructure/overcloud/workflows/undeployed_overview.py
deleted file mode 100644
index 6bb01b1ee..000000000
--- a/tuskar_ui/infrastructure/overcloud/workflows/undeployed_overview.py
+++ /dev/null
@@ -1,134 +0,0 @@
-# -*- coding: utf8 -*-
-#
-# 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.
-
-import django.forms
-from django.utils.translation import ugettext_lazy as _
-from horizon.utils import memoized
-import horizon.workflows
-from openstack_dashboard import api as horizon_api
-
-from tuskar_ui import api
-import tuskar_ui.forms
-
-
-def get_role_id_and_flavor_id_from_field_name(field_name):
- """Extract the ids of overcloud role and flavor from the field
- name.
- """
- _count, role_id, flavor_id = field_name.split('__', 2)
- return role_id, flavor_id
-
-
-def get_field_name_from_role_id_and_flavor_id(role_id, flavor_id=''):
- """Compose the ids of overcloud role and flavor into a field name."""
- return 'count__%s__%s' % (role_id, flavor_id)
-
-
-class Action(horizon.workflows.Action):
- class Meta:
- slug = 'undeployed_overview'
- name = _("Overview")
-
- def _get_flavor_names(self):
- # Get all flavors in one call, instead of getting them one by one.
- try:
- flavors = horizon_api.nova.flavor_list(self.request, None)
- except Exception:
- horizon.exceptions.handle(self.request,
- _('Unable to retrieve flavor list.'))
- flavors = []
- return dict((str(flavor.id), flavor.name) for flavor in flavors)
-
- def _get_flavors(self, role, flavor_names):
- # TODO(rdopieralski) Get a list of flavors for each
- # role here, when we support multiple flavors per role.
- if role.flavor_id and role.flavor_id in flavor_names:
- flavors = [(
- role.flavor_id,
- flavor_names[role.flavor_id],
- )]
- else:
- flavors = []
- return flavors
-
- def __init__(self, *args, **kwargs):
- super(Action, self).__init__(*args, **kwargs)
- flavor_names = self._get_flavor_names()
- for role in self._get_roles():
- if role.name == 'Controller':
- initial = 1
- attrs = {'readonly': 'readonly'}
- else:
- initial = 0
- attrs = {}
- flavors = self._get_flavors(role, flavor_names)
- if not flavors:
- name = get_field_name_from_role_id_and_flavor_id(str(role.id))
- attrs = {'readonly': 'readonly'}
- self.fields[name] = django.forms.IntegerField(
- label='', initial=initial, min_value=initial,
- widget=tuskar_ui.forms.NumberPickerInput(attrs=attrs))
- for flavor_id, label in flavors:
- name = get_field_name_from_role_id_and_flavor_id(
- str(role.id), flavor_id)
- self.fields[name] = django.forms.IntegerField(
- label=label, initial=initial, min_value=initial,
- widget=tuskar_ui.forms.NumberPickerInput(attrs=attrs))
-
- def roles_fieldset(self):
- """Iterates over lists of fields for each role."""
- for role in self._get_roles():
- yield (
- role.id,
- role.name,
- list(tuskar_ui.forms.fieldset(
- self, prefix=get_field_name_from_role_id_and_flavor_id(
- str(role.id)))),
- )
-
- @memoized.memoized
- def _get_roles(self):
- """Retrieve the list of all overcloud roles."""
- return api.tuskar.OvercloudRole.list(self.request)
-
- def clean(self):
- for key, value in self.cleaned_data.iteritems():
- if not key.startswith('count_'):
- continue
- role_id, flavor = get_role_id_and_flavor_id_from_field_name(key)
- if int(value) and not flavor:
- raise django.forms.ValidationError(
- _("Can't deploy nodes without a flavor assigned."))
- return self.cleaned_data
-
-
-class Step(horizon.workflows.Step):
- action_class = Action
- contributes = ('role_counts',)
- template_name = 'infrastructure/overcloud/undeployed_overview.html'
- help_text = _("Nothing deployed yet. Design your first deployment.")
-
- def get_free_nodes(self):
- """Get the count of nodes that are not assigned yet."""
- return len(api.node.Node.list(self.workflow.request, False))
-
- def contribute(self, data, context):
- counts = {}
- for key, value in data.iteritems():
- if not key.startswith('count_'):
- continue
- count, role_id, flavor = key.split('__', 2)
- counts[role_id, flavor] = int(value)
- context['role_counts'] = counts
- return context
diff --git a/tuskar_ui/infrastructure/overcloud/workflows/__init__.py b/tuskar_ui/infrastructure/plans/__init__.py
similarity index 100%
rename from tuskar_ui/infrastructure/overcloud/workflows/__init__.py
rename to tuskar_ui/infrastructure/plans/__init__.py
diff --git a/tuskar_ui/infrastructure/plans/forms.py b/tuskar_ui/infrastructure/plans/forms.py
new file mode 100644
index 000000000..5d04b348f
--- /dev/null
+++ b/tuskar_ui/infrastructure/plans/forms.py
@@ -0,0 +1,66 @@
+# -*- coding: utf8 -*-
+#
+# 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.
+
+import django.forms
+from django.utils.translation import ugettext_lazy as _
+import horizon.exceptions
+import horizon.forms
+import horizon.messages
+from openstack_dashboard import api as horizon_api
+
+from tuskar_ui import api
+
+
+def get_flavor_choices(request):
+ empty = [('', '----')]
+ try:
+ flavors = horizon_api.nova.flavor_list(request, None)
+ except Exception:
+ horizon.exceptions.handle(request,
+ _('Unable to retrieve flavor list.'))
+ return empty
+ return empty + [(flavor.id, flavor.name) for flavor in flavors]
+
+
+class OvercloudRoleForm(horizon.forms.SelfHandlingForm):
+ id = django.forms.IntegerField(
+ widget=django.forms.HiddenInput)
+ name = django.forms.CharField(
+ label=_("Name"), required=False,
+ widget=django.forms.TextInput(
+ attrs={'readonly': 'readonly', 'disabled': 'disabled'}))
+ description = django.forms.CharField(
+ label=_("Description"), required=False,
+ widget=django.forms.Textarea(
+ attrs={'readonly': 'readonly', 'disabled': 'disabled'}))
+ image_name = django.forms.CharField(
+ label=_("Image"), required=False,
+ widget=django.forms.TextInput(
+ attrs={'readonly': 'readonly', 'disabled': 'disabled'}))
+ flavor_id = django.forms.ChoiceField(
+ label=_("Flavor"), required=False, choices=())
+
+ def __init__(self, *args, **kwargs):
+ super(OvercloudRoleForm, self).__init__(*args, **kwargs)
+ self.fields['flavor_id'].choices = get_flavor_choices(self.request)
+
+ def handle(self, request, context):
+ try:
+ role = api.tuskar.OvercloudRole.get(request, context['id'])
+ role.update(request, flavor_id=context['flavor_id'])
+ except Exception:
+ horizon.exceptions.handle(request,
+ _('Unable to update the role.'))
+ return False
+ return True
diff --git a/tuskar_ui/infrastructure/plans/panel.py b/tuskar_ui/infrastructure/plans/panel.py
new file mode 100644
index 000000000..87247f997
--- /dev/null
+++ b/tuskar_ui/infrastructure/plans/panel.py
@@ -0,0 +1,27 @@
+# -*- coding: utf8 -*-
+#
+# 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 django.utils.translation import ugettext_lazy as _
+
+import horizon
+
+from tuskar_ui.infrastructure import dashboard
+
+
+class Plans(horizon.Panel):
+ name = _("Plans")
+ slug = "plans"
+
+
+dashboard.Infrastructure.register(Plans)
diff --git a/tuskar_ui/infrastructure/plans/tables.py b/tuskar_ui/infrastructure/plans/tables.py
new file mode 100644
index 000000000..2be5d6f91
--- /dev/null
+++ b/tuskar_ui/infrastructure/plans/tables.py
@@ -0,0 +1,35 @@
+# -*- coding: utf8 -*-
+#
+# 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 django.utils.translation import ugettext_lazy as _
+
+from horizon import tables
+
+
+class ConfigurationTable(tables.DataTable):
+
+ key = tables.Column(lambda parameter: parameter[0],
+ verbose_name=_("Attribute Name"))
+ value = tables.Column(lambda parameter: parameter[1],
+ verbose_name=_("Attribute Value"))
+
+ class Meta:
+ name = "configuration"
+ verbose_name = _("Configuration")
+ multi_select = False
+ table_actions = ()
+ row_actions = ()
+
+ def get_object_id(self, datum):
+ return datum[0]
diff --git a/tuskar_ui/infrastructure/overcloud/templates/overcloud/undeployed_configuration.html b/tuskar_ui/infrastructure/plans/templates/plans/create_configuration.html
similarity index 100%
rename from tuskar_ui/infrastructure/overcloud/templates/overcloud/undeployed_configuration.html
rename to tuskar_ui/infrastructure/plans/templates/plans/create_configuration.html
diff --git a/tuskar_ui/infrastructure/plans/templates/plans/create_overview.html b/tuskar_ui/infrastructure/plans/templates/plans/create_overview.html
new file mode 100644
index 000000000..bb8e889b8
--- /dev/null
+++ b/tuskar_ui/infrastructure/plans/templates/plans/create_overview.html
@@ -0,0 +1,16 @@
+{% load i18n %}
+{% load url from future%}
+
+
+{{ step.get_help_text }}
+
+
+
+ {% trans "Roles" %}
+
+
+ {% include "horizon/common/_form_fields.html" %}
+ |
+
+
+
diff --git a/tuskar_ui/infrastructure/overcloud/templates/overcloud/node_counts.html b/tuskar_ui/infrastructure/plans/templates/plans/node_counts.html
similarity index 71%
rename from tuskar_ui/infrastructure/overcloud/templates/overcloud/node_counts.html
rename to tuskar_ui/infrastructure/plans/templates/plans/node_counts.html
index cb4956a42..e8c8ff063 100644
--- a/tuskar_ui/infrastructure/overcloud/templates/overcloud/node_counts.html
+++ b/tuskar_ui/infrastructure/plans/templates/plans/node_counts.html
@@ -17,23 +17,12 @@
{% if forloop.first %}
- {% if editable %}
-
- {% endif %}
{{ label }}
|
{% endif %}
{% if field.field.label %}
{{ field.label }}
- {% elif editable %}
- ({% trans "Add a flavor" %})
{% else %}
({% trans "No flavor" %})
{% endif %}
diff --git a/tuskar_ui/infrastructure/overcloud/templates/overcloud/scale_node_counts.html b/tuskar_ui/infrastructure/plans/templates/plans/scale_node_counts.html
similarity index 100%
rename from tuskar_ui/infrastructure/overcloud/templates/overcloud/scale_node_counts.html
rename to tuskar_ui/infrastructure/plans/templates/plans/scale_node_counts.html
diff --git a/tuskar_ui/infrastructure/plans/tests.py b/tuskar_ui/infrastructure/plans/tests.py
new file mode 100644
index 000000000..1c1e6a572
--- /dev/null
+++ b/tuskar_ui/infrastructure/plans/tests.py
@@ -0,0 +1,65 @@
+# -*- coding: utf8 -*-
+#
+# 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.
+
+import contextlib
+
+from django.core import urlresolvers
+from mock import patch, call # noqa
+from openstack_dashboard.test.test_data import utils
+
+from tuskar_ui import api
+from tuskar_ui.test import helpers as test
+from tuskar_ui.test.test_data import flavor_data
+from tuskar_ui.test.test_data import heat_data
+from tuskar_ui.test.test_data import node_data
+from tuskar_ui.test.test_data import tuskar_data
+
+
+INDEX_URL = urlresolvers.reverse(
+ 'horizon:infrastructure:plans:index')
+CREATE_URL = urlresolvers.reverse(
+ 'horizon:infrastructure:plans:create')
+OVERCLOUD_INDEX_URL = urlresolvers.reverse(
+ 'horizon:infrastructure:overcloud:index')
+
+TEST_DATA = utils.TestDataContainer()
+flavor_data.data(TEST_DATA)
+node_data.data(TEST_DATA)
+heat_data.data(TEST_DATA)
+tuskar_data.data(TEST_DATA)
+
+
+class OvercloudTests(test.BaseAdminViewTests):
+
+ def test_index_no_plan_get(self):
+ with contextlib.nested(
+ patch('tuskar_ui.api.tuskar.OvercloudPlan.get_the_plan',
+ return_value=None),
+ ):
+ res = self.client.get(INDEX_URL)
+ self.assertRedirectsNoFollow(res, CREATE_URL)
+
+ def test_index_with_plan_get(self):
+ plan = api.tuskar.OvercloudPlan(TEST_DATA.tuskarclient_plans.first())
+ with contextlib.nested(
+ patch('tuskar_ui.api.tuskar.OvercloudPlan.get_the_plan',
+ return_value=plan),
+ ):
+ res = self.client.get(INDEX_URL)
+ request = api.tuskar.OvercloudPlan.get_the_plan. \
+ call_args_list[0][0][0]
+ self.assertListEqual(
+ api.tuskar.OvercloudPlan.get_the_plan.call_args_list,
+ [call(request)])
+ self.assertRedirectsNoFollow(res, OVERCLOUD_INDEX_URL)
diff --git a/tuskar_ui/infrastructure/plans/urls.py b/tuskar_ui/infrastructure/plans/urls.py
new file mode 100644
index 000000000..7ffa5d271
--- /dev/null
+++ b/tuskar_ui/infrastructure/plans/urls.py
@@ -0,0 +1,26 @@
+# -*- coding: utf8 -*-
+#
+# 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 django.conf import urls
+
+from tuskar_ui.infrastructure.plans import views
+
+
+urlpatterns = urls.patterns(
+ '',
+ urls.url(r'^$', views.IndexView.as_view(), name='index'),
+ urls.url(r'^create/$', views.CreateView.as_view(), name='create'),
+ urls.url(r'^(?P[^/]+)/scale$', views.Scale.as_view(),
+ name='scale'),
+)
diff --git a/tuskar_ui/infrastructure/plans/views.py b/tuskar_ui/infrastructure/plans/views.py
new file mode 100644
index 000000000..a8958d863
--- /dev/null
+++ b/tuskar_ui/infrastructure/plans/views.py
@@ -0,0 +1,91 @@
+# -*- coding: utf8 -*-
+#
+# 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 django.core.urlresolvers import reverse
+from django.views.generic import base as base_views
+
+from horizon.utils import memoized
+import horizon.workflows
+
+from tuskar_ui import api
+from tuskar_ui.infrastructure.plans.workflows import create
+from tuskar_ui.infrastructure.plans.workflows import scale
+
+
+INDEX_URL = 'horizon:infrastructure:plans:index'
+CREATE_URL = 'horizon:infrastructure:plans:create'
+OVERCLOUD_INDEX_URL = 'horizon:infrastructure:overcloud:index'
+
+
+class OvercloudPlanMixin(object):
+ @memoized.memoized
+ def get_plan(self, redirect=None):
+ if redirect is None:
+ redirect = reverse(INDEX_URL)
+ plan_id = self.kwargs['plan_id']
+ plan = api.tuskar.OvercloudPlan.get(self.request, plan_id,
+ _error_redirect=redirect)
+ return plan
+
+
+class OvercloudRoleMixin(object):
+ @memoized.memoized
+ def get_role(self, redirect=None):
+ role_id = self.kwargs['role_id']
+ role = api.tuskar.OvercloudRole.get(self.request, role_id,
+ _error_redirect=redirect)
+ return role
+
+
+class IndexView(base_views.RedirectView):
+ permanent = False
+
+ def get_redirect_url(self):
+ plan = api.tuskar.OvercloudPlan.get_the_plan(self.request)
+
+ if plan is None:
+ redirect = reverse(CREATE_URL)
+ else:
+ redirect = reverse(OVERCLOUD_INDEX_URL)
+
+ return redirect
+
+
+class CreateView(horizon.workflows.WorkflowView):
+ workflow_class = create.Workflow
+ template_name = 'infrastructure/_fullscreen_workflow_base.html'
+
+
+class Scale(horizon.workflows.WorkflowView, OvercloudPlanMixin):
+ workflow_class = scale.Workflow
+
+ def get_context_data(self, **kwargs):
+ context = super(Scale, self).get_context_data(**kwargs)
+ context['plan_id'] = self.kwargs['plan_id']
+ return context
+
+ def get_initial(self):
+ plan = self.get_plan()
+ overcloud_roles = dict((overcloud_role.id, overcloud_role)
+ for overcloud_role in
+ api.tuskar.OvercloudRole.list(self.request))
+
+ role_counts = dict((
+ (count['overcloud_role_id'],
+ overcloud_roles[count['overcloud_role_id']].flavor_id),
+ count['num_nodes'],
+ ) for count in plan.counts)
+ return {
+ 'plan_id': plan.id,
+ 'role_counts': role_counts,
+ }
diff --git a/tuskar_ui/infrastructure/plans/workflows/__init__.py b/tuskar_ui/infrastructure/plans/workflows/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tuskar_ui/infrastructure/overcloud/workflows/undeployed.py b/tuskar_ui/infrastructure/plans/workflows/create.py
similarity index 79%
rename from tuskar_ui/infrastructure/overcloud/workflows/undeployed.py
rename to tuskar_ui/infrastructure/plans/workflows/create.py
index a0ca717a6..fe083544d 100644
--- a/tuskar_ui/infrastructure/overcloud/workflows/undeployed.py
+++ b/tuskar_ui/infrastructure/plans/workflows/create.py
@@ -19,9 +19,8 @@ from django.utils.translation import ugettext_lazy as _
import horizon.workflows
from tuskar_ui import api
-from tuskar_ui.infrastructure.overcloud.workflows\
- import undeployed_configuration
-from tuskar_ui.infrastructure.overcloud.workflows import undeployed_overview
+from tuskar_ui.infrastructure.plans.workflows import create_configuration
+from tuskar_ui.infrastructure.plans.workflows import create_overview
LOG = logging.getLogger(__name__)
@@ -46,18 +45,18 @@ class DeploymentValidationMixin(object):
free)
m2 %= {'free': free}
message = unicode(translation.string_concat(m1, m2))
- self.add_error_to_step(message, 'undeployed_overview')
+ self.add_error_to_step(message, 'create_overview')
self.add_error_to_step(message, 'scale_node_counts')
return False
return super(DeploymentValidationMixin, self).validate(context)
class Workflow(DeploymentValidationMixin, horizon.workflows.Workflow):
- slug = 'undeployed_overcloud'
- name = _("My OpenStack Deployment")
+ slug = 'create_plan'
+ name = _("My OpenStack Deployment Plan")
default_steps = (
- undeployed_overview.Step,
- undeployed_configuration.Step,
+ create_overview.Step,
+ create_configuration.Step,
)
finalize_button_name = _("Deploy")
success_message = _("OpenStack deployment launched")
@@ -66,14 +65,13 @@ class Workflow(DeploymentValidationMixin, horizon.workflows.Workflow):
def handle(self, request, context):
try:
api.tuskar.OvercloudPlan.create(
- self.request, context['role_counts'],
- context['configuration'])
+ self.request, 'overcloud', 'overcloud')
except Exception as e:
# Showing error in both workflow tabs, because from the exception
# type we can't recognize where it should show
msg = unicode(e)
- self.add_error_to_step(msg, 'undeployed_overview')
- self.add_error_to_step(msg, 'deployed_configuration')
- LOG.exception('Error creating overcloud')
+ self.add_error_to_step(msg, 'create_overview')
+ self.add_error_to_step(msg, 'create_configuration')
+ LOG.exception('Error creating overcloud plan')
raise django.forms.ValidationError(msg)
return True
diff --git a/tuskar_ui/infrastructure/overcloud/workflows/undeployed_configuration.py b/tuskar_ui/infrastructure/plans/workflows/create_configuration.py
similarity index 94%
rename from tuskar_ui/infrastructure/overcloud/workflows/undeployed_configuration.py
rename to tuskar_ui/infrastructure/plans/workflows/create_configuration.py
index bc6697d5c..41a9f84de 100644
--- a/tuskar_ui/infrastructure/overcloud/workflows/undeployed_configuration.py
+++ b/tuskar_ui/infrastructure/plans/workflows/create_configuration.py
@@ -16,7 +16,6 @@ from django.utils.translation import ugettext_lazy as _
import horizon.workflows
from openstack_dashboard.api import neutron
-from tuskar_ui import api
from tuskar_ui import utils
@@ -57,7 +56,7 @@ class Action(horizon.workflows.Action):
def __init__(self, request, *args, **kwargs):
super(Action, self).__init__(request, *args, **kwargs)
- params = api.tuskar.OvercloudPlan.template_parameters(request).items()
+ params = []
params.sort()
for name, data in params:
@@ -83,7 +82,7 @@ class Action(horizon.workflows.Action):
class Step(horizon.workflows.Step):
action_class = Action
contributes = ('configuration',)
- template_name = 'infrastructure/overcloud/undeployed_configuration.html'
+ template_name = 'infrastructure/plans/create_configuration.html'
def contribute(self, data, context):
context['configuration'] = data
diff --git a/tuskar_ui/infrastructure/plans/workflows/create_overview.py b/tuskar_ui/infrastructure/plans/workflows/create_overview.py
new file mode 100644
index 000000000..45284694d
--- /dev/null
+++ b/tuskar_ui/infrastructure/plans/workflows/create_overview.py
@@ -0,0 +1,53 @@
+# -*- coding: utf8 -*-
+#
+# 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 django.utils.translation import ugettext_lazy as _
+from horizon import forms
+import horizon.workflows
+
+from tuskar_ui import api
+
+
+class Action(horizon.workflows.Action):
+
+ role_ids = forms.MultipleChoiceField(
+ label=_("Roles"),
+ required=True,
+ widget=forms.CheckboxSelectMultiple(),
+ help_text=_("Select roles for this plan."))
+
+ class Meta:
+ slug = 'create_overview'
+ name = _("Overview")
+
+ def __init__(self, *args, **kwargs):
+ super(Action, self).__init__(*args, **kwargs)
+
+ role_ids_choices = []
+ roles = api.tuskar.OvercloudRole.list(self.request)
+ for r in roles:
+ role_ids_choices.append((r.id, r.name))
+ self.fields['role_ids'].choices = sorted(
+ role_ids_choices)
+
+
+class Step(horizon.workflows.Step):
+ action_class = Action
+ contributes = ('role_ids',)
+ template_name = 'infrastructure/plans/create_overview.html'
+ help_text = _("Nothing deployed yet. Design your first deployment.")
+
+ def contribute(self, data, context):
+ context = super(Step, self).contribute(data, context)
+ return context
diff --git a/tuskar_ui/infrastructure/overcloud/workflows/scale.py b/tuskar_ui/infrastructure/plans/workflows/scale.py
similarity index 89%
rename from tuskar_ui/infrastructure/overcloud/workflows/scale.py
rename to tuskar_ui/infrastructure/plans/workflows/scale.py
index fdbed0e62..da91d9a6d 100644
--- a/tuskar_ui/infrastructure/overcloud/workflows/scale.py
+++ b/tuskar_ui/infrastructure/plans/workflows/scale.py
@@ -18,11 +18,11 @@ from horizon import exceptions
import horizon.workflows
from tuskar_ui import api
-from tuskar_ui.infrastructure.overcloud.workflows import scale_node_counts
-from tuskar_ui.infrastructure.overcloud.workflows import undeployed
+from tuskar_ui.infrastructure.plans.workflows import create
+from tuskar_ui.infrastructure.plans.workflows import scale_node_counts
-class Workflow(undeployed.DeploymentValidationMixin,
+class Workflow(create.DeploymentValidationMixin,
horizon.workflows.Workflow):
slug = 'scale_overcloud'
name = _("Scale Deployment")
diff --git a/tuskar_ui/infrastructure/overcloud/workflows/scale_node_counts.py b/tuskar_ui/infrastructure/plans/workflows/scale_node_counts.py
similarity index 82%
rename from tuskar_ui/infrastructure/overcloud/workflows/scale_node_counts.py
rename to tuskar_ui/infrastructure/plans/workflows/scale_node_counts.py
index 5bbbe5a67..c001d1ee3 100644
--- a/tuskar_ui/infrastructure/overcloud/workflows/scale_node_counts.py
+++ b/tuskar_ui/infrastructure/plans/workflows/scale_node_counts.py
@@ -14,19 +14,19 @@
from django.utils.translation import ugettext_lazy as _
-from tuskar_ui.infrastructure.overcloud.workflows import undeployed_overview
+from tuskar_ui.infrastructure.plans.workflows import create_overview
-class Action(undeployed_overview.Action):
+class Action(create_overview.Action):
class Meta:
slug = 'scale_node_counts'
name = _("Node Counts")
-class Step(undeployed_overview.Step):
+class Step(create_overview.Step):
action_class = Action
contributes = ('role_counts', 'plan_id')
- template_name = 'infrastructure/overcloud/scale_node_counts.html'
+ template_name = 'infrastructure/plans/scale_node_counts.html'
def prepare_action_context(self, request, context):
for (role_id, flavor_id), count in context['role_counts'].items():
diff --git a/tuskar_ui/test/api_tests/heat_tests.py b/tuskar_ui/test/api_tests/heat_tests.py
index 33dfb0d33..ba63cdb51 100644
--- a/tuskar_ui/test/api_tests/heat_tests.py
+++ b/tuskar_ui/test/api_tests/heat_tests.py
@@ -24,34 +24,40 @@ from tuskar_ui.test import helpers as test
class HeatAPITests(test.APITestCase):
- def test_overcloud_stack(self):
- stack = self.heatclient_stacks.first()
- ocs = api.heat.OvercloudStack(
- self.tuskarclient_overcloud_plans.first(),
- request=object())
- with patch('openstack_dashboard.api.heat.stack_get',
- return_value=stack):
- ret_val = ocs
- self.assertIsInstance(ret_val, api.heat.OvercloudStack)
+ def test_stack_list(self):
+ ret_val = api.heat.Stack.list(self.request)
+ for stack in ret_val:
+ self.assertIsInstance(stack, api.heat.Stack)
+ self.assertEqual(1, len(ret_val))
- def test_overcloud_stack_events(self):
+ def test_stack_get(self):
+ stack = self.heatclient_stacks.first()
+ ret_val = api.heat.Stack.get(self.request, stack.id)
+ self.assertIsInstance(ret_val, api.heat.Stack)
+
+ def test_stack_plan(self):
+ stack = api.heat.Stack(self.heatclient_stacks.first())
+ ret_val = stack.plan
+ self.assertIsInstance(ret_val, api.tuskar.OvercloudPlan)
+
+ def test_stack_events(self):
event_list = self.heatclient_events.list()
stack = self.heatclient_stacks.first()
with patch('openstack_dashboard.api.heat.events_list',
return_value=event_list):
- ret_val = api.heat.OvercloudStack(stack).events
+ ret_val = api.heat.Stack(stack).events
for e in ret_val:
self.assertIsInstance(e, events.Event)
self.assertEqual(8, len(ret_val))
- def test_overcloud_stack_is_deployed(self):
- stack = api.heat.OvercloudStack(self.heatclient_stacks.first())
+ def test_stack_is_deployed(self):
+ stack = api.heat.Stack(self.heatclient_stacks.first())
ret_val = stack.is_deployed
self.assertFalse(ret_val)
- def test_overcloud_stack_resources(self):
- stack = api.heat.OvercloudStack(self.heatclient_stacks.first())
+ def test_stack_resources(self):
+ stack = api.heat.Stack(self.heatclient_stacks.first())
resources = self.heatclient_resources.list()
nodes = self.baremetalclient_nodes.list()
@@ -70,49 +76,30 @@ class HeatAPITests(test.APITestCase):
for i in ret_val:
self.assertIsInstance(i, api.heat.Resource)
- self.assertEqual(4, len(ret_val))
+ self.assertEqual(3, len(ret_val))
- def test_overcloud_stack_resources_no_ironic(self):
- stack = api.heat.OvercloudStack(self.heatclient_stacks.first())
+ def test_stack_resources_no_ironic(self):
+ stack = api.heat.Stack(self.heatclient_stacks.first())
role = api.tuskar.OvercloudRole(
- self.tuskarclient_overcloud_roles.first())
+ self.tuskarclient_roles.first())
# FIXME(lsmola) only resources and image_name should be tested
# here, anybody has idea how to do that?
- image = self.glanceclient_images.first()
- resources = self.heatclient_resources.list()
- instances = self.novaclient_servers.list()
- nodes = self.baremetalclient_nodes.list()
with patch('openstack_dashboard.api.base.is_service_enabled',
return_value=False):
- with patch('openstack_dashboard.api.heat.resources_list',
- return_value=resources) as resource_list:
- with patch('openstack_dashboard.api.nova.server_list',
- return_value=(instances, None)) as server_list:
- with patch('openstack_dashboard.api.glance.image_get',
- return_value=image) as image_get:
- with patch('novaclient.v1_1.contrib.baremetal.'
- 'BareMetalNodeManager.list',
- return_value=nodes) as node_list:
- ret_val = stack.resources_by_role(role)
- self.assertEqual(resource_list.call_count, 1)
- self.assertEqual(server_list.call_count, 1)
- self.assertEqual(image_get.call_count, 2)
- self.assertEqual(node_list.call_count, 1)
+ ret_val = stack.resources_by_role(role)
for i in ret_val:
self.assertIsInstance(i, api.heat.Resource)
- self.assertEqual(4, len(ret_val))
+ self.assertEqual(1, len(ret_val))
- def test_overcloud_stack_keystone_ip(self):
- stack = api.heat.OvercloudStack(self.heatclient_stacks.first())
+ def test_stack_keystone_ip(self):
+ stack = api.heat.Stack(self.heatclient_stacks.first())
self.assertEqual('192.0.2.23', stack.keystone_ip)
- def test_overcloud_stack_dashboard_url(self):
- stack = api.heat.OvercloudStack(self.heatclient_stacks.first())
- stack._plan = api.tuskar.OvercloudPlan(
- self.tuskarclient_overcloud_plans.first())
+ def test_stack_dashboard_url(self):
+ stack = api.heat.Stack(self.heatclient_stacks.first())
mocked_service = mock.Mock(id='horizon_id')
mocked_service.name = 'horizon'
@@ -141,14 +128,16 @@ class HeatAPITests(test.APITestCase):
stack = self.heatclient_stacks.first()
resource = self.heatclient_resources.first()
- with patch('openstack_dashboard.api.heat.resource_get',
- return_value=resource):
- with patch('openstack_dashboard.api.heat.stack_get',
- return_value=stack):
- ret_val = api.heat.Resource.get(None, stack,
- resource.resource_name)
+ ret_val = api.heat.Resource.get(None, stack,
+ resource.resource_name)
self.assertIsInstance(ret_val, api.heat.Resource)
+ def test_resource_role(self):
+ resource = api.heat.Resource(self.heatclient_resources.first())
+ 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()
diff --git a/tuskar_ui/test/api_tests/tuskar_tests.py b/tuskar_ui/test/api_tests/tuskar_tests.py
index 4db50f481..4f14e33cf 100644
--- a/tuskar_ui/test/api_tests/tuskar_tests.py
+++ b/tuskar_ui/test/api_tests/tuskar_tests.py
@@ -13,7 +13,6 @@
from __future__ import absolute_import
-import contextlib
from mock import patch # noqa
from tuskar_ui import api
@@ -21,72 +20,44 @@ from tuskar_ui.test import helpers as test
class TuskarAPITests(test.APITestCase):
- def test_overcloud_plan_create(self):
- plan = self.tuskarclient_overcloud_plans.first()
- with patch('tuskarclient.v1.overclouds.OvercloudManager.create',
- return_value=plan):
- ret_val = api.tuskar.OvercloudPlan.create(self.request, {}, {})
+ def test_plan_create(self):
+ ret_val = api.tuskar.OvercloudPlan.create(self.request, {}, {})
self.assertIsInstance(ret_val, api.tuskar.OvercloudPlan)
- def test_overcloud_plan_list(self):
- plans = self.tuskarclient_overcloud_plans.list()
- with patch('tuskarclient.v1.overclouds.OvercloudManager.list',
- return_value=plans):
- ret_val = api.tuskar.OvercloudPlan.list(self.request)
+ def test_plan_list(self):
+ ret_val = api.tuskar.OvercloudPlan.list(self.request)
for plan in ret_val:
self.assertIsInstance(plan, api.tuskar.OvercloudPlan)
self.assertEqual(1, len(ret_val))
- def test_overcloud_plan_get(self):
- plan = self.tuskarclient_overcloud_plans.first()
- with patch('tuskarclient.v1.overclouds.OvercloudManager.list',
- return_value=[plan]):
- ret_val = api.tuskar.OvercloudPlan.get(self.request, plan.id)
+ def test_plan_get(self):
+ plan = self.tuskarclient_plans.first()
+ ret_val = api.tuskar.OvercloudPlan.get(self.request, plan['id'])
self.assertIsInstance(ret_val, api.tuskar.OvercloudPlan)
- def test_overcloud_plan_delete(self):
- plan = self.tuskarclient_overcloud_plans.first()
- with patch('tuskarclient.v1.overclouds.OvercloudManager.delete',
- return_value=None):
- api.tuskar.OvercloudPlan.delete(self.request, plan.id)
+ def test_plan_delete(self):
+ plan = self.tuskarclient_plans.first()
+ api.tuskar.OvercloudPlan.delete(self.request, plan['id'])
- def test_overcloud_role_list(self):
- roles = self.tuskarclient_overcloud_roles.list()
+ def test_plan_role_list(self):
+ plan = api.tuskar.OvercloudPlan(self.tuskarclient_plans.first())
- with patch('tuskarclient.v1.overcloud_roles.OvercloudRoleManager.list',
- return_value=roles):
- ret_val = api.tuskar.OvercloudRole.list(self.request)
+ ret_val = plan.role_list
+
+ self.assertEqual(4, len(ret_val))
+ for r in ret_val:
+ self.assertIsInstance(r, api.tuskar.OvercloudRole)
+
+ def test_role_list(self):
+ ret_val = api.tuskar.OvercloudRole.list(self.request)
for r in ret_val:
self.assertIsInstance(r, api.tuskar.OvercloudRole)
- self.assertEqual(4, len(ret_val))
+ self.assertEqual(5, len(ret_val))
- def test_overcloud_role_get(self):
- role = self.tuskarclient_overcloud_roles.first()
-
- with patch('tuskarclient.v1.overcloud_roles.OvercloudRoleManager.get',
- return_value=role):
- ret_val = api.tuskar.OvercloudRole.get(self.request, role.id)
+ def test_role_get(self):
+ role = self.tuskarclient_roles.first()
+ ret_val = api.tuskar.OvercloudRole.get(self.request, role['id'])
self.assertIsInstance(ret_val, api.tuskar.OvercloudRole)
-
- def test_overcloud_role_get_by_node(self):
- node = api.node.Node(
- api.node.BareMetalNode(self.baremetalclient_nodes.first()))
- instance = self.novaclient_servers.first()
- image = self.glanceclient_images.first()
- roles = self.tuskarclient_overcloud_roles.list()
-
- with contextlib.nested(
- patch('tuskarclient.v1.overcloud_roles.'
- 'OvercloudRoleManager.list',
- return_value=roles),
- patch('openstack_dashboard.api.nova.server_get',
- return_value=instance),
- patch('openstack_dashboard.api.glance.image_get',
- return_value=image),
- ):
- ret_val = api.tuskar.OvercloudRole.get_by_node(self.request,
- node)
- self.assertEqual(ret_val.name, 'Controller')
diff --git a/tuskar_ui/test/test_data/heat_data.py b/tuskar_ui/test/test_data/heat_data.py
index 7ae87af1f..0d50886d0 100644
--- a/tuskar_ui/test/test_data/heat_data.py
+++ b/tuskar_ui/test/test_data/heat_data.py
@@ -33,6 +33,7 @@ def data(TEST):
'output_value': 'http://192.0.2.23:5000/v2',
}],
'parameters': {
+ 'plan_id': 'plan-1',
'one': 'one',
'two': 'two',
}})
@@ -117,7 +118,7 @@ def data(TEST):
'logical_resource_id': 'Compute0',
'physical_resource_id': 'aa',
'resource_status': 'CREATE_COMPLETE',
- 'resource_type': 'AWS::EC2::Instance'})
+ 'resource_type': 'Compute'})
resource_2 = resources.Resource(
resources.ResourceManager(None),
{'id': '2-resource-id',
@@ -126,7 +127,7 @@ def data(TEST):
'logical_resource_id': 'Controller',
'physical_resource_id': 'bb',
'resource_status': 'CREATE_COMPLETE',
- 'resource_type': 'AWS::EC2::Instance'})
+ 'resource_type': 'Controller'})
resource_3 = resources.Resource(
resources.ResourceManager(None),
{'id': '3-resource-id',
@@ -135,7 +136,7 @@ def data(TEST):
'logical_resource_id': 'Compute1',
'physical_resource_id': 'cc',
'resource_status': 'CREATE_COMPLETE',
- 'resource_type': 'AWS::EC2::Instance'})
+ 'resource_type': 'Compute'})
resource_4 = resources.Resource(
resources.ResourceManager(None),
{'id': '4-resource-id',
@@ -144,7 +145,7 @@ def data(TEST):
'logical_resource_id': 'Compute2',
'physical_resource_id': 'dd',
'resource_status': 'CREATE_COMPLETE',
- 'resource_type': 'AWS::EC2::Instance'})
+ 'resource_type': 'Compute'})
TEST.heatclient_resources.add(resource_1,
resource_2,
resource_3,
@@ -157,24 +158,36 @@ def data(TEST):
{'id': 'aa',
'name': 'Compute',
'image': {'id': 1},
+ 'flavor': {
+ 'id': '1',
+ },
'status': 'ACTIVE'})
s_2 = servers.Server(
servers.ServerManager(None),
{'id': 'bb',
'name': 'Controller',
'image': {'id': 2},
+ 'flavor': {
+ 'id': '2',
+ },
'status': 'ACTIVE'})
s_3 = servers.Server(
servers.ServerManager(None),
{'id': 'cc',
'name': 'Compute',
'image': {'id': 1},
+ 'flavor': {
+ 'id': '1',
+ },
'status': 'BUILD'})
s_4 = servers.Server(
servers.ServerManager(None),
{'id': 'dd',
'name': 'Compute',
'image': {'id': 1},
+ 'flavor': {
+ 'id': '1',
+ },
'status': 'ERROR'})
TEST.novaclient_servers.add(s_1, s_2, s_3, s_4)
diff --git a/tuskar_ui/test/test_data/tuskar_data.py b/tuskar_ui/test/test_data/tuskar_data.py
index 69c591d73..5fee10136 100644
--- a/tuskar_ui/test/test_data/tuskar_data.py
+++ b/tuskar_ui/test/test_data/tuskar_data.py
@@ -12,68 +12,79 @@
from openstack_dashboard.test.test_data import utils as test_data_utils
-from tuskarclient.v1 import overcloud_roles
-from tuskarclient.v1 import overclouds
-
def data(TEST):
- # Overcloud
- TEST.tuskarclient_overcloud_plans = test_data_utils.TestDataContainer()
- # TODO(Tzu-Mainn Chen): fix these to create Tuskar Overcloud objects
- # once the api supports it
- oc_1 = overclouds.Overcloud(
- overclouds.OvercloudManager(None),
- {'id': 1,
- 'name': 'overcloud',
- 'description': 'overcloud',
- 'stack_id': 'stack-id-1',
- 'attributes': {
- 'AdminPassword': "unset"
- }})
- TEST.tuskarclient_overcloud_plans.add(oc_1)
+ # OvercloudPlan
+ TEST.tuskarclient_plans = test_data_utils.TestDataContainer()
+ plan_1 = {
+ 'id': 'plan-1',
+ 'name': 'overcloud',
+ 'description': 'this is an overcloud deployment plan',
+ 'created_at': '2014-05-27T21:11:09Z',
+ 'modified_at': '2014-05-30T21:11:09Z',
+ 'roles': [{
+ 'id': 'role-1',
+ 'name': 'Controller',
+ 'version': 1,
+ }, {
+ 'id': 'role-2',
+ 'name': 'Compute',
+ 'version': 1,
+ }, {
+ 'id': 'role-3',
+ 'name': 'Object Storage',
+ 'version': 1,
+ }, {
+ 'id': 'role-5',
+ 'name': 'Block Storage',
+ 'version': 2,
+ }],
+ 'parameters': [{
+ 'name': 'AdminPassword',
+ 'label': 'Admin Password',
+ 'description': 'Admin password',
+ 'hidden': 'false',
+ 'value': 'unset',
+ }],
+ }
+ TEST.tuskarclient_plans.add(plan_1)
# OvercloudRole
- TEST.tuskarclient_overcloud_roles = test_data_utils.TestDataContainer()
- r_1 = overcloud_roles.OvercloudRole(
- overcloud_roles.OvercloudRoleManager(None),
- {
- 'id': 1,
- 'name': 'Controller',
- 'description': 'controller overcloud role',
- 'image_name': 'overcloud-control',
- 'flavor_id': '',
- })
- r_2 = overcloud_roles.OvercloudRole(
- overcloud_roles.OvercloudRoleManager(None),
- {'id': 2,
- 'name': 'Compute',
- 'description': 'compute overcloud role',
- 'flavor_id': '',
- 'image_name': 'overcloud-compute'})
- r_3 = overcloud_roles.OvercloudRole(
- overcloud_roles.OvercloudRoleManager(None),
- {'id': 3,
- 'name': 'Object Storage',
- 'description': 'object storage overcloud role',
- 'flavor_id': '',
- 'image_name': 'overcloud-object-storage'})
- r_4 = overcloud_roles.OvercloudRole(
- overcloud_roles.OvercloudRoleManager(None),
- {'id': 4,
- 'name': 'Block Storage',
- 'description': 'block storage overcloud role',
- 'flavor_id': '',
- 'image_name': 'overcloud-block-storage'})
- TEST.tuskarclient_overcloud_roles.add(r_1, r_2, r_3, r_4)
-
- # OvercloudRoles with flavors associated
- TEST.tuskarclient_roles_with_flavors = test_data_utils.TestDataContainer()
- role_with_flavor = overcloud_roles.OvercloudRole(
- overcloud_roles.OvercloudRoleManager(None),
- {'id': 5,
- 'name': 'Block Storage',
- 'description': 'block storage overcloud role',
- 'flavor_id': '1',
- 'image_name': 'overcloud-block-storage'})
- TEST.tuskarclient_roles_with_flavors.add(role_with_flavor)
+ TEST.tuskarclient_roles = test_data_utils.TestDataContainer()
+ r_1 = {
+ 'id': 'role-1',
+ 'name': 'Controller',
+ 'version': 1,
+ 'description': 'controller role',
+ 'created_at': '2014-05-27T21:11:09Z',
+ }
+ r_2 = {
+ 'id': 'role-2',
+ 'name': 'Compute',
+ 'version': 1,
+ 'description': 'compute role',
+ 'created_at': '2014-05-27T21:11:09Z',
+ }
+ r_3 = {
+ 'id': 'role-3',
+ 'name': 'Object Storage',
+ 'version': 1,
+ 'description': 'object storage role',
+ 'created_at': '2014-05-27T21:11:09Z',
+ }
+ r_4 = {
+ 'id': 'role-4',
+ 'name': 'Block Storage',
+ 'version': 1,
+ 'description': 'block storage role',
+ 'created_at': '2014-05-27T21:11:09Z',
+ }
+ r_5 = {
+ 'id': 'role-5',
+ 'name': 'Block Storage',
+ 'version': 2,
+ 'description': 'block storage role',
+ 'created_at': '2014-05-28T21:11:09Z',
+ }
+ TEST.tuskarclient_roles.add(r_1, r_2, r_3, r_4, r_5)
| |