本文目录如下:
1 引言
2 复合告警解析规则
3 总结
aodh是从ceilometer拆分出来的告警组件。之前已经分析过,这个组件主要的服务在于
aodh-evaluator服务: 每隔一定时间进行告警是否触发的验证,如果触发,就发送告警信息给aodh-notifier服务
aodh-notifier服务: 进行告警通知的触发。例如: webhook,日志等告警通知器。
之前分析过,aodh-evaluator服务与aodh-notifier服务之间的通信是借助于消息队列(即rabbitmq)来完成。
aodh这个组件在openstack各个组件中算是一个比较简单的组件,看最近社区似乎在这个组件上几乎没有太多重要的更新了。
这个组件本身无非是进行告警的校验,告警的通知这两个主要功能。但是这个组件除了其服务的分工框架上可以学到一些以后
自研告警项目对于框架的整体划分外。
我认为这个组件最核心的一个功能就是复合告警规则解析,这个功能完全是可以应用到以后
类似的很多基于规则解析的项目中。
下面是针对这部分内容的源码分析。
如果是复合告警规则类型的告警,则进入
源码:
aodh/evaluator/composite.py的CompositeEvaluator类的evaluate方法
该方法具体如下:
def evaluate(self, alarm):
if not self.within_time_constraint(alarm):
LOG.debug('Attempted to evaluate alarm %s, but it is not '
'within its time constraint.', alarm.alarm_id)
return
LOG.debug("Evaluating composite rule alarm %s ...", alarm.alarm_id)
self.rule_targets = []
self.rule_num = 0
rule_target_alarm, rule_target_ok = self._parse_composite_rule(
alarm.rule)
sufficient = self._evaluate_sufficient(alarm, rule_target_alarm,
rule_target_ok)
if not sufficient:
for rule in self.rule_targets:
rule.evaluate()
sufficient = self._evaluate_sufficient(alarm, rule_target_alarm,
rule_target_ok)
if not sufficient:
# The following unknown situations is like these:
# 1. 'unknown' and 'alarm'
# 2. 'unknown' or 'ok'
reason, reason_data = self._reason(alarm, evaluator.UNKNOWN,
rule_target_alarm)
if alarm.state != evaluator.UNKNOWN:
self._refresh(alarm, evaluator.UNKNOWN, reason, reason_data)
else:
LOG.debug(reason)
分析:
1) 上述方法中最重要的内容如下:
rule_target_alarm, rule_target_ok = self._parse_composite_rule(
alarm.rule)
这个方法就是对复合告警规则解析
2) evaluate: 总入口方法,
步骤1: 先解析复合规则
步骤2: 对解析的复合规则调用bool方法
步骤3: 得到校验结果后,进行后续处理
该方法是aodh/evaluator/composite.py的CompositeEvaluator类的
_parse_composite_rule方法,
方法定义如下:
def _parse_composite_rule(self, alarm_rule):
"""Parse the composite rule.
The composite rule is assembled by sub threshold rules with 'and',
'or', the form can be nested. e.g. the form of composite rule can be
like this:
{
"and": [threshold_rule0, threshold_rule1,
{'or': [threshold_rule2, threshold_rule3,
threshold_rule4, threshold_rule5]}]
}
"""
if (isinstance(alarm_rule, dict) and len(alarm_rule) == 1
and list(alarm_rule)[0] in ('and', 'or')):
and_or_key = list(alarm_rule)[0]
if and_or_key == 'and':
rules = (self._parse_composite_rule(r) for r in
alarm_rule['and'])
rules_alarm, rules_ok = zip(*rules)
return AndOp(rules_alarm), OrOp(rules_ok)
else:
rules = (self._parse_composite_rule(r) for r in
alarm_rule['or'])
rules_alarm, rules_ok = zip(*rules)
return OrOp(rules_alarm), AndOp(rules_ok)
else:
rule_evaluator = self.threshold_evaluators[alarm_rule['type']].obj
self.rule_num += 1
name = self.rule_name_prefix + str(self.rule_num)
rule = RuleTarget(alarm_rule, rule_evaluator, name)
self.rule_targets.append(rule)
return AlarmEvaluation(rule), OkEvaluation(rule)
分析:
1) 输入参数alarm_rule分析,这个alarm_rule本身就是一个复合告警规则,实际上是一个字典,
例如:
{
'or': [{
'evaluation_periods': 5,
'meter_name': 'cpu_util',
'exclude_outliers': False,
'statistic': 'avg',
'threshold': 0.8,
'period': 60,
'query': [{
'value': '36b20eb3-d749-4964-a7d2-a71147cd8145',
'op': 'eq',
'field': 'metadata.metering.stack_id'
}],
'type': 'threshold',
'comparison_operator': 'gt'
}, {
'and': [{
'evaluation_periods': 4,
'meter_name': 'disk.iops',
'exclude_outliers': False,
'statistic': 'max',
'threshold': 200,
'period': 60,
'query': [{
'value': '36b20eb3-d749-4964-a7d2-a71147cd8145',
'op': 'eq',
'field': 'metadata.metering.stack_id'
}],
'type': 'threshold',
'comparison_operator': 'gt'
}, {
'evaluation_periods': 3,
'meter_name': 'network.incoming.packets.rate',
'exclude_outliers': False,
'statistic': 'avg',
'threshold': 1000,
'period': 60,
'query': [{
'value': '36b20eb3-d749-4964-a7d2-a71147cd8145',
'op': 'eq',
'field': 'metadata.metering.stack_id'
}],
'type': 'threshold',
'comparison_operator': 'gt'
}]
}]
}
为了能够更方便地看出这个规则的结构,我把一些字典,例如
{
'evaluation_periods': 5,
'meter_name': 'cpu_util',
'exclude_outliers': False,
'statistic': 'avg',
'threshold': 0.8,
'period': 60,
'query': [{
'value': '36b20eb3-d749-4964-a7d2-a71147cd8145',
'op': 'eq',
'field': 'metadata.metering.stack_id'
}],
'type': 'threshold',
'comparison_operator': 'gt'
}
就用一个alarm代替,那么经过这样的简化,整个规则变成了下面的格式:
{
'or': [ alarm1,
{ 'and': [alarm2, alarm3]
}
]
}
分析这个复合规则的结构:
规则本身是一个字典,
A)键是操作符号: or或and;
B)值是一个列表,列表中的每个元素可以是如下的类型:
B.1) 一个告警,字典类型,具体描述这个具体的告警
B.2) 一个复合告警规则,是字典类型,键为and或or,值是列表,列表又可以是B),如此构成一个递归的复合规则。
2)分析这个复合规则解析方法
这个方法本身是递归的。这个才是解析复合告警规则的核心。
def _parse_composite_rule(self, alarm_rule):
"""Parse the composite rule.
The composite rule is assembled by sub threshold rules with 'and',
'or', the form can be nested. e.g. the form of composite rule can be
like this:
{
"and": [threshold_rule0, threshold_rule1,
{'or': [threshold_rule2, threshold_rule3,
threshold_rule4, threshold_rule5]}]
}
"""
if (isinstance(alarm_rule, dict) and len(alarm_rule) == 1
and list(alarm_rule)[0] in ('and', 'or')):
and_or_key = list(alarm_rule)[0]
if and_or_key == 'and':
rules = (self._parse_composite_rule(r) for r in
alarm_rule['and'])
rules_alarm, rules_ok = zip(*rules)
return AndOp(rules_alarm), OrOp(rules_ok)
else:
rules = (self._parse_composite_rule(r) for r in
alarm_rule['or'])
rules_alarm, rules_ok = zip(*rules)
return OrOp(rules_alarm), AndOp(rules_ok)
else:
rule_evaluator = self.threshold_evaluators[alarm_rule['type']].obj
self.rule_num += 1
name = self.rule_name_prefix + str(self.rule_num)
rule = RuleTarget(alarm_rule, rule_evaluator, name)
self.rule_targets.append(rule)
return AlarmEvaluation(rule), OkEvaluation(rule)
步骤1: 判断如果当前规则如果是复合规则(规则是字典,长度为1,并且第一个元素是and或or); 否则转步骤2
1.1 如果该规则是and规则,则编历当前and规则对应的规则列表,对每个规则递归解析,得到规则列表,
将规则列表传递给AndOp,并返回
1.2 如果该规则是or规则,则编历当前or规则对应的规则列表,对每个规则递归解析,得到规则列表,
将规则列表传递给OrOp,并返回
步骤2: 说明当前规则是一个原子规则,获取该规则对应的规则校验器,规则个数累加,用规则,规则校验器,规则名称,
实例化得到一个规则实体,将该规则实体加入到规则列表中。将规则实体传递给AlarmEvaluation实例化得到对象,
并返回。
AndOp类具体定义如下:
class AndOp(object):
def __init__(self, rule_targets):
self.rule_targets = rule_targets
def __bool__(self):
return all(self.rule_targets)
def __str__(self):
return '(' + ' and '.join(six.moves.map(str, self.rule_targets)) + ')'
__nonzero__ = __bool__
OrOp类具体定义如下:
class OrOp(object):
def __init__(self, rule_targets):
self.rule_targets = rule_targets
def __bool__(self):
return any(self.rule_targets)
def __str__(self):
return '(' + ' or '.join(six.moves.map(str, self.rule_targets)) + ')'
__nonzero__ = __bool__
分析:
1)
AndOp: And操作符类,接受规则实体列表rule_targets,定义了__bool__方法为all(rule_targets),
从而可以调用各个规则自身的__bool__方法。并令__nonzero__等于__bool__。
OrOp: Or操作符类,接受规则实体列表rule_targets,定义了__bool__方法为any(rule_targets),
从而可以调用各个规则自身的__bool__方法。并令__nonzero__等于__bool__。
2) 这里的关键就是这两个类的成员变量rule_targets,下面具体分析rule_targets,
根据_parse_composite_rule方法的代码:
rules = (self._parse_composite_rule(r) for r in
alarm_rule['and'])
rules_alarm, rules_ok = zip(*rules)
return AndOp(rules_alarm), OrOp(rules_ok)
可知,rules_alarm或者rules_ok都是源于真正的实体告警规则,也就是
parse_composite_rule方法中如下代码的返回值:
rule_evaluator = self.threshold_evaluators[alarm_rule['type']].obj
self.rule_num += 1
name = self.rule_name_prefix + str(self.rule_num)
rule = RuleTarget(alarm_rule, rule_evaluator, name)
self.rule_targets.append(rule)
return AlarmEvaluation(rule), OkEvaluation(rule)
那么最终可以确认AndOp或者OrOp中的rule_targest这个成员变量的值实际是
AlarmEvaluation(rule)
而rule又是RuleTarget类对象。
问题的关键就是AlarmEvaluation,RuleTarget分别代表什么。
AlarmEvaluation的分析参见2.4, RuleTarget的分析参见2.5
AlarmEvaluation定义如下:
class AlarmEvaluation(RuleEvaluationBase):
def __bool__(self):
self.rule_target.evaluate()
return self.rule_target.state == evaluator.ALARM
__nonzero__ = __bool__
分析:
1) AlarmEvaluation: 继承自RuleEvaluationBase类,实现了__bool__方法,调用RuleTarget对象
的evaluate方法生成的结果和指定结果比较,返回该实体规则最终是否校验成功。
并令__nonzero__等于__bool__
2) RuleEvaluationBase类定义如下
class RuleEvaluationBase(object):
def __init__(self, rule_target):
self.rule_target = rule_target
def __str__(self):
return self.rule_target.rule_name
分析:
2.1) RuleEvaluationBase: 规则校验基类,聚合了RuleTarget对象
RuleTaeget类定义如下:
class RuleTarget(object):
def __init__(self, rule, rule_evaluator, rule_name):
self.rule = rule
self.type = rule.get('type')
self.rule_evaluator = rule_evaluator
self.rule_name = rule_name
self.state = None
self.trending_state = None
self.statistics = None
self.evaluated = False
def evaluate(self):
# Evaluate a sub-rule of composite rule
if not self.evaluated:
LOG.debug('Evaluating %(type)s rule: %(rule)s',
{'type': self.type, 'rule': self.rule})
self.state, self.trending_state, self.statistics, __ = \
self.rule_evaluator.evaluate_rule(self.rule)
self.evaluated = True
分析:
1) RuleTarget: 具体的规则实体,包含具体的规则信息,以及一个evaluate方法进行该规则的校验
RuleTarget: 具体的规则实体,包含具体的规则信息,以及一个evaluate方法进行该规则的校验
RuleEvaluationBase: 规则校验基类,聚合了RuleTarget对象
AlarmEvaluation: 继承自RuleEvaluationBase类,实现了__bool__方法,调用RuleTarget对象
的evaluate方法生成的结果和指定结果比较,返回该实体规则最终是否校验成功。
并令__nonzero__等于__bool__
AndOp: And操作符类,接受规则实体列表rule_targets,定义了__bool__方法为all(rule_targets),
从而可以调用各个规则自身的__bool__方法。并令__nonzero__等于__bool__。
OrOp: Or操作符类,接受规则实体列表rule_targets,定义了__bool__方法为any(rule_targets),
从而可以调用各个规则自身的__bool__方法。并令__nonzero__等于__bool__。
CompositeEvaluator: 组合校验器。包含规则个数,规则实体列表,配置等对象。
类之间的关系:
CompositeEvaluator:使用了AndOp,OrOp,AlarmEvaluation类
AndOp: 聚合了AlarmEvaluation类
OpOp: 聚合了AlarmEvaluation类
AlarmEvaluation: 聚合了RuleTarget类
aodh的复合告警规则解析是整个aodh告警组件的核心功能,举一反三,
以后设计其他复合规则的解析,也可以基于递归做复合规则的解析。
复合规则的设计结构如下:
{ op: [rule_target,
{op: [ rule_target, rule_target, ... ]},
... ]
}
其中:
op是操作符,例如: and或者or
rule_target是规则实体(即真正可解析的原子规则)
可基于自己的需要设计嵌套的规则结构
参考:
[1] https://github.com/openstack/aodh/tree/newton