Adding nova-baremetal, heat and nova API

Adding nova-baremetal, heat and nova API instead of
mock API.

nova-baremetal is supposed to be just temporary solution,
replaced by ironic later.

Tests were fixed accordingly.

Change-Id: Iee6436196d52db626a9cfccc24ae303f5917707c
This commit is contained in:
Ladislav Smola 2014-01-24 18:19:54 +01:00
parent 41015e2e58
commit ca2f3792e4
10 changed files with 514 additions and 184 deletions

View File

@ -19,6 +19,11 @@ import django.conf
from horizon.utils import memoized
from openstack_dashboard.api import base
from openstack_dashboard.api import glance
from openstack_dashboard.api import heat
from openstack_dashboard.api import nova
from novaclient.v1_1.contrib import baremetal
from openstack_dashboard.test.test_data import utils
from tuskar_ui.cached_property import cached_property # noqa
from tuskar_ui.test.test_data import tuskar_data
@ -28,6 +33,11 @@ LOG = logging.getLogger(__name__)
TUSKAR_ENDPOINT_URL = getattr(django.conf.settings, 'TUSKAR_ENDPOINT_URL')
def baremetalclient(request):
nc = nova.novaclient(request)
return baremetal.BareMetalNodeManager(nc)
# TODO(Tzu-Mainn Chen): remove test data when possible
def test_data():
test_data = utils.TestDataContainer()
@ -35,8 +45,8 @@ def test_data():
return test_data
# FIXME: request isn't used right in the tuskar client right now, but looking
# at other clients, it seems like it will be in the future
# FIXME: request isn't used right in the tuskar client right now,
# but looking at other clients, it seems like it will be in the future
def tuskarclient(request):
c = tuskar_client.Client(TUSKAR_ENDPOINT_URL)
return c
@ -57,11 +67,33 @@ def list_to_dict(object_list, key_attribute='id'):
return dict((getattr(o, key_attribute), o) for o in object_list)
# FIXME(lsmola) This should be done in Horizon, they don't have caching
@memoized.memoized
def image_get(request, image_id):
"""Returns an Image object with metadata
Returns an Image object populated with metadata for image
with supplied identifier.
:param image_id: list of objects to be put into a dict
:type object_list: list
:return: object
:rtype: glanceclient.v1.images.Image
"""
image = glance.image_get(request, image_id)
return image
# TODO(Tzu-Mainn Chen): change this to APIResourceWrapper once
# ResourceCategory object exists in tuskar
class Overcloud(base.APIDictWrapper):
_attrs = ('id', 'stack_id', 'name', 'description')
def __init__(self, apiresource, request=None):
super(Overcloud, self).__init__(apiresource)
self._request = request
@classmethod
def create(cls, request, overcloud_sizing):
"""Create an Overcloud in Tuskar
@ -82,7 +114,7 @@ class Overcloud(base.APIDictWrapper):
# overcloud_sizing)
overcloud = test_data().tuskarclient_overclouds.first()
return cls(overcloud)
return cls(overcloud, request=request)
@classmethod
def list(cls, request):
@ -98,7 +130,7 @@ class Overcloud(base.APIDictWrapper):
# ocs = tuskarclient(request).overclouds.list()
ocs = test_data().tuskarclient_overclouds.list()
return [cls(oc) for oc in ocs]
return [cls(oc, request=request) for oc in ocs]
@classmethod
def get(cls, request, overcloud_id):
@ -118,7 +150,7 @@ class Overcloud(base.APIDictWrapper):
# overcloud = tuskarclient(request).overclouds.get(overcloud_id)
overcloud = test_data().tuskarclient_overclouds.first()
return cls(overcloud)
return cls(overcloud, request=request)
@cached_property
def stack(self):
@ -132,7 +164,8 @@ class Overcloud(base.APIDictWrapper):
if self.stack_id:
# TODO(Tzu-Mainn Chen): remove test data when possible
# stack = heatclient(request).stacks.get(self.stack_id)
stack = test_data().heatclient_stacks.first()
# stack = test_data().heatclient_stacks.first()
stack = heat.stack_get(self._request, 'overcloud')
return stack
return None
@ -160,11 +193,42 @@ class Overcloud(base.APIDictWrapper):
False otherwise
:rtype: bool
"""
# TODO(rdopieralski) Actually implement it
return False
return self.stack.stack_status in ('CREATE_COMPLETE',
'UPDATE_COMPLETE')
@memoized.memoized
def resources(self, resource_category, with_joins=False):
def all_resources(self, with_joins=True):
"""Return a list of all Overcloud Resources
:param with_joins: should we also retrieve objects associated with each
retrieved Resource?
:type with_joins: bool
:return: list of all Overcloud Resources or an empty list if there
are none
:rtype: list of tuskar_ui.api.Resource
"""
resources = [r for r in heat.resources_list(self._request,
self.stack.stack_name)]
if not with_joins:
return [Resource(r, request=self._request) for r in resources]
nodes_dict = list_to_dict(Node.list(self._request, associated=True),
key_attribute='instance_uuid')
joined_resources = []
for r in resources:
node = nodes_dict.get(r.physical_resource_id, None)
joined_resources.append(Resource(r,
node=node,
request=self._request))
# TODO(lsmola) I want just resources with nova instance
# this could be probably filtered a better way, investigate
return [r for r in joined_resources if r.node is not None]
@memoized.memoized
def resources(self, resource_category, with_joins=True):
"""Return a list of Overcloud Resources that match a Resource Category
:param resource_category: category of resources to be returned
@ -178,34 +242,57 @@ class Overcloud(base.APIDictWrapper):
or an empty list if there are none
:rtype: list of tuskar_ui.api.Resource
"""
# TODO(Tzu-Mainn Chen): uncomment when possible
#resources = tuskarclient(request).overclouds.get_resources(
# self.id, resource_category.id)
resources = [r for r in test_data().heatclient_resources.list()
if r.logical_resource_id.startswith(
resource_category.name)]
# FIXME(lsmola) with_joins is not necessary here, I need at least
# nova instance
all_resources = self.all_resources(with_joins)
filtered_resources = [resource for resource in all_resources if
(resource.image_name ==
resource_category.image_name)]
if not with_joins:
return [Resource(r) for r in resources]
nodes_dict = list_to_dict(Node.list(None, associated=True),
key_attribute='instance_uuid')
joined_resources = []
for r in resources:
node = nodes_dict.get(r.physical_resource_id, None)
joined_resources.append(Resource(r,
node=node))
return joined_resources
return filtered_resources
class Node(base.APIResourceWrapper):
_attrs = ('uuid', 'instance_uuid', 'driver', 'driver_info',
'properties', 'power_state')
# FIXME(lsmola) uncomment this and delete equivalent methods
#_attrs = ('uuid', 'instance_uuid', 'driver', 'driver_info',
# 'properties', 'power_state')
_attrs = ('id', 'uuid', 'instance_uuid')
def __init__(self, apiresource, instance=None):
def __init__(self, apiresource, request=None, **kwargs):
"""Initialize a node
:param apiresource: apiresource we want to wrap
:type apiresource: novaclient.v1_1.contrib.baremetal.BareMetalNode
:param request: request
:type request: django.core.handlers.wsgi.WSGIRequest
:param instance: instance relation we want to cache
:type instance: openstack_dashboard.api.nova.Server
:return: Node object
:rtype: Node
"""
super(Node, self).__init__(apiresource)
if instance is not None:
self._instance = instance
self._request = request
if 'instance' in kwargs:
self._instance = kwargs['instance']
@classmethod
def nova_baremetal_format(cls, ipmi_address, cpu, ram, local_disk,
mac_addresses, ipmi_username=None,
ipmi_password=None):
"""Converts Ironic parameters to Nova-baremetal format
"""
return {'service_host': 'undercloud',
'cpus': cpu,
'memory_mb': ram,
'local_gb': local_disk,
'prov_mac_address': mac_addresses,
'pm_address': ipmi_address,
'pm_user': ipmi_username,
'pm_password': ipmi_password,
'terminal_port': None}
@classmethod
def create(cls, request, ipmi_address, cpu, ram, local_disk,
@ -254,7 +341,9 @@ class Node(base.APIResourceWrapper):
# node_uuid=node.uuid,
# address=mac_address
# )
node = test_data().ironicclient_nodes.first()
node = baremetalclient(request).create(**cls.nova_baremetal_format(
ipmi_address, cpu, ram, local_disk, mac_addresses,
ipmi_username=None, ipmi_password=None))
return cls(node)
@ -273,15 +362,12 @@ class Node(base.APIResourceWrapper):
"""
# TODO(Tzu-Mainn Chen): remove test data when possible
# node = ironicclient(request).nodes.get(uuid)
nodes = test_data().ironicclient_nodes.list()
node = next((n for n in nodes if uuid == n.uuid),
None)
node = baremetalclient(request).get(uuid)
if node.instance_uuid is not None:
# server = novaclient(request).servers.get(node.instance_uuid)
servers = test_data().novaclient_servers.list()
server = next((s for s in servers if node.instance_uuid == s.id),
None)
return cls(node, instance=server)
server = nova.server_get(request, node.instance_uuid)
return cls(node, instance=server, request=request)
return cls(node)
@ -303,17 +389,16 @@ class Node(base.APIResourceWrapper):
matching instance UUID
"""
# TODO(Tzu-Mainn Chen): remove test data when possible
#node = ironicclient(request).nodes.get_by_instance_uuid(
# node = ironicclient(request).nodes.get_by_instance_uuid(
# instance_uuid)
#server = novaclient(request).servers.get(instance_id)
nodes = test_data().ironicclient_nodes.list()
server = nova.server_get(request, instance_uuid)
nodes = baremetalclient(request).list()
node = next((n for n in nodes if instance_uuid == n.instance_uuid),
None)
servers = test_data().novaclient_servers.list()
server = next((s for s in servers if instance_uuid == s.id),
None)
return cls(node, instance=server)
return cls(node, instance=server, request=request)
@classmethod
def list(cls, request, associated=None):
@ -333,7 +418,9 @@ class Node(base.APIResourceWrapper):
# TODO(Tzu-Mainn Chen): remove test data when possible
# nodes = ironicclient(request).nodes.list(
# associated=associated)
nodes = test_data().ironicclient_nodes.list()
# nodes = test_data().ironicclient_nodes.list()
nodes = baremetalclient(request).list()
if associated is not None:
if associated:
@ -342,14 +429,16 @@ class Node(base.APIResourceWrapper):
else:
nodes = [node for node in nodes
if node.instance_uuid is None]
return [cls(node) for node in nodes]
return [cls(node, request=request) for node in nodes]
# servers = novaclient(request).servers.list(detailed=True)
servers_dict = list_to_dict(test_data().novaclient_servers.list())
servers, has_more_data = nova.server_list(request)
servers_dict = list_to_dict(servers)
nodes_with_instance = []
for n in nodes:
server = servers_dict.get(n.instance_uuid, None)
nodes_with_instance.append(cls(n, instance=server))
nodes_with_instance.append(cls(n, instance=server,
request=request))
return nodes_with_instance
@ -366,6 +455,7 @@ class Node(base.APIResourceWrapper):
"""
# TODO(Tzu-Mainn Chen): uncomment when possible
# ironicclient(request).nodes.delete(uuid)
baremetalclient(request).delete(uuid)
return
@cached_property
@ -379,18 +469,16 @@ class Node(base.APIResourceWrapper):
"""
if hasattr(self, '_instance'):
return self._instance
if self.instance_uuid:
# TODO(Tzu-Mainn Chen): remove test data when possible
# server = novaclient(request).servers.get(self.instance_uuid)
servers = test_data().novaclient_servers.list()
server = next((s for s in servers if self.instance_uuid == s.id),
None)
if self.instance_uuid:
server = nova.server_get(self._request, self.instance_uuid)
return server
return None
@cached_property
def addresses(self):
# FIXME(lsmola) remove when Ironic is in
"""Return a list of port addresses associated with this Node
:return: list of port addresses associated with this Node, or
@ -400,17 +488,71 @@ class Node(base.APIResourceWrapper):
"""
# TODO(Tzu-Mainn Chen): uncomment when possible
# ports = self.list_ports()
ports = test_data().ironicclient_ports.list()[:2]
# ports = test_data().ironicclient_ports.list()[:2]
return [port.address for port in ports]
# return [port.address for port in ports]
return [interface["address"] for interface in
self._apiresource.interfaces]
@cached_property
def power_state(self):
# FIXME(lsmola) remove when Ironic is in
"""Return a power state of this Node
:return: power state of this node
:rtype: str
"""
return self._apiresource.task_state
@cached_property
def properties(self):
# FIXME(lsmola) remove when Ironic is in
"""Return properties of this Node
:return: return memory, cpus and local_disk properties
of this Node
:rtype: dict of str
"""
return {
'ram': self._apiresource.memory_mb / 1024.0,
'cpu': self._apiresource.cpus,
'local_disk': self._apiresource.local_gb / 1000.0
}
@cached_property
def driver_info(self):
# FIXME(lsmola) remove when Ironic is in
"""Return driver_info this Node
:return: return pm_address property of this Node
:rtype: dict of str
"""
return {
'ipmi_address': self._apiresource.pm_address
}
class Resource(base.APIResourceWrapper):
_attrs = ('resource_name', 'resource_type', 'resource_status',
'physical_resource_id')
def __init__(self, apiresource, **kwargs):
def __init__(self, apiresource, request=None, **kwargs):
"""Initialize a resource
:param apiresource: apiresource we want to wrap
:type apiresource: heatclient.v1.resources.Resource
:param request: request
:type request: django.core.handlers.wsgi.WSGIRequest
:param node: node relation we want to cache
:type node: tuskar_ui.api.Node
:return: Resource object
:rtype: Resource
"""
super(Resource, self).__init__(apiresource)
self._request = request
if 'node' in kwargs:
self._node = kwargs['node']
@ -431,17 +573,9 @@ class Resource(base.APIResourceWrapper):
stack matches the resource name
:rtype: tuskar_ui.api.Resource
"""
# TODO(Tzu-Mainn Chen): uncomment when possible
# resource = heatclient(request).resources.get(
# overcloud.id,
# resource_name)
resources = test_data().heatclient_resources.list()
resource = next((r for r in resources
if overcloud['id'] == r.stack_id
and resource_name == r.resource_name),
None)
return cls(resource)
resource = heat.resource_get(overcloud.stack.id,
resource_name)
return cls(resource, request=request)
@cached_property
def node(self):
@ -457,14 +591,29 @@ class Resource(base.APIResourceWrapper):
if hasattr(self, '_node'):
return self._node
if self.physical_resource_id:
return Node.get_by_instance_uuid(None, self.physical_resource_id)
return Node.get_by_instance_uuid(self._request,
self.physical_resource_id)
return None
@cached_property
def image_name(self):
"""Return image name of resource
Returns image name of instance associated with resource
:return: Image name of resources
:rtype: string
"""
instance = getattr(getattr(self, 'node', None), 'instance', None)
if instance is not None:
return image_get(self._request, instance.image['id']).name
return None
# TODO(Tzu-Mainn Chen): change this to APIResourceWrapper once
# ResourceCategory object exists in tuskar
class ResourceCategory(base.APIDictWrapper):
_attrs = ('id', 'name', 'description', 'image_id')
_attrs = ('id', 'name', 'description', 'image_id', 'image_name')
@classmethod
def list(cls, request):

View File

@ -41,9 +41,13 @@ class NumberPickerInput(NumberInput):
class MACField(forms.fields.Field):
def clean(self, value):
class mac_dialect(netaddr.mac_eui48):
"""Same validation as Nova uses."""
word_fmt = '%.02x'
word_sep = ':'
try:
return str(netaddr.EUI(
value.strip(), version=48, dialect=netaddr.mac_unix)).upper()
value.strip(), version=48, dialect=mac_dialect)).upper()
except (netaddr.AddrFormatError, TypeError):
raise forms.ValidationError(_(u'Enter a valid MAC address.'))

View File

@ -26,9 +26,9 @@ class NodeForm(django.forms.Form):
required=False,
widget=django.forms.HiddenInput(),
)
ip_address = django.forms.IPAddressField(
label=_("IP Address"),
ipmi_address = django.forms.IPAddressField(
label=_("IPMI Address"),
required=False,
widget=django.forms.TextInput(attrs={'class': 'input input-medium'}),
)
ipmi_user = django.forms.CharField(
@ -42,26 +42,12 @@ class NodeForm(django.forms.Form):
widget=django.forms.PasswordInput(
render_value=False, attrs={'class': 'input input-medium'}),
)
mac_address = tuskar_ui.forms.MACField(
label=_("NIC MAC Address"),
widget=django.forms.Textarea(attrs={
'class': 'input input-medium',
'rows': 2,
widget=django.forms.TextInput(attrs={
'class': 'input input-medium'
}),
)
ipmi_user = django.forms.CharField(
label=_("IPMI User"),
required=False,
widget=django.forms.TextInput(attrs={'class': 'input input-medium'}),
)
ipmi_password = django.forms.CharField(
label=_("IPMI Password"),
required=False,
widget=django.forms.PasswordInput(
render_value=False, attrs={'class': 'input input-medium'}),
)
cpus = django.forms.IntegerField(
label=_("CPUs"),
required=True,
@ -89,7 +75,8 @@ class NodeForm(django.forms.Form):
def get_name(self):
try:
name = self.fields['ip_address'].value()
# FIXME(lsmola) show somethign meaningful here
name = self.fields['ipmi_address'].value()
except AttributeError:
# when the field is not bound
name = _("Undefined node")
@ -103,11 +90,11 @@ class BaseNodeFormset(django.forms.formsets.BaseFormSet):
try:
api.Node.create(
request,
form.cleaned_data['ip_address'],
form.cleaned_data['ipmi_address'],
form.cleaned_data.get('cpus'),
form.cleaned_data.get('memory'),
form.cleaned_data.get('local_disk'),
[form.cleaned_data['mac_address']],
form.cleaned_data['mac_address'],
form.cleaned_data.get('ipmi_username'),
form.cleaned_data.get('ipmi_password'),
)

View File

@ -17,9 +17,25 @@ from django.utils.translation import ugettext_lazy as _
from horizon import tables
from tuskar_ui import api
class DeleteNode(tables.BatchAction):
name = "delete"
action_present = _("Delete")
action_past = _("Deleting")
data_type_singular = _("Node")
data_type_plural = _("Nodes")
classes = ('btn-danger',)
def allowed(self, request, obj=None):
return getattr(obj, 'instance', None) is None
def action(self, request, obj_id):
api.Node.delete(request, obj_id)
class NodesTable(tables.DataTable):
uuid = tables.Column("uuid",
link="horizon:infrastructure:nodes:detail",
verbose_name=_("UUID"))
@ -51,7 +67,7 @@ class NodesTable(tables.DataTable):
row_actions = ()
def get_object_id(self, datum):
return datum.uuid
return datum.id
def get_object_display(self, datum):
return datum.uuid
@ -62,8 +78,8 @@ class FreeNodesTable(NodesTable):
class Meta:
name = "free_nodes"
verbose_name = _("Free Nodes")
table_actions = ()
row_actions = ()
table_actions = (DeleteNode,)
row_actions = (DeleteNode,)
class DeployedNodesTable(NodesTable):

View File

@ -7,7 +7,7 @@
</div>
<div class="row-fluid">
<h4>Power Management</h4>
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ip_address required=True %}
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ipmi_address %}
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ipmi_user %}
{% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ipmi_password %}
</div>
@ -38,7 +38,8 @@
var $nav_link = $('a[href="#' + $form.attr('id') + '"]');
var undefined_name = '{{ form.get_name|escapejs }}';
$form.find('input[name$="-ip_address"]').change(function () {
// FIXME(lsmola) what is this good for exactly?
$form.find('input[name$="-ipmi_address"]').change(function () {
$nav_link.html($(this).val() || undefined_name);
});
});

View File

@ -31,7 +31,15 @@ tuskar_data.data(TEST_DATA)
class NodesTests(test.BaseAdminViewTests):
def test_index_get(self):
res = self.client.get(INDEX_URL)
with patch('tuskar_ui.api.Node', **{
'spec_set': ['list'], # Only allow these attributes
'list.return_value': [],
}) as mock:
res = self.client.get(INDEX_URL)
# FIXME(lsmola) optimize, this should call 1 time, what the hell
self.assertEqual(mock.list.call_count, 8)
self.assertTemplateUsed(
res, 'infrastructure/nodes/index.html')
self.assertTemplateUsed(res, 'infrastructure/nodes/_overview.html')
@ -102,13 +110,13 @@ class NodesTests(test.BaseAdminViewTests):
'register_nodes-INITIAL_FORMS': 1,
'register_nodes-MAX_NUM_FORMS': 1000,
'register_nodes-0-ip_address': '127.0.0.1',
'register_nodes-0-ipmi_address': '127.0.0.1',
'register_nodes-0-mac_address': 'de:ad:be:ef:ca:fe',
'register_nodes-0-cpus': '1',
'register_nodes-0-memory': '2',
'register_nodes-0-local_disk': '3',
'register_nodes-1-ip_address': '127.0.0.2',
'register_nodes-1-ipmi_address': '127.0.0.2',
'register_nodes-1-mac_address': 'de:ad:be:ef:ca:ff',
'register_nodes-1-cpus': '4',
'register_nodes-1-memory': '5',
@ -122,9 +130,9 @@ class NodesTests(test.BaseAdminViewTests):
request = Node.create.call_args_list[0][0][0] # This is a hack.
self.assertListEqual(Node.create.call_args_list, [
call(request, '127.0.0.1', 1, 2, 3,
['DE:AD:BE:EF:CA:FE'], None, u''),
'DE:AD:BE:EF:CA:FE', None, u''),
call(request, '127.0.0.2', 4, 5, 6,
['DE:AD:BE:EF:CA:FF'], None, u''),
'DE:AD:BE:EF:CA:FF', None, u''),
])
self.assertRedirectsNoFollow(res, INDEX_URL)
@ -134,13 +142,13 @@ class NodesTests(test.BaseAdminViewTests):
'register_nodes-INITIAL_FORMS': 1,
'register_nodes-MAX_NUM_FORMS': 1000,
'register_nodes-0-ip_address': '127.0.0.1',
'register_nodes-0-ipmi_address': '127.0.0.1',
'register_nodes-0-mac_address': 'de:ad:be:ef:ca:fe',
'register_nodes-0-cpus': '1',
'register_nodes-0-memory': '2',
'register_nodes-0-local_disk': '3',
'register_nodes-1-ip_address': '127.0.0.2',
'register_nodes-1-ipmi_address': '127.0.0.2',
'register_nodes-1-mac_address': 'de:ad:be:ef:ca:ff',
'register_nodes-1-cpus': '4',
'register_nodes-1-memory': '5',
@ -154,9 +162,9 @@ class NodesTests(test.BaseAdminViewTests):
request = Node.create.call_args_list[0][0][0] # This is a hack.
self.assertListEqual(Node.create.call_args_list, [
call(request, '127.0.0.1', 1, 2, 3,
['DE:AD:BE:EF:CA:FE'], None, u''),
'DE:AD:BE:EF:CA:FE', None, u''),
call(request, '127.0.0.2', 4, 5, 6,
['DE:AD:BE:EF:CA:FF'], None, u''),
'DE:AD:BE:EF:CA:FF', None, u''),
])
self.assertTemplateUsed(
res, 'infrastructure/nodes/register.html')

View File

@ -41,7 +41,7 @@ class ResourceCategoryNodeTable(tables.DataTable):
))
def get_object_id(self, datum):
return datum.uuid
return datum.id
class Meta:
name = "resource_category__nodetable"

View File

@ -26,7 +26,7 @@ INDEX_URL = urlresolvers.reverse(
CREATE_URL = urlresolvers.reverse(
'horizon:infrastructure:overcloud:create')
DETAIL_URL = urlresolvers.reverse(
'horizon:infrastructure:overcloud:create')
'horizon:infrastructure:overcloud:detail', args=(1,))
TEST_DATA = utils.TestDataContainer()
tuskar_data.data(TEST_DATA)
@ -34,12 +34,13 @@ tuskar_data.data(TEST_DATA)
class OvercloudTests(test.BaseAdminViewTests):
def test_index_overcloud_undeployed_get(self):
oc = api.Overcloud(TEST_DATA.tuskarclient_overclouds.first())
oc = None
with patch('tuskar_ui.api.Overcloud', **{
'spec_set': ['get', 'is_deployed'],
'is_deployed': False,
'get.return_value': oc,
'get.side_effect': lambda request, overcloud_id: oc,
}) as Overcloud:
oc = api.Overcloud
res = self.client.get(INDEX_URL)
request = Overcloud.get.call_args_list[0][0][0] # This is a hack.
self.assertListEqual(Overcloud.get.call_args_list,
@ -82,12 +83,14 @@ class OvercloudTests(test.BaseAdminViewTests):
self.assertRedirectsNoFollow(res, INDEX_URL)
def test_index_overcloud_deployed(self):
oc = api.Overcloud(TEST_DATA.tuskarclient_overclouds.first())
oc = None
with patch('tuskar_ui.api.Overcloud', **{
'spec_set': ['get', 'is_deployed'],
'spec_set': ['get', 'is_deployed', 'id'],
'is_deployed': True,
'get.return_value': oc,
'id': 1,
'get.side_effect': lambda request, overcloud_id: oc,
}) as Overcloud:
oc = Overcloud
res = self.client.get(INDEX_URL)
request = Overcloud.get.call_args_list[0][0][0] # This is a hack.
self.assertListEqual(Overcloud.get.call_args_list,

View File

@ -14,6 +14,8 @@
from __future__ import absolute_import
from mock import patch # noqa
from glanceclient.v1 import images
from heatclient.v1 import events
from heatclient.v1 import stacks
@ -27,16 +29,11 @@ from tuskar_ui.test import helpers as test
class TuskarAPITests(test.APITestCase):
def test_overcloud_create(self):
#overcloud = self.tuskarclient_overclouds.first()
ret_val = api.Overcloud.create(self.request, [])
self.assertIsInstance(ret_val, api.Overcloud)
def test_overcloud_list(self):
#overclouds = self.tuskarclient_overclouds.list()
ret_val = api.Overcloud.list(self.request)
for oc in ret_val:
self.assertIsInstance(oc, api.Overcloud)
@ -49,10 +46,12 @@ class TuskarAPITests(test.APITestCase):
self.assertIsInstance(ret_val, api.Overcloud)
def test_overcloud_stack(self):
overcloud = self.tuskarclient_overclouds.first()
ret_val = api.Overcloud(overcloud).stack
self.assertIsInstance(ret_val, stacks.Stack)
stack = self.heatclient_stacks.first()
oc = api.Overcloud(self.tuskarclient_overclouds.first(), request=None)
with patch('openstack_dashboard.api.heat.stack_get',
return_value=stack):
ret_val = oc.stack
self.assertIsInstance(ret_val, stacks.Stack)
def test_overcloud_stack_events(self):
overcloud = self.tuskarclient_overclouds.first()
@ -70,65 +69,150 @@ class TuskarAPITests(test.APITestCase):
self.assertListEqual([], ret_val)
def test_overcloud_is_deployed(self):
overcloud = self.tuskarclient_overclouds.first()
stack = self.heatclient_stacks.first()
oc = api.Overcloud(self.tuskarclient_overclouds.first(), request=None)
with patch('openstack_dashboard.api.heat.stack_get',
return_value=stack):
ret_val = oc.is_deployed
self.assertFalse(ret_val)
ret_val = api.Overcloud(overcloud).is_deployed
self.assertFalse(ret_val)
def test_overcloud_all_resources(self):
oc = api.Overcloud(self.tuskarclient_overclouds.first(), request=None)
def test_overcloud_resources(self):
overcloud = self.tuskarclient_overclouds.first()
category = self.tuskarclient_resource_categories.first()
# FIXME(lsmola) the stack call should not be tested in this unit test
# anybody has idea how to do it?
stack = self.heatclient_stacks.first()
resources = self.heatclient_resources.list()
nodes = self.ironicclient_nodes.list()
instances = []
with patch('openstack_dashboard.api.heat.resources_list',
return_value=resources):
with patch('openstack_dashboard.api.nova.server_list',
return_value=(instances, None)):
with patch('novaclient.v1_1.contrib.baremetal.'
'BareMetalNodeManager.list',
return_value=nodes):
with patch('openstack_dashboard.api.heat.stack_get',
return_value=stack):
ret_val = oc.all_resources()
ret_val = api.Overcloud(overcloud).resources(
api.ResourceCategory(category))
for i in ret_val:
self.assertIsInstance(i, api.Resource)
self.assertEqual(1, len(ret_val))
self.assertEqual(4, len(ret_val))
def test_overcloud_resources(self):
oc = api.Overcloud(self.tuskarclient_overclouds.first(), request=None)
category = api.ResourceCategory(self.tuskarclient_resource_categories.
first())
# FIXME(lsmola) only all_resources and image_name should be tested
# here, anybody has idea how to do that?
image = self.glanceclient_images.first()
stack = self.heatclient_stacks.first()
resources = self.heatclient_resources.list()
instances = self.novaclient_servers.list()
nodes = self.ironicclient_nodes.list()
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:
with patch('openstack_dashboard.api.heat.stack_get',
return_value=stack) as stack_get:
ret_val = oc.resources(category)
self.assertEqual(resource_list.call_count, 1)
self.assertEqual(server_list.call_count, 1)
# TODO(lsmola) isn't it better to call image_list?
# this will call image_get for every unique image
# used that should not be much (4 images should be
# there for start)
# FIXME(lsmola) testing caching here is bad,
# because it gets cached for the whole tests run
self.assertEqual(image_get.call_count, 2)
# FIXME(lsmola) optimize this, it's enough to call
# node_list once
self.assertEqual(node_list.call_count, 1)
self.assertEqual(stack_get.call_count, 1)
for i in ret_val:
self.assertIsInstance(i, api.Resource)
self.assertEqual(4, len(ret_val))
def test_node_create(self):
node = self.ironicclient_nodes.first()
node = api.Node(self.ironicclient_nodes.first())
ret_val = api.Node.create(
self.request,
node.driver_info['ipmi_address'],
node.properties['cpu'],
node.properties['ram'],
node.properties['local_disk'],
['aa:aa:aa:aa:aa:aa'],
ipmi_username='admin',
ipmi_password='password')
ret_val.instance_uuid = None
# FIXME(lsmola) this should be mocking client call no Node
with patch('novaclient.v1_1.contrib.baremetal.'
'BareMetalNodeManager.create',
return_value=node):
ret_val = api.Node.create(
self.request,
node.driver_info['ipmi_address'],
node.properties['cpu'],
node.properties['ram'],
node.properties['local_disk'],
['aa:aa:aa:aa:aa:aa'],
ipmi_username='admin',
ipmi_password='password')
self.assertIsInstance(ret_val, api.Node)
self.assertIsNone(ret_val.instance)
def test_node_get(self):
node = self.ironicclient_nodes.first()
instance = self.novaclient_servers.first()
with patch('openstack_dashboard.api.nova.server_get',
return_value=instance):
with patch('novaclient.v1_1.contrib.baremetal.'
'BareMetalNodeManager.get',
return_value=node):
ret_val = api.Node.get(self.request, node.uuid)
ret_val = api.Node.get(self.request, node.uuid)
self.assertIsInstance(ret_val, api.Node)
self.assertIsInstance(ret_val.instance, servers.Server)
def test_node_get_by_instance_uuid(self):
instance = self.novaclient_servers.first()
node = self.ironicclient_nodes.first()
nodes = self.ironicclient_nodes.list()
with patch('openstack_dashboard.api.nova.server_get',
return_value=instance):
with patch('novaclient.v1_1.contrib.baremetal.'
'BareMetalNodeManager.list',
return_value=nodes):
ret_val = api.Node.get_by_instance_uuid(self.request,
node.instance_uuid)
ret_val = api.Node.get_by_instance_uuid(self.request,
node.instance_uuid)
self.assertIsInstance(ret_val, api.Node)
self.assertIsInstance(ret_val.instance, servers.Server)
def test_node_list(self):
#nodes = self.tuskarclient_overclouds.list()
instances = self.novaclient_servers.list()
nodes = self.ironicclient_nodes.list()
with patch('openstack_dashboard.api.nova.server_list',
return_value=(instances, None)):
with patch('novaclient.v1_1.contrib.baremetal.'
'BareMetalNodeManager.list',
return_value=nodes):
ret_val = api.Node.list(self.request)
ret_val = api.Node.list(self.request)
for node in ret_val:
self.assertIsInstance(node, api.Node)
self.assertEqual(5, len(ret_val))
def test_node_delete(self):
node = self.ironicclient_nodes.first()
api.Node.delete(self.request, node.uuid)
with patch('novaclient.v1_1.contrib.baremetal.'
'BareMetalNodeManager.delete',
return_value=None):
api.Node.delete(self.request, node.uuid)
def test_node_addresses(self):
node = self.ironicclient_nodes.first()
@ -137,17 +221,30 @@ class TuskarAPITests(test.APITestCase):
self.assertEqual(2, len(ret_val))
def test_resource_get(self):
overcloud = self.tuskarclient_overclouds.first()
stack = self.heatclient_stacks.first()
overcloud = api.Overcloud(self.tuskarclient_overclouds.first(),
request=None)
resource = self.heatclient_resources.first()
ret_val = api.Resource.get(self.request, overcloud,
resource.resource_name)
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.Resource.get(None, overcloud,
resource.resource_name)
self.assertIsInstance(ret_val, api.Resource)
def test_resource_node(self):
resource = self.heatclient_resources.first()
nodes = self.ironicclient_nodes.list()
instance = self.novaclient_servers.first()
ret_val = api.Resource(resource).node
with patch('openstack_dashboard.api.nova.server_get',
return_value=instance):
with patch('novaclient.v1_1.contrib.baremetal.'
'BareMetalNodeManager.list',
return_value=nodes):
ret_val = api.Resource(resource, request=None).node
self.assertIsInstance(ret_val, api.Node)
self.assertIsInstance(ret_val.instance, servers.Server)

View File

@ -105,7 +105,8 @@ def data(TEST):
TEST.ironicclient_nodes = test_data_utils.TestDataContainer()
node_1 = node.Node(
node.NodeManager(None),
{'uuid': 'aa-11',
{'id': '1',
'uuid': 'aa-11',
'instance_uuid': 'aa',
'driver': 'pxe_ipmitool',
'driver_info': {
@ -118,10 +119,23 @@ def data(TEST):
'ram': '16',
'local_disk': '10',
},
'power_state': 'on'})
'power_state': 'on',
# FIXME(lsmola) nova-baremetal test attrs, delete when Ironic is in
"pm_address": None,
"pm_user": None,
"task_state": "active",
"interfaces": [{"address": "52:54:00:90:38:01"},
{"address": "52:54:00:90:38:01"}],
"cpus": 1,
"memory_mb": 4096,
"service_host": "undercloud",
"local_gb": 20,
})
node_2 = node.Node(
node.NodeManager(None),
{'uuid': 'bb-22',
{'id': '2',
'uuid': 'bb-22',
'instance_uuid': 'bb',
'driver': 'pxe_ipmitool',
'driver_info': {
@ -134,10 +148,22 @@ def data(TEST):
'ram': '32',
'local_disk': '100',
},
'power_state': 'on'})
'power_state': 'on',
# FIXME(lsmola) nova-baremetal test attrs, delete when Ironic is in
"pm_address": None,
"pm_user": None,
"task_state": "active",
"interfaces": [{"address": "52:54:00:90:38:01"}],
"cpus": 1,
"memory_mb": 4096,
"service_host": "undercloud",
"local_gb": 20,
})
node_3 = node.Node(
node.NodeManager(None),
{'uuid': 'cc-33',
{'id': '3',
'uuid': 'cc-33',
'instance_uuid': None,
'driver': 'pxe_ipmitool',
'driver_info': {
@ -150,10 +176,22 @@ def data(TEST):
'ram': '64',
'local_disk': '1',
},
'power_state': 'rebooting'})
'power_state': 'rebooting',
# FIXME(lsmola) nova-baremetal test attrs, delete when Ironic is in
"pm_address": None,
"pm_user": None,
"task_state": "active",
"interfaces": [{"address": "52:54:00:90:38:01"}],
"cpus": 1,
"memory_mb": 4096,
"service_host": "undercloud",
"local_gb": 20,
})
node_4 = node.Node(
node.NodeManager(None),
{'uuid': 'cc-44',
{'id': '4',
'uuid': 'cc-44',
'instance_uuid': 'cc',
'driver': 'pxe_ipmitool',
'driver_info': {
@ -166,10 +204,22 @@ def data(TEST):
'ram': '16',
'local_disk': '10',
},
'power_state': 'on'})
'power_state': 'on',
# FIXME(lsmola) nova-baremetal test attrs, delete when Ironic is in
"pm_address": None,
"pm_user": None,
"task_state": "active",
"interfaces": [{"address": "52:54:00:90:38:01"}],
"cpus": 1,
"memory_mb": 4096,
"service_host": "undercloud",
"local_gb": 20,
})
node_5 = node.Node(
node.NodeManager(None),
{'uuid': 'dd-55',
{'id': '5',
'uuid': 'dd-55',
'instance_uuid': 'dd',
'driver': 'pxe_ipmitool',
'driver_info': {
@ -182,7 +232,18 @@ def data(TEST):
'ram': '16',
'local_disk': '10',
},
'power_state': 'on'})
'power_state': 'on',
# FIXME(lsmola) nova-baremetal test attrs, delete when Ironic is in
"pm_address": None,
"pm_user": None,
"task_state": "active",
"interfaces": [{"address": "52:54:00:90:38:01"}],
"cpus": 1,
"memory_mb": 4096,
"service_host": "undercloud",
"local_gb": 20,
})
TEST.ironicclient_nodes.add(node_1, node_2, node_3, node_4, node_5)
# Ports
@ -258,25 +319,25 @@ def data(TEST):
servers.ServerManager(None),
{'id': 'aa',
'name': 'Compute',
'image': 'compute-image',
'image': {'id': 1},
'status': 'ACTIVE'})
s_2 = servers.Server(
servers.ServerManager(None),
{'id': 'bb',
'name': 'Controller',
'image': 'controller-image',
'image': {'id': 2},
'status': 'ACTIVE'})
s_3 = servers.Server(
servers.ServerManager(None),
{'id': 'cc',
'name': 'Compute',
'image': 'compute-image',
'image': {'id': 1},
'status': 'BUILD'})
s_4 = servers.Server(
servers.ServerManager(None),
{'id': 'dd',
'name': 'Compute',
'image': 'compute-image',
'image': {'id': 1},
'status': 'ERROR'})
TEST.novaclient_servers.add(s_1, s_2, s_3, s_4)
@ -297,37 +358,41 @@ def data(TEST):
rc_1 = {'id': 1,
'name': 'Controller',
'description': 'controller resource category',
'image_id': 'image-id-1'}
'image_id': '2',
'image_name': 'overcloud-control'}
rc_2 = {'id': 2,
'name': 'Compute',
'description': 'compute resource category',
'image_id': 'image-id-2'}
'image_id': '1',
'image_name': 'overcloud-compute'}
rc_3 = {'id': 3,
'name': 'Object Storage',
'description': 'object storage resource category',
'image_id': 'image-id-3'}
'image_id': '3',
'image_name': 'overcloud-object-storage'}
rc_4 = {'id': 4,
'name': 'Block Storage',
'description': 'block storage resource category',
'image_id': 'image-id-4'}
'image_id': '4',
'image_name': 'overcloud-block-storage'}
TEST.tuskarclient_resource_categories.add(rc_1, rc_2, rc_3, rc_4)
# Image
TEST.glanceclient_images = test_data_utils.TestDataContainer()
image_1 = images.Image(
images.ImageManager(None),
{'id': 'image-id-1',
'name': 'Controller Image'})
{'id': '2',
'name': 'overcloud-control'})
image_2 = images.Image(
images.ImageManager(None),
{'id': 'image-id-2',
'name': 'Compute Image'})
{'id': '1',
'name': 'overcloud-compute'})
image_3 = images.Image(
images.ImageManager(None),
{'id': 'image-id-3',
{'id': '3',
'name': 'Object Storage Image'})
image_4 = images.Image(
images.ImageManager(None),
{'id': 'image-id-4',
{'id': '4',
'name': 'Block Storage Image'})
TEST.glanceclient_images.add(image_1, image_2, image_3, image_4)