Add OS::Aodh::PrometheusAlarm resource

This patch adds support for Aodh alarms of type Prometheus.

Depends-On: I72e124cca4398b78f7ed12e1db3f66bdbfcb196e
Change-Id: I5bb7c4d9086715fc22c0f7abc36d9bbfc88a60c9
This commit is contained in:
Martin Mágr 2023-08-29 15:05:29 +02:00 committed by Martin Magr
parent a4ac653e35
commit f8a44f4904
3 changed files with 199 additions and 40 deletions

View File

@ -20,7 +20,22 @@ from heat.engine import support
from heat.engine import translation from heat.engine import translation
class AodhAlarm(alarm_base.BaseAlarm): class AodhBaseActionsMixin:
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))
class AodhAlarm(AodhBaseActionsMixin, alarm_base.BaseAlarm):
"""A resource that implements alarming service of Aodh. """A resource that implements alarming service of Aodh.
A resource that allows for the setting alarms based on threshold evaluation A resource that allows for the setting alarms based on threshold evaluation
@ -176,19 +191,6 @@ class AodhAlarm(alarm_base.BaseAlarm):
kwargs['threshold_rule'] = rule kwargs['threshold_rule'] = rule
return kwargs return kwargs
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 parse_live_resource_data(self, resource_properties, resource_data): def parse_live_resource_data(self, resource_properties, resource_data):
record_reality = {} record_reality = {}
threshold_data = resource_data.get('threshold_rule').copy() threshold_data = resource_data.get('threshold_rule').copy()
@ -232,7 +234,7 @@ class CombinationAlarm(none_resource.NoneResource):
) )
class EventAlarm(alarm_base.BaseAlarm): class EventAlarm(AodhBaseActionsMixin, alarm_base.BaseAlarm):
"""A resource that implements event alarms. """A resource that implements event alarms.
Allows users to define alarms which can be evaluated based on events Allows users to define alarms which can be evaluated based on events
@ -313,21 +315,8 @@ class EventAlarm(alarm_base.BaseAlarm):
kwargs['event_rule'] = rule kwargs['event_rule'] = rule
return kwargs return kwargs
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): class LBMemberHealthAlarm(AodhBaseActionsMixin, alarm_base.BaseAlarm):
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))
class LBMemberHealthAlarm(alarm_base.BaseAlarm):
"""A resource that implements a Loadbalancer Member Health Alarm. """A resource that implements a Loadbalancer Member Health Alarm.
Allows setting alarms based on the health of load balancer pool members, Allows setting alarms based on the health of load balancer pool members,
@ -411,18 +400,65 @@ class LBMemberHealthAlarm(alarm_base.BaseAlarm):
] ]
return translation_rules 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): class PrometheusAlarm(AodhBaseActionsMixin,
if prop_diff: alarm_base.BaseAlarm):
new_props = json_snippet.properties(self.properties_schema, """A resource that implements Aodh alarm of type prometheus.
self.context)
self.client().alarm.update(self.resource_id, An alarm that evaluates threshold based on metric data fetched from
self.get_alarm_props(new_props)) Prometheus.
"""
support_status = support.SupportStatus(version='22.0.0')
PROPERTIES = (
COMPARISON_OPERATOR, QUERY, THRESHOLD,
) = (
'comparison_operator', 'query', 'threshold',
)
properties_schema = {
COMPARISON_OPERATOR: properties.Schema(
properties.Schema.STRING,
_('Operator used to compare specified statistic with threshold.'),
constraints=[alarm_base.BaseAlarm.QF_OP_VALS],
update_allowed=True
),
QUERY: properties.Schema(
properties.Schema.STRING,
_('The PromQL query string to fetch metrics data '
'from Prometheus.'),
required=True,
update_allowed=True
),
THRESHOLD: properties.Schema(
properties.Schema.NUMBER,
_('Threshold to evaluate against.'),
required=True,
update_allowed=True
),
}
properties_schema.update(alarm_base.common_properties_schema)
alarm_type = 'prometheus'
def get_alarm_props(self, props):
kwargs = self.actions_to_urls(props)
kwargs['type'] = self.alarm_type
return self._reformat_properties(kwargs)
def parse_live_resource_data(self, resource_properties,
resource_data):
record_reality = {}
rule = self.alarm_type + '_rule'
data = resource_data.get(rule).copy()
data.update(resource_data)
for key in self.properties_schema.keys():
if key in alarm_base.INTERNAL_PROPERTIES:
continue
if self.properties_schema[key].update_allowed:
record_reality.update({key: data.get(key)})
return record_reality
def resource_mapping(): def resource_mapping():
@ -431,4 +467,5 @@ def resource_mapping():
'OS::Aodh::CombinationAlarm': CombinationAlarm, 'OS::Aodh::CombinationAlarm': CombinationAlarm,
'OS::Aodh::EventAlarm': EventAlarm, 'OS::Aodh::EventAlarm': EventAlarm,
'OS::Aodh::LBMemberHealthAlarm': LBMemberHealthAlarm, 'OS::Aodh::LBMemberHealthAlarm': LBMemberHealthAlarm,
'OS::Aodh::PrometheusAlarm': PrometheusAlarm,
} }

View File

@ -166,6 +166,28 @@ lbmemberhealth_alarm_template = '''
} }
''' '''
prometheus_alarm_template = '''
{
"heat_template_version" : "wallaby",
"description" : "Prometheus alarm test",
"parameters" : {},
"resources" : {
"test_prometheus_alarm": {
"type": "OS::Aodh::PrometheusAlarm",
"properties": {
"alarm_actions": [],
"threshold": "10",
"comparison_operator": "gt",
"query": "some_metric{some_label='some_value'}"
}
},
"signal_handler" : {
"type" : "SignalResourceType"
}
}
}
'''
FakeAodhAlarm = {'other_attrs': 'val', FakeAodhAlarm = {'other_attrs': 'val',
'alarm_id': 'foo'} 'alarm_id': 'foo'}
@ -830,3 +852,98 @@ class LBMemberHealthAlarmTest(common.HeatTestCase):
self.assertEqual((rsrc.UPDATE, rsrc.COMPLETE), rsrc.state) self.assertEqual((rsrc.UPDATE, rsrc.COMPLETE), rsrc.state)
self.assertEqual(1, update_mock.call_count) self.assertEqual(1, update_mock.call_count)
class PrometheusAlarmTest(common.HeatTestCase):
def setUp(self):
super(PrometheusAlarmTest, self).setUp()
self.fa = mock.Mock()
def create_stack(self, template=None):
if template is None:
template = prometheus_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(prometheus_alarm_template)
self.stack = utils.parse_stack(snippet)
res = self.stack['test_prometheus_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_prometheus_alarm']
rsrc.resource_id = '12345'
self.patchobject(aodh.AodhClientPlugin, 'client',
return_value=self.fa)
self.patchobject(self.fa.alarm, 'delete')
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_prometheus_alarm']
update_props = copy.deepcopy(rsrc.properties.data)
update_props.update({
'comparison_operator': 'lt',
'enabled': True,
'threshold': '9',
'insufficient_data_actions': [],
'alarm_actions': [],
'ok_actions': ['signal_handler'],
'query': "some_other_metric{some_other_label='value'}"
})
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,5 @@
---
features:
- |
Add OS::Aodh::PrometheusAlarm resource to enable autoscaling
with Prometheus instead of Gnocchi.