Как предотвратить запуск скрипта Python в течение получаса от другой копии - PullRequest
0 голосов
/ 13 декабря 2018

У меня есть скрипт, который выполняет длительную и потенциально разрушительную команду на выбранном пользователем коммутаторе SAN.После запуска я хочу предотвратить повторный запуск того же сценария на любом переключателе в течение как минимум получаса.

Я искал решения, но либо, по-моему, существует состояние гонки (pickle, configparser и т. Д.), Либо он включает только одновременную блокировку (тендо.singleton, fcntl и т. Д.)

sqlite3выглядело как хороший вариант, но я иногда получаю ошибки блокировки - а не просто ожидание - sqlite3.OperationalError: база данных заблокирована

На самом деле, я не знаю достаточно близко о sqlite и SQL в целом, чтобы знатьлучший способ написать надежную вещь типа блокировки / запроса / обновления.

Обработка, которую я считаю нужной, заключается в следующем:

  1. Чтение даты последнего запуска
  2. Рассчитайте разницу во времени
    • Если слишком недавно, завершите с сообщением об ошибке, чтобы попытаться позже
    • Если "в процессе", завершите с сообщением об ошибке, чтобы попытаться позже
  3. Пометить как "в процессе".Очевидно, только одна копия должна быть разрешена, чтобы установить это.Любые другие копии должны заканчиваться сообщением, чтобы попытаться позже
  4. Запустите команду на коммутаторе
  5. Обновите время последнего запуска.Не должно быть возможности попасть сюда, если запущена любая другая копия, но это не имеет значения.Слишком поздно, так как команда коммутатора SAN уже запущена:)

Вот код 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:])
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...