Update the deployment design page
* Real free node count. * Role list taken from API, not hardcoded. * Dialog for associating roles with hardware profiles. TODO: * Get hardware profiles list. * Actually update the role.flavor_id. * Update the free node count with JavaScript. Change-Id: Ieeb100f25ec40ab8bc260ea542f098019d5cd04b Implements: blueprint tripleo-ui-deployment-design
This commit is contained in:
parent
1fc6db3187
commit
02e71c472e
@ -114,11 +114,10 @@ class Overcloud(base.APIResourceWrapper):
|
||||
# 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 = []
|
||||
for role_flavor_list, sizing in overcloud_sizing.items():
|
||||
transformed_sizing.append(
|
||||
{'overcloud_role_id': role_flavor_list[0],
|
||||
'num_nodes': sizing})
|
||||
transformed_sizing = [{
|
||||
'overcloud_role_id': role,
|
||||
'num_nodes': sizing,
|
||||
} for (role, flavor), sizing in overcloud_sizing.items()]
|
||||
|
||||
overcloud = tuskarclient(request).overclouds.create(
|
||||
name='overcloud', description="Openstack cloud providing VMs",
|
||||
|
@ -1,4 +1,4 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
# -*- 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
|
||||
@ -12,23 +12,54 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _ # noqa
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
import django.forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
import horizon.exceptions
|
||||
import horizon.forms
|
||||
import horizon.messages
|
||||
|
||||
from tuskar_ui import api
|
||||
|
||||
|
||||
class UndeployOvercloud(forms.SelfHandlingForm):
|
||||
class UndeployOvercloud(horizon.forms.SelfHandlingForm):
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
api.Overcloud.delete(request, self.initial['overcloud_id'])
|
||||
except Exception:
|
||||
exceptions.handle(request, _("Unable to undeploy overcloud."))
|
||||
horizon.exceptions.handle(request,
|
||||
_("Unable to undeploy overcloud."))
|
||||
return False
|
||||
else:
|
||||
msg = _('Undeployment in progress.')
|
||||
messages.success(request, msg)
|
||||
horizon.messages.success(request, msg)
|
||||
return True
|
||||
|
||||
|
||||
# TODO(rdopieralski) Get the list of flavors
|
||||
def get_flavors():
|
||||
yield (None, '----')
|
||||
yield ('xxx', 'Some Hardware Profile')
|
||||
yield ('yyy', 'Other Hardware Profile')
|
||||
|
||||
|
||||
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=_("Node Profile"), required=False, choices=get_flavors())
|
||||
|
||||
def handle(self, request, context):
|
||||
# TODO(rdopieralski) Associate the flavor with the role
|
||||
return True
|
||||
|
@ -0,0 +1,15 @@
|
||||
{% 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 %}
|
||||
<input class="btn btn-primary pull-right" type="submit"
|
||||
value="{% trans "Apply Changes" %}" />
|
||||
<a href="{% url 'horizon:infrastructure:overcloud:create' %}"
|
||||
class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -16,12 +16,29 @@
|
||||
<tr>
|
||||
{% if forloop.first %}
|
||||
<td rowspan="{{ fields|length }}">
|
||||
<i class="icon-pencil"></i>
|
||||
<a
|
||||
href="{% url 'horizon:infrastructure:overcloud:role_edit' role_id %}"
|
||||
class="ajax-modal"
|
||||
><i class="icon-pencil"></i></a>
|
||||
{{ label }}
|
||||
</td>
|
||||
{% endif %}
|
||||
<td>{{ field.label }}</td>
|
||||
<td>{{ field }}</td>
|
||||
<td>
|
||||
{% if field.field.label %}
|
||||
{{ field.label }}
|
||||
{% else %}
|
||||
(<a
|
||||
href="{% url 'horizon:infrastructure:overcloud:role_edit' role_id %}"
|
||||
class="ajax-modal"
|
||||
>{% trans "Add a node profile" %}</a>)
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ field }}
|
||||
{% for error in field.errors %}
|
||||
<span class="help-inline">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
{% if show_change %}
|
||||
<td class="changed"><span class="muted">no change</span></td>
|
||||
{% endif %}
|
||||
|
@ -0,0 +1,11 @@
|
||||
{% 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 %}
|
@ -1,11 +1,13 @@
|
||||
{% load i18n %}
|
||||
{% load url from future%}
|
||||
|
||||
<noscript><h3>{{ step }}</h3></noscript>
|
||||
<div class="widget muted">{{ step.get_help_text }}</div>
|
||||
|
||||
<div class="row-fluid">
|
||||
<div class="span8">
|
||||
<div class="pull-right">
|
||||
{{ free_nodes|default:0 }} {% trans "free nodes" %}
|
||||
<span id="free-nodes">{{ step.get_free_nodes }}</span> {% trans "free nodes" %}
|
||||
</div>
|
||||
<h2>{% trans "Roles" %}</h2>
|
||||
<div class="widget">
|
||||
|
@ -98,10 +98,13 @@ class OvercloudTests(test.BaseAdminViewTests):
|
||||
|
||||
def test_create_get(self):
|
||||
roles = TEST_DATA.tuskarclient_overcloud_roles.list()
|
||||
with patch('tuskar_ui.api.OvercloudRole', **{
|
||||
with contextlib.nested(patch('tuskar_ui.api.OvercloudRole', **{
|
||||
'spec_set': ['list'],
|
||||
'list.side_effect': lambda request: roles,
|
||||
}):
|
||||
'list.return_value': roles,
|
||||
}), patch('tuskar_ui.api.Node', **{
|
||||
'spec_set': ['list'],
|
||||
'list.return_value': [],
|
||||
})):
|
||||
res = self.client.get(CREATE_URL)
|
||||
self.assertTemplateUsed(
|
||||
res, 'infrastructure/_fullscreen_workflow_base.html')
|
||||
@ -109,7 +112,7 @@ class OvercloudTests(test.BaseAdminViewTests):
|
||||
res, 'infrastructure/overcloud/undeployed_overview.html')
|
||||
|
||||
def test_create_post(self):
|
||||
oc = api.Overcloud(TEST_DATA.tuskarclient_overclouds.first())
|
||||
oc = None
|
||||
roles = TEST_DATA.tuskarclient_overcloud_roles.list()
|
||||
data = {
|
||||
'count__1__default': '1',
|
||||
@ -117,55 +120,58 @@ class OvercloudTests(test.BaseAdminViewTests):
|
||||
'count__3__default': '0',
|
||||
'count__4__default': '0',
|
||||
}
|
||||
with patch('tuskar_ui.api.OvercloudRole', **{
|
||||
'spec_set': ['list'],
|
||||
'list.side_effect': lambda request: roles,
|
||||
}):
|
||||
with patch('tuskar_ui.api.Overcloud', **{
|
||||
'spec_set': ['create'],
|
||||
'create.return_value': oc,
|
||||
}) as Overcloud:
|
||||
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', 'default'): 1,
|
||||
('2', 'default'): 0,
|
||||
('3', 'default'): 0,
|
||||
('4', 'default'): 0,
|
||||
}, {
|
||||
'NeutronPublicInterfaceRawDevice': '',
|
||||
'NovaComputeDriver': '',
|
||||
'NeutronPassword': '',
|
||||
'NeutronFlatNetworks': '',
|
||||
'NeutronPublicInterfaceDefaultRoute': '',
|
||||
'HeatPassword': '',
|
||||
'notcomputeImage': '',
|
||||
'NovaImage': '',
|
||||
'SSLKey': '',
|
||||
'KeyName': '',
|
||||
'GlancePassword': '',
|
||||
'CeilometerPassword': '',
|
||||
'NtpServer': '',
|
||||
'CinderPassword': '',
|
||||
'ImageUpdatePolicy': '',
|
||||
'NeutronPublicInterface': '',
|
||||
'NovaPassword': '',
|
||||
'AdminToken': '',
|
||||
'SwiftHashSuffix': '',
|
||||
'NeutronPublicInterfaceIP': '',
|
||||
'NovaComputeLibvirtType': '',
|
||||
'AdminPassword': '',
|
||||
'CeilometerComputeAgent': '',
|
||||
'NeutronBridgeMappings': '',
|
||||
'SwiftPassword': '',
|
||||
'CeilometerMeteringSecret': '',
|
||||
'SSLCertificate': '',
|
||||
'Flavor': '',
|
||||
}),
|
||||
])
|
||||
with contextlib.nested(
|
||||
patch('tuskar_ui.api.OvercloudRole', **{
|
||||
'spec_set': ['list'],
|
||||
'list.side_effect': lambda request: roles,
|
||||
}),
|
||||
patch('tuskar_ui.api.Overcloud', **{
|
||||
'spec_set': ['create'],
|
||||
'create.return_value': oc,
|
||||
}),
|
||||
) as (OvercloudRole, Overcloud):
|
||||
oc = Overcloud
|
||||
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', 'default'): 1,
|
||||
('2', 'default'): 0,
|
||||
('3', 'default'): 0,
|
||||
('4', 'default'): 0,
|
||||
}, {
|
||||
'NeutronPublicInterfaceRawDevice': '',
|
||||
'NovaComputeDriver': '',
|
||||
'NeutronPassword': '',
|
||||
'NeutronFlatNetworks': '',
|
||||
'NeutronPublicInterfaceDefaultRoute': '',
|
||||
'HeatPassword': '',
|
||||
'notcomputeImage': '',
|
||||
'NovaImage': '',
|
||||
'SSLKey': '',
|
||||
'KeyName': '',
|
||||
'GlancePassword': '',
|
||||
'CeilometerPassword': '',
|
||||
'NtpServer': '',
|
||||
'CinderPassword': '',
|
||||
'ImageUpdatePolicy': '',
|
||||
'NeutronPublicInterface': '',
|
||||
'NovaPassword': '',
|
||||
'AdminToken': '',
|
||||
'SwiftHashSuffix': '',
|
||||
'NeutronPublicInterfaceIP': '',
|
||||
'NovaComputeLibvirtType': '',
|
||||
'AdminPassword': '',
|
||||
'CeilometerComputeAgent': '',
|
||||
'NeutronBridgeMappings': '',
|
||||
'SwiftPassword': '',
|
||||
'CeilometerMeteringSecret': '',
|
||||
'SSLCertificate': '',
|
||||
'Flavor': '',
|
||||
}),
|
||||
])
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
def test_detail_get(self):
|
||||
@ -192,7 +198,7 @@ class OvercloudTests(test.BaseAdminViewTests):
|
||||
'stack_events': [],
|
||||
}), patch('tuskar_ui.api.OvercloudRole', **{
|
||||
'spec_set': ['list'],
|
||||
'list.side_effect': lambda request: roles,
|
||||
'list.return_value': roles,
|
||||
})) as (Overcloud, OvercloudRole):
|
||||
oc = Overcloud
|
||||
res = self.client.get(DETAIL_URL)
|
||||
@ -285,3 +291,33 @@ class OvercloudTests(test.BaseAdminViewTests):
|
||||
# }),
|
||||
# ])
|
||||
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,
|
||||
}):
|
||||
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 = 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.
|
||||
res = self.client.post(url, data)
|
||||
self.assertRedirectsNoFollow(res, CREATE_URL)
|
||||
|
@ -12,25 +12,24 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.conf.urls import defaults
|
||||
from django.conf import urls
|
||||
|
||||
from tuskar_ui.infrastructure.overcloud import views
|
||||
|
||||
|
||||
urlpatterns = defaults.patterns(
|
||||
urlpatterns = urls.patterns(
|
||||
'',
|
||||
defaults.url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
defaults.url(r'^create/$', views.CreateView.as_view(),
|
||||
name='create'),
|
||||
defaults.url(r'^(?P<overcloud_id>[^/]+)/$',
|
||||
views.DetailView.as_view(), name='detail'),
|
||||
defaults.url(r'^(?P<overcloud_id>[^/]+)/scale$',
|
||||
views.Scale.as_view(), name='scale'),
|
||||
defaults.url(r'^(?P<overcloud_id>[^/]+)/role/'
|
||||
'(?P<role_id>[^/]+)$',
|
||||
views.OvercloudRoleView.as_view(),
|
||||
name='role'),
|
||||
defaults.url(r'^(?P<overcloud_id>[^/]+)/undeploy-confirmation$',
|
||||
views.UndeployConfirmationView.as_view(),
|
||||
name='undeploy_confirmation'),
|
||||
urls.url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
urls.url(r'^create/$', views.CreateView.as_view(), name='create'),
|
||||
urls.url(r'^create/role-edit/(?P<role_id>[^/]+)$',
|
||||
views.OvercloudRoleEdit.as_view(), name='role_edit'),
|
||||
urls.url(r'^(?P<overcloud_id>[^/]+)/$', views.DetailView.as_view(),
|
||||
name='detail'),
|
||||
urls.url(r'^(?P<overcloud_id>[^/]+)/scale$', views.Scale.as_view(),
|
||||
name='scale'),
|
||||
urls.url(r'^(?P<overcloud_id>[^/]+)/role/(?P<role_id>[^/]+)$',
|
||||
views.OvercloudRoleView.as_view(), name='role'),
|
||||
urls.url(r'^(?P<overcloud_id>[^/]+)/undeploy-confirmation$',
|
||||
views.UndeployConfirmationView.as_view(),
|
||||
name='undeploy_confirmation'),
|
||||
)
|
||||
|
@ -17,6 +17,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import base as base_views
|
||||
|
||||
from horizon import exceptions
|
||||
import horizon.forms
|
||||
from horizon import tables as horizon_tables
|
||||
from horizon import tabs as horizon_tabs
|
||||
from horizon.utils import memoized
|
||||
@ -48,6 +49,18 @@ class OvercloudMixin(object):
|
||||
return overcloud
|
||||
|
||||
|
||||
class OvercloudRoleMixin(object):
|
||||
@memoized.memoized
|
||||
def get_role(self, redirect=None):
|
||||
role_id = self.kwargs['role_id']
|
||||
try:
|
||||
role = api.OvercloudRole.get(self.request, role_id)
|
||||
except Exception:
|
||||
msg = _("Unable to retrieve overcloud role.")
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
return role
|
||||
|
||||
|
||||
class IndexView(base_views.RedirectView):
|
||||
permanent = False
|
||||
|
||||
@ -125,56 +138,49 @@ class Scale(horizon.workflows.WorkflowView, OvercloudMixin):
|
||||
}
|
||||
|
||||
|
||||
class OvercloudRoleView(horizon_tables.DataTableView):
|
||||
class OvercloudRoleView(horizon_tables.DataTableView,
|
||||
OvercloudRoleMixin, OvercloudMixin):
|
||||
table_class = tables.OvercloudRoleNodeTable
|
||||
template_name = 'infrastructure/overcloud/overcloud_role.html'
|
||||
|
||||
def get_data(self):
|
||||
overcloud = self._get_overcloud()
|
||||
role = self._get_role(overcloud)
|
||||
|
||||
return self._get_nodes(overcloud, role)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(OvercloudRoleView, self).get_context_data(**kwargs)
|
||||
|
||||
overcloud = self._get_overcloud()
|
||||
role = self._get_role(overcloud)
|
||||
|
||||
context['role'] = role
|
||||
context['image_name'] = role.image_name
|
||||
context['nodes'] = self._get_nodes(overcloud, role)
|
||||
|
||||
return context
|
||||
|
||||
@memoized.memoized
|
||||
def _get_nodes(self, overcloud, role):
|
||||
resources = overcloud.resources(role, with_joins=True)
|
||||
return [r.node for r in resources]
|
||||
|
||||
@memoized.memoized
|
||||
def _get_overcloud(self):
|
||||
overcloud_id = self.kwargs['overcloud_id']
|
||||
def get_data(self):
|
||||
overcloud = self.get_overcloud()
|
||||
redirect = reverse('horizon:infrastructure:overcloud:detail',
|
||||
args=(overcloud.id,))
|
||||
role = self.get_role(redirect)
|
||||
return self._get_nodes(overcloud, role)
|
||||
|
||||
try:
|
||||
overcloud = api.Overcloud.get(self.request, overcloud_id)
|
||||
except Exception:
|
||||
msg = _("Unable to retrieve deployment.")
|
||||
redirect = reverse('horizon:infrastructure:overcloud:index')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(OvercloudRoleView, self).get_context_data(**kwargs)
|
||||
|
||||
return overcloud
|
||||
overcloud = self.get_overcloud()
|
||||
redirect = reverse('horizon:infrastructure:overcloud:detail',
|
||||
args=(overcloud.id,))
|
||||
role = self.get_role(redirect)
|
||||
context['role'] = role
|
||||
context['image_name'] = role.image_name
|
||||
context['nodes'] = self._get_nodes(overcloud, role)
|
||||
return context
|
||||
|
||||
@memoized.memoized
|
||||
def _get_role(self, overcloud):
|
||||
role_id = self.kwargs['role_id']
|
||||
|
||||
try:
|
||||
role = api.OvercloudRole.get(self.request, role_id)
|
||||
except Exception:
|
||||
msg = _("Unable to retrieve overcloud role.")
|
||||
redirect = reverse('horizon:infrastructure:overcloud:detail',
|
||||
args=(overcloud.id,))
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
class OvercloudRoleEdit(horizon.forms.ModalFormView, OvercloudRoleMixin):
|
||||
form_class = forms.OvercloudRoleForm
|
||||
template_name = 'infrastructure/overcloud/role_edit.html'
|
||||
|
||||
return role
|
||||
def get_success_url(self):
|
||||
return reverse('horizon:infrastructure:overcloud:create')
|
||||
|
||||
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,
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from horizon import exceptions
|
||||
import horizon.workflows
|
||||
|
||||
from tuskar_ui import api
|
||||
# from tuskar_ui import api
|
||||
from tuskar_ui.infrastructure.overcloud.workflows import scale_node_counts
|
||||
|
||||
|
||||
@ -30,19 +30,18 @@ class Workflow(horizon.workflows.Workflow):
|
||||
finalize_button_name = _("Apply Changes")
|
||||
|
||||
def handle(self, request, context):
|
||||
success = True
|
||||
overcloud_id = self.context['overcloud_id']
|
||||
# overcloud_id = self.context['overcloud_id']
|
||||
try:
|
||||
# TODO(rdopieralski) Actually update it when possible.
|
||||
overcloud = api.Overcloud.get(request, overcloud_id) # noqa
|
||||
# overcloud = api.Overcloud.get(request, overcloud_id)
|
||||
# overcloud.update(self.request, context['role_counts'])
|
||||
pass
|
||||
except Exception:
|
||||
success = False
|
||||
exceptions.handle(request, _('Unable to update deployment.'))
|
||||
return success
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_success_url(self):
|
||||
overcloud_id = self.context.get('overcloud_id')
|
||||
overcloud_id = self.context.get('overcloud_id', 1)
|
||||
return reverse('horizon:infrastructure:overcloud:detail',
|
||||
args=(overcloud_id,))
|
||||
|
@ -21,6 +21,19 @@ from tuskar_ui import api
|
||||
import tuskar_ui.forms
|
||||
|
||||
|
||||
def get_role_id_and_profile_id_from_field_name(field_name):
|
||||
"""Extract the ids of overcloud role and node profile from the field
|
||||
name.
|
||||
"""
|
||||
_count, role_id, profile_id = field_name.split('__', 2)
|
||||
return role_id, profile_id
|
||||
|
||||
|
||||
def get_field_name_from_role_id_and_profile_id(role_id, profile_id=''):
|
||||
"""Compose the ids of overcloud role and node profile into a field name."""
|
||||
return 'count__%s__%s' % (role_id, profile_id)
|
||||
|
||||
|
||||
class Action(horizon.workflows.Action):
|
||||
class Meta:
|
||||
slug = 'undeployed_overview'
|
||||
@ -29,41 +42,60 @@ class Action(horizon.workflows.Action):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Action, self).__init__(*args, **kwargs)
|
||||
for role in self._get_roles():
|
||||
# TODO(rdopieralski) Get a list of hardware profiles for each
|
||||
# role here.
|
||||
name = 'count__%s__%s' % (str(role.id), 'default')
|
||||
if role.name == 'Controller':
|
||||
initial = 1
|
||||
self.fields[name] = django.forms.IntegerField(
|
||||
label=_("Default"), initial=initial, min_value=initial,
|
||||
widget=tuskar_ui.forms.NumberPickerInput(attrs={
|
||||
'readonly': 'readonly',
|
||||
}))
|
||||
attrs = {'readonly': 'readonly'}
|
||||
else:
|
||||
initial = 0
|
||||
attrs = {}
|
||||
# TODO(rdopieralski) Get a list of hardware profiles for each
|
||||
# role here.
|
||||
profiles = [(_("Default"), 'default')]
|
||||
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=_("Default"), initial=initial, min_value=initial,
|
||||
widget=tuskar_ui.forms.NumberPickerInput)
|
||||
label='', initial=initial, min_value=initial,
|
||||
widget=tuskar_ui.forms.NumberPickerInput(attrs=attrs))
|
||||
for label, profile in profiles:
|
||||
name = get_field_name_from_role_id_and_profile_id(
|
||||
str(role.id), profile)
|
||||
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='count__%s__' % str(role.id))),
|
||||
self, prefix=get_field_name_from_role_id_and_profile_id(
|
||||
str(role.id)))),
|
||||
)
|
||||
|
||||
@memoized.memoized
|
||||
def _get_roles(self):
|
||||
"""Retrieve the list of all overcloud roles."""
|
||||
return api.OvercloudRole.list(self.request)
|
||||
|
||||
def clean(self):
|
||||
for key, value in self.cleaned_data.iteritems():
|
||||
if not key.startswith('count_'):
|
||||
continue
|
||||
role_id, profile = get_role_id_and_profile_id_from_field_name(key)
|
||||
if int(value) and not profile:
|
||||
raise django.forms.ValidationError(
|
||||
_("Can't deploy nodes without a node profile assigned."))
|
||||
return self.cleaned_data
|
||||
|
||||
def handle(self, request, context):
|
||||
counts = {}
|
||||
for key, value in self.cleaned_data.iteritems():
|
||||
if not key.startswith('count_'):
|
||||
continue
|
||||
_count, role_id, profile = key.split('__', 2)
|
||||
count, role_id, profile = key.split('__', 2)
|
||||
counts[role_id, profile] = int(value)
|
||||
context['role_counts'] = counts
|
||||
return context
|
||||
@ -75,7 +107,6 @@ class Step(horizon.workflows.Step):
|
||||
template_name = 'infrastructure/overcloud/undeployed_overview.html'
|
||||
help_text = _("Nothing deployed yet. Design your first deployment.")
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
context = super(Step, self).get_context_data(*args, **kwargs)
|
||||
context['free_nodes'] = 3
|
||||
return context
|
||||
def get_free_nodes(self):
|
||||
"""Get the count of nodes that are not assigned yet."""
|
||||
return len(api.Node.list(self.workflow.request, False))
|
||||
|
@ -360,10 +360,13 @@ def data(TEST):
|
||||
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'})
|
||||
{
|
||||
'id': 1,
|
||||
'name': 'Controller',
|
||||
'description': 'controller overcloud role',
|
||||
'image_name': 'overcloud-control',
|
||||
'flavor_id': None,
|
||||
})
|
||||
r_2 = overcloud_roles.OvercloudRole(
|
||||
overcloud_roles.OvercloudRoleManager(None),
|
||||
{'id': 2,
|
||||
|
Loading…
x
Reference in New Issue
Block a user