diff options
58 files changed, 1292 insertions, 139 deletions
diff --git a/tosca2heat/heat-translator/.python-version b/tosca2heat/heat-translator/.python-version new file mode 100644 index 0000000..ecc17b8 --- /dev/null +++ b/tosca2heat/heat-translator/.python-version @@ -0,0 +1 @@ +2.7.13 diff --git a/tosca2heat/heat-translator/CONTRIBUTING.rst b/tosca2heat/heat-translator/CONTRIBUTING.rst new file mode 100644 index 0000000..e12c422 --- /dev/null +++ b/tosca2heat/heat-translator/CONTRIBUTING.rst @@ -0,0 +1,16 @@ +If you would like to contribute to the development of OpenStack, +you must follow the steps in this page: + + http://docs.openstack.org/infra/manual/developers.html + +Once those steps have been completed, changes to OpenStack +should be submitted for review via the Gerrit tool, following +the workflow documented at: + + http://docs.openstack.org/infra/manual/developers.html#development-workflow + +Pull requests submitted through GitHub will be ignored. + +Bugs should be filed on Launchpad, not GitHub: + + https://bugs.launchpad.net/heat-translator
\ No newline at end of file diff --git a/tosca2heat/heat-translator/HACKING.rst b/tosca2heat/heat-translator/HACKING.rst new file mode 100644 index 0000000..9c60464 --- /dev/null +++ b/tosca2heat/heat-translator/HACKING.rst @@ -0,0 +1,4 @@ +heat-translator Style Commandments +=============================================== + +Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
\ No newline at end of file diff --git a/tosca2heat/heat-translator/MANIFEST.in b/tosca2heat/heat-translator/MANIFEST.in deleted file mode 100644 index 1fc29b3..0000000 --- a/tosca2heat/heat-translator/MANIFEST.in +++ /dev/null @@ -1,14 +0,0 @@ -include AUTHORS -include ChangeLog -global-include *.py -global-include *.yaml -global-include *.sh -global-include *.txt -global-include *.csar -global-include *.zip -global-include *.meta -global-include *.conf -exclude .gitignore -exclude .gitreview - -global-exclude *.pyc diff --git a/tosca2heat/heat-translator/README.md b/tosca2heat/heat-translator/README.rst index c8af42a..c8af42a 100644 --- a/tosca2heat/heat-translator/README.md +++ b/tosca2heat/heat-translator/README.rst diff --git a/tosca2heat/heat-translator/openstack-common.conf b/tosca2heat/heat-translator/openstack-common.conf deleted file mode 100644 index d359e40..0000000 --- a/tosca2heat/heat-translator/openstack-common.conf +++ /dev/null @@ -1,6 +0,0 @@ -[DEFAULT] - -# The list of modules to copy from oslo-incubator.git - -# The base module to hold the copy of openstack.common -base=translator
\ No newline at end of file diff --git a/tosca2heat/heat-translator/requirements.txt b/tosca2heat/heat-translator/requirements.txt index b211383..2de8022 100644 --- a/tosca2heat/heat-translator/requirements.txt +++ b/tosca2heat/heat-translator/requirements.txt @@ -1,15 +1,15 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr>=1.8 # Apache-2.0 -Babel>=2.3.4 # BSD -cliff>=2.3.0 # Apache-2.0 +pbr!=2.1.0,>=2.0.0 # Apache-2.0 +Babel!=2.4.0,>=2.3.4 # BSD +cliff>=2.6.0 # Apache-2.0 PyYAML>=3.10.0 # MIT python-dateutil>=2.4.2 # BSD six>=1.9.0 # MIT -tosca-parser>=0.7.0 # Apache-2.0 -keystoneauth1>=2.18.0 # Apache-2.0 -python-novaclient>=7.1.0 # Apache-2.0 +tosca-parser>=0.8.1 # Apache-2.0 +keystoneauth1>=2.21.0 # Apache-2.0 +python-novaclient>=9.0.0 # Apache-2.0 python-heatclient>=1.6.1 # Apache-2.0 -python-glanceclient>=2.5.0 # Apache-2.0 -requests!=2.12.2,!=2.13.0,>=2.10.0 # Apache-2.0 +python-glanceclient>=2.7.0 # Apache-2.0 +requests>=2.14.2 # Apache-2.0 diff --git a/tosca2heat/heat-translator/setup.cfg b/tosca2heat/heat-translator/setup.cfg index 38bc1b9..21d0c6f 100644 --- a/tosca2heat/heat-translator/setup.cfg +++ b/tosca2heat/heat-translator/setup.cfg @@ -2,7 +2,7 @@ name = heat-translator summary = Tool to translate non-heat templates to Heat Orchestration Template. description-file = - README.md + README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = http://docs.openstack.org/developer/heat-translator/ @@ -35,6 +35,14 @@ openstack.translator.v1 = console_scripts = heat-translator = translator.shell:main +[build_sphinx] +source-dir = doc/source +build-dir = doc/build +all_files = 1 + +[upload_sphinx] +upload-dir = doc/build/html + [compile_catalog] directory = translator/locale domain = translator diff --git a/tosca2heat/heat-translator/setup.py b/tosca2heat/heat-translator/setup.py index 782bb21..566d844 100644 --- a/tosca2heat/heat-translator/setup.py +++ b/tosca2heat/heat-translator/setup.py @@ -25,5 +25,5 @@ except ImportError: pass setuptools.setup( - setup_requires=['pbr>=1.8'], + setup_requires=['pbr>=2.0.0'], pbr=True) diff --git a/tosca2heat/heat-translator/test-requirements.txt b/tosca2heat/heat-translator/test-requirements.txt index d4b59c3..da69d4f 100644 --- a/tosca2heat/heat-translator/test-requirements.txt +++ b/tosca2heat/heat-translator/test-requirements.txt @@ -1,13 +1,13 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -hacking<0.11,>=0.10.0 -coverage>=4.0 # Apache-2.0 +hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 +coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD oslotest>=1.10.0 # Apache-2.0 oslosphinx>=4.7.0 # Apache-2.0 python-subunit>=0.0.18 # Apache-2.0/BSD -sphinx>=1.5.1 # BSD +sphinx>=1.6.2 # BSD testrepository>=0.0.18 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD testtools>=1.4.0 # MIT diff --git a/tosca2heat/heat-translator/translator/common/images.py b/tosca2heat/heat-translator/translator/common/images.py index f9fa4f1..d9b8818 100644 --- a/tosca2heat/heat-translator/translator/common/images.py +++ b/tosca2heat/heat-translator/translator/common/images.py @@ -1,3 +1,4 @@ + # 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 @@ -24,37 +25,46 @@ log = logging.getLogger('heat-translator') PREDEF_IMAGES = { 'ubuntu-software-config-os-init': {'architecture': 'x86_64', - 'type': 'Linux', - 'distribution': 'Ubuntu', - 'version': '14.04'}, + 'os_type': 'linux', + 'os_distro': 'ubuntu', + 'os_version': '14.04' + }, + 'ubuntu-12.04-software-config-os-init': {'architecture': 'x86_64', - 'type': 'Linux', - 'distribution': 'Ubuntu', - 'version': '12.04'}, + 'os_type': 'linux', + 'os_distro': 'ubuntu', + 'os_version': '12.04' + }, 'fedora-amd64-heat-config': {'architecture': 'x86_64', - 'type': 'Linux', - 'distribution': 'Fedora', - 'version': '18.0'}, + 'os_type': 'linux', + 'os_distro': 'fedora', + 'os_version': '18.0' + }, 'F18-x86_64-cfntools': {'architecture': 'x86_64', - 'type': 'Linux', - 'distribution': 'Fedora', - 'version': '19'}, + 'os_type': 'linux', + 'os_distro': 'fedora', + 'os_version': '19' + }, 'Fedora-x86_64-20-20131211.1-sda': {'architecture': 'x86_64', - 'type': 'Linux', - 'distribution': 'Fedora', - 'version': '20'}, + 'os_type': 'linux', + 'os_distro': 'fedora', + 'os_version': '20' + }, 'cirros-0.3.1-x86_64-uec': {'architecture': 'x86_64', - 'type': 'Linux', - 'distribution': 'CirrOS', - 'version': '0.3.1'}, + 'os_type': 'linux', + 'os_distro': 'cirros', + 'os_version': '0.3.1' + }, 'cirros-0.3.2-x86_64-uec': {'architecture': 'x86_64', - 'type': 'Linux', - 'distribution': 'CirrOS', - 'version': '0.3.2'}, + 'os_type': 'linux', + 'os_distro': 'cirros', + 'os_version': '0.3.2' + }, 'rhel-6.5-test-image': {'architecture': 'x86_64', - 'type': 'Linux', - 'distribution': 'RHEL', - 'version': '6.5'} + 'os_type': 'linux', + 'os_distro': 'rhel', + 'os_version': '6.5' + } } SESSION = None @@ -78,7 +88,8 @@ def get_images(): else: for image in client.images.list(): image_name = image.name.encode('ascii', 'ignore') - metadata = ["architecture", "type", "distribution", "version"] + metadata = ["architecture", "type", "distribution", "version", + "os_distro", "os_type", "os_version"] if any(key in image.keys() for key in metadata): IMAGES[image_name] = {} for key in metadata: diff --git a/tosca2heat/heat-translator/translator/common/utils.py b/tosca2heat/heat-translator/translator/common/utils.py index 874c8ec..85af60a 100644 --- a/tosca2heat/heat-translator/translator/common/utils.py +++ b/tosca2heat/heat-translator/translator/common/utils.py @@ -216,7 +216,8 @@ class YamlUtils(object): class TranslationUtils(object): @staticmethod - def compare_tosca_translation_with_hot(tosca_file, hot_files, params): + def compare_tosca_translation_with_hot(tosca_file, hot_files, params, + nested_resources=False): '''Verify tosca translation against the given hot specification. inputs: @@ -247,6 +248,12 @@ class TranslationUtils(object): basename = os.path.basename(hot_files[0]) output_hot_templates = translate.translate_to_yaml_files_dict(basename) + + if nested_resources: + basename = os.path.basename(hot_files[0]) + output_hot_templates =\ + translate.translate_to_yaml_files_dict(basename, True) + output_dict = {} for output_hot_template_name in output_hot_templates: output_dict[output_hot_template_name] = \ diff --git a/tosca2heat/heat-translator/translator/hot/syntax/hot_resource.py b/tosca2heat/heat-translator/translator/hot/syntax/hot_resource.py index 80a62ff..ff2111a 100644 --- a/tosca2heat/heat-translator/translator/hot/syntax/hot_resource.py +++ b/tosca2heat/heat-translator/translator/hot/syntax/hot_resource.py @@ -30,7 +30,8 @@ policy_type = ['tosca.policies.Placement', 'tosca.policies.Scaling', 'tosca.policies.Scaling.Cluster', 'tosca.policies.Placement.Colocate', - 'tosca.policies.Placement.Antilocate'] + 'tosca.policies.Placement.Antilocate', + 'tosca.policies.Monitoring'] log = logging.getLogger('heat-translator') diff --git a/tosca2heat/heat-translator/translator/hot/syntax/hot_template.py b/tosca2heat/heat-translator/translator/hot/syntax/hot_template.py index 7fae022..f279997 100644 --- a/tosca2heat/heat-translator/translator/hot/syntax/hot_template.py +++ b/tosca2heat/heat-translator/translator/hot/syntax/hot_template.py @@ -46,7 +46,8 @@ class HotTemplate(object): return yaml.nodes.MappingNode(u'tag:yaml.org,2002:map', nodes) def output_to_yaml_files_dict(self, base_filename, - hot_template_version=LATEST): + hot_template_version=LATEST, + embed_substack_templates=False): yaml_files_dict = {} base_filename, ext = os.path.splitext(base_filename) @@ -55,9 +56,9 @@ class HotTemplate(object): yaml_files_dict.update( resource.extract_substack_templates(base_filename, hot_template_version)) - - yaml_files_dict[base_filename + ext] = \ - self.output_to_yaml(hot_template_version, False) + if not embed_substack_templates: + yaml_files_dict[base_filename + ext] = \ + self.output_to_yaml(hot_template_version, False) return yaml_files_dict diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_autoscaling.py b/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_autoscaling.py index 978e965..f093c2e 100644 --- a/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_autoscaling.py +++ b/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_autoscaling.py @@ -79,11 +79,13 @@ class AutoscalingTest(TestCase): max_instances: 10 default_instances: 3 increment: 1 + cooldown: 60 ''' expectedprops = {'desired_capacity': 3, 'max_size': 10, 'min_size': 2, + 'cooldown': 60, 'resource': {'type': 'asg_res.yaml'}} self._tosca_scaling_test( diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_floatingip.py b/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_floatingip.py new file mode 100644 index 0000000..445390d --- /dev/null +++ b/tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_floatingip.py @@ -0,0 +1,71 @@ +# 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 toscaparser.nodetemplate import NodeTemplate +from toscaparser.tests.base import TestCase +import toscaparser.utils.yamlparser +from translator.hot.tosca.tosca_floating import ToscaFloatingIP + + +class ToscaFloatingIPTest(TestCase): + + def _tosca_floatingip_test(self, tpl_snippet, expectedprops, name=None): + nodetemplates = (toscaparser.utils.yamlparser. + simple_parse(tpl_snippet)['node_templates']) + if not name: + name = list(nodetemplates.keys())[0] + nodetemplate = NodeTemplate(name, nodetemplates, custom_def=[]) + nodetemplate.validate() + tosca_floatingip = ToscaFloatingIP(nodetemplate) + tosca_floatingip.handle_properties() + self.assertEqual(expectedprops, tosca_floatingip.properties) + + def test_node_floatingip_with_properties(self): + tpl_snippet = ''' + node_templates: + floating_ip: + type: tosca.nodes.network.FloatingIP + properties: + floating_network: public + floating_ip_address: 192.168.56.8 + port_id: abcd + ''' + expectedprops = {'floating_network': 'public', + 'floating_ip_address': '192.168.56.8', + 'port_id': 'abcd'} + self._tosca_floatingip_test( + tpl_snippet, + expectedprops) + + def test_node_floatingip_with_properties_and_link_requirements(self): + tpl_snippet = ''' + node_templates: + floating_ip: + type: tosca.nodes.network.FloatingIP + properties: + floating_network: public + floating_ip_address: 192.168.56.8 + requirements: + - link: + node: port1 + port1: + type: tosca.nodes.network.Port + properties: + ip_address: 10.0.0.6 + ''' + expectedprops = {'floating_network': 'public', + 'floating_ip_address': '192.168.56.8', + 'port_id': '{ get_resource: port1 }'} + self._tosca_floatingip_test( + tpl_snippet, + expectedprops, + name='floating_ip') diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_cluster_policies_scaling.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_cluster_policies_scaling.py new file mode 100644 index 0000000..1de0158 --- /dev/null +++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_cluster_policies_scaling.py @@ -0,0 +1,195 @@ +# +# 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 collections import defaultdict + +from translator.hot.syntax.hot_resource import HotResource +# Name used to dynamically load appropriate map class. +TARGET_CLASS_NAME = 'ToscaClusterAutoscaling' + +SCALE_POLICY = 'senlin.policy.scaling-1.0' +SERVER_TYPE = 'os.nova.server-1.0' +SCALE_TYPE = {'SCALE_IN': 'CLUSTER_SCALE_IN', + 'SCALE_OUT': 'CLUSTER_SCALE_OUT'} + +ALARM_METER_NAME = {'utilization': 'cpu_util'} +ALARM_COMPARISON_OPERATOR = {'greater_than': 'gt', 'gerater_equal': 'ge', + 'less_than': 'lt', 'less_equal': 'le', + 'equal': 'eq', 'not_equal': 'ne'} +ALARM_STATISTIC = {'average': 'avg'} + + +class ToscaClusterAutoscaling(HotResource): + '''Translate TOSCA node type tosca.policies.Scaling.Cluster''' + + toscatype = 'tosca.policies.Scaling.Cluster' + + def __init__(self, policy, csar_dir=None): + hot_type = "OS::Senlin::Policy" + super(ToscaClusterAutoscaling, self).__init__(policy, + type=hot_type, + csar_dir=csar_dir) + self.policy = policy + + def _generate_scale_properties(self, + target_cluster_nodes, + cluster_scale_type): + properties = {} + bindings = [] + policy_res = {} + adjustment = {} + properties["type"] = SCALE_POLICY + for cluster_node in target_cluster_nodes: + bindings.append({'cluster': cluster_node}) + properties["bindings"] = bindings + policy_res["event"] = cluster_scale_type + adjustment["type"] = "CHANGE_IN_CAPACITY" + adjustment["number"] = self.\ + policy.entity_tpl["properties"]["increment"] + policy_res["adjustment"] = adjustment + properties["properties"] = policy_res + return properties + + def handle_expansion(self): + hot_resources = [] + trigger_receivers = defaultdict(list) + for node in self.policy.targets: + for trigger in self.policy.entity_tpl['triggers']: + for action in self.policy.\ + entity_tpl['triggers'][trigger]['action']: + scale_name = action + action_sample = self.policy.\ + entity_tpl['triggers'][trigger]['action'][action] + scale_type = action_sample['type'] + scale_implement = action_sample['implementation'] + (entity, method) = scale_implement.split('.') + receiver_prop = {} + receiver_prop['cluster'] = { + "get_resource": "%s_cluster" % node + } + receiver_prop['action'] = SCALE_TYPE[scale_type] + receiver_prop['type'] = method + receiver_name = node + '_' + scale_name + '_receiver' + trigger_receivers[trigger].append(receiver_name) + receiver_resources = HotResource(self.nodetemplate, + type='OS::Senlin::Receiver', + name=receiver_name, + properties=receiver_prop) + hot_resources.append(receiver_resources) + + for trigger in self.policy.entity_tpl['triggers']: + sample = self.policy.\ + entity_tpl['triggers'][trigger]['condition'] + (meter_name, comparison_operator, threshold) = \ + sample["constraint"].split() + threshold = threshold.strip("%") + alarm_prop = {} + alarm_prop["description"] = self.policy.entity_tpl['description'] + alarm_prop["meter_name"] = self.policy.\ + entity_tpl['triggers'][trigger]['event_type']['metrics'] + alarm_prop["statistic"] = ALARM_STATISTIC[sample['method']] + alarm_prop["period"] = sample["period"] + alarm_prop["evaluation_periods"] = sample["evaluations"] + alarm_prop["threshold"] = threshold + alarm_prop["comparison_operator"] = \ + ALARM_COMPARISON_OPERATOR[comparison_operator] + alarm_prop["repeat_actions"] = "True" + alarm_prop["alarm_actions"] = [] + for index in range(len(trigger_receivers[trigger])): + alarm_prop["alarm_actions"].\ + append({'get_attr': [trigger_receivers[trigger][index], + 'channel', + 'alarm_url']}) + ceilometer_resources = HotResource(self.nodetemplate, + type='OS::Aodh::Alarm', + name=trigger + '_alarm', + properties=alarm_prop) + hot_resources.append(ceilometer_resources) + return hot_resources + + def handle_properties(self, resources): + remove_resources = [] + networks = defaultdict(list) + for index, resource in enumerate(resources): + if resource.type == 'OS::Neutron::Port': + for hot_resource in resource.depends_on_nodes: + if hot_resource.type != 'OS::Neutron::Net': + networks[hot_resource.name].\ + append( + {'network': '%s' % resource.properties['network']} + ) + remove_resources.append(resource) + elif resource.type == 'OS::Neutron::Net': + remove_resources.append(resource) + elif resource.name in self.policy.targets and \ + resource.type != 'OS::Senlin::Policy': + props = {} + del resource.properties['user_data_format'] + del resource.properties['networks'] + props['type'] = SERVER_TYPE + props['properties'] = resource.properties + profile_resources = \ + HotResource(resource, + type='OS::Senlin::Profile', + name=resource.name, + properties=props) + resources.pop(index) + resources.insert(index, profile_resources) + for remove_resource in remove_resources: + resources.remove(remove_resource) + + for index, resource in enumerate(resources): + if resource.name in self.policy.targets: + resource.properties['properties']['networks'] = \ + networks[resource.name] + + for node in self.policy.targets: + props = {} + props["profile"] = {'get_resource': '%s' % node} + temp = self.policy.entity_tpl["properties"] + props["min_size"] = temp["min_instances"] + props["max_size"] = temp["max_instances"] + props["desired_capacity"] = temp["default_instances"] + self.cluster_name = '%s_cluster' % node + cluster_resources = \ + HotResource(self.nodetemplate, + type='OS::Senlin::Cluster', + name=self.cluster_name, + properties=props) + resources.append(cluster_resources) + + trigger_num = len(self.policy.entity_tpl['triggers']) + for num, trigger in enumerate(self.policy.entity_tpl['triggers']): + target_cluster_nodes = [] + for action in self.policy.\ + entity_tpl['triggers'][trigger]['action']: + scale_type = self.policy.\ + entity_tpl['triggers'][trigger]['action'][action]['type'] + for node in self.policy.targets: + target_cluster_nodes.\ + append({"get_resource": "%s_cluster" % node}) + cluster_scale_type = SCALE_TYPE[scale_type] + scale_in_props = \ + self._generate_scale_properties(target_cluster_nodes, + cluster_scale_type) + if num == trigger_num - 1: + self.name = self.name + '_' + trigger + self.properties = scale_in_props + break + policy_resources = \ + HotResource(self.nodetemplate, + type='OS::Senlin::Policy', + name=self.name + '_' + trigger, + properties=scale_in_props) + resources.append(policy_resources) + return resources diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_compute.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_compute.py index 5f6b751..40dc799 100644 --- a/tosca2heat/heat-translator/translator/hot/tosca/tosca_compute.py +++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_compute.py @@ -28,13 +28,16 @@ TARGET_CLASS_NAME = 'ToscaCompute' class ToscaCompute(HotResource): - '''Translate TOSCA node type tosca.nodes.Compute.''' + """Translate TOSCA node type tosca.nodes.Compute.""" COMPUTE_HOST_PROP = (DISK_SIZE, MEM_SIZE, NUM_CPUS) = \ ('disk_size', 'mem_size', 'num_cpus') COMPUTE_OS_PROP = (ARCHITECTURE, DISTRIBUTION, TYPE, VERSION) = \ ('architecture', 'distribution', 'type', 'version') + + IMAGE_OS_PROP = (OS_DISTRO, OS_TYPE, OS_VERSION) = \ + ('os_distro', 'os_type', 'os_version') toscatype = 'tosca.nodes.Compute' ALLOWED_NOVA_SERVER_PROPS = \ @@ -146,28 +149,35 @@ class ToscaCompute(HotResource): if architecture is None: self._log_compute_msg(self.ARCHITECTURE, 'image') match_arch = self._match_images(match_all, images, - self.ARCHITECTURE, architecture) - type = properties.get(self.TYPE) - if type is None: + [self.ARCHITECTURE], architecture) + + image_type = properties.get(self.TYPE) + if image_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, + self.OS_TYPE], + image_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, - self.DISTRIBUTION, + [self.DISTRIBUTION, + self.OS_DISTRO], 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, - self.VERSION, version) + [self.VERSION, self.OS_VERSION], + version) if len(match_version): return list(match_version)[0] - def _match_flavors(self, this_list, this_dict, attr, size): - '''Return from this list all flavors matching the attribute size.''' + @staticmethod + def _match_flavors(this_list, this_dict, attr, size): + """Return from this list all flavors matching the attribute size.""" if not size: return list(this_list) matching_flavors = [] @@ -178,24 +188,27 @@ class ToscaCompute(HotResource): log.debug(_('Returning list of flavors matching the attribute size.')) return matching_flavors - def _least_flavor(self, this_list, this_dict, attr): - '''Return from this list the flavor with the smallest attr.''' + @staticmethod + def _least_flavor(this_list, this_dict, attr): + """Return from this list the flavor with the smallest attr.""" least_flavor = this_list[0] for flavor in this_list: if this_dict[flavor][attr] < this_dict[least_flavor][attr]: least_flavor = flavor return least_flavor - def _match_images(self, this_list, this_dict, attr, prop): + @staticmethod + def _match_images(this_list, this_dict, attr_list, prop): if not prop: return this_list matching_images = [] for image in this_list: - if attr in this_dict[image]: - if this_dict[image][attr].lower() == str(prop).lower(): - matching_images.insert(0, image) - else: - matching_images.append(image) + for attr in attr_list: + if attr in this_dict[image]: + if this_dict[image][attr].lower() == str(prop).lower(): + matching_images.insert(0, image) + else: + matching_images.append(image) return matching_images def get_hot_attribute(self, attribute, args): @@ -215,8 +228,9 @@ class ToscaCompute(HotResource): return attr - def _log_compute_msg(self, prop, what): + @staticmethod + def _log_compute_msg(prop, what): msg = _('No value is provided for Compute capability ' 'property "%(prop)s". This may set an undesired "%(what)s" ' 'in the template.') % {'prop': prop, 'what': what} - log.warn(msg) + log.warning(msg) diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_floating.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_floating.py new file mode 100644 index 0000000..6126653 --- /dev/null +++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_floating.py @@ -0,0 +1,48 @@ +# +# 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 = 'ToscaFloatingIP' +TOSCA_LINKS_TO = 'tosca.relationships.network.LinksTo' + + +class ToscaFloatingIP(HotResource): + '''Translate TOSCA node type tosca.nodes.network.FloatingIP''' + + toscatype = 'tosca.nodes.network.FloatingIP' + + def __init__(self, nodetemplate, csar_dir=None): + super(ToscaFloatingIP, self).__init__(nodetemplate, + type='OS::Neutron::FloatingIP', + csar_dir=csar_dir) + + def handle_properties(self): + tosca_props = self.get_tosca_props() + fip_props = {} + for key, value in tosca_props.items(): + fip_props[key] = value + + links_to = None + for rel, node in self.nodetemplate.relationships.items(): + if not links_to and rel.is_derived_from(TOSCA_LINKS_TO): + links_to = node + for hot_resource in self.depends_on_nodes: + if links_to.name == hot_resource.name: + self.depends_on.remove(hot_resource) + break + fip_props['port_id'] =\ + '{ get_resource: %s }' % (links_to.name) + + self.properties = fip_props diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_policies_monitoring.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_policies_monitoring.py new file mode 100644 index 0000000..7aac1c7 --- /dev/null +++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_policies_monitoring.py @@ -0,0 +1,82 @@ +# +# 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 logging +from toscaparser.common.exception import InvalidPropertyValueError +from translator.hot.syntax.hot_resource import HotResource + +# Name used to dynamically load appropriate map class. +TARGET_CLASS_NAME = 'ToscaMonitoring' + +log = logging.getLogger('heat-translator') + +ALARM_STATISTIC = {'average': 'avg', 'summary': 'sum', + 'maximum': 'max', 'minimum': 'min'} + + +class ToscaMonitoring(HotResource): + '''Translate TOSCA node type tosca.policies.Monitoring''' + + toscatype = 'tosca.policies.Monitoring' + + def __init__(self, policy, csar_dir=None): + hot_type = "OS::Aodh::Alarm" + super(ToscaMonitoring, self).__init__(policy, + type=hot_type, + csar_dir=csar_dir) + self.policy = policy + self.filter = list() + + def handle_expansion(self): + '''handle monitoring resources in case of multiple triggers''' + extra_resources = list() + extra_triggers = self.policy.entity_tpl["triggers"] + for trigger_name, trigger_dict in extra_triggers.items(): + if trigger_name not in self.filter: + self.filter.append(trigger_name) + prop = self._get_monitoring_prop(trigger_dict) + mon_resources = HotResource(self.nodetemplate, + type='OS::Aodh::Alarm', + name=trigger_name, + properties=prop) + extra_resources.append(mon_resources) + return extra_resources + + def handle_properties(self): + self.properties = {} + if self.policy.entity_tpl.get('triggers'): + triggers = self.policy.entity_tpl["triggers"] + trigger_name, trigger_dict = list(triggers.items())[0] + self.filter.append(trigger_name) + self.properties = self._get_monitoring_prop(trigger_dict) + self.name = trigger_name + return self.properties + + def _get_monitoring_prop(self, trigger): + sample = trigger.get('condition') + prop = dict() + prop["description"] = sample.get('constraint') + prop["meter_name"] = trigger.get('meter_name') + if sample.get('method') not in ALARM_STATISTIC: + msg = _('method should be one of given statistics') + log.error(msg) + raise InvalidPropertyValueError(what=msg) + prop["statistic"] = ALARM_STATISTIC[sample["method"]] + prop["period"] = sample.get("period") + prop["threshold"] = sample.get("threshold") + prop["comparison_operator"] = sample.get('comparison_operator') + prop['evaluation_periods'] = sample.get('evaluations') + return prop + + def embed_substack_templates(self, hot_template_version): + pass diff --git a/tosca2heat/heat-translator/translator/hot/tosca/tosca_policies_scaling.py b/tosca2heat/heat-translator/translator/hot/tosca/tosca_policies_scaling.py index 1b63f24..d34acd4 100644 --- a/tosca2heat/heat-translator/translator/hot/tosca/tosca_policies_scaling.py +++ b/tosca2heat/heat-translator/translator/hot/tosca/tosca_policies_scaling.py @@ -94,6 +94,8 @@ class ToscaAutoscaling(HotResource): self.properties["adjustment_type"] = "change_in_capacity " self.properties["scaling_adjustment"] = self.\ policy.entity_tpl["properties"]["increment"] + self.properties["cooldown"] =\ + self.policy.entity_tpl["properties"]["cooldown"] delete_res_names = [] scale_res = [] for index, resource in enumerate(resources): @@ -105,6 +107,7 @@ class ToscaAutoscaling(HotResource): res["min_size"] = temp["min_instances"] res["max_size"] = temp["max_instances"] res["desired_capacity"] = temp["default_instances"] + res["cooldown"] = temp["cooldown"] props['type'] = resource.type props['properties'] = resource.properties res['resource'] = {'type': self.policy.name + '_res.yaml'} diff --git a/tosca2heat/heat-translator/translator/hot/tosca_translator.py b/tosca2heat/heat-translator/translator/hot/tosca_translator.py index b9d4c77..a637a8c 100644 --- a/tosca2heat/heat-translator/translator/hot/tosca_translator.py +++ b/tosca2heat/heat-translator/translator/hot/tosca_translator.py @@ -71,7 +71,8 @@ class TOSCATranslator(object): return yaml_files["output.yaml"] - def translate_to_yaml_files_dict(self, base_filename): + def translate_to_yaml_files_dict(self, base_filename, + nested_resources=False): """Translate to HOT YAML This method produces a translated output containing main and @@ -82,7 +83,7 @@ class TOSCATranslator(object): self._translate_to_hot_yaml() return self.hot_template.output_to_yaml_files_dict( base_filename, - self.node_translator.hot_template_version) + self.node_translator.hot_template_version, nested_resources) def _translate_inputs(self): translator = TranslateInputs(self.tosca.inputs, self.parsed_params, diff --git a/tosca2heat/heat-translator/translator/hot/translate_node_templates.py b/tosca2heat/heat-translator/translator/hot/translate_node_templates.py index 1a1a4d7..78ab1c4 100644 --- a/tosca2heat/heat-translator/translator/hot/translate_node_templates.py +++ b/tosca2heat/heat-translator/translator/hot/translate_node_templates.py @@ -228,11 +228,11 @@ class TranslateNodeTemplates(object): # BlockStorage Attachment is a special case, # which doesn't match to Heat Resources 1 to 1. if base_type == "tosca.nodes.Compute": - volume_name = None requirements = node.requirements if requirements: # Find the name of associated BlockStorage node for requires in requirements: + volume_name = None for value in requires.values(): if isinstance(value, dict): for node_name in value.values(): @@ -250,11 +250,12 @@ class TranslateNodeTemplates(object): volume_name = node_name break - suffix = suffix + 1 - attachment_node = self._get_attachment_node( - node, suffix, volume_name) - if attachment_node: - self.hot_resources.append(attachment_node) + if volume_name: + suffix = suffix + 1 + attachment_node = self._get_attachment_node( + node, suffix, volume_name) + if attachment_node: + self.hot_resources.append(attachment_node) for i in self.tosca.inputs: if (i.name == 'key_name' and node.get_property_value('key_name') is None): @@ -269,8 +270,12 @@ class TranslateNodeTemplates(object): policy_type.type != 'tosca.policies.Scaling.Cluster': TOSCA_TO_HOT_TYPE[policy_type.type] = \ TOSCA_TO_HOT_TYPE['tosca.policies.Scaling'] - if not policy.is_derived_from('tosca.policies.Scaling') and \ - policy_type.type not in TOSCA_TO_HOT_TYPE: + if policy.is_derived_from('tosca.policies.Monitoring'): + TOSCA_TO_HOT_TYPE[policy_type.type] = \ + TOSCA_TO_HOT_TYPE['tosca.policies.Monitoring'] + if not policy.is_derived_from('tosca.policies.Monitoring') and \ + not policy.is_derived_from('tosca.policies.Scaling') and \ + policy_type.type not in TOSCA_TO_HOT_TYPE: raise UnsupportedTypeError(type=_('%s') % policy_type.type) elif policy_type.type == 'tosca.policies.Scaling.Cluster': self.hot_template_version = '2016-04-08' @@ -587,7 +592,8 @@ class TranslateNodeTemplates(object): for key_r, value_n in node.relationships.items(): if key_r.is_derived_from('tosca.relationships.AttachesTo'): if value_n.is_derived_from('tosca.nodes.BlockStorage'): - attach = True + if volume_name == value_n.name: + attach = True if attach: relationship_tpl = None for req in node.requirements: diff --git a/tosca2heat/heat-translator/translator/tests/data/autoscaling/tosca_autoscaling.yaml b/tosca2heat/heat-translator/translator/tests/data/autoscaling/tosca_autoscaling.yaml index f58d727..d5356be 100644 --- a/tosca2heat/heat-translator/translator/tests/data/autoscaling/tosca_autoscaling.yaml +++ b/tosca2heat/heat-translator/translator/tests/data/autoscaling/tosca_autoscaling.yaml @@ -38,3 +38,4 @@ topology_template: max_instances: 10 default_instances: 3 increment: 1 + cooldown: 60 diff --git a/tosca2heat/heat-translator/translator/tests/data/hot_output/autoscaling/hot_autoscaling.yaml b/tosca2heat/heat-translator/translator/tests/data/hot_output/autoscaling/hot_autoscaling.yaml index 1cd2c03..a83f019 100644 --- a/tosca2heat/heat-translator/translator/tests/data/hot_output/autoscaling/hot_autoscaling.yaml +++ b/tosca2heat/heat-translator/translator/tests/data/hot_output/autoscaling/hot_autoscaling.yaml @@ -10,6 +10,7 @@ resources: properties: min_size: 2 desired_capacity: 3 + cooldown: 60 resource: type: asg_res.yaml max_size: 10 @@ -20,6 +21,7 @@ resources: get_resource: asg_group adjustment_type: change_in_capacity scaling_adjustment: 1 + cooldown: 60 asg_scale_in: type: OS::Heat::ScalingPolicy properties: @@ -27,6 +29,7 @@ resources: get_resource: asg_group adjustment_type: change_in_capacity scaling_adjustment: -1 + cooldown: 60 asg_alarm: type: OS::Aodh::Alarm properties: diff --git a/tosca2heat/heat-translator/translator/tests/data/hot_output/autoscaling/hot_cluster_autoscaling.yaml b/tosca2heat/heat-translator/translator/tests/data/hot_output/autoscaling/hot_cluster_autoscaling.yaml index ca0fb3a..e0dbb7f 100644 --- a/tosca2heat/heat-translator/translator/tests/data/hot_output/autoscaling/hot_cluster_autoscaling.yaml +++ b/tosca2heat/heat-translator/translator/tests/data/hot_output/autoscaling/hot_cluster_autoscaling.yaml @@ -12,6 +12,7 @@ resources: properties:
flavor: m1.medium
image: rhel-6.5-test-image
+ software_config_transport: POLL_SERVER_HEAT
networks:
- network: net0
cluster_scaling_scale_out:
diff --git a/tosca2heat/heat-translator/translator/tests/data/hot_output/monitoring/asg_res.yaml b/tosca2heat/heat-translator/translator/tests/data/hot_output/monitoring/asg_res.yaml new file mode 100644 index 0000000..d3415ea --- /dev/null +++ b/tosca2heat/heat-translator/translator/tests/data/hot_output/monitoring/asg_res.yaml @@ -0,0 +1,10 @@ +heat_template_version: 2013-05-23 +description: Tacker Scaling template +resources: + my_server_1: + type: OS::Nova::Server + properties: + flavor: m1.medium + user_data_format: SOFTWARE_CONFIG + software_config_transport: POLL_SERVER_HEAT + image: rhel-6.5-test-image diff --git a/tosca2heat/heat-translator/translator/tests/data/hot_output/monitoring/hot_monitoring_scaling.yaml b/tosca2heat/heat-translator/translator/tests/data/hot_output/monitoring/hot_monitoring_scaling.yaml new file mode 100644 index 0000000..85ff54d --- /dev/null +++ b/tosca2heat/heat-translator/translator/tests/data/hot_output/monitoring/hot_monitoring_scaling.yaml @@ -0,0 +1,53 @@ +heat_template_version: 2013-05-23 + +description: > + Template for deploying servers based on policies. + +parameters: {} +resources: + asg_scale_out: + type: OS::Heat::ScalingPolicy + properties: + auto_scaling_group_id: + get_resource: asg_group + adjustment_type: change_in_capacity + scaling_adjustment: 1 + cooldown: 60 + low_cpu_usage: + type: OS::Aodh::Alarm + properties: + meter_name: cpu_util + description: utilization less_than 20% + evaluation_periods: 1 + period: 600 + statistic: avg + threshold: 20 + comparison_operator: gt + asg_group: + type: OS::Heat::AutoScalingGroup + properties: + min_size: 2 + desired_capacity: 3 + resource: + type: asg_res.yaml + max_size: 10 + cooldown: 60 + asg_scale_in: + type: OS::Heat::ScalingPolicy + properties: + auto_scaling_group_id: + get_resource: asg_group + adjustment_type: change_in_capacity + scaling_adjustment: -1 + cooldown: 60 + high_cpu_usage: + type: OS::Aodh::Alarm + properties: + meter_name: cpu_util + description: utilization greater_than 60% + evaluation_periods: 1 + period: 600 + statistic: avg + threshold: 60 + comparison_operator: gt +outputs: {} diff --git a/tosca2heat/heat-translator/translator/tests/data/hot_output/nfv/hot_tosca_nfv_autoscaling.yaml b/tosca2heat/heat-translator/translator/tests/data/hot_output/nfv/hot_tosca_nfv_autoscaling.yaml index 7d1e2f6..dde597b 100644 --- a/tosca2heat/heat-translator/translator/tests/data/hot_output/nfv/hot_tosca_nfv_autoscaling.yaml +++ b/tosca2heat/heat-translator/translator/tests/data/hot_output/nfv/hot_tosca_nfv_autoscaling.yaml @@ -12,6 +12,7 @@ resources: get_resource: SP1_group adjustment_type: change_in_capacity scaling_adjustment: 1 + cooldown: 120 SP1_group: type: OS::Heat::AutoScalingGroup properties: @@ -20,6 +21,7 @@ resources: resource: type: SP1_res.yaml max_size: 3 + cooldown: 120 SP1_scale_in: type: OS::Heat::ScalingPolicy properties: @@ -27,4 +29,5 @@ resources: get_resource: SP1_group adjustment_type: change_in_capacity scaling_adjustment: -1 + cooldown: 120 outputs: {}
\ No newline at end of file diff --git a/tosca2heat/heat-translator/translator/tests/data/hot_output/storage/hot_multiple_blockstorage_w_multiple_attachment.yaml b/tosca2heat/heat-translator/translator/tests/data/hot_output/storage/hot_multiple_blockstorage_w_multiple_attachment.yaml new file mode 100644 index 0000000..34f408e --- /dev/null +++ b/tosca2heat/heat-translator/translator/tests/data/hot_output/storage/hot_multiple_blockstorage_w_multiple_attachment.yaml @@ -0,0 +1,96 @@ +heat_template_version: 2013-05-23 + +description: > + TOSCA simple profile with 1 server attached 2 block storages. + +parameters: + cpus: + type: number + description: Number of CPUs for the server. + default: 1 + constraints: + - allowed_values: + - 1 + - 2 + - 4 + - 8 + storage_location1: + type: string + description: Block storage mount point (filesystem path). + default: /dev/vdb + storage_location2: + type: string + description: Block storage mount point (filesystem path). + default: /dev/vdc + storage_size: + type: number + description: Size of the storage to be created. + default: 1 + storage_snapshot_id: + type: string + description: Optional identifier for an existing snapshot to use when creating storage. + default: ssid + +resources: + my_server: + type: OS::Nova::Server + properties: + flavor: m1.medium + image: fedora-amd64-heat-config + software_config_transport: POLL_SERVER_HEAT + user_data_format: SOFTWARE_CONFIG + depends_on: + - my_storage1 + - my_storage2 + + my_storage1: + type: OS::Cinder::Volume + properties: + size: + get_param: storage_size + snapshot_id: + get_param: storage_snapshot_id + + my_storage2: + type: OS::Cinder::Volume + properties: + size: + get_param: storage_size + snapshot_id: + get_param: storage_snapshot_id + + attachesto_1: + type: OS::Cinder::VolumeAttachment + properties: + instance_uuid: + get_resource: my_server + mountpoint: + get_param: storage_location1 + volume_id: + get_resource: my_storage1 + + attachesto_2: + type: OS::Cinder::VolumeAttachment + properties: + instance_uuid: + get_resource: my_server + mountpoint: + get_param: storage_location2 + volume_id: + get_resource: my_storage2 + +outputs: + server_ip: + description: The private IP address of the applications server. + value: + get_attr: + - my_server + - first_address + volume_id_1: + description: The volume id of the first block storage instance. + value: + get_resource: my_storage1 + volume_id_2: + description: The volume id of the second block storage instance. + value: + get_resource: my_storage2 diff --git a/tosca2heat/heat-translator/translator/tests/data/monitoring/tosca_monitoring_scaling.yaml b/tosca2heat/heat-translator/translator/tests/data/monitoring/tosca_monitoring_scaling.yaml new file mode 100644 index 0000000..0c36236 --- /dev/null +++ b/tosca2heat/heat-translator/translator/tests/data/monitoring/tosca_monitoring_scaling.yaml @@ -0,0 +1,60 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Template for deploying servers based on policies. + +topology_template: + node_templates: + my_server_1: + type: tosca.nodes.Compute + capabilities: + host: + properties: + num_cpus: 2 + disk_size: 10 GB + mem_size: 512 MB + os: + properties: + # host Operating System image properties + architecture: x86_64 + type: Linux + distribution: RHEL + version: 6.5 + policies: + - asg: + type: tosca.policies.Scaling + description: Simple node autoscaling + targets: [my_server_1] + properties: + min_instances: 2 + max_instances: 10 + default_instances: 3 + increment: 1 + cooldown: 60 + + - cpu_monitoring: + type: tosca.policies.Monitoring + description: Simple node monitoring + targets: [my_server_1] + triggers: + high_cpu_usage: + description: trigger + meter_name: cpu_util + condition: + constraint: utilization greater_than 60% + threshold: 60 + period: 600 + evaluations: 1 + method: average + comparison_operator: gt + + low_cpu_usage: + description: trigger + meter_name: cpu_util + condition: + constraint: utilization less_than 20% + threshold: 20 + period: 600 + evaluations: 1 + method: average + comparison_operator: gt
\ No newline at end of file diff --git a/tosca2heat/heat-translator/translator/tests/data/storage/tosca_multiple_blockstorage_w_multiple_attachment.yaml b/tosca2heat/heat-translator/translator/tests/data/storage/tosca_multiple_blockstorage_w_multiple_attachment.yaml new file mode 100644 index 0000000..7eb7843 --- /dev/null +++ b/tosca2heat/heat-translator/translator/tests/data/storage/tosca_multiple_blockstorage_w_multiple_attachment.yaml @@ -0,0 +1,79 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA simple profile with 1 server attached 2 block storages. + +topology_template: + inputs: + cpus: + type: integer + description: Number of CPUs for the server. + constraints: + - valid_values: [ 1, 2, 4, 8 ] + storage_size: + type: scalar-unit.size + default: 1 GB + description: Size of the storage to be created. + storage_snapshot_id: + type: string + description: > + Optional identifier for an existing snapshot to use when creating storage. + storage_location1: + type: string + description: > + Block storage mount point (filesystem path). + storage_location2: + type: string + description: > + Block storage mount point (filesystem path). + + node_templates: + my_server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + disk_size: 10 GB + num_cpus: { get_input: cpus } + mem_size: 4096 MB + os: + properties: + architecture: x86_64 + type: Linux + distribution: Fedora + version: 18.0 + requirements: + - local_storage1: + node: my_storage1 + relationship: + type: AttachesTo + properties: + location: { get_input: storage_location1 } + - local_storage2: + node: my_storage2 + relationship: + type: AttachesTo + properties: + location: { get_input: storage_location2 } + my_storage1: + type: tosca.nodes.BlockStorage + properties: + size: { get_input: storage_size } + snapshot_id: { get_input: storage_snapshot_id } + + my_storage2: + type: tosca.nodes.BlockStorage + properties: + size: { get_input: storage_size } + snapshot_id: { get_input: storage_snapshot_id } + + outputs: + server_ip: + description: The private IP address of the application's server. + value: { get_attribute: [my_server, private_address] } + volume_id_1: + description: The volume id of the first block storage instance. + value: { get_attribute: [my_storage1, volume_id] } + volume_id_2: + description: The volume id of the second block storage instance. + value: { get_attribute: [my_storage2, volume_id] } diff --git a/tosca2heat/heat-translator/translator/tests/test_tosca_hot_translation.py b/tosca2heat/heat-translator/translator/tests/test_tosca_hot_translation.py index 6d0d316..95df72a 100644 --- a/tosca2heat/heat-translator/translator/tests/test_tosca_hot_translation.py +++ b/tosca2heat/heat-translator/translator/tests/test_tosca_hot_translation.py @@ -26,14 +26,15 @@ from translator.tests.base import TestCase class ToscaHotTranslationTest(TestCase): - def _test_successful_translation(self, tosca_file, hot_files, params=None): + def _test_successful_translation(self, tosca_file, hot_files, params=None, + nested_resources=False): if not params: params = {} if not isinstance(hot_files, list): hot_files = [hot_files] - diff = TranslationUtils.compare_tosca_translation_with_hot(tosca_file, - hot_files, - params) + diff =\ + TranslationUtils.compare_tosca_translation_with_hot( + tosca_file, hot_files, params, nested_resources) self.assertEqual({}, diff, '<difference> : ' + json.dumps(diff, indent=4, separators=(', ', ': '))) @@ -191,6 +192,18 @@ class ToscaHotTranslationTest(TestCase): except Exception: self._test_successful_translation(tosca_file, hot_file2, params) + def test_hot_translate_multiple_blockstorage_w_multiple_attachment(self): + tosca_file = '../tests/data/storage/' \ + 'tosca_multiple_blockstorage_w_multiple_attachment.yaml' + hot_file = '../tests/data/hot_output/storage/' \ + 'hot_multiple_blockstorage_w_multiple_attachment.yaml' + params = {'cpus': 1, + 'storage_location1': '/dev/vdb', + 'storage_location2': '/dev/vdc', + 'storage_size': '1 GB', + 'storage_snapshot_id': 'ssid'} + self._test_successful_translation(tosca_file, hot_file, params) + def test_hot_translate_single_object_store(self): tosca_file = '../tests/data/storage/tosca_single_object_store.yaml' hot_file = '../tests/data/hot_output/hot_single_object_store.yaml' @@ -515,6 +528,15 @@ class ToscaHotTranslationTest(TestCase): params = {} self._test_successful_translation(tosca_file, hot_files, params) + def test_hot_translate_scaling_nested_plate(self): + tosca_file = '../tests/data/autoscaling/tosca_autoscaling.yaml' + hot_files = [ + '../tests/data/hot_output/autoscaling/asg_res.yaml' + ] + params = {} + self._test_successful_translation(tosca_file, hot_files, params, + nested_resources=True) + def test_translate_unsupported_tosca_type(self): tosca_file = '../tests/data/test_tosca_unsupported_type.yaml' tosca_tpl = os.path.normpath(os.path.join( @@ -528,7 +550,7 @@ class ToscaHotTranslationTest(TestCase): .translate) self.assertEqual(expected_msg, err.__str__()) - def _translate_nodetemplates(self): + def test_hot_translate_cluster_scaling_policy(self): tosca_file = '../tests/data/autoscaling/tosca_cluster_autoscaling.yaml' hot_file = '../tests/data/hot_output/autoscaling/' \ 'hot_cluster_autoscaling.yaml' @@ -543,3 +565,12 @@ class ToscaHotTranslationTest(TestCase): ] params = {} self._test_successful_translation(tosca_file, hot_files, params) + + def test_hot_translate_mon_scaling_policy(self): + tosca_file = '../tests/data/monitoring/tosca_monitoring_scaling.yaml' + hot_files = [ + '../tests/data/hot_output/monitoring/hot_monitoring_scaling.yaml', + '../tests/data/hot_output/monitoring/asg_res.yaml', + ] + params = {} + self._test_successful_translation(tosca_file, hot_files, params) diff --git a/tosca2heat/tosca-parser/CONTRIBUTING.rst b/tosca2heat/tosca-parser/CONTRIBUTING.rst new file mode 100644 index 0000000..7366825 --- /dev/null +++ b/tosca2heat/tosca-parser/CONTRIBUTING.rst @@ -0,0 +1,16 @@ +If you would like to contribute to the development of OpenStack, +you must follow the steps in this page: + + http://docs.openstack.org/infra/manual/developers.html + +Once those steps have been completed, changes to OpenStack +should be submitted for review via the Gerrit tool, following +the workflow documented at: + + http://docs.openstack.org/infra/manual/developers.html#development-workflow + +Pull requests submitted through GitHub will be ignored. + +Bugs should be filed on Launchpad, not GitHub: + + https://bugs.launchpad.net/tosca-parser
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/HACKING.rst b/tosca2heat/tosca-parser/HACKING.rst new file mode 100644 index 0000000..60505f1 --- /dev/null +++ b/tosca2heat/tosca-parser/HACKING.rst @@ -0,0 +1,4 @@ +tosca-parser Style Commandments +=============================================== + +Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
\ No newline at end of file diff --git a/tosca2heat/tosca-parser/README.md b/tosca2heat/tosca-parser/README.rst index 0f94072..0f94072 100644 --- a/tosca2heat/tosca-parser/README.md +++ b/tosca2heat/tosca-parser/README.rst diff --git a/tosca2heat/tosca-parser/requirements.txt b/tosca2heat/tosca-parser/requirements.txt index 45a19f0..807d749 100644 --- a/tosca2heat/tosca-parser/requirements.txt +++ b/tosca2heat/tosca-parser/requirements.txt @@ -1,10 +1,10 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr>=1.8 # Apache-2.0 -Babel>=2.3.4 # BSD -cliff>=2.3.0 # Apache-2.0 +pbr!=2.1.0,>=2.0.0 # Apache-2.0 +Babel!=2.4.0,>=2.3.4 # BSD +cliff>=2.6.0 # Apache-2.0 PyYAML>=3.10.0 # MIT python-dateutil>=2.4.2 # BSD six>=1.9.0 # MIT -requests!=2.12.2,!=2.13.0,>=2.10.0 # Apache-2.0 +requests>=2.14.2 # Apache-2.0 diff --git a/tosca2heat/tosca-parser/setup.cfg b/tosca2heat/tosca-parser/setup.cfg index 45a5ae7..77e1b2e 100644 --- a/tosca2heat/tosca-parser/setup.cfg +++ b/tosca2heat/tosca-parser/setup.cfg @@ -3,7 +3,7 @@ name = tosca-parser url = https://launchpad.net/tosca-parser summary = Parser for TOSCA Simple Profile in YAML. description-file = - README.md + README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = http://docs.openstack.org/developer/tosca-parser/ @@ -28,6 +28,14 @@ packages = console_scripts = tosca-parser = toscaparser.shell:main +[build_sphinx] +source-dir = doc/source +build-dir = doc/build +all_files = 1 + +[upload_sphinx] +upload-dir = doc/build/html + [compile_catalog] directory = toscaparser/locale domain = toscaparser diff --git a/tosca2heat/tosca-parser/setup.py b/tosca2heat/tosca-parser/setup.py index 782bb21..566d844 100644 --- a/tosca2heat/tosca-parser/setup.py +++ b/tosca2heat/tosca-parser/setup.py @@ -25,5 +25,5 @@ except ImportError: pass setuptools.setup( - setup_requires=['pbr>=1.8'], + setup_requires=['pbr>=2.0.0'], pbr=True) diff --git a/tosca2heat/tosca-parser/test-requirements.txt b/tosca2heat/tosca-parser/test-requirements.txt index d4b59c3..da69d4f 100644 --- a/tosca2heat/tosca-parser/test-requirements.txt +++ b/tosca2heat/tosca-parser/test-requirements.txt @@ -1,13 +1,13 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -hacking<0.11,>=0.10.0 -coverage>=4.0 # Apache-2.0 +hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 +coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD oslotest>=1.10.0 # Apache-2.0 oslosphinx>=4.7.0 # Apache-2.0 python-subunit>=0.0.18 # Apache-2.0/BSD -sphinx>=1.5.1 # BSD +sphinx>=1.6.2 # BSD testrepository>=0.0.18 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD testtools>=1.4.0 # MIT diff --git a/tosca2heat/tosca-parser/toscaparser/elements/TOSCA_definition_1_0.yaml b/tosca2heat/tosca-parser/toscaparser/elements/TOSCA_definition_1_0.yaml index 9f3369e..57c9bf9 100644 --- a/tosca2heat/tosca-parser/toscaparser/elements/TOSCA_definition_1_0.yaml +++ b/tosca2heat/tosca-parser/toscaparser/elements/TOSCA_definition_1_0.yaml @@ -339,6 +339,26 @@ node_types: relationship: tosca.relationships.network.LinksTo node: tosca.nodes.network.Network + tosca.nodes.network.FloatingIP: + derived_from: tosca.nodes.Root + description: > + The TOSCA FloatingIP node represents a floating IP that can associate to a Port. + properties: + floating_network: + type: string + required: true + floating_ip_address: + type: string + required: false + port_id: + type: string + required: false + requirements: + - link: + capability: tosca.capabilities.network.Linkable + relationship: tosca.relationships.network.LinksTo + node: tosca.nodes.network.Port + tosca.nodes.ObjectStorage: derived_from: tosca.nodes.Root description: > @@ -928,6 +948,11 @@ policy_types: description: The TOSCA Policy Type definition that is used to govern scaling of TOSCA nodes or groups of nodes. + tosca.policies.Monitoring: + derived_from: tosca.policies.Root + description: The TOSCA Policy Type definition that is used to govern + monitoring of TOSCA nodes or groups of nodes. + tosca.policies.Update: derived_from: tosca.policies.Root description: The TOSCA Policy Type definition that is used to govern diff --git a/tosca2heat/tosca-parser/toscaparser/elements/nodetype.py b/tosca2heat/tosca-parser/toscaparser/elements/nodetype.py index 7f3da2d..4889ed8 100644 --- a/tosca2heat/tosca-parser/toscaparser/elements/nodetype.py +++ b/tosca2heat/tosca-parser/toscaparser/elements/nodetype.py @@ -83,8 +83,6 @@ class NodeType(StatefulEntityType): captype = value['capability'] value = (self. _get_node_type_by_cap(captype)) - # _get_node_type_by_cap(key, captype)) - # relation = self._get_relation(key, value) keyword = key node_type = value rtype = RelationshipType(relation, keyword, self.custom_def) diff --git a/tosca2heat/tosca-parser/toscaparser/elements/policytype.py b/tosca2heat/tosca-parser/toscaparser/elements/policytype.py index a922d26..82aed0a 100644 --- a/tosca2heat/tosca-parser/toscaparser/elements/policytype.py +++ b/tosca2heat/tosca-parser/toscaparser/elements/policytype.py @@ -113,7 +113,7 @@ class PolicyType(StatefulEntityType): for entry_schema, entry_schema_type in meta_data.items(): if isinstance(entry_schema_type, dict) and not \ - entry_schema_type.get('type') == 'string': + entry_schema_type.get('type') == 'string': ExceptionCollector.appendException( InvalidTypeError(what='"%s" defined in policy for ' 'metadata "%s"' diff --git a/tosca2heat/tosca-parser/toscaparser/functions.py b/tosca2heat/tosca-parser/toscaparser/functions.py index d498229..b64f1bc 100644 --- a/tosca2heat/tosca-parser/toscaparser/functions.py +++ b/tosca2heat/tosca-parser/toscaparser/functions.py @@ -158,6 +158,8 @@ class GetAttribute(Function): # then check the req or caps attr = self._find_req_or_cap_attribute(self.args[1], self.args[2]) + if not attr: + return value_type = attr.schema['type'] if len(self.args) > index: @@ -236,8 +238,8 @@ class GetAttribute(Function): target_node = self._find_node_template(target_name) target_type = target_node.type_definition for capability in target_type.get_capabilities_objects(): - if capability.type in \ - hosted_on_rel['valid_target_types']: + if capability.inherits_from( + hosted_on_rel['valid_target_types']): if self._attribute_exists_in_type(target_type): return target_node return self._find_host_containing_attribute( @@ -412,6 +414,8 @@ class GetProperty(Function): def _find_req_or_cap_property(self, req_or_cap, property_name): node_tpl = self._find_node_template(self.args[0]) + if node_tpl is None: + return None # Find property in node template's requirements for r in node_tpl.requirements: for req, node_name in r.items(): @@ -429,7 +433,8 @@ class GetProperty(Function): def _get_capability_property(self, node_template, capability_name, - property_name): + property_name, + throw_errors=True): """Gets a node template capability property.""" caps = node_template.get_capabilities() if caps and capability_name in caps.keys(): @@ -438,7 +443,7 @@ class GetProperty(Function): props = cap.get_properties() if props and property_name in props.keys(): property = props[property_name].value - if not property: + if property is None and throw_errors: ExceptionCollector.appendException( KeyError(_('Property "%(prop)s" was not found in ' 'capability "%(cap)s" of node template ' @@ -448,12 +453,15 @@ class GetProperty(Function): 'ntpl1': node_template.name, 'ntpl2': self.context.name})) return property - msg = _('Requirement/Capability "{0}" referenced from node template ' - '"{1}" was not found in node template "{2}".').format( - capability_name, - self.context.name, - node_template.name) - ExceptionCollector.appendException(KeyError(msg)) + if throw_errors: + msg = _('Requirement/Capability "{0}" referenced from ' + 'node template "{1}" was not found in node template' + ' "{2}".').format(capability_name, + self.context.name, + node_template.name) + ExceptionCollector.appendException(KeyError(msg)) + else: + return None def _find_property(self, property_name): node_tpl = self._find_node_template(self.args[0]) @@ -475,7 +483,18 @@ class GetProperty(Function): return self.context # enable the HOST value in the function if node_template_name == HOST: - return self._find_host_containing_property() + node = self._find_host_containing_property() + if node is None: + ExceptionCollector.appendException( + KeyError(_( + "Property '{0}' not found in capability/requirement" + " '{1}' referenced from node template {2}"). + format(self.args[2], + self.args[1], + self.context.name))) + return None + else: + return node if node_template_name == TARGET: if not isinstance(self.context.type_definition, RelationshipType): ExceptionCollector.appendException( @@ -498,7 +517,9 @@ class GetProperty(Function): ExceptionCollector.appendException( KeyError(_( 'Node template "{0}" was not found.' - ).format(node_template_name))) + ' referenced from node template {1}' + ).format(node_template_name, + self.context.name))) def _get_index_value(self, value, index): if isinstance(value, list): @@ -555,9 +576,19 @@ class GetProperty(Function): target_node = self._find_node_template(target_name) target_type = target_node.type_definition for capability in target_type.get_capabilities_objects(): - if capability.type in hosted_on_rel['valid_target_types']: + if capability.inherits_from( + hosted_on_rel['valid_target_types']): if self._property_exists_in_type(target_type): return target_node + # If requirement was not found, look in node + # template's capabilities + if (len(self.args) > 2 and + self._get_capability_property(target_node, + self.args[1], + self.args[2], + False) + is not None): + return target_node return self._find_host_containing_property( target_name) return None diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/container_cap_child.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/container_cap_child.yaml new file mode 100644 index 0000000..1df09dd --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/container_cap_child.yaml @@ -0,0 +1,33 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Define a capability class that inherits from tosca.capabilities.Container + +capability_types: + + tosca.capabilities.ContainerChild: + derived_from: tosca.capabilities.Container + +node_types: + + tosca.nodes.SomeNode: + derived_from: tosca.nodes.Root + properties: + some_prop: + type: string + requirements: + - host_child: + capability: tosca.capabilities.ContainerChild + node: tosca.nodes.SomeNode2 + relationship: tosca.relationships.HostedOn + + tosca.nodes.SomeNode2: + derived_from: tosca.nodes.Root + capabilities: + host_child: + type: tosca.capabilities.ContainerChild + requirements: + - host: + capability: tosca.capabilities.Container + node: tosca.nodes.Compute + relationship: tosca.relationships.HostedOn diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/custom_cap.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/custom_cap.yaml new file mode 100644 index 0000000..018bcf6 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/custom_cap.yaml @@ -0,0 +1,22 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +capability_types: + + tosca.capabilities.SomeCap: + derived_from: tosca.capabilities.Container + +node_types: + + tosca.nodes.NodeWithReq: + derived_from: tosca.nodes.SoftwareComponent + requirements: + - host: + capability: tosca.capabilities.SomeCap + relationship: tosca.relationships.HostedOn + occurrences: [1, 1] + + tosca.nodes.NodeWithCap: + derived_from: tosca.nodes.SoftwareComponent + capabilities: + host: + type: tosca.capabilities.SomeCap diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/node_with_cap.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/node_with_cap.yaml index b17513f..332f830 100644 --- a/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/node_with_cap.yaml +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/custom_types/node_with_cap.yaml @@ -6,7 +6,7 @@ description: > capability_types: tosca.capabilities.SomeCap: - derived_from: tosca.capabilities.Root + derived_from: tosca.capabilities.Container properties: type: type: string @@ -19,6 +19,11 @@ node_types: tosca.nodes.SomeNode: derived_from: tosca.nodes.Root + properties: + some_prop: + type: string + required: false + default: some requirements: - some_req: capability: tosca.capabilities.SomeCap diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_container_cap_child.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_container_cap_child.yaml new file mode 100644 index 0000000..84118c8 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_container_cap_child.yaml @@ -0,0 +1,28 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA simple profile to test the get attribute function with HOST parameter + using a node that has as capability a child class of Container + +imports: + - ../custom_types/container_cap_child.yaml + +topology_template: + + node_templates: + + test_node: + type: tosca.nodes.SomeNode + properties: + some_prop: { get_attribute: [ HOST, public_address ] } + requirements: + - host_child: test_node2 + + test_node2: + type: tosca.nodes.SomeNode2 + requirements: + - host: server + + server: + type: tosca.nodes.Compute + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_prop_cap_bool.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_prop_cap_bool.yaml new file mode 100644 index 0000000..d9c4c1c --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_prop_cap_bool.yaml @@ -0,0 +1,37 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: TOSCA test for boolean properties + +node_types: + + tosca.nodes.SoftwareComponentTest: + derived_from: tosca.nodes.SoftwareComponent + properties: + some_prop: + type: boolean + + tosca.nodes.ComputeTest: + derived_from: tosca.nodes.Compute + capabilities: + endpoint: + type: tosca.capabilities.Endpoint + +topology_template: + + node_templates: + + software: + type: tosca.nodes.SoftwareComponentTest + properties: + some_prop: { get_property: [ HOST, endpoint, secure ] } + requirements: + - host: server + + server: + type: tosca.nodes.ComputeTest + capabilities: + endpoint: + properties: + network_name: PUBLIC + secure: false + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_prop_cap_host.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_prop_cap_host.yaml new file mode 100644 index 0000000..7fcb4a7 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/functions/test_get_prop_cap_host.yaml @@ -0,0 +1,25 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: TOSCA test for the IM + +imports: + - test: ../custom_types/node_with_cap.yaml + +topology_template: + + node_templates: + + some_node: + type: tosca.nodes.SomeNode + properties: + some_prop: { get_property: [ HOST, some_req, type ] } + requirements: + - some_req: server + + server: + type: tosca.nodes.NodeWithCap + capabilities: + some_req: + properties: + type: someval + diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/policies/tosca_policy_template.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/policies/tosca_policy_template.yaml index 47f7870..4c18d9d 100644 --- a/tosca2heat/tosca-parser/toscaparser/tests/data/policies/tosca_policy_template.yaml +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/policies/tosca_policy_template.yaml @@ -75,6 +75,32 @@ topology_template: inputs: strategy: LEAST_USED implementation: Senlin.webhook() + high_cpu_usage: + description: trigger + meter_name: cpu_util + condition: + constraint: utilization greater_than 60% + threshold: 60 + period: 600 + evaluations: 1 + method: average + comparison_operator: gt + metadata: SG1 + action: [SP1] + + low_cpu_usage: + description: trigger + meter_name: cpu_util + condition: + constraint: utilization less_than 20% + threshold: 20 + period: 600 + evaluations: 1 + method: average + comparison_operator: gt + metadata: SG1 + action: [SP1] + - my_groups_placement: type: mycompany.mytypes.myScalingPolicy targets: [ webserver_group ] diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/test_custom_capabilty.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/test_custom_capabilty.yaml new file mode 100644 index 0000000..03a8a07 --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/test_custom_capabilty.yaml @@ -0,0 +1,23 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: TOSCA simple profile to test a custom defined capability + +imports: + - custom_types/custom_cap.yaml + +topology_template: + + node_templates: + + node_req: + type: tosca.nodes.NodeWithReq + requirements: + - host: node_cap + + node_cap: + type: tosca.nodes.NodeWithCap + requirements: + - host: server + + server: + type: tosca.nodes.Compute diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/definitions.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/definitions.yaml index ba5eac1..300bb8a 100644 --- a/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/definitions.yaml +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/definitions.yaml @@ -49,7 +49,7 @@ node_types: derived_from: tosca.nodes.Database example.SomeApp: - derived_from: tosca.nodes.SoftwareComponent + derived_from: tosca.nodes.WebApplication properties: admin_user: type: string diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_functions.py b/tosca2heat/tosca-parser/toscaparser/tests/test_functions.py index fa60140..2e1d71e 100644 --- a/tosca2heat/tosca-parser/toscaparser/tests/test_functions.py +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_functions.py @@ -188,6 +188,26 @@ class IntrinsicFunctionsTest(TestCase): self.assertIsInstance(source_port, functions.GetProperty) self.assertEqual(3306, source_port.result()) + def test_get_prop_cap_host(self): + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/functions/test_get_prop_cap_host.yaml") + some_node = self._get_node('some_node', + ToscaTemplate(tosca_tpl)) + some_prop = some_node.get_properties()['some_prop'] + self.assertIsInstance(some_prop.value, functions.GetProperty) + self.assertEqual('someval', some_prop.value.result()) + + def test_get_prop_cap_bool(self): + tosca_tpl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "data/functions/test_get_prop_cap_bool.yaml") + some_node = self._get_node('software', + ToscaTemplate(tosca_tpl)) + some_prop = some_node.get_properties()['some_prop'] + self.assertIsInstance(some_prop.value, functions.GetProperty) + self.assertEqual(False, some_prop.value.result()) + class GetAttributeTest(TestCase): @@ -318,6 +338,10 @@ class GetAttributeTest(TestCase): self.assertIsNotNone(self._load_template( 'functions/test_get_implicit_attribute.yaml')) + def test_get_attribute_capability_inheritance(self): + self.assertIsNotNone(self._load_template( + 'functions/test_container_cap_child.yaml')) + class ConcatTest(TestCase): diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_topology_template.py b/tosca2heat/tosca-parser/toscaparser/tests/test_topology_template.py index 3aabc9b..a0d6dc3 100644 --- a/tosca2heat/tosca-parser/toscaparser/tests/test_topology_template.py +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_topology_template.py @@ -91,7 +91,7 @@ class TopologyTemplateTest(TestCase): tpl_name = "app" expected_type = "example.SomeApp" expected_properties = ['admin_user', 'pool_size'] - expected_capabilities = ['feature', 'message_receiver'] + expected_capabilities = ['app_endpoint', 'feature', 'message_receiver'] expected_requirements = [{'host': {'node': 'websrv'}}] expected_relationshp = ['tosca.relationships.HostedOn'] expected_host = ['websrv'] diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_toscatplvalidation.py b/tosca2heat/tosca-parser/toscaparser/tests/test_toscatplvalidation.py index 911867f..f7c22ab 100644 --- a/tosca2heat/tosca-parser/toscaparser/tests/test_toscatplvalidation.py +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_toscatplvalidation.py @@ -1581,7 +1581,7 @@ heat-translator/master/translator/tests/data/custom_types/wordpress.yaml lambda: Policy(name, policies[name], None, None)) self.assertEqual(expectedmessage, err.__str__()) - def test_policy_trigger_valid_keyname(self): + def test_policy_trigger_valid_keyname_senlin_resources(self): tpl_snippet = ''' triggers: - resize_compute: @@ -1610,7 +1610,28 @@ heat-translator/master/translator/tests/data/custom_types/wordpress.yaml name = list(triggers.keys())[0] Triggers(name, triggers[name]) - def test_policy_trigger_invalid_keyname(self): + def test_policy_trigger_valid_keyname_heat_resources(self): + tpl_snippet = ''' + triggers: + - high_cpu_usage: + description: trigger + meter_name: cpu_util + condition: + constraint: utilization greater_than 60% + threshold: 60 + period: 600 + evaluations: 1 + method: average + comparison_operator: gt + metadata: SG1 + action: [SP1] + ''' + triggers = (toscaparser.utils.yamlparser. + simple_parse(tpl_snippet))['triggers'][0] + name = list(triggers.keys())[0] + Triggers(name, triggers[name]) + + def test_policy_trigger_invalid_keyname_senlin_resources(self): tpl_snippet = ''' triggers: - resize_compute: @@ -1646,6 +1667,34 @@ heat-translator/master/translator/tests/data/custom_types/wordpress.yaml lambda: Triggers(name, triggers[name])) self.assertEqual(expectedmessage, err.__str__()) + def test_policy_trigger_invalid_keyname_heat_resources(self): + tpl_snippet = ''' + triggers: + - high_cpu_usage: + description: trigger + meter_name: cpu_util + condition: + constraint: utilization greater_than 60% + threshold: 60 + period: 600 + evaluations: 1 + method: average + comparison_operator: gt + metadata1: SG1 + action: [SP1] + ''' + triggers = (toscaparser.utils.yamlparser. + simple_parse(tpl_snippet))['triggers'][0] + name = list(triggers.keys())[0] + expectedmessage = _( + 'Triggers "high_cpu_usage" contains unknown field ' + '"metadata1". Refer to the definition ' + 'to verify valid values.') + err = self.assertRaises( + exception.UnknownFieldError, + lambda: Triggers(name, triggers[name])) + self.assertEqual(expectedmessage, err.__str__()) + def test_policy_missing_required_keyname(self): tpl_snippet = ''' policies: diff --git a/tosca2heat/tosca-parser/toscaparser/triggers.py b/tosca2heat/tosca-parser/toscaparser/triggers.py index 9edeef4..8e47eee 100644 --- a/tosca2heat/tosca-parser/toscaparser/triggers.py +++ b/tosca2heat/tosca-parser/toscaparser/triggers.py @@ -16,12 +16,16 @@ import logging from toscaparser.common.exception import ExceptionCollector from toscaparser.common.exception import UnknownFieldError from toscaparser.entity_template import EntityTemplate - -SECTIONS = (DESCRIPTION, EVENT, SCHEDULE, TARGET_FILTER, CONDITION, ACTION) = \ - ('description', 'event_type', 'schedule', - 'target_filter', 'condition', 'action') -CONDITION_KEYNAMES = (CONTRAINT, PERIOD, EVALUATIONS, METHOD) = \ - ('constraint', 'period', 'evaluations', 'method') +from toscaparser.utils import validateutils + +SECTIONS = (DESCRIPTION, EVENT, SCHEDULE, METER_NAME, METADATA, + TARGET_FILTER, CONDITION, ACTION) = \ + ('description', 'event_type', 'schedule', 'meter_name', + 'metadata', 'target_filter', 'condition', 'action') +CONDITION_KEYNAMES = (CONSTRAINT, PERIOD, EVALUATIONS, METHOD, + THRESHOLD, COMPARISON_OPERATOR) = \ + ('constraint', 'period', 'evaluations', + 'method', 'threshold', 'comparison_operator') log = logging.getLogger('tosca') @@ -34,6 +38,7 @@ class Triggers(EntityTemplate): self.trigger_tpl = trigger_tpl self._validate_keys() self._validate_condition() + self._validate_input() def get_description(self): return self.trigger_tpl['description'] @@ -66,3 +71,12 @@ class Triggers(EntityTemplate): ExceptionCollector.appendException( UnknownFieldError(what='Triggers "%s"' % self.name, field=key)) + + def _validate_input(self): + for key, value in self.get_condition().items(): + if key in [PERIOD, EVALUATIONS]: + validateutils.validate_integer(value) + elif key == THRESHOLD: + validateutils.validate_numeric(value) + elif key in [METER_NAME, METHOD]: + validateutils.validate_string(value) diff --git a/tosca2heat/tosca-parser/tox.ini b/tosca2heat/tosca-parser/tox.ini index 8fc2a88..3603a18 100644 --- a/tosca2heat/tosca-parser/tox.ini +++ b/tosca2heat/tosca-parser/tox.ini @@ -1,5 +1,5 @@ [tox] -minversion = 1.6 +minversion = 2.0 envlist = py27,pep8 skipsdist = True @@ -28,9 +28,6 @@ commands = python setup.py build_sphinx commands = oslo_debug_helper -t toscaparser/tests {posargs} [flake8] -# H803 skipped on purpose per list discussion. -# E123, E125 skipped as they are invalid PEP-8. - show-source = True ignore = E123,E125,H803 builtins = _ |