summaryrefslogtreecommitdiffstats
path: root/tosca2heat/heat-translator/translator/hot
diff options
context:
space:
mode:
Diffstat (limited to 'tosca2heat/heat-translator/translator/hot')
-rw-r--r--tosca2heat/heat-translator/translator/hot/syntax/hot_output.py7
-rw-r--r--tosca2heat/heat-translator/translator/hot/syntax/hot_resource.py71
-rw-r--r--tosca2heat/heat-translator/translator/hot/syntax/hot_template.py1
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_compute.py60
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage.py2
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tosca_block_storage_attachment.py7
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tosca_compute.py79
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tosca_policies_antilocate.py35
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tosca_policies_colocate.py35
-rw-r--r--tosca2heat/heat-translator/translator/hot/translate_node_templates.py287
-rw-r--r--tosca2heat/heat-translator/translator/hot/translate_outputs.py14
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