From 4a2fd4f74264b5cad2723f0021bdff6f652f5af3 Mon Sep 17 00:00:00 2001 From: Radomir Dopieralski Date: Thu, 19 Feb 2015 16:22:57 +0100 Subject: [PATCH] Add node count field to the role edit dialog Change-Id: If5e16b54bd41be404334432647f3192afb982961 --- .../roles/templates/roles/info.html | 11 +++++ tuskar_ui/infrastructure/roles/tests.py | 3 ++ tuskar_ui/infrastructure/roles/views.py | 38 ++++++++------- tuskar_ui/infrastructure/roles/workflows.py | 47 +++++++++++++++---- tuskar_ui/infrastructure/views.py | 7 ++- 5 files changed, 79 insertions(+), 27 deletions(-) create mode 100644 tuskar_ui/infrastructure/roles/templates/roles/info.html diff --git a/tuskar_ui/infrastructure/roles/templates/roles/info.html b/tuskar_ui/infrastructure/roles/templates/roles/info.html new file mode 100644 index 000000000..2909bd1cf --- /dev/null +++ b/tuskar_ui/infrastructure/roles/templates/roles/info.html @@ -0,0 +1,11 @@ + +
+
+
+ {% include "horizon/common/_horizontal_fields.html" %} +
+
+
+ {{ step.get_help_text }} +
+
diff --git a/tuskar_ui/infrastructure/roles/tests.py b/tuskar_ui/infrastructure/roles/tests.py index ec653ef4e..5fd2aeb0e 100644 --- a/tuskar_ui/infrastructure/roles/tests.py +++ b/tuskar_ui/infrastructure/roles/tests.py @@ -134,8 +134,11 @@ class RolesTest(test.BaseAdminViewTests): images = self.glanceclient_images.list() data = { + 'name': 'controller', + 'description': 'The controller node role.', 'flavor': self.novaclient_flavors.first().name, 'image': self.glanceclient_images.first().id, + 'nodes': '0', } with contextlib.nested( diff --git a/tuskar_ui/infrastructure/roles/views.py b/tuskar_ui/infrastructure/roles/views.py index 726ce9fb0..7ff6068d1 100644 --- a/tuskar_ui/infrastructure/roles/views.py +++ b/tuskar_ui/infrastructure/roles/views.py @@ -14,12 +14,10 @@ import json from django.core.urlresolvers import reverse -from django.core.urlresolvers import reverse_lazy from django import http from django.utils.translation import ugettext_lazy as _ from django.views.generic import base from glanceclient import exc as glance_exc -from horizon import exceptions as horizon_exceptions from horizon import tables as horizon_tables from horizon import utils from horizon import workflows @@ -127,20 +125,19 @@ class DetailView(horizon_tables.DataTableView, views.RoleMixin, return context -class UpdateView(workflows.WorkflowView): +class UpdateView(workflows.WorkflowView, views.StackMixin, views.RoleMixin): workflow_class = role_workflows.UpdateRole def get_initial(self): - role_id = self.kwargs['role_id'] + plan = self.get_plan() + role = self.get_role() - try: - # Get initial role information - plan = api.tuskar.Plan.get_the_plan(self.request) - role = api.tuskar.Role.get(self.request, role_id) - except Exception: - horizon_exceptions.handle(self.request, - _('Unable to retrieve role details.'), - redirect=reverse_lazy(INDEX_URL)) + stack = self.get_stack() + if stack: + resources = stack.resources(role=role, with_joins=True) + role_nodes = len(resources) + else: + role_nodes = 0 role_flavor = role.flavor(plan) role_flavor = '' if role_flavor is None else role_flavor.name @@ -148,11 +145,18 @@ class UpdateView(workflows.WorkflowView): role_image = role.image(plan) role_image = '' if role_image is None else role_image.id - return {'role_id': role.id, - 'name': role.name, - 'flavor': role_flavor, - 'image': role_image, - } + free_nodes = len(api.node.Node.list(self.request, associated=False, + maintenance=False)) + available_nodes = role_nodes + free_nodes + + return { + 'role_id': role.id, + 'name': role.name, + 'flavor': role_flavor, + 'image': role_image, + 'nodes': role_nodes, + 'available_nodes': available_nodes, + } class PerformanceView(base.TemplateView, views.RoleMixin, views.StackMixin): diff --git a/tuskar_ui/infrastructure/roles/workflows.py b/tuskar_ui/infrastructure/roles/workflows.py index 089f06cdf..2a138c108 100644 --- a/tuskar_ui/infrastructure/roles/workflows.py +++ b/tuskar_ui/infrastructure/roles/workflows.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. from django.core.urlresolvers import reverse_lazy +import django.forms from django.utils.translation import ugettext_lazy as _ from horizon import exceptions from horizon import forms @@ -26,25 +27,28 @@ from tuskar_ui.utils import utils as tuskar_utils class UpdateRoleInfoAction(workflows.Action): + # TODO(rdopiera) Make the name and description editable. name = forms.CharField( label=_("Name"), - widget=tuskar_ui.forms.LabelWidget(), required=False, + widget=tuskar_ui.forms.StaticTextWidget ) - description = forms.CharField( label=_("Description"), - widget=tuskar_ui.forms.LabelWidget(), required=False, + widget=tuskar_ui.forms.StaticTextWidget ) - flavor = forms.ChoiceField( label=_("Flavor"), ) - image = forms.ChoiceField( label=_("Image"), ) + nodes = forms.IntegerField( + label=_("Number of Nodes"), + required=False, + initial=0, + ) class Meta(object): name = _("Overall Settings") @@ -54,6 +58,13 @@ class UpdateRoleInfoAction(workflows.Action): def __init__(self, request, context, *args, **kwargs): super(UpdateRoleInfoAction, self).__init__(request, context, *args, **kwargs) + self.available_nodes = context['available_nodes'] + self.fields['nodes'].widget = tuskar_ui.forms.NumberInput(attrs={ + 'min': 0, + 'max': self.available_nodes, + }) + self.fields['nodes'].help_text = _( + "{0} nodes available").format(self.available_nodes) if not utils.matching_deployment_mode(): del self.fields['flavor'] @@ -70,6 +81,24 @@ class UpdateRoleInfoAction(workflows.Action): choices = [(i.id, i.name) for i in images] return [('', _('Unknown'))] + choices + def clean_nodes(self): + new_count = int(self.cleaned_data['nodes'] or 0) + if new_count > self.available_nodes: + raise django.forms.ValidationError(_( + "There are only {0} nodes available " + "for the selected flavor." + ).format(self.available_nodes)) + return str(new_count) + + def handle(self, request, context): + return { + 'name': self.cleaned_data['name'], + 'description': self.cleaned_data['description'], + 'flavor': self.cleaned_data.get('flavor'), + 'image': self.cleaned_data['image'], + 'nodes': self.cleaned_data['nodes'], + } + class UpdateRoleConfigAction(workflows.Action): class Meta(object): @@ -92,8 +121,9 @@ class UpdateRoleConfigAction(workflows.Action): class UpdateRoleInfo(workflows.Step): action_class = UpdateRoleInfoAction - depends_on = ("role_id",) - contributes = ("name", "flavor", "image",) + depends_on = ("role_id", "available_nodes") + contributes = ("name", "description", "flavor", "image", "nodes") + template_name = 'infrastructure/roles/info.html' class UpdateRoleConfig(workflows.Step): @@ -141,9 +171,10 @@ class UpdateRole(workflows.Workflow): parameters = data['parameters'] parameters[role.image_id_parameter_name] = data['image'] + parameters[role.node_count_parameter_name] = data['nodes'] if utils.matching_deployment_mode(): parameters[role.flavor_parameter_name] = data['flavor'] plan.patch(request, plan.uuid, parameters) - # success + # TODO(rdopiera) Find out how to update role's name and description. return True diff --git a/tuskar_ui/infrastructure/views.py b/tuskar_ui/infrastructure/views.py index 7c0219bc0..b22aade3b 100644 --- a/tuskar_ui/infrastructure/views.py +++ b/tuskar_ui/infrastructure/views.py @@ -28,10 +28,13 @@ class ItemCountMixin(object): class StackMixin(object): + @memoized.memoized + def get_plan(self): + return api.tuskar.Plan.get_the_plan(self.request) + @memoized.memoized def get_stack(self): - plan = api.tuskar.Plan.get_the_plan(self.request) - return api.heat.Stack.get_by_plan(self.request, plan) + return api.heat.Stack.get_by_plan(self.request, self.get_plan()) class RoleMixin(object):