Time Dependencies
Sometimes you want a task to run at a given time, or to run every three hours, or to run only on the first of the month, or on Mondays. For that, pyflow supports date and time dependencies.
Like triggers, date and time dependencies can be set for a family. In this case, the tasks of this family will only run according to these dependencies.
Warning
All time-related dependencies (like cron, time, today and date) are relative to the clock of the suite.
[2]:
with pf.Suite('test') as s:
with pf.Family('f2'):
pf.Variable('SLEEP', 20)
t1 = pf.Task('t1')
t2 = pf.Task('t2')
t3 = pf.Task('t3')
t4 = pf.Task('t4')
t5 = pf.Task('t5')
t1.time = '00:30 23:30 00:30' # start(hh:mm) end(hh:mm) increment(hh:mm)
t2.day = 'thursday' # thursday at 1 pm
t2.time = '13:00'
t3.time = '0 12 * * *' # time is not considered until date is free
t3.date = '1.*.*' # `day.month.year` - * means every day, month, year
t4.time = '+00:02' # + means realative to suite begin/requeue time
t5.time = '00:02' # 2 minutes past midnight
s
[2]:
suite test edit ECF_JOB_CMD 'bash -c 'export ECF_PORT=%ECF_PORT%; export ECF_HOST=%ECF_HOST%; export ECF_NAME=%ECF_NAME%; export ECF_PASS=%ECF_PASS%; export ECF_TRYNO=%ECF_TRYNO%; export PATH=/usr/local/apps/ecflow/%ECF_VERSION%/bin:$PATH; ecflow_client --init="$$" && %ECF_JOB% && ecflow_client --complete || ecflow_client --abort ' 1> %ECF_JOBOUT% 2>&1 &' edit ECF_KILL_CMD 'pkill -15 -P %ECF_RID%' edit ECF_STATUS_CMD 'true' edit ECF_OUT '%ECF_HOME%' label exec_host "default" family f2 edit SLEEP '20' task t1 time 00:30 23:30 00:30 task t2 time 13:00 day thursday task t3 time 12:00 date 1.*.* task t4 time +00:02 task t5 time 00:02 endfamily endsuite
Time
Time dependencies can be absolute, i.e. they will run at the exact time. They can also be relative; in this case, we provide the time from the moment the suite is begun.
Time dependencies can be repeated at regular intervals. The nodes stay complete once all-time instances have run.
[3]:
with pf.Suite('test') as s:
with pf.Family('f3'):
with pf.Task('t1') as t1:
t1.time = '23:00' # at next 23:00
with pf.Task('t2') as t2:
t2.time = '0 10-20 * * *' # every hour between 10 am and 8 pm
with pf.Task('t3') as t3:
t3.time = '+00:01' # one minute after the suite has begun
with pf.Task('t4') as t4:
t4.time = '+00:10 01:00 00:05' # 10 to 60 minutes after begin every 5 minutes
s
[3]:
suite test edit ECF_JOB_CMD 'bash -c 'export ECF_PORT=%ECF_PORT%; export ECF_HOST=%ECF_HOST%; export ECF_NAME=%ECF_NAME%; export ECF_PASS=%ECF_PASS%; export ECF_TRYNO=%ECF_TRYNO%; export PATH=/usr/local/apps/ecflow/%ECF_VERSION%/bin:$PATH; ecflow_client --init="$$" && %ECF_JOB% && ecflow_client --complete || ecflow_client --abort ' 1> %ECF_JOBOUT% 2>&1 &' edit ECF_KILL_CMD 'pkill -15 -P %ECF_RID%' edit ECF_STATUS_CMD 'true' edit ECF_OUT '%ECF_HOME%' label exec_host "default" family f3 task t1 time 23:00 task t2 time 10:00 20:00 01:00 task t3 time +00:01 task t4 time +00:10 01:00 00:05 endfamily endsuite
In the last example, we have a task that runs every hour. However, if the task takes longer than an hour, the time slot will be missed.
Cron
Cron dependencies can be specified using the pf.Cron class. Cron differs from time as when the node is complete it queues again immediately. Cron also only works with a real time clock, not a hybrid clock.
[4]:
with pf.Suite('test') as s:
with pf.Family('f4'):
with pf.Task('t1') as t1:
t1.cron = '0 23 * * *' # every day at 11 pm
with pf.Task('t2') as t2:
t2.cron = '0 8-12 * * *' # every hour between 8 and 12 am
with pf.Task('t3') as t3:
t3.cron = '0 11 * * SUN,TUE' # every Sunday and Tuesday at 11 am
with pf.Task('t4') as t4:
t4.cron = '0 2 1,15 * *' # every 1st and 15th of each month at 2 am
with pf.Task('t5') as t5:
t5.cron = '0 14 1 1 *' # every first of January at 2 pm
with pf.Task('t6'):
pf.Cron('23:00', last_week_days_of_the_month=[5]) # every *last* Friday of month at 11 pm
with pf.Task('t7'):
pf.Cron('23:00', days_of_month=[1], last_day_of_the_month=True) # every first and last of month at 11 pm
s
[4]:
suite test edit ECF_JOB_CMD 'bash -c 'export ECF_PORT=%ECF_PORT%; export ECF_HOST=%ECF_HOST%; export ECF_NAME=%ECF_NAME%; export ECF_PASS=%ECF_PASS%; export ECF_TRYNO=%ECF_TRYNO%; export PATH=/usr/local/apps/ecflow/%ECF_VERSION%/bin:$PATH; ecflow_client --init="$$" && %ECF_JOB% && ecflow_client --complete || ecflow_client --abort ' 1> %ECF_JOBOUT% 2>&1 &' edit ECF_KILL_CMD 'pkill -15 -P %ECF_RID%' edit ECF_STATUS_CMD 'true' edit ECF_OUT '%ECF_HOME%' label exec_host "default" family f4 task t1 cron 23:00 task t2 cron 08:00 12:00 01:00 task t3 cron -w 0,2 11:00 task t4 cron -d 1,15 02:00 task t5 cron -d 1 -m 1 14:00 task t6 cron -w 5L 23:00 task t7 cron -d 1,L 23:00 endfamily endsuite
Note
When the time has expired, the associated node is free to run. The time will stay expired until the node is re-queued.
The ecFlow server records all commands sent to it, in a log file <host>.<port>.ecf.log. This log file will grow over time to a considerable size.
In the following example, we will create a task whose job is to periodically back up and clear this log file. This will be done with cron attribute. A cron will run indefinitely, i.e. once it has completed it will automatically re-queue itself.
[5]:
with pf.Suite('test', host=pf.LocalHost(), files='/test') as s:
with pf.Family('house_keeping'):
with pf.Task('clear_log', script=[
'# copy the log file to the ECF_HOME/log directory',
'cp %ECF_LOG% %ECF_HOME%/log/.',
'',
'# clear the log file',
'ecflow_client --log=clear',
]):
pf.Cron('30 22 * * SUN') # run every Sunday at 10:30 pm
s
[5]:
suite test edit ECF_FILES '/test' edit ECF_JOB_CMD 'bash -c 'export ECF_PORT=%ECF_PORT%; export ECF_HOST=%ECF_HOST%; export ECF_NAME=%ECF_NAME%; export ECF_PASS=%ECF_PASS%; export ECF_TRYNO=%ECF_TRYNO%; export PATH=/usr/local/apps/ecflow/%ECF_VERSION%/bin:$PATH; ecflow_client --init="$$" && %ECF_JOB% && ecflow_client --complete || ecflow_client --abort ' 1> %ECF_JOBOUT% 2>&1 &' edit ECF_KILL_CMD 'pkill -15 -P %ECF_RID%' edit ECF_STATUS_CMD 'true' edit ECF_OUT '%ECF_HOME%' label exec_host "localhost" family house_keeping task clear_log cron -w 0 22:30 endfamily endsuite
[6]:
s.deploy_suite(pf.Notebook)
[6]:
File: /test/clear_log.ecf
#!/bin/bash echo "Running on: $(hostname)" || true set -uex export ECF_PORT=%ECF_PORT% # The server port number export ECF_HOST=%ECF_HOST% # The host name where the server is running export ECF_NAME=%ECF_NAME% # The name of this current task export ECF_PASS=%ECF_PASS% # A unique password export ECF_TRYNO=%ECF_TRYNO% # Current try number of the task echo "Current working directory: $(pwd)" %nopp # copy the log file to the ECF_HOME/log directory cp %ECF_LOG% %ECF_HOME%/log/. # clear the log file ecflow_client --log=clear %end
Date or Day
Date dependencies can be specified using the date or day attribute. Date dependencies are always absolute, but wildcards can be used.
[7]:
with pf.Suite('test') as s:
with pf.Family('f5'):
with pf.Task('t1') as t1:
t1.date = '31.12.2012' # the 31st of December 2012
with pf.Task('t2') as t2:
t2.date = '01.*.*' # every first of the month
with pf.Task('t3') as t3:
t3.date = '*.10.*' # every day in October
with pf.Task('t4') as t4:
t4.date = '1.*.2008' # every first of the month, but only in 2008
with pf.Task('t5') as t5:
t5.day = 'monday' # every monday
s
[7]:
suite test edit ECF_JOB_CMD 'bash -c 'export ECF_PORT=%ECF_PORT%; export ECF_HOST=%ECF_HOST%; export ECF_NAME=%ECF_NAME%; export ECF_PASS=%ECF_PASS%; export ECF_TRYNO=%ECF_TRYNO%; export PATH=/usr/local/apps/ecflow/%ECF_VERSION%/bin:$PATH; ecflow_client --init="$$" && %ECF_JOB% && ecflow_client --complete || ecflow_client --abort ' 1> %ECF_JOBOUT% 2>&1 &' edit ECF_KILL_CMD 'pkill -15 -P %ECF_RID%' edit ECF_STATUS_CMD 'true' edit ECF_OUT '%ECF_HOME%' label exec_host "default" family f5 task t1 date 31.12.2012 task t2 date 1.*.* task t3 date *.10.* task t4 date 1.*.2008 task t5 day monday endfamily endsuite
Mixing time dependencies on the same node
A single task can have many time and date dependencies.
[8]:
with pf.Suite('test') as s:
with pf.Family('f6'):
with pf.Task('t1') as t1:
t1.time = '10:00' # here day acts like a guard over the time. i.e. time is not considered until Monday
t1.day = 'monday' # run on Monday at 10 am
with pf.Task('t2') as t2:
t2.time = '0 1,16 * * *' # on the same node, day/date acts like a guard over the time attributes
t2.date = ['01.*.*', '10.*.*'] # the first and tenth of every month and year
t2.day = ['sunday', 'wednesday'] # the time is only set free *if* we are on one of the day/dates
s
[8]:
suite test edit ECF_JOB_CMD 'bash -c 'export ECF_PORT=%ECF_PORT%; export ECF_HOST=%ECF_HOST%; export ECF_NAME=%ECF_NAME%; export ECF_PASS=%ECF_PASS%; export ECF_TRYNO=%ECF_TRYNO%; export PATH=/usr/local/apps/ecflow/%ECF_VERSION%/bin:$PATH; ecflow_client --init="$$" && %ECF_JOB% && ecflow_client --complete || ecflow_client --abort ' 1> %ECF_JOBOUT% 2>&1 &' edit ECF_KILL_CMD 'pkill -15 -P %ECF_RID%' edit ECF_STATUS_CMD 'true' edit ECF_OUT '%ECF_HOME%' label exec_host "default" family f6 task t1 time 10:00 day monday task t2 time 01:00 16:00 15:00 date 1.*.* date 10.*.* day sunday day wednesday endfamily endsuite
The second task will run on Sundays and Wednesdays at 1 am and 4 pm, but only if the day is the 1st or the 10th of the month.
Note
With multiple time dependencies on the same node, the dependencies of the same type are or-ed together, then and-ed with the different types.
Mixing time dependencies on different nodes
When time dependencies are placed on different nodes in the hierarchy, the results may seem surprising.
[9]:
with pf.Suite('test') as s:
with pf.Family('f1') as f1:
f1.day = 'monday' # the day *still* guards the time attribute
with pf.Task('t1') as t1:
t1.time = '10:00' # will run on Monday at 10 am
with pf.Family('f2') as f2:
f2.time = '10:00'
with pf.Task('t2') as t2:
t2.day = 'monday' # this will run on Monday morning at 00:00 and Monday at 10 am
s
[9]:
suite test edit ECF_JOB_CMD 'bash -c 'export ECF_PORT=%ECF_PORT%; export ECF_HOST=%ECF_HOST%; export ECF_NAME=%ECF_NAME%; export ECF_PASS=%ECF_PASS%; export ECF_TRYNO=%ECF_TRYNO%; export PATH=/usr/local/apps/ecflow/%ECF_VERSION%/bin:$PATH; ecflow_client --init="$$" && %ECF_JOB% && ecflow_client --complete || ecflow_client --abort ' 1> %ECF_JOBOUT% 2>&1 &' edit ECF_KILL_CMD 'pkill -15 -P %ECF_RID%' edit ECF_STATUS_CMD 'true' edit ECF_OUT '%ECF_HOME%' label exec_host "default" family f1 day monday task t1 time 10:00 endfamily family f2 time 10:00 task t2 day monday endfamily endsuite
The example above assumes we have a suite, with an infinite repeat loop.
The task t2 will run on Monday morning at 00:00 because time dependencies on different nodes act independently of each other. In this case, the time attribute was set free on Sunday at 10 am (and once free it stays free until it is re-queued). Hence task t2 is free to run on Monday morning. After the task has run and re-queued, it will then run on Monday at 10 am.
Time Triggers
Time triggers are an alternative to time-based attributes.
The following suite based generated variables are available for time-based triggers:
DDDay of the month
DOWDay of the week,
0-6, where0is SundayDOYDay of the year
ECF_DATE YYYYMMDDYear, month, day format. This has the same format as repeat date.
MMMonth
01-12TIME
HHMMformatYYYYYear
These time-based variables on the suite use the suites calendar. The suites calendar can be configured with the clock attribute.
[10]:
with pf.Suite('test') as s:
with pf.Family('f1') as f1:
with pf.Task('t1') as t1:
t1.time = '23:00' # t1.triggers = ':TIME == 2300'
with pf.Task('t2') as t2:
t2.date = '1.*.*' # t2.triggers = ':DD == 1'
with pf.Task('t3') as t3:
t3.day = 'monday' # t3.triggers = ':DOW == 1'
s
[10]:
suite test edit ECF_JOB_CMD 'bash -c 'export ECF_PORT=%ECF_PORT%; export ECF_HOST=%ECF_HOST%; export ECF_NAME=%ECF_NAME%; export ECF_PASS=%ECF_PASS%; export ECF_TRYNO=%ECF_TRYNO%; export PATH=/usr/local/apps/ecflow/%ECF_VERSION%/bin:$PATH; ecflow_client --init="$$" && %ECF_JOB% && ecflow_client --complete || ecflow_client --abort ' 1> %ECF_JOBOUT% 2>&1 &' edit ECF_KILL_CMD 'pkill -15 -P %ECF_RID%' edit ECF_STATUS_CMD 'true' edit ECF_OUT '%ECF_HOME%' label exec_host "default" family f1 task t1 time 23:00 task t2 date 1.*.* task t3 day monday endfamily endsuite
The : means a search for the variable up the node tree.
Triggers can also use AND/OR logic and the full range of operators <, >, <=, >=.
[11]:
with pf.Suite('test') as s:
with pf.Family('f1') as f1:
with pf.Task('t1') as t1:
t1.time = '23:00' # time attributes
t1.day = 'monday'
with pf.Task('t2') as t2:
t2.triggers = ':DOW == 1 and :TIME >= 1300' # time based trigger
with pf.Task('t3') as t3:
t3.triggers = ':TIME >= 1300' # combination
t3.day = 'monday'
s
[11]:
suite test edit ECF_JOB_CMD 'bash -c 'export ECF_PORT=%ECF_PORT%; export ECF_HOST=%ECF_HOST%; export ECF_NAME=%ECF_NAME%; export ECF_PASS=%ECF_PASS%; export ECF_TRYNO=%ECF_TRYNO%; export PATH=/usr/local/apps/ecflow/%ECF_VERSION%/bin:$PATH; ecflow_client --init="$$" && %ECF_JOB% && ecflow_client --complete || ecflow_client --abort ' 1> %ECF_JOBOUT% 2>&1 &' edit ECF_KILL_CMD 'pkill -15 -P %ECF_RID%' edit ECF_STATUS_CMD 'true' edit ECF_OUT '%ECF_HOME%' label exec_host "default" family f1 task t1 time 23:00 day monday task t2 trigger :DOW == 1 and :TIME >= 1300 task t3 trigger :TIME >= 1300 day monday endfamily endsuite
[12]:
with pf.Suite('test') as s:
with pf.Family('f1') as f1:
with pf.Task('t1') as t1:
t1.triggers = ':ECF_DATE == 20200720 and :TIME >= 1000'
with pf.Task('t2') as t2:
t2.triggers = ':DOW == 4 and :TIME >= 1300'
with pf.Task('t3') as t3:
t3.triggers = ':DD == 1 and :TIME >= 1200'
with pf.Task('t4') as t4:
t4.triggers = '(:DOW == 1 and :TIME >= 1300) or (:DOW == 5 and :TIME >= 1000)'
with pf.Task('t5') as t5:
t5.triggers = ':TIME == 0002' # 2 minutes past midnight
s
[12]:
suite test edit ECF_JOB_CMD 'bash -c 'export ECF_PORT=%ECF_PORT%; export ECF_HOST=%ECF_HOST%; export ECF_NAME=%ECF_NAME%; export ECF_PASS=%ECF_PASS%; export ECF_TRYNO=%ECF_TRYNO%; export PATH=/usr/local/apps/ecflow/%ECF_VERSION%/bin:$PATH; ecflow_client --init="$$" && %ECF_JOB% && ecflow_client --complete || ecflow_client --abort ' 1> %ECF_JOBOUT% 2>&1 &' edit ECF_KILL_CMD 'pkill -15 -P %ECF_RID%' edit ECF_STATUS_CMD 'true' edit ECF_OUT '%ECF_HOME%' label exec_host "default" family f1 task t1 trigger :ECF_DATE == 20200720 and :TIME >= 1000 task t2 trigger :DOW == 4 and :TIME >= 1300 task t3 trigger :DD == 1 and :TIME >= 1200 task t4 trigger (:DOW == 1 and :TIME >= 1300) or (:DOW == 5 and :TIME >= 1000) task t5 trigger :TIME == 0002 endfamily endsuite