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
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 allows for the setting alarms based on threshold evaluation
@ -176,19 +191,6 @@ class AodhAlarm(alarm_base.BaseAlarm):
kwargs['threshold_rule'] = rule
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):
record_reality = {}
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.
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
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))
class LBMemberHealthAlarm(alarm_base.BaseAlarm):
class LBMemberHealthAlarm(AodhBaseActionsMixin, alarm_base.BaseAlarm):
"""A resource that implements a Loadbalancer Member Health Alarm.
Allows setting alarms based on the health of load balancer pool members,
@ -411,18 +400,65 @@ class LBMemberHealthAlarm(alarm_base.BaseAlarm):
]
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))
class PrometheusAlarm(AodhBaseActionsMixin,
alarm_base.BaseAlarm):
"""A resource that implements Aodh alarm of type prometheus.
An alarm that evaluates threshold based on metric data fetched from
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():
@ -431,4 +467,5 @@ def resource_mapping():
'OS::Aodh::CombinationAlarm': CombinationAlarm,
'OS::Aodh::EventAlarm': EventAlarm,
'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',
'alarm_id': 'foo'}
@ -830,3 +852,98 @@ class LBMemberHealthAlarmTest(common.HeatTestCase):
self.assertEqual((rsrc.UPDATE, rsrc.COMPLETE), rsrc.state)
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.