diff options
Diffstat (limited to 'tosca2heat/heat-translator/translator')
23 files changed, 845 insertions, 66 deletions
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) |