In case of several plugins, nailgun should use the newest

For example Fuel has 3 plugins:
- name: plugin_name, version: 1.0.0
- name: plugin_name, version: 2.0.0
- name: plugin_name, version: 0.1.0

The problem was, when user enables a plugin on UI,
Nailgun enables all of the installed plugins.

With this patch, after user creates environment he can
enable or disable only 2.0.0 version of plugin.

If after environment creation, new version of the plugin
is installed, user cannot use this new version, he
should create new environment in order to enable it.

Change-Id: I51ba03166cd2a7379c6f0912d668265bf88a316d
Closes-bug: #1395342
This commit is contained in:
Evgeniy L 2014-11-21 21:25:16 +04:00
parent e4c59607c8
commit 00dcb89989
7 changed files with 164 additions and 75 deletions

View File

@ -14,12 +14,14 @@
# License for the specific language governing permissions and limitations
# under the License.
from distutils.version import LooseVersion
from itertools import groupby
from nailgun.db import db
from nailgun.db.sqlalchemy.models import plugins as plugin_db_model
from nailgun.objects import base
from nailgun.objects.serializers import plugin
from nailgun.db import db
class Plugin(base.NailgunObject):
@ -35,3 +37,30 @@ class Plugin(base.NailgunObject):
class PluginCollection(base.NailgunCollection):
single = Plugin
@classmethod
def all_newest(cls):
"""Returns new plugins.
Example:
There are 4 plugins:
- name: plugin_name, version: 1.0.0
- name: plugin_name, version: 2.0.0
- name: plugin_name, version: 0.1.0
- name: plugin_another_name, version: 1.0.0
In this case the method returns 2 plugins:
- name: plugin_name, version: 2.0.0
- name: plugin_another_name, version: 1.0.0
:returns: list of Plugin models
"""
newest_plugins = []
grouped_by_name = groupby(cls.all(), lambda p: p.name)
for name, plugins in grouped_by_name:
newest_plugin = sorted(
plugins,
key=lambda p: LooseVersion(p.version),
reverse=True)[0]
newest_plugins.append(newest_plugin)
return newest_plugins

View File

@ -96,7 +96,13 @@ class ClusterAttributesPlugin(object):
enabled field.
"""
custom_attrs = cluster_attrs.get(self.plugin.name, {})
if custom_attrs:
# Skip if it's wrong plugin version
attr_plugin_version = custom_attrs['metadata']['plugin_version']
if attr_plugin_version != self.plugin.version:
return
enable = custom_attrs['metadata']['enabled']
# value is true and plugin is not enabled for this cluster
# that means plugin was enabled on this request
@ -119,7 +125,8 @@ class ClusterAttributesPlugin(object):
@property
def default_metadata(self):
return {u'enabled': False, u'toggleable': True,
u'weight': 70, u'label': self.plugin.title}
u'weight': 70, u'label': self.plugin.title,
'plugin_version': self.plugin.version}
def set_cluster_tasks(self, cluster):
"""Loads plugins provided tasks from tasks config file and

View File

@ -30,7 +30,7 @@ class PluginManager(object):
@classmethod
def get_plugin_attributes(cls, cluster):
plugins_attrs = {}
for plugin_db in PluginCollection.all():
for plugin_db in PluginCollection.all_newest():
attr_plugin = ClusterAttributesPlugin(plugin_db)
attrs = attr_plugin.get_plugin_attributes(cluster)
plugins_attrs.update(attrs)

View File

@ -438,6 +438,37 @@ class Environment(object):
return self.read_fixtures(
['openstack'])[0]['fields']['attributes_metadata']
def get_default_plugin_env_config(self, **kwargs):
return {
'attributes': {
'{0}_text'.format(kwargs.get('plugin_name', 'plugin_name')): {
'value': kwargs.get('value', 'value'),
'type': kwargs.get('type', 'type'),
'description': kwargs.get('description', 'description'),
'weight': kwargs.get('weight', 25),
'label': kwargs.get('label', 'label')}}}
def get_default_plugin_metadata(self, **kwargs):
sample_plugin = {
'version': '0.1.0',
'name': 'testing_plugin',
'title': 'Test plugin',
'package_version': '1.0.0',
'description': 'Enable to use plugin X for Neutron',
'fuel_version': ['6.0'],
'releases': [
{'repository_path': 'repositories/ubuntu',
'version': '2014.2-6.0', 'os': 'ubuntu',
'mode': ['ha', 'multinode'],
'deployment_scripts_path': 'deployment_scripts/'},
{'repository_path': 'repositories/centos',
'version': '2014.2-6.0', 'os': 'centos',
'mode': ['ha', 'multinode'],
'deployment_scripts_path': 'deployment_scripts/'}]}
sample_plugin.update(kwargs)
return sample_plugin
def upload_fixtures(self, fxtr_names):
for fxtr_path in self.fxtr_paths_by_names(fxtr_names):
with open(fxtr_path, "r") as fxtr_file:

View File

@ -32,33 +32,6 @@ def get_config(config):
class BasePluginTest(base.BaseIntegrationTest):
SAMPLE_PLUGIN = {
'version': '1.1.0',
'name': 'testing',
'title': 'Test plugin',
'package_version': '1.0.0',
'description': 'Enable to use plugin X for Neutron',
'fuel_version': ["6.0"],
'releases': [
{'repository_path': 'repositories/ubuntu',
'version': '2014.2-6.0',
'os': 'ubuntu',
'mode': ['ha_compact', 'multinode'],
'deployment_scripts_path': 'deployment_scripts/'},
{'repository_path': 'repositories/centos',
'version': '2014.2-6.0', 'os': 'centos',
'mode': ['ha', 'multinode'],
'deployment_scripts_path': 'deployment_scripts/'}]}
ENVIRONMENT_CONFIG = {
'attributes': {
'lbaas_simple_text': {
'value': 'Set default value',
'type': 'text',
'description': 'Description for text field',
'weight': 25,
'label': 'Text field'}}}
TASKS_CONFIG = [
{'priority': 10,
'role': ['controller'],
@ -71,8 +44,13 @@ class BasePluginTest(base.BaseIntegrationTest):
'parameters': {'cmd': 'echo all > /tmp/plugin.all', 'timeout': 42},
'stage': 'pre_deployment'}]
def setUp(self):
super(BasePluginTest, self).setUp()
self.sample_plugin = self.env.get_default_plugin_metadata()
self.plugin_env_config = self.env.get_default_plugin_env_config()
def create_plugin(self, sample=None, expect_errors=False):
sample = sample or self.SAMPLE_PLUGIN
sample = sample or self.sample_plugin
resp = self.app.post(
base.reverse('PluginCollectionHandler'),
jsonutils.dumps(sample),
@ -95,7 +73,7 @@ class BasePluginTest(base.BaseIntegrationTest):
create=True) as f_m:
os.access.return_value = True
os.path.exists.return_value = True
f_m.side_effect = get_config(self.ENVIRONMENT_CONFIG)
f_m.side_effect = get_config(self.plugin_env_config)
self.env.create(
release_kwargs={'version': '2014.2-6.0',
'operating_system': 'Ubuntu'},
@ -163,7 +141,7 @@ class TestPluginsApi(BasePluginTest):
def test_env_create_and_load_env_config(self):
self.create_plugin()
cluster = self.create_cluster()
self.assertIn(self.SAMPLE_PLUGIN['name'], cluster.attributes.editable)
self.assertIn(self.sample_plugin['name'], cluster.attributes.editable)
def test_enable_disable_plugin(self):
resp = self.create_plugin()
@ -201,7 +179,29 @@ class TestPluginsApi(BasePluginTest):
self.create_plugin()
cluster = self.create_cluster()
default_attributes = self.default_attributes(cluster)
self.assertIn(self.SAMPLE_PLUGIN['name'], default_attributes)
self.assertIn(self.sample_plugin['name'], default_attributes)
def test_plugins_multiversioning(self):
def create_with_version(version):
self.create_plugin(sample=self.env.get_default_plugin_metadata(
name='multiversion_plugin', version=version))
for version in ['1.0.0', '2.0.0', '0.0.1']:
create_with_version(version)
cluster = self.create_cluster()
# Create new plugin after environment is created
create_with_version('5.0.0')
self.enable_plugin(cluster, 'multiversion_plugin')
self.assertEqual(len(cluster.plugins), 1)
enabled_plugin = cluster.plugins[0]
# Should be enabled the newest plugin,
# at the moment of environment creation
self.assertEqual(enabled_plugin.version, '2.0.0')
self.disable_plugin(cluster, 'multiversion_plugin')
self.assertEqual(len(cluster.plugins), 0)
class TestPrePostHooks(BasePluginTest):
@ -214,7 +214,7 @@ class TestPrePostHooks(BasePluginTest):
self.cluster = self.create_cluster([
{'roles': ['controller'], 'pending_addition': True},
{'roles': ['compute'], 'pending_addition': True}])
self.enable_plugin(self.cluster, self.SAMPLE_PLUGIN['name'])
self.enable_plugin(self.cluster, self.sample_plugin['name'])
def test_generate_pre_hooks(self):
tasks = self.get_pre_hooks(self.cluster).json

View File

@ -25,48 +25,22 @@ from nailgun.settings import settings
from nailgun.test import base
SAMPLE_PLUGIN = {
'version': '0.1.0',
'name': 'testing_plugin',
'title': 'Test plugin',
'package_version': '1.0.0',
'description': 'Enable to use plugin X for Neutron',
'fuel_version': ['6.0'],
'releases': [
{'repository_path': 'repositories/ubuntu',
'version': '2014.2-6.0', 'os': 'ubuntu',
'mode': ['ha', 'multinode'],
'deployment_scripts_path': 'deployment_scripts/'},
{'repository_path': 'repositories/centos',
'version': '2014.2-6.0', 'os': 'centos',
'mode': ['ha', 'multinode'],
'deployment_scripts_path': 'deployment_scripts/'}]}
ENVIRONMENT_CONFIG = {
'attributes': {
'lbaas_simple_text': {
'value': 'Set default value',
'type': 'text',
'description': 'Description for text field',
'weight': 25,
'label': 'Text field'}}}
def get_config(*args):
return mock.mock_open(read_data=yaml.dump(ENVIRONMENT_CONFIG))()
class TestPlugin(base.BaseTestCase):
def setUp(self):
super(TestPlugin, self).setUp()
self.plugin = Plugin.create(SAMPLE_PLUGIN)
self.plugin_metadata = self.env.get_default_plugin_metadata()
self.plugin = Plugin.create(self.plugin_metadata)
self.env.create(
cluster_kwargs={'mode': 'multinode'},
release_kwargs={'version': '2014.2-6.0',
'operating_system': 'Ubuntu'})
self.cluster = self.env.clusters[0]
self.attr_plugin = attr_plugin.ClusterAttributesPlugin(self.plugin)
self.env_config = self.env.get_default_plugin_env_config()
self.get_config = lambda *args: mock.mock_open(
read_data=yaml.dump(self.env_config))()
db().flush()
@mock.patch('nailgun.plugins.attr_plugin.open', create=True)
@ -79,12 +53,11 @@ class TestPlugin(base.BaseTestCase):
"""
maccess.return_value = True
mexists.return_value = True
mopen.side_effect = get_config
attributes = self.attr_plugin.get_plugin_attributes(
self.cluster)
mopen.side_effect = self.get_config
attributes = self.attr_plugin.get_plugin_attributes(self.cluster)
self.assertEqual(
attributes['testing_plugin']['lbaas_simple_text'],
ENVIRONMENT_CONFIG['attributes']['lbaas_simple_text'])
attributes['testing_plugin']['plugin_name_text'],
self.env_config['attributes']['plugin_name_text'])
self.assertEqual(
attributes['testing_plugin']['metadata'],
self.attr_plugin.default_metadata)
@ -107,7 +80,7 @@ class TestPlugin(base.BaseTestCase):
provided release.
"""
release = self.attr_plugin.get_release_info(self.cluster.release)
self.assertEqual(release, SAMPLE_PLUGIN['releases'][0])
self.assertEqual(release, self.plugin_metadata['releases'][0])
def test_slaves_scripts_path(self):
expected = settings.PLUGINS_SLAVES_SCRIPTS_PATH.format(
@ -145,7 +118,7 @@ class TestClusterCompatiblityValidation(base.BaseTestCase):
def setUp(self):
super(TestClusterCompatiblityValidation, self).setUp()
self.plugin = Plugin.create(SAMPLE_PLUGIN)
self.plugin = Plugin.create(self.env.get_default_plugin_metadata())
self.attr_plugin = attr_plugin.ClusterAttributesPlugin(self.plugin)
def get_cluster(self, os, mode, version):

View File

@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
# Copyright 2014 Mirantis, Inc.
#
# 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 nailgun.objects import Plugin
from nailgun.objects import PluginCollection
from nailgun.test import base
class TestPluginCollection(base.BaseTestCase):
def test_all_newest(self):
for version in ['1.0.0', '2.0.0', '0.0.1']:
plugin_data = self.env.get_default_plugin_metadata(
version=version,
name='multiversion_plugin')
Plugin.create(plugin_data)
single_plugin_data = self.env.get_default_plugin_metadata(
name='single_plugin')
Plugin.create(single_plugin_data)
newest_plugins = PluginCollection.all_newest()
self.assertEqual(len(newest_plugins), 2)
single_plugin = filter(
lambda p: p.name == 'single_plugin',
newest_plugins)
multiversion_plugin = filter(
lambda p: p.name == 'multiversion_plugin',
newest_plugins)
self.assertEqual(len(single_plugin), 1)
self.assertEqual(len(multiversion_plugin), 1)
self.assertEqual(multiversion_plugin[0].version, '2.0.0')