diff --git a/translator/hot/__init__.py b/translator/hot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/translator/hot/tosca_translator.py b/translator/hot/tosca_translator.py new file mode 100644 index 0000000..8e0a3be --- /dev/null +++ b/translator/hot/tosca_translator.py @@ -0,0 +1,61 @@ +# +# 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_template import HotTemplate +from translator.hot.translate_inputs import TranslateInputs +from translator.hot.translate_node_templates import TranslateNodeTemplates +from translator.hot.translate_outputs import TranslateOutputs + + +class TOSCATranslator(object): + '''Invokes translation methods.''' + + def __init__(self, tosca, parsed_params): + super(TOSCATranslator, self).__init__() + self.tosca = tosca + self.hot_template = HotTemplate() + self.parsed_params = parsed_params + + def translate(self): + self._resolve_input() + self.hot_template.description = self.tosca.description + self.hot_template.parameters = self._translate_inputs() + self.hot_template.resources = self._translate_node_templates() + self.hot_template.outputs = self._translate_outputs() + return self.hot_template.output_to_yaml() + + def _translate_inputs(self): + translator = TranslateInputs(self.tosca.inputs, self.parsed_params) + return translator.translate() + + def _translate_node_templates(self): + translator = TranslateNodeTemplates(self.tosca.nodetemplates, + self.hot_template) + return translator.translate() + + def _translate_outputs(self): + translator = TranslateOutputs(self.tosca.outputs) + return translator.translate() + + # check all properties for all node and ensure they are resolved + # to actual value + def _resolve_input(self): + for n in self.tosca.nodetemplates: + for node_prop in n.properties: + if isinstance(node_prop.value, dict): + try: + self.parsed_params[node_prop.value['get_input']] + except Exception: + raise ValueError('Must specify all input values in \ + TOSCA template, missing %s' % + node_prop.value['get_input']) diff --git a/translator/hot/translate_inputs.py b/translator/hot/translate_inputs.py new file mode 100644 index 0000000..b4b8b2c --- /dev/null +++ b/translator/hot/translate_inputs.py @@ -0,0 +1,110 @@ +# +# 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_parameter import HotParameter + + +INPUT_CONSTRAINTS = (CONSTRAINTS, DESCRIPTION, LENGTH, RANGE, + MIN, MAX, ALLOWED_VALUES, ALLOWED_PATTERN) = \ + ('constraints', 'description', 'length', 'range', + 'min', 'max', 'allowed_values', 'allowed_pattern') + +TOSCA_CONSTRAINT_OPERATORS = (EQUAL, GREATER_THAN, GREATER_OR_EQUAL, LESS_THAN, + LESS_OR_EQUAL, IN_RANGE, VALID_VALUES, LENGTH, + MIN_LENGTH, MAX_LENGTH, PATTERN) = \ + ('equal', 'greater_than', 'greater_or_equal', + 'less_than', 'less_or_equal', 'in_range', + 'valid_values', 'length', 'min_length', + 'max_length', 'pattern') + +TOSCA_TO_HOT_CONSTRAINTS_ATTRS = {'equal': 'allowed_values', + 'greater_than': 'range', + 'greater_or_equal': 'range', + 'less_than': 'range', + 'less_or_equal': 'range', + 'in_range': 'range', + 'valid_values': 'allowed_values', + 'length': 'length', + 'min_length': 'length', + 'max_length': 'length', + 'pattern': 'allowed_pattern'} + +TOSCA_TO_HOT_INPUT_TYPES = {'string': 'string', + 'integer': 'number', + 'float': 'number', + 'boolean': 'boolean', + 'timestamp': 'string', + 'null': 'string'} + + +class TranslateInputs(): + '''Translate TOSCA Inputs to Heat Parameters.''' + + def __init__(self, inputs, parsed_params): + self.inputs = inputs + self.parsed_params = parsed_params + + def translate(self): + return self._translate_inputs() + + def _translate_inputs(self): + hot_inputs = [] + for input in self.inputs: + hot_input_type = TOSCA_TO_HOT_INPUT_TYPES[input.type] + + hot_constraints = [] + if input.constraints: + for constraint in input.constraints: + constraint_name, value = constraint.iteritems().next() + hc, hvalue = self._translate_constraints(constraint_name, + value) + hot_constraints.append({hc: hvalue}) + cli_value = self.parsed_params[input.name] + hot_inputs.append(HotParameter(name=input.name, + type=hot_input_type, + description=input.description, + default=cli_value, + constraints=hot_constraints)) + return hot_inputs + + def _translate_constraints(self, name, value): + hot_constraint = TOSCA_TO_HOT_CONSTRAINTS_ATTRS[name] + + # Offset used to support less_than and greater_than. + # TODO(anyone): when parser supports float, verify this works + offset = 1 + + if name == EQUAL: + hot_value = [value] + elif name == GREATER_THAN: + hot_value = {"min": value + offset} + elif name == GREATER_OR_EQUAL: + hot_value = {"min": value} + elif name == LESS_THAN: + hot_value = {"max": value - offset} + elif name == LESS_OR_EQUAL: + hot_value = {"max": value} + elif name == IN_RANGE: + range_values = value.keys() + min_value = min(range_values) + max_value = max(range_values) + hot_value = {"min": min_value, "max": max_value} + elif name == LENGTH: + hot_value = {"min": value, "max": value} + elif name == MIN_LENGTH: + hot_value = {"min": value} + elif name == MAX_LENGTH: + hot_value = {"max": value} + else: + hot_value = value + return hot_constraint, hot_value diff --git a/translator/hot/translate_node_templates.py b/translator/hot/translate_node_templates.py new file mode 100644 index 0000000..c52cec2 --- /dev/null +++ b/translator/hot/translate_node_templates.py @@ -0,0 +1,100 @@ +# +# 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.tosca.tosca_compute import ToscaCompute +from translator.hot.tosca.tosca_database import ToscaDatabase +from translator.hot.tosca.tosca_dbms import ToscaDbms +from translator.hot.tosca.tosca_webserver import ToscaWebserver +from translator.hot.tosca.tosca_wordpress import ToscaWordpress + +SECTIONS = (TYPE, PROPERTIES, REQUIREMENTS, INTERFACES, LIFECYCLE, INPUT) = \ + ('type', 'properties', 'requirements', + 'interfaces', 'lifecycle', 'input') + +# TODO(anyone): the following requirement names should not be hard-coded +# in the translator. Since they are basically arbitrary names, we have to get +# them from TOSCA type definitions. +# To be fixed with the blueprint: +# https://blueprints.launchpad.net/heat-translator/+spec/tosca-custom-types +REQUIRES = (CONTAINER, DEPENDENCY, DATABASE_ENDPOINT, CONNECTION, HOST) = \ + ('container', 'dependency', 'database_endpoint', + 'connection', 'host') + +INTERFACES_STATE = (CREATE, START, CONFIGURE, START, DELETE) = \ + ('create', 'stop', 'configure', 'start', 'delete') + +# dict to look up HOT translation class, +# TODO(replace with function to scan the classes in translator.hot.tosca) +TOSCA_TO_HOT_TYPE = {'tosca.nodes.Compute': ToscaCompute, + 'tosca.nodes.WebServer': ToscaWebserver, + 'tosca.nodes.DBMS': ToscaDbms, + 'tosca.nodes.Database': ToscaDatabase, + 'tosca.nodes.WebApplication.WordPress': ToscaWordpress} + +TOSCA_TO_HOT_REQUIRES = {'container': 'server', 'host': 'server', + 'dependency': 'depends_on', "connects": 'depends_on'} + +TOSCA_TO_HOT_PROPERTIES = {'properties': 'input'} + + +class TranslateNodeTemplates(): + '''Translate TOSCA NodeTemplates to Heat Resources.''' + + def __init__(self, nodetemplates, hot_template): + self.nodetemplates = nodetemplates + self.hot_template = hot_template + + def translate(self): + return self._translate_nodetemplates() + + def _translate_nodetemplates(self): + hot_resources = [] + hot_lookup = {} + + # Copy the TOSCA graph: nodetemplate + for node in self.nodetemplates: + hot_node = TOSCA_TO_HOT_TYPE[node.type](node) + hot_resources.append(hot_node) + hot_lookup[node] = hot_node + + # Handle life cycle operations: this may expand each node into + # multiple HOT resources and may change their name + lifecycle_resources = [] + for resource in hot_resources: + expanded = resource.handle_life_cycle() + lifecycle_resources += expanded + hot_resources += lifecycle_resources + + # Copy the initial dependencies based on the relationship in + # the TOSCA template + for node in self.nodetemplates: + for node_depend in node.related_nodes: + # if the source of dependency is a server, add dependency + # as properties.get_resource + if node_depend.type == 'tosca.nodes.Compute': + hot_lookup[node].properties['server'] = \ + {'get_resource': hot_lookup[node_depend].name} + # for all others, add dependency as depends_on + else: + hot_lookup[node].depends_on.append(hot_lookup[node_depend]. + top_of_chain()) + + # handle hosting relationship + for resource in hot_resources: + resource.handle_hosting() + + # Handle properties + for resource in hot_resources: + resource.handle_properties() + + return hot_resources diff --git a/translator/hot/translate_outputs.py b/translator/hot/translate_outputs.py new file mode 100644 index 0000000..44d9a06 --- /dev/null +++ b/translator/hot/translate_outputs.py @@ -0,0 +1,45 @@ +# +# 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_output import HotOutput + +TOSCA_TO_HOT_GET_ATTRS = {'ip_address': 'first_address'} + + +class TranslateOutputs(): + '''Translate TOSCA Outputs to Heat Outputs.''' + + def __init__(self, outputs): + self.outputs = outputs + + def translate(self): + return self._translate_outputs() + + def _translate_outputs(self): + hot_outputs = [] + for output in self.outputs: + hot_value = {} + if 'get_property' in output.value: + get_parameters = output.value['get_property'] + if get_parameters[1] in TOSCA_TO_HOT_GET_ATTRS: + get_parameters[1] = \ + TOSCA_TO_HOT_GET_ATTRS[get_parameters[1]] + hot_value['get_attr'] = 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