Support Senlin auto-scaling policy in heat-translator

Implementation of Senlin auto-scaling policy in heat-translator

Change-Id: I83b24082789474ad99c8d383c6ca62aa830011a7
This commit is contained in:
beatitudo 2016-09-03 16:58:15 +09:00
parent 68377c69bb
commit c5601ffac6
9 changed files with 352 additions and 6 deletions

View File

@ -27,7 +27,9 @@ SECTIONS = (TYPE, PROPERTIES, MEDADATA, DEPENDS_ON, UPDATE_POLICY,
('type', 'properties', 'metadata',
'depends_on', 'update_policy', 'deletion_policy')
policy_type = ['tosca.policies.Placement', 'tosca.policies.Scaling']
policy_type = ['tosca.policies.Placement',
'tosca.policies.Scaling',
'tosca.policies.Scaling.Cluster']
log = logging.getLogger('heat-translator')

View File

@ -44,11 +44,11 @@ class HotTemplate(object):
nodes.append((node_key, node_value))
return yaml.nodes.MappingNode(u'tag:yaml.org,2002:map', nodes)
def output_to_yaml(self):
def output_to_yaml(self, hot_template_version=LATEST):
log.debug(_('Converting translated output to yaml format.'))
dict_output = OrderedDict()
# Version
version_string = self.VERSION + ": " + self.LATEST + "\n\n"
version_string = self.VERSION + ": " + hot_template_version + "\n\n"
# Description
desc_str = ""

View File

@ -0,0 +1,194 @@
#
# 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):
hot_type = "OS::Senlin::Policy"
super(ToscaClusterAutoscaling, self).__init__(policy,
type=hot_type)
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

View File

@ -41,7 +41,11 @@ class TOSCATranslator(object):
self.hot_template)
self.hot_template.resources = self.node_translator.translate()
self.hot_template.outputs = self._translate_outputs()
return self.hot_template.output_to_yaml()
if self.node_translator.hot_template_version:
return self.hot_template.\
output_to_yaml(self.node_translator.hot_template_version)
else:
return self.hot_template.output_to_yaml()
def _translate_inputs(self):
translator = TranslateInputs(self.tosca.inputs, self.parsed_params,

View File

@ -138,6 +138,9 @@ TOSCA_TO_HOT_TYPE = _generate_type_map()
BASE_TYPES = six.string_types + six.integer_types + (dict, OrderedDict)
HOT_SCALING_POLICY_TYPE = ["OS::Heat::AutoScalingGroup",
"OS::Senlin::Profile"]
class TranslateNodeTemplates(object):
'''Translate TOSCA NodeTemplates to Heat Resources.'''
@ -155,6 +158,7 @@ class TranslateNodeTemplates(object):
# stores the last deploy of generated behavior for a resource
# useful to satisfy underlying dependencies between interfaces
self.last_deploy_map = {}
self.hot_template_version = None
def translate(self):
return self._translate_nodetemplates()
@ -170,7 +174,8 @@ class TranslateNodeTemplates(object):
if resource.type == "OS::Nova::ServerGroup":
resource.handle_properties(self.hot_resources)
elif resource.type == "OS::Heat::ScalingPolicy":
elif resource.type in ("OS::Heat::ScalingPolicy",
"OS::Senlin::Policy"):
self.hot_resources = resource.handle_properties(self.hot_resources)
else:
resource.handle_properties()
@ -225,6 +230,8 @@ class TranslateNodeTemplates(object):
policy_type = policy.type_definition
if 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'
policy_node = TOSCA_TO_HOT_TYPE[policy_type.type](policy)
self.hot_resources.append(policy_node)
@ -304,7 +311,7 @@ class TranslateNodeTemplates(object):
# dependent nodes in correct order
self.processed_resources = []
for resource in self.hot_resources:
if resource.type != "OS::Heat::AutoScalingGroup":
if resource.type not in HOT_SCALING_POLICY_TYPE:
self._recursive_handle_properties(resource)
# handle resources that need to expand to more than one HOT resource

View File

@ -0,0 +1,11 @@
tosca_definitions_version: tosca_simple_yaml_1_0
description: >
The TOSCA Policy Type definition that is used to govern
Senlin Policy of TOSCA nodes or groups of nodes
policy_types:
tosca.policies.Scaling.Cluster:
derived_from: tosca.policies.Scaling
description: The TOSCA Policy Type definition that is used to govern
scaling of TOSCA nodes or groups of nodes.

View File

@ -0,0 +1,60 @@
heat_template_version: 2016-04-08
description: >
Template for deploying servers based on policies.
parameters: {}
resources:
my_server_1:
type: OS::Senlin::Profile
properties:
type: os.nova.server-1.0
properties:
flavor: m1.medium
image: rhel-6.5-test-image
networks:
- network: net0
cluster_scaling_scale_out:
type: OS::Senlin::Policy
properties:
bindings:
- cluster:
get_resource: my_server_1_cluster
type: senlin.policy.scaling-1.0
properties:
adjustment:
type: CHANGE_IN_CAPACITY
number: 1
event: CLUSTER_SCALE_OUT
my_server_1_cluster:
type: OS::Senlin::Cluster
properties:
profile:
get_resource: my_server_1
min_size: 2
max_size: 10
desired_capacity: 3
my_server_1_scale_out_receiver:
type: OS::Senlin::Receiver
properties:
action: CLUSTER_SCALE_OUT
cluster:
get_resource: my_server_1_cluster
type: webhook
scale_out_alarm:
type: OS::Aodh::Alarm
properties:
meter_name: cpu_util
alarm_actions:
- get_attr:
- my_server_1_scale_out_receiver
- channel
- alarm_url
description: Cluster node autoscaling
evaluation_periods: 1
repeat_actions: True
period: 60
statistic: avg
threshold: 50
comparison_operator: gt
outputs: {}

View File

@ -0,0 +1,62 @@
tosca_definitions_version: tosca_simple_yaml_1_0
description: >
Template for deploying servers based on policies.
imports:
- custom_types/senlin_cluster_policies.yaml
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
my_port_1:
type: tosca.nodes.network.Port
requirements:
- link:
node: my_network_1
- binding:
node: my_server_1
my_network_1:
type: tosca.nodes.network.Network
properties:
network_name: net0
policies:
- cluster_scaling:
type: tosca.policies.Scaling.Cluster
description: Cluster node autoscaling
targets: [my_server_1]
triggers:
scale_out:
description: trigger
event_type:
type: tosca.events.resource.cpu.utilization
metrics: cpu_util
implementation: Ceilometer
condition:
constraint: utilization greater_than 50%
period: 60
evaluations: 1
method: average
action:
scale_out:
type: SCALE_OUT
implementation: Senlin.webhook
properties:
min_instances: 2
max_instances: 10
default_instances: 3
increment: 1

View File

@ -510,3 +510,9 @@ class ToscaHotTranslationTest(TestCase):
err = self.assertRaises(UnsupportedTypeError,
TOSCATranslator(tosca, params).translate)
self.assertEqual(expected_msg, err.__str__())
def test_hot_translate_cluster_scaling_policy(self):
tosca_file = '../tests/data/tosca_cluster_autoscaling.yaml'
hot_file = '../tests/data/hot_output/hot_cluster_autoscaling.yaml'
params = {}
self._test_successful_translation(tosca_file, hot_file, params)