summaryrefslogtreecommitdiffstats
path: root/tosca2heat/heat-translator/translator/hot/tosca
diff options
context:
space:
mode:
Diffstat (limited to 'tosca2heat/heat-translator/translator/hot/tosca')
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_autoscaling.py2
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tests/test_tosca_floatingip.py71
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tosca_cluster_policies_scaling.py195
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tosca_compute.py52
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tosca_floating.py48
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tosca_policies_monitoring.py82
-rw-r--r--tosca2heat/heat-translator/translator/hot/tosca/tosca_policies_scaling.py3
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'}