实战:开发Python项目管理平台Sailboat 4

Sailboat执行器的编写和日志的生成

在sailboat/executor中新建名为actuator的Python文件,并定义执行者方法。对应的代码如下:
在这里插入图片描述
将其引入到TimerHandler所在的文件,并传给APScheduler的add_job() 方法。代码改动如下:
在这里插入图片描述
保存改动后,再次用Postman工具测试。此时服务端返回的信息如下:
在这里插入图片描述
并且运行Sailboat的控制台输出了performer() 方法中打印的内容:
在这里插入图片描述
这说明调度接口能够正常工作了。现在我们要做的是完善执行器的代码,让Sailboat能够定时调度指定的EGG文件。
执行器要完成的工作并不多,大体流程如下:

  • ·解包EGG文件。
  • ·执行文件中指定的方法。
  • ·记录执行时间。
  • ·构造执行信息并存入数据库。

项目的解包和执行可以使用多进程或者多线程执行,最大化地发挥服务器的能力。考虑到项目日志的获取和生成,这里用Python中的内置函数subprocess.Popen() 启动执行器,这样就可以获取到被执行项目在运行过程中的输出和异常信息,这正是被执行项目日志生成的基础。

Sailboat的执行器代码便不难设计:

  • ·执行器启动时接收传入的参数。
  • ·构造EGG文件路径。
  • ·解包EGG文件。
  • ·调用EGG文件中指定的方法。

这里我们选择将EGG文件拷贝到临时区执行,执行完毕后需要删除临时区中对应的文件。熟悉Python上下文管理器的读者很快就得出了执行器的整体框架,执行器sailboat/executor/boat.py文件的完整代码

import os
import sys
from importlib import import_module

from components.storage import FileStorages

storages = FileStorages()


class Helmsman:
    """为文件导入和执行创造条件的上下文管理器"""

    def __init__(self, project, version):
        self.project = project
        self.version = version
        self.storage = storages

    def __enter__(self):
        """上文"""
        # 将文件拷贝到临时区
        target = self.storage.copy_to_temporary(self.project, self.version)
        self.temp_file = target
        if target:
            # 将文件路径添加到 sys.path
            sys.path.insert(0, target)

    def __exit__(self, exc_type, exc_val, exc_tb):
        """下文"""
        if os.path.exists(self.temp_file):
            # 清理临时区中对应的文件
            os.remove(self.temp_file)


def main(project, version):
    hemsman = Helmsman(project, version)
    with hemsman:
        # 从指定的文件中导入模块并调用指定方法
        spider = import_module("sail")
        spider.main()


if __name__ == '__main__':
    project, version = sys.argv[-2], sys.argv[-1]
    main(project, version)

执行器中代码的执行过程和上面设计的一样。需要注意的是,解包运行时导入的是名为sail的模块,并调用指定的main() 方法。也就是说,在打包Python项目时,项目必须符合如下结构:
在这里插入图片描述
入口函数main() 必须写在__init__.py文件中。这样一来,只要符合这个结构的Python项目都可以部署到Sailboat上进行管理和调度。执行器的代码确定后,完善执行者performer() 的代码。上面提到了Popen() ,依照Python文档中相应的介绍,执行者的代码顺势而成:

import os
import sys
import logging
import subprocess

from datetime import datetime
from uuid import uuid4

from settings import LOGPATH, RIDEPATH
from connect import databases


def delta_format(delta):
    """时间差格式化
    将时间差格式转换为时分秒
    """
    seconds = delta.seconds
    minutes, second = divmod(seconds, 60)
    hour, minute = divmod(minutes, 60)
    return "%s:%s:%s" % (hour, minute, second)


def time_format(start_time, end_time):
    """时间格式化
    将时间格式转换为年月日时分秒
    """
    duration = delta_format(end_time - start_time)
    start = start_time.strftime("%Y-%m-%d %H:%M:%S")
    end = end_time.strftime("%Y-%m-%d %H:%M:%S")
    return start, end, duration


def performer(*args, **kwargs):
    """执行者
    获取 stdout stderr
    生成日记文件名
    将日记写入文件
    将执行信息,执行结果,以及启动时间,结果时间和运行时长存入数据库
    """
    job = str(uuid4())
    # 接受传入的参数
    project, version, mode, rule, jid, idn, username = args
    # 开启子进程调用执行器
    instructions = ['-m', RIDEPATH, project, version]
    # 记录启动时间
    start = datetime.now()
    sub = subprocess.Popen(instructions, executable=sys.executable, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    # 获取输出和异常信息
    stdout, stderr = sub.communicate()
    # 记录结束时间
    end = datetime.now()
    out, err = "", ""
    try:
        out = stdout.decode("utf8")
        err = stderr.decode("utf8")
    except Exception as e:
        logging.warning(e)
    # 格式化起止时间并计算运行时长
    start, end, duration = time_format(start, end)
    # 生成日记
    if not os.path.exists(LOGPATH):
        os.makedirs(LOGPATH)
    room = os.path.join(LOGPATH, project)
    if not os.path.exists(room):
        os.makedirs(room)
    log = os.path.join(room, "%s.log" % job)
    with open(log, 'w') as file:
        # 将子进程输出写入日记文件
        file.write(out)
        file.write(err)
    # 构造执行记录并存储到数据库中
    message = {
        "project": project,
        "version": version,
        "mode": mode,
        "job": job,
        "start": start,
        "end": end,
        "duration": duration,
        "jid": jid,
        "idn": idn,
        "username": username,
        "create": datetime.now()
    }
    databases.record.insert_one(message).inserted_id

这里用到的LOGPATH和RIDEPATH是日志存放路径和执行器路径,它们定义在sailboat/settings.py文件中:
在这里插入图片描述
执行者通过subprocess.Popen() 方法启动执行器的boat.py文件,在启动前后分别记录下当前时间,两次记录的时间就是项目启动时间和项目运行结束时间,它们的差值就是项目运行时长。

运行过程中产生的输出和异常信息可以通过communicate() 方法获取,接着将它们组合起来写入到文本文件中,这便是项目运行日志。

Sailboat定时调度功能的实现

准备好执行者和执行器后,回到TimerHandler中,执行者编写的参数接收语句正是预留给TimerHandler的,这样我们就可以将项目信息和用户信息传递给执行者,待执行器运行结束后,信息就会存储到MongoDB中。TimerHandler的代码改动如下:
在这里插入图片描述
保存改动后用Postman进行新一轮的测试,客户端请求信息如下:
在这里插入图片描述
服务端返回的信息与上次相似,没有问题。Sailboat目录下多出了与日志相关的文件,日志目录结构如下:
在这里插入图片描述
打开任意一个日志文件,其内容为:

200

这里记录的200正是EGG文件中sail项目的运行输出。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值