Adding the data_image_registry panel for Sahara

The data_image_registry panel allows for tagging
images with the flavors and versions of hadoop that they
support.  Those tags are used in creating clusters to
be sure that a cluster has all the necessary pieces to
function properly.
This code was originally from:
https://github.com/openstack/sahara-dashboard

Change-Id: I647c03a0ce9b802dd1100bce49dc46e24cac7190
Partial-Implements: blueprint merge-sahara-dashboard
Co-Authored-By: Nikita Konovalov <nkonovalov@mirantis.com>
Co-Authored-By: Dmitry Mescheryakov <dmescheryakov@mirantis.com>
This commit is contained in:
Chad Roberts 2014-04-24 11:18:33 -04:00
parent 7c3897a4bd
commit 46d420c8e7
15 changed files with 628 additions and 1 deletions

View File

@ -60,7 +60,8 @@ class DatabasePanels(horizon.PanelGroup):
class DataProcessingPanels(horizon.PanelGroup):
name = _("Data Processing")
slug = "data_processing"
panels = ('data_processing.data_plugins',)
panels = ('data_processing.data_plugins',
'data_processing.data_image_registry', )
class Project(horizon.Dashboard):

View File

@ -0,0 +1,117 @@
# 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
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from horizon import messages
from openstack_dashboard.api import glance
from openstack_dashboard.api import sahara as saharaclient
class ImageForm(forms.SelfHandlingForm):
image_id = forms.CharField(widget=forms.HiddenInput())
tags_list = forms.CharField(widget=forms.HiddenInput())
user_name = forms.CharField(max_length=80, label=_("User Name"))
description = forms.CharField(max_length=80,
label=_("Description"),
required=False,
widget=forms.Textarea(attrs={'cols': 80,
'rows': 20}))
def handle(self, request, data):
try:
image_id = data['image_id']
user_name = data['user_name']
desc = data['description']
saharaclient.image_update(request, image_id, user_name, desc)
image_tags = json.loads(data["tags_list"])
saharaclient.image_tags_update(request, image_id, image_tags)
messages.success(request,
_("Successfully updated image."))
return True
except Exception:
exceptions.handle(request,
_("Failed to update image."))
return False
class EditTagsForm(ImageForm):
image_id = forms.CharField(widget=forms.HiddenInput())
class RegisterImageForm(ImageForm):
image_id = forms.ChoiceField(label=_("Image"),
required=True)
def __init__(self, request, *args, **kwargs):
super(RegisterImageForm, self).__init__(request, *args, **kwargs)
self._populate_image_id_choices()
def _populate_image_id_choices(self):
images = self._get_available_images(self.request)
choices = [(image.id, image.name)
for image in images
if image.properties.get("image_type", '') != "snapshot"]
if choices:
choices.insert(0, ("", _("Select Image")))
else:
choices.insert(0, ("", _("No images available.")))
self.fields['image_id'].choices = choices
def _get_images(self, request, filter):
try:
images, _more = glance.image_list_detailed(request, filters=filter)
except Exception:
images = []
exceptions.handle(request,
_("Unable to retrieve images with filter %s.") %
filter)
return images
def _get_public_images(self, request):
filter = {"is_public": True,
"status": "active"}
return self._get_images(request, filter)
def _get_tenant_images(self, request):
filter = {"owner": request.user.tenant_id,
"status": "active"}
return self._get_images(request, filter)
def _get_available_images(self, request):
images = self._get_tenant_images(request)
if request.user.is_superuser:
images += self._get_public_images(request)
final_images = []
try:
image_ids = set(img.id for img in saharaclient.image_list(request))
except Exception:
image_ids = set()
exceptions.handle(request,
_("Unable to fetch available images."))
for image in images:
if (image.id not in image_ids and
image.container_format not in ('aki', 'ari')):
final_images.append(image)
return final_images

View File

@ -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 ImageRegistryPanel(horizon.Panel):
name = _("Image Registry")
slug = 'data_processing.data_image_registry'
permissions = ('openstack.services.data_processing',)
dashboard.Project.register(ImageRegistryPanel)

View File

@ -0,0 +1,72 @@
# 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 import template
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 EditTagsAction(tables.LinkAction):
name = "edit_tags"
verbose_name = _("Edit Tags")
url = "horizon:project:data_processing.data_image_registry:edit_tags"
classes = ("ajax-modal", "btn-create")
def tags_to_string(image):
template_name = (
'project/data_processing.data_image_registry/_list_tags.html')
context = {"image": image}
return template.loader.render_to_string(template_name, context)
class RegisterImage(tables.LinkAction):
name = "register"
verbose_name = _("Register Image")
url = "horizon:project:data_processing.data_image_registry:register"
classes = ("btn-launch", "ajax-modal")
class UnregisterImages(tables.BatchAction):
name = "Unregister"
action_present = _("Unregister")
action_past = _("Unregistered")
data_type_singular = _("Image")
data_type_plural = _("Images")
classes = ('btn-danger', 'btn-terminate')
def action(self, request, obj_id):
saharaclient.image_unregister(request, obj_id)
class ImageRegistryTable(tables.DataTable):
name = tables.Column("name",
verbose_name=_("Image"),
link=("horizon:project:"
"images:images:detail"))
tags = tables.Column(tags_to_string,
verbose_name=_("Tags"))
class Meta:
name = "image_registry"
verbose_name = _("Image Registry")
table_actions = (RegisterImage, UnregisterImages,)
row_actions = (EditTagsAction, UnregisterImages,)

View File

@ -0,0 +1,29 @@
{% extends "horizon/common/_modal_form.html" %}
{% load url from future %}
{% load i18n %}
{% block form_id %}edit_tags_form{% endblock %}
{% block form_action %}{% url 'horizon:project:data_processing.data_image_registry:edit_tags' image.id %}{% endblock %}
{% block modal-header %}{% trans "Edit Image Tags" %}{% endblock %}
{% block modal-body %}
<div class="left">
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
{% include 'project/data_processing.data_image_registry/_tag_form.html' %}
</div>
<div class="right">
{% include 'project/data_processing.data_image_registry/_help.html' %}
</div>
{% endblock %}
{% block modal-footer %}
<input class="btn btn-primary pull-right" id="edit_image_tags_btn" type="submit" value="{% trans "Done" %}"/>
<a href="{% url 'horizon:project:data_processing.data_image_registry:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
{% endblock %}

View File

@ -0,0 +1,21 @@
{% load i18n %}
<div class="well well-small">
<h3>{% blocktrans %}Image Registry tool:{% endblocktrans %}</h3>
<br />
<p>
{% blocktrans %}Image Registry is used to provide additional information about images for Data Processing.{% endblocktrans %}
</p>
<p>
{% blocktrans %}Specified User Name will be used by Data Processing to apply configs and manage processes on instances.{% endblocktrans %}
</p>
<p>
{% blocktrans %}Tags are used for filtering images suitable for each plugin and each Data Processing version.
To add required tags, select a plugin and a Data Processing version and click &quot;Add all&quot; button.{% endblocktrans %}
</p>
<p>
{% blocktrans %}You may also add any custom tag.{% endblocktrans %}
</p>
<p>
{% blocktrans %}Unnecessary tags may be removed by clicking a cross near tag's name.{% endblocktrans %}
</p>
</div>

View File

@ -0,0 +1,5 @@
<ul>
{% for tag in image.tags %}
<li><span class="label label-info" style="float: left;display: block; margin: 2px;">{{ tag }}</span></li>
{% endfor %}
</ul>

View File

@ -0,0 +1,27 @@
{% extends "horizon/common/_modal_form.html" %}
{% load url from future %}
{% load i18n %}
{% block form_id %}register_image_form{% endblock %}
{% block form_action %}{% url 'horizon:project:data_processing.data_image_registry:register' %}{% endblock %}
{% block modal-header %}{% trans "Register Image" %}{% endblock %}
{% block modal-body %}
<div class="left">
<fieldset>
{% include "horizon/common/_form_fields.html" %}
{% include 'project/data_processing.data_image_registry/_tag_form.html' %}
</fieldset>
</div>
<div class="right">
{% include 'project/data_processing.data_image_registry/_help.html' %}
</div>
{% endblock %}
{% block modal-footer %}
<input class="btn btn-primary pull-right" id="edit_image_tags_btn" type="submit" value="{% trans "Done" %}"/>
<a href="{% url 'horizon:project:data_processing.data_image_registry:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
{% endblock %}

View File

@ -0,0 +1,119 @@
{% load i18n %}
<div id="image_tags_list" class="well"></div>
<div style="clear: both;"></div>
<div id="plugin_populate_section">
<h5>{% trans "Register tags required for the Plugin with specified Data Processing Version" %}</h5>
<br>
<table style="margin-bottom: 4px">
<tr>
<td style="width: 200px">
<label for="plugin_select">{% trans "Plugin" %}</label>
<select id="plugin_select" class="plugin-choice" style="width: 100%">
{% for plugin, version_dict in plugins.items %}
<option value="{{ plugin }}">{{ plugin }}</option>
{% endfor %}
</select>
</td>
<td style="width: 200px">
{% for plugin, version_dict in plugins.items %}
<div id="version_group_{{ plugin }}" class="data_processing-version-choice" >
<label for="data_processing_version_{{ plugin }}">{% trans "Version" %}</label>
<select id="data_processing_version_{{ plugin }}" style="width: 100%">
{% for version, tags in version_dict.items %}
<option value="{{ version }}">{{ version }}</option>
{% endfor %}
</select>
</div>
{% endfor %}
</td>
</tr>
</table>
<input type="button" id="add_all_btn" class="btn btn-small" value="{% trans "Add all" %}" />
</div>
<hr />
<div style="margin-top: 5px;">
<input type="text" class="tag-input" id="_sahara_image_tag" data-original-title="" onclick=""/>
<a href="#" id="add_tag_btn" class="btn btn-small btn-create btn-inline" onclick="add_tag_to_image()">{% trans "Add custom tag" %}</a>
</div>
<script type="text/javascript">
$(function() {
$(".plugin-choice").change(function(e) {
$(".data_processing-version-choice").hide();
var val = $(this).val();
$("#version_group_" + val).show();
}).change();
$("#add_all_btn").click(function(e) {
var plugin = $("#plugin_select").val();
var version = $("#data_processing_version_" + plugin).val();
var tags = plugin_tags_map[plugin][version];
$(tags).each(function(idx, tag) {
add_tag_to_image(tag);
});
})
})
$("#_sahara_image_tag").keypress(function (event) {
if (event.keyCode == 13) {
add_tag_to_image();
return false;
}
return true;
});
function add_tag_to_image(tag) {
if (!tag) {
tag = $.trim($("#_sahara_image_tag").val());
}
if (tag.length == 0) {
return;
}
$("#image_tags_list span").each(function (el) {
if ($.trim($(this).text()) == tag) {
return;
}
});
var tags = get_current_tags();
if ($.inArray(tag, tags) == -1) {
var span = ' <span class="label label-warning" style="float: left;display: block; margin: 2px;">$tag <i class="icon-remove-sign" onclick="remove_tag(this);"></i></span>'.replace("$tag", tag)
$("#image_tags_list").append(span);
update_image_tags();
}
$("#_sahara_image_tag").val("");
}
function get_current_tags() {
var tags = [];
$("#image_tags_list span").each(function (el) {
tags.push($.trim($(this).text()));
});
return tags;
}
function update_image_tags() {
var tags = get_current_tags();
$("#id_tags_list").val(JSON.stringify(tags));
}
function remove_tag(icon) {
span = icon.parentNode;
span.parentNode.removeChild(span);
update_image_tags()
}
// {"plugin": {"v1": [...tags...], "v2": [...tags...]},
// "other_plugin": ... }
var plugin_tags_map = {};
{% for plugin, version_dict in plugins.items %}
plugin_tags_map["{{ plugin }}"] = {};
{% for version, tags in version_dict.items %}
plugin_tags_map["{{ plugin }}"]["{{ version }}"] = [];
{% for tag in tags %}
plugin_tags_map["{{ plugin }}"]["{{ version }}"].push("{{ tag }}");
{% endfor %}
{% endfor %}
{% endfor %}
</script>

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Edit Image Tags" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Edit Image Tags") %}
{% endblock page_header %}
{% block main %}
{% include 'project/data_processing.data_image_registry/_edit_tags.html' %}
{% endblock %}

View File

@ -0,0 +1,28 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Data Processing" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Image Registry") %}
{% endblock page_header %}
{% block main %}
<div class="image_registry">
{{ image_registry_table.render }}
</div>
<script type="text/javascript">
addHorizonLoadEvent(function () {
horizon.modals.addModalInitFunction(function (modal) {
var tags = JSON.parse($("#id_tags_list").val());
$.each(tags, function(i, tag) {
var tagspan = '<span class="label label-info" style="float: left;display: block; margin: 2px;">' +
tag +
'<i class="icon-remove-sign" onclick="remove_tag(this);"></i></span>';
$("#image_tags_list").append(tagspan);
});
});
});
</script>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Register Image" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Register Image") %}
{% endblock page_header %}
{% block main %}
{% include 'project/data_processing.data_image_registry/_register_image.html' %}
{% endblock %}

View File

@ -0,0 +1,33 @@
# 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.data_image_registry.views as views
urlpatterns = patterns('',
url(r'^$', views.ImageRegistryView.as_view(),
name='index'),
url(r'^$', views.ImageRegistryView.as_view(),
name='image_registry'),
url(r'^edit_tags/(?P<image_id>[^/]+)/$',
views.EditTagsView.as_view(),
name='edit_tags'),
url(r'^register/$',
views.RegisterImageView.as_view(),
name='register'),
)

View File

@ -0,0 +1,126 @@
# 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.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.utils import memoized
from openstack_dashboard.api import sahara as saharaclient
from openstack_dashboard.dashboards.project. \
data_processing.data_image_registry.forms import EditTagsForm
from openstack_dashboard.dashboards.project. \
data_processing.data_image_registry.forms import RegisterImageForm
from openstack_dashboard.dashboards.project. \
data_processing.data_image_registry.tables import ImageRegistryTable
LOG = logging.getLogger(__name__)
class ImageRegistryView(tables.DataTableView):
table_class = ImageRegistryTable
template_name = (
'project/data_processing.data_image_registry/image_registry.html')
def get_data(self):
try:
images = saharaclient.image_list(self.request)
except Exception:
images = []
msg = _('Unable to retrieve image list')
exceptions.handle(self.request, msg)
return images
def update_context_with_plugin_tags(request, context):
try:
plugins = saharaclient.plugin_list(request)
except Exception:
plugins = []
msg = _("Unable to process plugin tags")
exceptions.handle(request, msg)
plugins_object = dict()
for plugin in plugins:
plugins_object[plugin.name] = dict()
for version in plugin.versions:
try:
details = saharaclient. \
plugin_get_version_details(request,
plugin.name,
version)
plugins_object[plugin.name][version] = (
details.required_image_tags)
except Exception:
msg = _("Unable to process plugin tags")
exceptions.handle(request, msg)
context["plugins"] = plugins_object
class EditTagsView(forms.ModalFormView):
form_class = EditTagsForm
template_name = (
'project/data_processing.data_image_registry/edit_tags.html')
success_url = reverse_lazy(
'horizon:project:data_processing.data_image_registry:index')
def get_context_data(self, **kwargs):
context = super(EditTagsView, self).get_context_data(**kwargs)
context['image'] = self.get_object()
update_context_with_plugin_tags(self.request, context)
return context
@memoized.memoized_method
def get_object(self):
try:
image = saharaclient.image_get(self.request,
self.kwargs["image_id"])
except Exception:
image = None
msg = _("Unable to fetch the image details")
exceptions.handle(self.request, msg)
return image
def get_initial(self):
image = self.get_object()
return {"image_id": image.id,
"tags_list": json.dumps(image.tags),
"user_name": image.username,
"description": image.description}
class RegisterImageView(forms.ModalFormView):
form_class = RegisterImageForm
template_name = (
'project/data_processing.data_image_registry/register_image.html')
success_url = reverse_lazy(
'horizon:project:data_processing.data_image_registry:index')
def get_context_data(self, **kwargs):
context = super(RegisterImageView, self).get_context_data(**kwargs)
update_context_with_plugin_tags(self.request, context)
return context
def get_initial(self):
# need this initialization to allow registration
# of images without tags
return {"tags_list": json.dumps([])}