From f7b240c6893a48d71da29974e54cb560b6575eb3 Mon Sep 17 00:00:00 2001 From: shangxdy Date: Sun, 26 Feb 2017 16:22:30 +0800 Subject: Sync heat-translator code Sync heat-translator code from upstream JIRA:PARSER-119 Change-Id: I2287b78ad38bc54f7740fd1ee5f08989d5e680bf Signed-off-by: shangxdy --- .../translator/hot/syntax/hot_output.py | 7 +- .../translator/hot/syntax/hot_resource.py | 272 +++++++++++--- .../translator/hot/syntax/hot_template.py | 35 +- .../translator/hot/tests/test_translate_outputs.py | 4 +- .../hot/tosca/tests/test_tosca_autoscaling.py | 91 +++++ .../hot/tosca/tests/test_tosca_compute.py | 134 +++---- .../translator/hot/tosca/tosca_block_storage.py | 7 +- .../hot/tosca/tosca_block_storage_attachment.py | 8 +- .../translator/hot/tosca/tosca_compute.py | 167 ++------- .../translator/hot/tosca/tosca_database.py | 4 +- .../translator/hot/tosca/tosca_dbms.py | 4 +- .../translator/hot/tosca/tosca_network_network.py | 7 +- .../translator/hot/tosca/tosca_network_port.py | 5 +- .../translator/hot/tosca/tosca_object_storage.py | 5 +- .../translator/hot/tosca/tosca_policies.py | 5 +- .../translator/hot/tosca/tosca_policies_scaling.py | 131 +++++++ .../hot/tosca/tosca_software_component.py | 5 +- .../translator/hot/tosca/tosca_web_application.py | 5 +- .../translator/hot/tosca/tosca_webserver.py | 5 +- .../translator/hot/tosca_translator.py | 51 ++- .../translator/hot/translate_node_templates.py | 400 +++++++++++++++++---- .../translator/hot/translate_outputs.py | 14 +- 22 files changed, 963 insertions(+), 403 deletions(-) create mode 100644 tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_autoscaling.py create mode 100644 tosca2heat/heat-translator/translator/hot/tosca/tosca_policies_scaling.py (limited to 'tosca2heat/heat-translator/translator/hot') 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 54e0d96..6499333 100644 --- a/tosca2heat/heat-translator/translator/hot/syntax/hot_resource.py +++ b/tosca2heat/heat-translator/translator/hot/syntax/hot_resource.py @@ -25,6 +25,10 @@ SECTIONS = (TYPE, PROPERTIES, MEDADATA, DEPENDS_ON, UPDATE_POLICY, DELETION_POLICY) = \ ('type', 'properties', 'metadata', 'depends_on', 'update_policy', 'deletion_policy') + +policy_type = ['tosca.policies.Placement', + 'tosca.policies.Scaling', + 'tosca.policies.Scaling.Cluster'] log = logging.getLogger('heat-translator') @@ -33,7 +37,7 @@ class HotResource(object): def __init__(self, nodetemplate, name=None, type=None, properties=None, metadata=None, depends_on=None, - update_policy=None, deletion_policy=None): + update_policy=None, deletion_policy=None, csar_dir=None): log.debug(_('Translating TOSCA node type to HOT resource type.')) self.nodetemplate = nodetemplate if name: @@ -42,11 +46,19 @@ class HotResource(object): self.name = nodetemplate.name self.type = type self.properties = properties or {} + + self.csar_dir = csar_dir # special case for HOT softwareconfig + cwd = os.getcwd() if type == 'OS::Heat::SoftwareConfig': config = self.properties.get('config') - if config: - implementation_artifact = config.get('get_file') + if isinstance(config, dict): + if self.csar_dir: + os.chdir(self.csar_dir) + implementation_artifact = os.path.abspath(config.get( + 'get_file')) + else: + implementation_artifact = config.get('get_file') if implementation_artifact: filename, file_extension = os.path.splitext( implementation_artifact) @@ -63,7 +75,7 @@ class HotResource(object): if self.properties.get('group') is None: self.properties['group'] = 'script' - + os.chdir(cwd) self.metadata = metadata # The difference between depends_on and depends_on_nodes is @@ -103,7 +115,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 @@ -126,68 +138,151 @@ class HotResource(object): hosting_server = None if self.nodetemplate.requirements is not None: hosting_server = self._get_hosting_server() + + sw_deployment_resouce = HOTSoftwareDeploymentResources(hosting_server) + server_key = sw_deployment_resouce.server_key + servers = sw_deployment_resouce.servers + sw_deploy_res = sw_deployment_resouce.software_deployment + + # hosting_server is None if requirements is None + hosting_on_server = hosting_server if hosting_server else None + base_type = HotResource.get_base_type_str( + self.nodetemplate.type_definition) + # if we are on a compute node the host is self + if hosting_on_server is None and base_type == 'tosca.nodes.Compute': + hosting_on_server = self.name + servers = {'get_resource': self.name} + + cwd = os.getcwd() for operation in operations.values(): if operation.name in operations_deploy_sequence: config_name = node_name + '_' + operation.name + '_config' deploy_name = node_name + '_' + operation.name + '_deploy' + if self.csar_dir: + os.chdir(self.csar_dir) + get_file = os.path.abspath(operation.implementation) + else: + get_file = operation.implementation hot_resources.append( HotResource(self.nodetemplate, config_name, 'OS::Heat::SoftwareConfig', {'config': - {'get_file': operation.implementation}})) - - # hosting_server is None if requirements is None - hosting_on_server = (hosting_server.name if - hosting_server else None) - if operation.name == reserve_current: + {'get_file': get_file}}, + csar_dir=self.csar_dir)) + if operation.name == reserve_current and \ + base_type != 'tosca.nodes.Compute': deploy_resource = self self.name = deploy_name - self.type = 'OS::Heat::SoftwareDeployment' + self.type = sw_deploy_res self.properties = {'config': {'get_resource': config_name}, - 'server': {'get_resource': - hosting_on_server}, + server_key: servers, 'signal_transport': 'HEAT_SIGNAL'} - deploy_lookup[operation.name] = self + deploy_lookup[operation] = self else: sd_config = {'config': {'get_resource': config_name}, - 'server': {'get_resource': - hosting_on_server}, + server_key: servers, 'signal_transport': 'HEAT_SIGNAL'} deploy_resource = \ HotResource(self.nodetemplate, deploy_name, - 'OS::Heat::SoftwareDeployment', - sd_config) + sw_deploy_res, + sd_config, csar_dir=self.csar_dir) 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'] = \ lifecycle_inputs + os.chdir(cwd) # Add dependencies for the set of HOT resources in the sequence defined # in operations_deploy_sequence # TODO(anyone): find some better way to encode this implicit sequence group = {} + op_index_min = None + 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_min is None or op_index < op_index_min: + op_index_min = op_index + 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 + roles_deploy_resource = self._handle_ansiblegalaxy_roles( + hot_resources, node_name, servers) + + # add a dependency to this ansible roles deploy to + # the first "classic" deploy generated for this node + if roles_deploy_resource and op_index_min: + first_deploy = deploy_lookup.get(operations.get( + operations_deploy_sequence[op_index_min])) + first_deploy.depends_on.append(roles_deploy_resource) + first_deploy.depends_on_nodes.append(roles_deploy_resource) + + return hot_resources, deploy_lookup, last_deploy + + def _handle_ansiblegalaxy_roles(self, hot_resources, initial_node_name, + hosting_on_server): + artifacts = self.get_all_artifacts(self.nodetemplate) + install_roles_script = '' + + sw_deployment_resouce = \ + HOTSoftwareDeploymentResources(hosting_on_server) + server_key = sw_deployment_resouce.server_key + sw_deploy_res = sw_deployment_resouce.software_deployment + for artifact_name, artifact in artifacts.items(): + artifact_type = artifact.get('type', '').lower() + if artifact_type == 'tosca.artifacts.ansiblegalaxy.role': + role = artifact.get('file', None) + if role: + install_roles_script += 'ansible-galaxy install ' + role \ + + '\n' + + if install_roles_script: + # remove trailing \n + install_roles_script = install_roles_script[:-1] + # add shebang and | to use literal scalar type (for multiline) + install_roles_script = '|\n#!/bin/bash\n' + install_roles_script + + config_name = initial_node_name + '_install_roles_config' + deploy_name = initial_node_name + '_install_roles_deploy' + hot_resources.append( + HotResource(self.nodetemplate, config_name, + 'OS::Heat::SoftwareConfig', + {'config': install_roles_script}, + csar_dir=self.csar_dir)) + sd_config = {'config': {'get_resource': config_name}, + server_key: hosting_on_server, + 'signal_transport': 'HEAT_SIGNAL'} + deploy_resource = \ + HotResource(self.nodetemplate, deploy_name, + sw_deploy_res, + sd_config, csar_dir=self.csar_dir) + hot_resources.append(deploy_resource) + + return deploy_resource def handle_connectsto(self, tosca_source, tosca_target, hot_source, hot_target, config_location, operation): @@ -202,17 +297,22 @@ class HotResource(object): elif config_location == 'source': hosting_server = self._get_hosting_server() hot_depends = hot_source + sw_deployment_resouce = HOTSoftwareDeploymentResources(hosting_server) + server_key = sw_deployment_resouce.server_key + servers = sw_deployment_resouce.servers + sw_deploy_res = sw_deployment_resouce.software_deployment + deploy_name = tosca_source.name + '_' + tosca_target.name + \ '_connect_deploy' sd_config = {'config': {'get_resource': self.name}, - 'server': {'get_resource': hosting_server.name}, + server_key: servers, 'signal_transport': 'HEAT_SIGNAL'} deploy_resource = \ HotResource(self.nodetemplate, deploy_name, - 'OS::Heat::SoftwareDeployment', + sw_deploy_res, sd_config, - depends_on=[hot_depends]) + depends_on=[hot_depends], csar_dir=self.csar_dir) connect_inputs = self._get_connect_inputs(config_location, operation) if connect_inputs: deploy_resource.properties['input_values'] = connect_inputs @@ -226,17 +326,31 @@ class HotResource(object): # handle hosting server for the OS:HEAT::SoftwareDeployment # from the TOSCA nodetemplate, traverse the relationship chain # down to the server - if self.type == 'OS::Heat::SoftwareDeployment': + sw_deploy_group = \ + HOTSoftwareDeploymentResources.HOT_SW_DEPLOYMENT_GROUP_RESOURCE + sw_deploy = HOTSoftwareDeploymentResources.HOT_SW_DEPLOYMENT_RESOURCE + + if self.properties.get('servers') and \ + self.properties.get('server'): + del self.properties['server'] + if self.type == sw_deploy_group or self.type == sw_deploy: # skip if already have hosting # If type is NodeTemplate, look up corresponding HotResrouce - host_server = self.properties.get('server') - if host_server is None or not host_server['get_resource']: + host_server = self.properties.get('servers') \ + or self.properties.get('server') + if host_server is None: raise Exception(_("Internal Error: expecting host " "in software deployment")) - elif isinstance(host_server['get_resource'], NodeTemplate): + + elif isinstance(host_server.get('get_resource'), NodeTemplate): self.properties['server']['get_resource'] = \ host_server['get_resource'].name + elif isinstance(host_server, dict) and \ + not host_server.get('get_resource'): + self.properties['servers'] = \ + host_server + def top_of_chain(self): dependent = self.group_dependencies.get(self) if dependent is None: @@ -244,6 +358,19 @@ class HotResource(object): else: return dependent.top_of_chain() + # this function allows to provides substacks as external files + # those files will be dumped along the output file. + # + # return a dict of filename-content + def extract_substack_templates(self, base_filename, hot_template_version): + return {} + + # this function asks the resource to embed substacks + # into the main template, if any. + # this is used when the final output is stdout + def embed_substack_templates(self, hot_template_version): + pass + def get_dict_output(self): resource_sections = OrderedDict() resource_sections[TYPE] = self.type @@ -273,7 +400,7 @@ class HotResource(object): inputs = operation.value.get('inputs') deploy_inputs = {} if inputs: - for name, value in six.iteritems(inputs): + for name, value in inputs.items(): deploy_inputs[name] = value return deploy_inputs @@ -284,17 +411,19 @@ class HotResource(object): inputs = operation.get('pre_configure_source').get('inputs') deploy_inputs = {} if inputs: - for name, value in six.iteritems(inputs): + for name, value in inputs.items(): deploy_inputs[name] = value return deploy_inputs def _get_hosting_server(self, node_template=None): # find the server that hosts this software by checking the # requirements and following the hosting chain + hosting_servers = [] + host_exists = False this_node_template = self.nodetemplate \ if node_template is None else node_template for requirement in this_node_template.requirements: - for requirement_name, assignment in six.iteritems(requirement): + for requirement_name, assignment in requirement.items(): for check_node in this_node_template.related_nodes: # check if the capability is Container if isinstance(assignment, dict): @@ -304,17 +433,20 @@ class HotResource(object): if node_name and node_name == check_node.name: if self._is_container_type(requirement_name, check_node): - return check_node - elif check_node.related_nodes: + hosting_servers.append(check_node.name) + host_exists = True + elif check_node.related_nodes and not host_exists: return self._get_hosting_server(check_node) + if hosting_servers: + return hosting_servers return None def _is_container_type(self, requirement_name, node): # capability is a list of dict # For now just check if it's type tosca.nodes.Compute # TODO(anyone): match up requirement and capability - base_type = HotResource.get_base_type(node.type_definition) - if base_type.type == 'tosca.nodes.Compute': + base_type = HotResource.get_base_type_str(node.type_definition) + if base_type == 'tosca.nodes.Compute': return True else: return False @@ -335,7 +467,24 @@ class HotResource(object): return tosca_props @staticmethod - def _get_all_operations(node): + def get_all_artifacts(nodetemplate): + # workaround bug in the parser + base_type = HotResource.get_base_type_str(nodetemplate.type_definition) + if base_type in policy_type: + artifacts = {} + else: + artifacts = nodetemplate.type_definition.get_value('artifacts', + parent=True) + if not artifacts: + artifacts = {} + tpl_artifacts = nodetemplate.entity_tpl.get('artifacts') + if tpl_artifacts: + artifacts.update(tpl_artifacts) + + return artifacts + + @staticmethod + def get_all_operations(node): operations = {} for operation in node.interfaces: operations[operation.name] = operation @@ -388,5 +537,48 @@ class HotResource(object): return node_type else: return HotResource.get_base_type(node_type.parent_type) - else: + return node_type.type + + @staticmethod + def get_base_type_str(node_type): + if isinstance(node_type, six.string_types): return node_type + if node_type.parent_type is not None: + parent_type_str = None + if isinstance(node_type.parent_type, six.string_types): + parent_type_str = node_type.parent_type + else: + parent_type_str = node_type.parent_type.type + + if parent_type_str and parent_type_str.endswith('.Root'): + return node_type.type + else: + return HotResource.get_base_type_str(node_type.parent_type) + + return node_type.type + + +class HOTSoftwareDeploymentResources(object): + """Provides HOT Software Deployment resources + + SoftwareDeployment or SoftwareDeploymentGroup Resource + """ + + HOT_SW_DEPLOYMENT_RESOURCE = 'OS::Heat::SoftwareDeployment' + HOT_SW_DEPLOYMENT_GROUP_RESOURCE = 'OS::Heat::SoftwareDeploymentGroup' + + def __init__(self, hosting_server=None): + self.software_deployment = self.HOT_SW_DEPLOYMENT_RESOURCE + self.software_deployment_group = self.HOT_SW_DEPLOYMENT_GROUP_RESOURCE + self.server_key = 'server' + self.hosting_server = hosting_server + self.servers = {} + if hosting_server is not None: + if len(self.hosting_server) == 1: + if isinstance(hosting_server, list): + self.servers['get_resource'] = self.hosting_server[0] + else: + for server in self.hosting_server: + self.servers[server] = {'get_resource': server} + self.software_deployment = self.software_deployment_group + self.server_key = 'servers' diff --git a/tosca2heat/heat-translator/translator/hot/syntax/hot_template.py b/tosca2heat/heat-translator/translator/hot/syntax/hot_template.py index c92d341..7fae022 100644 --- a/tosca2heat/heat-translator/translator/hot/syntax/hot_template.py +++ b/tosca2heat/heat-translator/translator/hot/syntax/hot_template.py @@ -13,6 +13,7 @@ from collections import OrderedDict import logging +import os import textwrap from toscaparser.utils.gettextutils import _ import yaml @@ -28,7 +29,7 @@ class HotTemplate(object): ('heat_template_version', 'description', 'parameter_groups', 'parameters', 'resources', 'outputs', '__undefined__') - VERSIONS = (LATEST,) = ('2014-10-16',) + VERSIONS = (LATEST,) = ('2013-05-23',) def __init__(self): self.resources = [] @@ -44,11 +45,34 @@ class HotTemplate(object): nodes.append((node_key, node_value)) return yaml.nodes.MappingNode(u'tag:yaml.org,2002:map', nodes) - def output_to_yaml(self): + def output_to_yaml_files_dict(self, base_filename, + hot_template_version=LATEST): + yaml_files_dict = {} + base_filename, ext = os.path.splitext(base_filename) + + # convert from inlined substack to a substack defined in another file + for resource in self.resources: + yaml_files_dict.update( + resource.extract_substack_templates(base_filename, + hot_template_version)) + + yaml_files_dict[base_filename + ext] = \ + self.output_to_yaml(hot_template_version, False) + + return yaml_files_dict + + def output_to_yaml(self, hot_template_version=LATEST, + embed_substack_templates=True): log.debug(_('Converting translated output to yaml format.')) + + if embed_substack_templates: + # fully inlined substack by storing the template as a blob string + for resource in self.resources: + resource.embed_substack_templates(hot_template_version) + dict_output = OrderedDict() # Version - version_string = self.VERSION + ": " + self.LATEST + "\n\n" + version_string = self.VERSION + ": " + hot_template_version + "\n\n" # Description desc_str = "" @@ -77,7 +101,10 @@ 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('\'', '') + # also replace double return lines with a single one + # seems to be a bug in the serialization of multiline literal scalars + yaml_string = yaml_string.replace('\'', '') .replace('\n\n', '\n') return version_string + desc_str + yaml_string diff --git a/tosca2heat/heat-translator/translator/hot/tests/test_translate_outputs.py b/tosca2heat/heat-translator/translator/hot/tests/test_translate_outputs.py index 12ea355..955150e 100644 --- a/tosca2heat/heat-translator/translator/hot/tests/test_translate_outputs.py +++ b/tosca2heat/heat-translator/translator/hot/tests/test_translate_outputs.py @@ -33,12 +33,12 @@ class ToscaTemplateOutputTest(TestCase): 'server, http://:3000', 'value': {'get_attr': - ['app_server', 'first_address']}}, + ['app_server', 'networks', 'private', 0]}}, 'mongodb_url': {'description': 'URL for the mongodb server.', 'value': {'get_attr': - ['mongo_server', 'first_address']}}} + ['mongo_server', 'networks', 'private', 0]}}} hot_translation_dict = \ toscaparser.utils.yamlparser.simple_parse(hot_translation) diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_autoscaling.py b/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_autoscaling.py new file mode 100644 index 0000000..978e965 --- /dev/null +++ b/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_autoscaling.py @@ -0,0 +1,91 @@ +# 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 toscaparser.nodetemplate import NodeTemplate +from toscaparser.policy import Policy +from toscaparser.tests.base import TestCase +import toscaparser.utils.yamlparser +from translator.hot.tosca.tosca_compute import ToscaCompute +from translator.hot.tosca.tosca_policies_scaling import ToscaAutoscaling + + +class AutoscalingTest(TestCase): + + def _tosca_scaling_test(self, tpl_snippet, expectedprops): + nodetemplates = (toscaparser.utils.yamlparser. + simple_parse(tpl_snippet)['node_templates']) + policies = (toscaparser.utils.yamlparser. + simple_parse(tpl_snippet)['policies']) + name = list(nodetemplates.keys())[0] + policy_name = list(policies[0].keys())[0] + for policy in policies: + tpl = policy[policy_name] + targets = tpl["targets"] + properties = tpl["properties"] + try: + nodetemplate = NodeTemplate(name, nodetemplates) + toscacompute = ToscaCompute(nodetemplate) + toscacompute.handle_properties() + policy = Policy(policy_name, tpl, targets, + properties, "node_templates") + toscascaling = ToscaAutoscaling(policy) + parameters = toscascaling.handle_properties([toscacompute]) + self.assertEqual(parameters[0].properties, expectedprops) + except Exception: + raise + + def test_compute_with_scaling(self): + tpl_snippet = ''' + node_templates: + my_server_1: + type: tosca.nodes.Compute + capabilities: + host: + properties: + num_cpus: 2 + disk_size: 10 GB + mem_size: 512 MB + os: + properties: + # host Operating System image properties + architecture: x86_64 + type: Linux + distribution: RHEL + version: 6.5 + policies: + - asg: + type: tosca.policies.Scaling + description: Simple node autoscaling + targets: [my_server_1] + triggers: + resize_compute: + description: trigger + condition: + constraint: utilization greater_than 50% + period: 60 + evaluations: 1 + method: average + properties: + min_instances: 2 + max_instances: 10 + default_instances: 3 + increment: 1 + ''' + + expectedprops = {'desired_capacity': 3, + 'max_size': 10, + 'min_size': 2, + 'resource': {'type': 'asg_res.yaml'}} + + self._tosca_scaling_test( + tpl_snippet, + expectedprops) 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 408ee8b..1a135f4 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 @@ -10,13 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. -import json import mock -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 +24,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.assertEqual(expectedprops, toscacompute.properties) def test_node_compute_with_host_and_os_capabilities(self): tpl_snippet = ''' @@ -84,7 +71,6 @@ class ToscaComputeTest(TestCase): #left intentionally ''' expectedprops = {'flavor': 'm1.large', - 'image': None, 'user_data_format': 'SOFTWARE_CONFIG', 'software_config_transport': 'POLL_SERVER_HEAT'} self._tosca_compute_test( @@ -123,7 +109,6 @@ class ToscaComputeTest(TestCase): #left intentionally ''' expectedprops = {'flavor': None, - 'image': None, 'user_data_format': 'SOFTWARE_CONFIG', 'software_config_transport': 'POLL_SERVER_HEAT'} self._tosca_compute_test( @@ -137,7 +122,6 @@ class ToscaComputeTest(TestCase): type: tosca.nodes.Compute ''' expectedprops = {'flavor': None, - 'image': None, 'user_data_format': 'SOFTWARE_CONFIG', 'software_config_transport': 'POLL_SERVER_HEAT'} self._tosca_compute_test( @@ -155,7 +139,6 @@ class ToscaComputeTest(TestCase): #left intentionally ''' expectedprops = {'flavor': None, - 'image': None, 'user_data_format': 'SOFTWARE_CONFIG', 'software_config_transport': 'POLL_SERVER_HEAT'} self._tosca_compute_test( @@ -174,7 +157,6 @@ class ToscaComputeTest(TestCase): mem_size: 4 GB ''' expectedprops = {'flavor': 'm1.large', - 'image': None, 'user_data_format': 'SOFTWARE_CONFIG', 'software_config_transport': 'POLL_SERVER_HEAT'} self._tosca_compute_test( @@ -193,7 +175,6 @@ class ToscaComputeTest(TestCase): disk_size: 10 GB ''' expectedprops = {'flavor': 'm1.large', - 'image': None, 'user_data_format': 'SOFTWARE_CONFIG', 'software_config_transport': 'POLL_SERVER_HEAT'} self._tosca_compute_test( @@ -211,18 +192,14 @@ class ToscaComputeTest(TestCase): num_cpus: 4 ''' expectedprops = {'flavor': 'm1.large', - 'image': None, 'user_data_format': 'SOFTWARE_CONFIG', 'software_config_transport': 'POLL_SERVER_HEAT'} self._tosca_compute_test( tpl_snippet, expectedprops) - @patch('requests.post') - @patch('requests.get') - @patch('os.getenv') - def test_node_compute_with_nova_flavor(self, mock_os_getenv, - mock_get, mock_post): + @mock.patch('translator.common.flavors.get_flavors') + def test_node_compute_with_nova_flavor(self, mock_flavor): tpl_snippet = ''' node_templates: server: @@ -234,56 +211,19 @@ class ToscaComputeTest(TestCase): disk_size: 1 GB mem_size: 1 GB ''' - with patch('translator.common.utils.' - 'check_for_env_variables') as mock_check_env: - mock_check_env.return_value = True - mock_os_getenv.side_effect = ['demo', 'demo', - 'demo', 'http://abc.com/5000/', - 'demo', 'demo', - 'demo', 'http://abc.com/5000/'] - mock_ks_response = mock.MagicMock() - mock_ks_response.status_code = 200 - mock_ks_content = { - 'access': { - 'token': { - 'id': 'd1dfa603-3662-47e0-b0b6-3ae7914bdf76' - }, - 'serviceCatalog': [{ - 'type': 'compute', - 'endpoints': [{ - 'publicURL': 'http://abc.com' - }] - }] - } - } - mock_ks_response.content = json.dumps(mock_ks_content) - mock_nova_response = mock.MagicMock() - mock_nova_response.status_code = 200 - mock_flavor_content = { - 'flavors': [{ - 'name': 'm1.mock_flavor', - 'ram': 1024, - 'disk': 1, - 'vcpus': 1 - }] - } - mock_nova_response.content = \ - json.dumps(mock_flavor_content) - mock_post.return_value = mock_ks_response - mock_get.return_value = mock_nova_response - expectedprops = {'flavor': 'm1.mock_flavor', - 'image': None, - 'user_data_format': 'SOFTWARE_CONFIG', - 'software_config_transport': 'POLL_SERVER_HEAT'} - self._tosca_compute_test( - tpl_snippet, - expectedprops) + mock_flavor.return_value = { + 'm1.mock_flavor': { + 'mem_size': 1024, + 'disk_size': 1, + 'num_cpus': 1} + } + expectedprops = {'flavor': 'm1.mock_flavor', + 'user_data_format': 'SOFTWARE_CONFIG', + 'software_config_transport': 'POLL_SERVER_HEAT'} + self._tosca_compute_test(tpl_snippet, expectedprops) - @patch('requests.post') - @patch('requests.get') - @patch('os.getenv') - def test_node_compute_without_nova_flavor(self, mock_os_getenv, - mock_get, mock_post): + @mock.patch('translator.common.images.get_images') + def test_node_compute_with_glance_image(self, mock_images): tpl_snippet = ''' node_templates: server: @@ -294,19 +234,25 @@ class ToscaComputeTest(TestCase): num_cpus: 1 disk_size: 1 GB mem_size: 1 GB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Fake Distribution + version: 19.0 ''' - with patch('translator.common.utils.' - 'check_for_env_variables') as mock_check_env: - mock_check_env.return_value = True - mock_os_getenv.side_effect = ['demo', 'demo', - 'demo', 'http://abc.com/5000/'] - mock_ks_response = mock.MagicMock() - mock_ks_content = {} - mock_ks_response.content = json.dumps(mock_ks_content) - expectedprops = {'flavor': 'm1.small', - 'image': None, - 'user_data_format': 'SOFTWARE_CONFIG', - 'software_config_transport': 'POLL_SERVER_HEAT'} - self._tosca_compute_test( - tpl_snippet, - expectedprops) + mock_images.return_value = { + 'fake-image-foobar': {'architecture': 'x86_64', + 'type': 'Linux', + 'distribution': 'Fake Distribution', + 'version': '19.0'}, + 'fake-image-foobar-old': {'architecture': 'x86_64', + 'type': 'Linux', + 'distribution': 'Fake Distribution', + 'version': '18.0'} + } + expectedprops = {'flavor': 'm1.small', + 'image': 'fake-image-foobar', + 'user_data_format': 'SOFTWARE_CONFIG', + 'software_config_transport': 'POLL_SERVER_HEAT'} + 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..27c1033 100644 --- a/tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage.py +++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage.py @@ -29,9 +29,10 @@ class ToscaBlockStorage(HotResource): toscatype = 'tosca.nodes.BlockStorage' - def __init__(self, nodetemplate): + def __init__(self, nodetemplate, csar_dir=None): super(ToscaBlockStorage, self).__init__(nodetemplate, - type='OS::Cinder::Volume') + type='OS::Cinder::Volume', + csar_dir=csar_dir) pass def handle_properties(self): @@ -67,5 +68,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 71b9822..f471b83 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 @@ -23,9 +23,11 @@ class ToscaBlockStorageAttachment(HotResource): toscatype = 'tosca.nodes.BlockStorageAttachment' - def __init__(self, template, nodetemplates, instance_uuid, volume_id): + def __init__(self, template, nodetemplates, instance_uuid, volume_id, + csar_dir=None): super(ToscaBlockStorageAttachment, - self).__init__(template, type='OS::Cinder::VolumeAttachment') + self).__init__(template, type='OS::Cinder::VolumeAttachment', + csar_dir=csar_dir) self.nodetemplates = nodetemplates self.instance_uuid = {'get_resource': instance_uuid} self.volume_id = {'get_resource': volume_id} @@ -50,4 +52,4 @@ class ToscaBlockStorageAttachment(HotResource): 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 9d6a459..85f312d 100644 --- a/tosca2heat/heat-translator/translator/hot/tosca/tosca_compute.py +++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_compute.py @@ -11,68 +11,21 @@ # License for the specific language governing permissions and limitations # under the License. -import json import logging -import requests from toscaparser.utils.gettextutils import _ +from translator.common import flavors as nova_flavors +from translator.common import images as glance_images import translator.common.utils from translator.hot.syntax.hot_resource import HotResource + log = logging.getLogger('heat-translator') # Name used to dynamically load appropriate map class. TARGET_CLASS_NAME = 'ToscaCompute' -# A design issue to be resolved is how to translate the generic TOSCA server -# properties to OpenStack flavors and images. At the Atlanta design summit, -# there was discussion on using Glance to store metadata and Graffiti to -# describe artifacts. We will follow these projects to see if they can be -# leveraged for this TOSCA translation. -# For development purpose at this time, we temporarily hardcode a list of -# flavors and images here -FLAVORS = {'m1.xlarge': {'mem_size': 16384, 'disk_size': 160, 'num_cpus': 8}, - 'm1.large': {'mem_size': 8192, 'disk_size': 80, 'num_cpus': 4}, - 'm1.medium': {'mem_size': 4096, 'disk_size': 40, 'num_cpus': 2}, - 'm1.small': {'mem_size': 2048, 'disk_size': 20, 'num_cpus': 1}, - 'm1.tiny': {'mem_size': 512, 'disk_size': 1, 'num_cpus': 1}, - 'm1.micro': {'mem_size': 128, 'disk_size': 0, 'num_cpus': 1}, - 'm1.nano': {'mem_size': 64, 'disk_size': 0, 'num_cpus': 1}} - -IMAGES = {'ubuntu-software-config-os-init': {'architecture': 'x86_64', - 'type': 'Linux', - 'distribution': 'Ubuntu', - 'version': '14.04'}, - 'ubuntu-12.04-software-config-os-init': {'architecture': 'x86_64', - 'type': 'Linux', - 'distribution': 'Ubuntu', - 'version': '12.04'}, - 'fedora-amd64-heat-config': {'architecture': 'x86_64', - 'type': 'Linux', - 'distribution': 'Fedora', - 'version': '18.0'}, - 'F18-x86_64-cfntools': {'architecture': 'x86_64', - 'type': 'Linux', - 'distribution': 'Fedora', - 'version': '19'}, - 'Fedora-x86_64-20-20131211.1-sda': {'architecture': 'x86_64', - 'type': 'Linux', - 'distribution': 'Fedora', - 'version': '20'}, - 'cirros-0.3.1-x86_64-uec': {'architecture': 'x86_64', - 'type': 'Linux', - 'distribution': 'CirrOS', - 'version': '0.3.1'}, - 'cirros-0.3.2-x86_64-uec': {'architecture': 'x86_64', - 'type': 'Linux', - 'distribution': 'CirrOS', - 'version': '0.3.2'}, - 'rhel-6.5-test-image': {'architecture': 'x86_64', - 'type': 'Linux', - 'distribution': 'RHEL', - 'version': '6.5'}} - class ToscaCompute(HotResource): '''Translate TOSCA node type tosca.nodes.Compute.''' @@ -84,9 +37,18 @@ class ToscaCompute(HotResource): ('architecture', 'distribution', 'type', 'version') toscatype = 'tosca.nodes.Compute' - def __init__(self, nodetemplate): + 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, csar_dir=None): super(ToscaCompute, self).__init__(nodetemplate, - type='OS::Nova::Server') + type='OS::Nova::Server', + csar_dir=csar_dir) # List with associated hot port resources with this server self.assoc_port_resources = [] pass @@ -99,7 +61,8 @@ class ToscaCompute(HotResource): self.properties['software_config_transport'] = 'POLL_SERVER_HEAT' 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, @@ -125,91 +88,16 @@ class ToscaCompute(HotResource): if os_cap_props: image = self._best_image(os_cap_props) hot_properties['flavor'] = flavor - hot_properties['image'] = image + if image: + hot_properties['image'] = image + else: + hot_properties.pop('image', None) return hot_properties - def _create_nova_flavor_dict(self): - '''Populates and returns the flavors dict using Nova ReST API''' - 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 - nova_url = translator.common.utils.get_url_for(access_dict, - 'compute') - if not nova_url: - return None - nova_response = requests.get(nova_url + '/flavors/detail', - headers={'X-Auth-Token': - access_token}) - if nova_response.status_code != 200: - return None - flavors = json.loads(nova_response.content)['flavors'] - flavor_dict = dict() - for flavor in flavors: - flavor_name = str(flavor['name']) - flavor_dict[flavor_name] = { - 'mem_size': flavor['ram'], - 'disk_size': flavor['disk'], - 'num_cpus': flavor['vcpus'], - } - except Exception as e: - # Handles any exception coming from openstack - log.warn(_('Choosing predefined flavors since received ' - 'Openstack Exception: %s') % str(e)) - 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. - flavors = FLAVORS - if translator.common.utils.check_for_env_variables(): - resp = self._create_nova_flavor_dict() - if resp: - flavors = resp + flavors = nova_flavors.get_flavors() # start with all flavors match_all = flavors.keys() @@ -252,11 +140,7 @@ class ToscaCompute(HotResource): def _best_image(self, properties): # Check whether user exported all required environment variables. - images = IMAGES - if translator.common.utils.check_for_env_variables(): - resp = self._populate_image_dict() - if resp and len(resp.keys()) > 0: - images = resp + images = glance_images.get_images() match_all = images.keys() architecture = properties.get(self.ARCHITECTURE) if architecture is None: @@ -307,7 +191,10 @@ class ToscaCompute(HotResource): return this_list matching_images = [] for image in this_list: - if this_dict[image][attr].lower() == str(prop).lower(): + if attr in this_dict[image]: + if this_dict[image][attr].lower() == str(prop).lower(): + matching_images.insert(0, image) + else: matching_images.append(image) return matching_images @@ -324,7 +211,7 @@ class ToscaCompute(HotResource): attriute.')) if attribute == 'private_address' or \ attribute == 'public_address': - attr['get_attr'] = [self.name, 'first_address'] + attr['get_attr'] = [self.name, 'networks', 'private', 0] return attr diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_database.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_database.py index 26c9d4d..7c8bc45 100644 --- a/tosca2heat/heat-translator/translator/hot/tosca/tosca_database.py +++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_database.py @@ -22,8 +22,8 @@ class ToscaDatabase(HotResource): toscatype = 'tosca.nodes.Database' - def __init__(self, nodetemplate): - super(ToscaDatabase, self).__init__(nodetemplate) + def __init__(self, nodetemplate, csar_dir=None): + super(ToscaDatabase, self).__init__(nodetemplate, csar_dir=csar_dir) pass def handle_properties(self): diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_dbms.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_dbms.py index 38c31bd..3136792 100644 --- a/tosca2heat/heat-translator/translator/hot/tosca/tosca_dbms.py +++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_dbms.py @@ -22,8 +22,8 @@ class ToscaDbms(HotResource): toscatype = 'tosca.nodes.DBMS' - def __init__(self, nodetemplate): - super(ToscaDbms, self).__init__(nodetemplate) + def __init__(self, nodetemplate, csar_dir=None): + super(ToscaDbms, self).__init__(nodetemplate, csar_dir=csar_dir) pass def handle_properties(self): diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_network_network.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_network_network.py index a4e565e..10e6405 100644 --- a/tosca2heat/heat-translator/translator/hot/tosca/tosca_network_network.py +++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_network_network.py @@ -28,9 +28,10 @@ class ToscaNetwork(HotResource): existing_resource_id = None - def __init__(self, nodetemplate): + def __init__(self, nodetemplate, csar_dir=None): super(ToscaNetwork, self).__init__(nodetemplate, - type='OS::Neutron::Net') + type='OS::Neutron::Net', + csar_dir=csar_dir) pass def handle_properties(self): @@ -57,8 +58,6 @@ class ToscaNetwork(HotResource): self.existing_resource_id = value break elif key == 'segmentation_id': - # net_props['segmentation_id'] = \ - # tosca_props['segmentation_id'] # Hardcode to vxlan for now until we add the network type # and physical network to the spec. net_props['value_specs'] = {'provider:segmentation_id': diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_network_port.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_network_port.py index 4fd2d70..86733e4 100644 --- a/tosca2heat/heat-translator/translator/hot/tosca/tosca_network_port.py +++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_network_port.py @@ -24,9 +24,10 @@ class ToscaNetworkPort(HotResource): toscatype = 'tosca.nodes.network.Port' - def __init__(self, nodetemplate): + def __init__(self, nodetemplate, csar_dir=None): super(ToscaNetworkPort, self).__init__(nodetemplate, - type='OS::Neutron::Port') + type='OS::Neutron::Port', + csar_dir=csar_dir) # Default order self.order = 0 pass diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_object_storage.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_object_storage.py index 177503f..e30c46c 100644 --- a/tosca2heat/heat-translator/translator/hot/tosca/tosca_object_storage.py +++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_object_storage.py @@ -23,9 +23,10 @@ class ToscaObjectStorage(HotResource): toscatype = 'tosca.nodes.ObjectStorage' - def __init__(self, nodetemplate): + def __init__(self, nodetemplate, csar_dir=None): super(ToscaObjectStorage, self).__init__(nodetemplate, - type='OS::Swift::Container') + type='OS::Swift::Container', + csar_dir=csar_dir) pass def handle_properties(self): diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_policies.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_policies.py index b32fc1d..12b40d5 100644 --- a/tosca2heat/heat-translator/translator/hot/tosca/tosca_policies.py +++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_policies.py @@ -22,9 +22,10 @@ class ToscaPolicies(HotResource): toscatype = 'tosca.policies.Placement' - def __init__(self, policy): + def __init__(self, policy, csar_dir=None): super(ToscaPolicies, self).__init__(policy, - type='OS::Nova::ServerGroup') + type='OS::Nova::ServerGroup', + csar_dir=csar_dir) self.policy = policy def handle_properties(self, resources): diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_policies_scaling.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_policies_scaling.py new file mode 100644 index 0000000..1b63f24 --- /dev/null +++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_policies_scaling.py @@ -0,0 +1,131 @@ +# +# 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 collections import OrderedDict +import yaml + +from translator.hot.syntax.hot_resource import HotResource +# Name used to dynamically load appropriate map class. +TARGET_CLASS_NAME = 'ToscaAutoscaling' +HEAT_TEMPLATE_BASE = """ +heat_template_version: 2013-05-23 +""" +ALARM_STATISTIC = {'average': 'avg'} +SCALING_RESOURCES = ["OS::Heat::ScalingPolicy", "OS::Heat::AutoScalingGroup", + "OS::Aodh::Alarm"] + + +class ToscaAutoscaling(HotResource): + '''Translate TOSCA node type tosca.policies.Scaling''' + + toscatype = 'tosca.policies.Scaling' + + def __init__(self, policy, csar_dir=None): + hot_type = "OS::Heat::ScalingPolicy" + super(ToscaAutoscaling, self).__init__(policy, + type=hot_type, + csar_dir=csar_dir) + self.policy = policy + + def handle_expansion(self): + if self.policy.entity_tpl.get('triggers'): + sample = self.policy.\ + entity_tpl["triggers"]["resize_compute"]["condition"] + prop = {} + prop["description"] = self.policy.entity_tpl.get('description') + prop["meter_name"] = "cpu_util" + if sample: + prop["statistic"] = ALARM_STATISTIC[sample["method"]] + prop["period"] = sample["period"] + prop["threshold"] = sample["evaluations"] + prop["comparison_operator"] = "gt" + alarm_name = self.name.replace('_scale_in', '').\ + replace('_scale_out', '') + ceilometer_resources = HotResource(self.nodetemplate, + type='OS::Aodh::Alarm', + name=alarm_name + '_alarm', + properties=prop) + hot_resources = [ceilometer_resources] + return hot_resources + + def represent_ordereddict(self, dumper, data): + nodes = [] + for key, value in data.items(): + node_key = dumper.represent_data(key) + node_value = dumper.represent_data(value) + nodes.append((node_key, node_value)) + return yaml.nodes.MappingNode(u'tag:yaml.org,2002:map', nodes) + + def _handle_nested_template(self, scale_res): + template_dict = yaml.safe_load(HEAT_TEMPLATE_BASE) + template_dict['description'] = 'Tacker Scaling template' + template_dict["resources"] = {} + dict_res = OrderedDict() + for res in scale_res: + dict_res = res.get_dict_output() + res_name = list(dict_res.keys())[0] + template_dict["resources"][res_name] = \ + dict_res[res_name] + + yaml.add_representer(OrderedDict, self.represent_ordereddict) + yaml.add_representer(dict, self.represent_ordereddict) + yaml_string = yaml.dump(template_dict, default_flow_style=False) + yaml_string = yaml_string.replace('\'', '') .replace('\n\n', '\n') + self.nested_template = { + self.policy.name + '_res.yaml': yaml_string + } + + def handle_properties(self, resources): + self.properties = {} + self.properties["auto_scaling_group_id"] = { + 'get_resource': self.policy.name + '_group' + } + self.properties["adjustment_type"] = "change_in_capacity " + self.properties["scaling_adjustment"] = self.\ + policy.entity_tpl["properties"]["increment"] + delete_res_names = [] + scale_res = [] + for index, resource in enumerate(resources): + if resource.name in self.policy.targets and \ + resource.type != 'OS::Heat::AutoScalingGroup': + temp = self.policy.entity_tpl["properties"] + props = {} + res = {} + res["min_size"] = temp["min_instances"] + res["max_size"] = temp["max_instances"] + res["desired_capacity"] = temp["default_instances"] + props['type'] = resource.type + props['properties'] = resource.properties + res['resource'] = {'type': self.policy.name + '_res.yaml'} + scaling_resources = \ + HotResource(resource, + type='OS::Heat::AutoScalingGroup', + name=self.policy.name + '_group', + properties=res) + + if resource.type not in SCALING_RESOURCES: + delete_res_names.append(resource.name) + scale_res.append(resource) + self._handle_nested_template(scale_res) + resources = [tmp_res + for tmp_res in resources + if tmp_res.name not in delete_res_names] + resources.append(scaling_resources) + return resources + + def extract_substack_templates(self, base_filename, hot_template_version): + return self.nested_template + + def embed_substack_templates(self, hot_template_version): + pass diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_software_component.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_software_component.py index 044de43..9b0819e 100644 --- a/tosca2heat/heat-translator/translator/hot/tosca/tosca_software_component.py +++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_software_component.py @@ -22,8 +22,9 @@ class ToscaSoftwareComponent(HotResource): toscatype = 'tosca.nodes.SoftwareComponent' - def __init__(self, nodetemplate): - super(ToscaSoftwareComponent, self).__init__(nodetemplate) + def __init__(self, nodetemplate, csar_dir=None): + super(ToscaSoftwareComponent, self).__init__(nodetemplate, + csar_dir=csar_dir) pass def handle_properties(self): diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_web_application.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_web_application.py index d0a9c5d..03474aa 100644 --- a/tosca2heat/heat-translator/translator/hot/tosca/tosca_web_application.py +++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_web_application.py @@ -22,8 +22,9 @@ class ToscaWebApplication(HotResource): toscatype = 'tosca.nodes.WebApplication' - def __init__(self, nodetemplate): - super(ToscaWebApplication, self).__init__(nodetemplate) + def __init__(self, nodetemplate, csar_dir=None): + super(ToscaWebApplication, self).__init__(nodetemplate, + csar_dir=csar_dir) pass def handle_properties(self): diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_webserver.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_webserver.py index 83bda80..32b80c5 100644 --- a/tosca2heat/heat-translator/translator/hot/tosca/tosca_webserver.py +++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_webserver.py @@ -22,8 +22,9 @@ class ToscaWebserver(HotResource): toscatype = 'tosca.nodes.WebServer' - def __init__(self, nodetemplate): - super(ToscaWebserver, self).__init__(nodetemplate) + def __init__(self, nodetemplate, csar_dir): + super(ToscaWebserver, self).__init__(nodetemplate, + csar_dir=csar_dir) pass def handle_properties(self): diff --git a/tosca2heat/heat-translator/translator/hot/tosca_translator.py b/tosca2heat/heat-translator/translator/hot/tosca_translator.py index 14ef8a1..b9d4c77 100644 --- a/tosca2heat/heat-translator/translator/hot/tosca_translator.py +++ b/tosca2heat/heat-translator/translator/hot/tosca_translator.py @@ -12,6 +12,7 @@ # under the License. import logging +import six from toscaparser.utils.gettextutils import _ from translator.hot.syntax.hot_template import HotTemplate from translator.hot.translate_inputs import TranslateInputs @@ -24,24 +25,64 @@ log = logging.getLogger('heat-translator') class TOSCATranslator(object): '''Invokes translation methods.''' - def __init__(self, tosca, parsed_params, deploy=None): + def __init__(self, tosca, parsed_params, deploy=None, csar_dir=None): super(TOSCATranslator, self).__init__() self.tosca = tosca self.hot_template = HotTemplate() self.parsed_params = parsed_params self.deploy = deploy + self.csar_dir = csar_dir self.node_translator = None log.info(_('Initialized parmaters for translation.')) - def translate(self): + def _translate_to_hot_yaml(self): self._resolve_input() self.hot_template.description = self.tosca.description self.hot_template.parameters = self._translate_inputs() self.node_translator = TranslateNodeTemplates(self.tosca, - self.hot_template) - self.hot_template.resources = self.node_translator.translate() + self.hot_template, + csar_dir=self.csar_dir) + self.hot_template.resources = \ + self.node_translator.translate() self.hot_template.outputs = self._translate_outputs() - return self.hot_template.output_to_yaml() + if self.node_translator.hot_template_version is None: + self.node_translator.hot_template_version = HotTemplate.LATEST + + def translate(self): + """Translate to HOT YAML + + This method produces a translated output for main template. + The nested template, if any referenced by main, will be created + as a separate file. + """ + self._translate_to_hot_yaml() + + # TODO(mvelten) go back to calling hot_template.output_to_yaml instead + # for stdout once embed_substack_templates is correctly implemented + # return self.hot_template.output_to_yaml( + # self.node_translator.hot_template_version) + yaml_files = self.hot_template.output_to_yaml_files_dict( + "output.yaml", + self.node_translator.hot_template_version) + for name, content in six.iteritems(yaml_files): + if name != "output.yaml": + with open(name, 'w+') as f: + f.write(content) + + return yaml_files["output.yaml"] + + def translate_to_yaml_files_dict(self, base_filename): + """Translate to HOT YAML + + This method produces a translated output containing main and + any nested templates referenced by main. This output can be + programmatically stored into different files by using key as + template name and value as template content. + """ + self._translate_to_hot_yaml() + return self.hot_template.output_to_yaml_files_dict( + base_filename, + self.node_translator.hot_template_version) def _translate_inputs(self): translator = TranslateInputs(self.tosca.inputs, self.parsed_params, diff --git a/tosca2heat/heat-translator/translator/hot/translate_node_templates.py b/tosca2heat/heat-translator/translator/hot/translate_node_templates.py index 0aefd48..1a1a4d7 100644 --- a/tosca2heat/heat-translator/translator/hot/translate_node_templates.py +++ b/tosca2heat/heat-translator/translator/hot/translate_node_templates.py @@ -11,13 +11,17 @@ # License for the specific language governing permissions and limitations # under the License. +import copy import importlib 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 GetOperationOutput from toscaparser.functions import GetProperty from toscaparser.properties import Property from toscaparser.relationship_template import RelationshipTemplate @@ -25,6 +29,8 @@ 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.exception import UnsupportedTypeError +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,20 +138,31 @@ log = logging.getLogger('heat-translator') TOSCA_TO_HOT_TYPE = _generate_type_map() +BASE_TYPES = six.string_types + six.integer_types + (dict, OrderedDict) + +HOT_SCALING_POLICY_TYPE = ["OS::Heat::AutoScalingGroup", + "OS::Senlin::Profile"] + class TranslateNodeTemplates(object): '''Translate TOSCA NodeTemplates to Heat Resources.''' - def __init__(self, tosca, hot_template): + def __init__(self, tosca, hot_template, csar_dir=None): self.tosca = tosca self.nodetemplates = self.tosca.nodetemplates self.hot_template = hot_template + self.csar_dir = csar_dir # list of all HOT resources generated self.hot_resources = [] # mapping between TOSCA nodetemplate and HOT resource 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 = {} + self.hot_template_version = None + self.processed_policy_res = [] def translate(self): return self._translate_nodetemplates() @@ -161,23 +178,56 @@ class TranslateNodeTemplates(object): if resource.type == "OS::Nova::ServerGroup": resource.handle_properties(self.hot_resources) + elif resource.type in ("OS::Heat::ScalingPolicy", + "OS::Senlin::Policy"): + if resource.name in self.processed_policy_res: + return + self.processed_policy_res.append(resource.name) + self.hot_resources = \ + resource.handle_properties(self.hot_resources) + extra_hot_resources = [] + for res in self.hot_resources: + if res.type == 'OS::Heat::ScalingPolicy': + extra_res = copy.deepcopy(res) + scaling_adjustment = res.properties['scaling_adjustment'] + if scaling_adjustment < 0: + res.name = res.name + '_scale_in' + extra_res.name = extra_res.name + '_scale_out' + extra_res.properties['scaling_adjustment'] = \ + -1 * scaling_adjustment + extra_hot_resources.append(extra_res) + self.processed_policy_res.append(res.name) + self.processed_policy_res.append(extra_res.name) + elif scaling_adjustment > 0: + res.name = res.name + '_scale_out' + extra_res.name = extra_res.name + '_scale_in' + extra_res.properties['scaling_adjustment'] = \ + -1 * scaling_adjustment + extra_hot_resources.append(extra_res) + self.processed_policy_res.append(res.name) + self.processed_policy_res.append(extra_res.name) + else: + continue + self.hot_resources += extra_hot_resources else: resource.handle_properties() def _translate_nodetemplates(self): - log.debug(_('Translating the node templates.')) suffix = 0 # Copy the TOSCA graph: nodetemplate for node in self.nodetemplates: - base_type = HotResource.get_base_type(node.type_definition) - hot_node = TOSCA_TO_HOT_TYPE[base_type.type](node) + base_type = HotResource.get_base_type_str(node.type_definition) + if base_type not in TOSCA_TO_HOT_TYPE: + raise UnsupportedTypeError(type=_('%s') % base_type) + hot_node = TOSCA_TO_HOT_TYPE[base_type](node, + csar_dir=self.csar_dir) self.hot_resources.append(hot_node) self.hot_lookup[node] = hot_node # BlockStorage Attachment is a special case, # which doesn't match to Heat Resources 1 to 1. - if base_type.type == "tosca.nodes.Compute": + if base_type == "tosca.nodes.Compute": volume_name = None requirements = node.requirements if requirements: @@ -192,7 +242,7 @@ class TranslateNodeTemplates(object): "tosca.nodes.BlockStorage"): volume_name = node_name break - else: # unreachable code ! + else: for n in self.nodetemplates: if n.name == value and \ n.is_derived_from( @@ -201,9 +251,8 @@ class TranslateNodeTemplates(object): break suffix = suffix + 1 - attachment_node = self._get_attachment_node(node, - suffix, - volume_name) + attachment_node = self._get_attachment_node( + node, suffix, volume_name) if attachment_node: self.hot_resources.append(attachment_node) for i in self.tosca.inputs: @@ -216,6 +265,15 @@ class TranslateNodeTemplates(object): for policy in self.policies: policy_type = policy.type_definition + if policy.is_derived_from('tosca.policies.Scaling') and \ + policy_type.type != 'tosca.policies.Scaling.Cluster': + TOSCA_TO_HOT_TYPE[policy_type.type] = \ + TOSCA_TO_HOT_TYPE['tosca.policies.Scaling'] + if not policy.is_derived_from('tosca.policies.Scaling') and \ + policy_type.type not in TOSCA_TO_HOT_TYPE: + raise UnsupportedTypeError(type=_('%s') % policy_type.type) + elif policy_type.type == 'tosca.policies.Scaling.Cluster': + self.hot_template_version = '2016-04-08' policy_node = TOSCA_TO_HOT_TYPE[policy_type.type](policy) self.hot_resources.append(policy_node) @@ -223,9 +281,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: @@ -234,7 +297,7 @@ class TranslateNodeTemplates(object): connectsto_resources = [] for node in self.nodetemplates: for requirement in node.requirements: - for endpoint, details in six.iteritems(requirement): + for endpoint, details in requirement.items(): relation = None if isinstance(details, dict): target = details.get('node') @@ -257,7 +320,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_str( + node_depend.type_definition) + if base_type == 'tosca.nodes.Compute' and \ node.related[node_depend].type == \ node.type_definition.HOSTEDON: self.hot_lookup[node].properties['server'] = \ @@ -270,6 +335,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() @@ -281,7 +353,8 @@ class TranslateNodeTemplates(object): # dependent nodes in correct order self.processed_resources = [] for resource in self.hot_resources: - self._recursive_handle_properties(resource) + if resource.type not in HOT_SCALING_POLICY_TYPE: + self._recursive_handle_properties(resource) # handle resources that need to expand to more than one HOT resource expansion_resources = [] @@ -298,66 +371,215 @@ class TranslateNodeTemplates(object): # traverse the reference chain to get the actual value inputs = resource.properties.get('input_values') if inputs: - for name, value in six.iteritems(inputs): - inputs[name] = self._translate_input(value, resource) + for name, value in inputs.items(): + 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().args - 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 = HotResource.get_all_artifacts(tosca_target) + if artifact_name in artifacts: + cwd = os.getcwd() + artifact = artifacts[artifact_name] + if self.csar_dir: + os.chdir(self.csar_dir) + get_file = os.path.abspath(artifact.get('file')) + else: + get_file = artifact.get('file') + if artifact.get('type', None) == 'tosca.artifacts.File': + return {'get_file': get_file} + os.chdir(cwd) + 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': input_value.args} + return {'get_param': self.translate_param_value( + get_input_args, resource)} + elif isinstance(param_value, GetOperationOutput): + res = self._translate_get_operation_output_function( + param_value.args, tosca_template) + if res: + return res + 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 - return input_value + 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 get_all_artifacts(nodetemplate): - artifacts = nodetemplate.type_definition.get_value('artifacts', - parent=True) - if not artifacts: - artifacts = {} - tpl_artifacts = nodetemplate.entity_tpl.get('artifacts') - if tpl_artifacts: - artifacts.update(tpl_artifacts) + 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: + 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 artifacts + return tosca_target, prop_name, prop_arg def _get_attachment_node(self, node, suffix, volume_name): attach = False @@ -420,23 +642,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 @@ -444,7 +672,7 @@ class TranslateNodeTemplates(object): connect_interfaces): connectsto_resources = [] if connect_interfaces: - for iname, interface in six.iteritems(connect_interfaces): + for iname, interface in connect_interfaces.items(): connectsto_resources += \ self._create_connect_config(source_node, target_name, interface) @@ -470,16 +698,30 @@ class TranslateNodeTemplates(object): raise Exception(msg) config_name = source_node.name + '_' + target_name + '_connect_config' implement = connect_config.get('implementation') + cwd = os.getcwd() if config_location == 'target': + if self.csar_dir: + os.chdir(self.csar_dir) + get_file = os.path.abspath(implement) + else: + get_file = implement hot_config = HotResource(target_node, config_name, 'OS::Heat::SoftwareConfig', - {'config': {'get_file': implement}}) + {'config': {'get_file': get_file}}, + csar_dir=self.csar_dir) elif config_location == 'source': + if self.csar_dir: + os.chdir(self.csar_dir) + get_file = os.path.abspath(implement) + else: + get_file = implement hot_config = HotResource(source_node, config_name, 'OS::Heat::SoftwareConfig', - {'config': {'get_file': implement}}) + {'config': {'get_file': get_file}}, + csar_dir=self.csar_dir) + os.chdir(cwd) connectsto_resources.append(hot_config) hot_target = self._find_hot_resource_for_tosca(target_name) hot_source = self._find_hot_resource_for_tosca(source_node.name) 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 -- cgit 1.2.3-korg