У меня есть скрипт, который выполняет длительную и потенциально разрушительную команду на выбранном пользователем коммутаторе SAN.После запуска я хочу предотвратить повторный запуск того же сценария на любом переключателе в течение как минимум получаса.
Я искал решения, но либо, по-моему, существует состояние гонки (pickle, configparser и т. Д.), Либо он включает только одновременную блокировку (тендо.singleton, fcntl и т. Д.)
sqlite3выглядело как хороший вариант, но я иногда получаю ошибки блокировки - а не просто ожидание - sqlite3.OperationalError: база данных заблокирована
На самом деле, я не знаю достаточно близко о sqlite и SQL в целом, чтобы знатьлучший способ написать надежную вещь типа блокировки / запроса / обновления.
Обработка, которую я считаю нужной, заключается в следующем:
- Чтение даты последнего запуска
- Рассчитайте разницу во времени
- Если слишком недавно, завершите с сообщением об ошибке, чтобы попытаться позже
- Если "в процессе", завершите с сообщением об ошибке, чтобы попытаться позже
- Пометить как "в процессе".Очевидно, только одна копия должна быть разрешена, чтобы установить это.Любые другие копии должны заканчиваться сообщением, чтобы попытаться позже
- Запустите команду на коммутаторе
- Обновите время последнего запуска.Не должно быть возможности попасть сюда, если запущена любая другая копия, но это не имеет значения.Слишком поздно, так как команда коммутатора 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:])