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:
parent
68377c69bb
commit
c5601ffac6
|
@ -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')
|
||||
|
||||
|
||||
|
|
|
@ -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 = ""
|
||||
|
|
|
@ -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
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
|
@ -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: {}
|
|
@ -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
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue