目标:
弄清楚st2resultstracker服务原理
调试命令:
sudo /opt/stackstorm/st2/bin/st2resultstracker --config-file /etc/st2/st2.conf
1 主入口
/opt/stackstorm/st2/lib/python2.7/site-packages/st2actions/cmd/st2resultstracker.py
def _run_worker():
LOG.info('(PID=%s) Results tracker started.', os.getpid())
tracker = resultstracker.get_tracker()
try:
tracker.start(wait=True)
except (KeyboardInterrupt, SystemExit):
LOG.info('(PID=%s) Results tracker stopped.', os.getpid())
tracker.shutdown()
except:
return 1
return 0
def main():
try:
_setup()
return _run_worker()
except SystemExit as exit_code:
sys.exit(exit_code)
except:
LOG.exception('(PID=%s) Results tracker quit due to exception.', os.getpid())
return 1
finally:
_teardown()
分析:
1.1 重点是
tracker = resultstracker.get_tracker()
tracker.start(wait=True)
先分析 tracker = resultstracker.get_tracker()
进入:
st2/st2actions/st2actions/resultstracker/resultstracker.py
def get_tracker():
with Connection(transport_utils.get_messaging_urls()) as conn:
return ResultsTracker(conn, [RESULTSTRACKER_ACTIONSTATE_WORK_QUEUE])
分析:
1.1.1) 对于RESULTSTRACKER_ACTIONSTATE_WORK_QUEUE
(Pdb) p RESULTSTRACKER_ACTIONSTATE_WORK_QUEUE
<unbound Queue st2.resultstracker.work -> <unbound Exchange st2.actionexecutionstate(topic)> -> create>
那实际从exchange经过routing_key绑定到队列的关系如下:
st2.actionexecutionstate--->create--->st2.resultstracker.work
1.1.2) 分析ResultsTracker
class ResultsTracker(consumers.MessageHandler):
message_type = ActionExecutionStateDB
def __init__(self, connection, queues):
super(ResultsTracker, self).__init__(connection, queues)
self._queriers = {}
self._query_threads = []
self._failed_imports = set()
分析:
ResultsTracker本质是一个消息处理器,实现了自己的process方法进行消息的处理。
1.2 分析tracker.start(wait=True)
class ResultsTracker(consumers.MessageHandler):
def start(self, wait=False):
self._bootstrap()
super(ResultsTracker, self).start(wait=wait)
进入_bootstrap(),代码如下:
def _bootstrap(self):
all_states = ActionExecutionState.get_all()
LOG.info('Found %d pending states in db.' % len(all_states))
query_contexts_dict = defaultdict(list)
for state_db in all_states:
try:
context = QueryContext.from_model(state_db)
except:
LOG.exception('Invalid state object: %s', state_db)
continue
query_module_name = state_db.query_module
querier = self.get_querier(query_module_name)
if querier is not None:
query_contexts_dict[querier].append(context)
分析:
1.2.1)
_bootstrap(self):
1 调用ActionExecutionState.get_all()向action_execution_state_d_b查询得到动作执行状态列表,返回样例如下:
[<ActionExecutionStateDB: ActionExecutionStateDB(execution_id=5e952199d53a7e0001e62a44, id=5e952199bf92d90001c49cc2, query_context={u'mistral': {u'workflow_name': u'email.mistral-network-check', u'execution_id': u'fd56a8e8-9924-49aa-ab24-232b24c168e4'}}, query_module="mistral_v2")>]
对动作执行状态列表中每个动作执行状态对象,生成对应的查询上下文对象,样例如下:
<QueryContext id=5e952199bf92d90001c49cc2,execution_id=5e952199d53a7e0001e62a44,query_context={u'mistral': {u'workflow_name': u'email.mistral-network-check', u'execution_id': u'fd56a8e8-9924-49aa-ab24-232b24c168e4'}}>
获取查询上下文对象的查询模块(例如:u'mistral_v2')
2 调用get_querier(self, query_module_name):
总结: 根据query_module名称(例如u'mistral_v2'),在<查询模块名,查询模块对象>字典中查找有无该查找对象。
若有,则直接返回;
否则实例化一个mistral_v2.MistralResultsQuerier对象,开启死循环,每隔5秒,
从队列中获取待查询对象,根据st2的execution_id查询到liveaction,根据mistral的execution_id查询到workflow状态以及该工作流的task列表,
对每个task根据task id查询得到task具有信息,返回到最终的结果列表中。
根据mistral工作流状态,以及st2中execution.child中每个child execution状态来确定execution的状态,返回该状态以及结果(字典)
根据execution_id查询到st2中liveaction,设置liveaction的status为输入参数指定的status;设置liveaction的results为输入参数指定的results;更新该liveaction。
根据liveaction_db的id,向action_execution_d_b中查询获取execution记录,并利用liveaction_db的信息更新execution记录。
根据exchange: st2.liveaction和routing_key: 'update',发送liveaction更新消息。
根据query_context.id查询到ActionExecutionStateDB对象并从数据库中删除该对象
返回:querier对象,例如:
<mistral_v2.MistralResultsQuerier object at 0x485f790>
3 遍历待查询对象列表,将每个待查询对象和当前时间的二元组放入查询对象队列中
1.2.2)分析
querier = self.get_querier(query_module_name)
代码如下:
def get_querier(self, query_module_name):
if (query_module_name not in self._queriers and
query_module_name not in self._failed_imports):
try:
query_module = register_query_module(query_module_name)
except:
LOG.exception('Failed importing query module: %s', query_module_name)
self._failed_imports.add(query_module_name)
self._queriers[query_module_name] = None
else:
querier = query_module.get_instance()
self._queriers[query_module_name] = querier
self._query_threads.append(eventlet.spawn(querier.start))
return self._queriers[query_module_name]
分析:
1.2.2.1) 查看 query_module = register_query_module(query_module_name)代码如下
def register_query_module(module_name):
base_path = cfg.CONF.system.base_path
module_path = os.path.join(base_path, 'runners', module_name, 'query', module_name + '.py')
if module_name not in QUERIER_MODULES_CACHE:
LOG.info('Loading query module from "%s".', module_path)
QUERIER_MODULES_CACHE[module_name] = imp.load_source(module_name, module_path)
else:
LOG.info('Reusing query module "%s" from cache.', module_path)
return QUERIER_MODULES_CACHE[module_name]
分析:
register_query_module(module_name):
1 根据输入参数,形如:
(Pdb) p module_name
u'mistral_v2'
2 获取module路径(例如:'/opt/stackstorm/runners/mistral_v2/query/mistral_v2.py')
3 调用imp.load_source(name,pathname[,file])的作用把源文件pathname导入到name模块中,
name可以是自定义的名字或者内置的模块名称。
4 返回该导入的模块,形如:
<module 'mistral_v2' from '/opt/stackstorm/runners/mistral_v2/query/mistral_v2.pyc'>
1.2.2.2)分析self._query_threads.append(eventlet.spawn(querier.start))
主要就是querier.start
进入:
st2/st2common/st2common/query/base.py代码如下
def start(self):
self._started = True
while True:
while self._query_contexts.empty():
eventlet.greenthread.sleep(self._empty_q_sleep_time)
while self._thread_pool.free() <= 0:
eventlet.greenthread.sleep(self._no_workers_sleep_time)
self._fire_queries()
eventlet.sleep(self._query_interval)
分析:
Querier.start(self):
如果查询队列是不为空,且协程池中空闲worker个数大于0,则调用_fire_queries方法,然后sleep 5秒,继续循环执行。
对于真正进行查询和处理的方法_fire_queries的分析参见2
2 对_fire_queries分析
@six.add_metaclass(abc.ABCMeta)
class Querier(object):
def _fire_queries(self):
if self._thread_pool.free() <= 0:
return
now = time.time()
reschedule_queries = []
while not self._query_contexts.empty() and self._thread_pool.free() > 0:
(last_query_time, query_context) = self._query_contexts.get_nowait()
if now - last_query_time < self._query_interval:
reschedule_queries.append((last_query_time, query_context))
continue
else:
self._thread_pool.spawn(
self._query_and_save_results,
query_context,
last_query_time
)
for query in reschedule_queries:
self._query_contexts.put((query[0], query[1]))
分析:
2.1)
Querier._fire_queries(self):
1 从查询队列中获取一个查询对象,如果当前时间减去上次查询时间还没有超过查询时间间隔,则放入
reschedule_queries列表中后面会再次存放到查询队列中;否则执行2
2 使用协程池执行_query_and_save_results方法,具体如下:
_query_and_save_results(self, query_context, last_query_time=None):
1 根据输入参数,形如:
(Pdb) p query_context
<QueryContext id=5e952199bf92d90001c49cc2,execution_id=5e952199d53a7e0001e62a44,query_context={u'mistral': {u'workflow_name': u'email.mistral-network-check', u'execution_id': u'fd56a8e8-9924-49aa-ab24-232b24c168e4'}}>
(Pdb) p last_query_time
1586837111.809033
2 获取查询的execution_id和查询上下文,形如:
(Pdb) p query_context.execution_id
'5e952199d53a7e0001e62a44'
(Pdb) p query_context.query_context
{u'mistral': {u'workflow_name': u'email.mistral-network-check', u'execution_id': u'fd56a8e8-9924-49aa-ab24-232b24c168e4'}}
3 调用查询器的query方法,具体如下:
3 遍历需要再次调度的reschedule_queries,将数据再次入队
2.2 分析_query_and_save_results方法
代码如下:
def _query_and_save_results(self, query_context, last_query_time=None):
this_query_time = time.time()
execution_id = query_context.execution_id
actual_query_context = query_context.query_context
LOG.debug('Querying external service for results. Context: %s' % actual_query_context)
try:
(status, results) = self.query(
execution_id,
actual_query_context,
last_query_time=last_query_time
)
except:
LOG.exception('Failed querying results for liveaction_id %s.', execution_id)
if self.delete_state_object_on_error:
self._delete_state_object(query_context)
LOG.debug('Removed state object %s.', query_context)