From 89c397901c2b88492e28b8ba4effacfadd138e09 Mon Sep 17 00:00:00 2001 From: tonanhngo <ton@us.ibm.com> Date: Tue, 27 May 2014 23:21:27 -0700 Subject: [PATCH] TOSCA generator template syntax A set of classes to hold the required data structure for a Heat template. The generator uses these classes to construct the HOT graph, then print the yaml text Partially implements blueprint heat-translator-tosca Change-Id: If9def2d90483d84824e405ed69b61c9fb757d8ac --- translator/hot/syntax/__init__.py | 0 translator/hot/syntax/hot_output.py | 25 ++++ translator/hot/syntax/hot_parameter.py | 44 +++++++ translator/hot/syntax/hot_resource.py | 166 +++++++++++++++++++++++++ translator/hot/syntax/hot_template.py | 67 ++++++++++ 5 files changed, 302 insertions(+) create mode 100644 translator/hot/syntax/__init__.py create mode 100644 translator/hot/syntax/hot_output.py create mode 100644 translator/hot/syntax/hot_parameter.py create mode 100644 translator/hot/syntax/hot_resource.py create mode 100644 translator/hot/syntax/hot_template.py diff --git a/translator/hot/syntax/__init__.py b/translator/hot/syntax/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/translator/hot/syntax/hot_output.py b/translator/hot/syntax/hot_output.py new file mode 100644 index 00000000..ad77fb39 --- /dev/null +++ b/translator/hot/syntax/hot_output.py @@ -0,0 +1,25 @@ +# +# 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. + + +class HotOutput(object): + '''Attributes for HOT output section.''' + + def __init__(self, name, value, description=None): + self.name = name + self.value = value + self.description = description + + def get_dict_output(self): + return {self.name: {'value': self.value, + 'description': self.description}} diff --git a/translator/hot/syntax/hot_parameter.py b/translator/hot/syntax/hot_parameter.py new file mode 100644 index 00000000..3521ef24 --- /dev/null +++ b/translator/hot/syntax/hot_parameter.py @@ -0,0 +1,44 @@ +# +# 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. + +KEYS = (TYPE, DESCRIPTION, DEFAULT, CONSTRAINTS, HIDDEN, LABEL) = \ + ('type', 'description', 'default', 'constraints', 'hidden', 'label') + + +class HotParameter(object): + '''Attributes for HOT parameter section.''' + + def __init__(self, name, type, label=None, description=None, default=None, + hidden=None, constraints=None): + self.name = name + self.type = type + self.label = label + self.description = description + self.default = default + self.hidden = hidden + self.constraints = constraints + + def get_dict_output(self): + param_sections = {TYPE: self.type} + if self.label: + param_sections[LABEL] = self.label + if self.description: + param_sections[DESCRIPTION] = self.description + if self.default: + param_sections[DEFAULT] = self.default + if self.hidden: + param_sections[HIDDEN] = self.hidden + if self.constraints: + param_sections[CONSTRAINTS] = self.constraints + + return {self.name: param_sections} diff --git a/translator/hot/syntax/hot_resource.py b/translator/hot/syntax/hot_resource.py new file mode 100644 index 00000000..09269e90 --- /dev/null +++ b/translator/hot/syntax/hot_resource.py @@ -0,0 +1,166 @@ +# +# 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. + +SECTIONS = (TYPE, PROPERTIES, MEDADATA, DEPENDS_ON, UPDATE_POLICY, + DELETION_POLICY) = \ + ('type', 'properties', 'metadata', + 'depends_on', 'update_policy', 'deletion_policy') + + +class HotResource(object): + '''Base class for TOSCA node type translation to Heat resource type.''' + + def __init__(self, nodetemplate, name=None, type=None, properties=None, + metadata=None, depends_on=None, + update_policy=None, deletion_policy=None): + self.nodetemplate = nodetemplate + if name: + self.name = name + else: + self.name = nodetemplate.name + self.type = type + self.properties = properties + # special case for HOT softwareconfig + if type == 'OS::Heat::SoftwareConfig': + self.properties['group'] = 'script' + self.metadata = metadata + if depends_on: + self.depends_on = depends_on + else: + self.depends_on = [] + self.update_policy = update_policy + self.deletion_policy = deletion_policy + self.group_dependencies = {} + + def handle_properties(self): + # the property can hold a value or the intrinsic function get_input + # for value, copy it + # for get_input, convert to get_param + for prop in self.nodetemplate.properties: + pass + + def handle_life_cycle(self): + hot_resources = [] + deploy_lookup = {} + interfaces_deploy_sequence = ['create', 'start', 'configure'] + + # create HotResource for each interface used for deployment: + # create, start, configure + # ignore the other interfaces + # observe the order: create, start, configure + # use the current HotResource for the first interface in this order + + # hold the original name since it will be changed during + # the transformation + node_name = self.name + reserve_current = 'NONE' + interfaces_actual = [] + for interface in self.nodetemplate.interfaces: + interfaces_actual.append(interface.name) + for operation in interfaces_deploy_sequence: + if operation in interfaces_actual: + reserve_current = operation + break + + # create the set of SoftwareDeployment and SoftwareConfig for + # the interface operations + for interface in self.nodetemplate.interfaces: + if interface.name in interfaces_deploy_sequence: + config_name = node_name + '_' + interface.name + '_config' + deploy_name = node_name + '_' + interface.name + '_deploy' + hot_resources.append( + HotResource(self.nodetemplate, + config_name, + 'OS::Heat::SoftwareConfig', + {'config': + {'get_file': interface.implementation}})) + if interface.name == reserve_current: + deploy_resource = self + self.name = deploy_name + self.type = 'OS::Heat::SoftwareDeployment' + self.properties = {'config': {'get_resource': config_name}} + deploy_lookup[interface.name] = self + else: + deploy_resource = \ + HotResource(self.nodetemplate, + deploy_name, + 'OS::Heat::SoftwareDeployment', + {'config': {'get_resource': config_name}}) + hot_resources.append(deploy_resource) + deploy_lookup[interface.name] = deploy_resource + + # Add dependencies for the set of HOT resources in the sequence defined + # in interfaces_deploy_sequence + # TODO(anyone): find some better way to encode this implicit sequence + group = {} + for op, hot in deploy_lookup.iteritems(): + # position to determine potential preceding nodes + op_index = interfaces_deploy_sequence.index(op) + for preceding_op in \ + reversed(interfaces_deploy_sequence[:op_index]): + preceding_hot = deploy_lookup.get(preceding_op) + if preceding_hot: + hot.depends_on.append(preceding_hot) + group[preceding_hot] = hot + break + + # 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 + + def handle_hosting(self): + # 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': + # skip if already have hosting + host_server = self.properties.get('server') + if host_server is None: + host_server = self.bottom_of_chain().\ + properties['server']['get_resource'] + self.properties['server'] = {'get_resource': host_server} + + def top_of_chain(self): + dependent = self.group_dependencies.get(self) + if dependent is None: + return self + else: + return dependent.top_of_chain() + + # TODO(anyone): traverse using the relationship requirement:host in TOSCA + def bottom_of_chain(self): + if len(self.depends_on) == 0: + return self + else: + for preceding in self.depends_on: + return preceding.bottom_of_chain() + + def get_dict_output(self): + resource_sections = {TYPE: self.type} + if self.properties: + resource_sections[PROPERTIES] = self.properties + if self.metadata: + resource_sections[MEDADATA] = self.metadata + if self.depends_on: + resource_sections[DEPENDS_ON] = [] + for depend in self.depends_on: + resource_sections[DEPENDS_ON].append(depend.name) + if self.update_policy: + resource_sections[UPDATE_POLICY] = self.update_policy + if self.deletion_policy: + resource_sections[DELETION_POLICY] = self.deletion_policy + + return {self.name: resource_sections} diff --git a/translator/hot/syntax/hot_template.py b/translator/hot/syntax/hot_template.py new file mode 100644 index 00000000..fc8f00a0 --- /dev/null +++ b/translator/hot/syntax/hot_template.py @@ -0,0 +1,67 @@ +# +# 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. + +import textwrap +import yaml + + +class HotTemplate(object): + '''Container for full Heat Orchestration template.''' + + SECTIONS = (VERSION, DESCRIPTION, PARAMETER_GROUPS, PARAMETERS, + RESOURCES, OUTPUTS, MAPPINGS) = \ + ('heat_template_version', 'description', 'parameter_groups', + 'parameters', 'resources', 'outputs', '__undefined__') + + VERSIONS = (LATEST,) = ('2013-05-23',) + + def __init__(self): + self.resources = [] + self.outputs = [] + self.parameters = [] + self.description = "" + + def output_to_yaml(self): + dict_output = {} + # Version + version_string = self.VERSION + ": " + self.LATEST + "\n\n" + + # Description + desc_str = "" + if self.description: + # Wrap the text to a new line if the line exceeds 80 characters. + wrapped_txt = "\n ".join(textwrap.wrap(self.description, 80)) + desc_str = self.DESCRIPTION + ": >\n " + wrapped_txt + "\n\n" + + # Parameters + all_params = {} + for parameter in self.parameters: + all_params.update(parameter.get_dict_output()) + dict_output.update({self.PARAMETERS: all_params}) + + # Resources + all_resources = {} + for resource in self.resources: + all_resources.update(resource.get_dict_output()) + dict_output.update({self.RESOURCES: all_resources}) + + # Outputs + all_outputs = {} + for output in self.outputs: + all_outputs.update(output.get_dict_output()) + dict_output.update({self.OUTPUTS: all_outputs}) + + yaml_string = yaml.dump(dict_output, default_flow_style=False) + # get rid of the '' from yaml.dump around numbers + yaml_string = yaml_string.replace('\'', '') + return version_string + desc_str + yaml_string