diff --git a/openstack_dashboard/dashboards/project/dashboard.py b/openstack_dashboard/dashboards/project/dashboard.py
index 28ca1091ed..ee50e5c382 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" %}
+
+
+
+
+ {% trans "Plugin" %}
+
+ {% for plugin, version_dict in plugins.items %}
+ {{ plugin }}
+ {% endfor %}
+
+
+
+ {% for plugin, version_dict in plugins.items %}
+
+ {% trans "Version" %}
+
+ {% for version, tags in version_dict.items %}
+ {{ version }}
+ {% endfor %}
+
+
+ {% endfor %}
+
+
+
+
+
+
+
+
+
+
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([])}