Fully implements the semantic of get_* functions :

- handle optional capability/requirement parameter
- handle optional nested property / index

Handle functions in outputs.

Change-Id: I3f070f0a2531b31c897e96c3581280d8afb09d89
This commit is contained in:
Mathieu Velten 2016-05-26 11:54:18 +02:00
parent 1267ad19c0
commit f17a5c24af
8 changed files with 263 additions and 67 deletions

View File

@ -18,6 +18,7 @@ import numbers
import os
import re
import requests
import six
from six.moves.urllib.parse import urlparse
import yaml
@ -262,12 +263,17 @@ class UrlUtils(object):
def str_to_num(value):
"""Convert a string representation of a number into a numeric type."""
if isinstance(value, numbers.Number):
if isinstance(value, numbers.Number) \
or isinstance(value, six.integer_types) \
or isinstance(value, float):
return value
try:
return int(value)
except ValueError:
return float(value)
try:
return float(value)
except ValueError:
return None
def check_for_env_variables():

View File

@ -104,7 +104,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
@ -351,7 +351,7 @@ 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

View File

@ -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

View File

@ -25,6 +25,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 (
@ -312,7 +313,7 @@ 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
@ -327,49 +328,118 @@ class TranslateNodeTemplates(object):
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']
if 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
if 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)
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)
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]}
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
if 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)}
return input_value
return param_value
@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:
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 tosca_target, prop_name, prop_arg
@staticmethod
def get_all_artifacts(nodetemplate):
@ -438,23 +508,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_tosca_node(self, tosca_name, current_tosca_template=None):
tosca_node = None
if tosca_name == 'SELF':
tosca_node = current_tosca_template
if tosca_name == 'HOST' and current_tosca_template:
for req in current_tosca_template.requirements:
if 'host' in req:
tosca_node = self._find_tosca_node(req['host'])
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):
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:
if 'host' in req:
return self._find_hot_resource_for_tosca(req['host'])
for node in self.nodetemplates:
if node.name == tosca_name:
return self.hot_lookup[node]
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

View File

@ -33,16 +33,7 @@ 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,
output.description))
hot_value = self.nodes.translate_param_value(output.value, None)
hot_outputs.append(HotOutput(output.name, hot_value,
output.description))
return hot_outputs

View File

@ -0,0 +1,40 @@
heat_template_version: 2013-05-23
description: >
TOSCA template to test get_* functions semantic
parameters:
map_val:
type: string
resources:
myapp_configure_deploy:
type: OS::Heat::SoftwareDeployment
properties:
config:
get_resource: myapp_configure_config
input_values:
the_list_val: list_val_0
server:
get_resource: server
depends_on:
- mysql_database
server:
type: OS::Nova::Server
properties:
flavor: m1.small
image: ubuntu-12.04-software-config-os-init
user_data_format: SOFTWARE_CONFIG
myapp_configure_config:
type: OS::Heat::SoftwareConfig
properties:
config:
get_file: myapp_configure.sh
group: script
outputs:
map_val:
description: null
value:
get_input: map_val
static_map_val:
description: null
value: static_value

View File

@ -0,0 +1,77 @@
tosca_definitions_version: tosca_simple_yaml_1_0
description: TOSCA template to test get_* functions semantic
node_types:
tosca.capabilities.MyFeature:
derived_from: tosca.capabilities.Root
properties:
my_list:
type: list
my_map:
type: map
tosca.nodes.WebApplication.MyApp:
derived_from: tosca.nodes.WebApplication
requirements:
- myfeature:
capability: tosca.capabilities.MyFeature
node: tosca.nodes.MyDatabase
relationship: tosca.relationships.ConnectsTo
tosca.nodes.MyDatabase:
derived_from: tosca.nodes.Database
capabilities:
myfeature:
type: tosca.capabilities.MyFeature
topology_template:
inputs:
map_val:
type: string
node_templates:
server:
type: tosca.nodes.Compute
capabilities:
host:
properties:
num_cpus: 1
mem_size: 1 GB
os:
properties:
type: Linux
distribution: Ubuntu
version: 12.04
architecture: x86_64
mysql_database:
type: tosca.nodes.MyDatabase
requirements:
- host: server
capabilities:
myfeature:
properties:
my_list: [list_val_0]
my_map:
test_key: {get_input: map_val}
test_key_static: static_value
myapp:
type: tosca.nodes.WebApplication.MyApp
requirements:
- myfeature: mysql_database
- host: server
interfaces:
Standard:
configure:
implementation: myapp_configure.sh
inputs:
the_list_val: { get_property: [ SELF, myfeature, my_list, 0 ] }
outputs:
map_val:
value: { get_property: [ myapp, myfeature, my_map, test_key ] }
static_map_val:
value: { get_property: [ myapp, myfeature, my_map, test_key_static ] }

View File

@ -477,3 +477,9 @@ class ToscaHotTranslationTest(TestCase):
hot_file = '../tests/data/hot_output/hot_interface_on_compute.yaml'
params = {}
self._test_successful_translation(tosca_file, hot_file, params)
def test_hot_get_functions_semantic(self):
tosca_file = '../tests/data/test_tosca_get_functions_semantic.yaml'
hot_file = '../tests/data/hot_output/hot_get_functions_semantic.yaml'
params = {}
self._test_successful_translation(tosca_file, hot_file, params)