Running a channel at a specific time

A common interface requirement involves running a batch job at a specific time of day (for example, every night at 10pm). Although Iguana does not have this capability built directly into the product, it is dead easy to create a simple channel that will perform this task. To help you get this functionality up and running, we’ve provided some copy-and-paste code below. Simply create a “From Translator” channel and copy this script over as a new module.

This solution also demonstrates some of Iguana’s cool features:

  1. Using the simple API call iguana.setChannelStatus to customize Iguana Dashboard tooltips.
  2. Using iguana.logInfo to produce some good information whenever the batch job is invoked.
  3. Using iguana.isTest to make the script run differently in the Translator than it does in production.

These are all helpful techniques that you can apply to your own scripts!

How It Works

Applying this solution is easy:

  1. Create a new channel with a ‘From Translator’ source component.
  2. When configuring the new channel, set the source script’s Poll time to a reasonable interval. For example, every 5 minutes (300,000 milliseconds).
  3. Launch the Translator and copy the code at the bottom of this page into a new module called “scheduler“.
  4. In the main script, add some code to call the module (similar to the following):
    scheduler = require 'scheduler'
    
    function main()
    
       scheduler.runAt(11.5,
          DoBatchProcess, "Some Argument")
    
    end
    
    function DoBatchProcess(Data)
       iguana.logInfo('Processed a lot of X12')
    end

As you can see above, the first argument represents the hour of day (0-23) when you wish to run the script. Notice that hours can be expressed in fractions? Then we simply give the name of a function to execute and an argument to pass to that function.

For simplicity I’ve coded the scheduled time directly into the script. This has a lot advantages – it’s means that the time is versioned and there is an exact record of who set the time and when they did it.  That can be good to drive accountability in many organizations.  But of course it’s not the only way you have to do it – you could pull that scheduled time from any data source – get it from a database, or perhaps from a shared calendar.  That’s the beauty of Iguana – it’s such an open, easy to customize platform.  Anyway – back to our scheduling problem!

In the Translator, the function will alway be called… but what about in production? This code is set up so that the channel will only run the function after the hour indicated. What’s more, the script records when the batch job was last run in a text file based on the channel name.  When it runs the batch function, it records the time and begins a new cycle.

Being able to alter the channel’s status tooltip also gives nice visibility to what the channel is doing:

Need proof? The logs show what is going on quite nicely:

Module Code

Here’s the copy-and-paste code for the ‘scheduler’ module. Feel free to tweak this to meet your own specific needs:

local scheduler={}

local LastRunTime = 0
local ScheduledRunTime = 0

local function NextRunTime(Hour, LastRunTime)
   local T = os.ts.date('*t')
   T.hour = Hour
   T.min = (Hour - math.floor(Hour)) * 60
   T.sec = 0
   local NextTime = os.ts.time(T)
   local LastT = os.ts.date('*t', LastRunTime)
   os.ts.date("%c", LastRunTime)
   if os.ts.difftime(LastRunTime, NextTime) > 0 then
      NextTime = NextTime + 24*60*60
   end
   return NextTime, os.ts.date("%c", NextTime)
end

local function RunFileName()
   return iguana.channelName():gsub('%s','_')..'_LastScheduledTime.txt'
end

local function LastRun()
   if not os.fs.access(RunFileName()) then
      return 0, 'No recorded run'
   end
   local F = io.open(RunFileName(), 'r')
   local T = F:read('*a')
   F:close()
   return tonumber(T), 'Last run at '..os.ts.date('%c', tonumber(T))
end

local function Status(LastRun, ScheduledTime)
   local R 
   if LastRun ~= 0 then
      R = 'Last run at '..os.ts.date('%c', LastRun)
   else
      R = 'Has not run yet.'
   end
   R = R..'\nScheduled to run at '..os.ts.date('%c',ScheduledTime)

   iguana.setChannelStatus{color='green', text=R}

   return R
end

local function RecordRun(ScheduledHour)
   if iguana.isTest() then return end
   local R = os.ts.time()
   local F = io.open(RunFileName(), 'w')
   F:write(R)
   F:close()
   LastRunTime = R
   ScheduledRunTime = NextRunTime(ScheduledHour, LastRunTime)
   local R  = Status(LastRunTime, ScheduledRunTime)
   iguana.logInfo(R)
end

local function Init(Time)
   LastRunTime = LastRun()
   ScheduledRunTime = NextRunTime(Time, LastRunTime)
   local R = Status(LastRunTime, ScheduledRunTime)
   iguana.logInfo(R)
   return R
end

function scheduler.runAt(ScheduledHour, Func, Arg)
   local R
   trace(LastRunTime)
   if LastRunTime == 0 or iguana.isTest() then
      -- We need to do one time initialization
      R = Init(ScheduledHour)
   end
   trace(ScheduledRunTime)
   local WouldRun = (os.ts.time() > ScheduledRunTime and LastRunTime <= ScheduledRunTime)  
   trace("Would run = "..tostring(WouldRun))

   if WouldRun then
      iguana.logInfo('Kicking off batch process')
      Func(Arg)
      RecordRun(ScheduledHour)
      return R
   end
   if iguana.isTest() then
      Func(Arg)
      return R
   end

   return R
end

return scheduler

Leave A Comment?