From 46d420c8e7fcc7824853b117b1e844dec832e7f1 Mon Sep 17 00:00:00 2001 From: Chad Roberts Date: Thu, 24 Apr 2014 11:18:33 -0400 Subject: [PATCH] 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 Co-Authored-By: Dmitry Mescheryakov --- .../dashboards/project/dashboard.py | 3 +- .../data_image_registry/__init__.py | 0 .../data_image_registry/forms.py | 117 ++++++++++++++++ .../data_image_registry/panel.py | 27 ++++ .../data_image_registry/tables.py | 72 ++++++++++ .../_edit_tags.html | 29 ++++ .../_help.html | 21 +++ .../_list_tags.html | 5 + .../_register_image.html | 27 ++++ .../_tag_form.html | 119 +++++++++++++++++ .../edit_tags.html | 11 ++ .../image_registry.html | 28 ++++ .../register_image.html | 11 ++ .../data_image_registry/urls.py | 33 +++++ .../data_image_registry/views.py | 126 ++++++++++++++++++ 15 files changed, 628 insertions(+), 1 deletion(-) create mode 100644 openstack_dashboard/dashboards/project/data_processing/data_image_registry/__init__.py create mode 100644 openstack_dashboard/dashboards/project/data_processing/data_image_registry/forms.py create mode 100644 openstack_dashboard/dashboards/project/data_processing/data_image_registry/panel.py create mode 100644 openstack_dashboard/dashboards/project/data_processing/data_image_registry/tables.py create mode 100644 openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/_edit_tags.html create mode 100644 openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/_help.html create mode 100644 openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/_list_tags.html create mode 100644 openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/_register_image.html create mode 100644 openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/_tag_form.html create mode 100644 openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/edit_tags.html create mode 100644 openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/image_registry.html create mode 100644 openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/register_image.html create mode 100644 openstack_dashboard/dashboards/project/data_processing/data_image_registry/urls.py create mode 100644 openstack_dashboard/dashboards/project/data_processing/data_image_registry/views.py diff --git a/openstack_dashboard/dashboards/project/dashboard.py b/openstack_dashboard/dashboards/project/dashboard.py index 21d4917f91..e22ba9c229 100644 --- a/openstack_dashboard/dashboards/project/dashboard.py +++ b/openstack_dashboard/dashboards/project/dashboard.py @@ -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): diff --git a/openstack_dashboard/dashboards/project/data_processing/data_image_registry/__init__.py b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstack_dashboard/dashboards/project/data_processing/data_image_registry/forms.py b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/forms.py new file mode 100644 index 0000000000..b2d6f17292 --- /dev/null +++ b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/forms.py @@ -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 diff --git a/openstack_dashboard/dashboards/project/data_processing/data_image_registry/panel.py b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/panel.py new file mode 100644 index 0000000000..b2153d3e49 --- /dev/null +++ b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/panel.py @@ -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) diff --git a/openstack_dashboard/dashboards/project/data_processing/data_image_registry/tables.py b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/tables.py new file mode 100644 index 0000000000..b037c3b58f --- /dev/null +++ b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/tables.py @@ -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,) diff --git a/openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/_edit_tags.html b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/_edit_tags.html new file mode 100644 index 0000000000..4796cea47d --- /dev/null +++ b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/_edit_tags.html @@ -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 %} + + +
+
+ {% include "horizon/common/_form_fields.html" %} +
+ {% include 'project/data_processing.data_image_registry/_tag_form.html' %} +
+
+ {% include 'project/data_processing.data_image_registry/_help.html' %} +
+{% endblock %} + +{% block modal-footer %} + + {% trans "Cancel" %} +{% endblock %} \ No newline at end of file diff --git a/openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/_help.html b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/_help.html new file mode 100644 index 0000000000..ddd0834a5f --- /dev/null +++ b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/_help.html @@ -0,0 +1,21 @@ +{% load i18n %} +
+

{% blocktrans %}Image Registry tool:{% endblocktrans %}

+
+

+ {% blocktrans %}Image Registry is used to provide additional information about images for Data Processing.{% endblocktrans %} +

+

+ {% blocktrans %}Specified User Name will be used by Data Processing to apply configs and manage processes on instances.{% endblocktrans %} +

+

+ {% 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 "Add all" button.{% endblocktrans %} +

+

+ {% blocktrans %}You may also add any custom tag.{% endblocktrans %} +

+

+ {% blocktrans %}Unnecessary tags may be removed by clicking a cross near tag's name.{% endblocktrans %} +

+
\ No newline at end of file diff --git a/openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/_list_tags.html b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/_list_tags.html new file mode 100644 index 0000000000..4359c9d732 --- /dev/null +++ b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/_list_tags.html @@ -0,0 +1,5 @@ +
    + {% for tag in image.tags %} +
  • {{ tag }}
  • + {% endfor %} +
\ No newline at end of file diff --git a/openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/_register_image.html b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/_register_image.html new file mode 100644 index 0000000000..2a58b00797 --- /dev/null +++ b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/_register_image.html @@ -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 %} +
+
+ {% include "horizon/common/_form_fields.html" %} + {% include 'project/data_processing.data_image_registry/_tag_form.html' %} +
+
+
+ {% include 'project/data_processing.data_image_registry/_help.html' %} +
+{% endblock %} + +{% block modal-footer %} + + {% trans "Cancel" %} +{% endblock %} \ No newline at end of file diff --git a/openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/_tag_form.html b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/_tag_form.html new file mode 100644 index 0000000000..09e13dc4db --- /dev/null +++ b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/_tag_form.html @@ -0,0 +1,119 @@ +{% load i18n %} +
+
+
+
{% trans "Register tags required for the Plugin with specified Data Processing Version" %}
+
+ + + + + +
+ + + + {% for plugin, version_dict in plugins.items %} +
+ + +
+ {% endfor %} +
+ +
+ +
+
+ + {% trans "Add custom tag" %} +
+ + diff --git a/openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/edit_tags.html b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/edit_tags.html new file mode 100644 index 0000000000..0667f03b2c --- /dev/null +++ b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/edit_tags.html @@ -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 %} \ No newline at end of file diff --git a/openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/image_registry.html b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/image_registry.html new file mode 100644 index 0000000000..2e6fedd8b3 --- /dev/null +++ b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/image_registry.html @@ -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 %} + +
+ {{ image_registry_table.render }} +
+ + +{% endblock %} \ No newline at end of file diff --git a/openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/register_image.html b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/register_image.html new file mode 100644 index 0000000000..788f5b741b --- /dev/null +++ b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/templates/data_processing.data_image_registry/register_image.html @@ -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 %} \ No newline at end of file diff --git a/openstack_dashboard/dashboards/project/data_processing/data_image_registry/urls.py b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/urls.py new file mode 100644 index 0000000000..f53e91878c --- /dev/null +++ b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/urls.py @@ -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[^/]+)/$', + views.EditTagsView.as_view(), + name='edit_tags'), + url(r'^register/$', + views.RegisterImageView.as_view(), + name='register'), + ) diff --git a/openstack_dashboard/dashboards/project/data_processing/data_image_registry/views.py b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/views.py new file mode 100644 index 0000000000..b4985a4822 --- /dev/null +++ b/openstack_dashboard/dashboards/project/data_processing/data_image_registry/views.py @@ -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([])}