diff options
Diffstat (limited to 'tosca2heat/heat-translator/translator/hot')
11 files changed, 478 insertions, 120 deletions
diff --git a/tosca2heat/heat-translator/translator/hot/syntax/hot_output.py b/tosca2heat/heat-translator/translator/hot/syntax/hot_output.py index ad77fb3..a41208a 100644 --- a/tosca2heat/heat-translator/translator/hot/syntax/hot_output.py +++ b/tosca2heat/heat-translator/translator/hot/syntax/hot_output.py @@ -21,5 +21,8 @@ class HotOutput(object): self.description = description def get_dict_output(self): - return {self.name: {'value': self.value, - 'description': self.description}} + if self.description: + return {self.name: {'value': self.value, + 'description': self.description}} + else: + return {self.name: {'value': self.value}} diff --git a/tosca2heat/heat-translator/translator/hot/syntax/hot_resource.py b/tosca2heat/heat-translator/translator/hot/syntax/hot_resource.py index d7d0100..261d8ee 100644 --- a/tosca2heat/heat-translator/translator/hot/syntax/hot_resource.py +++ b/tosca2heat/heat-translator/translator/hot/syntax/hot_resource.py @@ -1,4 +1,3 @@ -# # 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 @@ -13,6 +12,7 @@ from collections import OrderedDict import logging +import os import six from toscaparser.elements.interfaces import InterfacesDef @@ -44,7 +44,26 @@ class HotResource(object): self.properties = properties or {} # special case for HOT softwareconfig if type == 'OS::Heat::SoftwareConfig': - self.properties['group'] = 'script' + config = self.properties.get('config') + if config: + implementation_artifact = config.get('get_file') + if implementation_artifact: + filename, file_extension = os.path.splitext( + implementation_artifact) + file_extension = file_extension.lower() + # artifact_types should be read to find the exact script + # type, unfortunately artifact_types doesn't seem to be + # supported by the parser + if file_extension == '.ansible' \ + or file_extension == '.yaml' \ + or file_extension == '.yml': + self.properties['group'] = 'ansible' + if file_extension == '.pp': + self.properties['group'] = 'puppet' + + if self.properties.get('group') is None: + self.properties['group'] = 'script' + self.metadata = metadata # The difference between depends_on and depends_on_nodes is @@ -84,7 +103,7 @@ class HotResource(object): # scenarios and cannot be fixed or hard coded here operations_deploy_sequence = ['create', 'configure', 'start'] - operations = HotResource._get_all_operations(self.nodetemplate) + operations = HotResource.get_all_operations(self.nodetemplate) # create HotResource for each operation used for deployment: # create, start, configure @@ -121,14 +140,22 @@ class HotResource(object): # hosting_server is None if requirements is None hosting_on_server = (hosting_server.name if hosting_server else None) - if operation.name == reserve_current: + base_type = HotResource.get_base_type( + self.nodetemplate.type_definition).type + # handle interfaces directly defined on a compute + if hosting_on_server is None \ + and base_type == 'tosca.nodes.Compute': + hosting_on_server = self.name + + if operation.name == reserve_current and \ + base_type != 'tosca.nodes.Compute': deploy_resource = self self.name = deploy_name self.type = 'OS::Heat::SoftwareDeployment' self.properties = {'config': {'get_resource': config_name}, 'server': {'get_resource': hosting_on_server}} - deploy_lookup[operation.name] = self + deploy_lookup[operation] = self else: sd_config = {'config': {'get_resource': config_name}, 'server': {'get_resource': @@ -139,7 +166,7 @@ class HotResource(object): 'OS::Heat::SoftwareDeployment', sd_config) hot_resources.append(deploy_resource) - deploy_lookup[operation.name] = deploy_resource + deploy_lookup[operation] = deploy_resource lifecycle_inputs = self._get_lifecycle_inputs(operation) if lifecycle_inputs: deploy_resource.properties['input_values'] = \ @@ -149,24 +176,34 @@ class HotResource(object): # in operations_deploy_sequence # TODO(anyone): find some better way to encode this implicit sequence group = {} + op_index_max = -1 for op, hot in deploy_lookup.items(): # position to determine potential preceding nodes - op_index = operations_deploy_sequence.index(op) - for preceding_op in \ + op_index = operations_deploy_sequence.index(op.name) + if op_index > op_index_max: + op_index_max = op_index + for preceding_op_name in \ reversed(operations_deploy_sequence[:op_index]): - preceding_hot = deploy_lookup.get(preceding_op) + preceding_hot = deploy_lookup.get( + operations.get(preceding_op_name)) if preceding_hot: hot.depends_on.append(preceding_hot) hot.depends_on_nodes.append(preceding_hot) group[preceding_hot] = hot break + if op_index_max >= 0: + last_deploy = deploy_lookup.get(operations.get( + operations_deploy_sequence[op_index_max])) + else: + last_deploy = None + # save this dependency chain in the set of HOT resources self.group_dependencies.update(group) for hot in hot_resources: hot.group_dependencies.update(group) - return hot_resources + return hot_resources, deploy_lookup, last_deploy def handle_connectsto(self, tosca_source, tosca_target, hot_source, hot_target, config_location, operation): @@ -313,14 +350,16 @@ class HotResource(object): return tosca_props @staticmethod - def _get_all_operations(node): + def get_all_operations(node): operations = {} for operation in node.interfaces: operations[operation.name] = operation node_type = node.type_definition if isinstance(node_type, str) or \ - node_type.type == "tosca.policies.Placement": + node_type.type == "tosca.policies.Placement" or \ + node_type.type == "tosca.policies.Colocate" or \ + node_type.type == "tosca.policies.Antilocate": return operations while True: @@ -338,7 +377,9 @@ class HotResource(object): def _get_interface_operations_from_type(node_type, node, lifecycle_name): operations = {} if isinstance(node_type, str) or \ - node_type.type == "tosca.policies.Placement": + node_type.type == "tosca.policies.Placement" or \ + node_type.type == "tosca.policies.Colocate" or \ + node_type.type == "tosca.policies.Antilocate": return operations if node_type.interfaces and lifecycle_name in node_type.interfaces: for name, elems in node_type.interfaces[lifecycle_name].items(): @@ -354,7 +395,9 @@ class HotResource(object): @staticmethod def get_base_type(node_type): if node_type.parent_type is not None: - if node_type.parent_type.type.endswith('.Root'): + if node_type.parent_type.type.endswith('.Root') or \ + node_type.type == "tosca.policies.Colocate" or \ + node_type.type == "tosca.policies.Antilocate": return node_type else: return HotResource.get_base_type(node_type.parent_type) diff --git a/tosca2heat/heat-translator/translator/hot/syntax/hot_template.py b/tosca2heat/heat-translator/translator/hot/syntax/hot_template.py index 4263c4d..0403562 100644 --- a/tosca2heat/heat-translator/translator/hot/syntax/hot_template.py +++ b/tosca2heat/heat-translator/translator/hot/syntax/hot_template.py @@ -77,6 +77,7 @@ class HotTemplate(object): dict_output.update({self.OUTPUTS: all_outputs}) yaml.add_representer(OrderedDict, self.represent_ordereddict) + yaml.add_representer(dict, self.represent_ordereddict) yaml_string = yaml.dump(dict_output, default_flow_style=False) # get rid of the '' from yaml.dump around numbers yaml_string = yaml_string.replace('\'', '') diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_compute.py b/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_compute.py index e0cdbb6..d42cdc8 100644 --- a/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_compute.py +++ b/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_compute.py @@ -16,7 +16,6 @@ from mock import patch from toscaparser.nodetemplate import NodeTemplate from toscaparser.tests.base import TestCase -from toscaparser.utils.gettextutils import _ import toscaparser.utils.yamlparser from translator.hot.tosca.tosca_compute import ToscaCompute @@ -27,22 +26,12 @@ class ToscaComputeTest(TestCase): nodetemplates = (toscaparser.utils.yamlparser. simple_parse(tpl_snippet)['node_templates']) name = list(nodetemplates.keys())[0] - try: - nodetemplate = NodeTemplate(name, nodetemplates) - nodetemplate.validate() - toscacompute = ToscaCompute(nodetemplate) - toscacompute.handle_properties() - if not self._compare_properties(toscacompute.properties, - expectedprops): - raise Exception(_("Hot Properties are not" - " same as expected properties")) - except Exception: - # for time being rethrowing. Will be handled future based - # on new development in Glance and Graffiti - raise + nodetemplate = NodeTemplate(name, nodetemplates) + nodetemplate.validate() + toscacompute = ToscaCompute(nodetemplate) + toscacompute.handle_properties() - def _compare_properties(self, hotprops, expectedprops): - return all(item in hotprops.items() for item in expectedprops.items()) + self.assertDictEqual(expectedprops, toscacompute.properties) def test_node_compute_with_host_and_os_capabilities(self): tpl_snippet = ''' @@ -63,7 +52,8 @@ class ToscaComputeTest(TestCase): version: 18.0 ''' expectedprops = {'flavor': 'm1.large', - 'image': 'fedora-amd64-heat-config'} + 'image': 'fedora-amd64-heat-config', + 'user_data_format': 'SOFTWARE_CONFIG'} self._tosca_compute_test( tpl_snippet, expectedprops) @@ -82,7 +72,8 @@ class ToscaComputeTest(TestCase): #left intentionally ''' expectedprops = {'flavor': 'm1.large', - 'image': None} + 'image': None, + 'user_data_format': 'SOFTWARE_CONFIG'} self._tosca_compute_test( tpl_snippet, expectedprops) @@ -101,7 +92,8 @@ class ToscaComputeTest(TestCase): version: 18.0 ''' expectedprops = {'flavor': None, - 'image': 'fedora-amd64-heat-config'} + 'image': 'fedora-amd64-heat-config', + 'user_data_format': 'SOFTWARE_CONFIG'} self._tosca_compute_test( tpl_snippet, expectedprops) @@ -117,7 +109,8 @@ class ToscaComputeTest(TestCase): #left intentionally ''' expectedprops = {'flavor': None, - 'image': None} + 'image': None, + 'user_data_format': 'SOFTWARE_CONFIG'} self._tosca_compute_test( tpl_snippet, expectedprops) @@ -129,7 +122,8 @@ class ToscaComputeTest(TestCase): type: tosca.nodes.Compute ''' expectedprops = {'flavor': None, - 'image': None} + 'image': None, + 'user_data_format': 'SOFTWARE_CONFIG'} self._tosca_compute_test( tpl_snippet, expectedprops) @@ -144,7 +138,9 @@ class ToscaComputeTest(TestCase): properties: #left intentionally ''' - expectedprops = {'flavor': 'm1.nano'} + expectedprops = {'flavor': None, + 'image': None, + 'user_data_format': 'SOFTWARE_CONFIG'} self._tosca_compute_test( tpl_snippet, expectedprops) @@ -160,7 +156,9 @@ class ToscaComputeTest(TestCase): num_cpus: 4 mem_size: 4 GB ''' - expectedprops = {'flavor': 'm1.large'} + expectedprops = {'flavor': 'm1.large', + 'image': None, + 'user_data_format': 'SOFTWARE_CONFIG'} self._tosca_compute_test( tpl_snippet, expectedprops) @@ -176,7 +174,9 @@ class ToscaComputeTest(TestCase): num_cpus: 4 disk_size: 10 GB ''' - expectedprops = {'flavor': 'm1.large'} + expectedprops = {'flavor': 'm1.large', + 'image': None, + 'user_data_format': 'SOFTWARE_CONFIG'} self._tosca_compute_test( tpl_snippet, expectedprops) @@ -191,7 +191,9 @@ class ToscaComputeTest(TestCase): properties: num_cpus: 4 ''' - expectedprops = {'flavor': 'm1.large'} + expectedprops = {'flavor': 'm1.large', + 'image': None, + 'user_data_format': 'SOFTWARE_CONFIG'} self._tosca_compute_test( tpl_snippet, expectedprops) @@ -249,7 +251,9 @@ class ToscaComputeTest(TestCase): json.dumps(mock_flavor_content) mock_post.return_value = mock_ks_response mock_get.return_value = mock_nova_response - expectedprops = {'flavor': 'm1.mock_flavor'} + expectedprops = {'flavor': 'm1.mock_flavor', + 'image': None, + 'user_data_format': 'SOFTWARE_CONFIG'} self._tosca_compute_test( tpl_snippet, expectedprops) @@ -279,8 +283,8 @@ class ToscaComputeTest(TestCase): mock_ks_content = {} mock_ks_response.content = json.dumps(mock_ks_content) expectedprops = {'flavor': 'm1.small', - 'user_data_format': 'SOFTWARE_CONFIG', - 'image': None} + 'image': None, + 'user_data_format': 'SOFTWARE_CONFIG'} self._tosca_compute_test( tpl_snippet, expectedprops) diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage.py index d4b2f44..924ff9d 100644 --- a/tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage.py +++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage.py @@ -67,5 +67,5 @@ class ToscaBlockStorage(HotResource): # attribute for the matching resource. Unless there is additional # runtime support, this should be a one to one mapping. if attribute == 'volume_id': - attr['get_resource'] = args[0] + attr['get_resource'] = self.name return attr diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage_attachment.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage_attachment.py index 715d5b3..2242c2d 100644 --- a/tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage_attachment.py +++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage_attachment.py @@ -44,5 +44,10 @@ class ToscaBlockStorageAttachment(HotResource): if 'location' in self.properties: self.properties['mountpoint'] = self.properties.pop('location') + # TOSCA type can have a device name specified, + # this is unsupported by Heat + if 'device' in self.properties: + self.properties.pop('device') + def handle_life_cycle(self): - pass + return None, None, None diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_compute.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_compute.py index e2ac130..b8ad83c 100644 --- a/tosca2heat/heat-translator/translator/hot/tosca/tosca_compute.py +++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_compute.py @@ -84,6 +84,14 @@ class ToscaCompute(HotResource): ('architecture', 'distribution', 'type', 'version') toscatype = 'tosca.nodes.Compute' + ALLOWED_NOVA_SERVER_PROPS = \ + ('admin_pass', 'availability_zone', 'block_device_mapping', + 'block_device_mapping_v2', 'config_drive', 'diskConfig', 'flavor', + 'flavor_update_policy', 'image', 'image_update_policy', 'key_name', + 'metadata', 'name', 'networks', 'personality', 'reservation_id', + 'scheduler_hints', 'security_groups', 'software_config_transport', + 'user_data', 'user_data_format', 'user_data_update_policy') + def __init__(self, nodetemplate): super(ToscaCompute, self).__init__(nodetemplate, type='OS::Nova::Server') @@ -98,7 +106,8 @@ class ToscaCompute(HotResource): self.properties['user_data_format'] = 'SOFTWARE_CONFIG' tosca_props = self.get_tosca_props() for key, value in tosca_props.items(): - self.properties[key] = value + if key in self.ALLOWED_NOVA_SERVER_PROPS: + self.properties[key] = value # To be reorganized later based on new development in Glance and Graffiti def translate_compute_flavor_and_image(self, @@ -112,11 +121,17 @@ class ToscaCompute(HotResource): if host_capability: for prop in host_capability.get_properties_objects(): host_cap_props[prop.name] = prop.value - flavor = self._best_flavor(host_cap_props) + # if HOST properties are not specified, we should not attempt to + # find best match of flavor + if host_cap_props: + flavor = self._best_flavor(host_cap_props) if os_capability: for prop in os_capability.get_properties_objects(): os_cap_props[prop.name] = prop.value - image = self._best_image(os_cap_props) + # if OS properties are not specified, we should not attempt to + # find best match of image + if os_cap_props: + image = self._best_image(os_cap_props) hot_properties['flavor'] = flavor hot_properties['image'] = image return hot_properties @@ -153,6 +168,48 @@ class ToscaCompute(HotResource): return None return flavor_dict + def _populate_image_dict(self): + '''Populates and returns the images dict using Glance ReST API''' + images_dict = {} + try: + access_dict = translator.common.utils.get_ks_access_dict() + access_token = translator.common.utils.get_token_id(access_dict) + if access_token is None: + return None + glance_url = translator.common.utils.get_url_for(access_dict, + 'image') + if not glance_url: + return None + glance_response = requests.get(glance_url + '/v2/images', + headers={'X-Auth-Token': + access_token}) + if glance_response.status_code != 200: + return None + images = json.loads(glance_response.content)["images"] + for image in images: + image_resp = requests.get(glance_url + '/v2/images/' + + image["id"], + headers={'X-Auth-Token': + access_token}) + if image_resp.status_code != 200: + continue + metadata = ["architecture", "type", "distribution", "version"] + image_data = json.loads(image_resp.content) + if any(key in image_data.keys() for key in metadata): + images_dict[image_data["name"]] = dict() + for key in metadata: + if key in image_data.keys(): + images_dict[image_data["name"]][key] = \ + image_data[key] + else: + continue + + except Exception as e: + # Handles any exception coming from openstack + log.warn(_('Choosing predefined flavors since received ' + 'Openstack Exception: %s') % str(e)) + return images_dict + def _best_flavor(self, properties): log.info(_('Choosing the best flavor for given attributes.')) # Check whether user exported all required environment variables. @@ -202,26 +259,32 @@ class ToscaCompute(HotResource): return None def _best_image(self, properties): - match_all = IMAGES.keys() + # Check whether user exported all required environment variables. + images = IMAGES + if translator.common.utils.check_for_env_variables(): + resp = self._populate_image_dict() + if len(resp.keys()) > 0: + images = resp + match_all = images.keys() architecture = properties.get(self.ARCHITECTURE) if architecture is None: self._log_compute_msg(self.ARCHITECTURE, 'image') - match_arch = self._match_images(match_all, IMAGES, + match_arch = self._match_images(match_all, images, self.ARCHITECTURE, architecture) type = properties.get(self.TYPE) if type is None: self._log_compute_msg(self.TYPE, 'image') - match_type = self._match_images(match_arch, IMAGES, self.TYPE, type) + match_type = self._match_images(match_arch, images, self.TYPE, type) distribution = properties.get(self.DISTRIBUTION) if distribution is None: self._log_compute_msg(self.DISTRIBUTION, 'image') - match_distribution = self._match_images(match_type, IMAGES, + match_distribution = self._match_images(match_type, images, self.DISTRIBUTION, distribution) version = properties.get(self.VERSION) if version is None: self._log_compute_msg(self.VERSION, 'image') - match_version = self._match_images(match_distribution, IMAGES, + match_version = self._match_images(match_distribution, images, self.VERSION, version) if len(match_version): diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_policies_antilocate.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_policies_antilocate.py new file mode 100644 index 0000000..37bfed6 --- /dev/null +++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_policies_antilocate.py @@ -0,0 +1,35 @@ +# 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 translator.hot.syntax.hot_resource import HotResource + +# Name used to dynamically load appropriate map class. +TARGET_CLASS_NAME = 'ToscaPoliciesAntilocate' + + +class ToscaPoliciesAntilocate(HotResource): + '''Translate TOSCA policy type tosca.poicies.Placement.Antilocate''' + + toscatype = 'tosca.policies.Placement.Antilocate' + + def __init__(self, policy): + super(ToscaPoliciesAntilocate, self).__init__( + policy, type='OS::Nova::ServerGroup') + self.policy = policy + + def handle_properties(self, resources): + self.properties["name"] = self.name + self.properties["policies"] = ["anti-affinity"] + for resource in resources: + if resource.name in self.policy.targets: + resource.properties["scheduler_hints"] = { + "group": {"get_resource": self.name}} diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_policies_colocate.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_policies_colocate.py new file mode 100644 index 0000000..778f97e --- /dev/null +++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_policies_colocate.py @@ -0,0 +1,35 @@ +# 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 translator.hot.syntax.hot_resource import HotResource + +# Name used to dynamically load appropriate map class. +TARGET_CLASS_NAME = 'ToscaPoliciesColocate' + + +class ToscaPoliciesColocate(HotResource): + '''Translate TOSCA policy type tosca.poicies.Placement.Colocate''' + + toscatype = 'tosca.policies.Placement.Colocate' + + def __init__(self, policy): + super(ToscaPoliciesColocate, self).__init__( + policy, type='OS::Nova::ServerGroup') + self.policy = policy + + def handle_properties(self, resources): + self.properties["name"] = self.name + self.properties["policies"] = ["affinity"] + for resource in resources: + if resource.name in self.policy.targets: + resource.properties["scheduler_hints"] = { + "group": {"get_resource": self.name}} diff --git a/tosca2heat/heat-translator/translator/hot/translate_node_templates.py b/tosca2heat/heat-translator/translator/hot/translate_node_templates.py index 46cdd71..d8e7e48 100644 --- a/tosca2heat/heat-translator/translator/hot/translate_node_templates.py +++ b/tosca2heat/heat-translator/translator/hot/translate_node_templates.py @@ -16,6 +16,8 @@ import logging import os import six +from collections import OrderedDict +from toscaparser.functions import Concat from toscaparser.functions import GetAttribute from toscaparser.functions import GetInput from toscaparser.functions import GetProperty @@ -25,6 +27,7 @@ from toscaparser.utils.gettextutils import _ from translator.common.exception import ToscaClassAttributeError from translator.common.exception import ToscaClassImportError from translator.common.exception import ToscaModImportError +from translator.common import utils from translator.conf.config import ConfigProvider as translatorConfig from translator.hot.syntax.hot_resource import HotResource from translator.hot.tosca.tosca_block_storage_attachment import ( @@ -132,6 +135,8 @@ log = logging.getLogger('heat-translator') TOSCA_TO_HOT_TYPE = _generate_type_map() +BASE_TYPES = six.string_types + six.integer_types + (dict, OrderedDict) + class TranslateNodeTemplates(object): '''Translate TOSCA NodeTemplates to Heat Resources.''' @@ -146,6 +151,9 @@ class TranslateNodeTemplates(object): log.debug(_('Mapping between TOSCA nodetemplate and HOT resource.')) self.hot_lookup = {} self.policies = self.tosca.topology_template.policies + # stores the last deploy of generated behavior for a resource + # useful to satisfy underlying dependencies between interfaces + self.last_deploy_map = {} def translate(self): return self._translate_nodetemplates() @@ -219,9 +227,14 @@ class TranslateNodeTemplates(object): # into multiple HOT resources and may change their name lifecycle_resources = [] for resource in self.hot_resources: - expanded = resource.handle_life_cycle() - if expanded: - lifecycle_resources += expanded + expanded_resources, deploy_lookup, last_deploy = resource.\ + handle_life_cycle() + if expanded_resources: + lifecycle_resources += expanded_resources + if deploy_lookup: + self.hot_lookup.update(deploy_lookup) + if last_deploy: + self.last_deploy_map[resource] = last_deploy self.hot_resources += lifecycle_resources # Handle configuration from ConnectsTo relationship in the TOSCA node: @@ -253,7 +266,9 @@ class TranslateNodeTemplates(object): # if the source of dependency is a server and the # relationship type is 'tosca.relationships.HostedOn', # add dependency as properties.server - if node_depend.type == 'tosca.nodes.Compute' and \ + base_type = HotResource.get_base_type( + node_depend.type_definition) + if base_type.type == 'tosca.nodes.Compute' and \ node.related[node_depend].type == \ node.type_definition.HOSTEDON: self.hot_lookup[node].properties['server'] = \ @@ -266,6 +281,13 @@ class TranslateNodeTemplates(object): self.hot_lookup[node].depends_on_nodes.append( self.hot_lookup[node_depend].top_of_chain()) + last_deploy = self.last_deploy_map.get( + self.hot_lookup[node_depend]) + if last_deploy and \ + last_deploy not in self.hot_lookup[node].depends_on: + self.hot_lookup[node].depends_on.append(last_deploy) + self.hot_lookup[node].depends_on_nodes.append(last_deploy) + # handle hosting relationship for resource in self.hot_resources: resource.handle_hosting() @@ -295,53 +317,202 @@ class TranslateNodeTemplates(object): inputs = resource.properties.get('input_values') if inputs: for name, value in six.iteritems(inputs): - inputs[name] = self._translate_input(value, resource) + inputs[name] = self.translate_param_value(value, resource) + + # remove resources without type defined + # for example a SoftwareComponent without interfaces + # would fall in this case + to_remove = [] + for resource in self.hot_resources: + if resource.type is None: + to_remove.append(resource) + + for resource in to_remove: + self.hot_resources.remove(resource) return self.hot_resources - def _translate_input(self, input_value, resource): + def translate_param_value(self, param_value, resource): + tosca_template = None + if resource: + tosca_template = resource.nodetemplate + get_property_args = None - if isinstance(input_value, GetProperty): - get_property_args = input_value.args + if isinstance(param_value, GetProperty): + get_property_args = param_value.args # to remove when the parser is fixed to return GetProperty - if isinstance(input_value, dict) and 'get_property' in input_value: - get_property_args = input_value['get_property'] + elif isinstance(param_value, dict) and 'get_property' in param_value: + get_property_args = param_value['get_property'] if get_property_args is not None: - hot_target = self._find_hot_resource_for_tosca( - get_property_args[0], resource) - if hot_target: - props = hot_target.get_tosca_props() - prop_name = get_property_args[1] - if prop_name in props: - return props[prop_name] - elif isinstance(input_value, GetAttribute): + tosca_target, prop_name, prop_arg = \ + self.decipher_get_operation(get_property_args, + tosca_template) + if tosca_target: + prop_value = tosca_target.get_property_value(prop_name) + if prop_value: + prop_value = self.translate_param_value( + prop_value, resource) + return self._unfold_value(prop_value, prop_arg) + get_attr_args = None + if isinstance(param_value, GetAttribute): + get_attr_args = param_value.result().args + # to remove when the parser is fixed to return GetAttribute + elif isinstance(param_value, dict) and 'get_attribute' in param_value: + get_attr_args = param_value['get_attribute'] + if get_attr_args is not None: # for the attribute # get the proper target type to perform the translation - args = input_value.result() - hot_target = self._find_hot_resource_for_tosca(args[0], resource) - - return hot_target.get_hot_attribute(args[1], args) - # most of artifacts logic should move to the parser - elif isinstance(input_value, dict) and 'get_artifact' in input_value: - get_artifact_args = input_value['get_artifact'] - - hot_target = self._find_hot_resource_for_tosca( - get_artifact_args[0], resource) - artifacts = TranslateNodeTemplates.get_all_artifacts( - hot_target.nodetemplate) - - if get_artifact_args[1] in artifacts: - artifact = artifacts[get_artifact_args[1]] - if artifact.get('type', None) == 'tosca.artifacts.File': - return {'get_file': artifact.get('file')} - elif isinstance(input_value, GetInput): - if isinstance(input_value.args, list) \ - and len(input_value.args) == 1: - return {'get_param': input_value.args[0]} + tosca_target, attr_name, attr_arg = \ + self.decipher_get_operation(get_attr_args, tosca_template) + attr_args = [] + if attr_arg: + attr_args += attr_arg + if tosca_target: + if tosca_target in self.hot_lookup: + attr_value = self.hot_lookup[tosca_target].\ + get_hot_attribute(attr_name, attr_args) + attr_value = self.translate_param_value( + attr_value, resource) + return self._unfold_value(attr_value, attr_arg) + elif isinstance(param_value, dict) and 'get_artifact' in param_value: + get_artifact_args = param_value['get_artifact'] + tosca_target, artifact_name, _ = \ + self.decipher_get_operation(get_artifact_args, + tosca_template) + + if tosca_target: + artifacts = self.get_all_artifacts(tosca_target) + if artifact_name in artifacts: + artifact = artifacts[artifact_name] + if artifact.get('type', None) == 'tosca.artifacts.File': + return {'get_file': artifact.get('file')} + get_input_args = None + if isinstance(param_value, GetInput): + get_input_args = param_value.args + elif isinstance(param_value, dict) and 'get_input' in param_value: + get_input_args = param_value['get_input'] + if get_input_args is not None: + if isinstance(get_input_args, list) \ + and len(get_input_args) == 1: + return {'get_param': self.translate_param_value( + get_input_args[0], resource)} + else: + return {'get_param': self.translate_param_value( + get_input_args, resource)} + elif isinstance(param_value, dict) \ + and 'get_operation_output' in param_value: + res = self._translate_get_operation_output_function( + param_value['get_operation_output'], tosca_template) + if res: + return res + concat_list = None + if isinstance(param_value, Concat): + concat_list = param_value.args + elif isinstance(param_value, dict) and 'concat' in param_value: + concat_list = param_value['concat'] + if concat_list is not None: + res = self._translate_concat_function(concat_list, resource) + if res: + return res + + if isinstance(param_value, list): + translated_list = [] + for elem in param_value: + translated_elem = self.translate_param_value(elem, resource) + if translated_elem: + translated_list.append(translated_elem) + return translated_list + + if isinstance(param_value, BASE_TYPES): + return param_value + + return None + + def _translate_concat_function(self, concat_list, resource): + str_replace_template = '' + str_replace_params = {} + index = 0 + for elem in concat_list: + str_replace_template += '$s' + str(index) + str_replace_params['$s' + str(index)] = \ + self.translate_param_value(elem, resource) + index += 1 + + return {'str_replace': { + 'template': str_replace_template, + 'params': str_replace_params + }} + + def _translate_get_operation_output_function(self, args, tosca_template): + tosca_target = self._find_tosca_node(args[0], + tosca_template) + if tosca_target and len(args) >= 4: + operations = HotResource.get_all_operations(tosca_target) + # ignore Standard interface name, + # it is the only one supported in the translator anyway + op_name = args[2] + output_name = args[3] + if op_name in operations: + operation = operations[op_name] + if operation in self.hot_lookup: + matching_deploy = self.hot_lookup[operation] + matching_config_name = matching_deploy.\ + properties['config']['get_resource'] + matching_config = self.find_hot_resource( + matching_config_name) + if matching_config: + outputs = matching_config.properties.get('outputs') + if outputs is None: + outputs = [] + outputs.append({'name': output_name}) + matching_config.properties['outputs'] = outputs + return {'get_attr': [ + matching_deploy.name, + output_name + ]} + + @staticmethod + def _unfold_value(value, value_arg): + if value_arg is not None: + if isinstance(value, dict): + val = value.get(value_arg) + if val is not None: + return val + + index = utils.str_to_num(value_arg) + if isinstance(value, list) and index is not None: + return value[index] + return value + + def decipher_get_operation(self, args, current_tosca_node): + tosca_target = self._find_tosca_node(args[0], + current_tosca_node) + new_target = None + if tosca_target and len(args) > 2: + cap_or_req_name = args[1] + cap = tosca_target.get_capability(cap_or_req_name) + if cap: + new_target = cap else: - return {'get_param': input_value.args} + for req in tosca_target.requirements: + if cap_or_req_name in req: + new_target = self._find_tosca_node( + req[cap_or_req_name]) + cap = new_target.get_capability(cap_or_req_name) + if cap: + new_target = cap + break + + if new_target: + tosca_target = new_target + + prop_name = args[2] + prop_arg = args[3] if len(args) >= 4 else None + else: + prop_name = args[1] + prop_arg = args[2] if len(args) >= 3 else None - return input_value + return tosca_target, prop_name, prop_arg @staticmethod def get_all_artifacts(nodetemplate): @@ -410,23 +581,29 @@ class TranslateNodeTemplates(object): if resource.name == name: return resource - def _find_tosca_node(self, tosca_name): - for node in self.nodetemplates: - if node.name == tosca_name: - return node - - def _find_hot_resource_for_tosca(self, tosca_name, - current_hot_resource=None): + def _find_tosca_node(self, tosca_name, current_tosca_template=None): + tosca_node = None if tosca_name == 'SELF': - return current_hot_resource - if tosca_name == 'HOST' and current_hot_resource is not None: - for req in current_hot_resource.nodetemplate.requirements: + tosca_node = current_tosca_template + if tosca_name == 'HOST' and current_tosca_template: + for req in current_tosca_template.requirements: if 'host' in req: - return self._find_hot_resource_for_tosca(req['host']) + tosca_node = self._find_tosca_node(req['host']) - for node in self.nodetemplates: - if node.name == tosca_name: - return self.hot_lookup[node] + if tosca_node is None: + for node in self.nodetemplates: + if node.name == tosca_name: + tosca_node = node + break + return tosca_node + + def _find_hot_resource_for_tosca(self, tosca_name, + current_hot_resource=None): + current_tosca_resource = current_hot_resource.nodetemplate \ + if current_hot_resource else None + tosca_node = self._find_tosca_node(tosca_name, current_tosca_resource) + if tosca_node: + return self.hot_lookup[tosca_node] return None diff --git a/tosca2heat/heat-translator/translator/hot/translate_outputs.py b/tosca2heat/heat-translator/translator/hot/translate_outputs.py index 4197cdd..87ec02a 100644 --- a/tosca2heat/heat-translator/translator/hot/translate_outputs.py +++ b/tosca2heat/heat-translator/translator/hot/translate_outputs.py @@ -33,16 +33,8 @@ class TranslateOutputs(object): def _translate_outputs(self): hot_outputs = [] for output in self.outputs: - if output.value.name == 'get_attribute': - get_parameters = output.value.args - hot_target = self.nodes.find_hot_resource(get_parameters[0]) - hot_value = hot_target.get_hot_attribute(get_parameters[1], - get_parameters) - hot_outputs.append(HotOutput(output.name, - hot_value, - output.description)) - else: - hot_outputs.append(HotOutput(output.name, - output.value, + hot_value = self.nodes.translate_param_value(output.value, None) + if hot_value is not None: + hot_outputs.append(HotOutput(output.name, hot_value, output.description)) return hot_outputs |