Merge "Adding cluster_template and cluster panels for Sahara"
This commit is contained in:
commit
49f8b944e3
@ -109,6 +109,10 @@ def nodegroup_template_get(request, ngt_id):
|
||||
return client(request).node_group_templates.get(ngt_id)
|
||||
|
||||
|
||||
def nodegroup_template_find(request, **kwargs):
|
||||
return client(request).node_group_templates.find(**kwargs)
|
||||
|
||||
|
||||
def nodegroup_template_delete(request, ngt_id):
|
||||
client(request).node_group_templates.delete(ngt_id)
|
||||
|
||||
|
@ -62,7 +62,9 @@ class DataProcessingPanels(horizon.PanelGroup):
|
||||
slug = "data_processing"
|
||||
panels = ('data_processing.data_plugins',
|
||||
'data_processing.data_image_registry',
|
||||
'data_processing.nodegroup_templates', )
|
||||
'data_processing.nodegroup_templates',
|
||||
'data_processing.cluster_templates',
|
||||
'data_processing.clusters', )
|
||||
|
||||
|
||||
class Project(horizon.Dashboard):
|
||||
|
@ -0,0 +1,59 @@
|
||||
# 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.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
|
||||
from openstack_dashboard.api import sahara as saharaclient
|
||||
from openstack_dashboard.dashboards.project.data_processing. \
|
||||
utils import workflow_helpers
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UploadFileForm(forms.SelfHandlingForm,
|
||||
workflow_helpers.PluginAndVersionMixin):
|
||||
template_name = forms.CharField(max_length=80,
|
||||
label=_("Cluster Template Name"))
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(UploadFileForm, self).__init__(request, *args, **kwargs)
|
||||
|
||||
sahara = saharaclient.client(request)
|
||||
self._generate_plugin_version_fields(sahara)
|
||||
|
||||
self.fields['template_file'] = forms.FileField(label=_("Template"),
|
||||
required=True)
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
# we can set a limit on file size, but should we?
|
||||
filecontent = self.files['template_file'].read()
|
||||
|
||||
plugin_name = data['plugin_name']
|
||||
hadoop_version = data.get(plugin_name + "_version")
|
||||
|
||||
saharaclient.plugin_convert_to_template(request,
|
||||
plugin_name,
|
||||
hadoop_version,
|
||||
data['template_name'],
|
||||
filecontent)
|
||||
return True
|
||||
except Exception:
|
||||
exceptions.handle(request,
|
||||
_("Unable to upload cluster template file"))
|
||||
return False
|
@ -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 ClusterTemplatesPanel(horizon.Panel):
|
||||
name = _("Cluster Templates")
|
||||
slug = 'data_processing.cluster_templates'
|
||||
permissions = ('openstack.services.data_processing',)
|
||||
|
||||
|
||||
dashboard.Project.register(ClusterTemplatesPanel)
|
@ -0,0 +1,118 @@
|
||||
# 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 import urlresolvers
|
||||
from django import template
|
||||
from django.utils import http
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import tables
|
||||
|
||||
from openstack_dashboard.api import sahara as saharaclient
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def render_node_groups(cluster_template):
|
||||
template_name = (
|
||||
'project/data_processing.cluster_templates/_nodegroups_list.html')
|
||||
context = {"node_groups": cluster_template.node_groups}
|
||||
return template.loader.render_to_string(template_name, context)
|
||||
|
||||
|
||||
class UploadFile(tables.LinkAction):
|
||||
name = 'upload_file'
|
||||
verbose_name = _("Upload Template")
|
||||
url = 'horizon:project:data_processing.cluster_templates:upload_file'
|
||||
classes = ("btn-launch", "ajax-modal")
|
||||
|
||||
|
||||
class CreateCluster(tables.LinkAction):
|
||||
name = "create cluster"
|
||||
verbose_name = _("Launch Cluster")
|
||||
url = "horizon:project:data_processing.clusters:configure-cluster"
|
||||
classes = ("btn-launch", "ajax-modal")
|
||||
|
||||
def get_link_url(self, datum):
|
||||
base_url = urlresolvers.reverse(self.url)
|
||||
|
||||
params = http.urlencode({"hadoop_version": datum.hadoop_version,
|
||||
"plugin_name": datum.plugin_name,
|
||||
"cluster_template_id": datum.id})
|
||||
return "?".join([base_url, params])
|
||||
|
||||
|
||||
class CopyTemplate(tables.LinkAction):
|
||||
name = "copy"
|
||||
verbose_name = _("Copy Template")
|
||||
url = "horizon:project:data_processing.cluster_templates:copy"
|
||||
classes = ("ajax-modal", )
|
||||
|
||||
|
||||
class DeleteTemplate(tables.BatchAction):
|
||||
name = "delete_cluster_template"
|
||||
verbose_name = _("Delete Template")
|
||||
classes = ("btn-terminate", "btn-danger")
|
||||
|
||||
action_present = _("Delete")
|
||||
action_past = _("Deleted")
|
||||
data_type_singular = _("Template")
|
||||
data_type_plural = _("Templates")
|
||||
|
||||
def action(self, request, template_id):
|
||||
saharaclient.cluster_template_delete(request, template_id)
|
||||
|
||||
|
||||
class CreateClusterTemplate(tables.LinkAction):
|
||||
name = "create"
|
||||
verbose_name = _("Create Template")
|
||||
url = ("horizon:project:data_processing.cluster_templates:"
|
||||
"create-cluster-template")
|
||||
classes = ("ajax-modal", "btn-create", "create-clustertemplate-btn")
|
||||
|
||||
|
||||
class ConfigureClusterTemplate(tables.LinkAction):
|
||||
name = "configure"
|
||||
verbose_name = _("Configure Cluster Template")
|
||||
url = ("horizon:project:data_processing.cluster_templates:"
|
||||
"configure-cluster-template")
|
||||
classes = ("ajax-modal", "btn-create", "configure-clustertemplate-btn")
|
||||
attrs = {"style": "display: none"}
|
||||
|
||||
|
||||
class ClusterTemplatesTable(tables.DataTable):
|
||||
name = tables.Column("name",
|
||||
verbose_name=_("Name"),
|
||||
link=("horizon:project:data_processing.cluster_templates:details"))
|
||||
plugin_name = tables.Column("plugin_name",
|
||||
verbose_name=_("Plugin"))
|
||||
hadoop_version = tables.Column("hadoop_version",
|
||||
verbose_name=_("Hadoop Version"))
|
||||
node_groups = tables.Column(render_node_groups,
|
||||
verbose_name=_("Node Groups"))
|
||||
description = tables.Column("description",
|
||||
verbose_name=_("Description"))
|
||||
|
||||
class Meta:
|
||||
name = "cluster_templates"
|
||||
verbose_name = _("Cluster Templates")
|
||||
table_actions = (UploadFile,
|
||||
CreateClusterTemplate,
|
||||
ConfigureClusterTemplate,
|
||||
DeleteTemplate,)
|
||||
|
||||
row_actions = (CreateCluster,
|
||||
CopyTemplate,
|
||||
DeleteTemplate,)
|
@ -0,0 +1,75 @@
|
||||
# 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.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tabs
|
||||
|
||||
from openstack_dashboard.api import nova
|
||||
from openstack_dashboard.api import sahara as saharaclient
|
||||
from openstack_dashboard.dashboards.project. \
|
||||
data_processing.utils import workflow_helpers as helpers
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GeneralTab(tabs.Tab):
|
||||
name = _("General Info")
|
||||
slug = "cluster_template_details_tab"
|
||||
template_name = (
|
||||
"project/data_processing.cluster_templates/_details.html")
|
||||
|
||||
def get_context_data(self, request):
|
||||
template_id = self.tab_group.kwargs['template_id']
|
||||
try:
|
||||
template = saharaclient.cluster_template_get(request, template_id)
|
||||
except Exception:
|
||||
template = {}
|
||||
exceptions.handle(request,
|
||||
_("Unable to fetch cluster template details."))
|
||||
return {"template": template}
|
||||
|
||||
|
||||
class NodeGroupsTab(tabs.Tab):
|
||||
name = _("Node Groups")
|
||||
slug = "cluster_template_nodegroups_tab"
|
||||
template_name = (
|
||||
"project/data_processing.cluster_templates/_nodegroups_details.html")
|
||||
|
||||
def get_context_data(self, request):
|
||||
template_id = self.tab_group.kwargs['template_id']
|
||||
try:
|
||||
template = saharaclient.cluster_template_get(request, template_id)
|
||||
for ng in template.node_groups:
|
||||
if not ng["flavor_id"]:
|
||||
continue
|
||||
ng["flavor_name"] = (
|
||||
nova.flavor_get(request, ng["flavor_id"]).name)
|
||||
ng["node_group_template"] = helpers.safe_call(
|
||||
saharaclient.nodegroup_template_get,
|
||||
request, ng.get("node_group_template_id", None))
|
||||
except Exception:
|
||||
template = {}
|
||||
exceptions.handle(request,
|
||||
_("Unable to fetch node group details."))
|
||||
return {"template": template}
|
||||
|
||||
|
||||
class ClusterTemplateDetailsTabs(tabs.TabGroup):
|
||||
slug = "cluster_template_details"
|
||||
tabs = (GeneralTab, NodeGroupsTab, )
|
||||
sticky = True
|
@ -0,0 +1,22 @@
|
||||
{% load i18n horizon %}
|
||||
<div class="well">
|
||||
<p>
|
||||
{% blocktrans %}This Cluster Template will be created for:{% endblocktrans %}
|
||||
<br >
|
||||
<b>{% blocktrans %}Plugin{% endblocktrans %}</b>: {{ plugin_name }}
|
||||
<br />
|
||||
<b>{% blocktrans %}Hadoop version{% endblocktrans %}</b>: {{ hadoop_version }}
|
||||
<br />
|
||||
</p>
|
||||
<p>
|
||||
{% blocktrans %}The Cluster Template object should specify Node Group Templates that will be used to build a Hadoop Cluster.
|
||||
You can add Node Groups using Node Group Templates on a "Node Groups" tab.{% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
{% blocktrans %}You may set <b>cluster</b> scoped Hadoop configurations on corresponding tabs.{% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
{% blocktrans %}The Cluster Template object may specify a list of processes in anti-affinity group.
|
||||
That means these processes may not be launched more than once on a single host.{% endblocktrans %}
|
||||
</p>
|
||||
</div>
|
@ -0,0 +1,4 @@
|
||||
{% load i18n horizon %}
|
||||
<p class="well">
|
||||
{% blocktrans %}Select a plugin and Hadoop version for a new Cluster template.{% endblocktrans %}
|
||||
</p>
|
@ -0,0 +1,54 @@
|
||||
{% load i18n sizeformat %}
|
||||
{% load url from future %}
|
||||
<h3>{% trans "Template Overview" %}</h3>
|
||||
<div class="status row-fluid detail">
|
||||
<dl>
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ template.name }}</dd>
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ template.id }}</dd>
|
||||
<dt>{% trans "Description" %}</dt>
|
||||
<dd>{{ template.description|default:"None" }}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>{% trans "Plugin" %}</dt>
|
||||
<dd><a href="{% url 'horizon:project:data_processing.data_plugins:details' template.plugin_name %}">{{ template.plugin_name }}</a></dd>
|
||||
<dt>{% trans "Hadoop Version" %}</dt>
|
||||
<dd>{{ template.hadoop_version }}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>{% trans "Anti-affinity enabled for" %}</dt>
|
||||
{% if template.anti_affinity %}
|
||||
<dd>
|
||||
<ul>
|
||||
{% for process in template.anti_affinity %}
|
||||
<li>{{ process }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</dd>
|
||||
{% else %}
|
||||
<h6>{% trans "no processes" %}</h6>
|
||||
{% endif %}
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>{% trans "Node Configurations" %}</dt>
|
||||
{% if template.cluster_configs %}
|
||||
<dd>
|
||||
{% for service, service_conf in template.cluster_configs.items %}
|
||||
<h4>{{ service }}</h4>
|
||||
{% if service_conf %}
|
||||
<ul>
|
||||
{% for conf_name, conf_value in service_conf.items %}
|
||||
<li>{{ conf_name }}: {{ conf_value }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<h6>{% trans "No configurations" }%</h6>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</dd>
|
||||
{% else %}
|
||||
<dd>{% trans "Cluster configurations are not specified" %}</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
@ -0,0 +1,55 @@
|
||||
{% load i18n sizeformat %}
|
||||
{% load url from future %}
|
||||
<h3>{% trans "Node Groups" %}</h3>
|
||||
<div class="status row-fluid detail">
|
||||
{% for node_group in template.node_groups %}
|
||||
<dl class="well">
|
||||
<h4>{% trans "Node Group" %}: {{ node_group.name }}</h4>
|
||||
<dt>{% trans "Nodes Count" %}</dt>
|
||||
<dd>{{ node_group.count }}</dd>
|
||||
|
||||
<dt>{% trans "Flavor" %}</dt>
|
||||
<dd>{{ node_group.flavor_id|default:"Flavor is not specified" }}</dd>
|
||||
|
||||
<dt>{% trans "Template" %}</dt>
|
||||
{% if node_group.node_group_template_id %}
|
||||
<dd><a href="{% url 'horizon:project:data_processing.nodegroup_templates:details' node_group.node_group_template_id %}">{{ node_group.node_group_template.name }} </a></dd>
|
||||
{% else %}
|
||||
<dd>{% trans "Template not specified" %}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt>{% trans "Node Processes" %}</dt>
|
||||
{% if node_group.node_processes %}
|
||||
<dd>
|
||||
<ul>
|
||||
{% for process in node_group.node_processes %}
|
||||
<li>{{ process }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</dd>
|
||||
{% else %}
|
||||
<dd>{% trans "Node processes are not specified" %}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt>{% trans "Node Configurations" %}</dt>
|
||||
{% if node_group.node_configs %}
|
||||
<dd>
|
||||
{% for service, service_conf in node_group.node_configs.items %}
|
||||
<h6>{{ service }}</h6>
|
||||
{% if service_conf %}
|
||||
<ul>
|
||||
{% for conf_name, conf_value in service_conf.items %}
|
||||
<li>{{ conf_name }}: {{ conf_value }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<h6>{% trans "No configurations" %}</h6>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</dd>
|
||||
{% else %}
|
||||
<dd>{% trans "Node configurations are not specified" %}</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
{% endfor %}
|
||||
</div>
|
@ -0,0 +1,5 @@
|
||||
<ul>
|
||||
{% for group in node_groups %}
|
||||
<li>{{ group.name }}: {{ group.count }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
@ -0,0 +1,24 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
|
||||
{% load url from future %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}upload_file{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:project:data_processing.cluster_templates:upload_file' %}{% endblock %}
|
||||
{% block form_attrs %}enctype="multipart/form-data"{% endblock %}
|
||||
|
||||
{% block modal-header %}{% trans "Upload Template" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" id="upload_file_btn" type="submit" value="{% trans "Upload" %}"/>
|
||||
<a href="{% url 'horizon:project:data_processing.cluster_templates:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -0,0 +1,155 @@
|
||||
<script>
|
||||
var template = '<tr id_attr="$id"><td>' +
|
||||
'<div class="input control-group" style="padding-right:15px;">' +
|
||||
'<input id="template_id_$id" value="$template_id" type="hidden" name="template_id_$id">' +
|
||||
'<input id="group_name_$id" value="$group_name" type="text" name="group_name_$id" class="input-medium">' +
|
||||
'</div>' +
|
||||
'</td>' +
|
||||
'<td>' +
|
||||
'<div class="input control-group" style="padding-right:15px;"><input disabled value="$template_name" class="input-medium" /></div>' +
|
||||
'</td>' +
|
||||
'<td>' +
|
||||
'<div class="input control-group btn-group input-append" style="float:left;padding-right:5px;">' +
|
||||
'<input id="count_$id" class="count-field" value="$node_count" type="text" max="4" maxlength="4" name="count_$id" size="4" style="width:50px">' +
|
||||
'<div class="btn dec-btn" data-count-id="count_$id"><i class="icon-minus"></i></div>' +
|
||||
'<div class="btn inc-btn" data-count-id="count_$id"><i class="icon-plus"></i></div>' +
|
||||
'</div>' +
|
||||
'<div class="input" style="float:left">' +
|
||||
|
||||
'</div>' +
|
||||
'</td>' +
|
||||
'<td>' +
|
||||
'<div class="input control-group" style="float:left;padding-right:5px;">' +
|
||||
'<input type="button" class="btn btn-danger" id="delete_btn_$id" data-toggle="dropdown" onclick="delete_node_group(this)" value="Remove" style="margin-bottom: 10px"/>' +
|
||||
'</div>' +
|
||||
'</td>' +
|
||||
|
||||
'</tr>';
|
||||
|
||||
function mark_element_as_wrong(id){
|
||||
$("#"+id).parent("div").addClass("error");
|
||||
}
|
||||
|
||||
function get_next_id() {
|
||||
var max = -1;
|
||||
$("#node-templates tbody tr").each(function () {
|
||||
max = Math.max(max, parseInt($(this).attr("id_attr")));
|
||||
});
|
||||
return max + 1;
|
||||
}
|
||||
|
||||
function set_nodes_ids() {
|
||||
var ids = [];
|
||||
$("#node-templates tbody tr").each(function () {
|
||||
ids.push(parseInt($(this).attr("id_attr")));
|
||||
});
|
||||
$("#forms_ids").val(JSON.stringify(ids));
|
||||
}
|
||||
|
||||
function add_node(node_count, group_name, template_id, id, deletable) {
|
||||
var template_name = $("select option[value='" + template_id + "']").html();
|
||||
var tmp = template.
|
||||
replace(/\$id/g, id).
|
||||
replace(/\$group_name/g, group_name).
|
||||
replace(/\$template_id/g, template_id).
|
||||
replace(/\$node_count/g, node_count).
|
||||
replace(/\$template_name/g, template_name);
|
||||
$("#node-templates tbody").append(tmp);
|
||||
if (!deletable) {
|
||||
$("#delete_btn_" + id).remove();
|
||||
$("#group_name_" + id).prop('readonly', true);
|
||||
}
|
||||
$("#node-templates").show();
|
||||
set_nodes_ids();
|
||||
}
|
||||
|
||||
function add_node_group_template(node_count) {
|
||||
if ($("select option:selected").html() == "Select") {
|
||||
return;
|
||||
}
|
||||
var template_id = $("#template_id option:selected").val();
|
||||
var template_name = $("#template_id option:selected").html();
|
||||
add_node(node_count, template_name, template_id, get_next_id(), true);
|
||||
$(".count-field").change();
|
||||
}
|
||||
function delete_node_group(el) {
|
||||
var tr = $(el).parents("tr")[0];
|
||||
tr.parentNode.removeChild(tr);
|
||||
var id = get_next_id();
|
||||
if (id == 0) {
|
||||
$("#node-templates").hide();
|
||||
}
|
||||
set_nodes_ids();
|
||||
}
|
||||
$("#template_id").change(function () {
|
||||
if ($("select option:selected").html() == "Select") {
|
||||
$("#add_group_button").addClass("disabled");
|
||||
} else {
|
||||
$("#add_group_button").removeClass("disabled");
|
||||
}
|
||||
});
|
||||
$("#node-templates").hide();
|
||||
</script>
|
||||
<input type="hidden" value="[]" name="forms_ids" id="forms_ids">
|
||||
<div class="input">
|
||||
<select id="template_id" name="template">
|
||||
<option>Select</option>
|
||||
{% for template in form.templates %}
|
||||
<option value="{{ template.id }}">{{ template.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<a id="add_group_button" class="btn disabled btn-inline" onclick="add_node_group_template(1);">+</a>
|
||||
</div>
|
||||
<br/>
|
||||
<div id="node-templates">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td><label style="padding-bottom:5px">Group Name</label></td>
|
||||
<td><label style="padding-bottom:5px">Template </label></td>
|
||||
<td><label style="padding-bottom:5px">Count</label></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
{% for group in form.groups %}
|
||||
add_node("{{ group.count }}", "{{ group.name }}", "{{ group.template_id }}", "{{ group.id }}", {{ group.deletable }});
|
||||
{% endfor %}
|
||||
{% for field_id in form.errors_fields %}
|
||||
mark_element_as_wrong("{{ field_id }}");
|
||||
{% endfor %}
|
||||
|
||||
var handlers_registred;
|
||||
var lower_limit = 1;
|
||||
$(function() {
|
||||
if (!handlers_registred) {
|
||||
handlers_registred = true;
|
||||
$(".inc-btn").live("click", function(e) {
|
||||
var id = $(this).attr("data-count-id");
|
||||
$("#" + id).val(parseInt($("#" + id).val()) + 1);
|
||||
$(".count-field").change();
|
||||
});
|
||||
$(".dec-btn").live("click", function(e) {
|
||||
var id = $(this).attr("data-count-id");
|
||||
var val = parseInt($("#" + id).val());
|
||||
if (val > lower_limit) {
|
||||
$("#" + id).val(val - 1);
|
||||
}
|
||||
$(".count-field").change();
|
||||
});
|
||||
}
|
||||
|
||||
$(".count-field").live("change", function() {
|
||||
var val = $(this).val();
|
||||
if (val > lower_limit) {
|
||||
$(this).parent("div").find(".dec-btn").removeClass("disabled");
|
||||
} else {
|
||||
$(this).parent("div").find(".dec-btn").addClass("disabled");
|
||||
}
|
||||
}).change();
|
||||
});
|
||||
</script>
|
@ -0,0 +1,66 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Data Processing" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Data Processing - Cluster Templates") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
<div class="cluster_templates">
|
||||
{{ cluster_templates_table.render }}
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
addHorizonLoadEvent(function () {
|
||||
horizon.modals.addModalInitFunction(function (modal) {
|
||||
if ($(modal).find(".nav-tabs").find("li").size() == 1) {
|
||||
// hide tab bar for plugin/version modal wizard
|
||||
$('div#modal_wrapper ul.nav-tabs').hide();
|
||||
}
|
||||
|
||||
$(".hidden_nodegroups_field").after("<input type='button' id='add_nodegroup' value='Add Node group'/><br/>");
|
||||
$("#add_nodegroup").click(function() {
|
||||
$(".hidden_nodegroups_field").val("create_nodegroup");
|
||||
$(".hidden_configure_field").val("create_nodegroup");
|
||||
var form = $(".hidden_nodegroups_field").closest("form");
|
||||
form.submit();
|
||||
});
|
||||
|
||||
|
||||
$(".hidden_nodegroups_field").val("");
|
||||
$(".hidden_configure_field").val("");
|
||||
|
||||
if ($(modal).find(".hidden_create_field").length > 0) {
|
||||
var form = $(".hidden_create_field").closest("form");
|
||||
var successful = false;
|
||||
form.submit(function (e) {
|
||||
var oldHref = $(".configure-clustertemplate-btn")[0].href;
|
||||
var plugin = $("#id_plugin_name option:selected").val();
|
||||
var version = $("#id_" + plugin + "_version option:selected").val();
|
||||
form.find(".close").click();
|
||||
$(".configure-clustertemplate-btn")[0].href = oldHref +
|
||||
"?plugin_name=" + encodeURIComponent(plugin) +
|
||||
"&hadoop_version=" + encodeURIComponent(version);
|
||||
$(".configure-clustertemplate-btn").click();
|
||||
$(".configure-clustertemplate-btn")[0].href = oldHref;
|
||||
return false;
|
||||
});
|
||||
$(".plugin_version_choice").closest(".control-group").hide();
|
||||
}
|
||||
|
||||
//display version for selected plugin
|
||||
$(document).on('change', '.plugin_name_choice', switch_versions);
|
||||
function switch_versions() {
|
||||
$(".plugin_version_choice").closest(".control-group").hide();
|
||||
var plugin = $(this);
|
||||
$("." + plugin.val() + "_version_choice").closest(".control-group").show();
|
||||
}
|
||||
$(".plugin_name_choice").change();
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Create Cluster Template" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Create Cluster Template") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'horizon/common/_workflow.html' %}
|
||||
{% endblock %}
|
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Create Cluster Template" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Create Cluster Template") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'horizon/common/_workflow.html' %}
|
||||
{% endblock %}
|
@ -0,0 +1,15 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Cluster Template Details" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Cluster Template Details") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
{{ tab_group.render }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Upload Template" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Upload Template") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'project/data_processing.cluster_templates/_upload_file.html' %}
|
||||
{% endblock %}
|
@ -0,0 +1,54 @@
|
||||
# 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 django import http
|
||||
|
||||
from mox import IsA # noqa
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.test import helpers as test
|
||||
|
||||
|
||||
INDEX_URL = reverse('horizon:project:data_processing.cluster_templates:index')
|
||||
DETAILS_URL = reverse(
|
||||
'horizon:project:data_processing.cluster_templates:details', args=['id'])
|
||||
|
||||
|
||||
class DataProcessingClusterTemplateTests(test.TestCase):
|
||||
@test.create_stubs({api.sahara: ('cluster_template_list',)})
|
||||
def test_index(self):
|
||||
api.sahara.cluster_template_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.cluster_templates.list())
|
||||
self.mox.ReplayAll()
|
||||
res = self.client.get(INDEX_URL)
|
||||
self.assertTemplateUsed(res,
|
||||
'project/data_processing.cluster_templates/'
|
||||
'cluster_templates.html')
|
||||
self.assertContains(res, 'Cluster Templates')
|
||||
self.assertContains(res, 'Name')
|
||||
|
||||
@test.create_stubs({api.sahara: ('cluster_template_get',),
|
||||
api.nova: ('flavor_get',)})
|
||||
def test_details(self):
|
||||
flavor = self.flavors.first()
|
||||
ct = self.cluster_templates.first()
|
||||
api.nova.flavor_get(IsA(http.HttpRequest), flavor.id) \
|
||||
.MultipleTimes().AndReturn(flavor)
|
||||
api.sahara.cluster_template_get(IsA(http.HttpRequest),
|
||||
IsA(unicode)) \
|
||||
.MultipleTimes().AndReturn(ct)
|
||||
self.mox.ReplayAll()
|
||||
res = self.client.get(DETAILS_URL)
|
||||
self.assertTemplateUsed(res,
|
||||
'project/data_processing.cluster_templates/'
|
||||
'details.html')
|
@ -0,0 +1,40 @@
|
||||
# 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 # noqa
|
||||
from django.conf.urls import url # noqa
|
||||
|
||||
import openstack_dashboard.dashboards.project. \
|
||||
data_processing.cluster_templates.views as views
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', views.ClusterTemplatesView.as_view(),
|
||||
name='index'),
|
||||
url(r'^$', views.ClusterTemplatesView.as_view(),
|
||||
name='cluster-templates'),
|
||||
url(r'^upload_file$',
|
||||
views.UploadFileView.as_view(),
|
||||
name='upload_file'),
|
||||
url(r'^create-cluster-template$',
|
||||
views.CreateClusterTemplateView.as_view(),
|
||||
name='create-cluster-template'),
|
||||
url(r'^configure-cluster-template$',
|
||||
views.ConfigureClusterTemplateView.as_view(),
|
||||
name='configure-cluster-template'),
|
||||
url(r'^(?P<template_id>[^/]+)$',
|
||||
views.ClusterTemplateDetailsView.as_view(),
|
||||
name='details'),
|
||||
url(r'^(?P<template_id>[^/]+)/copy$',
|
||||
views.CopyClusterTemplateView.as_view(),
|
||||
name='copy'))
|
@ -0,0 +1,113 @@
|
||||
# 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.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import tables
|
||||
from horizon import tabs
|
||||
from horizon import workflows
|
||||
|
||||
from openstack_dashboard.api import sahara as saharaclient
|
||||
from openstack_dashboard.dashboards.project.data_processing. \
|
||||
cluster_templates import forms as cluster_forms
|
||||
import openstack_dashboard.dashboards.project.data_processing. \
|
||||
cluster_templates.tables as ct_tables
|
||||
import openstack_dashboard.dashboards.project.data_processing. \
|
||||
cluster_templates.tabs as _tabs
|
||||
import openstack_dashboard.dashboards.project.data_processing. \
|
||||
cluster_templates.workflows.copy as copy_flow
|
||||
import openstack_dashboard.dashboards.project.data_processing. \
|
||||
cluster_templates.workflows.create as create_flow
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ClusterTemplatesView(tables.DataTableView):
|
||||
table_class = ct_tables.ClusterTemplatesTable
|
||||
template_name = (
|
||||
'project/data_processing.cluster_templates/cluster_templates.html')
|
||||
|
||||
def get_data(self):
|
||||
cluster_templates = saharaclient.cluster_template_list(self.request)
|
||||
return cluster_templates
|
||||
|
||||
|
||||
class ClusterTemplateDetailsView(tabs.TabView):
|
||||
tab_group_class = _tabs.ClusterTemplateDetailsTabs
|
||||
template_name = 'project/data_processing.cluster_templates/details.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ClusterTemplateDetailsView, self)\
|
||||
.get_context_data(**kwargs)
|
||||
return context
|
||||
|
||||
def get_data(self):
|
||||
pass
|
||||
|
||||
|
||||
class UploadFileView(forms.ModalFormView):
|
||||
form_class = cluster_forms.UploadFileForm
|
||||
template_name = (
|
||||
'project/data_processing.cluster_templates/upload_file.html')
|
||||
success_url = reverse_lazy(
|
||||
'horizon:project:data_processing.cluster_templates:index')
|
||||
|
||||
|
||||
class CreateClusterTemplateView(workflows.WorkflowView):
|
||||
workflow_class = create_flow.CreateClusterTemplate
|
||||
success_url = ("horizon:project:data_processing.cluster_templates"
|
||||
":create-cluster-template")
|
||||
classes = ("ajax-modal")
|
||||
template_name = "project/data_processing.cluster_templates/create.html"
|
||||
|
||||
|
||||
class ConfigureClusterTemplateView(workflows.WorkflowView):
|
||||
workflow_class = create_flow.ConfigureClusterTemplate
|
||||
success_url = "horizon:project:data_processing.cluster_templates"
|
||||
template_name = "project/data_processing.cluster_templates/configure.html"
|
||||
|
||||
|
||||
class CopyClusterTemplateView(workflows.WorkflowView):
|
||||
workflow_class = copy_flow.CopyClusterTemplate
|
||||
success_url = "horizon:project:data_processing.cluster_templates"
|
||||
template_name = "project/data_processing.cluster_templates/configure.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CopyClusterTemplateView, self)\
|
||||
.get_context_data(**kwargs)
|
||||
|
||||
context["template_id"] = kwargs["template_id"]
|
||||
return context
|
||||
|
||||
def get_object(self, *args, **kwargs):
|
||||
if not hasattr(self, "_object"):
|
||||
template_id = self.kwargs['template_id']
|
||||
try:
|
||||
template = saharaclient.cluster_template_get(self.request,
|
||||
template_id)
|
||||
except Exception:
|
||||
template = {}
|
||||
exceptions.handle(self.request,
|
||||
_("Unable to fetch cluster template."))
|
||||
self._object = template
|
||||
return self._object
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(CopyClusterTemplateView, self).get_initial()
|
||||
initial['template_id'] = self.kwargs['template_id']
|
||||
return initial
|
@ -0,0 +1,80 @@
|
||||
# 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.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
|
||||
from openstack_dashboard.api import sahara as saharaclient
|
||||
import openstack_dashboard.dashboards.project.data_processing. \
|
||||
cluster_templates.workflows.create as create_flow
|
||||
import openstack_dashboard.dashboards.project.data_processing.utils. \
|
||||
workflow_helpers as wf_helpers
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CopyClusterTemplate(create_flow.ConfigureClusterTemplate):
|
||||
success_message = _("Cluster Template copy %s created")
|
||||
|
||||
def __init__(self, request, context_seed, entry_point, *args, **kwargs):
|
||||
template_id = context_seed["template_id"]
|
||||
try:
|
||||
template = saharaclient.cluster_template_get(request, template_id)
|
||||
self._set_configs_to_copy(template.cluster_configs)
|
||||
|
||||
request.GET = request.GET.copy()
|
||||
request.GET.update({"plugin_name": template.plugin_name,
|
||||
"hadoop_version": template.hadoop_version,
|
||||
"aa_groups": template.anti_affinity})
|
||||
|
||||
super(CopyClusterTemplate, self).__init__(request, context_seed,
|
||||
entry_point, *args,
|
||||
**kwargs)
|
||||
#init Node Groups
|
||||
for step in self.steps:
|
||||
if isinstance(step, create_flow.ConfigureNodegroups):
|
||||
ng_action = step.action
|
||||
template_ngs = template.node_groups
|
||||
|
||||
if 'forms_ids' not in request.POST:
|
||||
ng_action.groups = []
|
||||
for id in range(0, len(template_ngs), 1):
|
||||
group_name = "group_name_" + str(id)
|
||||
template_id = "template_id_" + str(id)
|
||||
count = "count_" + str(id)
|
||||
templ_ng = template_ngs[id]
|
||||
ng_action.groups.append(
|
||||
{"name": templ_ng["name"],
|
||||
"template_id":
|
||||
templ_ng["node_group_template_id"],
|
||||
"count": templ_ng["count"],
|
||||
"id": id,
|
||||
"deletable": "true"})
|
||||
|
||||
wf_helpers.build_node_group_fields(ng_action,
|
||||
group_name,
|
||||
template_id,
|
||||
count)
|
||||
|
||||
elif isinstance(step, create_flow.GeneralConfig):
|
||||
fields = step.action.fields
|
||||
fields["cluster_template_name"].initial = \
|
||||
template.name + "-copy"
|
||||
|
||||
fields["description"].initial = template.description
|
||||
except Exception:
|
||||
exceptions.handle(request,
|
||||
_("Unable to fetch template to copy."))
|
@ -0,0 +1,306 @@
|
||||
# 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.utils.translation import ugettext_lazy as _
|
||||
import json
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import workflows
|
||||
|
||||
from openstack_dashboard.api import sahara as saharaclient
|
||||
from openstack_dashboard.dashboards.project.data_processing. \
|
||||
utils import helpers as helpers
|
||||
from openstack_dashboard.dashboards.project.data_processing. \
|
||||
utils import anti_affinity as aa
|
||||
import openstack_dashboard.dashboards.project.data_processing. \
|
||||
utils.workflow_helpers as whelpers
|
||||
|
||||
from saharaclient.api import base as api_base
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SelectPluginAction(workflows.Action):
|
||||
hidden_create_field = forms.CharField(
|
||||
required=False,
|
||||
widget=forms.HiddenInput(attrs={"class": "hidden_create_field"}))
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(SelectPluginAction, self).__init__(request, *args, **kwargs)
|
||||
|
||||
try:
|
||||
plugins = saharaclient.plugin_list(request)
|
||||
except Exception:
|
||||
plugins = []
|
||||
exceptions.handle(request,
|
||||
_("Unable to fetch plugin list."))
|
||||
plugin_choices = [(plugin.name, plugin.title) for plugin in plugins]
|
||||
|
||||
self.fields["plugin_name"] = forms.ChoiceField(
|
||||
label=_("Plugin name"),
|
||||
required=True,
|
||||
choices=plugin_choices,
|
||||
widget=forms.Select(attrs={"class": "plugin_name_choice"}))
|
||||
|
||||
for plugin in plugins:
|
||||
field_name = plugin.name + "_version"
|
||||
choice_field = forms.ChoiceField(
|
||||
label=_("Hadoop version"),
|
||||
required=True,
|
||||
choices=[(version, version) for version in plugin.versions],
|
||||
widget=forms.Select(
|
||||
attrs={"class": "plugin_version_choice "
|
||||
+ field_name + "_choice"})
|
||||
)
|
||||
self.fields[field_name] = choice_field
|
||||
|
||||
class Meta:
|
||||
name = _("Select plugin and hadoop version for cluster template")
|
||||
help_text_template = ("project/data_processing.cluster_templates/"
|
||||
"_create_general_help.html")
|
||||
|
||||
|
||||
class SelectPlugin(workflows.Step):
|
||||
action_class = SelectPluginAction
|
||||
|
||||
|
||||
class CreateClusterTemplate(workflows.Workflow):
|
||||
slug = "create_cluster_template"
|
||||
name = _("Create Cluster Template")
|
||||
finalize_button_name = _("Create")
|
||||
success_message = _("Created")
|
||||
failure_message = _("Could not create")
|
||||
success_url = "horizon:project:data_processing.cluster_templates:index"
|
||||
default_steps = (SelectPlugin,)
|
||||
|
||||
|
||||
class GeneralConfigAction(workflows.Action):
|
||||
hidden_configure_field = forms.CharField(
|
||||
required=False,
|
||||
widget=forms.HiddenInput(attrs={"class": "hidden_configure_field"}))
|
||||
|
||||
hidden_to_delete_field = forms.CharField(
|
||||
required=False,
|
||||
widget=forms.HiddenInput(attrs={"class": "hidden_to_delete_field"}))
|
||||
|
||||
cluster_template_name = forms.CharField(label=_("Template Name"),
|
||||
required=True)
|
||||
|
||||
description = forms.CharField(label=_("Description"),
|
||||
required=False,
|
||||
widget=forms.Textarea)
|
||||
|
||||
anti_affinity = aa.anti_affinity_field()
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(GeneralConfigAction, self).__init__(request, *args, **kwargs)
|
||||
plugin, hadoop_version = whelpers.\
|
||||
get_plugin_and_hadoop_version(request)
|
||||
|
||||
self.fields["plugin_name"] = forms.CharField(
|
||||
widget=forms.HiddenInput(),
|
||||
initial=plugin
|
||||
)
|
||||
self.fields["hadoop_version"] = forms.CharField(
|
||||
widget=forms.HiddenInput(),
|
||||
initial=hadoop_version
|
||||
)
|
||||
|
||||
populate_anti_affinity_choices = aa.populate_anti_affinity_choices
|
||||
|
||||
def get_help_text(self):
|
||||
extra = dict()
|
||||
plugin, hadoop_version = whelpers\
|
||||
.get_plugin_and_hadoop_version(self.request)
|
||||
|
||||
extra["plugin_name"] = plugin
|
||||
extra["hadoop_version"] = hadoop_version
|
||||
return super(GeneralConfigAction, self).get_help_text(extra)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(GeneralConfigAction, self).clean()
|
||||
if cleaned_data.get("hidden_configure_field", None) \
|
||||
== "create_nodegroup":
|
||||
self._errors = dict()
|
||||
return cleaned_data
|
||||
|
||||
class Meta:
|
||||
name = _("Details")
|
||||
help_text_template = ("project/data_processing.cluster_templates/"
|
||||
"_configure_general_help.html")
|
||||
|
||||
|
||||
class GeneralConfig(workflows.Step):
|
||||
action_class = GeneralConfigAction
|
||||
contributes = ("hidden_configure_field", )
|
||||
|
||||
def contribute(self, data, context):
|
||||
for k, v in data.items():
|
||||
context["general_" + k] = v
|
||||
|
||||
post = self.workflow.request.POST
|
||||
context['anti_affinity_info'] = post.getlist("anti_affinity")
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class ConfigureNodegroupsAction(workflows.Action):
|
||||
hidden_nodegroups_field = forms.CharField(
|
||||
required=False,
|
||||
widget=forms.HiddenInput(attrs={"class": "hidden_nodegroups_field"}))
|
||||
forms_ids = forms.CharField(
|
||||
required=False,
|
||||
widget=forms.HiddenInput())
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(ConfigureNodegroupsAction, self). \
|
||||
__init__(request, *args, **kwargs)
|
||||
|
||||
plugin, hadoop_version = whelpers.\
|
||||
get_plugin_and_hadoop_version(request)
|
||||
|
||||
self.templates = saharaclient.nodegroup_template_find(request,
|
||||
plugin_name=plugin,
|
||||
hadoop_version=hadoop_version)
|
||||
|
||||
deletable = request.REQUEST.get("deletable", dict())
|
||||
|
||||
if 'forms_ids' in request.POST:
|
||||
self.groups = []
|
||||
for id in json.loads(request.POST['forms_ids']):
|
||||
group_name = "group_name_" + str(id)
|
||||
template_id = "template_id_" + str(id)
|
||||
count = "count_" + str(id)
|
||||
self.groups.append({"name": request.POST[group_name],
|
||||
"template_id": request.POST[template_id],
|
||||
"count": request.POST[count],
|
||||
"id": id,
|
||||
"deletable": deletable.get(
|
||||
request.POST[group_name], "true")})
|
||||
|
||||
whelpers.build_node_group_fields(self,
|
||||
group_name,
|
||||
template_id,
|
||||
count)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(ConfigureNodegroupsAction, self).clean()
|
||||
if cleaned_data.get("hidden_nodegroups_field", None) \
|
||||
== "create_nodegroup":
|
||||
self._errors = dict()
|
||||
return cleaned_data
|
||||
|
||||
class Meta:
|
||||
name = _("Node Groups")
|
||||
|
||||
|
||||
class ConfigureNodegroups(workflows.Step):
|
||||
action_class = ConfigureNodegroupsAction
|
||||
contributes = ("hidden_nodegroups_field", )
|
||||
template_name = ("project/data_processing.cluster_templates/"
|
||||
"cluster_node_groups_template.html")
|
||||
|
||||
def contribute(self, data, context):
|
||||
for k, v in data.items():
|
||||
context["ng_" + k] = v
|
||||
return context
|
||||
|
||||
|
||||
class ConfigureClusterTemplate(whelpers.ServiceParametersWorkflow,
|
||||
whelpers.StatusFormatMixin):
|
||||
slug = "configure_cluster_template"
|
||||
name = _("Create Cluster Template")
|
||||
finalize_button_name = _("Create")
|
||||
success_message = _("Created Cluster Template %s")
|
||||
name_property = "general_cluster_template_name"
|
||||
success_url = "horizon:project:data_processing.cluster_templates:index"
|
||||
default_steps = (GeneralConfig,
|
||||
ConfigureNodegroups)
|
||||
|
||||
def __init__(self, request, context_seed, entry_point, *args, **kwargs):
|
||||
ConfigureClusterTemplate._cls_registry = set([])
|
||||
|
||||
sahara = saharaclient.client(request)
|
||||
hlps = helpers.Helpers(sahara)
|
||||
|
||||
plugin, hadoop_version = whelpers.\
|
||||
get_plugin_and_hadoop_version(request)
|
||||
|
||||
general_parameters = hlps.get_cluster_general_configs(
|
||||
plugin,
|
||||
hadoop_version)
|
||||
service_parameters = hlps.get_targeted_cluster_configs(
|
||||
plugin,
|
||||
hadoop_version)
|
||||
|
||||
self._populate_tabs(general_parameters, service_parameters)
|
||||
|
||||
super(ConfigureClusterTemplate, self).__init__(request,
|
||||
context_seed,
|
||||
entry_point,
|
||||
*args, **kwargs)
|
||||
|
||||
def is_valid(self):
|
||||
steps_valid = True
|
||||
for step in self.steps:
|
||||
if not step.action.is_valid():
|
||||
steps_valid = False
|
||||
step.has_errors = True
|
||||
errors_fields = list(step.action.errors.keys())
|
||||
step.action.errors_fields = errors_fields
|
||||
if not steps_valid:
|
||||
return steps_valid
|
||||
return self.validate(self.context)
|
||||
|
||||
def handle(self, request, context):
|
||||
try:
|
||||
node_groups = []
|
||||
configs_dict = whelpers.parse_configs_from_context(context,
|
||||
self.defaults)
|
||||
|
||||
ids = json.loads(context['ng_forms_ids'])
|
||||
for id in ids:
|
||||
name = context['ng_group_name_' + str(id)]
|
||||
template_id = context['ng_template_id_' + str(id)]
|
||||
count = context['ng_count_' + str(id)]
|
||||
|
||||
ng = {"name": name,
|
||||
"node_group_template_id": template_id,
|
||||
"count": count}
|
||||
node_groups.append(ng)
|
||||
|
||||
plugin, hadoop_version = whelpers.\
|
||||
get_plugin_and_hadoop_version(request)
|
||||
|
||||
#TODO(nkonovalov): Fix client to support default_image_id
|
||||
saharaclient.cluster_template_create(
|
||||
request,
|
||||
context["general_cluster_template_name"],
|
||||
plugin,
|
||||
hadoop_version,
|
||||
context["general_description"],
|
||||
configs_dict,
|
||||
node_groups,
|
||||
context["anti_affinity_info"])
|
||||
return True
|
||||
except api_base.APIException as e:
|
||||
self.error_description = str(e)
|
||||
return False
|
||||
except Exception:
|
||||
exceptions.handle(request,
|
||||
_("Cluster template creation failed"))
|
||||
return False
|
@ -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 ClustersPanel(horizon.Panel):
|
||||
name = _("Clusters")
|
||||
slug = 'data_processing.clusters'
|
||||
permissions = ('openstack.services.data_processing',)
|
||||
|
||||
|
||||
dashboard.Project.register(ClustersPanel)
|
@ -0,0 +1,100 @@
|
||||
# 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.utils.translation import ugettext_lazy as _
|
||||
from horizon import tables
|
||||
|
||||
from openstack_dashboard.api import sahara as saharaclient
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreateCluster(tables.LinkAction):
|
||||
name = "create"
|
||||
verbose_name = _("Launch Cluster")
|
||||
url = "horizon:project:data_processing.clusters:create-cluster"
|
||||
classes = ("btn-launch", "ajax-modal")
|
||||
|
||||
|
||||
class ScaleCluster(tables.LinkAction):
|
||||
name = "scale"
|
||||
verbose_name = _("Scale Cluster")
|
||||
url = "horizon:project:data_processing.clusters:scale"
|
||||
classes = ("ajax-modal", "btn-edit")
|
||||
|
||||
def allowed(self, request, cluster=None):
|
||||
return cluster.status == "Active"
|
||||
|
||||
|
||||
class DeleteCluster(tables.BatchAction):
|
||||
name = "delete"
|
||||
action_present = _("Delete")
|
||||
action_past = _("Deleted")
|
||||
data_type_singular = _("Cluster")
|
||||
data_type_plural = _("Clusters")
|
||||
classes = ('btn-danger', 'btn-terminate')
|
||||
|
||||
def action(self, request, obj_id):
|
||||
saharaclient.cluster_delete(request, obj_id)
|
||||
|
||||
|
||||
class UpdateRow(tables.Row):
|
||||
ajax = True
|
||||
|
||||
def get_data(self, request, instance_id):
|
||||
instance = saharaclient.cluster_get(request, instance_id)
|
||||
return instance
|
||||
|
||||
|
||||
def get_instances_count(cluster):
|
||||
return sum([len(ng["instances"])
|
||||
for ng in cluster.node_groups])
|
||||
|
||||
|
||||
class ConfigureCluster(tables.LinkAction):
|
||||
name = "configure"
|
||||
verbose_name = _("Configure Cluster")
|
||||
url = "horizon:project:data_processing.clusters:configure-cluster"
|
||||
classes = ("ajax-modal", "btn-create", "configure-cluster-btn")
|
||||
attrs = {"style": "display: none"}
|
||||
|
||||
|
||||
class ClustersTable(tables.DataTable):
|
||||
STATUS_CHOICES = (
|
||||
("active", True),
|
||||
("error", False)
|
||||
)
|
||||
|
||||
name = tables.Column("name",
|
||||
verbose_name=_("Name"),
|
||||
link=("horizon:project:data_processing.clusters:details"))
|
||||
status = tables.Column("status",
|
||||
verbose_name=_("Status"),
|
||||
status=True,
|
||||
status_choices=STATUS_CHOICES)
|
||||
instances_count = tables.Column(get_instances_count,
|
||||
verbose_name=_("Instances Count"))
|
||||
|
||||
class Meta:
|
||||
name = "clusters"
|
||||
verbose_name = _("Clusters")
|
||||
row_class = UpdateRow
|
||||
status_columns = ["status"]
|
||||
table_actions = (CreateCluster,
|
||||
ConfigureCluster,
|
||||
DeleteCluster)
|
||||
row_actions = (ScaleCluster,
|
||||
DeleteCluster,)
|
@ -0,0 +1,170 @@
|
||||
# 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.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
from horizon import tabs
|
||||
|
||||
from openstack_dashboard.dashboards.project. \
|
||||
data_processing.utils import workflow_helpers as helpers
|
||||
|
||||
from openstack_dashboard.api import glance
|
||||
from openstack_dashboard.api import neutron
|
||||
from openstack_dashboard.api import nova
|
||||
|
||||
|
||||
from openstack_dashboard.api import sahara as saharaclient
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GeneralTab(tabs.Tab):
|
||||
name = _("General Info")
|
||||
slug = "cluster_details_tab"
|
||||
template_name = "project/data_processing.clusters/_details.html"
|
||||
|
||||
def get_context_data(self, request):
|
||||
cluster_id = self.tab_group.kwargs['cluster_id']
|
||||
cluster_info = {}
|
||||
try:
|
||||
sahara = saharaclient.client(request)
|
||||
cluster = sahara.clusters.get(cluster_id)
|
||||
|
||||
for info_key, info_val in cluster.info.items():
|
||||
for key, val in info_val.items():
|
||||
if str(val).startswith(('http://', 'https://')):
|
||||
cluster.info[info_key][key] = build_link(val)
|
||||
|
||||
base_image = glance.image_get(request,
|
||||
cluster.default_image_id)
|
||||
|
||||
if getattr(cluster, 'cluster_template_id', None):
|
||||
cluster_template = helpers.safe_call(
|
||||
sahara.cluster_templates.get,
|
||||
cluster.cluster_template_id)
|
||||
else:
|
||||
cluster_template = None
|
||||
|
||||
if getattr(cluster, 'neutron_management_network', None):
|
||||
net_id = cluster.neutron_management_network
|
||||
network = neutron.network_get(request, net_id)
|
||||
network.set_id_as_name_if_empty()
|
||||
net_name = network.name
|
||||
else:
|
||||
net_name = None
|
||||
|
||||
cluster_info.update({"cluster": cluster,
|
||||
"base_image": base_image,
|
||||
"cluster_template": cluster_template,
|
||||
"network": net_name})
|
||||
except Exception as e:
|
||||
LOG.error("Unable to fetch cluster details: %s" % str(e))
|
||||
|
||||
return cluster_info
|
||||
|
||||
|
||||
def build_link(url):
|
||||
return "<a href='" + url + "' target=\"_blank\">" + url + "</a>"
|
||||
|
||||
|
||||
class NodeGroupsTab(tabs.Tab):
|
||||
name = _("Node Groups")
|
||||
slug = "cluster_nodegroups_tab"
|
||||
template_name = (
|
||||
"project/data_processing.clusters/_nodegroups_details.html")
|
||||
|
||||
def get_context_data(self, request):
|
||||
cluster_id = self.tab_group.kwargs['cluster_id']
|
||||
try:
|
||||
sahara = saharaclient.client(request)
|
||||
cluster = sahara.clusters.get(cluster_id)
|
||||
for ng in cluster.node_groups:
|
||||
if not ng["flavor_id"]:
|
||||
continue
|
||||
ng["flavor_name"] = (
|
||||
nova.flavor_get(request, ng["flavor_id"]).name)
|
||||
ng["node_group_template"] = helpers.safe_call(
|
||||
sahara.node_group_templates.get,
|
||||
ng.get("node_group_template_id", None))
|
||||
except Exception:
|
||||
cluster = {}
|
||||
exceptions.handle(request,
|
||||
_("Unable to get node group details."))
|
||||
|
||||
return {"cluster": cluster}
|
||||
|
||||
|
||||
class Instance(object):
|
||||
def __init__(self, name=None, id=None, internal_ip=None,
|
||||
management_ip=None):
|
||||
self.name = name
|
||||
self.id = id
|
||||
self.internal_ip = internal_ip
|
||||
self.management_ip = management_ip
|
||||
|
||||
|
||||
class InstancesTable(tables.DataTable):
|
||||
name = tables.Column("name",
|
||||
link=("horizon:project:instances:detail"),
|
||||
verbose_name=_("Name"))
|
||||
|
||||
internal_ip = tables.Column("internal_ip",
|
||||
verbose_name=_("Internal IP"))
|
||||
|
||||
management_ip = tables.Column("management_ip",
|
||||
verbose_name=_("Management IP"))
|
||||
|
||||
class Meta:
|
||||
name = "cluster_instances"
|
||||
#just ignoring the name
|
||||
verbose_name = _(" ")
|
||||
|
||||
|
||||
class InstancesTab(tabs.TableTab):
|
||||
name = _("Instances")
|
||||
slug = "cluster_instances_tab"
|
||||
template_name = "project/data_processing.clusters/_instances_details.html"
|
||||
table_classes = (InstancesTable, )
|
||||
|
||||
def get_cluster_instances_data(self):
|
||||
cluster_id = self.tab_group.kwargs['cluster_id']
|
||||
|
||||
try:
|
||||
sahara = saharaclient.client(self.request)
|
||||
cluster = sahara.clusters.get(cluster_id)
|
||||
|
||||
instances = []
|
||||
for ng in cluster.node_groups:
|
||||
for instance in ng["instances"]:
|
||||
instances.append(Instance(
|
||||
name=instance["instance_name"],
|
||||
id=instance["instance_id"],
|
||||
internal_ip=instance.get("internal_ip",
|
||||
"Not assigned"),
|
||||
management_ip=instance.get("management_ip",
|
||||
"Not assigned")))
|
||||
except Exception:
|
||||
instances = []
|
||||
exceptions.handle(self.request,
|
||||
_("Unable to fetch instance details."))
|
||||
return instances
|
||||
|
||||
|
||||
class ClusterDetailsTabs(tabs.TabGroup):
|
||||
slug = "cluster_details"
|
||||
tabs = (GeneralTab, NodeGroupsTab, InstancesTab, )
|
||||
sticky = True
|
@ -0,0 +1,20 @@
|
||||
{% load i18n horizon %}
|
||||
<div class="well">
|
||||
<p>
|
||||
{% blocktrans %}This Cluster will be started with:{% endblocktrans %}
|
||||
<br >
|
||||
<b>{% blocktrans %}Plugin{% endblocktrans %}</b>: {{ plugin_name }}
|
||||
<br />
|
||||
<b>{% blocktrans %}Hadoop version{% endblocktrans %}</b>: {{ hadoop_version }}
|
||||
<br />
|
||||
</p>
|
||||
<p>
|
||||
{% blocktrans %}Cluster can be launched using existing Cluster Templates.{% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
{% blocktrans %}The Cluster object should specify OpenStack Image to boot instances for Hadoop Cluster.{% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
{% blocktrans %}User has to choose a keypair to have access to clusters instances.{% endblocktrans %}
|
||||
</p>
|
||||
</div>
|
@ -0,0 +1,23 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
|
||||
{% load url from future %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}create_cluster_form{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:project:data_processing.clusters:create' %}{% endblock %}
|
||||
|
||||
{% block modal-header %}{% trans "Launch Cluster" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" id="create_cluster_btn" type="submit" value="{% trans " Done" %}"/>
|
||||
<a href="{% url 'horizon:project:data_processing.clusters:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -0,0 +1,4 @@
|
||||
{% load i18n %}
|
||||
<p class="well">
|
||||
{% trans "Select a plugin and Hadoop version for a new Cluster." %}
|
||||
</p>
|
@ -0,0 +1,89 @@
|
||||
{% load i18n sizeformat %}
|
||||
{% load url from future %}
|
||||
<h3>{% trans "Cluster Overview" %}</h3>
|
||||
<div class="status row-fluid detail">
|
||||
<dl>
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ cluster.name }}</dd>
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ cluster.id }}</dd>
|
||||
<dt>{% trans "Description" %}</dt>
|
||||
<dd>{{ cluster.description|default:"None" }}</dd>
|
||||
<dt>{% trans "Status" %}</dt>
|
||||
<dd>{{ cluster.status }}</dd>
|
||||
</dl>
|
||||
|
||||
{% if cluster.error_description %}
|
||||
<h4>{% trans "Error Details" %}</h4>
|
||||
<p class="well">
|
||||
{{ cluster.error_description }}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<dl>
|
||||
<dt>{% trans "Plugin" %}</dt>
|
||||
<dd><a href="{% url 'horizon:project:data_processing.data_plugins:details' cluster.plugin_name %}">{{ cluster.plugin_name }}</a></dd>
|
||||
<dt>{% trans "Hadoop Version" %}</dt>
|
||||
<dd>{{ cluster.hadoop_version }}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>{% trans "Template" %}</dt>
|
||||
{% if cluster_template %}
|
||||
<dd><a href="{% url 'horizon:project:data_processing.cluster_templates:details' cluster_template.id %}">{{ cluster_template.name }} </a></dd>
|
||||
{% else %}
|
||||
<dd>{% trans "Template not specified" %}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "Base Image" %}</dt>
|
||||
<dd>{{ base_image.name }}</dd>
|
||||
{% if network %}
|
||||
<dt>{% trans "Neutron Management Network" %}</dt>
|
||||
<dd>{{ network }}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "Keypair" %}</dt>
|
||||
<dd>{{ cluster.user_keypair_id }}</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>{% trans "Anti-affinity enabled for" %}</dt>
|
||||
{% if cluster.anti_affinity %}
|
||||
<dd>
|
||||
<ul>
|
||||
{% for process in cluster.anti_affinity %}
|
||||
<li>{{ process }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</dd>
|
||||
{% else %}
|
||||
<h6>{% trans "no processes" %}</h6>
|
||||
{% endif %}
|
||||
</dl>
|
||||
<dl>
|
||||
<dt>{% trans "Node Configurations" %}</dt>
|
||||
{% if cluster.cluster_configs %}
|
||||
<dd>
|
||||
{% for service, service_conf in cluster.cluster_configs.items %}
|
||||
<h4>{{ service }}</h4>
|
||||
{% if service_conf %}
|
||||
<ul>
|
||||
{% for conf_name, conf_value in service_conf.items %}
|
||||
<li>{{ conf_name }}: {{ conf_value }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<h6>{% trans "No configurations" %}</h6>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</dd>
|
||||
{% else %}
|
||||
<dd>{% trans "Cluster configurations are not specified" %}</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
|
||||
<dl>
|
||||
{% for info_key, info_val in cluster.info.items %}
|
||||
<dt>{{ info_key }}</dt>
|
||||
{% for key, val in info_val.items %}
|
||||
<dd>{{ key }}: {% autoescape off %}{{ val }}{% endautoescape %}</dd>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</dl>
|
||||
</div>
|
@ -0,0 +1,5 @@
|
||||
{% load i18n sizeformat %}
|
||||
<h3>{% trans "Cluster Instances" %}</h3>
|
||||
<div class="status row-fluid detail">
|
||||
{{ cluster_instances_table.render }}
|
||||
</div>
|
@ -0,0 +1,63 @@
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
{% load url from future %}
|
||||
|
||||
|
||||
<h3>{% trans "Node Groups" %}</h3>
|
||||
<div class="status row-fluid detail">
|
||||
{% for node_group in cluster.node_groups %}
|
||||
<dl class="well">
|
||||
<h4>{% trans "Name" %}: {{ node_group.name }}</h4>
|
||||
<dt>{% trans "Number of Nodes" %}</dt>
|
||||
<dd>{{ node_group.count }}</dd>
|
||||
|
||||
<dt>{% trans "Flavor" %}</dt>
|
||||
<dd>{{ node_group.flavor_name|default:"Flavor is not specified" }}</dd>
|
||||
|
||||
{% if node_group.floating_ip_pool %}
|
||||
<dt>{% trans "Floating IP Pool" %}</dt>
|
||||
<dd>{{ node_group.floating_ip_pool }}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt>{% trans "Template" %}</dt>
|
||||
{% if node_group.node_group_template_id %}
|
||||
<dd><a href="{% url 'horizon:project:data_processing.nodegroup_templates:details' node_group.node_group_template_id %}">{{ node_group.node_group_template.name }} </a></dd>
|
||||
{% else %}
|
||||
<dd>{% trans "Template not specified" %}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt>{% trans "Node Processes" %}</dt>
|
||||
{% if node_group.node_processes %}
|
||||
<dd>
|
||||
<ul>
|
||||
{% for process in node_group.node_processes %}
|
||||
<li>{{ process }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</dd>
|
||||
{% else %}
|
||||
<dd>{% trans "Node processes are not specified" %}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt>{% trans "Node Configurations" %}</dt>
|
||||
{% if node_group.node_configs %}
|
||||
<dd>
|
||||
{% for service, service_conf in node_group.node_configs.items %}
|
||||
<h4>{{ service }}</h4>
|
||||
{% if service_conf %}
|
||||
<ul>
|
||||
{% for conf_name, conf_value in service_conf.items %}
|
||||
<li>{{ conf_name }}: {{ conf_value }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<h6>{% trans "No configurations" %}</h6>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</dd>
|
||||
{% else %}
|
||||
<dd>{% trans "Node configurations are not specified" %}</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
{% endfor %}
|
||||
</div>
|
@ -0,0 +1,61 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Data Processing" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Data Processing - Clusters") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
<div class="cluster_templates">
|
||||
{{ clusters_table.render }}
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
addHorizonLoadEvent(function () {
|
||||
|
||||
horizon.modals.addModalInitFunction(function (modal) {
|
||||
if ($(modal).find(".nav-tabs").find("li").size() == 1) {
|
||||
// hide tab bar for plugin/version modal wizard
|
||||
$('div#modal_wrapper ul.nav-tabs').hide();
|
||||
}
|
||||
|
||||
$(".hidden_nodegroups_field").val("");
|
||||
$(".hidden_configure_field").val("");
|
||||
|
||||
lower_limit = 0;
|
||||
$(".count-field").change();
|
||||
|
||||
if ($(modal).find(".hidden_create_field").length > 0) {
|
||||
var form = $(".hidden_create_field").closest("form");
|
||||
var successful = false;
|
||||
form.submit(function (e) {
|
||||
var oldHref = $(".configure-cluster-btn")[0].href;
|
||||
var plugin = $("#id_plugin_name option:selected").val();
|
||||
var version = $("#id_" + plugin + "_version option:selected").val();
|
||||
form.find(".close").click();
|
||||
$(".configure-cluster-btn")[0].href = oldHref +
|
||||
"?plugin_name=" + encodeURIComponent(plugin) +
|
||||
"&hadoop_version=" + encodeURIComponent(version);
|
||||
$(".configure-cluster-btn").click();
|
||||
$(".configure-cluster-btn")[0].href = oldHref;
|
||||
return false;
|
||||
});
|
||||
$(".plugin_version_choice").closest(".control-group").hide();
|
||||
}
|
||||
|
||||
//display version for selected plugin
|
||||
$(document).on('change', '.plugin_name_choice', switch_versions);
|
||||
function switch_versions() {
|
||||
$(".plugin_version_choice").closest(".control-group").hide();
|
||||
var plugin = $(this);
|
||||
$("." + plugin.val() + "_version_choice").closest(".control-group").show();
|
||||
}
|
||||
$(".plugin_name_choice").change();
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Launch Cluster" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Launch Cluster") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'horizon/common/_workflow.html' %}
|
||||
{% endblock %}
|
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Launch Cluster" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Launch Cluster") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'horizon/common/_workflow.html' %}
|
||||
{% endblock %}
|
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Launch Cluster" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Launch Cluster") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'templates/data_processing.clusters/_create_cluster.html' %}
|
||||
{% endblock %}
|
@ -0,0 +1,15 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Cluster Details" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Cluster Details") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
{{ tab_group.render }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Scale Cluster" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Scale Cluster") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'horizon/common/_workflow.html' %}
|
||||
{% endblock %}
|
@ -0,0 +1,37 @@
|
||||
# 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 django import http
|
||||
|
||||
from mox import IsA # noqa
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.test import helpers as test
|
||||
|
||||
|
||||
INDEX_URL = reverse('horizon:project:data_processing.clusters:index')
|
||||
DETAILS_URL = reverse(
|
||||
'horizon:project:data_processing.clusters:details', args=['id'])
|
||||
|
||||
|
||||
class DataProcessingClusterTests(test.TestCase):
|
||||
@test.create_stubs({api.sahara: ('cluster_list',)})
|
||||
def test_index(self):
|
||||
api.sahara.cluster_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.clusters.list())
|
||||
self.mox.ReplayAll()
|
||||
res = self.client.get(INDEX_URL)
|
||||
self.assertTemplateUsed(res,
|
||||
'project/data_processing.clusters/clusters.html')
|
||||
self.assertContains(res, 'Clusters')
|
||||
self.assertContains(res, 'Name')
|
@ -0,0 +1,38 @@
|
||||
# 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 # noqa
|
||||
from django.conf.urls import url # noqa
|
||||
|
||||
import openstack_dashboard.dashboards.project.data_processing. \
|
||||
clusters.views as views
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', views.ClustersView.as_view(),
|
||||
name='index'),
|
||||
url(r'^$', views.ClustersView.as_view(),
|
||||
name='clusters'),
|
||||
url(r'^create-cluster$',
|
||||
views.CreateClusterView.as_view(),
|
||||
name='create-cluster'),
|
||||
url(r'^configure-cluster$',
|
||||
views.ConfigureClusterView.as_view(),
|
||||
name='configure-cluster'),
|
||||
url(r'^(?P<cluster_id>[^/]+)$',
|
||||
views.ClusterDetailsView.as_view(),
|
||||
name='details'),
|
||||
url(r'^(?P<cluster_id>[^/]+)/scale$',
|
||||
views.ScaleClusterView.as_view(),
|
||||
name='scale'))
|
@ -0,0 +1,104 @@
|
||||
# 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.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
from horizon import tabs
|
||||
from horizon import workflows
|
||||
|
||||
from openstack_dashboard.api import sahara as saharaclient
|
||||
|
||||
import openstack_dashboard.dashboards.project.data_processing.clusters. \
|
||||
tables as c_tables
|
||||
import openstack_dashboard.dashboards.project.data_processing.clusters.tabs \
|
||||
as _tabs
|
||||
import openstack_dashboard.dashboards.project.data_processing.clusters. \
|
||||
workflows.create as create_flow
|
||||
import openstack_dashboard.dashboards.project.data_processing.clusters. \
|
||||
workflows.scale as scale_flow
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ClustersView(tables.DataTableView):
|
||||
table_class = c_tables.ClustersTable
|
||||
template_name = 'project/data_processing.clusters/clusters.html'
|
||||
|
||||
def get_data(self):
|
||||
try:
|
||||
clusters = saharaclient.cluster_list(self.request)
|
||||
except Exception:
|
||||
clusters = []
|
||||
exceptions.handle(self.request,
|
||||
_("Unable to fetch cluster list"))
|
||||
return clusters
|
||||
|
||||
|
||||
class ClusterDetailsView(tabs.TabView):
|
||||
tab_group_class = _tabs.ClusterDetailsTabs
|
||||
template_name = 'project/data_processing.clusters/details.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ClusterDetailsView, self)\
|
||||
.get_context_data(**kwargs)
|
||||
return context
|
||||
|
||||
|
||||
class CreateClusterView(workflows.WorkflowView):
|
||||
workflow_class = create_flow.CreateCluster
|
||||
success_url = \
|
||||
"horizon:project:data_processing.clusters:create-cluster"
|
||||
classes = ("ajax-modal")
|
||||
template_name = "project/data_processing.clusters/create.html"
|
||||
|
||||
|
||||
class ConfigureClusterView(workflows.WorkflowView):
|
||||
workflow_class = create_flow.ConfigureCluster
|
||||
success_url = "horizon:project:data_processing.clusters"
|
||||
template_name = "project/data_processing.clusters/configure.html"
|
||||
|
||||
|
||||
class ScaleClusterView(workflows.WorkflowView):
|
||||
workflow_class = scale_flow.ScaleCluster
|
||||
success_url = "horizon:project:data_processing.clusters"
|
||||
classes = ("ajax-modal")
|
||||
template_name = "project/data_processing.clusters/scale.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ScaleClusterView, self)\
|
||||
.get_context_data(**kwargs)
|
||||
|
||||
context["cluster_id"] = kwargs["cluster_id"]
|
||||
return context
|
||||
|
||||
def get_object(self, *args, **kwargs):
|
||||
if not hasattr(self, "_object"):
|
||||
template_id = self.kwargs['cluster_id']
|
||||
try:
|
||||
template = saharaclient.cluster_template_get(self.request,
|
||||
template_id)
|
||||
except Exception:
|
||||
template = None
|
||||
exceptions.handle(self.request,
|
||||
_("Unable to fetch cluster template."))
|
||||
self._object = template
|
||||
return self._object
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(ScaleClusterView, self).get_initial()
|
||||
initial.update({'cluster_id': self.kwargs['cluster_id']})
|
||||
return initial
|
@ -0,0 +1,230 @@
|
||||
# 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 horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import workflows
|
||||
|
||||
from openstack_dashboard.api import nova
|
||||
|
||||
from openstack_dashboard.dashboards.project.data_processing.utils \
|
||||
import neutron_support
|
||||
import openstack_dashboard.dashboards.project.data_processing.utils. \
|
||||
workflow_helpers as whelpers
|
||||
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from openstack_dashboard.api import sahara as saharaclient
|
||||
import openstack_dashboard.dashboards.project.data_processing. \
|
||||
cluster_templates. workflows.create as t_flows
|
||||
|
||||
from saharaclient.api import base as api_base
|
||||
|
||||
import logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SelectPluginAction(t_flows.SelectPluginAction):
|
||||
class Meta:
|
||||
name = _("Select plugin and hadoop version for cluster")
|
||||
help_text_template = (
|
||||
"project/data_processing.clusters/_create_general_help.html")
|
||||
|
||||
|
||||
class SelectPlugin(t_flows.SelectPlugin):
|
||||
pass
|
||||
|
||||
|
||||
class CreateCluster(t_flows.CreateClusterTemplate):
|
||||
slug = "create_cluster"
|
||||
name = _("Launch Cluster")
|
||||
success_url = "horizon:project:data_processing.cluster_templates:index"
|
||||
|
||||
|
||||
class GeneralConfigAction(workflows.Action):
|
||||
populate_neutron_management_network_choices = \
|
||||
neutron_support.populate_neutron_management_network_choices
|
||||
|
||||
hidden_configure_field = forms.CharField(
|
||||
required=False,
|
||||
widget=forms.HiddenInput(attrs={"class": "hidden_configure_field"}))
|
||||
|
||||
hidden_to_delete_field = forms.CharField(
|
||||
required=False,
|
||||
widget=forms.HiddenInput(attrs={"class": "hidden_to_delete_field"}))
|
||||
|
||||
cluster_name = forms.CharField(label=_("Cluster Name"),
|
||||
required=True)
|
||||
|
||||
description = forms.CharField(label=_("Description"),
|
||||
required=False,
|
||||
widget=forms.Textarea)
|
||||
cluster_template = forms.ChoiceField(label=_("Cluster Template"),
|
||||
initial=(None, "None"),
|
||||
required=False)
|
||||
|
||||
image = forms.ChoiceField(label=_("Base Image"),
|
||||
required=True)
|
||||
|
||||
keypair = forms.ChoiceField(
|
||||
label=_("Keypair"),
|
||||
required=False,
|
||||
help_text=_("Which keypair to use for authentication."))
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(GeneralConfigAction, self).__init__(request, *args, **kwargs)
|
||||
|
||||
plugin, hadoop_version = whelpers.\
|
||||
get_plugin_and_hadoop_version(request)
|
||||
|
||||
if saharaclient.base.is_service_enabled(request, 'network'):
|
||||
self.fields["neutron_management_network"] = forms.ChoiceField(
|
||||
label=_("Neutron Management Network"),
|
||||
required=True,
|
||||
choices=self.populate_neutron_management_network_choices(
|
||||
request, {})
|
||||
)
|
||||
|
||||
self.fields["plugin_name"] = forms.CharField(
|
||||
widget=forms.HiddenInput(),
|
||||
initial=plugin
|
||||
)
|
||||
self.fields["hadoop_version"] = forms.CharField(
|
||||
widget=forms.HiddenInput(),
|
||||
initial=hadoop_version
|
||||
)
|
||||
|
||||
def populate_image_choices(self, request, context):
|
||||
try:
|
||||
all_images = saharaclient.image_list(request)
|
||||
|
||||
plugin, hadoop_version = whelpers.\
|
||||
get_plugin_and_hadoop_version(request)
|
||||
|
||||
details = saharaclient.plugin_get_version_details(request,
|
||||
plugin,
|
||||
hadoop_version)
|
||||
|
||||
return [(image.id, image.name) for image in all_images
|
||||
if set(details.required_image_tags).
|
||||
issubset(set(image.tags))]
|
||||
except Exception:
|
||||
exceptions.handle(request,
|
||||
_("Unable to fetch image choices."))
|
||||
return []
|
||||
|
||||
def populate_keypair_choices(self, request, context):
|
||||
try:
|
||||
keypairs = nova.keypair_list(request)
|
||||
except Exception:
|
||||
keypairs = []
|
||||
exceptions.handle(request,
|
||||
_("Unable to fetch keypair choices."))
|
||||
keypair_list = [(kp.name, kp.name) for kp in keypairs]
|
||||
keypair_list.insert(0, ("", _("No keypair")))
|
||||
|
||||
return keypair_list
|
||||
|
||||
def populate_cluster_template_choices(self, request, context):
|
||||
templates = saharaclient.cluster_template_list(request)
|
||||
|
||||
plugin, hadoop_version = whelpers.\
|
||||
get_plugin_and_hadoop_version(request)
|
||||
|
||||
choices = [(template.id, template.name)
|
||||
for template in templates
|
||||
if (template.hadoop_version == hadoop_version and
|
||||
template.plugin_name == plugin)]
|
||||
|
||||
# cluster_template_id comes from cluster templates table, when
|
||||
# Create Cluster from template is clicked there
|
||||
selected_template_id = request.REQUEST.get("cluster_template_id", None)
|
||||
|
||||
for template in templates:
|
||||
if template.id == selected_template_id:
|
||||
self.fields['cluster_template'].initial = template.id
|
||||
|
||||
return choices
|
||||
|
||||
def get_help_text(self):
|
||||
extra = dict()
|
||||
plugin, hadoop_version = whelpers.\
|
||||
get_plugin_and_hadoop_version(self.request)
|
||||
extra["plugin_name"] = plugin
|
||||
extra["hadoop_version"] = hadoop_version
|
||||
return super(GeneralConfigAction, self).get_help_text(extra)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(GeneralConfigAction, self).clean()
|
||||
if cleaned_data.get("hidden_configure_field", None) \
|
||||
== "create_nodegroup":
|
||||
self._errors = dict()
|
||||
return cleaned_data
|
||||
|
||||
class Meta:
|
||||
name = _("Configure Cluster")
|
||||
help_text_template = \
|
||||
("project/data_processing.clusters/_configure_general_help.html")
|
||||
|
||||
|
||||
class GeneralConfig(workflows.Step):
|
||||
action_class = GeneralConfigAction
|
||||
contributes = ("hidden_configure_field", )
|
||||
|
||||
def contribute(self, data, context):
|
||||
for k, v in data.items():
|
||||
context["general_" + k] = v
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class ConfigureCluster(whelpers.StatusFormatMixin, workflows.Workflow):
|
||||
slug = "configure_cluster"
|
||||
name = _("Launch Cluster")
|
||||
finalize_button_name = _("Create")
|
||||
success_message = _("Created Cluster %s")
|
||||
name_property = "general_cluster_name"
|
||||
success_url = "horizon:project:data_processing.clusters:index"
|
||||
default_steps = (GeneralConfig, )
|
||||
|
||||
def handle(self, request, context):
|
||||
try:
|
||||
#TODO(nkonovalov) Implement AJAX Node Groups
|
||||
node_groups = None
|
||||
|
||||
plugin, hadoop_version = whelpers.\
|
||||
get_plugin_and_hadoop_version(request)
|
||||
|
||||
cluster_template_id = context["general_cluster_template"] or None
|
||||
user_keypair = context["general_keypair"] or None
|
||||
|
||||
saharaclient.cluster_create(
|
||||
request,
|
||||
context["general_cluster_name"],
|
||||
plugin, hadoop_version,
|
||||
cluster_template_id=cluster_template_id,
|
||||
default_image_id=context["general_image"],
|
||||
description=context["general_description"],
|
||||
node_groups=node_groups,
|
||||
user_keypair_id=user_keypair,
|
||||
net_id=context.get("general_neutron_management_network", None))
|
||||
return True
|
||||
except api_base.APIException as e:
|
||||
self.error_description = str(e)
|
||||
return False
|
||||
except Exception:
|
||||
exceptions.handle(request,
|
||||
_('Unable to create the cluster'))
|
||||
return False
|
@ -0,0 +1,166 @@
|
||||
# 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 json
|
||||
import logging
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
|
||||
from openstack_dashboard.api import sahara as saharaclient
|
||||
import openstack_dashboard.dashboards.project.data_processing. \
|
||||
cluster_templates.workflows.create as clt_create_flow
|
||||
import openstack_dashboard.dashboards.project.data_processing. \
|
||||
clusters.workflows.create as cl_create_flow
|
||||
import openstack_dashboard.dashboards.project.data_processing. \
|
||||
utils.workflow_helpers as whelpers
|
||||
|
||||
from saharaclient.api import base as api_base
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NodeGroupsStep(clt_create_flow.ConfigureNodegroups):
|
||||
pass
|
||||
|
||||
|
||||
class ScaleCluster(cl_create_flow.ConfigureCluster,
|
||||
whelpers.StatusFormatMixin):
|
||||
slug = "scale_cluster"
|
||||
name = _("Scale Cluster")
|
||||
finalize_button_name = _("Scale")
|
||||
success_url = "horizon:project:data_processing.clusters:index"
|
||||
default_steps = (NodeGroupsStep, )
|
||||
|
||||
def __init__(self, request, context_seed, entry_point, *args, **kwargs):
|
||||
ScaleCluster._cls_registry = set([])
|
||||
|
||||
self.success_message = _("Scaled cluster successfully started.")
|
||||
|
||||
cluster_id = context_seed["cluster_id"]
|
||||
try:
|
||||
cluster = saharaclient.cluster_get(request, cluster_id)
|
||||
plugin = cluster.plugin_name
|
||||
hadoop_version = cluster.hadoop_version
|
||||
|
||||
#init deletable nodegroups
|
||||
deletable = dict()
|
||||
for group in cluster.node_groups:
|
||||
deletable[group["name"]] = "false"
|
||||
request.GET = request.GET.copy()
|
||||
request.GET.update({
|
||||
"cluster_id": cluster_id,
|
||||
"plugin_name": plugin,
|
||||
"hadoop_version": hadoop_version,
|
||||
"deletable": deletable
|
||||
})
|
||||
|
||||
super(ScaleCluster, self).__init__(request, context_seed,
|
||||
entry_point, *args,
|
||||
**kwargs)
|
||||
|
||||
#init Node Groups
|
||||
|
||||
for step in self.steps:
|
||||
if isinstance(step, clt_create_flow.ConfigureNodegroups):
|
||||
ng_action = step.action
|
||||
template_ngs = cluster.node_groups
|
||||
|
||||
if 'forms_ids' not in request.POST:
|
||||
ng_action.groups = []
|
||||
for id in range(0, len(template_ngs), 1):
|
||||
group_name = "group_name_" + str(id)
|
||||
template_id = "template_id_" + str(id)
|
||||
count = "count_" + str(id)
|
||||
templ_ng = template_ngs[id]
|
||||
ng_action.groups.append(
|
||||
{"name": templ_ng["name"],
|
||||
"template_id": templ_ng["node_group_template_id"],
|
||||
"count": templ_ng["count"],
|
||||
"id": id,
|
||||
"deletable": "false"})
|
||||
|
||||
whelpers.build_node_group_fields(ng_action,
|
||||
group_name,
|
||||
template_id,
|
||||
count)
|
||||
except Exception:
|
||||
exceptions.handle(request,
|
||||
_("Unable to fetch cluster to scale"))
|
||||
|
||||
def format_status_message(self, message):
|
||||
# Scaling form requires special handling because it has no Cluster name
|
||||
# in it's context
|
||||
|
||||
error_description = getattr(self, 'error_description', None)
|
||||
if error_description:
|
||||
return error_description
|
||||
else:
|
||||
return self.success_message
|
||||
|
||||
def handle(self, request, context):
|
||||
cluster_id = request.GET["cluster_id"]
|
||||
try:
|
||||
cluster = saharaclient.cluster_get(request, cluster_id)
|
||||
existing_node_groups = set([])
|
||||
for ng in cluster.node_groups:
|
||||
existing_node_groups.add(ng["name"])
|
||||
|
||||
scale_object = dict()
|
||||
|
||||
ids = json.loads(context["ng_forms_ids"])
|
||||
|
||||
for _id in ids:
|
||||
name = context["ng_group_name_%s" % _id]
|
||||
template_id = context["ng_template_id_%s" % _id]
|
||||
count = context["ng_count_%s" % _id]
|
||||
|
||||
if name not in existing_node_groups:
|
||||
if "add_node_groups" not in scale_object:
|
||||
scale_object["add_node_groups"] = []
|
||||
|
||||
scale_object["add_node_groups"].append(
|
||||
{"name": name,
|
||||
"node_group_template_id": template_id,
|
||||
"count": int(count)})
|
||||
else:
|
||||
old_count = None
|
||||
for ng in cluster.node_groups:
|
||||
if name == ng["name"]:
|
||||
old_count = ng["count"]
|
||||
break
|
||||
|
||||
if old_count != count:
|
||||
if "resize_node_groups" not in scale_object:
|
||||
scale_object["resize_node_groups"] = []
|
||||
|
||||
scale_object["resize_node_groups"].append(
|
||||
{"name": name,
|
||||
"count": int(count)}
|
||||
)
|
||||
except Exception:
|
||||
scale_object = {}
|
||||
exceptions.handle(request,
|
||||
_("Unable to fetch cluster to scale."))
|
||||
|
||||
try:
|
||||
saharaclient.cluster_scale(request, cluster_id, scale_object)
|
||||
return True
|
||||
except api_base.APIException as e:
|
||||
self.error_description = str(e)
|
||||
return False
|
||||
except Exception:
|
||||
exceptions.handle(request,
|
||||
_("Scale cluster operation failed"))
|
||||
return False
|
@ -0,0 +1,67 @@
|
||||
# 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.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
|
||||
from openstack_dashboard.api import sahara as saharaclient
|
||||
import openstack_dashboard.dashboards.project.data_processing. \
|
||||
utils.workflow_helpers as whelpers
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def anti_affinity_field():
|
||||
return forms.MultipleChoiceField(
|
||||
label=_("Use anti-affinity groups for: "),
|
||||
required=False,
|
||||
help_text=_("Use anti-affinity groups for processes"),
|
||||
widget=forms.CheckboxSelectMultiple()
|
||||
)
|
||||
|
||||
|
||||
def populate_anti_affinity_choices(self, request, context):
|
||||
try:
|
||||
sahara = saharaclient.client(request)
|
||||
plugin, version = whelpers.get_plugin_and_hadoop_version(request)
|
||||
|
||||
version_details = sahara.plugins.get_version_details(plugin, version)
|
||||
process_choices = []
|
||||
for processes in version_details.node_processes.values():
|
||||
for process in processes:
|
||||
process_choices.append((process, process))
|
||||
|
||||
cluster_template_id = request.REQUEST.get("cluster_template_id", None)
|
||||
if cluster_template_id is None:
|
||||
selected_processes = request.REQUEST.get("aa_groups", [])
|
||||
else:
|
||||
cluster_template = (
|
||||
sahara.cluster_templates.get(cluster_template_id))
|
||||
selected_processes = cluster_template.anti_affinity
|
||||
|
||||
checked_dict = dict()
|
||||
|
||||
for process in selected_processes:
|
||||
checked_dict[process] = process
|
||||
|
||||
self.fields['anti_affinity'].initial = checked_dict
|
||||
except Exception:
|
||||
process_choices = []
|
||||
exceptions.handle(request,
|
||||
_("Unable to populate anti-affinity processes."))
|
||||
return process_choices
|
@ -0,0 +1,33 @@
|
||||
# Copyright (c) 2013 Mirantis Inc.
|
||||
#
|
||||
# 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 _
|
||||
from horizon import exceptions
|
||||
|
||||
from openstack_dashboard.api import neutron
|
||||
|
||||
|
||||
def populate_neutron_management_network_choices(self, request, context):
|
||||
try:
|
||||
tenant_id = self.request.user.tenant_id
|
||||
networks = neutron.network_list_for_tenant(request, tenant_id)
|
||||
for n in networks:
|
||||
n.set_id_as_name_if_empty()
|
||||
network_list = [(network.id, network.name) for network in networks]
|
||||
except Exception:
|
||||
network_list = []
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve networks.'))
|
||||
return network_list
|
@ -12,6 +12,8 @@
|
||||
|
||||
from openstack_dashboard.test.test_data import utils
|
||||
|
||||
from saharaclient.api import cluster_templates
|
||||
from saharaclient.api import clusters
|
||||
from saharaclient.api import node_group_templates
|
||||
from saharaclient.api import plugins
|
||||
|
||||
@ -19,6 +21,8 @@ from saharaclient.api import plugins
|
||||
def data(TEST):
|
||||
TEST.plugins = utils.TestDataContainer()
|
||||
TEST.nodegroup_templates = utils.TestDataContainer()
|
||||
TEST.cluster_templates = utils.TestDataContainer()
|
||||
TEST.clusters = utils.TestDataContainer()
|
||||
|
||||
plugin1_dict = {
|
||||
"description": "vanilla plugin",
|
||||
@ -61,3 +65,167 @@ def data(TEST):
|
||||
node_group_templates.NodeGroupTemplateManager(None), ngt1_dict)
|
||||
|
||||
TEST.nodegroup_templates.add(ngt1)
|
||||
|
||||
#Cluster_templates
|
||||
ct1_dict = {
|
||||
"anti_affinity": [],
|
||||
"cluster_configs": {},
|
||||
"created_at": "2014-06-04 14:01:06.460711",
|
||||
"default_image_id": None,
|
||||
"description": None,
|
||||
"hadoop_version": "1.2.1",
|
||||
"id": "a2c3743f-31a2-4919-8d02-792138a87a98",
|
||||
"name": "sample-cluster-template",
|
||||
"neutron_management_network": None,
|
||||
"node_groups": [
|
||||
{
|
||||
"count": 1,
|
||||
"created_at": "2014-06-04 14:01:06.462512",
|
||||
"flavor_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
||||
"floating_ip_pool": None,
|
||||
"image_id": None,
|
||||
"name": "master",
|
||||
"node_configs": {},
|
||||
"node_group_template_id": "c166dfcc-9cc7-4b48-adc9",
|
||||
"node_processes": [
|
||||
"namenode",
|
||||
"jobtracker",
|
||||
"secondarynamenode",
|
||||
"hiveserver",
|
||||
"oozie"
|
||||
],
|
||||
"updated_at": None,
|
||||
"volume_mount_prefix": "/volumes/disk",
|
||||
"volumes_per_node": 0,
|
||||
"volumes_size": 0
|
||||
},
|
||||
{
|
||||
"count": 2,
|
||||
"created_at": "2014-06-04 14:01:06.463214",
|
||||
"flavor_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
||||
"floating_ip_pool": None,
|
||||
"image_id": None,
|
||||
"name": "workers",
|
||||
"node_configs": {},
|
||||
"node_group_template_id": "4eb5504c-94c9-4049-a440",
|
||||
"node_processes": [
|
||||
"datanode",
|
||||
"tasktracker"
|
||||
],
|
||||
"updated_at": None,
|
||||
"volume_mount_prefix": "/volumes/disk",
|
||||
"volumes_per_node": 0,
|
||||
"volumes_size": 0
|
||||
}
|
||||
],
|
||||
"plugin_name": "vanilla",
|
||||
"tenant_id": "429ad8447c2d47bc8e0382d244e1d1df",
|
||||
"updated_at": None
|
||||
}
|
||||
|
||||
ct1 = cluster_templates.ClusterTemplate(
|
||||
cluster_templates.ClusterTemplateManager(None), ct1_dict)
|
||||
TEST.cluster_templates.add(ct1)
|
||||
|
||||
#Clusters
|
||||
cluster1_dict = {
|
||||
"anti_affinity": [],
|
||||
"cluster_configs": {},
|
||||
"cluster_template_id": "a2c3743f-31a2-4919-8d02-792138a87a98",
|
||||
"created_at": "2014-06-04 20:02:14.051328",
|
||||
"default_image_id": "9eb4643c-dca8-4ea7-92d2-b773f88a8dc6",
|
||||
"description": "",
|
||||
"hadoop_version": "1.2.1",
|
||||
"id": "ec9a0d28-5cfb-4028-a0b5-40afe23f1533",
|
||||
"info": {},
|
||||
"is_transient": False,
|
||||
"management_public_key": "fakekey",
|
||||
"name": "cercluster",
|
||||
"neutron_management_network": None,
|
||||
"node_groups": [
|
||||
{
|
||||
"count": 1,
|
||||
"created_at": "2014-06-04 20:02:14.053153",
|
||||
"flavor_id": "0",
|
||||
"floating_ip_pool": None,
|
||||
"image_id": None,
|
||||
"instances": [
|
||||
{
|
||||
"created_at": "2014-06-04 20:02:14.834529",
|
||||
"id": "c3b8004b-7063-4b99-a082-820cdc6e961c",
|
||||
"instance_id": "a45f5495-4a10-4f17-8fae",
|
||||
"instance_name": "cercluster-master-001",
|
||||
"internal_ip": None,
|
||||
"management_ip": None,
|
||||
"updated_at": None,
|
||||
"volumes": []
|
||||
}
|
||||
],
|
||||
"name": "master",
|
||||
"node_configs": {},
|
||||
"node_group_template_id": "c166dfcc-9cc7-4b48-adc9",
|
||||
"node_processes": [
|
||||
"namenode",
|
||||
"jobtracker",
|
||||
"secondarynamenode",
|
||||
"hiveserver",
|
||||
"oozie"
|
||||
],
|
||||
"updated_at": "2014-06-04 20:02:14.841760",
|
||||
"volume_mount_prefix": "/volumes/disk",
|
||||
"volumes_per_node": 0,
|
||||
"volumes_size": 0
|
||||
},
|
||||
{
|
||||
"count": 2,
|
||||
"created_at": "2014-06-04 20:02:14.053849",
|
||||
"flavor_id": "0",
|
||||
"floating_ip_pool": None,
|
||||
"image_id": None,
|
||||
"instances": [
|
||||
{
|
||||
"created_at": "2014-06-04 20:02:15.097655",
|
||||
"id": "6a8ae0b1-bb28-4de2-bfbb-bdd3fd2d72b2",
|
||||
"instance_id": "38bf8168-fb30-483f-8d52",
|
||||
"instance_name": "cercluster-workers-001",
|
||||
"internal_ip": None,
|
||||
"management_ip": None,
|
||||
"updated_at": None,
|
||||
"volumes": []
|
||||
},
|
||||
{
|
||||
"created_at": "2014-06-04 20:02:15.344515",
|
||||
"id": "17b98ed3-a776-467a-90cf-9f46a841790b",
|
||||
"instance_id": "85606938-8e53-46a5-a50b",
|
||||
"instance_name": "cercluster-workers-002",
|
||||
"internal_ip": None,
|
||||
"management_ip": None,
|
||||
"updated_at": None,
|
||||
"volumes": []
|
||||
}
|
||||
],
|
||||
"name": "workers",
|
||||
"node_configs": {},
|
||||
"node_group_template_id": "4eb5504c-94c9-4049-a440",
|
||||
"node_processes": [
|
||||
"datanode",
|
||||
"tasktracker"
|
||||
],
|
||||
"updated_at": "2014-06-04 20:02:15.355745",
|
||||
"volume_mount_prefix": "/volumes/disk",
|
||||
"volumes_per_node": 0,
|
||||
"volumes_size": 0
|
||||
}
|
||||
],
|
||||
"plugin_name": "vanilla",
|
||||
"status": "Active",
|
||||
"status_description": "",
|
||||
"tenant_id": "429ad8447c2d47bc8e0382d244e1d1df",
|
||||
"trust_id": None,
|
||||
"updated_at": "2014-06-04 20:02:15.446087",
|
||||
"user_keypair_id": "stackboxkp"
|
||||
}
|
||||
|
||||
cluster1 = clusters.Cluster(
|
||||
clusters.ClusterManager(None), cluster1_dict)
|
||||
TEST.clusters.add(cluster1)
|
||||
|
Loading…
x
Reference in New Issue
Block a user