diff --git a/openstack_dashboard/dashboards/project/dashboard.py b/openstack_dashboard/dashboards/project/dashboard.py
index 1f0fbe0225..c3b45a3b42 100644
--- a/openstack_dashboard/dashboards/project/dashboard.py
+++ b/openstack_dashboard/dashboards/project/dashboard.py
@@ -65,7 +65,8 @@ class DataProcessingPanels(horizon.PanelGroup):
'data_processing.nodegroup_templates',
'data_processing.cluster_templates',
'data_processing.clusters',
- 'data_processing.data_sources', )
+ 'data_processing.data_sources',
+ 'data_processing.job_binaries', )
class Project(horizon.Dashboard):
diff --git a/openstack_dashboard/dashboards/project/data_processing/job_binaries/__init__.py b/openstack_dashboard/dashboards/project/data_processing/job_binaries/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/openstack_dashboard/dashboards/project/data_processing/job_binaries/forms.py b/openstack_dashboard/dashboards/project/data_processing/job_binaries/forms.py
new file mode 100644
index 0000000000..050744976d
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/data_processing/job_binaries/forms.py
@@ -0,0 +1,197 @@
+# 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
+import uuid
+
+from django.forms import util
+from django.forms import widgets
+from django import template
+from django.template import defaultfilters
+from django.utils.encoding import force_unicode
+from django.utils.safestring import mark_safe
+from django.utils.translation import ugettext_lazy as _
+
+from horizon import exceptions
+from horizon import forms
+from horizon import messages
+
+from openstack_dashboard.api import sahara as saharaclient
+
+LOG = logging.getLogger(__name__)
+
+
+class LabeledInput(widgets.Input):
+ def render(self, name, values, attrs=None):
+ final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
+ output = "%s %s" %\
+ ("id_%s_label" % name,
+ "internal-db://",
+ (' ' % util.flatatt(final_attrs)))
+ return mark_safe(output)
+
+
+class JobBinaryCreateForm(forms.SelfHandlingForm):
+ NEW_SCRIPT = "%%%NEWSCRIPT%%%"
+ UPLOAD_BIN = "%%%UPLOADFILE%%%"
+
+ job_binary_name = forms.CharField(label=_("Name"))
+
+ job_binary_type = forms.ChoiceField(label=_("Storage type"))
+
+ job_binary_url = forms.CharField(label=_("URL"),
+ required=False,
+ widget=LabeledInput())
+ job_binary_internal = forms.ChoiceField(label=_("Internal binary"),
+ required=False)
+
+ job_binary_file = forms.FileField(label=_("Upload File"),
+ required=False)
+
+ job_binary_script_name = forms.CharField(label=_("Script name"),
+ required=False)
+
+ job_binary_script = forms.CharField(label=_("Script text"),
+ required=False,
+ widget=forms.Textarea())
+
+ job_binary_username = forms.CharField(label=_("Username"),
+ required=False)
+
+ job_binary_password = forms.CharField(label=_("Password"),
+ required=False,
+ widget=forms.PasswordInput(
+ attrs={'autocomplete': 'off'}))
+
+ job_binary_description = forms.CharField(label=_("Description"),
+ required=False,
+ widget=forms.Textarea())
+
+ def __init__(self, request, *args, **kwargs):
+ super(JobBinaryCreateForm, self).__init__(request, *args, **kwargs)
+
+ self.help_text_template = ("project/data_processing.job_binaries/"
+ "_create_job_binary_help.html")
+
+ self.fields["job_binary_type"].choices =\
+ [("internal-db", "Internal database"),
+ ("swift", "Swift")]
+
+ self.fields["job_binary_internal"].choices =\
+ self.populate_job_binary_internal_choices(request)
+
+ def populate_job_binary_internal_choices(self, request):
+ try:
+ job_binaries = saharaclient.job_binary_internal_list(request)
+ except Exception:
+ exceptions.handle(request,
+ _("Failed to get list of internal binaries."))
+ job_binaries = []
+
+ choices = [(job_binary.id, job_binary.name)
+ for job_binary in job_binaries]
+ choices.insert(0, (self.NEW_SCRIPT, '*Create a script'))
+ choices.insert(0, (self.UPLOAD_BIN, '*Upload a new file'))
+
+ return choices
+
+ def handle(self, request, context):
+ try:
+ extra = {}
+ bin_url = "%s://%s" % (context["job_binary_type"],
+ context["job_binary_url"])
+ if(context["job_binary_type"] == "internal-db"):
+ bin_url = self.handle_internal(request, context)
+ elif(context["job_binary_type"] == "swift"):
+ extra = self.handle_swift(request, context)
+
+ saharaclient.job_binary_create(
+ request,
+ context["job_binary_name"],
+ bin_url,
+ context["job_binary_description"],
+ extra)
+ messages.success(request, "Successfully created job binary")
+ return True
+ except Exception:
+ exceptions.handle(request,
+ _("Unable to create job binary"))
+ return False
+
+ def get_help_text(self, extra_context=None):
+ text = ""
+ extra_context = extra_context or {}
+ if self.help_text_template:
+ tmpl = template.loader.get_template(self.help_text_template)
+ context = template.RequestContext(self.request, extra_context)
+ text += tmpl.render(context)
+ else:
+ text += defaultfilters.linebreaks(force_unicode(self.help_text))
+ return defaultfilters.safe(text)
+
+ class Meta:
+ name = _("Create Job Binary")
+ help_text_template = ("project/data_processing.job_binaries/"
+ "_create_job_binary_help.html")
+
+ def handle_internal(self, request, context):
+ result = ""
+
+ bin_id = context["job_binary_internal"]
+ if(bin_id == self.UPLOAD_BIN):
+ try:
+ result = saharaclient.job_binary_internal_create(
+ request,
+ self.get_unique_binary_name(
+ request, request.FILES["job_binary_file"].name),
+ request.FILES["job_binary_file"].read())
+ except Exception:
+ exceptions.handle(request,
+ _("Unable to upload job binary"))
+ return None
+ elif(bin_id == self.NEW_SCRIPT):
+ try:
+ result = saharaclient.job_binary_internal_create(
+ request,
+ self.get_unique_binary_name(
+ request, context["job_binary_script_name"]),
+ context["job_binary_script"])
+ except Exception:
+ exceptions.handle(request,
+ _("Unable to create job binary"))
+ return None
+
+ bin_id = result.id
+ return "internal-db://%s" % bin_id
+
+ def handle_swift(self, request, context):
+ username = context["job_binary_username"]
+ password = context["job_binary_password"]
+
+ extra = {
+ "user": username,
+ "password": password
+ }
+ return extra
+
+ def get_unique_binary_name(self, request, base_name):
+ try:
+ internals = saharaclient.job_binary_internal_list(request)
+ except Exception:
+ internals = []
+ exceptions.handle(request,
+ _("Failed to fetch internal binary list"))
+ names = [internal.name for internal in internals]
+ if base_name in names:
+ return "%s_%s" % (base_name, uuid.uuid1())
+ return base_name
diff --git a/openstack_dashboard/dashboards/project/data_processing/job_binaries/panel.py b/openstack_dashboard/dashboards/project/data_processing/job_binaries/panel.py
new file mode 100644
index 0000000000..bdbec37d1c
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/data_processing/job_binaries/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 JobBinariesPanel(horizon.Panel):
+ name = _("Job Binaries")
+ slug = 'data_processing.job_binaries'
+ permissions = ('openstack.services.data_processing',)
+
+
+dashboard.Project.register(JobBinariesPanel)
diff --git a/openstack_dashboard/dashboards/project/data_processing/job_binaries/tables.py b/openstack_dashboard/dashboards/project/data_processing/job_binaries/tables.py
new file mode 100644
index 0000000000..449ae47c1c
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/data_processing/job_binaries/tables.py
@@ -0,0 +1,79 @@
+# 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
+
+from saharaclient.api import base as api_base
+
+
+LOG = logging.getLogger(__name__)
+
+
+class CreateJobBinary(tables.LinkAction):
+ name = "create job binary"
+ verbose_name = _("Create Job Binary")
+ url = "horizon:project:data_processing.job_binaries:create-job-binary"
+ classes = ("btn-launch", "ajax-modal")
+
+
+class DeleteJobBinary(tables.BatchAction):
+ name = "delete"
+ action_present = _("Delete")
+ action_past = _("Deleted")
+ data_type_singular = _("Job binary")
+ data_type_plural = _("Job binaries")
+ classes = ('btn-danger', 'btn-terminate')
+
+ def action(self, request, obj_id):
+ jb = saharaclient.job_binary_get(request, obj_id)
+ (jb_type, jb_internal_id) = jb.url.split("://")
+ if jb_type == "internal-db":
+ try:
+ saharaclient.job_binary_internal_delete(request,
+ jb_internal_id)
+ except api_base.APIException:
+ # nothing to do for job-binary-internal if
+ # it does not exist.
+ pass
+
+ saharaclient.job_binary_delete(request, obj_id)
+
+
+class DownloadJobBinary(tables.LinkAction):
+ name = "download job binary"
+ verbose_name = _("Download Job Binary")
+ url = "horizon:project:data_processing.job_binaries:download"
+ classes = ("btn-edit")
+
+
+class JobBinariesTable(tables.DataTable):
+ name = tables.Column("name",
+ verbose_name=_("Name"),
+ link=("horizon:project:data_processing.job_binaries:details"))
+ type = tables.Column("url",
+ verbose_name=_("Url"))
+ description = tables.Column("description",
+ verbose_name=_("Description"))
+
+ class Meta:
+ name = "job_binaries"
+ verbose_name = _("Job Binaries")
+ table_actions = (CreateJobBinary,
+ DeleteJobBinary)
+ row_actions = (DeleteJobBinary, DownloadJobBinary)
diff --git a/openstack_dashboard/dashboards/project/data_processing/job_binaries/tabs.py b/openstack_dashboard/dashboards/project/data_processing/job_binaries/tabs.py
new file mode 100644
index 0000000000..5ee175b689
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/data_processing/job_binaries/tabs.py
@@ -0,0 +1,45 @@
+# 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 sahara as saharaclient
+
+LOG = logging.getLogger(__name__)
+
+
+class JobBinaryDetailsTab(tabs.Tab):
+ name = _("General Info")
+ slug = "job_binaries_details_tab"
+ template_name = ("project/data_processing.job_binaries/_details.html")
+
+ def get_context_data(self, request):
+ job_binary_id = self.tab_group.kwargs['job_binary_id']
+ try:
+ job_binary = saharaclient.job_binary_get(request, job_binary_id)
+ except Exception:
+ job_binary = {}
+ exceptions.handle(request,
+ _("Unable to fetch job binary."))
+ return {"job_binary": job_binary}
+
+
+class JobBinaryDetailsTabs(tabs.TabGroup):
+ slug = "job_binary_details"
+ tabs = (JobBinaryDetailsTab,)
+ sticky = True
diff --git a/openstack_dashboard/dashboards/project/data_processing/job_binaries/templates/data_processing.job_binaries/_create.html b/openstack_dashboard/dashboards/project/data_processing/job_binaries/templates/data_processing.job_binaries/_create.html
new file mode 100644
index 0000000000..658dbbe92d
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/data_processing/job_binaries/templates/data_processing.job_binaries/_create.html
@@ -0,0 +1,27 @@
+{% extends "horizon/common/_modal_form.html" %}
+
+{% load url from future %}
+
+{% load i18n %}
+
+{% block form_id %}create-job-binary{% endblock %}
+{% block form_action %}{% url 'horizon:project:data_processing.job_binaries:create-job-binary' %}{% endblock %}
+{% block form_attrs %}enctype="multipart/form-data"{% endblock %}
+
+{% block modal-header %}{% trans "Create Job Binary" %}{% endblock %}
+
+{% block modal-body %}
+
+
+ {% include "horizon/common/_form_fields.html" %}
+
+
+
+ {{ form.get_help_text }}
+
+{% endblock %}
+
+{% block modal-footer %}
+
+ {% trans "Cancel" %}
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/data_processing/job_binaries/templates/data_processing.job_binaries/_create_job_binary_help.html b/openstack_dashboard/dashboards/project/data_processing/job_binaries/templates/data_processing.job_binaries/_create_job_binary_help.html
new file mode 100644
index 0000000000..39be0de1c3
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/data_processing/job_binaries/templates/data_processing.job_binaries/_create_job_binary_help.html
@@ -0,0 +1,32 @@
+{% load i18n horizon %}
+
+
+ {% blocktrans %}Important : The name that you give your job binary will be the name used in your job execution.
+ If your binary requires a particular name or extension (ie: ".jar"), be sure to include it here.{% endblocktrans %}
+
+
+ {% blocktrans %}Select the storage type for your job binary.{% endblocktrans %}
+
+ {% blocktrans %}Data Processing internal database{% endblocktrans %}
+ {% blocktrans %}Swift{% endblocktrans %}
+
+
+
+ {% blocktrans %}For Data Processing internal job binaries, you may choose from the following:{% endblocktrans %}
+
+ {% blocktrans %}Choose an existing file{% endblocktrans %}
+ {% blocktrans %}Upload a new file{% endblocktrans %}
+ {% blocktrans %}Create a script to be uploaded dynamically{% endblocktrans %}
+
+
+
+ {% blocktrans %}For Object Store job binaries, you must:{% endblocktrans %}
+
+ {% blocktrans %}Enter the URL for the file{% endblocktrans %}
+ {% blocktrans %}Enter the username and password required to access that file{% endblocktrans %}
+
+
+
+ {% blocktrans %}You may also enter an optional description for your job binary.{% endblocktrans %}
+
+
\ No newline at end of file
diff --git a/openstack_dashboard/dashboards/project/data_processing/job_binaries/templates/data_processing.job_binaries/_details.html b/openstack_dashboard/dashboards/project/data_processing/job_binaries/templates/data_processing.job_binaries/_details.html
new file mode 100644
index 0000000000..e942cd3dcc
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/data_processing/job_binaries/templates/data_processing.job_binaries/_details.html
@@ -0,0 +1,19 @@
+{% load i18n %}
+{% trans "Job Binary Overview" %}
+
+
+ {% trans "Name" %}
+ {{ job_binary.name }}
+ {% trans "ID" %}
+ {{ job_binary.id }}
+ {% trans "URL" %}
+ {{ job_binary.url }}
+ {% trans "Description" %}
+ {{ job_binary.description|default:_("None") }}
+ {% trans "Project id" %}
+ {{ job_binary.tenant_id }}
+ {% trans "Create time" %}
+ {{ job_binary.created_at }}
+
+
{% trans "Download job binary" %}
+
diff --git a/openstack_dashboard/dashboards/project/data_processing/job_binaries/templates/data_processing.job_binaries/create.html b/openstack_dashboard/dashboards/project/data_processing/job_binaries/templates/data_processing.job_binaries/create.html
new file mode 100644
index 0000000000..af1b14c581
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/data_processing/job_binaries/templates/data_processing.job_binaries/create.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Create Job Binary" %}{% endblock %}
+
+{% block page_header %}
+ {% include "horizon/common/_page_header.html" with title=_("Create Job Binary") %}
+{% endblock page_header %}
+
+{% block main %}
+ {% include 'project/data_processing.job_binaries/_create.html' %}
+{% endblock %}
\ No newline at end of file
diff --git a/openstack_dashboard/dashboards/project/data_processing/job_binaries/templates/data_processing.job_binaries/details.html b/openstack_dashboard/dashboards/project/data_processing/job_binaries/templates/data_processing.job_binaries/details.html
new file mode 100644
index 0000000000..c4e61589be
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/data_processing/job_binaries/templates/data_processing.job_binaries/details.html
@@ -0,0 +1,15 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Job Binary Details" %}{% endblock %}
+
+{% block page_header %}
+ {% include "horizon/common/_page_header.html" with title=_("Job Binary Details") %}
+{% endblock page_header %}
+
+{% block main %}
+
+
+ {{ tab_group.render }}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/openstack_dashboard/dashboards/project/data_processing/job_binaries/templates/data_processing.job_binaries/job_binaries.html b/openstack_dashboard/dashboards/project/data_processing/job_binaries/templates/data_processing.job_binaries/job_binaries.html
new file mode 100644
index 0000000000..686060c664
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/data_processing/job_binaries/templates/data_processing.job_binaries/job_binaries.html
@@ -0,0 +1,25 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Data Processing" %}{% endblock %}
+
+{% block page_header %}
+ {% include "horizon/common/_page_header.html" with title=_("Job Binaries") %}
+{% endblock page_header %}
+
+{% block main %}
+
+
+
+ {{ job_binaries_table.render }}
+
+
+{% include "project/data_processing.job_binaries/job_binaries_form_script.html" %}
+
+{% endblock %}
diff --git a/openstack_dashboard/dashboards/project/data_processing/job_binaries/templates/data_processing.job_binaries/job_binaries_form_script.html b/openstack_dashboard/dashboards/project/data_processing/job_binaries/templates/data_processing.job_binaries/job_binaries_form_script.html
new file mode 100644
index 0000000000..89a7a33a99
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/data_processing/job_binaries/templates/data_processing.job_binaries/job_binaries_form_script.html
@@ -0,0 +1,74 @@
+
+
diff --git a/openstack_dashboard/dashboards/project/data_processing/job_binaries/tests.py b/openstack_dashboard/dashboards/project/data_processing/job_binaries/tests.py
new file mode 100644
index 0000000000..2a997656a2
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/data_processing/job_binaries/tests.py
@@ -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.
+
+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.job_binaries:index')
+DETAILS_URL = reverse(
+ 'horizon:project:data_processing.job_binaries:details', args=['id'])
+
+
+class DataProcessingJobBinaryTests(test.TestCase):
+ @test.create_stubs({api.sahara: ('job_binary_list',)})
+ def test_index(self):
+ api.sahara.job_binary_list(IsA(http.HttpRequest)) \
+ .AndReturn(self.job_binaries.list())
+ self.mox.ReplayAll()
+ res = self.client.get(INDEX_URL)
+ self.assertTemplateUsed(res,
+ 'project/data_processing.job_binaries/job_binaries.html')
+ self.assertContains(res, 'Job Binaries')
+ self.assertContains(res, 'Name')
+ self.assertContains(res, 'example.pig')
+
+ @test.create_stubs({api.sahara: ('job_binary_get',)})
+ def test_details(self):
+ api.sahara.job_binary_get(IsA(http.HttpRequest), IsA(unicode)) \
+ .AndReturn(self.job_binaries.list()[0])
+ self.mox.ReplayAll()
+ res = self.client.get(DETAILS_URL)
+ self.assertTemplateUsed(res,
+ 'project/data_processing.job_binaries/details.html')
+ self.assertContains(res, 'Job Binary Details')
+
+ @test.create_stubs({api.sahara: ('job_binary_list',
+ 'job_binary_get',
+ 'job_binary_internal_delete',
+ 'job_binary_delete',)})
+ def test_delete(self):
+ jb_list = (api.sahara.job_binary_list(IsA(http.HttpRequest))
+ .AndReturn(self.job_binaries.list()))
+ api.sahara.job_binary_get(IsA(http.HttpRequest), IsA(unicode)) \
+ .AndReturn(self.job_binaries.list()[0])
+ api.sahara.job_binary_delete(IsA(http.HttpRequest), jb_list[0].id)
+ int_id = jb_list[0].url.split("//")[1]
+ api.sahara.job_binary_internal_delete(IsA(http.HttpRequest), int_id)
+ self.mox.ReplayAll()
+ form_data = {"action": "job_binaries__delete__%s" % jb_list[0].id}
+ res = self.client.post(INDEX_URL, form_data)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
+
+ @test.create_stubs({api.sahara: ('job_binary_get',
+ 'job_binary_get_file')})
+ def test_download(self):
+ jb = api.sahara.job_binary_get(IsA(http.HttpRequest), IsA(unicode)) \
+ .AndReturn(self.job_binaries.list()[0])
+ api.sahara.job_binary_get_file(IsA(http.HttpRequest), jb.id) \
+ .AndReturn("TEST FILE CONTENT")
+ self.mox.ReplayAll()
+
+ context = {'job_binary_id': jb.id}
+ url = reverse('horizon:project:data_processing.job_binaries:download',
+ kwargs={'job_binary_id': jb.id})
+ res = self.client.get(url, context)
+ self.assertTrue(res.has_header('content-disposition'))
diff --git a/openstack_dashboard/dashboards/project/data_processing/job_binaries/urls.py b/openstack_dashboard/dashboards/project/data_processing/job_binaries/urls.py
new file mode 100644
index 0000000000..0b29ba82b1
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/data_processing/job_binaries/urls.py
@@ -0,0 +1,35 @@
+# 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. \
+ job_binaries.views as views
+
+
+urlpatterns = patterns('',
+ url(r'^$', views.JobBinariesView.as_view(),
+ name='index'),
+ url(r'^$', views.JobBinariesView.as_view(),
+ name='job-binaries'),
+ url(r'^create-job-binary$',
+ views.CreateJobBinaryView.as_view(),
+ name='create-job-binary'),
+ url(r'^(?P[^/]+)$',
+ views.JobBinaryDetailsView.as_view(),
+ name='details'),
+ url(r'^(?P[^/]+)/download/$',
+ views.DownloadJobBinaryView.as_view(),
+ name='download'))
diff --git a/openstack_dashboard/dashboards/project/data_processing/job_binaries/views.py b/openstack_dashboard/dashboards/project/data_processing/job_binaries/views.py
new file mode 100644
index 0000000000..44917cb301
--- /dev/null
+++ b/openstack_dashboard/dashboards/project/data_processing/job_binaries/views.py
@@ -0,0 +1,93 @@
+# 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
+from django.core.urlresolvers import reverse_lazy
+from django import http
+from django.template import defaultfilters
+from django.utils.translation import ugettext_lazy as _
+from django.views import generic
+
+from horizon import exceptions
+from horizon import forms
+from horizon import tables
+from horizon import tabs
+
+from openstack_dashboard.api import sahara as saharaclient
+
+import openstack_dashboard.dashboards.project.data_processing. \
+ job_binaries.forms as job_binary_forms
+from openstack_dashboard.dashboards.project.data_processing.job_binaries \
+ import tables as jb_tables
+import openstack_dashboard.dashboards.project.data_processing.job_binaries. \
+ tabs as _tabs
+
+
+LOG = logging.getLogger(__name__)
+
+
+class JobBinariesView(tables.DataTableView):
+ table_class = jb_tables.JobBinariesTable
+ template_name = 'project/data_processing.job_binaries/job_binaries.html'
+
+ def get_data(self):
+ try:
+ job_binaries = saharaclient.job_binary_list(self.request)
+ except Exception:
+ job_binaries = []
+ exceptions.handle(self.request,
+ _("Unable to fetch job binary list."))
+ return job_binaries
+
+
+class CreateJobBinaryView(forms.ModalFormView):
+ form_class = job_binary_forms.JobBinaryCreateForm
+ success_url = reverse_lazy(
+ 'horizon:project:data_processing.job_binaries:index')
+ classes = ("ajax-modal")
+ template_name = "project/data_processing.job_binaries/create.html"
+
+
+class JobBinaryDetailsView(tabs.TabView):
+ tab_group_class = _tabs.JobBinaryDetailsTabs
+ template_name = 'project/data_processing.job_binaries/details.html'
+
+ def get_context_data(self, **kwargs):
+ context = super(JobBinaryDetailsView, self)\
+ .get_context_data(**kwargs)
+ return context
+
+ def get_data(self):
+ pass
+
+
+class DownloadJobBinaryView(generic.View):
+ def get(self, request, job_binary_id=None):
+ try:
+ jb = saharaclient.job_binary_get(request, job_binary_id)
+ data = saharaclient.job_binary_get_file(request, job_binary_id)
+ except Exception:
+ redirect = reverse(
+ 'horizon:project:data_processing.job_binaries:index')
+ exceptions.handle(self.request,
+ _('Unable to fetch job binary: %(exc)s'),
+ redirect=redirect)
+
+ response = http.HttpResponse(mimetype='application/binary')
+ response['Content-Disposition'] = \
+ 'attachment; filename=%s' % defaultfilters.slugify(jb.name)
+ response.write(data)
+ response['Content-Length'] = str(len(data))
+ return response
diff --git a/openstack_dashboard/test/test_data/sahara_data.py b/openstack_dashboard/test/test_data/sahara_data.py
index 97bb085f20..0455a30442 100644
--- a/openstack_dashboard/test/test_data/sahara_data.py
+++ b/openstack_dashboard/test/test_data/sahara_data.py
@@ -15,6 +15,7 @@ from openstack_dashboard.test.test_data import utils
from saharaclient.api import cluster_templates
from saharaclient.api import clusters
from saharaclient.api import data_sources
+from saharaclient.api import job_binaries
from saharaclient.api import node_group_templates
from saharaclient.api import plugins
@@ -25,6 +26,7 @@ def data(TEST):
TEST.cluster_templates = utils.TestDataContainer()
TEST.clusters = utils.TestDataContainer()
TEST.data_sources = utils.TestDataContainer()
+ TEST.job_binaries = utils.TestDataContainer()
plugin1_dict = {
"description": "vanilla plugin",
@@ -261,3 +263,18 @@ def data(TEST):
data_sources.DataSourceManager(None), data_source2_dict)
TEST.data_sources.add(data_source1)
TEST.data_sources.add(data_source2)
+
+ #Job Binaries
+ job_binary1_dict = {
+ "created_at": "2014-06-05 18:15:15.581285",
+ "description": "",
+ "id": "3f3a07ac-7d6f-49e8-8669-40b25ee891b7",
+ "name": "example.pig",
+ "tenant_id": "429ad8447c2d47bc8e0382d244e1d1df",
+ "updated_at": None,
+ "url": "internal-db://80121dea-f8bd-4ad3-bcc7-096f4bfc722d"
+ }
+
+ job_binary1 = job_binaries.JobBinaries(
+ job_binaries.JobBinariesManager(None), job_binary1_dict)
+ TEST.job_binaries.add(job_binary1)