Versions of a plugin have been placed in a separate container

Closes-Bug: #1440046
Closes-Bug: #1518993

Change-Id: I76655541fd7c00cdd863e145b28fba804b4ed801
(cherry picked from commit f83c4ea23b)
This commit is contained in:
Vitalii Myhal 2015-11-28 15:13:47 -06:00 committed by Vitaly Kramskikh
parent d35b2728ba
commit fe13a32863
12 changed files with 451 additions and 350 deletions

View File

@ -21,22 +21,20 @@ Handlers dealing with clusters
import traceback
from nailgun.api.v1.handlers.base import BaseHandler
from nailgun.api.v1.handlers.base import CollectionHandler
from nailgun.api.v1.handlers.base import content
from nailgun.api.v1.handlers.base import DeferredTaskHandler
from nailgun.api.v1.handlers.base import DeploymentTasksHandler
from nailgun.api.v1.handlers.base import CollectionHandler
from nailgun.api.v1.handlers.base import SingleHandler
from nailgun import objects
from nailgun.api.v1.handlers.base import content
from nailgun.api.v1.validators.cluster import AttributesValidator
from nailgun.api.v1.validators.cluster import ClusterChangesValidator
from nailgun.api.v1.validators.cluster import ClusterValidator
from nailgun.api.v1.validators.cluster import VmwareAttributesValidator
from nailgun.logger import logger
from nailgun import objects
from nailgun.task.manager import ApplyChangesTaskManager
from nailgun.task.manager import ClusterDeletionManager
from nailgun.task.manager import ResetEnvironmentTaskManager
@ -159,6 +157,7 @@ class ClusterAttributesHandler(BaseHandler):
:http: * 200 (OK)
* 400 (wrong attributes data specified)
* 403 (attribute changing is not allowed)
* 404 (cluster not found in db)
* 500 (cluster has no attributes)
"""
@ -168,24 +167,8 @@ class ClusterAttributesHandler(BaseHandler):
raise self.http(500, "No attributes found!")
data = self.checked_data(cluster=cluster)
# if cluster is locked we have to check which attributes
# we want to change and block an entire operation if there
# one with always_editable=False.
if cluster.is_locked:
editable = objects.Cluster.get_editable_attributes(
cluster, all_plugins_versions=True)
for group_name in data.get('editable', {}):
# we need bunch of gets because the attribute may not
# even exist (user adds a new one)
metadata = editable.get(group_name, {}).get('metadata', {})
if not metadata.get('always_editable'):
raise self.http(403, (
"Environment attribute '{0}' couldn't be changed "
"after or during deployment.".format(group_name)))
objects.Cluster.patch_attributes(cluster, data)
return {
'editable': objects.Cluster.get_editable_attributes(
cluster, all_plugins_versions=True)

View File

@ -26,11 +26,11 @@ from nailgun.api.v1.validators.json_schema import cluster as cluster_schema
from nailgun.api.v1.validators.node import ProvisionSelectedNodesValidator
from nailgun import consts
from nailgun.db import db
from nailgun.db.sqlalchemy.models import Node
from nailgun.errors import errors
from nailgun import objects
from nailgun.plugins.manager import PluginManager
from nailgun.utils import restrictions
@ -274,31 +274,30 @@ class AttributesValidator(BasicValidator):
attrs = d
if cluster is not None:
attrs = objects.Cluster.get_updated_editable_attributes(cluster, d)
# NOTE(agordeev): disable classic provisioning for 7.0 or higher
if StrictVersion(cluster.release.environment_version) >= \
StrictVersion(consts.FUEL_IMAGE_BASED_ONLY):
provision_data = attrs['editable'].get('provision')
if provision_data:
if provision_data['method']['value'] != \
consts.PROVISION_METHODS.image:
raise errors.InvalidData(
u"Cannot use classic provisioning for adding "
u"nodes to environment",
log_message=True)
else:
raise errors.InvalidData(
u"Provisioning method is not set. Unable to continue",
log_message=True)
cls.validate_plugin_attributes(
cluster, attrs.get('editable', {})
)
cls.validate_provision(cluster, attrs)
cls.validate_allowed_attributes(cluster, d)
cls.validate_editable_attributes(attrs)
return d
@classmethod
def validate_provision(cls, cluster, attrs):
# NOTE(agordeev): disable classic provisioning for 7.0 or higher
if StrictVersion(cluster.release.environment_version) >= \
StrictVersion(consts.FUEL_IMAGE_BASED_ONLY):
provision_data = attrs['editable'].get('provision')
if provision_data:
if provision_data['method']['value'] != \
consts.PROVISION_METHODS.image:
raise errors.InvalidData(
u"Cannot use classic provisioning for adding "
u"nodes to environment",
log_message=True)
else:
raise errors.InvalidData(
u"Provisioning method is not set. Unable to continue",
log_message=True)
@classmethod
def validate_editable_attributes(cls, data):
"""Validate 'editable' attributes."""
@ -366,52 +365,62 @@ class AttributesValidator(BasicValidator):
'[{0}] {1}'.format(attr_name, regex_err))
@classmethod
def validate_plugin_attributes(cls, cluster, attributes):
"""Validates Cluster-Plugins relations attributes
def validate_allowed_attributes(cls, cluster, data):
"""Validates if attributes are hot pluggable or not.
:param cluster: A cluster instance
:type cluster: nailgun.objects.cluster.Cluster
:param attributes: The editable attributes of the Cluster
:type attributes: dict
:type cluster: nailgun.db.sqlalchemy.models.cluster.Cluster
:param data: Changed attributes of cluster
:type data: dict
:raises: errors.NotAllowed
"""
# TODO(need to enable restrictions check for cluster attributes[1])
# [1] https://bugs.launchpad.net/fuel/+bug/1519904
# Validates only that plugin can be installed on deployed env.
# If cluster is locked we have to check which attributes
# we want to change and block an entire operation if there
# one with always_editable=False.
if not cluster.is_locked:
return
enabled_plugins = set(
p.id for p in objects.ClusterPlugins.get_enabled(cluster.id)
)
editable_cluster = objects.Cluster.get_editable_attributes(
cluster, all_plugins_versions=True)
editable_request = data.get('editable', {})
for attrs in six.itervalues(attributes):
if not isinstance(attrs, dict):
continue
for attr_name, attr_request in six.iteritems(editable_request):
attr_cluster = editable_cluster.get(attr_name, {})
meta_cluster = attr_cluster.get('metadata', {})
meta_request = attr_request.get('metadata', {})
plugin_versions = attrs.get('plugin_versions', None)
if plugin_versions is None:
continue
if not attrs.get('metadata', {}).get('enabled'):
continue
for version in plugin_versions['values']:
plugin_id = version.get('data')
plugin = objects.Plugin.get_by_uid(plugin_id)
if not plugin:
if PluginManager.is_plugin_data(attr_cluster):
if meta_request['enabled']:
changed_ids = [meta_request['chosen_id']]
if meta_cluster['enabled']:
changed_ids.append(meta_cluster['chosen_id'])
changed_ids = set(changed_ids)
elif meta_cluster['enabled']:
changed_ids = [meta_cluster['chosen_id']]
else:
continue
if plugin_id != plugin_versions['value']:
continue
if plugin.is_hotpluggable or plugin.id in enabled_plugins:
break
for plugin in meta_cluster['versions']:
plugin_id = plugin['metadata']['plugin_id']
always_editable = plugin['metadata']\
.get('always_editable', False)
if plugin_id in changed_ids and not always_editable:
raise errors.NotAllowed(
"Plugin '{0}' version '{1}' couldn't be changed "
"after or during deployment."
.format(attr_name,
plugin['metadata']['plugin_version']),
log_message=True
)
elif not meta_cluster.get('always_editable', False):
raise errors.NotAllowed(
"This plugin version can be enabled only "
"before environment is deployed.",
"Environment attribute '{0}' couldn't be changed "
"after or during deployment.".format(attr_name),
log_message=True
)

View File

@ -17,6 +17,7 @@
from nailgun.api.v1.validators.base import BasicValidator
from nailgun.api.v1.validators.json_schema import plugin
from nailgun.errors import errors
from nailgun.objects import ClusterPlugins
from nailgun.objects import Plugin
@ -24,7 +25,7 @@ class PluginValidator(BasicValidator):
@classmethod
def validate_delete(cls, data, instance):
if instance.clusters:
if ClusterPlugins.is_plugin_used(instance.id):
raise errors.CannotDelete(
"Can't delete plugin which is enabled "
"for some environment."

View File

@ -40,6 +40,13 @@
"mode": ["ha"],
"deployment_scripts_path": "deployment_scripts/",
"repository_path": "repositories/ubuntu"
},
{
"os": "ubuntu",
"version": "2016.1.0-9.0",
"mode": ["ha"],
"deployment_scripts_path": "deployment_scripts/",
"repository_path": "repositories/ubuntu"
}
],
"package_version": "2.0.0",
@ -86,6 +93,13 @@
"mode": ["ha"],
"deployment_scripts_path": "deployment_scripts/",
"repository_path": "repositories/ubuntu"
},
{
"os": "ubuntu",
"version": "2016.1.0-9.0",
"mode": ["ha"],
"deployment_scripts_path": "deployment_scripts/",
"repository_path": "repositories/ubuntu"
}
],
"package_version": "2.0.0"
@ -126,10 +140,25 @@
"mode": ["ha"],
"deployment_scripts_path": "deployment_scripts/",
"repository_path": "repositories/ubuntu"
},
{
"os": "ubuntu",
"version": "2016.1.0-9.0",
"mode": ["ha"],
"deployment_scripts_path": "deployment_scripts/",
"repository_path": "repositories/ubuntu"
}
],
"package_version": "2.0.0",
"attributes_metadata": {
"metadata": {
"restrictions": [
{
"condition": "settings:common.use_vcenter.value == true",
"action": "hide"
}
]
},
"zabbix_text_1": {
"value": "value 1.1",
"type": "text",
@ -175,23 +204,89 @@
"mode": ["ha"],
"deployment_scripts_path": "deployment_scripts/",
"repository_path": "repositories/ubuntu"
},
{
"os": "ubuntu",
"version": "2016.1.0-9.0",
"mode": ["ha"],
"deployment_scripts_path": "deployment_scripts/",
"repository_path": "repositories/ubuntu"
}
],
"package_version": "2.0.0",
"attributes_metadata": {
"metadata": {
"restrictions": [
{
"condition": "cluster:net_provider != 'neutron'",
"action": "hide"
}
]
},
"zabbix_text_1": {
"value": "value 2.1",
"type": "text",
"description": "description 2.1",
"weight": 25,
"label": "label 2.1"
"label": "Label of text field 2.1",
"description": "Description of text field 2.1",
"weight": 20,
"type": "text"
},
"zabbix_text_2": {
"value": "value 2.2",
"label": "Label of text field 2.2",
"description": "Description of text field 2.2",
"weight": 21,
"type": "text"
},
"zabbix_text_with_regex": {
"value": "",
"label": "Label of text field with regex (type 'aa-aa')",
"description": "Some description of text field with regex",
"weight": 30,
"type": "text",
"description": "description 2.2",
"weight": 26,
"label": "label 2.2"
"regex": {
"source": "^(?:[a-z]+-[a-z0-9]+)(?:,[a-z]+-[a-z0-9]+)*",
"error": "Invalid value in text field with regex"
}
},
"zabbix_checkbox": {
"value": false,
"label": "Hide file selector",
"description": "If True then file field will be hidden",
"weight": 40,
"type": "checkbox"
},
"zabbix_file": {
"value": "",
"label": "Select file",
"description": "Description of file field",
"weight": 41,
"type": "file",
"restrictions": [
{
"condition": "settings:zabbix_monitoring.zabbix_checkbox.value == true",
"action": "hide"
}
]
},
"zabbix_checkbox_long_name": {
"value": true,
"label": "Show text field",
"description": "If True then text field will be shown",
"weight": 50,
"type": "checkbox"
},
"zabbix_text_3": {
"value": "",
"label": "Label of text field 2.3",
"description": "Description of text field 2.3",
"weight": 51,
"type": "text",
"restrictions": [
{
"condition": "settings:zabbix_monitoring.zabbix_checkbox_long_name.value == false",
"action": "hide"
}
]
}
}
}

View File

@ -131,9 +131,9 @@ class ClusterPlugins(NailgunObject):
- release version
:param cluster: A cluster instance
:type cluster: nailgun.objects.cluster.Cluster
:type cluster: nailgun.db.sqlalchemy.models.cluster.Cluster
:param plugin: A plugin instance
:type plugin: nailgun.objects.plugin.Plugin
:type plugin: nailgun.db.sqlalchemy.models.plugins.Plugin
:return: True if compatible, False if not
:rtype: bool
"""
@ -176,7 +176,7 @@ class ClusterPlugins(NailgunObject):
"""Returns a list of plugins that are compatible with a given cluster.
:param cluster: A cluster instance
:type cluster: nailgun.objects.cluster.Cluster
:type cluster: nailgun.db.sqlalchemy.models.cluster.Cluster
:return: A list of plugin instances
:rtype: list
"""
@ -189,14 +189,16 @@ class ClusterPlugins(NailgunObject):
"""Populates 'cluster_plugins' table with compatible plugins.
:param cluster: A cluster instance
:type cluster: nailgun.objects.cluster.Cluster
:type cluster: nailgun.db.sqlalchemy.models.cluster.Cluster
"""
for plugin in cls.get_compatible_plugins(cluster):
plugin_attributes = dict(plugin.attributes_metadata)
plugin_attributes.pop('metadata', None)
cls.create({
'cluster_id': cluster.id,
'plugin_id': plugin.id,
'enabled': False,
'attributes': plugin.attributes_metadata
'attributes': plugin_attributes
})
@classmethod
@ -204,7 +206,7 @@ class ClusterPlugins(NailgunObject):
"""Returns a list of clusters that are compatible with a given plugin.
:param plugin: A plugin instance
:type plugin: nailgun.objects.plugin.Plugin
:type plugin: nailgun.db.sqlalchemy.models.plugins.Plugin
:return: A list of cluster instances
:rtype: list
"""
@ -217,14 +219,16 @@ class ClusterPlugins(NailgunObject):
"""Populates 'cluster_plugins' table with compatible cluster.
:param plugin: A plugin instance
:type plugin: nailgun.objects.plugin.Plugin
:type plugin: nailgun.db.sqlalchemy.models.plugins.Plugin
"""
plugin_attributes = dict(plugin.attributes_metadata)
plugin_attributes.pop('metadata', None)
for cluster in cls.get_compatible_clusters(plugin):
cls.create({
'cluster_id': cluster.id,
'plugin_id': plugin.id,
'enabled': False,
'attributes': plugin.attributes_metadata
'attributes': plugin_attributes
})
@classmethod
@ -324,3 +328,18 @@ class ClusterPlugins(NailgunObject):
.filter(cls.model.cluster_id == cluster_id)\
.filter(cls.model.enabled.is_(True))\
.order_by(models.Plugin.id)
@classmethod
def is_plugin_used(cls, plugin_id):
"""Check if plugin is used for any cluster or not.
:param plugin_id: Plugin ID
:type plugin_id: int
:return: True if some cluster uses this plugin
:rtype: bool
"""
q = db().query(cls.model)\
.filter(cls.model.plugin_id == plugin_id)\
.filter(cls.model.enabled.is_(True))
return db().query(q.exists()).scalar()

View File

@ -28,58 +28,40 @@ class PluginManager(object):
@classmethod
def process_cluster_attributes(cls, cluster, attributes):
"""Generate Cluster-Plugins relation based on attributes
"""Generate Cluster-Plugins relation based on attributes.
Iterates through plugins attributes, creates
or deletes Cluster <-> Plugins relation if plugin
is enabled or disabled.
:param cluster: A cluster instance
:type cluster: nailgun.objects.cluster.Cluster
:type cluster: nailgun.db.sqlalchemy.models.cluster.Cluster
:param attributes: Cluster attributes
:type attributes: dict
"""
def _convert_attrs(plugin_id, attrs):
prefix = "#{0}_".format(plugin_id)
result = dict((title[len(prefix):], attrs[title])
for title in attrs
if title.startswith(prefix))
for attr in six.itervalues(result):
if 'restrictions' not in attr:
continue
if len(attr['restrictions']) == 1:
attr.pop('restrictions')
else:
attr['restrictions'].pop()
return result
plugins = {}
for attrs in six.itervalues(attributes):
if not isinstance(attrs, dict):
continue
# Detach plugins data
for k in list(attributes):
if cls.is_plugin_data(attributes[k]):
plugins[k] = attributes.pop(k)['metadata']
cluster.attributes.editable.pop(k, None)
plugin_versions = attrs.pop('plugin_versions', None)
if plugin_versions is None:
continue
metadata = attrs.pop('metadata', {})
plugin_enabled = metadata.get('enabled', False)
default = metadata.get('default', False)
for version in plugin_versions['values']:
pid = version.get('data')
plugin = Plugin.get_by_uid(pid)
for container in six.itervalues(plugins):
default = container.get('default', False)
for attrs in container.get('versions', []):
version_metadata = attrs.pop('metadata')
plugin_id = version_metadata['plugin_id']
plugin = Plugin.get_by_uid(plugin_id)
if not plugin:
logger.warning(
'Plugin with id "%s" is not found, skip it', pid)
'Plugin with id "%s" is not found, skip it', plugin_id)
continue
enabled = plugin_enabled and\
pid == plugin_versions['value']
enabled = container['enabled']\
and plugin_id == container['chosen_id']
ClusterPlugins.set_attributes(
cluster.id, plugin.id, enabled=enabled,
attrs=_convert_attrs(plugin.id, attrs)
if enabled or default else None
attrs=attrs if enabled or default else None
)
@classmethod
@ -88,7 +70,7 @@ class PluginManager(object):
"""Gets attributes of all plugins connected with given cluster.
:param cluster: A cluster instance
:type cluster: nailgun.objects.cluster.Cluster
:type cluster: nailgun.db.sqlalchemy.models.cluster.Cluster
:param all_versions: True to get attributes of all versions of plugins
:type all_versions: bool
:param default: True to return a default plugins attributes (for UI)
@ -96,80 +78,87 @@ class PluginManager(object):
:return: Plugins attributes
:rtype: dict
"""
versions = {
'type': 'radio',
'values': [],
'weight': 10,
'value': None,
'label': 'Choose a plugin version'
}
plugins_attributes = {}
for plugin in ClusterPlugins.get_connected_plugins_data(cluster.id):
plugin_id = str(plugin.id)
enabled = plugin.enabled and not (all_versions and default)
plugin_attributes = plugins_attributes.setdefault(plugin.name, {})
metadata = plugin_attributes.setdefault('metadata', {
'toggleable': True,
'weight': 70
})
metadata['enabled'] = enabled or metadata.get('enabled', False)
metadata['label'] = plugin.title
if plugin.is_hotpluggable:
metadata["always_editable"] = True
default_attrs = plugin.attributes_metadata
if all_versions:
metadata['default'] = default
container = plugins_attributes.setdefault(plugin.name, {})
enabled = plugin.enabled and not (all_versions and default)
cls.create_common_metadata(plugin, container, enabled)
container['metadata']['default'] = default
plugin_attributes.update(
cls.convert_plugin_attributes(
plugin,
plugin.attributes_metadata
if default else plugin.attributes
)
)
plugin_version = {
'data': plugin_id,
'description': '',
'label': plugin.version,
}
if not plugin.is_hotpluggable:
plugin_version['restrictions'] = [{
'action': 'disable',
'condition': 'cluster:is_locked'
}]
plugin_versions = plugin_attributes.get('plugin_versions')
if plugin_versions is not None:
if enabled:
plugin_versions['value'] = plugin_id
versions = container['metadata'].setdefault('versions', [])
if default:
actual_attrs = copy.deepcopy(default_attrs)
actual_attrs.setdefault('metadata', {})
else:
plugin_versions = copy.deepcopy(versions)
plugin_versions['value'] = plugin_id
plugin_attributes['plugin_versions'] = plugin_versions
actual_attrs = copy.deepcopy(plugin.attributes)
actual_attrs['metadata'] = default_attrs.get('metadata',
{})
cls.fill_plugin_metadata(
plugin, actual_attrs['metadata'], True)
versions.append(actual_attrs)
plugin_versions['values'].append(plugin_version)
elif enabled:
plugin_attributes.update(plugin.attributes)
container['metadata'].setdefault('chosen_id', plugin.id)
if enabled:
container['metadata']['chosen_id'] = plugin.id
elif plugin.enabled:
container = plugins_attributes.setdefault(plugin.name, {})
cls.create_common_metadata(plugin, container)
container['metadata'].update(default_attrs.get('metadata', {}))
cls.fill_plugin_metadata(plugin, container['metadata'])
container.update(plugin.attributes)
return plugins_attributes
@classmethod
def convert_plugin_attributes(cls, plugin, attributes):
def converter(plugin_id, plugin_name, title, attr):
restrictions = attr.setdefault('restrictions', [])
restrictions.append({
'action': 'hide',
'condition': "settings:{0}.plugin_versions.value != '{1}'"
.format(plugin_name, plugin_id)
})
return "#{0}_{1}".format(plugin_id, title), attr
def is_plugin_data(cls, attributes):
"""Looking for a plugins hallmark.
return (
converter(plugin.id, plugin.name, k, v)
for k, v in six.iteritems(attributes)
)
:param attributes: Item of editable attributes of cluster
:type attributes: dict
:return: True if it's a plugins container
:rtype: bool
"""
return attributes.get('metadata', {}).get('class') == 'plugin'
@classmethod
def create_common_metadata(cls, plugin, attributes, enabled=None):
"""Create common metadata attribute for all versions of plugin.
:param plugin: A plugin instance
:type plugin: nailgun.db.sqlalchemy.models.plugins.Plugin
:param attributes: Common attributes of plugin versions
:type attributes: dict
:param enabled: Plugin status
:type enabled: bool
"""
metadata = attributes.setdefault('metadata', {
'class': 'plugin',
'toggleable': True,
'weight': 70
})
metadata['label'] = plugin.title
if enabled is None:
enabled = plugin.enabled
metadata['enabled'] = enabled or metadata.get('enabled', False)
@classmethod
def fill_plugin_metadata(cls, plugin, metadata, all_versions=False):
"""Fill a plugin's metadata attribute.
:param plugin: A plugin instance
:type plugin: nailgun.db.sqlalchemy.models.plugins.Plugin
:param metadata: Plugin metadata
:type metadata: dict
:param all_versions: Create for all versions of plugin or not
:type all_versions: bool
"""
metadata['plugin_id'] = plugin.id
metadata['plugin_version'] = plugin.version
metadata['always_editable'] = plugin.is_hotpluggable
@classmethod
def get_cluster_plugins_with_tasks(cls, cluster):
@ -367,14 +356,13 @@ class PluginManager(object):
@classmethod
def enable_plugins_by_components(cls, cluster):
"""Enable plugin by components
"""Enable plugin by components.
:param cluster: A cluster instance
:type cluster: Cluster model
:return: None
"""
cluster_components = set(cluster.components)
plugin_ids = set(p.id for p in PluginCollection.all_newest())
plugin_ids = [p.id for p in PluginCollection.all_newest()]
for plugin in ClusterPlugins.get_connected_plugins(
cluster, plugin_ids):
@ -383,6 +371,6 @@ class PluginManager(object):
component['name']
for component in plugin_adapter.components_metadata)
for component in cluster_components & plugin_components:
if cluster_components & plugin_components:
ClusterPlugins.set_attributes(
cluster.id, plugin.id, enabled=True)

View File

@ -87,14 +87,14 @@ class TestAttributes(BaseIntegrationTest):
kwargs={'cluster_id': cluster_id}),
params=jsonutils.dumps({
'editable': {
"foo": "bar"
'foo': {'bar': None}
},
}),
headers=self.default_headers
)
self.assertEqual(200, resp.status_code)
attrs = objects.Cluster.get_editable_attributes(cluster_db)
self.assertEqual("bar", attrs["foo"])
self.assertEqual({'bar': None}, attrs["foo"])
attrs.pop('foo')
# 400 on generated update
@ -140,14 +140,14 @@ class TestAttributes(BaseIntegrationTest):
kwargs={'cluster_id': cluster_id}),
params=jsonutils.dumps({
'editable': {
"foo": "bar"
'foo': {'bar': None}
},
}),
headers=self.default_headers
)
self.assertEqual(200, resp.status_code)
attrs = objects.Cluster.get_editable_attributes(cluster_db)
self.assertEqual("bar", attrs["foo"])
self.assertEqual({'bar': None}, attrs["foo"])
attrs.pop('foo')
self.assertNotEqual(attrs, {})
@ -210,7 +210,7 @@ class TestAttributes(BaseIntegrationTest):
kwargs={'cluster_id': cluster['id']}),
params=jsonutils.dumps({
'editable': {
"foo": "bar"
'foo': {'bar': None}
},
}),
headers=self.default_headers,
@ -218,7 +218,7 @@ class TestAttributes(BaseIntegrationTest):
)
self.assertEqual(200, resp.status_code, resp.body)
attrs = objects.Cluster.get_editable_attributes(cluster_db)
self.assertEqual("bar", attrs["foo"])
self.assertEqual({'bar': None}, attrs['foo'])
# Set attributes to defaults.
resp = self.app.put(
reverse(
@ -758,7 +758,6 @@ class TestAttributesWithPlugins(BaseIntegrationTest):
def test_change_plugins_attributes(self):
plugin = self.env.create_plugin(cluster=self.cluster,
**self.plugin_data)
attr = '#{0}_attr'.format(plugin.id)
def _modify_plugin(enabled=True):
return self.app.put(
@ -769,30 +768,27 @@ class TestAttributesWithPlugins(BaseIntegrationTest):
'editable': {
plugin.name: {
'metadata': {
'class': 'plugin',
'label': 'Test plugin',
'toggleable': True,
'weight': 70,
'enabled': enabled
'enabled': enabled,
'chosen_id': plugin.id,
'versions': [{
'metadata': {
'plugin_id': plugin.id,
'plugin_version': plugin.version
},
'attr': {
'type': 'text',
'description': 'description',
'label': 'label',
'value': '1',
'weight': 25,
'restrictions': [{'action': 'hide'}]
}
}]
},
'plugin_versions': {
'type': 'radio',
'values': [{
'data': str(plugin.id),
'description': '',
'label': '0.1.0'
}],
'weight': 10,
'value': str(plugin.id),
'label': 'Choose a plugin version'
},
attr: {
'type': 'text',
'description': 'description',
'label': 'label',
'value': '1',
'weight': 25,
'restrictions': [{'action': 'hide'}]
}
}
}
}),
@ -809,11 +805,9 @@ class TestAttributesWithPlugins(BaseIntegrationTest):
resp = _modify_plugin(enabled=False)
self.assertEqual(200, resp.status_code)
editable = objects.Cluster.get_editable_attributes(self.cluster)
self.assertIn(plugin.name, editable)
self.assertFalse(editable[plugin.name]['metadata']['enabled'])
self.assertNotIn(attr, editable[plugin.name])
self.assertNotIn(plugin.name, editable)
def _modify_plugin(self, plugin, enabled, **kwargs):
def _modify_plugin(self, plugin, enabled):
return self.app.put(
reverse(
'ClusterAttributesHandler',
@ -821,18 +815,19 @@ class TestAttributesWithPlugins(BaseIntegrationTest):
),
params=jsonutils.dumps({
'editable': {
plugin.name: dict(
metadata={'enabled': enabled},
plugin_versions={
'type': 'radio',
'values': [{
'data': str(plugin.id),
'label': plugin.version
}],
'value': str(plugin.id),
},
**kwargs
)
plugin.name: {
'metadata': {
'class': 'plugin',
'enabled': enabled,
'chosen_id': plugin.id,
'versions': [{
'metadata': {
'plugin_id': plugin.id,
'plugin_version': plugin.version
}
}]
}
}
}
}),
headers=self.default_headers,

View File

@ -106,7 +106,7 @@ class TestClusterChanges(BaseIntegrationTest):
kwargs={'cluster_id': cluster['id']}),
jsonutils.dumps({
'editable': {
"foo": "bar"
'foo': {'bar': None}
}
}),
headers=self.default_headers

View File

@ -91,7 +91,7 @@ class TestNetworkModels(BaseIntegrationTest):
kwargs={'cluster_id': self.env.clusters[0].id}),
jsonutils.dumps({
'editable': {
"foo": "bar"
"foo": {"bar": None}
}
}),
headers=self.default_headers,

View File

@ -302,67 +302,65 @@ class TestPluginManager(base.BaseIntegrationTest):
cluster=cluster, enabled=False
)
plugin_b = self.env.create_plugin(
name='plugin_b', title='plugin_a_title', cluster=cluster
name='plugin_b', title='plugin_a_title', cluster=cluster,
attributes_metadata={
'metadata': {
'restrictions': [
{
"condition": "cluster:net_provider != 'neutron'",
"action": "hide"
}
]
}
}
)
cluster.status = consts.CLUSTER_STATUSES.operational
self.db.flush()
self.assertTrue(cluster.is_locked)
attributes = PluginManager.get_plugins_attributes(
cluster, True, True
cluster, all_versions=True, default=True
)
pl_a1 = attributes['plugin_a']['metadata']['versions'][0]
pl_a2 = attributes['plugin_a']['metadata']['versions'][1]
pl_b = attributes['plugin_b']['metadata']['versions'][0]
self.assertItemsEqual(['plugin_a', 'plugin_b'], attributes)
self.assertItemsEqual(
{
'plugin_id': plugin_a1.id,
'plugin_version': plugin_a1.version,
'always_editable': False
}, pl_a1['metadata']
)
self.assertItemsEqual(
['plugin_a', 'plugin_b'], attributes
)
self.assertTrue(
attributes['plugin_a']['metadata']['always_editable']
{
'plugin_id': plugin_a2.id,
'plugin_version': plugin_a2.version,
'always_editable': True
},
pl_a2['metadata']
)
self.assertItemsEqual(
[
{
'data': str(plugin_a1.id),
'description': '',
'label': plugin_a1.version,
'restrictions': [
{
'action': 'disable',
'condition': 'cluster:is_locked'
}
],
},
{
'data': str(plugin_a2.id),
'description': '',
'label': plugin_a2.version
}
],
attributes['plugin_a']['plugin_versions']['values']
{
'plugin_id': plugin_b.id,
'plugin_version': plugin_b.version,
'always_editable': False,
'restrictions': [
{
"condition": "cluster:net_provider != 'neutron'",
"action": "hide"
}
]
}, pl_b['metadata']
)
self.assertEqual(
str(plugin_a1.id),
attributes['plugin_a']['plugin_versions']['value']
)
self.assertNotIn(
'always_editable', attributes['plugin_b']['metadata']
)
self.assertItemsEqual(
[
{
'restrictions': [
{
'action': 'disable',
'condition': 'cluster:is_locked'
}
],
'data': str(plugin_b.id),
'description': '',
'label': plugin_b.version,
},
],
attributes['plugin_b']['plugin_versions']['values']
plugin_a1.id,
attributes['plugin_a']['metadata']['chosen_id']
)
self.assertEqual(
str(plugin_b.id),
attributes['plugin_b']['plugin_versions']['value']
plugin_b.id,
attributes['plugin_b']['metadata']['chosen_id']
)
def test_get_plugins_attributes_when_cluster_is_not_locked(self):
@ -377,69 +375,67 @@ class TestPluginManager(base.BaseIntegrationTest):
cluster=cluster, enabled=True
)
plugin_b = self.env.create_plugin(
name='plugin_b', title='plugin_a_title', cluster=cluster
name='plugin_b', title='plugin_a_title', cluster=cluster,
attributes_metadata={
'metadata': {
'restrictions': [
{
"condition": "cluster:net_provider != 'neutron'",
"action": "hide"
}
]
}
}
)
self.assertFalse(plugin_a1.is_hotpluggable)
self.assertTrue(plugin_a2.is_hotpluggable)
self.assertFalse(plugin_b.is_hotpluggable)
self.assertFalse(cluster.is_locked)
attributes = PluginManager.get_plugins_attributes(
cluster, True, True
)
self.assertItemsEqual(
['plugin_a', 'plugin_b'], attributes
)
self.assertTrue(
attributes['plugin_a']['metadata']['always_editable']
)
self.assertItemsEqual(
[
{
'data': str(plugin_a1.id),
'description': '',
'label': plugin_a1.version,
'restrictions': [
{
'action': 'disable',
'condition': 'cluster:is_locked'
}
],
},
{
'data': str(plugin_a2.id),
'description': '',
'label': plugin_a2.version
}
],
attributes['plugin_a']['plugin_versions']['values']
)
self.assertEqual(
str(plugin_a1.id),
attributes['plugin_a']['plugin_versions']['value']
)
self.assertNotIn(
'always_editable', attributes['plugin_b']['metadata']
)
self.assertItemsEqual(
[
{
'restrictions': [
{
'action': 'disable',
'condition': 'cluster:is_locked'
}
],
'data': str(plugin_b.id),
'description': '',
'label': plugin_b.version,
},
],
attributes['plugin_b']['plugin_versions']['values']
cluster, all_versions=True, default=True
)
pl_a1 = attributes['plugin_a']['metadata']['versions'][0]
pl_a2 = attributes['plugin_a']['metadata']['versions'][1]
pl_b = attributes['plugin_b']['metadata']['versions'][0]
self.assertItemsEqual(['plugin_a', 'plugin_b'], attributes)
self.assertItemsEqual(
{
'plugin_id': plugin_a1.id,
'plugin_version': plugin_a1.version,
'always_editable': False
}, pl_a1['metadata']
)
self.assertItemsEqual(
{
'plugin_id': plugin_a2.id,
'plugin_version': plugin_a2.version,
'always_editable': True,
},
pl_a2['metadata']
)
self.assertItemsEqual(
{
'plugin_id': plugin_b.id,
'plugin_version': plugin_b.version,
'always_editable': False,
'restrictions': [
{
"condition": "cluster:net_provider != 'neutron'",
"action": "hide"
}
]
}, pl_b['metadata']
)
self.assertEqual(
str(plugin_b.id),
attributes['plugin_b']['plugin_versions']['value']
plugin_a1.id,
attributes['plugin_a']['metadata']['chosen_id']
)
self.assertEqual(
plugin_b.id,
attributes['plugin_b']['metadata']['chosen_id']
)

View File

@ -123,8 +123,7 @@ class BasePluginTest(base.BaseIntegrationTest):
editable_attrs = objects.Cluster.get_editable_attributes(
cluster, all_plugins_versions=True)
editable_attrs[plugin_name]['metadata']['enabled'] = enabled
editable_attrs[plugin_name]['plugin_versions']['value'] = \
str(plugin_id)
editable_attrs[plugin_name]['metadata']['chosen_id'] = plugin_id
resp = self.app.put(
base.reverse('ClusterAttributesHandler',
@ -229,6 +228,12 @@ class TestPluginsApi(BasePluginTest):
del_resp = self.delete_plugin(resp.json['id'])
self.assertEqual(del_resp.status_code, 204)
def test_delete_unused_plugin(self):
self.create_cluster()
resp = self.create_plugin()
del_resp = self.delete_plugin(resp.json['id'])
self.assertEqual(del_resp.status_code, 204)
def test_no_delete_of_used_plugin(self):
resp = self.create_plugin()
plugin = objects.Plugin.get_by_uid(resp.json['id'])
@ -262,8 +267,8 @@ class TestPluginsApi(BasePluginTest):
default_attributes.json_body['editable'])
def test_attributes_after_plugin_is_created(self):
sample = dict({
"attributes_metadata": {
sample = dict(
attributes_metadata={
"attr_text": {
"value": "value",
"type": "text",
@ -271,13 +276,14 @@ class TestPluginsApi(BasePluginTest):
"weight": 25,
"label": "label"
}
}
}, **self.sample_plugin)
plugin = self.create_plugin(sample=sample).json_body
}, **self.sample_plugin)
self.create_plugin(sample=sample).json_body
cluster = self.create_cluster()
editable = self.default_attributes(cluster).json_body['editable']
attr_name = "#{0}_{1}".format(plugin['id'], 'attr_text')
self.assertIn(attr_name, editable[self.sample_plugin['name']])
self.assertIn(
'attr_text',
editable[self.sample_plugin['name']]['metadata']['versions'][0]
)
def test_plugins_multiversioning(self):
def create_with_version(plugin_version):

View File

@ -164,3 +164,12 @@ class TestClusterPlugins(ExtraFunctions):
enabled_plugin = ClusterPlugins.get_enabled(cluster.id).first()
self.assertEqual(plugin.id, enabled_plugin.id)
def test_is_plugin_used(self):
self._create_test_plugins()
cluster = self._create_test_cluster()
plugin = ClusterPlugins.get_connected_plugins(cluster).first()
self.assertFalse(ClusterPlugins.is_plugin_used(plugin.id))
ClusterPlugins.set_attributes(cluster.id, plugin.id, enabled=True)
self.assertTrue(ClusterPlugins.is_plugin_used(plugin.id))