1. 程式人生 > >Ceilometer 22、aodh告警元件核心功能原始碼分析

Ceilometer 22、aodh告警元件核心功能原始碼分析

本文目錄如下:
1 引言
2 複合告警解析規則
3 總結

預計讀完時間5分鐘左右。

1 引言


aodh是從ceilometer拆分出來的告警元件。之前已經分析過,這個元件主要的服務在於
aodh-evaluator服務: 每隔一定時間進行告警是否觸發的驗證,如果觸發,就傳送告警資訊給aodh-notifier服務
aodh-notifier服務: 進行告警通知的觸發。例如: webhook,日誌等告警通知器。
之前分析過,aodh-evaluator服務與aodh-notifier服務之間的通訊是藉助於訊息佇列(即rabbitmq)來完成。

2 複合告警規則解析


aodh這個元件在openstack各個元件中算是一個比較簡單的元件,看最近社群似乎在這個元件上幾乎沒有太多重要的更新了。
這個元件本身無非是進行告警的校驗,告警的通知這兩個主要功能。但是這個元件除了其服務的分工框架上可以學到一些以後
自研告警專案對於框架的整體劃分外。
我認為這個元件最核心的一個功能就是複合告警規則解析,這個功能完全是可以應用到以後
類似的很多基於規則解析的專案中。
下面是針對這部分內容的原始碼分析。

2.1 總入口


如果是複合告警規則型別的告警,則進入
原始碼:
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: 得到校驗結果後,進行後續處理

2.2 複合告警規則解析方法分析


該方法是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例項化得到物件,
    並返回。

2.3 AndOp和OrOp類分析


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

2.4 AlarmEvaluation分析


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物件

2.5 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方法進行該規則的校驗


2.6 複合告警規則解析相關類之間關係分析


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類

3 總結


aodh的複合告警規則解析是整個aodh告警元件的核心功能,舉一反三,
以後設計其他複合規則的解析,也可以基於遞迴做複合規則的解析。
複合規則的設計結構如下:
{ op: [rule_target,
       {op: [ rule_target, rule_target, ... ]},
         ... ]
}
其中:
op是操作符,例如: and或者or
rule_target是規則實體(即真正可解析的原子規則)
可基於自己的需要設計巢狀的規則結構

參考:
[1] https://github.com/openstack/aodh/tree/newton