Merge "Refactor component model"

This commit is contained in:
Jenkins 2015-11-19 19:44:06 +00:00 committed by Gerrit Code Review
commit 7d7366c2ec
22 changed files with 288 additions and 605 deletions

View File

@ -15,7 +15,7 @@
# under the License.
from nailgun.api.v1.handlers import base
from nailgun.objects import ComponentCollection
from nailgun.objects import Release
class ComponentCollectionHandler(base.CollectionHandler):
@ -26,9 +26,10 @@ class ComponentCollectionHandler(base.CollectionHandler):
""":returns: JSONized component data for release and releated plugins.
:http: * 200 (OK)
* 404 (release not found in db)
"""
components = ComponentCollection.get_all_by_release(release_id)
return ComponentCollection.to_json(components)
release = self.get_object_or_404(Release, release_id)
return Release.get_all_components(release)
def POST(self, release_id):
"""Creating of components is disallowed

View File

@ -423,13 +423,6 @@ CLOUD_INIT_TEMPLATES = Enum(
'meta_data',
)
COMPONENT_TYPES = Enum(
'hypervisor',
'network',
'storage',
'additional_service',
)
# NOTE(kozhukalov): This constant is used to collect
# the information about installed fuel packages (rpm -q).
# This information is necessary for fuel-stats.

View File

@ -28,10 +28,7 @@ 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
from nailgun.db.sqlalchemy.models.fields import JSON
from nailgun.utils.migration import drop_enum
from nailgun.utils.migration import upgrade_enum
@ -100,8 +97,6 @@ node_errors_new = (
def upgrade():
create_components_table()
create_release_components_table()
upgrade_nodegroups_name_cluster_constraint()
upgrade_release_state()
task_statuses_upgrade()
@ -110,9 +105,11 @@ def upgrade():
upgrade_neutron_parameters()
upgrade_cluster_plugins()
upgrade_add_baremetal_net()
upgrade_with_components()
def downgrade():
downgrade_with_components()
downgrade_add_baremetal_net()
downgrade_cluster_plugins()
downgrade_neutron_parameters()
@ -120,11 +117,7 @@ def downgrade():
task_names_downgrade()
task_statuses_downgrade()
downgrade_release_state()
op.drop_constraint('_name_cluster_uc', 'nodegroups',)
op.drop_table('release_components')
op.drop_table('components')
drop_enum('component_types')
downgrade_nodegroups_name_cluster_constraint()
def upgrade_release_state():
@ -167,42 +160,27 @@ def upgrade_nodegroups_name_cluster_constraint():
)
def create_components_table():
op.create_table('components',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.Column('type', sa.Enum('hypervisor', 'network',
'storage', 'additional_service',
name='component_types'),
nullable=False),
sa.Column('hypervisors', psql.ARRAY(sa.String()),
server_default='{}', nullable=False),
sa.Column('networks', psql.ARRAY(sa.String()),
server_default='{}', nullable=False),
sa.Column('storages', psql.ARRAY(sa.String()),
server_default='{}', nullable=False),
sa.Column('additional_services', psql.ARRAY(sa.String()),
server_default='{}', nullable=False),
sa.Column('plugin_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(
['plugin_id'], ['plugins.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name', 'type',
name='_component_name_type_uc')
)
def downgrade_nodegroups_name_cluster_constraint():
op.drop_constraint('_name_cluster_uc', 'nodegroups',)
def create_release_components_table():
op.create_table('release_components',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('release_id', sa.Integer(), nullable=False),
sa.Column('component_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(
['component_id'], ['components.id'], ),
sa.ForeignKeyConstraint(
['release_id'], ['releases.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
def upgrade_with_components():
op.add_column(
'plugins',
sa.Column(
'components_metadata',
fields.JSON(),
server_default='[]'
)
)
op.add_column(
'releases',
sa.Column(
'components_metadata',
fields.JSON(),
server_default='[]'
)
)
def downgrade_release_state():
@ -484,10 +462,15 @@ def upgrade_add_baremetal_net():
sa.Column('baremetal_gateway', sa.String(length=25),
nullable=True))
op.add_column('neutron_config',
sa.Column('baremetal_range', JSON(), nullable=True,
sa.Column('baremetal_range', fields.JSON(), nullable=True,
server_default='[]'))
def downgrade_add_baremetal_net():
op.drop_column('neutron_config', 'baremetal_gateway')
op.drop_column('neutron_config', 'baremetal_range')
def downgrade_with_components():
op.drop_column('plugins', 'components_metadata')
op.drop_column('releases', 'components_metadata')

View File

@ -51,6 +51,3 @@ from nailgun.db.sqlalchemy.models.master_node_settings \
from nailgun.db.sqlalchemy.models.plugins import ClusterPlugins
from nailgun.db.sqlalchemy.models.plugins import Plugin
from nailgun.db.sqlalchemy.models.component import Component
from nailgun.db.sqlalchemy.models.component import ReleaseComponent

View File

@ -1,63 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2015 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from sqlalchemy import Column
from sqlalchemy import Enum
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy import UniqueConstraint
from sqlalchemy.dialects import postgresql as psql
from sqlalchemy.orm import relationship
from nailgun import consts
from nailgun.db.sqlalchemy.models.base import Base
class Component(Base):
__tablename__ = 'components'
__table_args__ = (
UniqueConstraint('name', 'type', name='_component_name_type_uc'),
)
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
type = Column(
Enum(*consts.COMPONENT_TYPES, name='component_types'),
nullable=False
)
hypervisors = Column(psql.ARRAY(String), nullable=False, default=[],
server_default='{}')
networks = Column(psql.ARRAY(String), nullable=False, default=[],
server_default='{}')
storages = Column(psql.ARRAY(String), nullable=False, default=[],
server_default='{}')
additional_services = Column(psql.ARRAY(String), nullable=False,
default=[], server_default='{}')
plugin_id = Column(Integer, ForeignKey('plugins.id', ondelete='CASCADE'))
plugin = relationship("Plugin", backref="components")
releases = relationship('Release',
secondary='release_components',
backref='components')
class ReleaseComponent(Base):
__tablename__ = 'release_components'
id = Column(Integer, primary_key=True)
release_id = Column(Integer, ForeignKey('releases.id', ondelete='CASCADE'),
nullable=False)
component_id = Column(Integer, ForeignKey('components.id'), nullable=False)

View File

@ -25,6 +25,7 @@ from sqlalchemy.orm import relationship
from nailgun.db.sqlalchemy.models.base import Base
from nailgun.db.sqlalchemy.models.fields import JSON
from nailgun.db.sqlalchemy.models.mutable import MutableList
class ClusterPlugins(Base):
@ -74,6 +75,8 @@ class Plugin(Base):
volumes_metadata = Column(JSON, server_default='{}', nullable=False)
roles_metadata = Column(JSON, server_default='{}', nullable=False)
network_roles_metadata = Column(JSON, server_default='[]', nullable=False)
components_metadata = Column(
MutableList.as_mutable(JSON), server_default='[]')
deployment_tasks = Column(JSON, server_default='[]', nullable=False)
# TODO(apopovych): To support old plugins versions we need separate
# tasks which runs directly during deployment(stored in `deployment_tasks`

View File

@ -27,6 +27,7 @@ from sqlalchemy.orm import relationship
from nailgun import consts
from nailgun.db.sqlalchemy.models.base import Base
from nailgun.db.sqlalchemy.models.fields import JSON
from nailgun.db.sqlalchemy.models.mutable import MutableList
class Release(Base):
@ -58,6 +59,8 @@ class Release(Base):
wizard_metadata = Column(JSON, default={})
deployment_tasks = Column(JSON, default=[])
vmware_attributes_metadata = Column(JSON, default=[])
components_metadata = Column(
MutableList.as_mutable(JSON), default=[], server_default='[]')
modes = Column(JSON, default=[])
clusters = relationship(
"Cluster",

View File

@ -54,6 +54,3 @@ from nailgun.objects.plugin import ClusterPlugins
from nailgun.objects.network_group import NetworkGroup
from nailgun.objects.network_group import NetworkGroupCollection
from nailgun.objects.component import Component
from nailgun.objects.component import ComponentCollection

View File

@ -1,71 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2015 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from sqlalchemy.orm import joinedload
from nailgun.db import db
from nailgun.db.sqlalchemy import models
from nailgun.objects import NailgunCollection
from nailgun.objects import NailgunObject
from nailgun.objects import Release
from nailgun.objects.serializers import component
class Component(NailgunObject):
model = models.Component
serializer = component.ComponentSerializer
@classmethod
def get_by_name_and_type(cls, component_name, component_type):
return db().query(cls.model).filter_by(
name=component_name, type=component_type).first()
class ComponentCollection(NailgunCollection):
single = Component
@classmethod
def get_all_by_release(cls, release_id):
"""Get all components for specific release.
:param release_id: release ID
:type release_id: int
:returns: list -- list of components
"""
components = []
release = Release.get_by_uid(release_id)
release_os = release.operating_system.lower()
release_version = release.version
db_components = db().query(cls.single.model).options(
joinedload(cls.single.model.releases),
joinedload(cls.single.model.plugin)).all()
for db_component in db_components:
if db_component.releases:
for db_release in db_component.releases:
if db_release.id == release.id:
components.append(db_component)
elif db_component.plugin:
for plugin_release in db_component.plugin.releases:
if (release_os == plugin_release.get('os') and
release_version == plugin_release.get('version')):
components.append(db_component)
return components

View File

@ -97,6 +97,26 @@ class PluginCollection(NailgunCollection):
"""
return cls.filter_by_id_list(cls.all(), plugin_ids)
@classmethod
def get_by_release(cls, release):
"""Returns plugins by given release
:param release: Release instance
:type release: Release DB model
:returns: list -- list of sorted plugins
"""
release_plugins = set()
release_os = release.operating_system.lower()
release_version = release.version
for plugin in PluginCollection.all():
for plugin_release in plugin.releases:
if (release_os == plugin_release.get('os') and
release_version == plugin_release.get('version')):
release_plugins.add(plugin)
return sorted(release_plugins, key=lambda plugin: plugin.name)
class ClusterPlugins(NailgunObject):

View File

@ -29,6 +29,7 @@ from nailgun.objects import NailgunCollection
from nailgun.objects import NailgunObject
from nailgun.objects.serializers import release as release_serializer
from nailgun.orchestrator import graph_configuration
from nailgun.plugins.manager import PluginManager
from nailgun.settings import settings
@ -162,6 +163,17 @@ class Release(NailgunObject):
def get_min_controller_count(cls, instance):
return instance.roles_metadata['controller']['limits']['min']
@classmethod
def get_all_components(cls, instance):
"""Get all components related to release
:param instance: Release instance
:type instance: Release DB instance
:returns: list -- list of all components
"""
plugin_components = PluginManager.get_components_metadata(instance)
return instance.components_metadata + plugin_components
class ReleaseCollection(NailgunCollection):
"""Release collection"""

View File

@ -1,41 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2015 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from nailgun.objects.serializers.base import BasicSerializer
class ComponentSerializer(BasicSerializer):
fields = (
"name",
"type",
"plugin_id"
)
@classmethod
def serialize(cls, instance, fields=None):
data_dict = BasicSerializer.serialize(
instance, fields=fields if fields else cls.fields)
prepare = lambda array: '*' if array == ['*'] else array
data_dict['compatible'] = {
'hypervisors': prepare(instance.hypervisors),
'networks': prepare(instance.networks),
'storages': prepare(instance.storages),
'additional_services': prepare(instance.additional_services)
}
data_dict['releases_ids'] = [r.id for r in instance.releases]
return data_dict

View File

@ -25,7 +25,6 @@ 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
@ -135,6 +134,14 @@ class PluginAdapterBase(object):
def volumes_metadata(self):
return self.plugin.volumes_metadata
@property
def components_metadata(self):
return self.plugin.components_metadata
@property
def releases(self):
return self.plugin.releases
@property
def normalized_roles_metadata(self):
"""Block plugin disabling if nodes with plugin-provided roles exist"""
@ -247,7 +254,6 @@ class PluginAdapterV3(PluginAdapterV2):
"""Sync metadata from all config yaml files to DB"""
super(PluginAdapterV3, self).sync_metadata_to_db()
data_to_update = {}
db_config_metadata_mapping = {
'attributes_metadata': self.environment_config_name,
'roles_metadata': self.node_roles_config_name,
@ -257,7 +263,12 @@ class PluginAdapterV3(PluginAdapterV2):
'tasks': self.task_config_name
}
for attribute, config in six.iteritems(db_config_metadata_mapping):
self._update_plugin(db_config_metadata_mapping)
def _update_plugin(self, mapping):
data_to_update = {}
for attribute, config in six.iteritems(mapping):
config_file_path = os.path.join(self.plugin_path, config)
attribute_data = self._load_config(config_file_path)
# Plugin columns have constraints for nullable data, so
@ -277,23 +288,12 @@ class PluginAdapterV4(PluginAdapterV3):
def sync_metadata_to_db(self):
super(PluginAdapterV4, self).sync_metadata_to_db()
components_file_path = os.path.join(
self.plugin_path, self.components)
components = self._load_config(components_file_path) or []
for component in components:
component_name = component.get('name')
component_type = component.get('type')
db_component = Component.get_by_name_and_type(
component_name, component_type)
if not db_component:
components_data = component.get('compatible', {})
components_data.update({
'name': component_name,
'type': component_type,
'plugin_id': self.plugin.id
})
Component.create(components_data)
db_config_metadata_mapping = {
'components_metadata': self.components
}
self._update_plugin(db_config_metadata_mapping)
__version_mapping = {

View File

@ -261,9 +261,8 @@ class PluginManager(object):
"""Get volumes metadata for cluster from all plugins which enabled it.
:param cluster: A cluster instance
:type cluster: nailgun.db.sqlalchemy.models.cluster.Cluster
:return: Object with merged volumes data from plugins
:rtype: dict
:type cluster: Cluster model
:return: dict -- Object with merged volumes data from plugins
"""
volumes_metadata = {
'volumes': [],
@ -301,6 +300,35 @@ class PluginManager(object):
return volumes_metadata
@classmethod
def get_components_metadata(cls, release):
"""Get components metadata for all plugins which related to release.
:param release: A release instance
:type release: Release model
:return: list -- List of plugins components
"""
components = []
seen_components = \
dict((c['name'], 'release') for c in release.components_metadata)
for plugin_adapter in map(
wrap_plugin, PluginCollection.get_by_release(release)):
for component in plugin_adapter.components_metadata:
name = component['name']
if name in seen_components:
raise errors.AlreadyExists(
'Plugin {0} is overlapping with {1} by introducing '
'the same component with name "{2}"'
.format(plugin_adapter.full_name,
seen_components[name],
name))
seen_components[name] = plugin_adapter.full_name
components.append(component)
return components
@classmethod
def sync_plugins_metadata(cls, plugin_ids=None):
"""Sync metadata for plugins by given IDs.

View File

@ -61,7 +61,6 @@ 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
from nailgun.objects import NodeGroup
@ -446,23 +445,6 @@ class EnvironmentManager(object):
ClusterPlugins.set_attributes(cluster.id, plugin.id, enabled=True)
return plugin
def create_component(self, release=None, plugin=None, **kwargs):
component = self.get_default_components(**kwargs)[0]
component_data = component.get('compatible', {})
component_data.update({
'name': component.get('name'),
'type': component.get('type')
})
component = Component.create(component_data)
if release:
component.releases.append(release)
if plugin:
component.plugin = plugin
self.db.flush()
return component
def default_metadata(self):
item = self.find_item_by_pk_model(
self.read_fixtures(("sample_environment",)),
@ -719,14 +701,15 @@ class EnvironmentManager(object):
def get_default_components(self, **kwargs):
default_components = [
{
'name': 'test_hypervisor',
'type': 'hypervisor',
'compatible': {
'hypervisors': ['*'],
'networks': [],
'storages': ['object:block:swift'],
'additional_services': []
}
'name': 'hypervisor:test_hypervisor',
'compatible': [
{'name': 'hypervisors:*'},
{'name': 'storages:object:block:swift'}
],
'incompatible': [
{'name': 'networks:*'},
{'name': 'additional_services:*'}
]
}
]

View File

@ -26,7 +26,9 @@ class TestComponentHandler(base.BaseIntegrationTest):
self.release = self.env.create_release(
version='2015.1-8.0',
operating_system='Ubuntu',
modes=[consts.CLUSTER_MODES.ha_compact])
modes=[consts.CLUSTER_MODES.ha_compact],
components_metadata=self.env.get_default_components(
name='hypervisor:test_component_1'))
self.plugin = self.env.create_plugin(
name='compatible_plugin',
fuel_version=['8.0'],
@ -35,47 +37,35 @@ class TestComponentHandler(base.BaseIntegrationTest):
'version': '2015.1-8.0',
'os': 'ubuntu',
'mode': ['ha'],
'deployment_scripts_path': 'deployment_scripts/'}])
self.core_component = self.env.create_component(
release=self.release,
name='test_component_1')
self.plugin_component = self.env.create_component(
plugin=self.plugin,
name='test_component_2',
type='additional_service')
'deployment_scripts_path': 'deployment_scripts/'}],
components_metadata=self.env.get_default_components(
name='storage:test_component_2'))
def test_get_components(self):
release_id = self.release.id
plugin_id = self.plugin.id
resp = self.app.get(
reverse(
'ComponentCollectionHandler',
kwargs={'release_id': release_id}),
kwargs={'release_id': self.release.id}),
headers=self.default_headers
)
self.assertEqual(200, resp.status_code)
self.assertEqual(resp.json_body, [
{
'name': 'test_component_1',
'type': 'hypervisor',
'releases_ids': [release_id],
'plugin_id': None,
'compatible': {
'hypervisors': '*',
'networks': [],
'storages': ['object:block:swift'],
'additional_services': []}},
'name': 'hypervisor:test_component_1',
'compatible': [
{'name': 'hypervisors:*'},
{'name': 'storages:object:block:swift'}],
'incompatible': [
{'name': 'networks:*'},
{'name': 'additional_services:*'}]},
{
'name': 'test_component_2',
'type': 'additional_service',
'releases_ids': [],
'plugin_id': plugin_id,
'compatible': {
'hypervisors': '*',
'networks': [],
'storages': ['object:block:swift'],
'additional_services': []}}])
'name': 'storage:test_component_2',
'compatible': [
{'name': 'hypervisors:*'},
{'name': 'storages:object:block:swift'}],
'incompatible': [
{'name': 'networks:*'},
{'name': 'additional_services:*'}]}])
def test_404_for_get_components_with_none_release_id(self):
resp = self.app.get(
@ -89,12 +79,10 @@ class TestComponentHandler(base.BaseIntegrationTest):
self.assertEqual(404, resp.status_code)
def test_post_components_not_allowed(self):
release_id = self.release.id
resp = self.app.post(
reverse(
'ComponentCollectionHandler',
kwargs={'release_id': release_id}),
kwargs={'release_id': self.release.id}),
headers=self.default_headers,
expect_errors=True
)

View File

@ -29,7 +29,12 @@ class TestPluginManager(base.BaseIntegrationTest):
def setUp(self):
super(TestPluginManager, self).setUp()
self.env.create()
self.env.create(
release_kwargs={
'version': '2015.1-8.0',
'operating_system': 'Ubuntu'})
self.release = self.env.releases[0]
self.cluster = self.env.clusters[0]
# Create two plugins with package verion 3.0.0
@ -161,6 +166,67 @@ class TestPluginManager(base.BaseIntegrationTest):
PluginManager.sync_plugins_metadata([self.env.plugins[0].id])
self.assertEqual(sync_mock.call_count, 1)
def test_get_components_metadata(self):
self.env.create_plugin(
name='plugin_with_components',
package_version='4.0.0',
fuel_version=['8.0'],
components_metadata=self.env.get_default_components())
components_metadata = PluginManager.get_components_metadata(
self.release)
self.assertEqual(
components_metadata, self.env.get_default_components())
def test_raise_exception_when_plugin_overlap_release_component(self):
release = self.env.create_release(
version='2015.1-8.1',
operating_system='Ubuntu',
modes=[consts.CLUSTER_MODES.ha_compact],
components_metadata=self.env.get_default_components())
self.env.create_plugin(
name='plugin_with_components',
package_version='4.0.0',
fuel_version=['8.0'],
releases=[{
'repository_path': 'repositories/ubuntu',
'version': '2015.1-8.1', 'os': 'ubuntu',
'mode': ['ha'],
'deployment_scripts_path': 'deployment_scripts/'}],
components_metadata=self.env.get_default_components())
expected_message = (
'Plugin plugin_with_components-0.1.0 is overlapping with release '
'by introducing the same component with name '
'"hypervisor:test_hypervisor"')
with self.assertRaisesRegexp(errors.AlreadyExists,
expected_message):
PluginManager.get_components_metadata(release)
def test_raise_exception_when_plugin_overlap_another_component(self):
self.env.create_plugin(
name='plugin_with_components_1',
package_version='4.0.0',
fuel_version=['8.0'],
components_metadata=self.env.get_default_components())
self.env.create_plugin(
name='plugin_with_components_2',
package_version='4.0.0',
fuel_version=['8.0'],
components_metadata=self.env.get_default_components())
expected_message = (
'Plugin plugin_with_components_2-0.1.0 is overlapping with '
'plugin_with_components_1-0.1.0 by introducing the same component '
'with name "hypervisor:test_hypervisor"')
with self.assertRaisesRegexp(errors.AlreadyExists,
expected_message):
PluginManager.get_components_metadata(self.release)
class TestClusterPluginIntegration(base.BaseTestCase):

View File

@ -1,112 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2015 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from nailgun import consts
from nailgun.objects import ComponentCollection
from nailgun.objects.serializers.component import ComponentSerializer
from nailgun.test import base
class BaseComponentTestCase(base.BaseTestCase):
def setUp(self):
super(BaseComponentTestCase, self).setUp()
self.release = self.env.create_release(
version='2015.1-8.0',
operating_system='Ubuntu',
modes=[consts.CLUSTER_MODES.ha_compact])
self.plugin = self.env.create_plugin(
name='compatible_plugin',
fuel_version=['8.0'],
releases=[{
'repository_path': 'repositories/ubuntu',
'version': '2015.1-8.0',
'os': 'ubuntu',
'mode': ['ha'],
'deployment_scripts_path': 'deployment_scripts/'}])
self.core_component = self.env.create_component(
release=self.release,
name='test_component_1')
self.plugin_component = self.env.create_component(
plugin=self.plugin,
name='test_component_2',
type='additional_service')
class TestComponentCollection(BaseComponentTestCase):
def test_get_all_by_release(self):
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])
self.env.create_component(
plugin=self.incompatible_plugin,
name='incompatible_plugin_component')
self.env.create_component(
release=self.incompatible_release,
name='incompatible_core_component')
components = ComponentCollection.get_all_by_release(self.release.id)
for component in components:
self.assertIn(
component.name, ['test_component_1', 'test_component_2'])
self.assertNotIn(component.name, [
'incompatible_plugin_component',
'incompatible_core_component'])
self.assertEqual(len(components), 2)
class TestComponentSerializer(BaseComponentTestCase):
def test_core_component_serialization(self):
release_id = self.release.id
component_data = ComponentSerializer.serialize(
self.core_component)
self.assertDictEqual(component_data, {
'name': 'test_component_1',
'type': 'hypervisor',
'compatible': {
'additional_services': [],
'networks': [],
'storages': ['object:block:swift'],
'hypervisors': '*'
},
'plugin_id': None,
'releases_ids': [release_id]})
def test_plugin_component_serialization(self):
plugin_id = self.plugin.id
component_data = ComponentSerializer.serialize(
self.plugin_component)
self.assertDictEqual(component_data, {
'name': 'test_component_2',
'type': 'additional_service',
'compatible': {
'additional_services': [],
'networks': [],
'storages': ['object:block:swift'],
'hypervisors': '*'
},
'plugin_id': plugin_id,
'releases_ids': []})

View File

@ -68,7 +68,7 @@ def prepare():
'status': 'new',
'net_provider': 'neutron',
'grouping': 'roles',
'fuel_version': '6.1',
'fuel_version': '7.0',
}
)
@ -200,159 +200,6 @@ def insert_table_row(table, row_data):
return result.inserted_primary_key[0]
class TestComponentTableMigration(base.BaseAlembicMigrationTest):
def test_table_fields_and_default_values(self):
component_table = self.meta.tables['components']
insert_table_row(component_table,
{'name': 'test_component', 'type': 'network'})
columns = [t.name for t in component_table.columns]
self.assertItemsEqual(columns, ['id', 'name', 'type', 'hypervisors',
'networks', 'storages',
'plugin_id', 'additional_services'])
column_with_default_values = [
(component_table.c.hypervisors, []),
(component_table.c.networks, []),
(component_table.c.storages, []),
(component_table.c.additional_services, [])
]
result = db.execute(
sa.select([item[0] for item in column_with_default_values]))
db_values = result.fetchone()
for idx, db_value in enumerate(db_values):
self.assertEqual(db_value, column_with_default_values[idx][1])
def test_unique_name_type_constraint(self):
test_name = six.text_type(uuid.uuid4())
test_type = 'storage'
component_table = self.meta.tables['components']
insert_table_row(component_table,
{'name': test_name, 'type': test_type})
insert_table_row(component_table,
{'name': six.text_type(uuid.uuid4()),
'type': test_type})
same_type_components_count = db.execute(
sa.select([sa.func.count(component_table.c.name)]).
where(component_table.c.type == test_type)
).fetchone()[0]
self.assertEqual(same_type_components_count, 2)
with self.assertRaisesRegexp(IntegrityError,
'duplicate key value violates unique '
'constraint "_component_name_type_uc"'):
insert_table_row(component_table,
{'name': test_name, 'type': test_type})
def test_component_types_enum(self):
allow_type_name = ('hypervisor', 'network', 'storage',
'additional_service')
component_table = self.meta.tables['components']
for type in allow_type_name:
name = six.text_type(uuid.uuid4())
insert_table_row(component_table, {'name': name, 'type': type})
inserted_count = db.execute(
sa.select([sa.func.count(component_table.c.name)]).
where(sa.and_(component_table.c.type == type,
component_table.c.name == name))
).fetchone()[0]
self.assertEqual(inserted_count, 1)
with self.assertRaisesRegexp(DataError, 'invalid input value for '
'enum component_types'):
insert_table_row(component_table,
{'name': 'test', 'type': 'wrong_type_name'})
def test_cascade_plugin_deletion(self):
plugin_table = self.meta.tables['plugins']
plugin_id = insert_table_row(
plugin_table,
{
'name': 'test_plugin',
'title': 'Test plugin',
'version': '1.0.0',
'description': 'Test plugin for Fuel',
'homepage': 'http://fuel_plugins.test_plugin.com',
'package_version': '3.0.0'
}
)
component_table = self.meta.tables['components']
insert_table_row(
component_table,
{'name': 'test_name', 'plugin_id': plugin_id, 'type': 'storage'})
db.execute(
sa.delete(plugin_table).where(plugin_table.c.id == plugin_id))
deleted_plugin_components = db.execute(
sa.select([sa.func.count(component_table.c.name)]).
where(component_table.c.plugin_id == plugin_id)
).fetchone()[0]
self.assertEqual(deleted_plugin_components, 0)
class TestReleaseComponentTableMigration(base.BaseAlembicMigrationTest):
def test_component_foreign_key_constraints(self):
release_component_table = self.meta.tables['release_components']
component_id = insert_table_row(
self.meta.tables['components'],
{'name': 'test_name', 'type': 'network'}
)
with self.assertRaisesRegexp(IntegrityError,
'violates foreign key constraint '
'"release_components_release_id_fkey"'):
insert_table_row(
release_component_table,
{'release_id': -1, 'component_id': component_id}
)
def test_release_foreign_key_constraints(self):
release_component_table = self.meta.tables['release_components']
release_table = self.meta.tables['releases']
release_id = db.execute(sa.select([release_table.c.id])).fetchone()[0]
with self.assertRaisesRegexp(IntegrityError,
'violates foreign key constraint '
'"release_components_component_id_fkey"'):
insert_table_row(
release_component_table,
{'release_id': release_id, 'component_id': -1}
)
def test_non_null_fields(self):
release_component_table = self.meta.tables['release_components']
with self.assertRaisesRegexp(IntegrityError,
'violates not-null constraint'):
insert_table_row(release_component_table, {})
def test_cascade_release_deletion(self):
release_component_table = self.meta.tables['release_components']
release_table = self.meta.tables['releases']
release_id = insert_table_row(
release_table,
{
'name': 'release_with_components',
'version': '2014.2.2-6.1',
'operating_system': 'ubuntu',
'state': 'available'
}
)
component_id = insert_table_row(
self.meta.tables['components'],
{'name': six.text_type(uuid.uuid4()), 'type': 'hypervisor'}
)
insert_table_row(
release_component_table,
{'release_id': release_id, 'component_id': component_id}
)
db.execute(
sa.delete(release_table).where(release_table.c.id == release_id))
deleted_plugin_components = db.execute(
sa.select([sa.func.count(release_component_table.c.id)]).
where(release_component_table.c.release_id == release_id)
).fetchone()[0]
self.assertEqual(deleted_plugin_components, 0)
class TestNodeGroupsMigration(base.BaseAlembicMigrationTest):
def test_name_cluster_unique_constraint_migration(self):
@ -396,6 +243,12 @@ 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):
@ -592,3 +445,12 @@ class TestBaremetalFields(base.BaseAlembicMigrationTest):
self.meta.tables['neutron_config'].c.baremetal_range])).\
fetchall()
self.assertIn((baremetal_gateway, baremetal_range), result)
class TestPluginMigration(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]), [])

View File

@ -89,6 +89,14 @@ class TestPluginCollection(ExtraFunctions):
self.assertListEqual(
[plugin.id for plugin in plugins], ids)
def test_get_by_release(self):
release = self.env.create_release(
version='2015.1-8.0',
operating_system='Ubuntu',
modes=[consts.CLUSTER_MODES.ha_compact])
for plugin in PluginCollection.get_by_release(release):
self.assertNotEqual(plugin.name, 'incompatible_plugin')
class TestClusterPlugins(ExtraFunctions):

View File

@ -1426,3 +1426,48 @@ class TestNetworkGroup(BaseTestCase):
def test_get_default_networkgroup(self):
ng = objects.NetworkGroup.get_default_admin_network()
self.assertIsNotNone(ng)
class TestRelease(BaseTestCase):
def test_get_all_components(self):
release = self.env.create_release(
version='2015.1-8.0',
operating_system='Ubuntu',
modes=[consts.CLUSTER_MODES.ha_compact],
components_metadata=self.env.get_default_components(
name='hypervisor:test_component_1'))
self.env.create_plugin(
name='plugin_with_components',
package_version='4.0.0',
fuel_version=['8.0'],
releases=[{
'repository_path': 'repositories/ubuntu',
'version': '2015.1-8.0',
'os': 'ubuntu',
'mode': ['ha'],
'deployment_scripts_path': 'deployment_scripts/'}],
components_metadata=self.env.get_default_components(
name='storage:test_component_2')
)
components = objects.Release.get_all_components(release)
self.assertListEqual(components, [
{
'name': 'hypervisor:test_component_1',
'compatible': [
{'name': 'hypervisors:*'},
{'name': 'storages:object:block:swift'}],
'incompatible': [
{'name': 'networks:*'},
{'name': 'additional_services:*'}]},
{
'name': 'storage:test_component_2',
'compatible': [
{'name': 'hypervisors:*'},
{'name': 'storages:object:block:swift'}],
'incompatible': [
{'name': 'networks:*'},
{'name': 'additional_services:*'}]}])

View File

@ -24,7 +24,6 @@ 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
from nailgun.settings import settings
@ -312,26 +311,8 @@ class TestPluginV4(TestPluginBase):
self.plugin.deployment_tasks, deployment_tasks)
self.assertEqual(
self.plugin.tasks, tasks)
component = Component.get_by_name_and_type(
components_metadata[0].get('name'),
components_metadata[0].get('type'))
compatible = components_metadata[0].get('compatible')
self.assertEqual(
component.name, components_metadata[0].get('name'))
self.assertEqual(
component.type, components_metadata[0].get('type'))
self.assertEqual(
component.hypervisors, compatible.get('hypervisors'))
self.assertEqual(
component.networks, compatible.get('networks'))
self.assertEqual(
component.storages, compatible.get('storages'))
self.assertEqual(
component.additional_services,
compatible.get('additional_services'))
self.assertEqual(component.plugin_id, self.plugin.id)
self.plugin.components_metadata, components_metadata)
class TestClusterCompatibilityValidation(base.BaseTestCase):