diff options
author | shangxdy <shang.xiaodong@zte.com.cn> | 2017-07-17 17:40:59 +0800 |
---|---|---|
committer | shangxdy <shang.xiaodong@zte.com.cn> | 2017-07-17 17:40:59 +0800 |
commit | c7491340551cdd9d757d63b4bbe79132506e386a (patch) | |
tree | ae14fd406f3f0ebe415ce69bd9c6d0698af30d1e /tosca2heat/heat-translator/translator/hot/tosca | |
parent | 859e2826074aa51e0334d4607ccfcde5972e882e (diff) |
Synchronize upstream version of 0.9
Synchronize heat-translator wiht upstream versionn of 0.9
JIRA: PARSER-128
Change-Id: I4d2c62a0e81119d5c0305e3ac052415a6d5acee3
Signed-off-by: shangxdy <shang.xiaodong@zte.com.cn>
Diffstat (limited to 'tosca2heat/heat-translator/translator/hot/tosca')
7 files changed, 434 insertions, 19 deletions
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'} |