Add heat resource for creating Aodh loadbalancer_member_health alarm type[1]

Aodh loadbalancer_member_health alarm was implemented in 654221[2]. This patch
adds a corresponding heat resource for managing it's lifecycle.

 - Add new resource OS:Aodh:LBMemberHealthAlarm
 - Add unit test for LoadBalancerMemberHealthAlarms

[1]: https://docs.openstack.org/aodh/latest/contributor/architecture.html#id5
[2]: https://review.opendev.org/#/c/654221/

Change-Id: I26582fbd0b980d848f7e4cdb8bb2c8833385fe9b
Task: 34767
This commit is contained in:
Simon Merrick 2019-05-31 16:38:26 +12:00
parent 70d7a1bc86
commit 3fda946ff3
3 changed files with 227 additions and 0 deletions

View File

@ -19,6 +19,7 @@ from heat.engine import properties
from heat.engine.resources import alarm_base from heat.engine.resources import alarm_base
from heat.engine.resources.openstack.heat import none_resource from heat.engine.resources.openstack.heat import none_resource
from heat.engine import support from heat.engine import support
from heat.engine import translation
class AodhAlarm(alarm_base.BaseAlarm): class AodhAlarm(alarm_base.BaseAlarm):
@ -328,9 +329,108 @@ class EventAlarm(alarm_base.BaseAlarm):
self.get_alarm_props(new_props)) self.get_alarm_props(new_props))
class LBMemberHealthAlarm(alarm_base.BaseAlarm):
"""A resource that implements a Loadbalancer Member Health Alarm.
Allows setting alarms based on the health of load balancer pool members,
where the health of a member is determined by the member reporting an
operating_status of ERROR beyond an initial grace period after creation
(120 seconds by default).
"""
alarm_type = "loadbalancer_member_health"
support_status = support.SupportStatus(version='13.0.0')
PROPERTIES = (
POOL, STACK, AUTOSCALING_GROUP_ID
) = (
"pool", "stack", "autoscaling_group_id"
)
RULE_PROPERTIES = (
POOL_ID, STACK_ID
) = (
"pool_id", "stack_id"
)
properties_schema = {
POOL: properties.Schema(
properties.Schema.STRING,
_("Name or ID of the loadbalancer pool for which the health of "
"each member will be evaluated."),
update_allowed=True,
required=True,
),
STACK: properties.Schema(
properties.Schema.STRING,
_("Name or ID of the root / top level Heat stack containing the "
"loadbalancer pool and members. An update will be triggered "
"on the root Stack if an unhealthy member is detected in the "
"loadbalancer pool."),
update_allowed=False,
required=True,
),
AUTOSCALING_GROUP_ID: properties.Schema(
properties.Schema.STRING,
_("ID of the Heat autoscaling group that contains the "
"loadbalancer members. Unhealthy members will be marked "
"as such before an update is triggered on the root stack."),
update_allowed=True,
required=True,
),
}
properties_schema.update(alarm_base.common_properties_schema)
def get_alarm_props(self, props):
"""Apply all relevant compatibility xforms."""
kwargs = self.actions_to_urls(props)
kwargs['type'] = self.alarm_type
for prop in (self.POOL, self.STACK, self.AUTOSCALING_GROUP_ID):
if prop in kwargs:
del kwargs[prop]
rule = {
self.POOL_ID: props[self.POOL],
self.STACK_ID: props[self.STACK],
self.AUTOSCALING_GROUP_ID: props[self.AUTOSCALING_GROUP_ID]
}
kwargs["loadbalancer_member_health_rule"] = rule
return kwargs
def translation_rules(self, properties):
translation_rules = [
translation.TranslationRule(
properties,
translation.TranslationRule.RESOLVE,
[self.POOL],
client_plugin=self.client_plugin('octavia'),
finder='get_pool'
),
]
return translation_rules
def handle_create(self):
props = self.get_alarm_props(self.properties)
props['name'] = self.physical_resource_name()
alarm = self.client().alarm.create(props)
self.resource_id_set(alarm['alarm_id'])
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
if prop_diff:
new_props = json_snippet.properties(self.properties_schema,
self.context)
self.client().alarm.update(self.resource_id,
self.get_alarm_props(new_props))
def resource_mapping(): def resource_mapping():
return { return {
'OS::Aodh::Alarm': AodhAlarm, 'OS::Aodh::Alarm': AodhAlarm,
'OS::Aodh::CombinationAlarm': CombinationAlarm, 'OS::Aodh::CombinationAlarm': CombinationAlarm,
'OS::Aodh::EventAlarm': EventAlarm, 'OS::Aodh::EventAlarm': EventAlarm,
'OS::Aodh::LBMemberHealthAlarm': LBMemberHealthAlarm,
} }

View File

@ -1,3 +1,4 @@
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
@ -20,6 +21,7 @@ import six
from heat.common import exception from heat.common import exception
from heat.common import template_format from heat.common import template_format
from heat.engine.clients.os import aodh from heat.engine.clients.os import aodh
from heat.engine.clients.os import octavia
from heat.engine import resource from heat.engine import resource
from heat.engine.resources.openstack.aodh import alarm from heat.engine.resources.openstack.aodh import alarm
from heat.engine import rsrc_defn from heat.engine import rsrc_defn
@ -142,6 +144,29 @@ event_alarm_template = '''
} }
''' '''
lbmemberhealth_alarm_template = '''
{
"heat_template_version" : "newton",
"description" : "Loadbalancer member health alarm test",
"parameters" : {},
"resources" : {
"test_loadbalancer_member_health_alarm": {
"type": "OS::Aodh::LBMemberHealthAlarm",
"properties": {
"description": "Something something dark side",
"alarm_actions": ["trust+heat://"],
"repeat_actions": false,
"pool": "12345",
"stack": "13579",
"autoscaling_group_id": "02468"
}
},
"signal_handler" : {
"type" : "SignalResourceType"
}
}
}
'''
FakeAodhAlarm = {'other_attrs': 'val', FakeAodhAlarm = {'other_attrs': 'val',
'alarm_id': 'foo'} 'alarm_id': 'foo'}
@ -709,3 +734,101 @@ class EventAlarmTest(common.HeatTestCase):
res.client().alarm.get.return_value = FakeAodhAlarm res.client().alarm.get.return_value = FakeAodhAlarm
scheduler.TaskRunner(res.create)() scheduler.TaskRunner(res.create)()
self.assertEqual(FakeAodhAlarm, res.FnGetAtt('show')) self.assertEqual(FakeAodhAlarm, res.FnGetAtt('show'))
class LBMemberHealthAlarmTest(common.HeatTestCase):
def setUp(self):
super(LBMemberHealthAlarmTest, self).setUp()
self.fa = mock.Mock()
self.patchobject(
octavia.OctaviaClientPlugin, 'get_pool').return_value = "9999"
def create_stack(self, template=None):
if template is None:
template = lbmemberhealth_alarm_template
temp = template_format.parse(template)
template = tmpl.Template(temp)
ctx = utils.dummy_context()
ctx.tenant = 'test_tenant'
stack = parser.Stack(ctx, utils.random_name(), template,
disable_rollback=True)
stack.store()
self.patchobject(aodh.AodhClientPlugin,
'_create').return_value = self.fa
self.patchobject(self.fa.alarm, 'create').return_value = FakeAodhAlarm
return stack
def _prepare_resource(self, for_check=True):
snippet = template_format.parse(lbmemberhealth_alarm_template)
self.stack = utils.parse_stack(snippet)
res = self.stack['test_loadbalancer_member_health_alarm']
if for_check:
res.state_set(res.CREATE, res.COMPLETE)
res.client = mock.Mock()
mock_alarm = mock.Mock(enabled=True, state='ok')
res.client().alarm.get.return_value = mock_alarm
return res
def test_delete(self):
test_stack = self.create_stack()
rsrc = test_stack['test_loadbalancer_member_health_alarm']
self.patchobject(aodh.AodhClientPlugin, 'client',
return_value=self.fa)
self.patchobject(self.fa.alarm, 'delete')
rsrc.resource_id = '12345'
self.assertEqual('12345', rsrc.handle_delete())
self.assertEqual(1, self.fa.alarm.delete.call_count)
def test_check(self):
res = self._prepare_resource()
scheduler.TaskRunner(res.check)()
self.assertEqual((res.CHECK, res.COMPLETE), res.state)
def test_check_alarm_failure(self):
res = self._prepare_resource()
res.client().alarm.get.side_effect = Exception('Boom')
self.assertRaises(exception.ResourceFailure,
scheduler.TaskRunner(res.check))
self.assertEqual((res.CHECK, res.FAILED), res.state)
self.assertIn('Boom', res.status_reason)
def test_show_resource(self):
res = self._prepare_resource(for_check=False)
res.client().alarm.create.return_value = FakeAodhAlarm
res.client().alarm.get.return_value = FakeAodhAlarm
scheduler.TaskRunner(res.create)()
self.assertEqual(FakeAodhAlarm, res.FnGetAtt('show'))
def test_update(self):
test_stack = self.create_stack()
update_mock = self.patchobject(self.fa.alarm, 'update')
test_stack.create()
rsrc = test_stack['test_loadbalancer_member_health_alarm']
update_props = copy.deepcopy(rsrc.properties.data)
update_props.update({
"enabled": True,
"description": "",
"insufficient_data_actions": [],
"alarm_actions": [],
"ok_actions": ["signal_handler"],
"pool": "0000",
"autoscaling_group_id": "2222"
})
snippet = rsrc_defn.ResourceDefinition(rsrc.name,
rsrc.type(),
update_props)
scheduler.TaskRunner(rsrc.update, snippet)()
self.assertEqual((rsrc.UPDATE, rsrc.COMPLETE), rsrc.state)
self.assertEqual(1, update_mock.call_count)

View File

@ -0,0 +1,4 @@
---
features:
- OS::Aodh::LBMemberHealthAlarm resource plugin is added to manage
Aodh loadbalancer_member_health alarm.