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 os
import re import re
import requests import requests
import six
from six.moves.urllib.parse import urlparse from six.moves.urllib.parse import urlparse
import yaml import yaml
@ -262,12 +263,17 @@ class UrlUtils(object):
def str_to_num(value): def str_to_num(value):
"""Convert a string representation of a number into a numeric type.""" """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 return value
try: try:
return int(value) return int(value)
except ValueError: except ValueError:
return float(value) try:
return float(value)
except ValueError:
return None
def check_for_env_variables(): def check_for_env_variables():

View File

@ -104,7 +104,7 @@ class HotResource(object):
# scenarios and cannot be fixed or hard coded here # scenarios and cannot be fixed or hard coded here
operations_deploy_sequence = ['create', 'configure', 'start'] 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 HotResource for each operation used for deployment:
# create, start, configure # create, start, configure
@ -351,7 +351,7 @@ class HotResource(object):
return tosca_props return tosca_props
@staticmethod @staticmethod
def _get_all_operations(node): def get_all_operations(node):
operations = {} operations = {}
for operation in node.interfaces: for operation in node.interfaces:
operations[operation.name] = operation operations[operation.name] = operation

View File

@ -67,5 +67,5 @@ class ToscaBlockStorage(HotResource):
# attribute for the matching resource. Unless there is additional # attribute for the matching resource. Unless there is additional
# runtime support, this should be a one to one mapping. # runtime support, this should be a one to one mapping.
if attribute == 'volume_id': if attribute == 'volume_id':
attr['get_resource'] = args[0] attr['get_resource'] = self.name
return attr return attr

View File

@ -25,6 +25,7 @@ from toscaparser.utils.gettextutils import _
from translator.common.exception import ToscaClassAttributeError from translator.common.exception import ToscaClassAttributeError
from translator.common.exception import ToscaClassImportError from translator.common.exception import ToscaClassImportError
from translator.common.exception import ToscaModImportError from translator.common.exception import ToscaModImportError
from translator.common import utils
from translator.conf.config import ConfigProvider as translatorConfig from translator.conf.config import ConfigProvider as translatorConfig
from translator.hot.syntax.hot_resource import HotResource from translator.hot.syntax.hot_resource import HotResource
from translator.hot.tosca.tosca_block_storage_attachment import ( from translator.hot.tosca.tosca_block_storage_attachment import (
@ -312,7 +313,7 @@ class TranslateNodeTemplates(object):
inputs = resource.properties.get('input_values') inputs = resource.properties.get('input_values')
if inputs: if inputs:
for name, value in six.iteritems(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 # remove resources without type defined
# for example a SoftwareComponent without interfaces # for example a SoftwareComponent without interfaces
@ -327,49 +328,118 @@ class TranslateNodeTemplates(object):
return self.hot_resources 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 get_property_args = None
if isinstance(input_value, GetProperty): if isinstance(param_value, GetProperty):
get_property_args = input_value.args get_property_args = param_value.args
# to remove when the parser is fixed to return GetProperty # to remove when the parser is fixed to return GetProperty
if isinstance(input_value, dict) and 'get_property' in input_value: if isinstance(param_value, dict) and 'get_property' in param_value:
get_property_args = input_value['get_property'] get_property_args = param_value['get_property']
if get_property_args is not None: if get_property_args is not None:
hot_target = self._find_hot_resource_for_tosca( tosca_target, prop_name, prop_arg = \
get_property_args[0], resource) self.decipher_get_operation(get_property_args,
if hot_target: tosca_template)
props = hot_target.get_tosca_props() if tosca_target:
prop_name = get_property_args[1] prop_value = tosca_target.get_property_value(prop_name)
if prop_name in props: if prop_value:
return props[prop_name] prop_value = self.translate_param_value(
elif isinstance(input_value, GetAttribute): 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 # for the attribute
# get the proper target type to perform the translation # get the proper target type to perform the translation
args = input_value.result().args tosca_target, attr_name, attr_arg = \
hot_target = self._find_hot_resource_for_tosca(args[0], resource) 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) if tosca_target:
# most of artifacts logic should move to the parser artifacts = self.get_all_artifacts(tosca_target)
elif isinstance(input_value, dict) and 'get_artifact' in input_value: if artifact_name in artifacts:
get_artifact_args = input_value['get_artifact'] artifact = artifacts[artifact_name]
if artifact.get('type', None) == 'tosca.artifacts.File':
hot_target = self._find_hot_resource_for_tosca( return {'get_file': artifact.get('file')}
get_artifact_args[0], resource) get_input_args = None
artifacts = TranslateNodeTemplates.get_all_artifacts( if isinstance(param_value, GetInput):
hot_target.nodetemplate) get_input_args = param_value.args
if isinstance(param_value, dict) and 'get_input' in param_value:
if get_artifact_args[1] in artifacts: get_input_args = param_value['get_input']
artifact = artifacts[get_artifact_args[1]] if get_input_args is not None:
if artifact.get('type', None) == 'tosca.artifacts.File': if isinstance(get_input_args, list) \
return {'get_file': artifact.get('file')} and len(get_input_args) == 1:
elif isinstance(input_value, GetInput): return {'get_param': self.translate_param_value(
if isinstance(input_value.args, list) \ get_input_args[0], resource)}
and len(input_value.args) == 1:
return {'get_param': input_value.args[0]}
else: 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 @staticmethod
def get_all_artifacts(nodetemplate): def get_all_artifacts(nodetemplate):
@ -438,23 +508,29 @@ class TranslateNodeTemplates(object):
if resource.name == name: if resource.name == name:
return resource return resource
def _find_tosca_node(self, tosca_name): def _find_tosca_node(self, tosca_name, current_tosca_template=None):
for node in self.nodetemplates: tosca_node = None
if node.name == tosca_name: if tosca_name == 'SELF':
return node 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, def _find_hot_resource_for_tosca(self, tosca_name,
current_hot_resource=None): current_hot_resource=None):
if tosca_name == 'SELF': current_tosca_resource = current_hot_resource.nodetemplate \
return current_hot_resource if current_hot_resource else None
if tosca_name == 'HOST' and current_hot_resource is not None: tosca_node = self._find_tosca_node(tosca_name, current_tosca_resource)
for req in current_hot_resource.nodetemplate.requirements: if tosca_node:
if 'host' in req: return self.hot_lookup[tosca_node]
return self._find_hot_resource_for_tosca(req['host'])
for node in self.nodetemplates:
if node.name == tosca_name:
return self.hot_lookup[node]
return None return None

View File

@ -33,16 +33,7 @@ class TranslateOutputs(object):
def _translate_outputs(self): def _translate_outputs(self):
hot_outputs = [] hot_outputs = []
for output in self.outputs: for output in self.outputs:
if output.value.name == 'get_attribute': hot_value = self.nodes.translate_param_value(output.value, None)
get_parameters = output.value.args hot_outputs.append(HotOutput(output.name, hot_value,
hot_target = self.nodes.find_hot_resource(get_parameters[0]) output.description))
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))
return hot_outputs 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' hot_file = '../tests/data/hot_output/hot_interface_on_compute.yaml'
params = {} params = {}
self._test_successful_translation(tosca_file, hot_file, 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)