Guided cluster creation mode for Sahara
Adding a guide for cluster creation in data processing. This is meant to weave the existing panels and forms together in a way that can be understood by all users. Partial-Implements: blueprint data-processing-rework-ui Change-Id: Icfd0c53e90fdb7a520a034ffcc20a3752976f54c
This commit is contained in:
parent
4dec85fccc
commit
22bdf25e23
@ -69,7 +69,8 @@ class DataProcessingPanels(horizon.PanelGroup):
|
|||||||
'data_processing.job_binaries',
|
'data_processing.job_binaries',
|
||||||
'data_processing.data_sources',
|
'data_processing.data_sources',
|
||||||
'data_processing.data_image_registry',
|
'data_processing.data_image_registry',
|
||||||
'data_processing.data_plugins',)
|
'data_processing.data_plugins',
|
||||||
|
'data_processing.wizard',)
|
||||||
|
|
||||||
|
|
||||||
class Project(horizon.Dashboard):
|
class Project(horizon.Dashboard):
|
||||||
|
@ -166,28 +166,39 @@ class ConfigureNodegroupsAction(workflows.Action):
|
|||||||
super(ConfigureNodegroupsAction, self). \
|
super(ConfigureNodegroupsAction, self). \
|
||||||
__init__(request, *args, **kwargs)
|
__init__(request, *args, **kwargs)
|
||||||
|
|
||||||
plugin, hadoop_version = whelpers.\
|
plugin = request.REQUEST.get("plugin_name")
|
||||||
get_plugin_and_hadoop_version(request)
|
version = request.REQUEST.get("hadoop_version")
|
||||||
|
if plugin and not version:
|
||||||
|
version_name = plugin + "_version"
|
||||||
|
version = request.REQUEST.get(version_name)
|
||||||
|
|
||||||
self.templates = saharaclient.nodegroup_template_find(
|
if not plugin or not version:
|
||||||
request, plugin_name=plugin, hadoop_version=hadoop_version)
|
self.templates = saharaclient.nodegroup_template_find(request)
|
||||||
|
else:
|
||||||
|
self.templates = saharaclient.nodegroup_template_find(
|
||||||
|
request, plugin_name=plugin, hadoop_version=version)
|
||||||
|
|
||||||
deletable = request.REQUEST.get("deletable", dict())
|
deletable = request.REQUEST.get("deletable", dict())
|
||||||
|
|
||||||
|
request_source = None
|
||||||
if 'forms_ids' in request.POST:
|
if 'forms_ids' in request.POST:
|
||||||
|
request_source = request.POST
|
||||||
|
elif 'forms_ids' in request.REQUEST:
|
||||||
|
request_source = request.REQUEST
|
||||||
|
if request_source:
|
||||||
self.groups = []
|
self.groups = []
|
||||||
for id in json.loads(request.POST['forms_ids']):
|
for id in json.loads(request_source['forms_ids']):
|
||||||
group_name = "group_name_" + str(id)
|
group_name = "group_name_" + str(id)
|
||||||
template_id = "template_id_" + str(id)
|
template_id = "template_id_" + str(id)
|
||||||
count = "count_" + str(id)
|
count = "count_" + str(id)
|
||||||
serialized = "serialized_" + str(id)
|
serialized = "serialized_" + str(id)
|
||||||
self.groups.append({"name": request.POST[group_name],
|
self.groups.append({"name": request_source[group_name],
|
||||||
"template_id": request.POST[template_id],
|
"template_id": request_source[template_id],
|
||||||
"count": request.POST[count],
|
"count": request_source[count],
|
||||||
"id": id,
|
"id": id,
|
||||||
"deletable": deletable.get(
|
"deletable": deletable.get(
|
||||||
request.POST[group_name], "true"),
|
request_source[group_name], "true"),
|
||||||
"serialized": request.POST[serialized]})
|
"serialized": request_source[serialized]})
|
||||||
|
|
||||||
whelpers.build_node_group_fields(self,
|
whelpers.build_node_group_fields(self,
|
||||||
group_name,
|
group_name,
|
||||||
@ -233,7 +244,6 @@ class ConfigureClusterTemplate(whelpers.ServiceParametersWorkflow,
|
|||||||
ConfigureClusterTemplate._cls_registry = set([])
|
ConfigureClusterTemplate._cls_registry = set([])
|
||||||
|
|
||||||
hlps = helpers.Helpers(request)
|
hlps = helpers.Helpers(request)
|
||||||
|
|
||||||
plugin, hadoop_version = whelpers.\
|
plugin, hadoop_version = whelpers.\
|
||||||
get_plugin_and_hadoop_version(request)
|
get_plugin_and_hadoop_version(request)
|
||||||
general_parameters = hlps.get_cluster_general_configs(
|
general_parameters = hlps.get_cluster_general_configs(
|
||||||
@ -300,6 +310,13 @@ class ConfigureClusterTemplate(whelpers.ServiceParametersWorkflow,
|
|||||||
node_groups,
|
node_groups,
|
||||||
context["anti_affinity_info"],
|
context["anti_affinity_info"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
hlps = helpers.Helpers(request)
|
||||||
|
if hlps.is_from_guide():
|
||||||
|
request.session["guide_cluster_template_name"] = (
|
||||||
|
context["general_cluster_template_name"])
|
||||||
|
self.success_url = (
|
||||||
|
"horizon:project:data_processing.wizard:cluster_guide")
|
||||||
return True
|
return True
|
||||||
except api_base.APIException as e:
|
except api_base.APIException as e:
|
||||||
self.error_description = str(e)
|
self.error_description = str(e)
|
||||||
|
@ -30,6 +30,12 @@ urlpatterns = patterns('',
|
|||||||
url(r'^configure-cluster$',
|
url(r'^configure-cluster$',
|
||||||
views.ConfigureClusterView.as_view(),
|
views.ConfigureClusterView.as_view(),
|
||||||
name='configure-cluster'),
|
name='configure-cluster'),
|
||||||
|
url(r'^configure-cluster'
|
||||||
|
'/(?P<plugin_name>[^/]+)'
|
||||||
|
'/(?P<hadoop_version>[^/]+)'
|
||||||
|
'/(?P<cluster_template_name>[^/]+)/$',
|
||||||
|
views.ConfigureClusterView.as_view(),
|
||||||
|
name='configure-cluster'),
|
||||||
url(r'^(?P<cluster_id>[^/]+)$',
|
url(r'^(?P<cluster_id>[^/]+)$',
|
||||||
views.ClusterDetailsView.as_view(),
|
views.ClusterDetailsView.as_view(),
|
||||||
name='details'),
|
name='details'),
|
||||||
|
@ -79,6 +79,11 @@ class ConfigureClusterView(workflows.WorkflowView):
|
|||||||
template_name = "project/data_processing.clusters/configure.html"
|
template_name = "project/data_processing.clusters/configure.html"
|
||||||
page_title = _("Configure Cluster")
|
page_title = _("Configure Cluster")
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
initial = super(ConfigureClusterView, self).get_initial()
|
||||||
|
initial.update(self.kwargs)
|
||||||
|
return initial
|
||||||
|
|
||||||
|
|
||||||
class ScaleClusterView(workflows.WorkflowView):
|
class ScaleClusterView(workflows.WorkflowView):
|
||||||
workflow_class = scale_flow.ScaleCluster
|
workflow_class = scale_flow.ScaleCluster
|
||||||
|
@ -22,7 +22,7 @@ from openstack_dashboard.dashboards.project.data_processing.utils \
|
|||||||
import openstack_dashboard.dashboards.project.data_processing.utils. \
|
import openstack_dashboard.dashboards.project.data_processing.utils. \
|
||||||
workflow_helpers as whelpers
|
workflow_helpers as whelpers
|
||||||
|
|
||||||
|
from django.core import urlresolvers
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from openstack_dashboard.api import sahara as saharaclient
|
from openstack_dashboard.api import sahara as saharaclient
|
||||||
@ -162,7 +162,19 @@ class GeneralConfigAction(workflows.Action):
|
|||||||
choices.append(("", _("No Templates Available")))
|
choices.append(("", _("No Templates Available")))
|
||||||
# cluster_template_id comes from cluster templates table, when
|
# cluster_template_id comes from cluster templates table, when
|
||||||
# Create Cluster from template is clicked there
|
# Create Cluster from template is clicked there
|
||||||
selected_template_id = request.REQUEST.get("cluster_template_id", None)
|
selected_template_name = None
|
||||||
|
resolver_match = urlresolvers.resolve(request.path)
|
||||||
|
if "cluster_template_name" in resolver_match.kwargs:
|
||||||
|
selected_template_name = (
|
||||||
|
resolver_match.kwargs["cluster_template_name"])
|
||||||
|
if selected_template_name:
|
||||||
|
for template in templates:
|
||||||
|
if template.name == selected_template_name:
|
||||||
|
selected_template_id = template.id
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
selected_template_id = (
|
||||||
|
request.REQUEST.get("cluster_template_id", None))
|
||||||
|
|
||||||
for template in templates:
|
for template in templates:
|
||||||
if template.id == selected_template_id:
|
if template.id == selected_template_id:
|
||||||
|
@ -31,6 +31,12 @@ urlpatterns = patterns('sahara.nodegroup_templates.views',
|
|||||||
url(r'^configure-nodegroup-template$',
|
url(r'^configure-nodegroup-template$',
|
||||||
views.ConfigureNodegroupTemplateView.as_view(),
|
views.ConfigureNodegroupTemplateView.as_view(),
|
||||||
name='configure-nodegroup-template'),
|
name='configure-nodegroup-template'),
|
||||||
|
url(r'^configure-nodegroup-template'
|
||||||
|
'/(?P<plugin_name>[^/]+)/'
|
||||||
|
'(?P<hadoop_version>[^/]+)/'
|
||||||
|
'(?P<guide_template_type>[^/]+)/$',
|
||||||
|
views.ConfigureNodegroupTemplateView.as_view(),
|
||||||
|
name='configure-nodegroup-template-defaults'),
|
||||||
url(r'^(?P<template_id>[^/]+)$',
|
url(r'^(?P<template_id>[^/]+)$',
|
||||||
views.NodegroupTemplateDetailsView.as_view(),
|
views.NodegroupTemplateDetailsView.as_view(),
|
||||||
name='details'),
|
name='details'),
|
||||||
|
@ -86,6 +86,11 @@ class ConfigureNodegroupTemplateView(workflows.WorkflowView):
|
|||||||
"project/data_processing.nodegroup_templates/configure.html")
|
"project/data_processing.nodegroup_templates/configure.html")
|
||||||
page_title = _("Create Node Group Template")
|
page_title = _("Create Node Group Template")
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
initial = super(ConfigureNodegroupTemplateView, self).get_initial()
|
||||||
|
initial.update(self.kwargs)
|
||||||
|
return initial
|
||||||
|
|
||||||
|
|
||||||
class CopyNodegroupTemplateView(workflows.WorkflowView):
|
class CopyNodegroupTemplateView(workflows.WorkflowView):
|
||||||
workflow_class = copy_flow.CopyNodegroupTemplate
|
workflow_class = copy_flow.CopyNodegroupTemplate
|
||||||
|
@ -13,7 +13,9 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from django.core import urlresolvers
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from saharaclient.api import base as api_base
|
from saharaclient.api import base as api_base
|
||||||
|
|
||||||
from horizon import exceptions
|
from horizon import exceptions
|
||||||
@ -147,6 +149,13 @@ class GeneralConfigAction(workflows.Action):
|
|||||||
for param in node_parameters:
|
for param in node_parameters:
|
||||||
self.fields[param.name] = workflow_helpers.build_control(param)
|
self.fields[param.name] = workflow_helpers.build_control(param)
|
||||||
|
|
||||||
|
resolver_match = urlresolvers.resolve(request.path)
|
||||||
|
if "guide_template_type" in resolver_match.kwargs:
|
||||||
|
self.fields["guide_template_type"] = forms.CharField(
|
||||||
|
required=False,
|
||||||
|
widget=forms.HiddenInput(),
|
||||||
|
initial=resolver_match.kwargs["guide_template_type"])
|
||||||
|
|
||||||
def populate_flavor_choices(self, request, context):
|
def populate_flavor_choices(self, request, context):
|
||||||
flavors = nova_utils.flavor_list(request)
|
flavors = nova_utils.flavor_list(request)
|
||||||
if flavors:
|
if flavors:
|
||||||
@ -314,7 +323,7 @@ class ConfigureNodegroupTemplate(workflow_helpers.ServiceParametersWorkflow,
|
|||||||
volumes_availability_zone = \
|
volumes_availability_zone = \
|
||||||
context["general_volumes_availability_zone"]
|
context["general_volumes_availability_zone"]
|
||||||
|
|
||||||
saharaclient.nodegroup_template_create(
|
ngt = saharaclient.nodegroup_template_create(
|
||||||
request,
|
request,
|
||||||
name=context["general_nodegroup_name"],
|
name=context["general_nodegroup_name"],
|
||||||
plugin_name=plugin,
|
plugin_name=plugin,
|
||||||
@ -330,6 +339,16 @@ class ConfigureNodegroupTemplate(workflow_helpers.ServiceParametersWorkflow,
|
|||||||
security_groups=context["security_groups"],
|
security_groups=context["security_groups"],
|
||||||
auto_security_group=context["security_autogroup"],
|
auto_security_group=context["security_autogroup"],
|
||||||
availability_zone=context["general_availability_zone"])
|
availability_zone=context["general_availability_zone"])
|
||||||
|
|
||||||
|
hlps = helpers.Helpers(request)
|
||||||
|
if hlps.is_from_guide():
|
||||||
|
guide_type = context["general_guide_template_type"]
|
||||||
|
request.session[guide_type + "_name"] = (
|
||||||
|
context["general_nodegroup_name"])
|
||||||
|
request.session[guide_type + "_id"] = ngt.id
|
||||||
|
self.success_url = (
|
||||||
|
"horizon:project:data_processing.wizard:cluster_guide")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
except api_base.APIException as e:
|
except api_base.APIException as e:
|
||||||
self.error_description = str(e)
|
self.error_description = str(e)
|
||||||
|
@ -84,3 +84,23 @@ class Helpers(object):
|
|||||||
'cluster', service)
|
'cluster', service)
|
||||||
|
|
||||||
return parameters
|
return parameters
|
||||||
|
|
||||||
|
def is_from_guide(self):
|
||||||
|
referer = self.request.environ.get("HTTP_REFERER")
|
||||||
|
if referer and "/wizard/" in referer:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def reset_guide(self):
|
||||||
|
try:
|
||||||
|
self.request.session.update(
|
||||||
|
{"plugin_name": None,
|
||||||
|
"plugin_version": None,
|
||||||
|
"master_name": None,
|
||||||
|
"master_id": None,
|
||||||
|
"worker_name": None,
|
||||||
|
"worker_id": None,
|
||||||
|
"guide_cluster_template_name": None})
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
@ -10,9 +10,9 @@
|
|||||||
# implied.
|
# implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from django.core import urlresolvers
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from horizon import forms
|
from horizon import forms
|
||||||
@ -163,8 +163,13 @@ def get_security_groups(request, security_group_ids):
|
|||||||
|
|
||||||
|
|
||||||
def get_plugin_and_hadoop_version(request):
|
def get_plugin_and_hadoop_version(request):
|
||||||
plugin_name = request.REQUEST["plugin_name"]
|
if request.REQUEST.get("plugin_name"):
|
||||||
hadoop_version = request.REQUEST["hadoop_version"]
|
plugin_name = request.REQUEST["plugin_name"]
|
||||||
|
hadoop_version = request.REQUEST["hadoop_version"]
|
||||||
|
else:
|
||||||
|
resolver_match = urlresolvers.resolve(request.path)
|
||||||
|
plugin_name = resolver_match.kwargs["plugin_name"]
|
||||||
|
hadoop_version = resolver_match.kwargs["hadoop_version"]
|
||||||
return (plugin_name, hadoop_version)
|
return (plugin_name, hadoop_version)
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,86 @@
|
|||||||
|
# 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 import template
|
||||||
|
from django.template import defaultfilters
|
||||||
|
from django.utils.encoding import force_text
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import exceptions
|
||||||
|
from horizon import forms
|
||||||
|
from horizon import messages
|
||||||
|
|
||||||
|
from openstack_dashboard.api import sahara as saharaclient
|
||||||
|
from openstack_dashboard.dashboards.project.data_processing.utils \
|
||||||
|
import helpers
|
||||||
|
|
||||||
|
|
||||||
|
class ChoosePluginForm(forms.SelfHandlingForm):
|
||||||
|
def __init__(self, request, *args, **kwargs):
|
||||||
|
super(ChoosePluginForm, self).__init__(request, *args, **kwargs)
|
||||||
|
self._generate_plugin_version_fields(request)
|
||||||
|
self.help_text_template = ("project/data_processing.wizard/"
|
||||||
|
"_plugin_select_help.html")
|
||||||
|
|
||||||
|
def handle(self, request, context):
|
||||||
|
try:
|
||||||
|
hlps = helpers.Helpers(request)
|
||||||
|
hlps.reset_guide()
|
||||||
|
plugin_name = context["plugin_name"]
|
||||||
|
request.session["plugin_name"] = plugin_name
|
||||||
|
request.session["plugin_version"] = (
|
||||||
|
context[plugin_name + "_version"])
|
||||||
|
messages.success(request, "Cluster type chosen")
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
exceptions.handle(request,
|
||||||
|
_("Unable to set cluster type"))
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _generate_plugin_version_fields(self, request):
|
||||||
|
sahara = saharaclient.client(request)
|
||||||
|
plugins = sahara.plugins.list()
|
||||||
|
plugin_choices = [(plugin.name, plugin.title) for plugin in plugins]
|
||||||
|
|
||||||
|
self.fields["plugin_name"] = forms.ChoiceField(
|
||||||
|
label=_("Plugin Name"),
|
||||||
|
choices=plugin_choices,
|
||||||
|
widget=forms.Select(attrs={"class": "switchable",
|
||||||
|
"data-slug": "plugin"}))
|
||||||
|
|
||||||
|
for plugin in plugins:
|
||||||
|
field_name = plugin.name + "_version"
|
||||||
|
choice_field = forms.ChoiceField(
|
||||||
|
label=_("Version"),
|
||||||
|
required=False,
|
||||||
|
choices=[(version, version) for version in plugin.versions],
|
||||||
|
widget=forms.Select(
|
||||||
|
attrs={"class": "switched",
|
||||||
|
"data-switch-on": "plugin",
|
||||||
|
"data-plugin-" + plugin.name: plugin.title})
|
||||||
|
)
|
||||||
|
self.fields[field_name] = choice_field
|
||||||
|
|
||||||
|
def get_help_text(self, extra_context=None):
|
||||||
|
text = ""
|
||||||
|
extra_context = extra_context or {}
|
||||||
|
if self.help_text_template:
|
||||||
|
tmpl = template.loader.get_template(self.help_text_template)
|
||||||
|
context = template.RequestContext(self.request, extra_context)
|
||||||
|
text += tmpl.render(context)
|
||||||
|
else:
|
||||||
|
text += defaultfilters.linebreaks(force_text(self.help_text))
|
||||||
|
return defaultfilters.safe(text)
|
||||||
|
|
||||||
|
class Meta(object):
|
||||||
|
name = _("Choose plugin type and version")
|
@ -0,0 +1,27 @@
|
|||||||
|
# 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 openstack_dashboard.dashboards.project import dashboard
|
||||||
|
|
||||||
|
|
||||||
|
class WizardPanel(horizon.Panel):
|
||||||
|
name = _("Guides")
|
||||||
|
slug = 'data_processing.wizard'
|
||||||
|
permissions = ('openstack.services.data-processing',)
|
||||||
|
|
||||||
|
|
||||||
|
dashboard.Project.register(WizardPanel)
|
@ -0,0 +1,31 @@
|
|||||||
|
{% extends "horizon/common/_modal_form.html" %}
|
||||||
|
|
||||||
|
{% load url from future %}
|
||||||
|
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block form_id %}create-job-binary{% endblock %}
|
||||||
|
{% block form_action %}
|
||||||
|
{% url 'horizon:project:data_processing.wizard:plugin_select' %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block form_attrs %}enctype="multipart/form-data"{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-header %}{% trans "Choose plugin and version" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-body %}
|
||||||
|
<div class="left">
|
||||||
|
<fieldset>
|
||||||
|
{% include "horizon/common/_form_fields.html" %}
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
<div class="form-help-block right">
|
||||||
|
{{ form.get_help_text }}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-footer %}
|
||||||
|
<input class="btn btn-primary pull-right" id="plugin_select_btn"
|
||||||
|
type="submit" value="{% trans "Select" %}"/>
|
||||||
|
<a href="{% url 'horizon:project:data_processing.wizard:cluster_guide' %}"
|
||||||
|
class="btn btn-default secondary cancel close">{% trans "Cancel" %}</a>
|
||||||
|
{% endblock %}
|
@ -0,0 +1,5 @@
|
|||||||
|
{% load i18n horizon %}
|
||||||
|
<p class="well">
|
||||||
|
{% blocktrans %}Select which plugin and version that you
|
||||||
|
want to use to create your cluster.{% endblocktrans %}
|
||||||
|
</p>
|
@ -0,0 +1,168 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load url from future %}
|
||||||
|
{% block title %}{% trans "Data Processing" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block page_header %}
|
||||||
|
{% include "horizon/common/_page_header.html" with title=_("Guided Cluster Creation") %}
|
||||||
|
{% endblock page_header %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
|
||||||
|
<div class="guide">
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
<div>
|
||||||
|
<div>{% blocktrans %}The first step is to determine which type of
|
||||||
|
cluster you want to run. You may have several choices
|
||||||
|
available depending on the configuration of your system.
|
||||||
|
Click on "choose plugin" to bring up the list of data
|
||||||
|
processing plugins. There you will be able to choose the
|
||||||
|
data processing plugin along with the version number.
|
||||||
|
Choosing this up front will allow the rest of the cluster
|
||||||
|
creation steps to focus only on options that are pertinent
|
||||||
|
to your desired cluster type.{% endblocktrans %}
|
||||||
|
</div>
|
||||||
|
<a class="btn btn-default btn-small btn-create btn-inline ajax-modal" href="
|
||||||
|
{% url 'horizon:project:data_processing.wizard:plugin_select' %}">{% trans "Choose plugin" %}</a>
|
||||||
|
<div>{% trans "Current choice:" %}
|
||||||
|
{% if request.session.plugin_name and request.session.plugin_version %}
|
||||||
|
<span class="text-success">
|
||||||
|
{% trans "Plugin:" %}
|
||||||
|
{{ request.session.plugin_name }}
|
||||||
|
{% trans "Version:" %}
|
||||||
|
{{ request.session.plugin_version }}
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-danger">
|
||||||
|
{% trans "No plugin chosen" %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<hr/>
|
||||||
|
<li>
|
||||||
|
<div>
|
||||||
|
<div>{% blocktrans %}Next, you need to define the different
|
||||||
|
types of machines in your cluster. This is done by
|
||||||
|
defining a Node Group Template for each type of
|
||||||
|
machine. A very common case is where you
|
||||||
|
need to have one or more machines running a "master"
|
||||||
|
set of processes while another set of machines need
|
||||||
|
to be running the "worker" processes. Here,
|
||||||
|
you will define the Node Group Template for your
|
||||||
|
"master" node(s).
|
||||||
|
{% endblocktrans %}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a class="btn btn-default btn-small btn-create btn-inline ajax-modal
|
||||||
|
{% if not request.session.plugin_name or not request.session.plugin_version %} disabled {% endif %}"
|
||||||
|
href="{% if request.session.plugin_name and request.session.plugin_version %}{% url 'horizon:project:data_processing.nodegroup_templates:configure-nodegroup-template-defaults' request.session.plugin_name request.session.plugin_version 'master' %}{% endif %}">
|
||||||
|
<span class="fa fa-plus"></span> {% trans "Create a Master Node Group Template" %}</a>
|
||||||
|
</div>
|
||||||
|
<div>{% trans "Current choice:" %}
|
||||||
|
{% if request.session.master_name %}
|
||||||
|
<span class="text-success">
|
||||||
|
{% trans "Master Node Group Template:" %}
|
||||||
|
{{ request.session.master_name }}
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-danger">
|
||||||
|
{% trans "No Master Node Group Template Created" %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<hr/>
|
||||||
|
<li>
|
||||||
|
<div>
|
||||||
|
<div>{% blocktrans %}Repeat the Node Group Template
|
||||||
|
creation process, but this time you are creating
|
||||||
|
your "worker" Node Group Template.{% endblocktrans %}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a class="btn btn-default btn-small btn-create btn-inline ajax-modal
|
||||||
|
{% if not request.session.master_name %} disabled{% endif %}"
|
||||||
|
href="{% if request.session.plugin_name and request.session.plugin_version %}{% url 'horizon:project:data_processing.nodegroup_templates:configure-nodegroup-template-defaults' request.session.plugin_name request.session.plugin_version 'worker' %}{% endif %}">
|
||||||
|
<span class="fa fa-plus"></span> {% trans "Create a Worker Node Group Template" %}</a>
|
||||||
|
</div>
|
||||||
|
<div>{% trans "Current choice:" %}
|
||||||
|
{% if request.session.worker_name %}
|
||||||
|
<span class="text-success">
|
||||||
|
{% trans "Worker Node Group Template:" %}
|
||||||
|
{{ request.session.worker_name }}
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-danger">
|
||||||
|
{% trans "No Worker Node Group Template Created" %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<hr/>
|
||||||
|
<li>
|
||||||
|
<div>
|
||||||
|
<div>{% blocktrans %}Now you need to set the layout of your
|
||||||
|
cluster. By
|
||||||
|
creating a Cluster Template, you will be choosing the
|
||||||
|
number of instances of each Node Group Template that
|
||||||
|
will appear in your cluster. Additionally,
|
||||||
|
you will have a chance to set any cluster-specific
|
||||||
|
configuration items in the additional tabs on the
|
||||||
|
create Cluster Template form.{% endblocktrans %}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a class="btn btn-default btn-small btn-create btn-inline ajax-modal
|
||||||
|
{% if not request.session.worker_name %} disabled{% endif %}"
|
||||||
|
href="{% url 'horizon:project:data_processing.cluster_templates:configure-cluster-template' %}?plugin_name={{ request.session.plugin_name }}&hadoop_version={{ request.session.plugin_version }}&forms_ids=[0,1]&count_0=1&group_name_0={{ request.session.master_name }}&template_id_0={{ request.session.master_id }}&count_1=1&group_name_1={{ request.session.worker_name }}&template_id_1={{ request.session.worker_id }}&serialized_0=null&serialized_1=null">
|
||||||
|
<span class="fa fa-plus"></span> {% trans "Create a Cluster Template" %}</a>
|
||||||
|
</div>
|
||||||
|
<div>{% trans "Current choice:" %}
|
||||||
|
{% if request.session.guide_cluster_template_name %}
|
||||||
|
<span class="text-success">
|
||||||
|
{% trans "Worker Node Group Template:" %}
|
||||||
|
{{ request.session.guide_cluster_template_name }}
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-danger">
|
||||||
|
{% trans "No Cluster Template Created" %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<hr/>
|
||||||
|
<li>
|
||||||
|
<div>
|
||||||
|
<div>{% blocktrans %}You are now ready to
|
||||||
|
launch your cluster. When you click on the link
|
||||||
|
below, you will need to give your cluster a name,
|
||||||
|
choose the Cluster Template to use and choose which
|
||||||
|
image to use to build your instances. After you
|
||||||
|
click on "Create", your instances will begin to
|
||||||
|
spawn. Your cluster should be operational in a few
|
||||||
|
minutes.{% endblocktrans %}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a class="btn btn-default btn-small btn-create btn-inline ajax-modal
|
||||||
|
{% if not request.session.guide_cluster_template_name %} disabled{% endif %}"
|
||||||
|
href="{% if request.session.guide_cluster_template_name %}{% url 'horizon:project:data_processing.clusters:configure-cluster' request.session.plugin_name request.session.plugin_version request.session.guide_cluster_template_name%}{% endif %}">
|
||||||
|
<span class="fa fa-plus"></span> {% trans "Launch a Cluster" %}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
<div>
|
||||||
|
<a id="reset_cluster_guide_btn"
|
||||||
|
class=" btn btn-default btn-small btn-create btn-inline btn-danger"
|
||||||
|
title="{% trans "Reset Cluster Guide" %}"
|
||||||
|
href="{% url 'horizon:project:data_processing.wizard:reset_cluster_guide' True %}">
|
||||||
|
{% trans "Reset Cluster Creation Guide" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -0,0 +1,11 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block title %}{% trans "Choose plugin and version" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block page_header %}
|
||||||
|
{% include "horizon/common/_page_header.html" with title=_("Choose plugin and version") %}
|
||||||
|
{% endblock page_header %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% include 'project/data_processing.wizard/_plugin_select.html' %}
|
||||||
|
{% endblock %}
|
@ -0,0 +1,38 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load url from future %}
|
||||||
|
{% block title %}{% trans "Data Processing" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block page_header %}
|
||||||
|
{% include "horizon/common/_page_header.html" with title=_("Data Processing Guides") %}
|
||||||
|
{% endblock page_header %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
|
||||||
|
<div class="guide">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<div>
|
||||||
|
<div>{% blocktrans %}
|
||||||
|
Each of the Data Processing frameworks require a cluster of machines
|
||||||
|
in order to do the work they are assigned. A cluster is
|
||||||
|
formed by creating a set of Node Group Templates, combining
|
||||||
|
those into a Cluster Template and then launching a Cluster.
|
||||||
|
You can do each of those steps manually, or you can follow
|
||||||
|
this guide to help take you through the steps of
|
||||||
|
Cluster creation.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a id="cluster_guide_btn"
|
||||||
|
class=" btn btn-default btn-small btn-create btn-inline"
|
||||||
|
title="{% trans "Cluster Guide" %}"
|
||||||
|
href="{% url 'horizon:project:data_processing.wizard:cluster_guide' %}">
|
||||||
|
{% trans "Cluster Creation Guide" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -0,0 +1,44 @@
|
|||||||
|
# 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 openstack_dashboard.test import helpers as test
|
||||||
|
|
||||||
|
|
||||||
|
INDEX_URL = reverse(
|
||||||
|
'horizon:project:data_processing.wizard:index')
|
||||||
|
CLUSTER_GUIDE_URL = reverse(
|
||||||
|
'horizon:project:data_processing.wizard:cluster_guide')
|
||||||
|
CLUSTER_GUIDE_RESET_URL = reverse(
|
||||||
|
'horizon:project:data_processing.wizard:reset_cluster_guide',
|
||||||
|
kwargs={"reset_cluster_guide": "true"})
|
||||||
|
|
||||||
|
|
||||||
|
class DataProcessingClusterGuideTests(test.TestCase):
|
||||||
|
def test_index(self):
|
||||||
|
res = self.client.get(INDEX_URL)
|
||||||
|
self.assertTemplateUsed(
|
||||||
|
res, 'project/data_processing.wizard/wizard.html')
|
||||||
|
self.assertContains(res, 'Data Processing Guides')
|
||||||
|
self.assertContains(res, 'Cluster Creation Guide')
|
||||||
|
|
||||||
|
def test_cluster_guide(self):
|
||||||
|
res = self.client.get(CLUSTER_GUIDE_URL)
|
||||||
|
self.assertTemplateUsed(
|
||||||
|
res, 'project/data_processing.wizard/cluster_guide.html')
|
||||||
|
self.assertContains(res, 'Guided Cluster Creation')
|
||||||
|
self.assertContains(res, 'Current choice')
|
||||||
|
|
||||||
|
def test_cluster_guide_reset(self):
|
||||||
|
res = self.client.get(CLUSTER_GUIDE_RESET_URL)
|
||||||
|
self.assertRedirectsNoFollow(res, CLUSTER_GUIDE_URL)
|
@ -0,0 +1,32 @@
|
|||||||
|
# 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.urls import patterns
|
||||||
|
from django.conf.urls import url
|
||||||
|
|
||||||
|
from openstack_dashboard.dashboards.project. \
|
||||||
|
data_processing.wizard import views
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
url(r'^$', views.WizardView.as_view(), name='index'),
|
||||||
|
url(r'^cluster_guide$',
|
||||||
|
views.ClusterGuideView.as_view(),
|
||||||
|
name='cluster_guide'),
|
||||||
|
url(r'^cluster_guide/(?P<reset_cluster_guide>[^/]+)/$',
|
||||||
|
views.ResetClusterGuideView.as_view(),
|
||||||
|
name='reset_cluster_guide'),
|
||||||
|
url(r'^plugin_select$',
|
||||||
|
views.PluginSelectView.as_view(),
|
||||||
|
name='plugin_select'),
|
||||||
|
)
|
@ -0,0 +1,64 @@
|
|||||||
|
# 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 logging
|
||||||
|
|
||||||
|
from django.core.urlresolvers import reverse_lazy
|
||||||
|
from django import http
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.views import generic
|
||||||
|
|
||||||
|
from horizon import exceptions
|
||||||
|
from horizon import forms
|
||||||
|
from horizon import views as horizon_views
|
||||||
|
|
||||||
|
from openstack_dashboard.dashboards.project.data_processing.utils \
|
||||||
|
import helpers
|
||||||
|
import openstack_dashboard.dashboards.project.data_processing.wizard \
|
||||||
|
.forms as wizforms
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class WizardView(horizon_views.APIView):
|
||||||
|
template_name = 'project/data_processing.wizard/wizard.html'
|
||||||
|
|
||||||
|
def get_data(self, request, context, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
context["test"] = "test data"
|
||||||
|
except Exception:
|
||||||
|
msg = _('Unable show guides.')
|
||||||
|
exceptions.handle(self.request, msg)
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class ClusterGuideView(horizon_views.APIView):
|
||||||
|
template_name = 'project/data_processing.wizard/cluster_guide.html'
|
||||||
|
|
||||||
|
|
||||||
|
class ResetClusterGuideView(generic.RedirectView):
|
||||||
|
pattern_name = 'horizon:project:data_processing.wizard:cluster_guide'
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
if kwargs["reset_cluster_guide"]:
|
||||||
|
hlps = helpers.Helpers(request)
|
||||||
|
hlps.reset_guide()
|
||||||
|
return http.HttpResponseRedirect(reverse_lazy(self.pattern_name))
|
||||||
|
|
||||||
|
|
||||||
|
class PluginSelectView(forms.ModalFormView):
|
||||||
|
form_class = wizforms.ChoosePluginForm
|
||||||
|
success_url = reverse_lazy(
|
||||||
|
'horizon:project:data_processing.wizard:cluster_guide')
|
||||||
|
classes = ("ajax-modal")
|
||||||
|
template_name = "project/data_processing.wizard/plugin_select.html"
|
Loading…
x
Reference in New Issue
Block a user