diff --git a/sahara_dashboard/api/sahara.py b/sahara_dashboard/api/sahara.py index af5b3ec2..28a74369 100644 --- a/sahara_dashboard/api/sahara.py +++ b/sahara_dashboard/api/sahara.py @@ -558,3 +558,7 @@ def job_types_list(request): def verification_update(request, cluster_id, status): return client(request).clusters.verification_update(cluster_id, status) + + +def plugin_update(request, plugin_name, values): + return client(request).plugins.update(plugin_name, values) diff --git a/sahara_dashboard/content/data_processing/data_plugins/tables.py b/sahara_dashboard/content/data_processing/data_plugins/tables.py index d1d17707..258bc0c2 100644 --- a/sahara_dashboard/content/data_processing/data_plugins/tables.py +++ b/sahara_dashboard/content/data_processing/data_plugins/tables.py @@ -16,6 +16,16 @@ from django.utils.translation import ugettext_lazy as _ from horizon import tables +from sahara_dashboard.content.data_processing.utils \ + import workflow_helpers as w_helpers + + +class UpdatePluginAction(tables.LinkAction): + name = "update_plugin" + verbose_name = _("Update Plugin") + url = "horizon:project:data_processing.data_plugins:update" + classes = ("ajax-modal", "btn-edit") + class PluginsTable(tables.DataTable): title = tables.Column("title", @@ -23,8 +33,8 @@ class PluginsTable(tables.DataTable): link=("horizon:project:data_processing." "data_plugins:plugin-details")) - versions = tables.Column("versions", - verbose_name=_("Supported Versions"), + versions = tables.Column(w_helpers.get_pretty_enabled_versions, + verbose_name=_("Enabled Versions"), wrap_list=True, filters=(filters.unordered_list,)) @@ -34,3 +44,5 @@ class PluginsTable(tables.DataTable): class Meta(object): name = "plugins" verbose_name = _("Plugins") + + row_actions = (UpdatePluginAction,) diff --git a/sahara_dashboard/content/data_processing/data_plugins/tabs.py b/sahara_dashboard/content/data_processing/data_plugins/tabs.py index 2c29570b..058d6772 100644 --- a/sahara_dashboard/content/data_processing/data_plugins/tabs.py +++ b/sahara_dashboard/content/data_processing/data_plugins/tabs.py @@ -14,6 +14,7 @@ import logging from django.utils.translation import ugettext_lazy as _ +import six from horizon import exceptions from horizon import tabs @@ -48,6 +49,10 @@ class DetailsTab(tabs.Tab): slug = "plugin_details_tab" template_name = "data_plugins/_details.html" + def _generate_context(self, plugin): + if not plugin: + return {'plugin': plugin} + def get_context_data(self, request): plugin_id = self.tab_group.kwargs['plugin_id'] plugin = None @@ -61,7 +66,52 @@ class DetailsTab(tabs.Tab): return {"plugin": plugin} +class LabelsTab(tabs.Tab): + name = _("Label details") + slug = "label_details_tab" + template_name = "data_plugins/_label_details.html" + + def _label_color(self, label): + color = 'info' + if label == 'deprecated': + color = 'danger' + elif label == 'stable': + color = 'success' + return color + + def get_context_data(self, request, **kwargs): + plugin_id = self.tab_group.kwargs['plugin_id'] + plugin = None + try: + plugin = saharaclient.plugin_get(request, plugin_id) + except Exception as e: + LOG.error("Unable to get plugin with plugin_id %s (%s)" % + (plugin_id, str(e))) + exceptions.handle(self.tab_group.request, + _('Unable to retrieve plugin.')) + + labels = [] + for label, data in six.iteritems(plugin.plugin_labels): + labels.append( + {'name': label, + 'color': self._label_color(label), + 'description': data.get('description', _("No description")), + 'scope': _("Plugin"), 'status': data.get('status', False)}) + + for version, version_data in six.iteritems(plugin.version_labels): + for label, data in six.iteritems(version_data): + labels.append( + {'name': label, + 'color': self._label_color(label), + 'description': data.get('description', + _("No description")), + 'scope': _("Plugin version %s") % version, + 'status': data.get('status', False)}) + + return {"labels": labels} + + class PluginDetailsTabs(tabs.TabGroup): slug = "cluster_details" - tabs = (DetailsTab,) + tabs = (DetailsTab, LabelsTab) sticky = True diff --git a/sahara_dashboard/content/data_processing/data_plugins/templates/data_plugins/_label_details.html b/sahara_dashboard/content/data_processing/data_plugins/templates/data_plugins/_label_details.html new file mode 100644 index 00000000..5d4d9378 --- /dev/null +++ b/sahara_dashboard/content/data_processing/data_plugins/templates/data_plugins/_label_details.html @@ -0,0 +1,27 @@ +{% load i18n %} + +

{% trans "Cluster health checks" %}

+ + + + + + + + + + + {% for label in labels %} + + + + + + + {% endfor %} + +
{% trans "Label" %}{% trans "Scope" %}{% trans "Checked?" %}{% trans "Description" %}
+ + {{ label.name }} + + {{ label.scope }}{{ label.status|yesno }}{{ label.description }}
diff --git a/sahara_dashboard/content/data_processing/data_plugins/templates/data_plugins/update.html b/sahara_dashboard/content/data_processing/data_plugins/templates/data_plugins/update.html new file mode 100644 index 00000000..da4f846d --- /dev/null +++ b/sahara_dashboard/content/data_processing/data_plugins/templates/data_plugins/update.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Update Plugin" %}{% endblock %} + +{% block main %} + {% include 'horizon/common/_workflow.html' %} +{% endblock %} \ No newline at end of file diff --git a/sahara_dashboard/content/data_processing/data_plugins/tests.py b/sahara_dashboard/content/data_processing/data_plugins/tests.py index fc18477b..0c5c86a9 100644 --- a/sahara_dashboard/content/data_processing/data_plugins/tests.py +++ b/sahara_dashboard/content/data_processing/data_plugins/tests.py @@ -38,7 +38,7 @@ class DataProcessingPluginsTests(test.TestCase): @test.create_stubs({api.sahara: ('plugin_get',)}) def test_details(self): - api.sahara.plugin_get(IsA(http.HttpRequest), IsA(six.text_type)) \ + api.sahara.plugin_get(IsA(http.HttpRequest), IsA(six.text_type)).MultipleTimes() \ .AndReturn(self.plugins.list()[0]) self.mox.ReplayAll() res = self.client.get(DETAILS_URL) diff --git a/sahara_dashboard/content/data_processing/data_plugins/urls.py b/sahara_dashboard/content/data_processing/data_plugins/urls.py index 82073913..6aeb2949 100644 --- a/sahara_dashboard/content/data_processing/data_plugins/urls.py +++ b/sahara_dashboard/content/data_processing/data_plugins/urls.py @@ -20,4 +20,7 @@ urlpatterns = [ url(r'^$', views.PluginsView.as_view(), name='index'), url(r'^(?P[^/]+)$', views.PluginDetailsView.as_view(), name='plugin-details'), + url(r'^(?P[^/]+)/update', + views.UpdatePluginView.as_view(), + name='update'), ] diff --git a/sahara_dashboard/content/data_processing/data_plugins/views.py b/sahara_dashboard/content/data_processing/data_plugins/views.py index 9e2a7796..e9a14a48 100644 --- a/sahara_dashboard/content/data_processing/data_plugins/views.py +++ b/sahara_dashboard/content/data_processing/data_plugins/views.py @@ -16,12 +16,15 @@ from django.utils.translation import ugettext_lazy as _ from horizon import exceptions from horizon import tables from horizon import tabs +from horizon import workflows from sahara_dashboard.api import sahara as saharaclient import sahara_dashboard.content.data_processing.data_plugins. \ tables as p_tables import sahara_dashboard.content.data_processing.data_plugins. \ tabs as p_tabs +from sahara_dashboard.content.data_processing.data_plugins.workflows \ + import update class PluginsView(tables.DataTableView): @@ -43,3 +46,33 @@ class PluginDetailsView(tabs.TabView): tab_group_class = p_tabs.PluginDetailsTabs template_name = 'horizon/common/_detail.html' page_title = _("Data Processing Plugin Details") + + +class UpdatePluginView(workflows.WorkflowView): + workflow_class = update.UpdatePlugin + success_url = "horizon:project:data_processing.data_plugins" + classes = ("ajax-modal",) + template_name = "data_plugins/update.html" + page_title = _("Update Plugin") + + def get_context_data(self, **kwargs): + context = super(UpdatePluginView, self) \ + .get_context_data(**kwargs) + context["plugin_name"] = kwargs["plugin_name"] + return context + + def get_object(self, *args, **kwargs): + if not hasattr(self, "_object"): + plugin_name = self.kwargs['plugin_name'] + try: + plugin = saharaclient.plugin_get(self.request, plugin_name) + except Exception: + exceptions.handle(self.request, + _("Unable to fetch plugin object.")) + self._object = plugin + return self._object + + def get_initial(self): + initial = super(UpdatePluginView, self).get_initial() + initial['plugin_name'] = self.kwargs['plugin_name'] + return initial diff --git a/sahara_dashboard/content/data_processing/data_plugins/workflows/__init__.py b/sahara_dashboard/content/data_processing/data_plugins/workflows/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sahara_dashboard/content/data_processing/data_plugins/workflows/update.py b/sahara_dashboard/content/data_processing/data_plugins/workflows/update.py new file mode 100644 index 00000000..e50d2209 --- /dev/null +++ b/sahara_dashboard/content/data_processing/data_plugins/workflows/update.py @@ -0,0 +1,111 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from django.utils.translation import ugettext_lazy as _ +from saharaclient.api import base as api_base +import six + +from horizon import exceptions +from horizon import forms +from horizon import workflows + +from sahara_dashboard.api import sahara as saharaclient + + +class UpdateLabelsAction(workflows.Action): + def __init__(self, request, *args, **kwargs): + super(UpdateLabelsAction, self).__init__(request, *args, **kwargs) + plugin_name = [ + x['plugin_name'] for x in args if 'plugin_name' in x][0] + plugin = saharaclient.plugin_get(request, plugin_name) + self._serialize_labels( + 'plugin_', _("Plugin label"), plugin.plugin_labels) + vers_labels = plugin.version_labels + for version in vers_labels.keys(): + field_label = _("Plugin version %(version)s label") % { + 'version': version} + self._serialize_labels( + 'version_%s_' % version, field_label, vers_labels[version]) + self.fields["plugin_name"] = forms.CharField( + widget=forms.HiddenInput(), + initial=plugin_name) + + def _serialize_labels(self, prefix, prefix_trans, labels): + for name, label in six.iteritems(labels): + if not label['mutable']: + continue + res_name_translated = "%s: %s" % (prefix_trans, name) + res_name = "label_%s%s" % (prefix, name) + self.fields[res_name] = forms.BooleanField( + label=res_name_translated, + help_text=label['description'], + widget=forms.CheckboxInput(), + initial=label['status'], + required=False, + ) + + class Meta(object): + name = _("Plugin") + help_text = _("Update the plugin labels") + + +class UpdatePluginStep(workflows.Step): + action_class = UpdateLabelsAction + depends_on = ('plugin_name', ) + + def contribute(self, data, context): + for name, item in six.iteritems(data): + context[name] = item + return context + + +class UpdatePlugin(workflows.Workflow): + slug = "update_plugin" + name = _("Update Plugin") + success_message = _("Updated") + failure_message = _("Could not update plugin") + success_url = "horizon:project:data_processing.data_plugins:index" + default_steps = (UpdatePluginStep,) + + def __init__(self, request, context_seed, entry_point, *args, **kwargs): + super(UpdatePlugin, self).__init__( + request, context_seed, entry_point, *args, **kwargs) + + def _get_update_values(self, context): + values = {'plugin_labels': {}, 'version_labels': {}} + for item, item_value in six.iteritems(context): + if not item.startswith('label_'): + continue + name = item.split('_')[1:] + if name[0] == 'plugin': + values['plugin_labels'][name[1]] = {'status': item_value} + else: + if name[1] not in values['version_labels']: + values['version_labels'][name[1]] = {} + values['version_labels'][ + name[1]][name[2]] = {'status': item_value} + return values + + def handle(self, request, context): + try: + update_values = self._get_update_values(context) + saharaclient.plugin_update( + request, context['plugin_name'], update_values) + return True + except api_base.APIException as e: + self.error_description = str(e) + return False + except Exception: + exceptions.handle(request, + _("Plugin update failed.")) + return False diff --git a/sahara_dashboard/content/data_processing/utils/workflow_helpers.py b/sahara_dashboard/content/data_processing/utils/workflow_helpers.py index d566caef..1b549c1f 100644 --- a/sahara_dashboard/content/data_processing/utils/workflow_helpers.py +++ b/sahara_dashboard/content/data_processing/utils/workflow_helpers.py @@ -25,6 +25,7 @@ from openstack_dashboard.api import network from sahara_dashboard.api import sahara as saharaclient + LOG = logging.getLogger(__name__) @@ -249,6 +250,7 @@ def populate_image_choices(self, request, context, empty_choice=False): class PluginAndVersionMixin(object): def _generate_plugin_version_fields(self, sahara): plugins = sahara.plugins.list() + plugins = filter(is_plugin_not_hidden_for_user, plugins) plugin_choices = [(plugin.name, plugin.title) for plugin in plugins] self.fields["plugin_name"] = forms.ChoiceField( @@ -262,7 +264,8 @@ class PluginAndVersionMixin(object): field_name = plugin.name + "_version" choice_field = forms.ChoiceField( label=_("Version"), - choices=[(version, version) for version in plugin.versions], + choices=[(version, version) + for version in get_enabled_versions(plugin)], widget=forms.Select( attrs={"class": "plugin_version_choice switched " + field_name + "_choice", @@ -417,3 +420,33 @@ class MultipleShareChoiceField(forms.MultipleChoiceField): raise ValidationError( _("The value of shares must be a list of values") ) + + +def is_plugin_not_hidden_for_user(plugin): + hidden_lbl = plugin.plugin_labels.get('hidden') + if hidden_lbl and hidden_lbl['status']: + return False + if get_enabled_versions(plugin): + return True + return False + + +def get_enabled_versions(plugin): + lbs = plugin.version_labels + + versions = [] + for version, data in six.iteritems(lbs): + if data.get('enabled', {'status': True}).get('status', True): + versions.append(version) + + if not plugin.plugin_labels.get( + 'enabled', {'status': True}).get('status', True): + versions = [] + return versions + + +def get_pretty_enabled_versions(plugin): + versions = get_enabled_versions(plugin) + if len(versions) == 0: + versions = [_("No enabled versions")] + return versions diff --git a/sahara_dashboard/test/test_data/sahara_data.py b/sahara_dashboard/test/test_data/sahara_data.py index 3ff22180..8b6c0da6 100644 --- a/sahara_dashboard/test/test_data/sahara_data.py +++ b/sahara_dashboard/test/test_data/sahara_data.py @@ -42,7 +42,24 @@ def data(TEST): "description": "vanilla plugin", "name": "vanilla", "title": "Vanilla Apache Hadoop", - "versions": ["2.3.0", "1.2.1"] + "versions": ["2.3.0", "1.2.1"], + 'version_labels': { + '2.3.0': { + 'enabled': { + 'status': True + } + }, + '1.2.1': { + 'enabled': { + 'status': True + } + } + }, + 'plugin_labels': { + 'enabled': { + 'status': True + } + } } plugin1 = plugins.Plugin(plugins.PluginManager(None), plugin1_dict) @@ -95,7 +112,19 @@ def data(TEST): }, ], "title": "Vanilla Apache Hadoop", - "name": "vanilla" + "name": "vanilla", + 'version_labels': { + '1.2.1': { + 'enabled': { + 'status': True + } + } + }, + 'plugin_labels': { + 'enabled': { + 'status': True + } + } } TEST.plugins_configs.add(plugins.Plugin(plugins.PluginManager(None), diff --git a/tools/gate/integration/commons b/tools/gate/integration/commons index dda9d524..28bd051f 100644 --- a/tools/gate/integration/commons +++ b/tools/gate/integration/commons @@ -2,4 +2,6 @@ set -ex +export DEST=${DEST:-$BASE/new} +export DEVSTACK_DIR=${DEVSTACK_DIR:-$DEST/devstack} export SAHARA_DASHBOARD_SCREENSHOTS_DIR=/opt/stack/new/sahara-dashboard/.tox/py27integration/src/horizon/openstack_dashboard/test/integration_tests/integration_tests_screenshots diff --git a/tools/gate/integration/post_test_hook.sh b/tools/gate/integration/post_test_hook.sh index 47fe5fc7..86d00d6c 100755 --- a/tools/gate/integration/post_test_hook.sh +++ b/tools/gate/integration/post_test_hook.sh @@ -13,6 +13,22 @@ sudo apt-get -y purge firefox sudo dpkg -i firefox.deb sudo rm firefox.deb +cat >> /tmp/fake_config.json <