Eu tenho vários trabalhos que exigem execução pelo menos uma vez por dia. O que eu faço é iniciar os scripts para esses trabalhos a cada hora (ou com mais frequência) e os scripts deles verificam se já foram executados, verificando um arquivo de status no disco.
Se o arquivo de status existir e estiver atualizado, o script será encerrado.
Se esse arquivo for antigo (ou seja, gravado pela última vez no dia anterior) ou não existir, o script será executado e, no término bem-sucedido, o arquivo de status será gravado.
Se você não puder criar essa funcionalidade em um programa existente, é simples criar um script de wrapper que verifique se o programa deve ser executado, chama o programa, se necessário, e em caso de sucesso (valor de saída, saída analisada) grava o arquivo de status.
/usr/local/bin/catchup.simple
:
#! /usr/bin/env python
"""
first parameter is a path to a file /..../daily/some_name
That is a status/script file and the /daily/ indicates it needs to run at least
once a day (after reboot, after midnight).
The rest of the parameters is the command executed and its parameters.
If there are no more parameters beyond the first the actual status
file is /..../daily/some_name.status and is expected to be updated by calling
the /....daily/some_name script (which has to be executable). That
script doesn't need to know about the frequency and gets called with
the status file as first (and only) argument.
Valid directory names and their functioning:
/daily/ run once a day (UTC)
/hourly/ run once an hour
The actual scheduling and frequency to check if running is necessary, is
done using a crontab entry:
CU=/usr/local/bin/catchup.simple
CUD=/root/catchup
# month, hour, day_of_month, month day_of_week command
*/5 * * * * $CU $CUD/daily/getlogs curl ....
If mulitple days (or hours) have gone by, no runs are made for skipped
days.
If subprocess.check_output() fails the status file is not updated.
"""
import sys
import datetime
import subprocess
verbose = False # set to True to debug
def main():
if len(sys.argv) < 2:
print 'not enough parameters for', sys.argv[0]
return
if len(sys.argv) == 2:
status_file_name = sys.argv[1] + '.status'
cmd = [sys.argv[1]]
else:
status_file_name = sys.argv[1]
cmd = sys.argv[2:]
freq = sys.argv[1].rsplit('/', 2)[-2]
if verbose:
print 'cmd', cmd
print 'status', status_file_name
print 'frequency', freq
try:
last_status = datetime.datetime.strptime(
open(status_file_name).read().split('.')[0],
"%Y-%m-%dT%H:%M:%S",
)
except (IOError, ValueError):
last_status = datetime.datetime(2000, 1, 1)
now = datetime.datetime.utcnow().replace(microsecond=0)
if verbose:
print last_status
print 'now', now.isoformat()
if freq == 'daily':
if last_status.date() < now.date():
subprocess.check_output(cmd)
elif verbose:
print 'already done today'
elif freq == 'hourly':
if last_status.date() < now.date() or \
last_status.date() == now.date() and \
last_status.hour < now.hour:
subprocess.check_output(cmd)
elif verbose:
print 'already done this hour'
with open(status_file_name, 'w') as fp:
fp.write(now.isoformat())
if __name__ == "__main__":
main()