Merge "Create duplex relations for component"
This commit is contained in:
commit
294a2998e5
|
@ -18,7 +18,9 @@
|
|||
Release object and collection
|
||||
"""
|
||||
|
||||
import copy
|
||||
from distutils.version import StrictVersion
|
||||
import itertools
|
||||
import yaml
|
||||
|
||||
from nailgun import consts
|
||||
|
@ -160,12 +162,48 @@ class Release(NailgunObject):
|
|||
def get_all_components(cls, instance):
|
||||
"""Get all components related to release
|
||||
|
||||
Due to components architecture compatible/incompatible are duplex
|
||||
relations. So if some component is compatible/incompatible with another
|
||||
the last one also should have such relation.
|
||||
|
||||
: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
|
||||
components = copy.deepcopy(
|
||||
instance.components_metadata + plugin_components)
|
||||
# we should provide commutative property for compatible/incompatible
|
||||
# relations between components
|
||||
for comp_i, comp_j in itertools.permutations(components, 2):
|
||||
if cls._check_relation(comp_j, comp_i, 'incompatible'):
|
||||
comp_i.setdefault('incompatible', []).append({
|
||||
'name': comp_j['name'],
|
||||
'message': "Not compatible with {0}".format(
|
||||
comp_j.get('label') or comp_j.get('name'))})
|
||||
if cls._check_relation(comp_j, comp_i, 'compatible'):
|
||||
comp_i.setdefault('compatible', []).append({
|
||||
'name': comp_j['name']})
|
||||
|
||||
return components
|
||||
|
||||
@classmethod
|
||||
def _check_relation(cls, a, b, relation):
|
||||
"""Helper function to check commutative property for relations"""
|
||||
return (cls._contain(a.get(relation, []), b['name']) and not
|
||||
cls._contain(b.get(relation, []), a['name']))
|
||||
|
||||
@staticmethod
|
||||
def _contain(components, name):
|
||||
"""Check if component with given name exists in components list
|
||||
|
||||
:param components: list of components objects(dicts)
|
||||
:type components: list
|
||||
:param name: component name or wildcard
|
||||
:type name: string
|
||||
"""
|
||||
prefixes = (comp['name'].split('*', 1)[0] for comp in components)
|
||||
return any(name.startswith(x) for x in prefixes)
|
||||
|
||||
|
||||
class ReleaseCollection(NailgunCollection):
|
||||
|
|
|
@ -809,12 +809,12 @@ class EnvironmentManager(object):
|
|||
{
|
||||
'name': 'hypervisor:test_hypervisor',
|
||||
'compatible': [
|
||||
{'name': 'hypervisors:*'},
|
||||
{'name': 'storages:object:block:swift'}
|
||||
{'name': 'hypervisor:*'},
|
||||
{'name': 'storage:object:block:swift'}
|
||||
],
|
||||
'incompatible': [
|
||||
{'name': 'networks:*'},
|
||||
{'name': 'additional_services:*'}
|
||||
{'name': 'network:*'},
|
||||
{'name': 'additional_service:*'}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -361,8 +361,8 @@ class TestClusterComponents(BaseIntegrationTest):
|
|||
self.assertEqual(resp.status_code, 400)
|
||||
self.assertEqual(
|
||||
u"Incompatible components were found: "
|
||||
"'network:core:test_network_1' incompatible with "
|
||||
"[u'hypervisor:test_hypervisor'].",
|
||||
"'hypervisor:test_hypervisor' incompatible with "
|
||||
"[u'network:core:test_network_1'].",
|
||||
resp.json_body['message']
|
||||
)
|
||||
|
||||
|
|
|
@ -50,23 +50,24 @@ class TestComponentHandler(base.BaseIntegrationTest):
|
|||
headers=self.default_headers
|
||||
)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual(resp.json_body, [
|
||||
self.assertItemsEqual(resp.json_body, [
|
||||
{
|
||||
'name': 'hypervisor:test_component_1',
|
||||
'compatible': [
|
||||
{'name': 'hypervisors:*'},
|
||||
{'name': 'storages:object:block:swift'}],
|
||||
{'name': 'hypervisor:*'},
|
||||
{'name': 'storage:object:block:swift'},
|
||||
{'name': 'storage:test_component_2'}],
|
||||
'incompatible': [
|
||||
{'name': 'networks:*'},
|
||||
{'name': 'additional_services:*'}]},
|
||||
{'name': 'network:*'},
|
||||
{'name': 'additional_service:*'}]},
|
||||
{
|
||||
'name': 'storage:test_component_2',
|
||||
'compatible': [
|
||||
{'name': 'hypervisors:*'},
|
||||
{'name': 'storages:object:block:swift'}],
|
||||
{'name': 'hypervisor:*'},
|
||||
{'name': 'storage:object:block:swift'}],
|
||||
'incompatible': [
|
||||
{'name': 'networks:*'},
|
||||
{'name': 'additional_services:*'}]}])
|
||||
{'name': 'network:*'},
|
||||
{'name': 'additional_service:*'}]}])
|
||||
|
||||
def test_404_for_get_components_with_none_release_id(self):
|
||||
resp = self.app.get(
|
||||
|
|
|
@ -1640,29 +1640,61 @@ class TestRelease(BaseTestCase):
|
|||
'os': 'ubuntu',
|
||||
'mode': ['ha'],
|
||||
'deployment_scripts_path': 'deployment_scripts/'}],
|
||||
components_metadata=self.env.get_default_components(
|
||||
name='storage:test_component_2')
|
||||
components_metadata=[dict(
|
||||
name='storage:test_component_2',
|
||||
label='Test storage',
|
||||
incompatible=[{
|
||||
'name': 'hypervisor:test_component_1',
|
||||
'message': 'component_2 not compatible with component_1'}]
|
||||
), dict(
|
||||
name='network:test_component_3',
|
||||
label='Test network',
|
||||
compatible=[{
|
||||
'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:*'}]}])
|
||||
self.assertItemsEqual(components, [{
|
||||
'name': 'hypervisor:test_component_1',
|
||||
'compatible': [
|
||||
{'name': 'hypervisor:*'},
|
||||
{'name': 'storage:object:block:swift'}],
|
||||
'incompatible': [
|
||||
{'name': 'network:*'},
|
||||
{'name': 'additional_service:*'},
|
||||
{'name': 'storage:test_component_2',
|
||||
'message': 'Not compatible with Test storage'}]}, {
|
||||
'name': 'storage:test_component_2',
|
||||
'label': 'Test storage',
|
||||
'compatible': [
|
||||
{'name': 'network:test_component_3'}],
|
||||
'incompatible': [
|
||||
{'name': 'hypervisor:test_component_1',
|
||||
'message': 'component_2 not compatible with component_1'}]}, {
|
||||
'name': 'network:test_component_3',
|
||||
'label': 'Test network',
|
||||
'compatible': [
|
||||
{'name': 'storage:test_component_2'}],
|
||||
'incompatible': [
|
||||
{'name': 'hypervisor:test_component_1',
|
||||
'message': 'Not compatible with hypervisor:test_component_1'}]
|
||||
}])
|
||||
|
||||
def test_contain_component(self):
|
||||
components = [
|
||||
{'name': 'test_component_type_1:test_component_1'},
|
||||
{'name': 'test_component_type_2:*'}
|
||||
]
|
||||
|
||||
self.assertTrue(objects.Release._contain(
|
||||
components, 'test_component_type_1:test_component_1'))
|
||||
|
||||
self.assertTrue(objects.Release._contain(
|
||||
components, 'test_component_type_2:test_component_3'))
|
||||
|
||||
self.assertFalse(objects.Release._contain(
|
||||
components, 'test_component_type_1:test_component_4'))
|
||||
|
||||
|
||||
class TestOpenstackConfig(BaseTestCase):
|
||||
|
|
Loading…
Reference in New Issue