Store attributes of plugins in a separate table

This is redesign of plugins architecture in order to store
the plugin's attributes in a separate table, not in cluster
attributes, so it will be possible to remove connection between
plugin and cluster when a plugin gets deleted.

Added ability to work with different versions of a plugin.
User can choose the preferred version in UI.

The test "test_plugin_generator" was removed because no longer
relevant.

Closes-Bug: #1440046
Implements: blueprint store-plugins-attributes

Change-Id: I52115f130bf1c7c80c66e18d0bf9f7acb16dd56c
This commit is contained in:
Vitaliy Mygal 2015-09-03 14:18:20 +03:00 committed by Vitalii Myhal
parent a5f4c44d08
commit a25db9a2c6
32 changed files with 1346 additions and 384 deletions

View File

@ -133,7 +133,10 @@ class ClusterAttributesHandler(BaseHandler):
if not cluster.attributes:
raise self.http(500, "No attributes found!")
return objects.Cluster.get_editable_attributes(cluster)
return {
'editable': objects.Cluster.get_editable_attributes(
cluster, all_plugins_versions=True)
}
def PUT(self, cluster_id):
""":returns: JSONized Cluster attributes.
@ -170,8 +173,8 @@ class ClusterAttributesHandler(BaseHandler):
# we want to change and block an entire operation if there
# one with always_editable=False.
if cluster.is_locked:
attrs = objects.Cluster.get_editable_attributes(cluster)
editable = attrs['editable']
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
@ -183,7 +186,10 @@ class ClusterAttributesHandler(BaseHandler):
"after or during deployment.".format(group_name)))
objects.Cluster.patch_attributes(cluster, data)
return objects.Cluster.get_editable_attributes(cluster)
return {
'editable': objects.Cluster.get_editable_attributes(
cluster, all_plugins_versions=True)
}
class ClusterAttributesDefaultsHandler(BaseHandler):

View File

@ -25,6 +25,7 @@ revision = '43b2cb64dae6'
down_revision = '1e50a4903910'
from alembic import op
from nailgun.db.sqlalchemy.models import fields
from oslo_serialization import jsonutils
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql as psql
@ -106,9 +107,11 @@ def upgrade():
task_names_upgrade()
add_node_discover_error_upgrade()
upgrade_neutron_parameters()
upgrade_cluster_plugins()
def downgrade():
downgrade_cluster_plugins()
downgrade_neutron_parameters()
add_node_discover_error_downgrade()
task_names_downgrade()
@ -320,3 +323,154 @@ def upgrade_neutron_parameters():
def downgrade_neutron_parameters():
op.drop_column('neutron_config', 'floating_name')
op.drop_column('neutron_config', 'internal_name')
def upgrade_cluster_plugins():
op.alter_column(
'cluster_plugins',
'cluster_id',
nullable=False
)
op.drop_constraint(
'cluster_plugins_cluster_id_fkey',
'cluster_plugins',
type_='foreignkey'
)
op.create_foreign_key(
'cluster_plugins_cluster_id_fkey',
'cluster_plugins', 'clusters',
['cluster_id'], ['id'],
ondelete='CASCADE'
)
op.add_column(
'cluster_plugins',
sa.Column(
'enabled',
sa.Boolean,
nullable=False,
server_default='false'
)
)
op.add_column(
'cluster_plugins',
sa.Column(
'attributes',
fields.JSON(),
nullable=False,
server_default='{}'
)
)
# Iterate over all editable cluster attributes,
# and set entry in 'cluster_plugins' table
connection = op.get_bind()
q_get_plugins = sa.text('''
SELECT id, name FROM plugins
''')
q_get_cluster_attributes = sa.text('''
SELECT cluster_id, editable FROM attributes
''')
q_update_cluster_attributes = sa.text('''
UPDATE attributes
SET editable = :editable
WHERE cluster_id = :cluster_id
''')
q_get_cluster_plugins = sa.text('''
SELECT id FROM cluster_plugins
WHERE cluster_id = :cluster_id AND plugin_id = :plugin_id
''')
q_update_cluster_plugins = sa.text('''
UPDATE cluster_plugins
SET enabled = :enabled, attributes = :attributes
WHERE cluster_id = :cluster_id AND plugin_id = :plugin_id
''')
q_insert_cluster_plugins = sa.text('''
INSERT INTO cluster_plugins
(cluster_id, plugin_id, enabled, attributes)
VALUES
(:cluster_id, :plugin_id, :enabled, :attributes)
''')
plugins = list(connection.execute(q_get_plugins))
for cluster_id, editable in connection.execute(q_get_cluster_attributes):
editable = jsonutils.loads(editable)
for plugin_id, plugin_name in plugins:
if plugin_name in editable:
attributes = editable.pop(plugin_name)
metadata = attributes.pop('metadata')
if connection.execute(q_get_cluster_plugins,
cluster_id=cluster_id,
plugin_id=plugin_id).first():
action = q_update_cluster_plugins
else:
action = q_insert_cluster_plugins
connection.execute(
action,
cluster_id=cluster_id,
plugin_id=plugin_id,
enabled=metadata['enabled'],
attributes=jsonutils.dumps(attributes)
)
connection.execute(
q_update_cluster_attributes,
cluster_id=cluster_id,
editable=jsonutils.dumps(editable)
)
def downgrade_cluster_plugins():
connection = op.get_bind()
q_get_cluster_attributes = sa.text('''
SELECT clusters.id, attributes.editable
FROM attributes JOIN clusters ON (attributes.cluster_id = clusters.id)
''')
q_get_plugins = sa.text('''
SELECT plugins.id, plugins.name, plugins.title,
cluster_plugins.enabled, cluster_plugins.attributes
FROM plugins JOIN cluster_plugins
ON (plugins.id = cluster_plugins.plugin_id)
WHERE cluster_plugins.cluster_id = :cluster_id
''')
q_update_cluster_attributes = sa.text('''
UPDATE attributes
SET editable = :editable
WHERE cluster_id = :cluster_id
''')
for cluster_id, editable in connection.execute(q_get_cluster_attributes):
editable = jsonutils.loads(editable)
plugins = connection.execute(q_get_plugins, cluster_id=cluster_id)
for p_id, p_name, p_title, p_enabled, p_attr in plugins:
p_attr = jsonutils.loads(p_attr)
p_attr['metadata'].update({
'plugin_id': p_id,
'enabled': p_enabled,
'label': p_title
})
editable[p_name] = p_attr
connection.execute(q_update_cluster_attributes,
cluster_id=cluster_id,
editable=jsonutils.dumps(editable))
op.drop_column('cluster_plugins', 'attributes')
op.drop_column('cluster_plugins', 'enabled')
op.drop_constraint(
'cluster_plugins_cluster_id_fkey',
'cluster_plugins',
type_='foreignkey'
)
op.create_foreign_key(
'cluster_plugins_cluster_id_fkey',
'cluster_plugins', 'clusters',
['cluster_id'], ['id']
)
op.alter_column(
'cluster_plugins',
'cluster_id',
nullable=None
)

View File

@ -12,7 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
from sqlalchemy import Boolean
from sqlalchemy import Column
from sqlalchemy.ext.mutable import MutableDict
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import String
@ -28,10 +30,25 @@ from nailgun.db.sqlalchemy.models.fields import JSON
class ClusterPlugins(Base):
__tablename__ = 'cluster_plugins'
id = Column(Integer, primary_key=True)
plugin_id = Column(Integer, ForeignKey('plugins.id', ondelete='CASCADE'),
plugin_id = Column(Integer,
ForeignKey('plugins.id', ondelete='CASCADE'),
nullable=False)
cluster_id = Column(Integer, ForeignKey('clusters.id'))
cluster_id = Column(Integer,
ForeignKey('clusters.id', ondelete='CASCADE'),
nullable=False)
enabled = Column(Boolean,
nullable=False,
default=False,
server_default='false')
# Initially, 'attributes' is a copy of 'Plugin.attributes_metadata'.
# We need this column in order to store in there the modified (by user)
# version of attributes, because we don't want to store them in cluster
# attributes with no chance to remove.
attributes = Column(MutableDict.as_mutable(JSON),
nullable=False,
server_default='{}')
class Plugin(Base):

View File

@ -7,7 +7,7 @@
"title": "The Logging, Monitoring and Alerting (LMA) Collector Plugin",
"version": "0.7.0",
"description": "Collect logs, metrics and notifications from system and OpenStack services and forward that information to external backends such as Elasticsearch and InfluxDB.",
"fuel_version": ["6.1"],
"fuel_version": ["7.0"],
"authors": ["Mirantis Inc."],
"licenses": ["Apache License Version 2.0"],
"homepage": "https://github.com/openstack/fuel-plugin-lma-collector",
@ -33,9 +33,25 @@
"mode": ["ha"],
"deployment_scripts_path": "deployment_scripts/",
"repository_path": "repositories/centos"
},
{
"os": "ubuntu",
"version": "2015.1-8.0",
"mode": ["ha"],
"deployment_scripts_path": "deployment_scripts/",
"repository_path": "repositories/ubuntu"
}
],
"package_version": "2.0.0"
"package_version": "2.0.0",
"attributes_metadata": {
"logging_text": {
"value": "value",
"type": "text",
"description": "description",
"weight": 25,
"label": "label"
}
}
}
},
{
@ -63,6 +79,13 @@
"mode": ["ha"],
"deployment_scripts_path": "deployment_scripts/",
"repository_path": "repositories/centos"
},
{
"os": "ubuntu",
"version": "2015.1-8.0",
"mode": ["ha"],
"deployment_scripts_path": "deployment_scripts/",
"repository_path": "repositories/ubuntu"
}
],
"package_version": "2.0.0"
@ -76,7 +99,7 @@
"title": "Zabbix for Fuel",
"version": "1.0.0",
"description": "Enables Zabbix Monitoring. For information how to access Zabbix UI refer to Zabbix plugin User Guide. Zabbix URL schema is http://<VIP>/zabbix",
"fuel_version": ["6.1"],
"fuel_version": ["7.0"],
"authors": ["Dmitry Klenov <dklenov@mirantis.com>", "Piotr Misiak <pmisiak@mirantis.com>", "Szymon Banka <sbanka@mirantis.com>", "Bartosz Kupidura <bkupidura@mirantis.com>", "Alexander Zatserklyany <azatserklyany@mirantis.com>"],
"licenses": ["Apache License Version 2.0"],
"homepage": "https://github.com/openstack/fuel-plugin-external-zabbix",
@ -95,9 +118,80 @@
"mode": ["ha"],
"deployment_scripts_path": "deployment_scripts/",
"repository_path": "repositories/centos"
},
{
"os": "ubuntu",
"version": "2015.1-8.0",
"mode": ["ha"],
"deployment_scripts_path": "deployment_scripts/",
"repository_path": "repositories/ubuntu"
}
],
"package_version": "2.0.0"
"package_version": "2.0.0",
"attributes_metadata": {
"zabbix_text_1": {
"value": "value 1.1",
"type": "text",
"description": "description 1.1",
"weight": 25,
"label": "label 1.1"
}
}
}
},
{
"pk": 4,
"model": "nailgun.plugin",
"fields": {
"name": "zabbix_monitoring",
"title": "Zabbix for Fuel",
"version": "2.0.0",
"description": "Enables Zabbix Monitoring. For information how to access Zabbix UI refer to Zabbix plugin User Guide. Zabbix URL schema is http://<VIP>/zabbix",
"fuel_version": ["7.0"],
"authors": ["Dmitry Klenov <dklenov@mirantis.com>", "Piotr Misiak <pmisiak@mirantis.com>", "Szymon Banka <sbanka@mirantis.com>", "Bartosz Kupidura <bkupidura@mirantis.com>", "Alexander Zatserklyany <azatserklyany@mirantis.com>"],
"licenses": ["Apache License Version 2.0"],
"homepage": "https://github.com/openstack/fuel-plugin-external-zabbix",
"groups": ["monitoring"],
"releases": [
{
"os": "ubuntu",
"version": "2014.2-6.0",
"mode": ["ha"],
"deployment_scripts_path": "deployment_scripts/",
"repository_path": "repositories/ubuntu"
},
{
"os": "centos",
"version": "2014.2-6.1",
"mode": ["ha"],
"deployment_scripts_path": "deployment_scripts/",
"repository_path": "repositories/centos"
},
{
"os": "ubuntu",
"version": "2015.1-8.0",
"mode": ["ha"],
"deployment_scripts_path": "deployment_scripts/",
"repository_path": "repositories/ubuntu"
}
],
"package_version": "2.0.0",
"attributes_metadata": {
"zabbix_text_1": {
"value": "value 2.1",
"type": "text",
"description": "description 2.1",
"weight": 25,
"label": "label 2.1"
},
"zabbix_text_2": {
"value": "value 2.2",
"type": "text",
"description": "description 2.2",
"weight": 26,
"label": "label 2.2"
}
}
}
}
]

View File

@ -50,6 +50,7 @@ from nailgun.objects.node_group import NodeGroupCollection
from nailgun.objects.plugin import Plugin
from nailgun.objects.plugin import PluginCollection
from nailgun.objects.plugin import ClusterPlugins
from nailgun.objects.network_group import NetworkGroup
from nailgun.objects.network_group import NetworkGroupCollection

View File

@ -24,7 +24,8 @@ from distutils.version import StrictVersion
import six
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql as psql
from sqlalchemy.orm.exc import MultipleResultsFound
from sqlalchemy.orm.exc import NoResultFound
from nailgun import consts
from nailgun.db import db
@ -35,6 +36,7 @@ from nailgun.extensions import fire_callback_on_node_collection_delete
from nailgun.logger import logger
from nailgun.objects import NailgunCollection
from nailgun.objects import NailgunObject
from nailgun.objects.plugin import ClusterPlugins
from nailgun.objects import Release
from nailgun.objects.serializers.cluster import ClusterSerializer
from nailgun.plugins.manager import PluginManager
@ -166,7 +168,6 @@ class Cluster(NailgunObject):
if assign_nodes:
cls.update_nodes(new_cluster, assign_nodes)
except (
errors.OutOfVLANs,
errors.OutOfIPs,
@ -177,6 +178,8 @@ class Cluster(NailgunObject):
db().flush()
ClusterPlugins.add_compatible_plugins(new_cluster)
return new_cluster
@classmethod
@ -235,31 +238,65 @@ class Cluster(NailgunObject):
:returns: Dict object
"""
editable = instance.release.attributes_metadata.get("editable")
# when attributes created we need to understand whether should plugin
# be applied for created cluster
plugin_attrs = PluginManager.get_plugin_attributes(instance)
# Add default attributes of connected plugins
plugin_attrs = PluginManager.get_plugins_attributes(
instance, all_versions=True, default=True)
editable = dict(plugin_attrs, **editable)
editable = traverse(editable, AttributesGenerator, {
'cluster': instance,
'settings': settings,
})
return editable
@classmethod
def get_attributes(cls, instance):
"""Get attributes for current Cluster instance
def get_attributes(cls, instance, all_plugins_versions=False):
"""Get attributes for current Cluster instance.
:param instance: Cluster instance
:returns: Attributes instance
:param all_plugins_versions: Get attributes of all versions of plugins
:returns: dict
"""
return db().query(models.Attributes).filter(
models.Attributes.cluster_id == instance.id
).first()
try:
attrs = db().query(models.Attributes).filter(
models.Attributes.cluster_id == instance.id
).one()
except MultipleResultsFound:
raise errors.InvalidData(
u"Multiple rows with attributes were found for cluster '{0}'"
.format(instance.name)
)
except NoResultFound:
raise errors.InvalidData(
u"No attributes were found for cluster '{0}'"
.format(instance.name)
)
attrs = dict(attrs)
# Merge plugins attributes into editable ones
plugin_attrs = PluginManager.get_plugins_attributes(
instance, all_versions=all_plugins_versions)
plugin_attrs = traverse(plugin_attrs, AttributesGenerator, {
'cluster': instance,
'settings': settings,
})
attrs['editable'].update(plugin_attrs)
return attrs
@classmethod
def get_editable_attributes(cls, instance, all_plugins_versions=False):
"""Get editable attributes for current Cluster instance.
:param instance: Cluster instance
:param all_plugins_versions: Get attributes of all versions of plugins
:return: dict
"""
return cls.get_attributes(instance, all_plugins_versions)['editable']
@classmethod
def update_attributes(cls, instance, data):
PluginManager.process_cluster_attributes(instance, data['editable'])
for key, value in data.iteritems():
setattr(instance.attributes, key, value)
cls.add_pending_changes(instance, "attributes")
@ -274,23 +311,16 @@ class Cluster(NailgunObject):
cls.get_network_manager(instance).update_restricted_networks(instance)
db().flush()
@classmethod
def get_editable_attributes(cls, instance):
attrs = cls.get_attributes(instance)
editable = attrs.editable
return {'editable': editable}
@classmethod
def get_updated_editable_attributes(cls, instance, data):
"""Same as get_editable_attributes but also merges given data.
:param instance: Cluster object
:param data: dict
:return: dict
:returns: dict
"""
return {'editable': dict_merge(
cls.get_editable_attributes(instance)['editable'],
cls.get_editable_attributes(instance),
data.get('editable', {})
)}
@ -936,7 +966,7 @@ class Cluster(NailgunObject):
@classmethod
def is_vmware_enabled(cls, instance):
"""Check if current cluster supports vmware configuration."""
attributes = cls.get_attributes(instance).editable
attributes = cls.get_editable_attributes(instance)
return attributes.get('common', {}).get('use_vcenter', {}).get('value')
@staticmethod

View File

@ -14,27 +14,48 @@
# License for the specific language governing permissions and limitations
# under the License.
import six
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.sqlalchemy import models
from nailgun.objects import NailgunCollection
from nailgun.objects import NailgunObject
from nailgun.objects.serializers.plugin import PluginSerializer
class Plugin(base.NailgunObject):
class Plugin(NailgunObject):
model = plugin_db_model.Plugin
serializer = plugin.PluginSerializer
model = models.Plugin
serializer = PluginSerializer
@classmethod
def create(cls, data):
new_plugin = super(Plugin, cls).create(data)
# FIXME (vmygal): This is very ugly hack and it must be fixed ASAP.
# Need to remove the syncing of plugin metadata from here.
# All plugin metadata must be sent via 'data' argument of this
# function and it must be fixed in 'python-fuelclient' repository.
from nailgun.plugins.adapters import wrap_plugin
plugin_adapter = wrap_plugin(new_plugin)
plugin_adapter.sync_metadata_to_db()
ClusterPlugins.add_compatible_clusters(new_plugin)
return new_plugin
@classmethod
def get_by_name_version(cls, name, version):
return db().query(cls.model).\
filter_by(name=name, version=version).first()
return db()\
.query(cls.model)\
.filter_by(name=name, version=version)\
.first()
class PluginCollection(base.NailgunCollection):
class PluginCollection(NailgunCollection):
single = Plugin
@ -68,12 +89,190 @@ class PluginCollection(base.NailgunCollection):
@classmethod
def get_by_uids(cls, plugin_ids):
"""Returns plugins by given ids.
"""Returns plugins by given IDs.
:param plugin_ids: list of plugin ids
:param plugin_ids: list of plugin IDs
:type plugin_ids: list
:returns: iterable (SQLAlchemy query)
"""
return cls.filter_by_id_list(
cls.all(), plugin_ids)
return cls.filter_by_id_list(cls.all(), plugin_ids)
class ClusterPlugins(NailgunObject):
model = models.ClusterPlugins
@classmethod
def validate_compatibility(cls, cluster, plugin):
"""Validates if plugin is compatible with cluster.
- validates operating systems
- modes of clusters (simple or ha)
- release version
:param cluster: A cluster instance
:type cluster: nailgun.objects.cluster.Cluster
:param plugin: A plugin instance
:type plugin: nailgun.objects.plugin.Plugin
:return: True if compatible, False if not
:rtype: bool
"""
for release in plugin.releases:
os_compat = cluster.release.operating_system.lower()\
== release['os'].lower()
# plugin writer should be able to specify ha in release['mode']
# and know nothing about ha_compact
mode_compat = any(mode in cluster.mode for mode in release['mode'])
release_version_compat = cls.is_release_version_compatible(
cluster.release.version, release['version'])
if all((os_compat, mode_compat, release_version_compat)):
return True
return False
@staticmethod
def is_release_version_compatible(rel_version, plugin_rel_version):
"""Checks if release version is compatible with plugin version.
:param rel_version: Release version
:type rel_version: str
:param plugin_rel_version: Plugin release version
:type plugin_rel_version: str
:return: True if compatible, False if not
:rtype: bool
"""
rel_os, rel_fuel = rel_version.split('-')
plugin_os, plugin_rel = plugin_rel_version.split('-')
return rel_os.startswith(plugin_os) and rel_fuel.startswith(plugin_rel)
@classmethod
def get_compatible_plugins(cls, cluster):
"""Returns a list of plugins that are compatible with a given cluster.
:param cluster: A cluster instance
:type cluster: nailgun.objects.cluster.Cluster
:return: A list of plugin instances
:rtype: list
"""
return list(six.moves.filter(
lambda p: cls.validate_compatibility(cluster, p),
PluginCollection.all()))
@classmethod
def add_compatible_plugins(cls, cluster):
"""Populates 'cluster_plugins' table with compatible plugins.
:param cluster: A cluster instance
:type cluster: nailgun.objects.cluster.Cluster
"""
for plugin in cls.get_compatible_plugins(cluster):
cls.create({
'cluster_id': cluster.id,
'plugin_id': plugin.id,
'enabled': False,
'attributes': plugin.attributes_metadata
})
@classmethod
def get_compatible_clusters(cls, plugin):
"""Returns a list of clusters that are compatible with a given plugin.
:param plugin: A plugin instance
:type plugin: nailgun.objects.plugin.Plugin
:return: A list of cluster instances
:rtype: list
"""
return list(six.moves.filter(
lambda c: cls.validate_compatibility(c, plugin),
db().query(models.Cluster)))
@classmethod
def add_compatible_clusters(cls, plugin):
"""Populates 'cluster_plugins' table with compatible cluster.
:param plugin: A plugin instance
:type plugin: nailgun.objects.plugin.Plugin
"""
for cluster in cls.get_compatible_clusters(plugin):
cls.create({
'cluster_id': cluster.id,
'plugin_id': plugin.id,
'enabled': False,
'attributes': plugin.attributes_metadata
})
@classmethod
def set_attributes(cls, cluster_id, plugin_id, enabled=None, attrs=None):
"""Sets plugin's attributes in cluster_plugins table.
:param cluster_id: Cluster ID
:type cluster_id: int
:param plugin_id: Plugin ID
:type plugin_id: int
:param enabled: Enabled or disabled plugin for given cluster
:type enabled: bool
:param attrs: Plugin metadata
:type attrs: dict
"""
params = {}
if enabled is not None:
params['enabled'] = enabled
if attrs is not None:
params['attributes'] = attrs
db().query(cls.model)\
.filter_by(plugin_id=plugin_id, cluster_id=cluster_id)\
.update(params, synchronize_session='fetch')
db().flush()
@classmethod
def get_connected_plugins(cls, cluster_id):
"""Returns plugins connected with given cluster.
:param cluster_id: Cluster ID
:type cluster_id: int
:returns: List of plugins
:rtype: iterable (SQLAlchemy query)
"""
return db().query(
models.Plugin.id,
models.Plugin.name,
models.Plugin.title,
models.Plugin.version,
cls.model.enabled,
models.Plugin.attributes_metadata,
cls.model.attributes
).join(cls.model)\
.filter(cls.model.cluster_id == cluster_id)\
.order_by(models.Plugin.name)\
.order_by(models.Plugin.version)
@classmethod
def get_connected_clusters(cls, plugin_id):
"""Returns clusters connected with given plugin.
:param plugin_id: Plugin ID
:type plugin_id: int
:returns: List of clusters
:rtype: iterable (SQLAlchemy query)
"""
return db()\
.query(models.Cluster)\
.join(cls.model)\
.filter(cls.model.plugin_id == plugin_id)\
.order_by(models.Cluster.name)
@classmethod
def get_enabled(cls, cluster_id):
"""Returns a list of plugins enabled for a given cluster.
:param cluster_id: Cluster ID
:type cluster_id: int
:returns: List of plugin instances
:rtype: iterable (SQLAlchemy query)
"""
return db().query(models.Plugin)\
.join(cls.model)\
.filter(cls.model.cluster_id == cluster_id)\
.filter(cls.model.enabled.is_(True))\
.all()

View File

@ -74,13 +74,13 @@ class NeutronNetworkDeploymentSerializer(NetworkDeploymentSerializer):
enabled.
"""
# Get Mellanox data
neutron_mellanox_data = \
Cluster.get_attributes(cluster).editable\
neutron_mellanox_data = \
Cluster.get_editable_attributes(cluster)\
.get('neutron_mellanox', {})
# Get storage data
storage_data = \
Cluster.get_attributes(cluster).editable.get('storage', {})
Cluster.get_editable_attributes(cluster).get('storage', {})
# Get network manager
nm = Cluster.get_network_manager(cluster)
@ -192,7 +192,7 @@ class NeutronNetworkDeploymentSerializer(NetworkDeploymentSerializer):
if cluster.release.operating_system == 'RHEL':
attrs['amqp'] = {'provider': 'qpid-rh'}
cluster_attrs = Cluster.get_attributes(cluster).editable
cluster_attrs = Cluster.get_editable_attributes(cluster)
if 'nsx_plugin' in cluster_attrs and \
cluster_attrs['nsx_plugin']['metadata']['enabled']:
attrs['L2']['provider'] = 'nsx'
@ -500,7 +500,7 @@ class NeutronNetworkDeploymentSerializer(NetworkDeploymentSerializer):
}
# Set non-default ml2 configurations
attrs = Cluster.get_attributes(cluster).editable
attrs = Cluster.get_editable_attributes(cluster)
if 'neutron_mellanox' in attrs and \
attrs['neutron_mellanox']['plugin']['value'] == 'ethernet':
res['mechanism_drivers'] = 'mlnx,openvswitch'
@ -515,7 +515,7 @@ class NeutronNetworkDeploymentSerializer(NetworkDeploymentSerializer):
l3 = {
"use_namespaces": True
}
attrs = Cluster.get_attributes(cluster).editable
attrs = Cluster.get_editable_attributes(cluster)
if 'nsx_plugin' in attrs and \
attrs['nsx_plugin']['metadata']['enabled']:
dhcp_attrs = l3.setdefault('dhcp_agent', {})

View File

@ -109,7 +109,8 @@ class ExpressionBasedTask(DeploymentHook):
@property
def _expression_context(self):
return {'cluster': self.cluster,
'settings': self.cluster.attributes.editable}
'settings':
objects.Cluster.get_editable_attributes(self.cluster)}
def should_execute(self):
if 'condition' not in self.task:
@ -259,7 +260,7 @@ class GenerateHaproxyKeys(GenericRolesHook):
uids = self.get_uids()
self.task['parameters']['cmd'] = self.task['parameters']['cmd'].format(
CLUSTER_ID=self.cluster.id,
CN_HOSTNAME=self.cluster.attributes.editable
CN_HOSTNAME=objects.Cluster.get_editable_attributes(self.cluster)
['public_ssl']['hostname']['value'])
yield templates.make_shell_task(uids, self.task)

View File

@ -26,6 +26,7 @@ import yaml
from nailgun.errors import errors
from nailgun.logger import logger
from nailgun.objects.component import Component
from nailgun.objects.plugin import ClusterPlugins
from nailgun.objects.plugin import Plugin
from nailgun.settings import settings
@ -49,9 +50,6 @@ class PluginAdapterBase(object):
self.plugin_path = os.path.join(
settings.PLUGINS_PATH,
self.path_name)
self.config_file = os.path.join(
self.plugin_path,
self.environment_config_name)
self.tasks = []
@abc.abstractmethod
@ -92,83 +90,13 @@ class PluginAdapterBase(object):
item['role'].append('primary-controller')
return data
def get_plugin_attributes(self, cluster):
"""Should be used for initial configuration uploading to custom storage
Will be invoked in 2 cases:
1. Cluster is created but there was no plugins in system
on that time, so when plugin is uploaded we need to iterate
over all clusters and decide if plugin should be applied
2. Plugins is uploaded before cluster creation, in this case
we will iterate over all plugins and upload configuration for them
In this case attributes will be added to same cluster attributes
model and stored in editable field
"""
config = {}
if os.path.exists(self.config_file):
config = self._load_config(self.config_file)
if self.validate_cluster_compatibility(cluster):
attrs = config.get("attributes", {})
self.update_metadata(attrs)
return {self.plugin.name: attrs}
return {}
def validate_cluster_compatibility(self, cluster):
"""Validates if plugin is compatible with cluster
- validates operating systems
- modes of clusters (simple or ha)
- release version
"""
for release in self.plugin.releases:
os_compat = (cluster.release.operating_system.lower()
== release['os'].lower())
# plugin writer should be able to specify ha in release['mode']
# and know nothing about ha_compact
mode_compat = any(mode in cluster.mode for mode in release['mode'])
release_version_compat = self._is_release_version_compatible(
cluster.release.version, release['version'])
if all((os_compat, mode_compat, release_version_compat)):
return True
return False
def _is_release_version_compatible(self, rel_version, plugin_rel_version):
"""Checks if release version is compatible with plugin version
:param str rel_version: release version
:param str plugin_rel_version: plugin release version
:returns: True if compatible, Fals if not
"""
rel_os, rel_fuel = rel_version.split('-')
plugin_os, plugin_rel = plugin_rel_version.split('-')
return rel_os.startswith(plugin_os) and rel_fuel.startswith(plugin_rel)
def update_metadata(self, attributes):
"""Overwrites only default values in metadata
Plugin should be able to provide UI "native" conditions
to enable/disable plugin on UI itself
"""
attributes.setdefault('metadata', {})
attributes['metadata'].update(self.default_metadata)
return attributes
@property
def default_metadata(self):
return {u'enabled': False, u'toggleable': True,
u'weight': 70, u'label': self.plugin.title,
'plugin_id': self.plugin.id}
def set_cluster_tasks(self):
"""Load plugins provided tasks and set them to instance tasks variable
Provided tasks are loaded from tasks config file.
"""
task_yaml = os.path.join(
self.plugin_path,
self.task_config_name)
self.plugin_path, self.task_config_name)
if os.path.exists(task_yaml):
self.tasks = self._load_tasks(task_yaml)
@ -222,13 +150,14 @@ class PluginAdapterBase(object):
def get_release_info(self, release):
"""Get plugin release information which corresponds to given release"""
os = release.operating_system.lower()
rel_os = release.operating_system.lower()
version = release.version
release_info = filter(
lambda r: (
r['os'] == os and
self._is_release_version_compatible(version, r['version'])),
r['os'] == rel_os and
ClusterPlugins.is_release_version_compatible(version,
r['version'])),
self.plugin.releases)
return release_info[0]
@ -334,6 +263,8 @@ class PluginAdapterV3(PluginAdapterV2):
# Plugin columns have constraints for nullable data, so
# we need to check it
if attribute_data:
if attribute == 'attributes_metadata':
attribute_data = attribute_data['attributes']
data_to_update[attribute] = attribute_data
Plugin.update(self.plugin, data_to_update)

View File

@ -12,11 +12,13 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
import six
from six.moves import map
from nailgun.errors import errors
from nailgun.logger import logger
from nailgun.objects.plugin import ClusterPlugins
from nailgun.objects.plugin import Plugin
from nailgun.objects.plugin import PluginCollection
from nailgun.plugins.adapters import wrap_plugin
@ -25,60 +27,135 @@ from nailgun.plugins.adapters import wrap_plugin
class PluginManager(object):
@classmethod
def process_cluster_attributes(cls, cluster, attrs):
def process_cluster_attributes(cls, cluster, 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: Cluster object
:param attrs: dictionary with cluster attributes
:param cluster: A cluster instance
:type cluster: nailgun.objects.cluster.Cluster
:param attributes: Cluster attributes
:type attributes: dict
"""
for key, attr in six.iteritems(attrs):
cls._process_attr(cluster, attr)
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
for attrs in six.itervalues(attributes):
if not isinstance(attrs, dict):
continue
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)
if not plugin:
logger.warning(
'Plugin with id "%s" is not found, skip it', pid)
continue
enabled = plugin_enabled and\
str(plugin.id) == plugin_versions['value']
ClusterPlugins.set_attributes(
cluster.id, plugin.id, enabled=enabled,
attrs=_convert_attrs(plugin.id, attrs)
if enabled or default else None
)
@classmethod
def _process_attr(cls, cluster, attr):
if not isinstance(attr, dict):
return
def get_plugins_attributes(
cls, cluster, all_versions=False, default=False):
"""Gets attributes of all plugins connected with given cluster.
metadata = attr.get('metadata', {})
plugin_id = metadata.get('plugin_id')
:param cluster: A cluster instance
:type cluster: nailgun.objects.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)
:type default: bool
:return: Plugins attributes
:rtype: dict
"""
versions = {
u'type': u'radio',
u'values': [],
u'weight': 10,
u'value': None,
u'label': 'Choose a plugin version'
}
if not plugin_id:
return
def _convert_attr(pid, name, title, attr):
restrictions = attr.setdefault('restrictions', [])
restrictions.append({
'action': 'hide',
'condition': "settings:{0}.plugin_versions.value != '{1}'"
.format(name, pid)
})
return "#{0}_{1}".format(pid, title), attr
plugin = Plugin.get_by_uid(plugin_id)
if not plugin:
logger.warning('Plugin with id "%s" is not found, skip it',
plugin_id)
return
plugins_attributes = {}
for pid, name, title, version, enabled, default_attrs, cluster_attrs\
in ClusterPlugins.get_connected_plugins(cluster.id):
if all_versions:
enabled = enabled and not default
data = plugins_attributes.get(name, {})
metadata = data.setdefault('metadata', {
u'toggleable': True,
u'weight': 70
})
metadata['enabled'] = enabled or metadata.get('enabled', False)
metadata['label'] = title
enabled = metadata.get('enabled', False)
if all_versions:
metadata['default'] = default
# Value is true and plugin is not enabled for this cluster
# that means plugin was enabled on this request
if enabled and cluster not in plugin.clusters:
plugin.clusters.append(cluster)
# Value is false and plugin is enabled for this cluster
# that means plugin was disabled on this request
elif not enabled and cluster in plugin.clusters:
plugin.clusters.remove(cluster)
attrs = default_attrs if default else cluster_attrs
data.update(_convert_attr(pid, name, key, attrs[key])
for key in attrs)
@classmethod
def get_plugin_attributes(cls, cluster):
plugin_attributes = {}
for plugin_db in PluginCollection.all_newest():
plugin_adapter = wrap_plugin(plugin_db)
attributes = plugin_adapter.get_plugin_attributes(cluster)
plugin_attributes.update(attributes)
return plugin_attributes
if 'plugin_versions' in data:
plugin_versions = data['plugin_versions']
else:
plugin_versions = copy.deepcopy(versions)
plugin_versions['values'].append({
u'data': str(pid),
u'description': '',
u'label': version
})
if not plugin_versions['value'] or enabled:
plugin_versions['value'] = str(pid)
data['plugin_versions'] = plugin_versions
else:
data.update(cluster_attrs if enabled else {})
plugins_attributes[name] = data
return plugins_attributes
@classmethod
def get_cluster_plugins_with_tasks(cls, cluster):
cluster_plugins = []
for plugin_db in cluster.plugins:
for plugin_db in ClusterPlugins.get_enabled(cluster.id):
plugin_adapter = wrap_plugin(plugin_db)
plugin_adapter.set_cluster_tasks()
cluster_plugins.append(plugin_adapter)
@ -96,7 +173,7 @@ class PluginManager(object):
all_roles = dict((role['id'], role) for role in instance_roles)
conflict_roles = dict()
for plugin in cluster.plugins:
for plugin in ClusterPlugins.get_enabled(cluster.id):
for role in plugin.network_roles_metadata:
role_id = role['id']
if role_id in all_roles:
@ -124,9 +201,10 @@ class PluginManager(object):
@classmethod
def get_plugins_deployment_tasks(cls, cluster):
deployment_tasks = []
processed_tasks = {}
for plugin_adapter in map(wrap_plugin, cluster.plugins):
enabled_plugins = ClusterPlugins.get_enabled(cluster.id)
for plugin_adapter in map(wrap_plugin, enabled_plugins):
depl_tasks = plugin_adapter.deployment_tasks
for t in depl_tasks:
@ -140,7 +218,6 @@ class PluginManager(object):
processed_tasks[t_id],
t_id)
)
processed_tasks[t_id] = plugin_adapter.full_name
deployment_tasks.extend(depl_tasks)
@ -152,7 +229,7 @@ class PluginManager(object):
result = {}
core_roles = set(cluster.release.roles_metadata)
for plugin_db in cluster.plugins:
for plugin_db in ClusterPlugins.get_enabled(cluster.id):
plugin_roles = wrap_plugin(plugin_db).normalized_roles_metadata
# we should check all possible cases of roles intersection
@ -181,10 +258,12 @@ class PluginManager(object):
@classmethod
def get_volumes_metadata(cls, cluster):
"""Get volumes metadata for cluster from all plugins which enabled it
"""Get volumes metadata for cluster from all plugins which enabled it.
:param cluster: Cluster DB model
:returns: dict -- object with merged volumes data from plugins
:param cluster: A cluster instance
:type cluster: nailgun.db.sqlalchemy.models.cluster.Cluster
:return: Object with merged volumes data from plugins
:rtype: dict
"""
volumes_metadata = {
'volumes': [],
@ -194,7 +273,8 @@ class PluginManager(object):
release_volumes_ids = [v['id'] for v in release_volumes]
processed_volumes = {}
for plugin_adapter in map(wrap_plugin, cluster.plugins):
enabled_plugins = ClusterPlugins.get_enabled(cluster.id)
for plugin_adapter in map(wrap_plugin, enabled_plugins):
metadata = plugin_adapter.volumes_metadata
for volume in metadata.get('volumes', []):
@ -223,9 +303,12 @@ class PluginManager(object):
@classmethod
def sync_plugins_metadata(cls, plugin_ids=None):
"""Sync metadata for plugins by given ids.
"""Sync metadata for plugins by given IDs.
If there are no ids all newest plugins will be synced
If there are no IDs, all newest plugins will be synced.
:param plugin_ids: list of plugin IDs
:type plugin_ids: list
"""
if plugin_ids:
plugins = PluginCollection.get_by_uids(plugin_ids)

View File

@ -39,6 +39,7 @@ from nailgun.db.sqlalchemy.models import Release
from nailgun.logger import logger
from nailgun.network import connectivity_check
from nailgun.network import utils as net_utils
from nailgun.objects.plugin import ClusterPlugins
from nailgun.task.helpers import TaskHelper
from nailgun.utils import logs as logs_utils
from nailgun.utils import reverse
@ -527,7 +528,8 @@ class NailgunReceiver(object):
message = "{0} Access Zabbix dashboard at {1}".format(
message, zabbix_url)
plugins_msg = cls._make_plugins_success_message(task.cluster.plugins)
plugins_msg = cls._make_plugins_success_message(
ClusterPlugins.get_enabled(task.cluster.id))
if plugins_msg:
message = '{0}\n\n{1}'.format(message, plugins_msg)

View File

@ -16,9 +16,11 @@ import copy
from nailgun.db.sqlalchemy.models import NeutronConfig
from nailgun.db.sqlalchemy.models import NovaNetworkConfig
from nailgun.objects import Cluster
from nailgun.objects import ClusterCollection
from nailgun.objects import MasterNodeSettings
from nailgun.objects import NodeCollection
from nailgun.objects.plugin import ClusterPlugins
from nailgun.settings import settings
from nailgun.statistics.utils import get_attr_value
from nailgun.statistics.utils import WhiteListRule
@ -170,8 +172,10 @@ class InstallationInfo(object):
'node_groups': self.get_node_groups_info(cluster.node_groups),
'status': cluster.status,
'extensions': cluster.extensions,
'attributes': self.get_attributes(cluster.attributes.editable,
self.attributes_white_list),
'attributes': self.get_attributes(
Cluster.get_editable_attributes(cluster),
self.attributes_white_list
),
'vmware_attributes': self.get_attributes(
vmware_attributes_editable,
self.vmware_attributes_white_list
@ -188,7 +192,7 @@ class InstallationInfo(object):
def get_cluster_plugins_info(self, cluster):
plugins_info = []
for plugin_inst in cluster.plugins:
for plugin_inst in ClusterPlugins.get_enabled(cluster.id):
plugin_info = {
"id": plugin_inst.id,
"name": plugin_inst.name,

View File

@ -112,13 +112,9 @@ class ClientProvider(object):
def credentials(self):
if self._credentials is None:
cluster_attrs_editable = \
objects.Cluster.get_editable_attributes(
self.cluster
)["editable"]
objects.Cluster.get_editable_attributes(self.cluster)
access_data = cluster_attrs_editable.get(
"workloads_collector"
)
access_data = cluster_attrs_editable.get("workloads_collector")
if not access_data:
# in case there is no section for workloads_collector

View File

@ -1234,7 +1234,7 @@ class CheckBeforeDeploymentTask(object):
@classmethod
def _check_vmware_consistency(cls, task):
"""Checks vmware attributes consistency and proper values."""
attributes = task.cluster.attributes.editable
attributes = objects.Cluster.get_editable_attributes(task.cluster)
vmware_attributes = task.cluster.vmware_attributes
# Old(< 6.1) clusters haven't vmware support
if vmware_attributes:

View File

@ -60,6 +60,7 @@ from nailgun.db.sqlalchemy.models import Task
# here come objects
from nailgun.objects import Cluster
from nailgun.objects import ClusterPlugins
from nailgun.objects import Component
from nailgun.objects import MasterNodeSettings
from nailgun.objects import Node
@ -433,7 +434,6 @@ class EnvironmentManager(object):
headers=self.default_headers,
expect_errors=False
)
plugin = Plugin.get_by_uid(resp.json_body['id'])
else:
plugin = Plugin.create(plugin_data)
@ -443,7 +443,7 @@ class EnvironmentManager(object):
# Enable plugin for specific cluster
if cluster:
cluster.plugins.append(plugin)
ClusterPlugins.set_attributes(cluster.id, plugin.id, enabled=True)
return plugin
def create_component(self, release=None, plugin=None, **kwargs):
@ -705,7 +705,13 @@ class EnvironmentManager(object):
{'repository_path': 'repositories/centos',
'version': '2014.2-6.0', 'os': 'centos',
'mode': ['ha', 'multinode'],
'deployment_scripts_path': 'deployment_scripts/'}]}
'deployment_scripts_path': 'deployment_scripts/'},
{'repository_path': 'repositories/ubuntu',
'version': '2015.1-8.0', 'os': 'ubuntu',
'mode': ['ha', 'multinode'],
'deployment_scripts_path': 'deployment_scripts/'},
]
}
sample_plugin.update(kwargs)
return sample_plugin

View File

@ -48,7 +48,7 @@ class TestAttributes(BaseIntegrationTest):
attrs = objects.Cluster.get_attributes(cluster_db)
self._compare_generated(
release.attributes_metadata['generated'],
attrs.generated,
attrs['generated'],
cluster_db
)
@ -92,9 +92,9 @@ class TestAttributes(BaseIntegrationTest):
headers=self.default_headers
)
self.assertEqual(200, resp.status_code)
attrs = objects.Cluster.get_attributes(cluster_db)
self.assertEqual("bar", attrs.editable["foo"])
attrs.editable.pop('foo')
attrs = objects.Cluster.get_editable_attributes(cluster_db)
self.assertEqual("bar", attrs["foo"])
attrs.pop('foo')
# 400 on generated update
resp = self.app.put(
@ -145,10 +145,10 @@ class TestAttributes(BaseIntegrationTest):
headers=self.default_headers
)
self.assertEqual(200, resp.status_code)
attrs = objects.Cluster.get_attributes(cluster_db)
self.assertEqual("bar", attrs.editable["foo"])
attrs.editable.pop('foo')
self.assertNotEqual(attrs.editable, {})
attrs = objects.Cluster.get_editable_attributes(cluster_db)
self.assertEqual("bar", attrs["foo"])
attrs.pop('foo')
self.assertNotEqual(attrs, {})
def test_failing_attributes_put(self):
cluster_id = self.env.create_cluster(api=True)['id']
@ -249,8 +249,8 @@ class TestAttributes(BaseIntegrationTest):
expect_errors=True
)
self.assertEqual(200, resp.status_code, resp.body)
attrs = objects.Cluster.get_attributes(cluster_db)
self.assertEqual("bar", attrs.editable["foo"])
attrs = objects.Cluster.get_editable_attributes(cluster_db)
self.assertEqual("bar", attrs["foo"])
# Set attributes to defaults.
resp = self.app.put(
reverse(
@ -271,9 +271,8 @@ class TestAttributes(BaseIntegrationTest):
def test_attributes_merged_values(self):
cluster = self.env.create_cluster(api=True)
cluster_db = objects.Cluster.get_by_uid(cluster['id'])
attrs = objects.Cluster.get_attributes(cluster_db)
orig_attrs = objects.Attributes.merged_attrs(attrs)
attrs = objects.Attributes.merged_attrs_values(attrs)
orig_attrs = objects.Attributes.merged_attrs(cluster_db.attributes)
attrs = objects.Attributes.merged_attrs_values(cluster_db.attributes)
for group, group_attrs in orig_attrs.iteritems():
for attr, orig_value in group_attrs.iteritems():
if group == 'common':
@ -383,7 +382,7 @@ class TestAttributes(BaseIntegrationTest):
def test_editable_attributes_generators(self):
self.env.create_cluster(api=True)
cluster = self.env.clusters[0]
editable = objects.Cluster.get_attributes(cluster).editable
editable = objects.Cluster.get_editable_attributes(cluster)
self.assertEqual(
editable["external_dns"]["dns_list"]["value"],
settings.DNS_UPSTREAM
@ -396,7 +395,7 @@ class TestAttributes(BaseIntegrationTest):
def test_workloads_collector_attributes(self):
self.env.create_cluster(api=True)
cluster = self.env.clusters[0]
editable = objects.Cluster.get_attributes(cluster).editable
editable = objects.Cluster.get_editable_attributes(cluster)
self.assertEqual(
editable["workloads_collector"]["enabled"]["value"],
True
@ -655,7 +654,7 @@ class TestVmwareAttributes(BaseIntegrationTest):
)
def _set_use_vcenter(self, cluster):
cluster_attrs = objects.Cluster.get_attributes(cluster).editable
cluster_attrs = objects.Cluster.get_editable_attributes(cluster)
cluster_attrs['common']['use_vcenter']['value'] = True
objects.Cluster.update_attributes(
cluster, {'editable': cluster_attrs})
@ -666,7 +665,7 @@ class TestVmwareAttributesDefaults(BaseIntegrationTest):
def test_get_default_vmware_attributes(self):
cluster = self.env.create_cluster(api=True)
cluster_db = self.env.clusters[0]
cluster_attrs = objects.Cluster.get_attributes(cluster_db).editable
cluster_attrs = objects.Cluster.get_editable_attributes(cluster_db)
cluster_attrs['common']['use_vcenter']['value'] = True
objects.Cluster.update_attributes(
cluster_db, {'editable': cluster_attrs})
@ -698,3 +697,102 @@ class TestVmwareAttributesDefaults(BaseIntegrationTest):
"Cluster doesn't support vmware configuration",
resp.json_body["message"]
)
class TestAttributesWithPlugins(BaseIntegrationTest):
def setUp(self):
super(TestAttributesWithPlugins, self).setUp()
self.env.create(
release_kwargs={
'operating_system': consts.RELEASE_OS.ubuntu,
'version': '2015.1.0-7.0',
},
cluster_kwargs={
'mode': consts.CLUSTER_MODES.ha_compact,
'net_provider': consts.CLUSTER_NET_PROVIDERS.neutron,
'net_segment_type': consts.NEUTRON_SEGMENT_TYPES.vlan,
})
self.cluster = self.env.clusters[0]
self.plugin_data = {
'releases': [
{
'repository_path': 'repositories/ubuntu',
'version': self.cluster.release.version,
'os': self.cluster.release.operating_system.lower(),
'mode': [self.cluster.mode],
}
]
}
def test_cluster_contains_plugins_attributes(self):
self.env.create_plugin(cluster=self.cluster, **self.plugin_data)
resp = self.app.get(
reverse(
'ClusterAttributesHandler',
kwargs={'cluster_id': self.cluster['id']}),
headers=self.default_headers
)
self.assertEqual(200, resp.status_code)
self.assertIn('testing_plugin', resp.json_body['editable'])
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(
reverse(
'ClusterAttributesHandler',
kwargs={'cluster_id': self.cluster['id']}),
params=jsonutils.dumps({
'editable': {
'testing_plugin': {
'metadata': {
'label': 'Test plugin',
'toggleable': True,
'weight': 70,
'enabled': enabled
},
'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'}]
}
}
}
}),
headers=self.default_headers
)
resp = _modify_plugin(enabled=True)
self.assertEqual(200, resp.status_code)
editable = objects.Cluster.get_editable_attributes(self.cluster)
self.assertIn('testing_plugin', editable)
self.assertTrue(editable['testing_plugin']['metadata']['enabled'])
self.assertEqual('1', editable['testing_plugin']['attr']['value'])
resp = _modify_plugin(enabled=False)
self.assertEqual(200, resp.status_code)
editable = objects.Cluster.get_editable_attributes(self.cluster)
self.assertIn('testing_plugin', editable)
self.assertFalse(editable['testing_plugin']['metadata']['enabled'])
self.assertNotIn(attr, editable['testing_plugin'])

View File

@ -428,7 +428,7 @@ class TestHandlers(BaseIntegrationTest):
self.db.delete(p)
self.db.flush()
attrs = cluster_db.attributes.editable
attrs = objects.Cluster.get_editable_attributes(cluster_db)
attrs['public_network_assignment']['assign_to_all_nodes']['value'] = \
True
attrs['provision']['method'] = consts.PROVISION_METHODS.cobbler
@ -915,7 +915,7 @@ class TestHandlers(BaseIntegrationTest):
self.db.delete(p)
self.db.flush()
attrs = cluster_db.attributes.editable
attrs = objects.Cluster.get_editable_attributes(cluster_db)
attrs['public_network_assignment']['assign_to_all_nodes']['value'] = \
True
attrs['provision']['method'] = consts.PROVISION_METHODS.cobbler

View File

@ -141,6 +141,9 @@ class TestClusterRolesHandler(base.BaseTestCase):
plugin_data['roles_metadata'] = self.ROLES
plugin = objects.Plugin.create(plugin_data)
self.cluster.plugins.append(plugin)
objects.ClusterPlugins.set_attributes(self.cluster.id,
plugin.id,
enabled=True)
self.db.flush()
roles = self.app.get(
@ -162,6 +165,9 @@ class TestClusterRolesHandler(base.BaseTestCase):
plugin_data['volumes_metadata'] = self.VOLUMES
plugin = objects.Plugin.create(plugin_data)
self.cluster.plugins.append(plugin)
objects.ClusterPlugins.set_attributes(self.cluster.id,
plugin.id,
enabled=True)
self.db.flush()
plugin_adapter = adapters.wrap_plugin(plugin)

View File

@ -122,7 +122,7 @@ class TestHandlers(BaseIntegrationTest):
cluster = self.env.create_cluster(api=False)
node = self.env.create_node(cluster_id=cluster.id)
cluster_attrs = objects.Cluster.get_attributes(cluster).editable
cluster_attrs = objects.Cluster.get_editable_attributes(cluster)
test_hostname = 'test-hostname'
cluster_attrs['public_ssl']['hostname']['value'] = test_hostname
objects.Cluster.update_attributes(

View File

@ -72,6 +72,7 @@ class OrchestratorSerializerTestBase(base.BaseIntegrationTest):
def setUp(self):
super(OrchestratorSerializerTestBase, self).setUp()
self.cluster_mock = mock.MagicMock(pending_release_id=None)
self.cluster_mock.id = 0
self.cluster_mock.deployment_tasks = []
self.cluster_mock.release.deployment_tasks = []
@ -2585,7 +2586,7 @@ class BaseDeploymentSerializer(base.BaseIntegrationTest):
def check_generate_vmware_attributes_data(self):
cluster_db = self.db.query(Cluster).get(self.cluster['id'])
cluster_attrs = objects.Cluster.get_attributes(cluster_db).editable
cluster_attrs = objects.Cluster.get_editable_attributes(cluster_db)
cluster_attrs.get('common', {}).setdefault('use_vcenter', {})
cluster_attrs['common']['use_vcenter']['value'] = True
@ -2802,7 +2803,7 @@ class TestDeploymentHASerializer61(BaseDeploymentSerializer):
def test_generate_test_vm_image_data(self):
cluster_db = self.db.query(Cluster).get(self.cluster['id'])
cluster_attrs = objects.Cluster.get_attributes(cluster_db).editable
cluster_attrs = objects.Cluster.get_editable_attributes(cluster_db)
cluster_attrs['common'].setdefault('use_vcenter', {})
cluster_attrs['common']['use_vcenter']['value'] = True

View File

@ -131,12 +131,20 @@ class TestDeploymentAttributesSerialization70(
namespace: "haproxy"
""".format(**custom_network))
def _add_plugin_network_roles(self):
plugin_data = self.env.get_default_plugin_metadata()
plugin_data['network_roles_metadata'] = self.plugin_network_roles
def _add_plugin_network_roles(self, **kwargs):
plugin_data = self.env.get_default_plugin_metadata(releases=[{
'repository_path': 'repositories/ubuntu',
'version': self.cluster_db.release.version,
'os': self.cluster_db.release.operating_system.lower(),
'mode': [self.cluster_db.mode],
}])
plugin_data.update(**kwargs)
plugin = objects.Plugin.create(plugin_data)
self.cluster_db.plugins.append(plugin)
self.db.commit()
objects.ClusterPlugins.set_attributes(self.cluster_db.id,
plugin.id,
enabled=True)
return plugin
def test_non_default_bridge_mapping(self):
expected_mapping = {
@ -206,7 +214,8 @@ class TestDeploymentAttributesSerialization70(
vlan_start=self.custom_network[
'vlan_start'
])
self._add_plugin_network_roles()
self._add_plugin_network_roles(
network_roles_metadata=self.plugin_network_roles)
self.env.create_node(
api=True,
cluster_id=cluster['id'],
@ -561,9 +570,6 @@ class TestPluginDeploymentTasksInjection(base.BaseIntegrationTest):
def setUp(self):
super(TestPluginDeploymentTasksInjection, self).setUp()
self.cluster = self._prepare_cluster()
def _prepare_cluster(self):
self.env.create(
release_kwargs={
'version': '2015.1.0-7.0',
@ -579,36 +585,25 @@ class TestPluginDeploymentTasksInjection(base.BaseIntegrationTest):
'pending_addition': True}
]
)
return self.env.clusters[0]
self.cluster = self.env.clusters[0]
self.plugin_data = {
'releases': [
{
'repository_path': 'plugin_test',
'version': self.cluster.release.version,
'os': self.cluster.release.operating_system.lower(),
'mode': ['ha', 'multinode'],
'deployment_scripts_path': 'plugin_test/'
},
],
}
def prepare_plugins_for_cluster(self, cluster, plugins_kw_list):
plugins = [
self._create_plugin(**kw)
for kw in plugins_kw_list
]
cluster.plugins.extend(plugins)
self.db.flush()
def _create_plugin(self, **plugin_kwargs):
plugin_kwargs.update(
{
'releases': [
{
'repository_path': 'plugin_test',
'version': self.cluster.release.version,
'os':
self.cluster.release.operating_system.lower(),
'mode': ['ha', 'multinode'],
'deployment_scripts_path': 'plugin_test/'
},
],
}
)
plugin_data = self.env.get_default_plugin_metadata(
**plugin_kwargs
)
return objects.Plugin.create(plugin_data)
for kw in plugins_kw_list:
kw.update(self.plugin_data)
self.env.create_plugin(cluster=cluster, **kw)
def _check_pre_deployment_tasks(self, serialized, task_type):
self.assertTrue(serialized)
@ -912,17 +907,30 @@ class TestRolesSerializationWithPlugins(BaseDeploymentSerializer):
'net_provider': consts.CLUSTER_NET_PROVIDERS.neutron,
'net_segment_type': consts.NEUTRON_SEGMENT_TYPES.vlan,
})
self.cluster = self.env.clusters[0]
self.plugin_data = {
'releases': [
{
'repository_path': 'repositories/ubuntu',
'version': self.cluster.release.version,
'os': self.cluster.release.operating_system.lower(),
'mode': [self.cluster.mode],
}
]
}
def _get_serializer(self, cluster):
return get_serializer_for_cluster(cluster)(AstuteGraph(cluster))
def test_tasks_were_serialized(self):
plugin_data = self.env.get_default_plugin_metadata()
plugin_data['roles_metadata'] = self.ROLES
plugin_data['deployment_tasks'] = self.DEPLOYMENT_TASKS
plugin = objects.Plugin.create(plugin_data)
self.cluster.plugins.append(plugin)
plugin_data = {
'roles_metadata': self.ROLES,
'deployment_tasks': self.DEPLOYMENT_TASKS
}
plugin_data.update(self.plugin_data)
self.env.create_plugin(cluster=self.cluster, **plugin_data)
self.env.create_node(
api=True,
@ -949,11 +957,12 @@ class TestRolesSerializationWithPlugins(BaseDeploymentSerializer):
}])
def test_tasks_were_not_serialized(self):
plugin_data = self.env.get_default_plugin_metadata()
plugin_data['roles_metadata'] = {}
plugin_data['deployment_tasks'] = self.DEPLOYMENT_TASKS
plugin = objects.Plugin.create(plugin_data)
self.cluster.plugins.append(plugin)
plugin_data = {
'roles_metadata': {},
'deployment_tasks': self.DEPLOYMENT_TASKS
}
plugin_data.update(self.plugin_data)
self.env.create_plugin(cluster=self.cluster, **plugin_data)
self.env.create_node(
api=True,

View File

@ -15,8 +15,11 @@
# under the License.
import mock
import uuid
from nailgun import consts
from nailgun.errors import errors
from nailgun.objects import ClusterPlugins
from nailgun.plugins.adapters import PluginAdapterV3
from nailgun.plugins.manager import PluginManager
from nailgun.test import base
@ -157,3 +160,73 @@ class TestPluginManager(base.BaseIntegrationTest):
def test_sync_metadata_for_specific_plugin(self, sync_mock):
PluginManager.sync_plugins_metadata([self.env.plugins[0].id])
self.assertEqual(sync_mock.call_count, 1)
class TestClusterPluginIntegration(base.BaseTestCase):
_compat_meta = {
'releases': [{
'os': 'ubuntu',
'mode': 'ha',
'version': '2015.1-8.0',
}]
}
_uncompat_meta = {
'releases': [{
'os': 'ubuntu',
'mode': 'ha',
'version': '2014.2-7.0',
}]
}
def setUp(self):
super(TestClusterPluginIntegration, self).setUp()
self.env.create(
release_kwargs={
'operating_system': consts.RELEASE_OS.ubuntu,
'version': '2015.1-8.0'},
cluster_kwargs={
'mode': consts.CLUSTER_MODES.ha_compact,
})
self.cluster = self.env.clusters[0]
def _create_plugin(self, **kwargs):
plugin = self.env.create_plugin(name=uuid.uuid4().get_hex(), **kwargs)
return plugin
def test_get_compatible_plugins(self):
plugin_a = self._create_plugin(**self._compat_meta)
self._create_plugin(**self._uncompat_meta)
compat_plugins = ClusterPlugins.get_compatible_plugins(self.cluster)
self.assertItemsEqual(compat_plugins, [plugin_a])
def test_get_compatible_plugins_for_new_cluster(self):
plugin_a = self._create_plugin(**self._compat_meta)
plugin_b = self._create_plugin(**self._compat_meta)
self._create_plugin(**self._uncompat_meta)
self.env.create(
cluster_kwargs={
'release_id': self.cluster.release.id,
'mode': consts.CLUSTER_MODES.ha_compact,
})
cluster = self.env.clusters[1]
compat_plugins = ClusterPlugins.get_compatible_plugins(cluster)
self.assertItemsEqual(compat_plugins, [plugin_a, plugin_b])
def test_get_enabled_plugins(self):
plugin_a = self._create_plugin(**self._compat_meta)
plugin_b = self._create_plugin(**self._compat_meta)
ClusterPlugins.set_attributes(
self.cluster.id, plugin_a.id, enabled=True)
compat_plugins = ClusterPlugins.get_compatible_plugins(self.cluster)
self.assertItemsEqual(compat_plugins, [plugin_a, plugin_b])
enabled_plugins = ClusterPlugins.get_enabled(self.cluster.id)
self.assertItemsEqual(enabled_plugins, [plugin_a])

View File

@ -49,14 +49,21 @@ class BasePluginTest(base.BaseIntegrationTest):
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
resp = self.app.post(
base.reverse('PluginCollectionHandler'),
jsonutils.dumps(sample),
headers=self.default_headers,
expect_errors=expect_errors
)
return resp
with mock.patch('nailgun.plugins.adapters.os') as os:
with mock.patch('nailgun.plugins.adapters.open',
create=True,
side_effect=get_config(self.plugin_env_config)):
os.access.return_value = True
os.path.exists.return_value = True
sample = sample or self.sample_plugin
resp = self.app.post(
base.reverse('PluginCollectionHandler'),
jsonutils.dumps(sample),
headers=self.default_headers,
expect_errors=expect_errors
)
return resp
def delete_plugin(self, plugin_id, expect_errors=False):
resp = self.app.delete(
@ -89,21 +96,26 @@ class BasePluginTest(base.BaseIntegrationTest):
headers=self.default_headers)
return resp
def modify_plugin(self, cluster, plugin_name, enabled):
editable_attrs = cluster.attributes.editable
def modify_plugin(self, cluster, plugin_name, plugin_id, enabled):
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)
resp = self.app.put(
base.reverse('ClusterAttributesHandler',
{'cluster_id': cluster.id}),
jsonutils.dumps({'editable': editable_attrs}),
headers=self.default_headers)
return resp
def enable_plugin(self, cluster, plugin_name):
return self.modify_plugin(cluster, plugin_name, True)
def enable_plugin(self, cluster, plugin_name, plugin_id):
return self.modify_plugin(cluster, plugin_name, plugin_id, True)
def disable_plugin(self, cluster, plugin_name):
return self.modify_plugin(cluster, plugin_name, False)
return self.modify_plugin(cluster, plugin_name, None, False)
def get_pre_hooks(self, cluster):
with mock.patch('nailgun.plugins.adapters.glob') as glob:
@ -162,19 +174,23 @@ 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'],
objects.Cluster.get_editable_attributes(
cluster, all_plugins_versions=True))
def test_enable_disable_plugin(self):
resp = self.create_plugin()
plugin = objects.Plugin.get_by_uid(resp.json['id'])
cluster = self.create_cluster()
self.assertEqual(plugin.clusters, [])
resp = self.enable_plugin(cluster, plugin.name)
self.assertEqual(objects.ClusterPlugins.get_enabled(cluster.id), [])
resp = self.enable_plugin(cluster, plugin.name, plugin.id)
self.assertEqual(resp.status_code, 200)
self.assertIn(cluster, plugin.clusters)
self.assertIn(plugin, objects.ClusterPlugins.get_enabled(cluster.id))
resp = self.disable_plugin(cluster, plugin.name)
self.assertEqual(resp.status_code, 200)
self.assertEqual(plugin.clusters, [])
self.assertEqual(objects.ClusterPlugins.get_enabled(cluster.id), [])
def test_delete_plugin(self):
resp = self.create_plugin()
@ -185,7 +201,7 @@ class TestPluginsApi(BasePluginTest):
resp = self.create_plugin()
plugin = objects.Plugin.get_by_uid(resp.json['id'])
cluster = self.create_cluster()
enable_resp = self.enable_plugin(cluster, plugin.name)
enable_resp = self.enable_plugin(cluster, plugin.name, plugin.id)
self.assertEqual(enable_resp.status_code, 200)
del_resp = self.delete_plugin(resp.json['id'], expect_errors=True)
self.assertEqual(del_resp.status_code, 400)
@ -210,29 +226,67 @@ 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.json_body['editable'])
def test_attributes_after_plugin_is_created(self):
sample = dict({
"attributes_metadata": {
"attr_text": {
"value": "value",
"type": "text",
"description": "description",
"weight": 25,
"label": "label"
}
}
}, **self.sample_plugin)
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']])
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))
def create_with_version(plugin_version):
response = self.create_plugin(
sample=self.env.get_default_plugin_metadata(
name='multiversion_plugin',
version=plugin_version
)
)
return response.json_body['id']
def get_num_enabled(cluster_id):
return len(objects.ClusterPlugins.get_enabled(cluster_id))
def get_enabled_version(cluster_id):
plugin = objects.ClusterPlugins.get_enabled(cluster_id)[0]
return plugin.version
plugin_ids = []
for version in ['1.0.0', '2.0.0', '0.0.1']:
create_with_version(version)
plugin_ids.append(create_with_version(version))
cluster = self.create_cluster()
# Create new plugin after environment is created
create_with_version('5.0.0')
self.assertEqual(get_num_enabled(cluster.id), 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.enable_plugin(cluster, 'multiversion_plugin', plugin_ids[1])
self.assertEqual(get_num_enabled(cluster.id), 1)
self.assertEqual(get_enabled_version(cluster.id), '2.0.0')
# Create new plugin after environment is created
plugin_ids.append(create_with_version('5.0.0'))
self.assertEqual(len(cluster.plugins), 4)
self.assertEqual(get_num_enabled(cluster.id), 1)
self.assertEqual(get_enabled_version(cluster.id), '2.0.0')
self.enable_plugin(cluster, 'multiversion_plugin', plugin_ids[3])
self.assertEqual(get_num_enabled(cluster.id), 1)
self.assertEqual(get_enabled_version(cluster.id), '5.0.0')
self.disable_plugin(cluster, 'multiversion_plugin')
self.assertEqual(len(cluster.plugins), 0)
self.assertEqual(get_num_enabled(cluster.id), 0)
def test_sync_all_plugins(self):
self._create_new_and_old_version_plugins_for_sync()
@ -268,27 +322,6 @@ class TestPluginsApi(BasePluginTest):
resp.json_body["message"],
'Problem with loading YAML file')
@mock.patch('nailgun.objects.cluster.AttributesGenerator')
def test_plugin_generator(self, mock_attributes_generator):
mock_attributes_generator.test_plugin_generator.return_value = 'test'
plugin_name = 'testing_plugin'
self.sample_plugin = self.env.get_default_plugin_metadata(
name=plugin_name
)
self.plugin_env_config = \
self.env.get_default_plugin_env_config(
value={
'generator': 'test_plugin_generator',
},
plugin_name=plugin_name
)
self.create_plugin()
cluster = self.create_cluster()
self.assertIn(self.sample_plugin['name'], cluster.attributes.editable)
plugin_dict = cluster.attributes.editable[plugin_name]
value = plugin_dict['%s_text' % plugin_name]['value']
self.assertEqual(value, 'test')
def _create_new_and_old_version_plugins_for_sync(self):
plugin_ids = []
@ -360,7 +393,9 @@ class TestPrePostHooks(BasePluginTest):
{'roles': ['controller'], 'pending_addition': True},
{'roles': ['compute'], 'pending_addition': True}])
objects.NodeCollection.prepare_for_deployment(self.cluster.nodes)
self.enable_plugin(self.cluster, self.sample_plugin['name'])
self.enable_plugin(self.cluster,
self.sample_plugin['name'],
resp.json['id'])
def tearDown(self):
self._requests_mock.stop()

View File

@ -274,7 +274,8 @@ class TestInstallationInfo(BaseTestCase):
cluster_plugin_kwargs = {
"cluster_id": cluster.id,
"plugin_id": plugin_obj.id
"plugin_id": plugin_obj.id,
"enabled": True
}
cluster_plugin = plugins.ClusterPlugins(**cluster_plugin_kwargs)

View File

@ -49,7 +49,15 @@ class BaseComponentTestCase(base.BaseTestCase):
class TestComponentCollection(BaseComponentTestCase):
def test_get_all_by_release(self):
self.incompatible_plugin = self.env.create_plugin()
self.incompatible_plugin = self.env.create_plugin(
fuel_version=['6.0'],
releases=[{
'repository_path': 'repositories/centos',
'version': '2014.2-6.0',
'os': 'centos',
'mode': ['ha'],
'deployment_scripts_path': 'deployment_scripts/'}]
)
self.incompatible_release = self.env.create_release(
operating_system='Ubuntu',
modes=[consts.CLUSTER_MODES.ha_compact])

View File

@ -33,7 +33,7 @@ _prepare_revision = '1e50a4903910'
_test_revision = '43b2cb64dae6'
def setup_module(module):
def setup_module():
dropdb()
alembic.command.upgrade(ALEMBIC_CONFIG, _prepare_revision)
prepare()
@ -103,6 +103,92 @@ def prepare():
'net_l23_provider': 'ovs'
}])
result = db.execute(
meta.tables['plugins'].insert(),
[{
'name': 'test_plugin_a',
'title': 'Test plugin A',
'version': '1.0.0',
'description': 'Test plugin A for Fuel',
'homepage': 'http://fuel_plugins.test_plugin.com',
'package_version': '3.0.0',
'groups': jsonutils.dumps(['tgroup']),
'authors': jsonutils.dumps(['tauthor']),
'licenses': jsonutils.dumps(['tlicense']),
'releases': jsonutils.dumps([
{'repository_path': 'repositories/ubuntu'}
]),
'fuel_version': jsonutils.dumps(['6.1', '7.0']),
}]
)
pluginid_a = result.inserted_primary_key[0]
result = db.execute(
meta.tables['plugins'].insert(),
[{
'name': 'test_plugin_b',
'title': 'Test plugin B',
'version': '1.0.0',
'description': 'Test plugin B for Fuel',
'homepage': 'http://fuel_plugins.test_plugin.com',
'package_version': '3.0.0',
'groups': jsonutils.dumps(['tgroup']),
'authors': jsonutils.dumps(['tauthor']),
'licenses': jsonutils.dumps(['tlicense']),
'releases': jsonutils.dumps([
{'repository_path': 'repositories/ubuntu'}
]),
'fuel_version': jsonutils.dumps(['6.1', '7.0']),
}]
)
pluginid_b = result.inserted_primary_key[0]
db.execute(
meta.tables['cluster_plugins'].insert(),
[
{
'cluster_id': clusterid,
'plugin_id': pluginid_a
},
{
'cluster_id': clusterid,
'plugin_id': pluginid_b
}
]
)
db.execute(
meta.tables['attributes'].insert(),
[{
'cluster_id': clusterid,
'editable': jsonutils.dumps({
'test_plugin_a': {
'metadata': {
'plugin_id': pluginid_a,
'enabled': True,
'toggleable': True,
'weight': 70,
},
'attribute': {
'value': 'value',
'type': 'text',
'description': 'description',
'weight': 25,
'label': 'label'
}
},
'test_plugin_b': {
'metadata': {
'plugin_id': pluginid_b,
'enabled': False,
'toggleable': True,
'weight': 80,
}
}
}),
'generated': jsonutils.dumps({}),
}])
db.commit()
@ -416,3 +502,56 @@ class TestNeutronConfigInternalFloatingNames(base.BaseAlembicMigrationTest):
self.assertEqual('net04', neutron_config['internal_name'])
self.assertEqual('net04_ext', neutron_config['floating_name'])
class TestClusterPluginsMigration(base.BaseAlembicMigrationTest):
def _get_enabled(self, plugin_name):
plugins = self.meta.tables['plugins']
cluster_plugins = self.meta.tables['cluster_plugins']
query = sa.select([cluster_plugins.c.enabled])\
.select_from(
sa.join(
cluster_plugins, plugins,
cluster_plugins.c.plugin_id == plugins.c.id))\
.where(plugins.c.name == plugin_name)
return db.execute(query).fetchone()[0]
def test_plugin_a_is_enabled(self):
enabled = self._get_enabled('test_plugin_a')
self.assertTrue(enabled)
def test_plugin_b_is_disabled(self):
enabled = self._get_enabled('test_plugin_b')
self.assertFalse(enabled)
def test_moving_plugin_attributes(self):
clusters = self.meta.tables['clusters']
attributes = self.meta.tables['attributes']
plugins = self.meta.tables['plugins']
cluster_plugins = self.meta.tables['cluster_plugins']
query = sa.select([attributes.c.editable])\
.select_from(
sa.join(
attributes, clusters,
attributes.c.cluster_id == clusters.c.id))
result = jsonutils.loads(db.execute(query).fetchone()[0])
self.assertItemsEqual(result, {})
query = sa.select([cluster_plugins.c.attributes])\
.select_from(
sa.join(
cluster_plugins, plugins,
cluster_plugins.c.plugin_id == plugins.c.id))\
.where(plugins.c.name == 'test_plugin_a')
result = jsonutils.loads(db.execute(query).fetchone()[0])
self.assertNotIn('metadata', result)
self.assertItemsEqual(result['attribute'], {
'value': 'value',
'type': 'text',
'description': 'description',
'weight': 25,
'label': 'label'
})

View File

@ -15,23 +15,59 @@
# under the License.
from nailgun import consts
from nailgun.objects import ClusterPlugins
from nailgun.objects import Plugin
from nailgun.objects import PluginCollection
from nailgun.test import base
import sqlalchemy as sa
import uuid
class TestPluginCollection(base.BaseTestCase):
class ExtraFunctions(base.BaseTestCase):
def setUp(self):
super(TestPluginCollection, self).setUp()
self.release = self.env.create_release(
version='2015.1-8.0',
operating_system='Ubuntu',
modes=[consts.CLUSTER_MODES.ha_compact])
self.plugin_ids = self._create_test_plugins()
def _create_test_plugins(self):
plugin_ids = []
for version in ['1.0.0', '2.0.0', '0.0.1', '3.0.0']:
plugin_data = self.env.get_default_plugin_metadata(
version=version,
name='multiversion_plugin')
plugin = Plugin.create(plugin_data)
plugin_ids.append(plugin.id)
single_plugin_data = self.env.get_default_plugin_metadata(
name='single_plugin')
plugin = Plugin.create(single_plugin_data)
plugin_ids.append(plugin.id)
incompatible_plugin_data = self.env.get_default_plugin_metadata(
name='incompatible_plugin',
releases=[]
)
plugin = Plugin.create(incompatible_plugin_data)
plugin_ids.append(plugin.id)
return plugin_ids
def _create_test_cluster(self):
self.env.create(
cluster_kwargs={'mode': consts.CLUSTER_MODES.multinode},
release_kwargs={
'name': uuid.uuid4().get_hex(),
'version': '2015.1-8.0',
'operating_system': 'Ubuntu',
'modes': [consts.CLUSTER_MODES.multinode,
consts.CLUSTER_MODES.ha_compact]})
return self.env.clusters[0]
class TestPluginCollection(ExtraFunctions):
def test_all_newest(self):
self._create_test_plugins()
newest_plugins = PluginCollection.all_newest()
self.assertEqual(len(newest_plugins), 2)
self.assertEqual(len(newest_plugins), 3)
single_plugin = filter(
lambda p: p.name == 'single_plugin',
@ -46,21 +82,62 @@ class TestPluginCollection(base.BaseTestCase):
self.assertEqual(multiversion_plugin[0].version, '3.0.0')
def test_get_by_uids(self):
ids = self.plugin_ids[:2]
plugin_ids = self._create_test_plugins()
ids = plugin_ids[:2]
plugins = PluginCollection.get_by_uids(ids)
self.assertEqual(len(list(plugins)), 2)
self.assertListEqual(
[plugin.id for plugin in plugins], ids)
def _create_test_plugins(self):
plugin_ids = []
for version in ['1.0.0', '2.0.0', '0.0.1', '3.0.0']:
plugin = self.env.create_plugin(
version=version,
name='multiversion_plugin')
plugin_ids.append(plugin.id)
plugin = self.env.create_plugin(name='single_plugin')
plugin_ids.append(plugin.id)
class TestClusterPlugins(ExtraFunctions):
return plugin_ids
def test_connect_to_cluster(self):
meta = base.reflect_db_metadata()
self._create_test_plugins()
self._create_test_cluster()
cluster_plugins = self.db.execute(
meta.tables['cluster_plugins'].select()
).fetchall()
self.assertEqual(len(cluster_plugins), 5)
def test_set_plugin_attributes(self):
meta = base.reflect_db_metadata()
self._create_test_plugins()
cluster = self._create_test_cluster()
plugin_id = ClusterPlugins.get_connected_plugins(cluster.id)[0][0]
ClusterPlugins.set_attributes(cluster.id, plugin_id, enabled=True)
columns = meta.tables['cluster_plugins'].c
enabled = self.db.execute(
sa.select([columns.enabled])
.where(columns.cluster_id == cluster.id)
.where(columns.plugin_id == plugin_id)
).fetchone()
self.assertTrue(enabled[0])
def test_get_connected_plugins(self):
self._create_test_plugins()
cluster = self._create_test_cluster()
connected_plugins =\
ClusterPlugins.get_connected_plugins(cluster.id).all()
self.assertEqual(len(connected_plugins), 5)
def test_get_connected_clusters(self):
plugin_id = self._create_test_plugins()[0]
for _ in range(2):
self._create_test_cluster()
connected_clusters =\
ClusterPlugins.get_connected_clusters(plugin_id).all()
self.assertEqual(len(connected_clusters), 2)
def test_get_enabled(self):
self._create_test_plugins()
cluster = self._create_test_cluster()
plugin_id = ClusterPlugins.get_connected_plugins(cluster.id)[0][0]
ClusterPlugins.set_attributes(cluster.id, plugin_id, enabled=True)
enabled_plugin = ClusterPlugins.get_enabled(cluster.id)[0].id
self.assertEqual(enabled_plugin, plugin_id)

View File

@ -940,8 +940,11 @@ class TestClusterObject(BaseTestCase):
cluster = self.env.create_cluster(api=False)
for kw in plugins_kw_list:
cluster.plugins.append(objects.Plugin.create(kw))
plugin = objects.Plugin.create(kw)
cluster.plugins.append(plugin)
objects.ClusterPlugins.set_attributes(cluster.id,
plugin.id,
enabled=True)
return cluster
def _get_network_role_metadata(self, **kwargs):
@ -1293,8 +1296,9 @@ class TestClusterObjectGetRoles(BaseTestCase):
roles_metadata=roles_metadata,
))
self.cluster.plugins.append(plugin)
self.db.flush()
objects.ClusterPlugins.set_attributes(self.cluster.id,
plugin.id,
enabled=True)
return plugin
def test_no_plugins_no_additional_roles(self):

View File

@ -23,6 +23,7 @@ from nailgun import consts
from nailgun.db import db
from nailgun.errors import errors
from nailgun.expression import Expression
from nailgun.objects import ClusterPlugins
from nailgun.objects import Component
from nailgun.objects import Plugin
from nailgun.plugins import adapters
@ -65,7 +66,7 @@ class TestPluginBase(base.BaseTestCase):
self.env.create(
cluster_kwargs={'mode': consts.CLUSTER_MODES.multinode},
release_kwargs={
'version': '2014.2-6.0',
'version': '2015.1-8.0',
'operating_system': 'Ubuntu',
'modes': [consts.CLUSTER_MODES.multinode,
consts.CLUSTER_MODES.ha_compact]})
@ -77,30 +78,12 @@ class TestPluginBase(base.BaseTestCase):
db().flush()
@mock.patch('nailgun.plugins.adapters.open', create=True)
@mock.patch('nailgun.plugins.adapters.os.access')
@mock.patch('nailgun.plugins.adapters.os.path.exists')
def test_get_plugin_attributes(self, mexists, maccess, mopen):
"""Should load attributes from environment_config
Attributes should contain provided attributes by plugin and
also generated metadata
"""
maccess.return_value = True
mexists.return_value = True
mopen.side_effect = self.get_config
attributes = self.plugin_adapter.get_plugin_attributes(self.cluster)
self.assertEqual(
attributes['testing_plugin']['plugin_name_text'],
self.env_config['attributes']['plugin_name_text'])
self.assertEqual(
attributes['testing_plugin']['metadata'],
self.plugin_adapter.default_metadata)
def test_plugin_release_versions(self):
"""Should return set of all versions this plugin is applicable to"""
self.assertEqual(
self.plugin_adapter.plugin_release_versions, set(['2014.2-6.0']))
self.plugin_adapter.plugin_release_versions,
set(['2014.2-6.0', '2015.1-8.0'])
)
def test_full_name(self):
"""Plugin full name should be made from name and version."""
@ -271,7 +254,8 @@ class TestPluginV3(TestPluginBase):
getattr(self.plugin, key), val)
self.assertEqual(
self.plugin.attributes_metadata, attributes_metadata)
self.plugin.attributes_metadata,
attributes_metadata['attributes'])
self.assertEqual(
self.plugin.roles_metadata, roles_metadata)
self.assertEqual(
@ -318,7 +302,8 @@ class TestPluginV4(TestPluginBase):
getattr(self.plugin, key), val)
self.assertEqual(
self.plugin.attributes_metadata, attributes_metadata)
self.plugin.attributes_metadata,
attributes_metadata['attributes'])
self.assertEqual(
self.plugin.roles_metadata, roles_metadata)
self.assertEqual(
@ -349,10 +334,10 @@ class TestPluginV4(TestPluginBase):
self.assertEqual(component.plugin_id, self.plugin.id)
class TestClusterCompatiblityValidation(base.BaseTestCase):
class TestClusterCompatibilityValidation(base.BaseTestCase):
def setUp(self):
super(TestClusterCompatiblityValidation, self).setUp()
super(TestClusterCompatibilityValidation, self).setUp()
self.plugin = Plugin.create(self.env.get_default_plugin_metadata(
releases=[{
'version': '2014.2-6.0',
@ -367,7 +352,7 @@ class TestClusterCompatiblityValidation(base.BaseTestCase):
def validate_with_cluster(self, **kwargs):
cluster = self.cluster_mock(**kwargs)
return self.plugin_adapter.validate_cluster_compatibility(cluster)
return ClusterPlugins.validate_compatibility(cluster, self.plugin)
def test_validation_ubuntu_ha(self):
self.assertTrue(self.validate_with_cluster(

View File

@ -19,6 +19,7 @@ from mock import patch
from nailgun import consts
from nailgun.errors import errors
from nailgun.objects import ClusterPlugins
from nailgun.objects import Plugin
from nailgun.rpc.receiver import NailgunReceiver
from nailgun.test import base
@ -44,6 +45,9 @@ class TestNailgunReceiver(base.BaseTestCase):
self.plugin = Plugin.create(meta)
self.cluster.plugins.append(self.plugin)
ClusterPlugins.set_attributes(self.cluster.id,
self.plugin.id,
enabled=True)
self.task = self.env.create_task(
name=consts.TASK_NAMES.deployment,

View File

@ -262,14 +262,13 @@ class TestAttributesRestriction(base.BaseTestCase):
def test_check_with_invalid_values(self):
objects.Cluster.update_attributes(
self.cluster, self.attributes_data)
attributes = objects.Cluster.get_attributes(self.cluster)
attributes = objects.Cluster.get_editable_attributes(self.cluster)
models = {
'settings': attributes.editable,
'default': attributes.editable,
'settings': attributes,
'default': attributes,
}
errs = AttributesRestriction.check_data(
models, attributes.editable)
errs = AttributesRestriction.check_data(models, attributes)
self.assertItemsEqual(
errs, ['Invalid username', 'Invalid tenant name'])
@ -280,14 +279,13 @@ class TestAttributesRestriction(base.BaseTestCase):
objects.Cluster.update_attributes(
self.cluster, self.attributes_data)
attributes = objects.Cluster.get_attributes(self.cluster)
attributes = objects.Cluster.get_editable_attributes(self.cluster)
models = {
'settings': attributes.editable,
'default': attributes.editable,
'settings': attributes,
'default': attributes,
}
errs = AttributesRestriction.check_data(
models, attributes.editable)
errs = AttributesRestriction.check_data(models, attributes)
self.assertListEqual(errs, [])
@ -303,7 +301,7 @@ class TestVmwareAttributesRestriction(base.BaseTestCase):
self.vm_data = self.env.read_fixtures(['vmware_attributes'])[0]
def test_check_data_with_empty_values_without_restrictions(self):
attributes = objects.Cluster.get_attributes(self.cluster).editable
attributes = objects.Cluster.get_editable_attributes(self.cluster)
attributes['common']['use_vcenter']['value'] = True
attributes['storage']['images_vcenter']['value'] = True
vmware_attributes = self.vm_data.copy()
@ -355,7 +353,7 @@ class TestVmwareAttributesRestriction(base.BaseTestCase):
def test_check_data_with_invalid_values_without_restrictions(self):
# Disable restrictions
attributes = objects.Cluster.get_attributes(self.cluster).editable
attributes = objects.Cluster.get_editable_attributes(self.cluster)
attributes['common']['use_vcenter']['value'] = True
attributes['storage']['images_vcenter']['value'] = True
# value data taken from fixture one cluster of
@ -376,7 +374,7 @@ class TestVmwareAttributesRestriction(base.BaseTestCase):
self.assertItemsEqual(errs, ['Empty cluster'])
def test_check_data_with_invalid_values_and_with_restrictions(self):
attributes = objects.Cluster.get_attributes(self.cluster).editable
attributes = objects.Cluster.get_editable_attributes(self.cluster)
# fixture have restrictions enabled for glance that's why
# only 'Empty cluster' should returned
vmware_attributes = self.vm_data.copy()
@ -395,7 +393,7 @@ class TestVmwareAttributesRestriction(base.BaseTestCase):
self.assertItemsEqual(errs, ['Empty cluster'])
def test_check_data_with_valid_values_and_with_restrictions(self):
attributes = objects.Cluster.get_attributes(self.cluster).editable
attributes = objects.Cluster.get_editable_attributes(self.cluster)
vmware_attributes = self.vm_data.copy()
# Set valid data for clusters
for i, azone in enumerate(
@ -419,7 +417,7 @@ class TestVmwareAttributesRestriction(base.BaseTestCase):
def test_check_data_with_valid_values_and_without_restrictions(self):
# Disable restrictions
attributes = objects.Cluster.get_attributes(self.cluster).editable
attributes = objects.Cluster.get_editable_attributes(self.cluster)
attributes['common']['use_vcenter']['value'] = True
attributes['storage']['images_vcenter']['value'] = True
vmware_attributes = self.vm_data.copy()