Enable plugins by chosen components

Modifying POST /clusters for handling components data
Enabling proper plugins by components from wizard

Change-Id: If6c0e9c2cb41652def663ba3c8c95484bd3430e9
Implements: blueprint component-registry
This commit is contained in:
Andriy Popovych 2015-11-11 16:10:31 +02:00 committed by Andriy Popovych
parent 2e761d177e
commit 4aaf0777b1
14 changed files with 196 additions and 52 deletions

View File

@ -16,6 +16,7 @@
from nailgun.api.v1.handlers import base
from nailgun.objects import Release
from nailgun.objects.serializers.release import ComponentSerializer
class ComponentCollectionHandler(base.CollectionHandler):
@ -29,7 +30,9 @@ class ComponentCollectionHandler(base.CollectionHandler):
* 404 (release not found in db)
"""
release = self.get_object_or_404(Release, release_id)
return Release.get_all_components(release)
components = Release.get_all_components(release)
return [ComponentSerializer.serialize(c) for c in components]
def POST(self, release_id):
"""Creating of components is disallowed

View File

@ -18,6 +18,11 @@ from nailgun import consts
from nailgun.api.v1.validators.json_schema import base_types
COMPONENTS_TYPES_STR = '|'.join(
['hypervisor', 'network', 'storage', 'additional_service'])
COMPONENT_NAME_PATTERN = \
'^({0}):([0-9a-z_-]+:)*[0-9a-z_-]+$'.format(COMPONENTS_TYPES_STR)
CLUSTER_UI_SETTINGS = {
"type": "object",
"required": [
@ -88,17 +93,19 @@ single_schema = {
"type": "string",
"enum": list(consts.CLUSTER_STATUSES)
},
"net_provider": {
"type": "string",
"enum": list(consts.CLUSTER_NET_PROVIDERS)
},
"ui_settings": CLUSTER_UI_SETTINGS,
"release_id": {"type": "number"},
"pending_release_id": base_types.NULLABLE_ID,
"replaced_deployment_info": {"type": "object"},
"replaced_provisioning_info": {"type": "object"},
"is_customized": {"type": "boolean"},
"fuel_version": {"type": "string"}
"fuel_version": {"type": "string"},
"components": {
'type': 'array',
'items': [{
'type': 'string',
'pattern': COMPONENT_NAME_PATTERN}]
}
}
}

View File

@ -198,6 +198,14 @@ def upgrade_with_components():
server_default='[]'
)
)
op.add_column(
'clusters',
sa.Column(
'components',
fields.JSON(),
nullable=False,
server_default='[]')
)
def create_openstack_configs_table():
@ -547,6 +555,7 @@ def downgrade_add_baremetal_net():
def downgrade_with_components():
op.drop_column('clusters', 'components')
op.drop_column('plugins', 'components_metadata')
op.drop_column('releases', 'components_metadata')

View File

@ -34,6 +34,7 @@ from nailgun import consts
from nailgun.db import db
from nailgun.db.sqlalchemy.models.base import Base
from nailgun.db.sqlalchemy.models.fields import JSON
from nailgun.db.sqlalchemy.models.mutable import MutableList
from nailgun.db.sqlalchemy.models.node import Node
@ -113,6 +114,11 @@ class Cluster(Base):
is_customized = Column(Boolean, default=False)
fuel_version = Column(Text, nullable=False)
deployment_tasks = Column(JSON, default=[])
components = Column(
MutableList.as_mutable(JSON),
default=[],
server_default='[]',
nullable=False)
extensions = Column(psql.ARRAY(String(consts.EXTENSION_NAME_MAX_SIZE)),
default=[], nullable=False, server_default='{}')

View File

@ -154,35 +154,38 @@ class Cluster(NailgunObject):
assign_nodes = data.pop("nodes", [])
data["fuel_version"] = settings.VERSION["release"]
new_cluster = super(Cluster, cls).create(data)
cls.create_default_group(new_cluster)
cluster = super(Cluster, cls).create(data)
cls.create_default_group(cluster)
cls.create_attributes(new_cluster)
cls.create_vmware_attributes(new_cluster)
cls.create_default_extensions(new_cluster)
cls.create_attributes(cluster)
cls.create_vmware_attributes(cluster)
cls.create_default_extensions(cluster)
try:
cls.get_network_manager(new_cluster).\
create_network_groups_and_config(new_cluster, data)
cls.add_pending_changes(new_cluster, "attributes")
cls.add_pending_changes(new_cluster, "networks")
cls.add_pending_changes(new_cluster, "vmware_attributes")
cls.get_network_manager(cluster).\
create_network_groups_and_config(cluster, data)
cls.add_pending_changes(
cluster, consts.CLUSTER_CHANGES.attributes)
cls.add_pending_changes(
cluster, consts.CLUSTER_CHANGES.networks)
cls.add_pending_changes(
cluster, consts.CLUSTER_CHANGES.vmware_attributes)
if assign_nodes:
cls.update_nodes(new_cluster, assign_nodes)
cls.update_nodes(cluster, assign_nodes)
except (
errors.OutOfVLANs,
errors.OutOfIPs,
errors.NoSuitableCIDR
) as exc:
db().delete(new_cluster)
raise errors.CannotCreate(exc.message)
db().flush()
ClusterPlugins.add_compatible_plugins(new_cluster)
ClusterPlugins.add_compatible_plugins(cluster)
PluginManager.enable_plugins_by_components(cluster)
return new_cluster
return cluster
@classmethod
def delete(cls, instance):

View File

@ -246,12 +246,12 @@ class ClusterPlugins(NailgunObject):
db().flush()
@classmethod
def get_connected_plugins(cls, cluster_id):
"""Returns plugins connected with given cluster.
def get_connected_plugins_data(cls, cluster_id):
"""Returns plugins and cluster_plugins data connected with cluster.
:param cluster_id: Cluster ID
:type cluster_id: int
:returns: List of plugins
:returns: List of mixed data from plugins and cluster_plugins
:rtype: iterable (SQLAlchemy query)
"""
return db().query(
@ -264,8 +264,29 @@ class ClusterPlugins(NailgunObject):
cls.model.attributes
).join(cls.model)\
.filter(cls.model.cluster_id == cluster_id)\
.order_by(models.Plugin.name)\
.order_by(models.Plugin.version)
.order_by(models.Plugin.name, models.Plugin.version)
@classmethod
def get_connected_plugins(cls, cluster, plugin_ids=None):
"""Returns plugins connected with given cluster.
:param cluster: Cluster instance
:type cluster: Cluster SQLAlchemy model
:param plugin_ids: List of specific plugins ids to chose from
:type plugin_ids: list
:returns: List of plugins
:rtype: iterable (SQLAlchemy query)
"""
plugins = db().query(
models.Plugin
).join(cls.model)\
.filter(cls.model.cluster_id == cluster.id)\
.order_by(models.Plugin.name, models.Plugin.version)
if plugin_ids:
plugins = plugins.filter(cls.model.plugin_id.in_(plugin_ids))
return plugins
@classmethod
def get_connected_clusters(cls, plugin_id):

View File

@ -32,6 +32,7 @@ class ClusterSerializer(BasicSerializer):
"fuel_version",
"pending_release_id",
"is_locked",
"components"
)

View File

@ -32,6 +32,7 @@ class ReleaseSerializer(BasicSerializer):
"state",
"attributes_metadata",
"vmware_attributes_metadata",
"components_metadata"
)
@classmethod
@ -43,3 +44,12 @@ class ReleaseSerializer(BasicSerializer):
release_dict["is_deployable"] = Release.is_deployable(instance)
return release_dict
class ComponentSerializer(BasicSerializer):
@classmethod
def serialize(cls, instance):
instance.pop('bind', None)
return instance

View File

@ -115,7 +115,8 @@ class PluginManager(object):
plugins_attributes = {}
for pid, name, title, version, enabled, default_attrs, cluster_attrs\
in ClusterPlugins.get_connected_plugins(cluster.id):
in ClusterPlugins.get_connected_plugins_data(cluster.id):
if all_versions:
enabled = enabled and not default
data = plugins_attributes.get(name, {})
@ -346,3 +347,25 @@ class PluginManager(object):
for plugin in plugins:
plugin_adapter = wrap_plugin(plugin)
plugin_adapter.sync_metadata_to_db()
@classmethod
def enable_plugins_by_components(cls, cluster):
"""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())
for plugin in ClusterPlugins.get_connected_plugins(
cluster, plugin_ids):
plugin_adapter = wrap_plugin(plugin)
plugin_components = set(
component['name']
for component in plugin_adapter.components_metadata)
for component in cluster_components & plugin_components:
ClusterPlugins.set_attributes(
cluster.id, plugin.id, enabled=True)

View File

@ -204,7 +204,8 @@ class InstallationInfo(object):
'is_customized': cluster.is_customized,
'network_configuration': self.get_network_configuration_info(
cluster),
'installed_plugins': self.get_cluster_plugins_info(cluster)
'installed_plugins': self.get_cluster_plugins_info(cluster),
'components': cluster.components
}
clusters_info.append(cluster_info)
return clusters_info

View File

@ -39,7 +39,8 @@ class TestComponentHandler(base.BaseIntegrationTest):
'mode': ['ha'],
'deployment_scripts_path': 'deployment_scripts/'}],
components_metadata=self.env.get_default_components(
name='storage:test_component_2'))
name='storage:test_component_2',
bind='some_action_to_process'))
def test_get_components(self):
resp = self.app.get(

View File

@ -227,6 +227,48 @@ class TestPluginManager(base.BaseIntegrationTest):
expected_message):
PluginManager.get_components_metadata(self.release)
def test_enable_plugins_by_component(self):
self.env.create_plugin(
name='plugin_with_test_storage',
package_version='4.0.0',
fuel_version=['8.0'],
releases=[{
'repository_path': 'repositories/ubuntu',
'version': '2015.1-8.3',
'os': 'ubuntu',
'mode': ['ha'],
'deployment_scripts_path': 'deployment_scripts/'}],
components_metadata=self.env.get_default_components(
name='storage:test_storage'))
plugin = self.env.create_plugin(
version='1.0.0',
name='plugin_with_test_storage',
package_version='4.0.0',
fuel_version=['8.0'],
releases=[{
'repository_path': 'repositories/ubuntu',
'version': '2015.1-8.3',
'os': 'ubuntu',
'mode': ['ha'],
'deployment_scripts_path': 'deployment_scripts/'}],
components_metadata=self.env.get_default_components(
name='storage:test_storage'))
cluster = self.env.create(
release_kwargs={
'operating_system': consts.RELEASE_OS.ubuntu,
'version': '2015.1-8.3'},
cluster_kwargs={
'mode': consts.CLUSTER_MODES.ha_compact,
'api': False,
'components': [
'hypervisor:test_hypervisor',
'storage:test_storage']})
enabled_plugins = ClusterPlugins.get_enabled(cluster.id)
self.assertItemsEqual([plugin], enabled_plugins)
class TestClusterPluginIntegration(base.BaseTestCase):

View File

@ -243,12 +243,6 @@ class TestReleaseMigrations(base.BaseAlembicMigrationTest):
for state in states:
self.assertEqual(state, 'manageonly')
def test_new_component_metadata_field_exists_and_empty(self):
result = db.execute(
sa.select([self.meta.tables['releases'].c.components_metadata]))
self.assertEqual(
jsonutils.loads(result.fetchone()[0]), [])
class TestTaskStatus(base.BaseAlembicMigrationTest):
@ -447,13 +441,21 @@ class TestBaremetalFields(base.BaseAlembicMigrationTest):
self.assertIn((baremetal_gateway, baremetal_range), result)
class TestPluginMigration(base.BaseAlembicMigrationTest):
class TestComponentsMigration(base.BaseAlembicMigrationTest):
def test_new_component_metadata_field_exists_and_empty(self):
result = db.execute(
sa.select([self.meta.tables['plugins'].c.components_metadata]))
self.assertEqual(
jsonutils.loads(result.fetchone()[0]), [])
column_values = [
(self.meta.tables['plugins'].c.components_metadata, []),
(self.meta.tables['releases'].c.components_metadata, []),
(self.meta.tables['clusters'].c.components, [])
]
result = db.execute(sa.select(
[item[0] for item in column_values]))
db_values = result.fetchone()
for idx, db_value in enumerate(db_values):
self.assertEqual(jsonutils.loads(db_value), column_values[idx][1])
class TestMasterSettingsMigration(base.BaseAlembicMigrationTest):

View File

@ -114,38 +114,53 @@ class TestClusterPlugins(ExtraFunctions):
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)
plugin = ClusterPlugins.get_connected_plugins(cluster)[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)
.where(columns.plugin_id == plugin.id)
).fetchone()
self.assertTrue(enabled[0])
def test_get_connected_plugins(self):
def test_get_connected_plugins_data(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)
number_of_connected_plugins_data_items =\
ClusterPlugins.get_connected_plugins_data(cluster.id).count()
self.assertEqual(5, number_of_connected_plugins_data_items)
def test_get_all_connected_plugins(self):
self._create_test_plugins()
cluster = self._create_test_cluster()
number_of_connected_plugins =\
ClusterPlugins.get_connected_plugins(cluster).count()
self.assertEqual(5, number_of_connected_plugins)
def test_get_connected_for_specific_plugins(self):
plugin_ids = self._create_test_plugins()
cluster = self._create_test_cluster()
number_of_connected_plugins =\
ClusterPlugins.get_connected_plugins(
cluster, plugin_ids[1:]).count()
self.assertEqual(4, number_of_connected_plugins)
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)
number_of_connected_clusters =\
ClusterPlugins.get_connected_clusters(plugin_id).count()
self.assertEqual(2, number_of_connected_clusters)
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)
plugin = ClusterPlugins.get_connected_plugins(cluster)[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)
self.assertEqual(enabled_plugin, plugin.id)