From 727ec1ac64c34f57c66d0984acf0f802455ae014 Mon Sep 17 00:00:00 2001 From: Steve Baker Date: Tue, 19 Aug 2014 10:49:10 +1200 Subject: [PATCH] Implement OS::Heat::StructuredDeployments This is a resource which subclasses SoftwareDeployments to deploy a group of OS::Heat::StructuredDeployment resources Change-Id: I5f9603c68bb7cd81ef60a6b2ba965e5b8e93887a Implements-Blueprint: deployment-multiple-servers --- .../software_config/structured_config.py | 124 +++++++++++++----- heat/tests/test_structured_config.py | 100 +++++++++++++- 2 files changed, 192 insertions(+), 32 deletions(-) diff --git a/heat/engine/resources/software_config/structured_config.py b/heat/engine/resources/software_config/structured_config.py index a91eca478..a44f7c142 100644 --- a/heat/engine/resources/software_config/structured_config.py +++ b/heat/engine/resources/software_config/structured_config.py @@ -21,14 +21,6 @@ from heat.engine import properties from heat.engine.resources.software_config import software_config as sc from heat.engine.resources.software_config import software_deployment as sd -PROPERTIES = ( - INPUT_KEY -) = ( - 'input_key' -) - -DEFAULT_INPUT_KEY = 'get_input' - class StructuredConfig(sc.SoftwareConfig): ''' @@ -40,16 +32,26 @@ class StructuredConfig(sc.SoftwareConfig): stored and returned by the software_configs API as parsed JSON. ''' + PROPERTIES = ( + GROUP, + CONFIG, + OPTIONS, + INPUTS, + OUTPUTS + ) = ( + sc.SoftwareConfig.GROUP, + sc.SoftwareConfig.CONFIG, + sc.SoftwareConfig.OPTIONS, + sc.SoftwareConfig.INPUTS, + sc.SoftwareConfig.OUTPUTS + ) + properties_schema = { - sc.SoftwareConfig.GROUP: sc.SoftwareConfig.properties_schema[ - sc.SoftwareConfig.GROUP], - sc.SoftwareConfig.OPTIONS: sc.SoftwareConfig.properties_schema[ - sc.SoftwareConfig.OPTIONS], - sc.SoftwareConfig.INPUTS: sc.SoftwareConfig.properties_schema[ - sc.SoftwareConfig.INPUTS], - sc.SoftwareConfig.OUTPUTS: sc.SoftwareConfig.properties_schema[ - sc.SoftwareConfig.OUTPUTS], - sc.SoftwareConfig.CONFIG: properties.Schema( + GROUP: sc.SoftwareConfig.properties_schema[GROUP], + OPTIONS: sc.SoftwareConfig.properties_schema[OPTIONS], + INPUTS: sc.SoftwareConfig.properties_schema[INPUTS], + OUTPUTS: sc.SoftwareConfig.properties_schema[OUTPUTS], + CONFIG: properties.Schema( properties.Schema.MAP, _('Map representing the configuration data structure which will ' 'be serialized to JSON format.') @@ -73,32 +75,44 @@ class StructuredDeployment(sd.SoftwareDeployment): different input_key property value can be specified. ''' + PROPERTIES = ( + CONFIG, + SERVER, + INPUT_VALUES, + DEPLOY_ACTIONS, + NAME, + SIGNAL_TRANSPORT, + INPUT_KEY + ) = ( + sd.SoftwareDeployment.CONFIG, + sd.SoftwareDeployment.SERVER, + sd.SoftwareDeployment.INPUT_VALUES, + sd.SoftwareDeployment.DEPLOY_ACTIONS, + sd.SoftwareDeployment.NAME, + sd.SoftwareDeployment.SIGNAL_TRANSPORT, + 'input_key' + ) + _sd_ps = sd.SoftwareDeployment.properties_schema properties_schema = { - sd.SoftwareDeployment.CONFIG: _sd_ps[ - sd.SoftwareDeployment.CONFIG], - sd.SoftwareDeployment.SERVER: _sd_ps[ - sd.SoftwareDeployment.SERVER], - sd.SoftwareDeployment.INPUT_VALUES: _sd_ps[ - sd.SoftwareDeployment.INPUT_VALUES], - sd.SoftwareDeployment.DEPLOY_ACTIONS: _sd_ps[ - sd.SoftwareDeployment.DEPLOY_ACTIONS], - sd.SoftwareDeployment.SIGNAL_TRANSPORT: _sd_ps[ - sd.SoftwareDeployment.SIGNAL_TRANSPORT], - sd.SoftwareDeployment.NAME: _sd_ps[ - sd.SoftwareDeployment.NAME], + CONFIG: _sd_ps[CONFIG], + SERVER: _sd_ps[SERVER], + INPUT_VALUES: _sd_ps[INPUT_VALUES], + DEPLOY_ACTIONS: _sd_ps[DEPLOY_ACTIONS], + SIGNAL_TRANSPORT: _sd_ps[SIGNAL_TRANSPORT], + NAME: _sd_ps[NAME], INPUT_KEY: properties.Schema( properties.Schema.STRING, _('Name of key to use for substituting inputs during deployment'), - default=DEFAULT_INPUT_KEY, + default='get_input', ) } def _build_derived_config(self, action, source, derived_inputs, derived_options): cfg = source.get(sc.SoftwareConfig.CONFIG) - input_key = self.properties.get(INPUT_KEY) + input_key = self.properties.get(self.INPUT_KEY) inputs = dict((i['name'], i['value']) for i in derived_inputs) @@ -123,8 +137,56 @@ class StructuredDeployment(sd.SoftwareDeployment): return snippet +class StructuredDeployments(sd.SoftwareDeployments): + + PROPERTIES = ( + SERVERS, + CONFIG, + INPUT_VALUES, + DEPLOY_ACTIONS, + NAME, + SIGNAL_TRANSPORT, + INPUT_KEY, + ) = ( + sd.SoftwareDeployments.SERVERS, + sd.SoftwareDeployments.CONFIG, + sd.SoftwareDeployments.INPUT_VALUES, + sd.SoftwareDeployments.DEPLOY_ACTIONS, + sd.SoftwareDeployments.NAME, + sd.SoftwareDeployments.SIGNAL_TRANSPORT, + StructuredDeployment.INPUT_KEY + ) + + _sds_ps = sd.SoftwareDeployments.properties_schema + + properties_schema = { + SERVERS: _sds_ps[SERVERS], + CONFIG: _sds_ps[CONFIG], + INPUT_VALUES: _sds_ps[INPUT_VALUES], + DEPLOY_ACTIONS: _sds_ps[DEPLOY_ACTIONS], + SIGNAL_TRANSPORT: _sds_ps[SIGNAL_TRANSPORT], + NAME: _sds_ps[NAME], + INPUT_KEY: StructuredDeployment.properties_schema[INPUT_KEY], + } + + def _build_resource_definition(self, include_all=False): + p = self.properties + return { + self.RESOURCE_DEF_TYPE: 'OS::Heat::StructuredDeployment', + self.RESOURCE_DEF_PROPERTIES: { + self.CONFIG: p[self.CONFIG], + self.INPUT_VALUES: p[self.INPUT_VALUES], + self.DEPLOY_ACTIONS: p[self.DEPLOY_ACTIONS], + self.SIGNAL_TRANSPORT: p[self.SIGNAL_TRANSPORT], + self.NAME: p[self.NAME], + self.INPUT_KEY: p[self.INPUT_KEY], + } + } + + def resource_mapping(): return { 'OS::Heat::StructuredConfig': StructuredConfig, 'OS::Heat::StructuredDeployment': StructuredDeployment, + 'OS::Heat::StructuredDeployments': StructuredDeployments, } diff --git a/heat/tests/test_structured_config.py b/heat/tests/test_structured_config.py index f61cbe2a9..06af1ad4e 100644 --- a/heat/tests/test_structured_config.py +++ b/heat/tests/test_structured_config.py @@ -50,11 +50,13 @@ class StructuredConfigTestJSON(HeatTestCase): def test_resource_mapping(self): mapping = sc.resource_mapping() - self.assertEqual(2, len(mapping)) + self.assertEqual(3, len(mapping)) self.assertEqual(sc.StructuredConfig, mapping['OS::Heat::StructuredConfig']) self.assertEqual(sc.StructuredDeployment, mapping['OS::Heat::StructuredDeployment']) + self.assertEqual(sc.StructuredDeployments, + mapping['OS::Heat::StructuredDeployments']) self.assertIsInstance(self.config, sc.StructuredConfig) def test_handle_create(self): @@ -193,3 +195,99 @@ class StructuredDeploymentParseTest(HeatTestCase): self.assertEqual( self.result, parse(self.inputs, self.input_key, self.config)) + + +class StructuredDeploymentsTest(HeatTestCase): + + template = { + 'heat_template_version': '2013-05-23', + 'resources': { + 'deploy_mysql': { + 'type': 'OS::Heat::StructuredDeployments', + 'properties': { + 'config': 'config_uuid', + 'servers': {'server1': 'uuid1', 'server2': 'uuid2'}, + } + } + } + } + + def setUp(self): + HeatTestCase.setUp(self) + heat = mock.MagicMock() + self.deployments = heat.return_value.software_deployments + + def test_build_resource_definition(self): + stack = utils.parse_stack(self.template) + snip = stack.t.resource_definitions(stack)['deploy_mysql'] + resg = sc.StructuredDeployments('test', snip, stack) + expect = { + 'type': 'OS::Heat::StructuredDeployment', + 'properties': { + 'actions': ['CREATE', 'UPDATE'], + 'config': 'config_uuid', + 'input_key': 'get_input', + 'input_values': None, + 'name': None, + 'signal_transport': 'CFN_SIGNAL' + } + } + self.assertEqual( + expect, resg._build_resource_definition()) + self.assertEqual( + expect, resg._build_resource_definition(include_all=True)) + + def test_resource_names(self): + stack = utils.parse_stack(self.template) + snip = stack.t.resource_definitions(stack)['deploy_mysql'] + resg = sc.StructuredDeployments('test', snip, stack) + self.assertEqual( + set(('server1', 'server2')), + set(resg._resource_names()) + ) + + self.assertEqual( + set(('s1', 's2', 's3')), + set(resg._resource_names({ + 'servers': {'s1': 'u1', 's2': 'u2', 's3': 'u3'}})) + ) + + def test_assemble_nested(self): + """ + Tests that the nested stack that implements the group is created + appropriately based on properties. + """ + stack = utils.parse_stack(self.template) + snip = stack.t.resource_definitions(stack)['deploy_mysql'] + resg = sc.StructuredDeployments('test', snip, stack) + templ = { + "heat_template_version": "2013-05-23", + "resources": { + "server1": { + 'type': 'OS::Heat::StructuredDeployment', + 'properties': { + 'server': 'uuid1', + 'actions': ['CREATE', 'UPDATE'], + 'config': 'config_uuid', + 'input_key': 'get_input', + 'input_values': None, + 'name': None, + 'signal_transport': 'CFN_SIGNAL' + } + }, + "server2": { + 'type': 'OS::Heat::StructuredDeployment', + 'properties': { + 'server': 'uuid2', + 'actions': ['CREATE', 'UPDATE'], + 'config': 'config_uuid', + 'input_key': 'get_input', + 'input_values': None, + 'name': None, + 'signal_transport': 'CFN_SIGNAL' + } + } + } + } + + self.assertEqual(templ, resg._assemble_nested(['server1', 'server2']))