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.openstack.heat import none_resource
from heat.engine import support
from heat.engine import translation
class AodhAlarm(alarm_base.BaseAlarm):
@ -328,9 +329,108 @@ class EventAlarm(alarm_base.BaseAlarm):
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():
return {
'OS::Aodh::Alarm': AodhAlarm,
'OS::Aodh::CombinationAlarm': CombinationAlarm,
'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
# 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 template_format
from heat.engine.clients.os import aodh
from heat.engine.clients.os import octavia
from heat.engine import resource
from heat.engine.resources.openstack.aodh import alarm
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',
'alarm_id': 'foo'}
@ -709,3 +734,101 @@ class EventAlarmTest(common.HeatTestCase):
res.client().alarm.get.return_value = FakeAodhAlarm
scheduler.TaskRunner(res.create)()
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.