diff --git a/tuskar_ui/api.py b/tuskar_ui/api.py
index 1254c6dd5..d5679022a 100644
--- a/tuskar_ui/api.py
+++ b/tuskar_ui/api.py
@@ -769,3 +769,14 @@ class OvercloudRole(base.APIResourceWrapper):
"""
role = tuskarclient(request).overcloud_roles.get(role_id)
return cls(role)
+
+ def update(self, request, **kwargs):
+ """Update the selected attributes of Tuskar OvercloudRole.
+
+ :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)
diff --git a/tuskar_ui/infrastructure/overcloud/forms.py b/tuskar_ui/infrastructure/overcloud/forms.py
index f150d6d69..e9b9efc2e 100644
--- a/tuskar_ui/infrastructure/overcloud/forms.py
+++ b/tuskar_ui/infrastructure/overcloud/forms.py
@@ -17,6 +17,7 @@ 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
@@ -35,11 +36,15 @@ class UndeployOvercloud(horizon.forms.SelfHandlingForm):
return True
-# TODO(rdopieralski) Get the list of flavors
-def get_flavors():
- yield (None, '----')
- yield ('xxx', 'Some Hardware Profile')
- yield ('yyy', 'Other Hardware Profile')
+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):
@@ -58,8 +63,18 @@ class OvercloudRoleForm(horizon.forms.SelfHandlingForm):
widget=django.forms.TextInput(
attrs={'readonly': 'readonly', 'disabled': 'disabled'}))
flavor_id = django.forms.ChoiceField(
- label=_("Node Profile"), required=False, choices=get_flavors())
+ label=_("Node Profile"), 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):
- # TODO(rdopieralski) Associate the flavor with the role
+ try:
+ role = api.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/templates/overcloud/node_counts.html b/tuskar_ui/infrastructure/overcloud/templates/overcloud/node_counts.html
index 210cd26fa..e69403673 100644
--- a/tuskar_ui/infrastructure/overcloud/templates/overcloud/node_counts.html
+++ b/tuskar_ui/infrastructure/overcloud/templates/overcloud/node_counts.html
@@ -16,21 +16,25 @@
{% if forloop.first %}
-
+ {% if editable %}
+
+ {% endif %}
{{ label }}
|
{% endif %}
{% if field.field.label %}
{{ field.label }}
- {% else %}
+ {% elif editable %}
({% trans "Add a node profile" %})
+ {% else %}
+ ({% trans "No node profile" %})
{% endif %}
|
diff --git a/tuskar_ui/infrastructure/overcloud/templates/overcloud/undeployed_overview.html b/tuskar_ui/infrastructure/overcloud/templates/overcloud/undeployed_overview.html
index cb09a429d..5c02e2ecd 100644
--- a/tuskar_ui/infrastructure/overcloud/templates/overcloud/undeployed_overview.html
+++ b/tuskar_ui/infrastructure/overcloud/templates/overcloud/undeployed_overview.html
@@ -11,7 +11,7 @@
{% trans "Roles" %}
- {% include 'infrastructure/overcloud/node_counts.html' with form=form %}
+ {% 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 a5ffc77ee..69fef3bdc 100644
--- a/tuskar_ui/infrastructure/overcloud/tests.py
+++ b/tuskar_ui/infrastructure/overcloud/tests.py
@@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import collections
import contextlib
from django.core import urlresolvers
@@ -101,13 +102,20 @@ class OvercloudTests(test.BaseAdminViewTests):
def test_create_get(self):
roles = TEST_DATA.tuskarclient_overcloud_roles.list()
- with contextlib.nested(patch('tuskar_ui.api.OvercloudRole', **{
- 'spec_set': ['list'],
- 'list.return_value': roles,
- }), patch('tuskar_ui.api.Node', **{
- 'spec_set': ['list'],
- 'list.return_value': [],
- })):
+ with contextlib.nested(
+ patch('tuskar_ui.api.OvercloudRole', **{
+ 'spec_set': ['list'],
+ 'list.return_value': roles,
+ }),
+ patch('tuskar_ui.api.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')
@@ -117,22 +125,30 @@ class OvercloudTests(test.BaseAdminViewTests):
def test_create_post(self):
oc = None
roles = TEST_DATA.tuskarclient_overcloud_roles.list()
+ old_flavor_id = roles[0].flavor_id
+ roles[0].flavor_id = 'default'
data = {
'count__1__default': '1',
- 'count__2__default': '0',
- 'count__3__default': '0',
- 'count__4__default': '0',
+ 'count__2__': '0',
+ 'count__3__': '0',
+ 'count__4__': '0',
}
with contextlib.nested(
patch('tuskar_ui.api.OvercloudRole', **{
'spec_set': ['list'],
- 'list.side_effect': lambda request: roles,
- }),
- patch('tuskar_ui.api.Overcloud', **{
+ 'list.return_value': roles,
+ }), patch('tuskar_ui.api.Overcloud', **{
'spec_set': ['create'],
- 'create.return_value': oc,
+ 'create.side_effect': lambda *args: oc,
+ }), patch('tuskar_ui.api.Node', **{
+ 'spec_set': ['list'],
+ 'list.return_value': [],
}),
- ) as (OvercloudRole, Overcloud):
+ patch('openstack_dashboard.api.nova', **{
+ 'spec_set': ['flavor_list'],
+ 'flavor_list.return_value': [],
+ }),
+ ) as (OvercloudRole, Overcloud, Node, nova):
oc = Overcloud
res = self.client.post(CREATE_URL, data)
request = Overcloud.create.call_args_list[0][0][0]
@@ -141,9 +157,9 @@ class OvercloudTests(test.BaseAdminViewTests):
[
call(request, {
('1', 'default'): 1,
- ('2', 'default'): 0,
- ('3', 'default'): 0,
- ('4', 'default'): 0,
+ ('2', ''): 0,
+ ('3', ''): 0,
+ ('4', ''): 0,
}, {
'NeutronPublicInterfaceRawDevice': '',
'NovaComputeDriver': '',
@@ -175,6 +191,7 @@ class OvercloudTests(test.BaseAdminViewTests):
'Flavor': '',
}),
])
+ roles[0].flavor_id = old_flavor_id
self.assertRedirectsNoFollow(res, INDEX_URL)
def test_detail_get(self):
@@ -308,7 +325,11 @@ class OvercloudTests(test.BaseAdminViewTests):
for role in roles
],
}),
- ) as (OvercloudRole, Overcloud):
+ 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,))
@@ -319,12 +340,14 @@ class OvercloudTests(test.BaseAdminViewTests):
def test_scale_post(self):
oc = None
roles = TEST_DATA.tuskarclient_overcloud_roles.list()
+ old_flavor_id = roles[0].flavor_id
+ roles[0].flavor_id = 'default'
data = {
'overcloud_id': '1',
'count__1__default': '1',
- 'count__2__default': '0',
- 'count__3__default': '0',
- 'count__4__default': '0',
+ 'count__2__': '0',
+ 'count__3__': '0',
+ 'count__4__': '0',
}
with contextlib.nested(
patch('tuskar_ui.api.OvercloudRole', **{
@@ -341,7 +364,11 @@ class OvercloudTests(test.BaseAdminViewTests):
for role in roles
],
}),
- ) as (OvercloudRole, Overcloud):
+ 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,))
@@ -353,21 +380,28 @@ class OvercloudTests(test.BaseAdminViewTests):
# [
# call(request, {
# ('1', 'default'): 1,
- # ('2', 'default'): 0,
- # ('3', 'default'): 0,
- # ('4', 'default'): 0,
+ # ('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 patch('tuskar_ui.api.OvercloudRole', **{
- 'spec_set': ['get'],
- 'get.return_value': role,
- }):
+ with contextlib.nested(
+ patch('tuskar_ui.api.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')
@@ -375,17 +409,42 @@ class OvercloudTests(test.BaseAdminViewTests):
res, 'infrastructure/overcloud/_role_edit.html')
def test_role_edit_post(self):
- role = TEST_DATA.tuskarclient_overcloud_roles.first()
- url = urlresolvers.reverse(
- 'horizon:infrastructure:overcloud:role_edit', args=(role.id,))
- data = {
- 'id': '1',
- 'flavor_id': 'xxx',
- }
- with patch('tuskar_ui.api.OvercloudRole', **{
- 'spec_set': ['get'],
- 'get.return_value': role,
- }):
- # TODO(rdopieralski) Check if the role got associated with flavor.
+ role = None
+ Flavor = collections.namedtuple('Flavor', 'id name')
+ flavor = Flavor('xxx', 'Xxx')
+ with contextlib.nested(
+ patch('tuskar_ui.api.OvercloudRole', **{
+ 'spec_set': [
+ 'get',
+ 'update',
+ 'id',
+ 'name',
+ 'description',
+ 'image_name',
+ 'flavor_id',
+ ],
+ 'get.side_effect': lambda *args: 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/workflows/undeployed_overview.py b/tuskar_ui/infrastructure/overcloud/workflows/undeployed_overview.py
index b9552e112..a66f9c466 100644
--- a/tuskar_ui/infrastructure/overcloud/workflows/undeployed_overview.py
+++ b/tuskar_ui/infrastructure/overcloud/workflows/undeployed_overview.py
@@ -16,6 +16,7 @@ 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
@@ -39,8 +40,31 @@ class Action(horizon.workflows.Action):
slug = 'undeployed_overview'
name = _("Overview")
+ def _get_profile_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_profiles(self, role, profile_names):
+ # TODO(rdopieralski) Get a list of hardware profiles for each
+ # role here, when we support multiple profiles per role.
+ if role.flavor_id:
+ profiles = [(
+ role.flavor_id,
+ profile_names.get(str(role.flavor_id), role.flavor_id),
+ )]
+ else:
+ profiles = []
+ return profiles
+
def __init__(self, *args, **kwargs):
super(Action, self).__init__(*args, **kwargs)
+ profile_names = self._get_profile_names()
for role in self._get_roles():
if role.name == 'Controller':
initial = 1
@@ -48,18 +72,16 @@ class Action(horizon.workflows.Action):
else:
initial = 0
attrs = {}
- # TODO(rdopieralski) Get a list of hardware profiles for each
- # role here.
- profiles = [(_("Default"), 'default')]
+ profiles = self._get_profiles(role, profile_names)
if not profiles:
name = get_field_name_from_role_id_and_profile_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 label, profile in profiles:
+ for profile_id, label in profiles:
name = get_field_name_from_role_id_and_profile_id(
- str(role.id), profile)
+ str(role.id), profile_id)
self.fields[name] = django.forms.IntegerField(
label=label, initial=initial, min_value=initial,
widget=tuskar_ui.forms.NumberPickerInput(attrs=attrs))
diff --git a/tuskar_ui/test/test_data/tuskar_data.py b/tuskar_ui/test/test_data/tuskar_data.py
index 733a4f537..0567623c3 100644
--- a/tuskar_ui/test/test_data/tuskar_data.py
+++ b/tuskar_ui/test/test_data/tuskar_data.py
@@ -369,25 +369,28 @@ def data(TEST):
'name': 'Controller',
'description': 'controller overcloud role',
'image_name': 'overcloud-control',
- 'flavor_id': None,
+ '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)
|