如何防止Python脚本在另一个副本的半小时内运行

时间:2018-12-13 01:51:08

标签: python python-3.x sqlite locking

我有一个脚本,可以在用户选择的SAN交换机上执行长时间运行的命令,并且可能会破坏命令。一旦运行,我希望防止在 any 交换机上再次运行同一脚本至少半个小时。

我一直在寻找解决方案,但我认为存在争用情况(棘手,configparser等),或者仅涉及并发锁定(tendo.singleton,fcntl等)

sqlite3看起来是一个不错的选择,但我偶尔会遇到锁定错误-而不是只是等待-sqlite3.OperationalError:数据库已锁定

实际上,我对sqlite和SQL的了解还不够广,不知道编写健壮的锁/查询/更新类型的东西的最佳方法。

我认为我想要的处理是:

  1. 读取上次运行日期
  2. 计算时差
    • 如果太新,则以错误消息结尾以供稍后尝试
    • 如果“进行中”,则显示一条错误消息以供稍后尝试
  3. 标记为“进行中”。显然,只有一个副本可以设置。其他任何副本都应以一条消息结尾,以供稍后尝试
  4. 在交换机上运行命令
  5. 更新上次运行时间。如果正在运行任何其他副本,则不可能到达此处,但实际上并不重要。现在太晚了,因为已经运行了SAN switch命令:)

这是我尝试过的sqlite3代码。不确定它是否真的能达到我想要的效果(例如,我不知道如何正确地进行“进行中”)

此外,我是否过于复杂?很高兴有一个简单的解决方案,但确实存在危险,有人可能要运行多个副本,这可能会导致问题。

等待1/2小时的默认操作也可以。

感谢您的任何建议

#!/usr/bin/env python3
""" sample header module to start with

    version v3.27.0 $Format:%h$
"""

# Standard library imports
from datetime import datetime
import os
import sqlite3
import sys


class PersistConfig:
    """ Simple sqlite3 implementation to handle persistant config
        that is also able to be updated concurretly from other
        copies of this script
    """

    # TODO really want a list of time things, with a lockable
    # TODO timestamp, info, in-progress
    # TODO set in-prog before
    def __init__(self, database, switch):
        self.database = database
        self.switch = switch
        self.ago = None
        self.last_switch = None
        self.now = int(now.timestamp())

        self.mins_delay = 30  # Minutes between runs

        if not os.path.exists(database):
            with sqlite3.connect(database) as conn:
                with conn:
                    conn.execute('BEGIN')
                    conn.execute("""
                    create table switchrun (
                        switch      text        primary key not null,
                        rundate     datetime    not null,
                        anyswitch   text
                    );
                    """)
                    conn.execute("insert into switchrun ( switch, rundate ) "
                                 "values ('any',0);")

    def can_run(self):
        """ Make sure the last run wasn't too recent.
            If not, update the last run to now to stop anyone else
            Also return details of last run
        """
        with sqlite3.connect(self.database, timeout=30) as conn:
            conn.execute("BEGIN")  # Mark transaction start. Take shared lock

            # Find last run
            for row in conn.execute("select rundate, anyswitch from switchrun "
                                    "where switch = 'any'"):
                rundate, switch = row
                mins_ago = (self.now - rundate) / 60
                self.ago = mins_ago
                self.last_switch = switch
                if mins_ago > self.mins_delay:
                    conn.execute(
                        'insert or replace into switchrun ( switch, rundate ) '
                        'values (?,?);', (self.switch, self.now))
                    conn.execute(
                        'insert or replace into switchrun'
                        ' ( switch, rundate, anyswitch ) '
                        'values (?,?,?);', ('any', self.now, self.switch))
                    return True
                else:
                    return False

            raise ValueError("Can't read last details")


def main(args):
    switch = args[0]

    db = 'config.db'
    config = PersistConfig(db, switch)

    if config.can_run():
        print(f"OK!! Last run {config.ago:.0f} mins ago "
              f"on {config.last_switch}")
    else:
        print(f"Denied. Last run {config.ago:.0f} mins ago "
              f"on {config.last_switch}")


if __name__ == '__main__':
    now = datetime.now()

    main(sys.argv[1:])

0 个答案:

没有答案