plugins api support

this change contains implementation:

1. of updating plugin workflow;
2. page with details of plugin labels;
3. only enabled versions are displayed on general
page with plugins.

blueprint: plugin-management-api
Change-Id: Ia125ad8108cafd4e39dc1c2c53f3c62ff3ce3e78
co-authored-by: Michael Lelyakin <mlelyakin@mirantis.com>
This commit is contained in:
Vitaly Gridnev 2016-07-01 16:16:17 +03:00
parent 21ef43d48e
commit 8fa78b9d9c
14 changed files with 334 additions and 7 deletions

View File

@ -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)

View File

@ -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,)

View File

@ -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

View File

@ -0,0 +1,27 @@
{% load i18n %}
<h4>{% trans "Cluster health checks" %}</h4>
<table id="labels_table" class="table table-bordered datatable">
<thead>
<tr>
<th>{% trans "Label" %}</th>
<th>{% trans "Scope" %}</th>
<th>{% trans "Checked?" %}</th>
<th>{% trans "Description" %}</th>
</tr>
</thead>
<tbody id="labels_body">
{% for label in labels %}
<tr>
<td>
<span class="label label-{{ label.color }}">
{{ label.name }}
</span>
</td>
<td>{{ label.scope }}</td>
<td>{{ label.status|yesno }}</td>
<td>{{ label.description }}</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Update Plugin" %}{% endblock %}
{% block main %}
{% include 'horizon/common/_workflow.html' %}
{% endblock %}

View File

@ -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)

View File

@ -20,4 +20,7 @@ urlpatterns = [
url(r'^$', views.PluginsView.as_view(), name='index'),
url(r'^(?P<plugin_id>[^/]+)$',
views.PluginDetailsView.as_view(), name='plugin-details'),
url(r'^(?P<plugin_name>[^/]+)/update',
views.UpdatePluginView.as_view(),
name='update'),
]

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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),

View File

@ -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

View File

@ -13,6 +13,22 @@ sudo apt-get -y purge firefox
sudo dpkg -i firefox.deb
sudo rm firefox.deb
cat >> /tmp/fake_config.json <<EOF
{
"plugin_labels": {
"hidden": {
"status": false
}
}
}
EOF
source $DEVSTACK_DIR/stackrc
source $DEVSTACK_DIR/openrc admin demo
openstack dataprocessing plugin update fake /tmp/fake_config.json
openstack dataprocessing plugin show fake
sudo -H -u stack tox -e py27integration
retval=$?
set -e