From: JUN JIE NAN Date: Wed, 7 Aug 2013 06:35:13 +0000 (+0800) Subject: Hot SoftwareConfig model part X-Git-Tag: 2014.1~213^2 X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=2fa20bf0c76c74b0d3473fe4dd203c8896432d22;p=openstack-build%2Fheat-build.git Hot SoftwareConfig model part Provide components model and related methods Implements blueprint hot-software-config Change-Id: I251d10f25943513ef346883263a4dd2e0f6fb7a6 --- diff --git a/heat/engine/components.py b/heat/engine/components.py new file mode 100644 index 00000000..e4bf0875 --- /dev/null +++ b/heat/engine/components.py @@ -0,0 +1,99 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# 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. + + +(TYPE, PROPERTIES, SCRIPTS, RELATIONSHIPS) = ( + 'type', 'properties', 'scripts', 'relationships') + +(SOFTWARE_CONFIG_TYPE, HOSTED_ON, DEPENDS_ON) = ( + 'OS::Heat::SoftwareConfig', 'hosted_on', 'depends_on') + + +class Component(dict): + """ + Model for hot component. + """ + + def __init__(self, schema={}): + super(Component, self).__init__(schema) + + @property + def properties(self): + return self.get(PROPERTIES, {}) + + @property + def type(self): + return self.get(TYPE, SOFTWARE_CONFIG_TYPE) + + @property + def scripts(self): + return self.get(SCRIPTS, {}) + + @property + def relations(self): + return self.get(RELATIONSHIPS, []) + + def hosted_on(self): + for rel in self.relations: + if HOSTED_ON in rel: + return rel[HOSTED_ON] + return None + + def depends(self): + deps = [] + rels = self.relations + for rel in rels: + if DEPENDS_ON in rel: + deps.append(rel[DEPENDS_ON]) + return deps + + +class Components(dict): + """ + Model for hot components. + """ + + def __init__(self, schema): + items = schema.iteritems() + schema = dict(map(lambda x: (x[0], Component(x[1])), items)) + super(Components, self).__init__(schema) + + def depends(self): + deps = [] + for (k, v) in self.iteritems(): + for dep in v.depends(): + if dep not in deps: + deps.append(dep) + return deps + + def filter(self, hosted): + return map(lambda x: x[0], + filter(lambda x: x[1].hosted_on() == hosted, + self.iteritems())) + + def validate(self): + deps = self.depends() + for dep in deps: + if dep not in self.iterkeys(): + raise ValueError('component %s is not defined.' % dep) + comp = self[dep] + if dep in comp.depends(): + raise ValueError('component %s depends on itself.' % dep) + for (name, comp) in self.iteritems(): + cdeps = comp.depends() + for dep in cdeps: + if cdeps.count(dep) > 1: + msg = 'duplicated %s in %s depends on.' % (dep, name) + raise ValueError(msg) + return True diff --git a/heat/tests/test_components.py b/heat/tests/test_components.py new file mode 100644 index 00000000..0c898483 --- /dev/null +++ b/heat/tests/test_components.py @@ -0,0 +1,218 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# 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 heat.engine.components import Component +from heat.engine.components import Components +from heat.tests.common import HeatTestCase + + +class ComponentTest(HeatTestCase): + + def test_init(self): + comp = Component() + self.assertEqual(comp.type, 'OS::Heat::SoftwareConfig') + self.assertEqual(comp.properties, {}) + self.assertEqual(comp.scripts, {}) + self.assertEqual(comp.relations, []) + self.assertEqual(comp.hosted_on(), None) + self.assertEqual(comp.depends(), []) + + def test_hosted_on(self): + schema = { + 'relationships': [ + {'hosted_on': 'wordpress'} + ] + } + comp = Component(schema) + self.assertEqual(comp.hosted_on(), 'wordpress') + + def test_depends(self): + schema = { + 'relationships': [ + {'depends_on': 'config_mysql'} + ] + } + comp = Component(schema) + self.assertEqual(comp.depends(), ['config_mysql']) + + comp['relationships'].append({'depends_on': 'config_wordpress'}) + self.assertEqual(comp.depends(), + ['config_mysql', 'config_wordpress']) + + +class ComponentsTest(HeatTestCase): + + def test_init(self): + schema = {} + comps = Components(schema) + self.assertEqual(0, len(comps)) + + schema['config_mysql'] = {} + comps = Components(schema) + self.assertEquals(1, len(comps)) + comp = comps['config_mysql'] + self.assertIsInstance(comp, Component) + + def test_depends(self): + schema = { + 'install_mysql': { + }, + 'config_mysql': { + 'relationships': [ + {'depends_on': 'install_mysql'} + ] + }, + 'start_mysql': { + 'relationships': [ + {'depends_on': 'config_mysql'} + ] + } + } + comps = Components(schema) + self.assertEqual(3, len(comps)) + deps = comps.depends() + self.assertEqual(2, len(deps)) + self.assertIn('install_mysql', deps) + self.assertIn('config_mysql', deps) + + def test_multi_depends(self): + schema = { + 'install_mysql': { + }, + 'config_mysql': { + 'relationships': [ + {'depends_on': 'install_mysql'} + ] + }, + 'start_mysql': { + 'relationships': [ + {'depends_on': 'config_mysql'} + ] + }, + 'install_wordpress': {}, + 'config_wordpress': { + 'relationships': [ + {'depends_on': 'install_wordpress'} + ] + }, + 'start_wordpress': { + 'relationships': [ + {'depends_on': 'config_wordpress'}, + {'depends_on': 'start_mysql'} + ] + } + } + comps = Components(schema) + deps = comps.depends() + self.assertEqual(5, len(deps)) + self.assertNotIn('start_wordpress', deps) + self.assertIn('install_wordpress', deps) + self.assertIn('config_wordpress', deps) + self.assertIn('start_mysql', deps) + self.assertIn('config_mysql', deps) + self.assertIn('install_mysql', deps) + + def test_filter(self): + schema = { + 'install_mysql': { + 'relationships': [ + {'hosted_on': 'mysql'} + ] + }, + 'config_mysql': { + 'relationships': [ + {'hosted_on': 'mysql'}, + {'depends_on': 'install_mysql'} + ] + }, + 'start_mysql': { + 'relationships': [ + {'hosted_on': 'mysql'}, + {'depends_on': 'config_mysql'} + ] + }, + 'install_wordpress': { + 'relationships': [ + {'hosted_on': 'wordpress'} + ] + }, + 'config_wordpress': { + 'relationships': [ + {'hosted_on': 'wordpress'}, + {'depends_on': 'install_wordpress'} + ] + }, + 'start_wordpress': { + 'relationships': [ + {'hosted_on': 'wordpress'}, + {'depends_on': 'config_wordpress'}, + {'depends_on': 'start_mysql'} + ] + } + } + + comps = Components(schema) + names = comps.filter('mysql') + self.assertEqual(3, len(names)) + self.assertIn('config_mysql', names) + self.assertIn('install_mysql', names) + self.assertIn('start_mysql', names) + + names = comps.filter('wordpress') + self.assertEqual(3, len(names)) + self.assertIn('config_wordpress', names) + self.assertIn('install_wordpress', names) + self.assertIn('start_wordpress', names) + + def test_validate(self): + schema = {'install_mysql': {}} + comps = Components(schema) + self.assertTrue(comps.validate()) + + schema = { + 'config_mysql': { + 'relationships': [ + {'depends_on': 'config_mysql'} + ] + } + } + comps = Components(schema) + err = self.assertRaises(ValueError, comps.validate) + self.assertIn('component config_mysql depends on itself.', str(err)) + + schema = { + 'config_mysql': { + 'relationships': [ + {'depends_on': 'install_mysql'} + ] + } + } + comps = Components(schema) + err = self.assertRaises(ValueError, comps.validate) + self.assertIn('component install_mysql is not defined.', str(err)) + + schema = { + 'install_mysql': { + }, + 'config_mysql': { + 'relationships': [ + {'depends_on': 'install_mysql'}, + {'depends_on': 'install_mysql'} + ] + } + } + comps = Components(schema) + err = self.assertRaises(ValueError, comps.validate) + self.assertIn('duplicated install_mysql in config_mysql depends on.', + str(err))