目标:
弄清楚st2rulesengine服务原理
1和2的分析参见:
https://blog.csdn.net/qingyuanluofeng/article/details/105374863
3的分析参见:
https://blog.csdn.net/qingyuanluofeng/article/details/105374978
4 分析RulesEngine规则引擎
分析RulesEngine类对象的handle_trigger_instance方法
class RulesEngine(object):
def handle_trigger_instance(self, trigger_instance):
# Find matching rules for trigger instance.
matching_rules = self.get_matching_rules_for_trigger(trigger_instance)
if matching_rules:
# Create rule enforcers.
enforcers = self.create_rule_enforcers(trigger_instance, matching_rules)
# Enforce the rules.
self.enforce_rules(enforcers)
else:
LOG.info('No matching rules found for trigger instance %s.', trigger_instance['id'])
分析:
handle_trigger_instance(self, trigger_instance):
1 根据给定的trigger_instance,形如:
(Pdb) p trigger_instance
<TriggerInstanceDB: TriggerInstanceDB(id=5e79d4d58dc0dd00ea0f362f, occurrence_time="2020-03-24 09:31:27.463733+00:00", payload={'executed_at': '2020-03-24 09:17:33.887548+00:00', 'schedule': None}, status="processing", trigger="core.8c13d9a3-12a3-4cff-88d3-66c804a47590")>
2 调用get_matching_rules_for_trigger方法,获得匹配的rule列表,具体是:
3 调用create_rule_enforcers(self, trigger_instance, matching_rules):
遍历所有匹配的rule列表,对每个rule,实例化一个RuleEnforcer(trigger_instance, matching_rule)并加入
结果列表中,最后返回结果列表
4 调用enforce_rules(self, enforcers):
遍历enforcers中每个enforcer,对每个enforcer,调用enforce方法,具体是
4.1) 分析get_matching_rules_for_trigger方法
class RulesEngine(object):
def get_matching_rules_for_trigger(self, trigger_instance):
trigger = trigger_instance.trigger
trigger_db = get_trigger_db_by_ref(trigger_instance.trigger)
if not trigger_db:
LOG.error('No matching trigger found in db for trigger instance %s.', trigger_instance)
return None
rules = get_rules_given_trigger(trigger=trigger)
LOG.info('Found %d rules defined for trigger %s', len(rules),
trigger_db.get_reference().ref)
if len(rules) < 1:
return rules
matcher = RulesMatcher(trigger_instance=trigger_instance,
trigger=trigger_db, rules=rules)
matching_rules = matcher.get_matching_rules()
LOG.info('Matched %s rule(s) for trigger_instance %s (trigger=%s)', len(matching_rules),
trigger_instance['id'], trigger_db.ref)
return matching_rules
分析:
get_matching_rules_for_trigger(self, trigger_instance):
1 根据给定trigger_instance中的trigger信息,形如:
(Pdb) p trigger
u'core.8c13d9a3-12a3-4cff-88d3-66c804a47590'
向trigger_d_b表查询该trigger记录
2 根据trigger记录查询到对应的rules。
原理: 由于特定的触发器会在trigger表生成一条trigger记录,包含ref字段。
而rule中会持有这个trigger的ref。所以通过trigger的ref可以查询到对应的rule。
3 用trigger_instance,trigger,rules实例化一个RulesMatcher。
对rules列表处理,对每个rule根据trigger_instance,trigger等信息实例化RuleFileter对象, 调用其filter方法,
判断其trigger_instance中的payload是否符合rule中criteria字典的校验要求。返回匹配criteria的rules列表。
4.2) 分析enforce_rules(self, enforcers)方法
class RulesEngine(object):
def enforce_rules(self, enforcers):
for enforcer in enforcers:
try:
enforcer.enforce() # Should this happen in an eventlet pool?
except:
LOG.exception('Exception enforcing rule %s.', enforcer.rule)
分析:
遍历enforcers中每个enforcer,对每个enforcer,调用enforce方法,
4.2.1) 调用RuleEnforcer的
class RuleEnforcer(object):
def __init__(self, trigger_instance, rule):
self.trigger_instance = trigger_instance
self.rule = rule
try:
self.data_transformer = get_transformer(trigger_instance.payload)
except Exception as e:
message = ('Failed to template-ize trigger payload: %s. If the payload contains '
'special characters such as "{
{" which dont\'t reference value in '
'a datastore, those characters need to be escaped' % (str(e)))
raise ValueError(message)
def enforce(self):
rule_spec = {'ref': self.rule.ref, 'id': str(self.rule.id), 'uid': self.rule.uid}
enforcement_db = RuleEnforcementDB(trigger_instance_id=str(self.trigger_instance.id),
rule=rule_spec)
extra = {
'trigger_instance_db': self.trigger_instance,
'rule_db': self.rule
}
execution_db = None
try:
execution_db = self._do_enforce()
# pylint: disable=no-member
enforcement_db.execution_id = str(execution_db.id)
extra['execution_db'] = execution_db
except Exception as e:
# Record the failure reason in the RuleEnforcement.
enforcement_db.failure_reason = e.message
LOG.exception('Failed kicking off execution for rule %s.', self.rule, extra=extra)
finally:
self._update_enforcement(enforcement_db)
# pylint: disable=no-member
if not execution_db or execution_db.status not in EXEC_KICKED_OFF_STATES:
LOG.audit('Rule enforcement failed. Execution of Action %s failed. '
'TriggerInstance: %s and Rule: %s',
self.rule.action.ref, self.trigger_instance, self.rule,
extra=extra)
else:
LOG.audit('Rule enforced. Execution %s, TriggerInstance %s and Rule %s.',
execution_db, self.trigger_instance, self.rule, extra=extra)
return execution_db
分析:
enforce(self):
1 根据trigger_instance_id和 rule_spec具体是一个字典,实例化一个rule_enforcement_d_b
rule_spec 样例:{'ref': self.rule.ref, 'id': str(self.rule.id), 'uid': self.rule.uid}
2 调用_do_enforce方法,具体是:
3 指定enforcement_db的execution_id为execution对象的id,相当于做了关联,然后更新rule_enforcement,
4 返回execution_db,样例如下:
<ActionExecutionDB: ActionExecutionDB(action={'name': u'mistral-network-check', 'runner_type': u'mistral-v2', 'tags': [], 'description': u'Run network check script and output the result then send email.', 'enabled': True, 'entry_point': u'workflows/mistral-network-check.yaml', 'notify': {}, 'uid': u'action:email:mistral-network-check', 'parameters': {u'email_account': {u'default': u'esdozer', u'type': u'string'}, u'cmd': {u'required': True, u'type': u'string'}, u'email_to': {u'required': True, u'type': u'array'}, u'email_from': {u'type': u'string'}, u'timeout': {u'default': 1800, u'type': u'integer'}}, 'ref': u'email.mistral-network-check', 'id': '5e8067ae146db400130967e8', 'pack': u'email'}, children=[], context={'trigger_instance': {'id': '5e81d44bb99f2a002b34aeed', 'name': None}, 'trace_context': {'id_': '5e81d44bb99f2a002b34aeee', 'trace_tag': u'st2.IntervalTimer-ac7abf3a-9966-480a-8376-1b156bdbef56'}, 'rule': {'id': '5e8067b3146db4001309686c', 'name': u'network-check'}, 'user': 'admin@example.org'}, end_timestamp=None, id=5e81d44cb99f2a002b34aef0, liveaction={'callback': {}, 'runner_info': {}, 'parameters': {u'cmd': u'curl busybox:80/cmd/network/check.sh%20cn', u'email_to': [u'chao.ma@easystack.cn'], u'email_from': u'noreply@easystack.cn'}, 'action': u'email.mistral-network-check', 'action_is_workflow': True, 'id': '5e81d44cb99f2a002b34aeef'}, log=[{'status': 'requested', 'timestamp': datetime.datetime(2020, 3, 30, 11, 13, 16, 797752, tzinfo=tzutc())}], parameters={u'cmd': u'curl busybox:80/cmd/network/check.sh%20cn', u'email_to': [u'chao.ma@easystack.cn'], u'email_from': u'noreply@easystack.cn'}, parent=None, result={}, rule={'description': u'Check L3 vRouter & LB & DHCP', 'tags': [], 'ref': u'email.network-check', 'enabled': True, 'trigger': {'type': u'core.st2.IntervalTimer', 'ref': u'core.ac7abf3a-9966-480a-8376-1b156bdbef56', 'parameters': {u'unit': u'minutes', u'delta': 10}}, 'context': {}, 'criteria': {}, 'action': {'ref': u'email.mistral-network-check', 'parameters': {u'cmd': u'curl busybox:80/cmd/network/check.sh%20cn', u'email_to': [u'chao.ma@easystack.cn'], u'email_from': u'noreply@easystack.cn'}}, 'uid': u'rule:email:network-check', 'pack': u'email', 'type': {'ref': u'standard', 'parameters': {}}, 'id': '5e8067b3146db4001309686c', 'name': u'network-check'}, runner={'runner_module': u'mistral_v2', 'uid': u'runner_type:mistral-v2', 'name': u'mistral-v2', 'enabled': True, 'query_module': u'mistral_v2', 'runner_parameters': {u'skip_notify': {u'default': [], u'type': u'array', u'description': u'List of tasks to skip notifications for.'}, u'task': {u'type': u'string', u'description': u'The name of the task to run for reverse workflow.'}, u'context': {u'default': {}, u'type': u'object', u'description': u'Additional workflow inputs.'}, u'workflow': {u'type': u'string', u'description': u'The name of the workflow to run if the entry_point is a workbook of many workflows. The name should be in the format "<pack_name>.<action_name>.<workflow_name>". If entry point is a workflow or a workbook with a single workflow, the runner will identify the workflow automatically.'}}, 'id': '5e806772146db40013096339', 'description': u'A runner for executing mistral v2 workflow.'}, start_timestamp="2020-03-30 11:13:16.718857+00:00", status="requested", trigger={'uid': u'trigger:core:ac7abf3a-9966-480a-8376-1b156bdbef56:d561c05e470c37028940525af03d7eb5', 'parameters': {u'unit': u'minutes', u'delta': 10}, 'type': u'core.st2.IntervalTimer', 'pack': u'core', 'ref': u'core.ac7abf3a-9966-480a-8376-1b156bdbef56', 'id': '5e8160e028d08b002d5ecd0c', 'name': u'ac7abf3a-9966-480a-8376-1b156bdbef56'}, trigger_instance={'status': u'processing', 'occurrence_time': '2020-03-30T11:13:15.815000Z', 'trigger': u'core.ac7abf3a-9966-480a-8376-1b156bdbef56', 'id': '5e81d44bb99f2a002b34aeed', 'payload': {u'executed_at': u'2020-03-30 11:13:15.770559+00:00', u'schedule': None}}, trigger_type={'uid': u'trigger_type:core:st2.IntervalTimer', 'tags': [], 'parameters_schema': {u'additionalProperties': False, u'type': u'object', u'properties': {u'timezone': {u'type': u'string'}, u'unit': {u'enum': [u'weeks', u'days', u'hours', u'minutes', u'seconds'], u'required': True}, u'delta': {u'required': True, u'type': u'integer'}}}, 'name': u'st2.IntervalTimer', 'payload_schema': {u'type': u'object', u'properties': {u'executed_at': {u'default': u'2014-07-30 05:04:24.578325', u'type': u'string', u'format': u'date-time'}, u'schedule': {u'default': {u'units': u'seconds', u'delta': 30}, u'type': u'object'}}}, 'pack': u'core', 'ref': u'core.st2.IntervalTimer', 'id': '5e80674e4a20a500018b1713', 'description': u'Triggers on specified intervals. e.g. every 30s, 1week etc.'}, web_url="https://dozer-st2rulesengine-5b6cc5466-ttchq/#/history/5e81d44cb99f2a002b34aef0/general")>
4.2.2) 分析_do_enforce方法
class RuleEnforcer(object):
def _do_enforce(self):
params = self.get_resolved_parameters()
LOG.info('Invoking action %s for trigger_instance %s with params %s.',
self.rule.action.ref, self.trigger_instance.id,
json.dumps(params))
# update trace before invoking the action.
trace_context = self._update_trace()
LOG.debug('Updated trace %s with rule %s.', trace_context, self.rule.id)
context = {
'trigger_instance': reference.get_ref_from_model(self.trigger_instance),
'rule': reference.get_ref_from_model(self.rule),
'user': get_system_username(),
TRACE_CONTEXT: trace_context
}
return RuleEnforcer._invoke_action(self.rule.action, params, context)
分析:
_do_enforce(self):
1 获取参数字典,形如:
{u'cmd': u'curl busybox:80/cmd/network/check.sh%20cn', u'email_to': [u'receiver@example.org'], u'email_from': u'sender@example.org'}
2 调用 update_trace方法,具体是:
s1 调用get_trace_db_by_trigger_instance(trigger_instance=None, trigger_instance_id=None):
根据trigger_instance的id,调用_get_single_trace_by_component(**component_filter):
根据形如如下的参数:
{'trigger_instances__object_id': '5e79d4d58dc0dd00ea0f362f'}
向trace_d_b表查询属于该trigger_instance id的trace记录,结果样例如下:
<TraceDB: TraceDB(action_executions=[], id=5e79ec278dc0dd00ea0f3630, rules=[], start_timestamp="2020-03-24 11:09:19.309179+00:00", trace_tag="st2.IntervalTimer-8c13d9a3-12a3-4cff-88d3-66c804a47590", trigger_instances=[<TraceComponentDB: TraceComponentDB@(object_id:5e79d4d58dc0dd00ea0f362f, updated_at:2020-03-24 11:14:41.289695+00:00)>], uid="trace:aaa8de00c846febf51ac71bd86a776c9")>
s2 更新trace记录,设置该trace记录的rules为形如如下的结果
[{'caused_by': {'type': 'trigger_instance', 'id': '5e79d4d58dc0dd00ea0f362f'}, 'ref': u'email.network-check', 'id': '5e660433c75b50001c06b822'}]
s3 返回TraceContext对象的字典
3 调用RuleEnforcer._invoke_action(self.rule.action, params, context)方法
4.2.3) 分析RuleEnforcer._invoke_action方法
class RuleEnforcer(object):
@staticmethod
def _invoke_action(action_exec_spec, params, context=None):
"""
Schedule an action execution.
:type action_exec_spec: :class:`ActionExecutionSpecDB`
:param params: Parameters to execute the action with.
:type params: ``dict``
:rtype: :class:`LiveActionDB` on successful schedueling, None otherwise.
"""
action_ref = action_exec_spec['ref']
# prior to shipping off the params cast them to the right type.
params = action_param_utils.cast_params(action_ref, params)
liveaction = LiveActionDB(action=action_ref, context=context, parameters=params)
liveaction, execution = action_service.request(liveaction)
return execution
分析:
_invoke_action(action_exec_spec, params, context=None):
1 根据输入参数,样例如下:
(Pdb) p action_exec_spec
<ActionExecutionSpecDB: ActionExecutionSpecDB@71113936(ref="email.mistral-network-check", parameters="{u'cmd': u'curl busybox:80/cmd/network/check.sh%20cn', u'email_to': [u'receiver@example.org'], u'email_from': u'sender@example.org'}")>
(Pdb) p params
{u'cmd': u'curl busybox:80/cmd/network/check.sh%20cn', u'email_to': [u'receiver@example.org'], u'email_from': u'sender@example.org'}
(Pdb) p context
{'trigger_instance': {'id': '5e79d4d58dc0dd00ea0f362f', 'name': None}, 'trace_context': {'id_': '5e79ec278dc0dd00ea0f3630', 'trace_tag': u'st2.IntervalTimer-8c13d9a3-12a3-4cff-88d3-66c804a47590'}, 'user': 'admin@example.org', 'rule': {'id': '5e660433c75b50001c06b822', 'name': u'network-check'}}
2 解析获得参数字典,形如:
{u'cmd': u'curl busybox:80/cmd/network/check.sh%20cn', u'email_to': [u'receiver@example.org'], u'email_from': u'sender@example.org'}
3 实例化一个LiveActionDB(action=action_ref, context=context, parameters=params)对象,形如:
<LiveActionDB: LiveActionDB(action="email.mistral-network-check", action_is_workflow=False, callback={}, context={'trigger_instance': {'id': '5e79d4d58dc0dd00ea0f362f', 'name': None}, 'trace_context': {'id_': '5e79ec278dc0dd00ea0f3630', 'trace_tag': u'st2.IntervalTimer-8c13d9a3-12a3-4cff-88d3-66c804a47590'}, 'rule': {'id': '5e660433c75b50001c06b822', 'name': u'network-check'}, 'user': 'admin@example.org'}, end_timestamp=None, id=None, notify=None, parameters={u'cmd': u'curl busybox:80/cmd/network/check.sh%20cn', u'email_to': [u'receiver@example.org'], u'email_from': u'sender@example.org'}, result={}, runner_info={}, start_timestamp="2020-03-24 13:24:21.009098+00:00", status=None)>
4 调用/opt/stackstorm/st2/lib/python2.7/site-packages/st2common/services/action.py(155)request(liveaction)方法
4.2.4) 分析/opt/stackstorm/st2/lib/python2.7/site-packages/st2common/services/action.py的request(liveaction)方法
def request(liveaction):
liveaction, execution = create_request(liveaction)
liveaction, execution = publish_request(liveaction, execution)
return liveaction, execution
分析:
request(liveaction):
1 根据输入参数liveaction,样例如下:
<LiveActionDB: LiveActionDB(action="email.mistral-network-check", action_is_workflow=False, callback={}, context={'trigger_instance': {'id': '5e79d4d58dc0dd00ea0f362f', 'name': None}, 'trace_context': {'id_': '5e79ec278dc0dd00ea0f3630', 'trace_tag': u'st2.IntervalTimer-8c13d9a3-12a3-4cff-88d3-66c804a47590'}, 'rule': {'id': '5e660433c75b50001c06b822', 'name': u'network-check'}, 'user': 'admin@example.org'}, end_timestamp=None, id=None, notify=None, parameters={u'cmd': u'curl busybox:80/cmd/network/check.sh%20cn', u'email_to': [u'receiver@example.org'], u'email_from': u'sender@example.org'}, result={}, runner_info={}, start_timestamp="2020-03-24 13:24:21.009098+00:00", status=None)>
2 调用 create_request(liveaction): 创建一个action的执行,返回(liveaction, execution)
3 调用 publish_request(liveaction, execution),具体是
4 返回: liveaction, execution
样例如下:
(Pdb) p liveaction
<LiveActionDB: LiveActionDB(action="email.mistral-network-check", action_is_workflow=True, callback={}, context={'trigger_instance': {'id': '5e81d44bb99f2a002b34aeed', 'name': None}, 'trace_context': {'id_': '5e81d44bb99f2a002b34aeee', 'trace_tag': u'st2.IntervalTimer-ac7abf3a-9966-480a-8376-1b156bdbef56'}, 'rule': {'id': '5e8067b3146db4001309686c', 'name': u'network-check'}, 'user': 'admin@example.org'}, end_timestamp=None, id=5e81d44cb99f2a002b34aeef, notify=None, parameters={u'cmd': u'curl busybox:80/cmd/network/check.sh%20cn', u'email_to': [u'chao.ma@easystack.cn'], u'email_from': u'noreply@easystack.cn'}, result={}, runner_info={}, start_timestamp="2020-03-30 11:13:16.718857+00:00", status="requested")>
(Pdb) p execution
<ActionExecutionDB: ActionExecutionDB(action={'name': u'mistral-network-check', 'runner_type': u'mistral-v2', 'tags': [], 'description': u'Run network check script and output the result then send email.', 'enabled': True, 'entry_point': u'workflows/mistral-network-check.yaml', 'notify': {}, 'uid': u'action:email:mistral-network-check', 'parameters': {u'email_account': {u'default': u'esdozer', u'type': u'string'}, u'cmd': {u'required': True, u'type': u'string'}, u'email_to': {u'required': True, u'type': u'array'}, u'email_from': {u'type': u'string'}, u'timeout': {u'default': 1800, u'type': u'integer'}}, 'ref': u'email.mistral-network-check', 'id': '5e8067ae146db400130967e8', 'pack': u'email'}, children=[], context={'trigger_instance': {'id': '5e81d44bb99f2a002b34aeed', 'name': None}, 'trace_context': {'id_': '5e81d44bb99f2a002b34aeee', 'trace_tag': u'st2.IntervalTimer-ac7abf3a-9966-480a-8376-1b156bdbef56'}, 'rule': {'id': '5e8067b3146db4001309686c', 'name': u'network-check'}, 'user': 'admin@example.org'}, end_timestamp=None, id=5e81d44cb99f2a002b34aef0, liveaction={'callback': {}, 'runner_info': {}, 'parameters': {u'cmd': u'curl busybox:80/cmd/network/check.sh%20cn', u'email_to': [u'chao.ma@easystack.cn'], u'email_from': u'noreply@easystack.cn'}, 'action': u'email.mistral-network-check', 'action_is_workflow': True, 'id': '5e81d44cb99f2a002b34aeef'}, log=[{'status': 'requested', 'timestamp': datetime.datetime(2020, 3, 30, 11, 13, 16, 797752, tzinfo=tzutc())}], parameters={u'cmd': u'curl busybox:80/cmd/network/check.sh%20cn', u'email_to': [u'chao.ma@easystack.cn'], u'email_from': u'noreply@easystack.cn'}, parent=None, result={}, rule={'description': u'Check L3 vRouter & LB & DHCP', 'tags': [], 'ref': u'email.network-check', 'enabled': True, 'trigger': {'type': u'core.st2.IntervalTimer', 'ref': u'core.ac7abf3a-9966-480a-8376-1b156bdbef56', 'parameters': {u'unit': u'minutes', u'delta': 10}}, 'context': {}, 'criteria': {}, 'action': {'ref': u'email.mistral-network-check', 'parameters': {u'cmd': u'cu