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:
parent
21ef43d48e
commit
8fa78b9d9c
|
@ -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)
|
||||
|
|
|
@ -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,)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -0,0 +1,7 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Update Plugin" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'horizon/common/_workflow.html' %}
|
||||
{% endblock %}
|
|
@ -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)
|
||||
|
|
|
@ -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'),
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue