Job Scheduler Service

Applications and application managers sometimes have the need to perform heavy jobs that are not part of the regular execution of the application.
Such jobs can be:

  • A bank wants to calculate end of day interest per account.
  • A car factory wants to calculate how many cars were sold in each branch of each zone.

Such jobs can be executed once or executed in some frequency.
In order to fulfill that need, we have created the job scheduler service, which is written on top of oe-cloud.

Deploying in CEP

  1. Create the following docker stack file and name it docker-compose.yml
version: "3"
services:
  mongo:
    image: registry.oecloud.local/alpine-mongo
    networks:
      - oecloud-network

  broadcaster:
    image: registry.oecloud.local/oecloudio-broadcaster-server
    environment:
      SERVICE_PORTS: "2345"
    networks:
      - oecloud-network

  routerservice:
    image: registry.oecloud.local/evfpaas-router-service
    environment:
      HASH_RING_REFRESH_TIME_INTERVAL: "10000"
      NODE_TLS_REJECT_UNAUTHORIZED: "0"
      SERVICE_PORTS: "3000"
      BROADCASTER_HOST : "broadcaster"
    networks:
      - oecloud-network
      - router_network

  rabbitmq:
    image: registry.oecloud.local/rabbitmq
    environment:
      VIRTUAL_HOST: "https://rabbitmq.oecloud.ad.infosys.com,rabbitmq.oecloud.ad.infosys.com"
      SERVICE_PORTS: "15672"
    networks:
      - oecloud-network
      - router_network

  scheduler:
    entrypoint: ["node", "."]
    image: registry.oecloud.local/scheduler
    depends_on:
      - mongo
    environment:
      VIRTUAL_HOST: "https://scheduler.oecloud.ad.infosys.com,scheduler.oecloud.ad.infosys.com"
      SERVICE_PORTS: "3001"
      FORCE_SSL: "yes"
      DISABLE_JOB_RUNNER: "true"
      RABBITMQ_HOSTNAME: "rabbitmq"
    networks:
      - oecloud-network
      - router_network

  web:
    entrypoint: ["node", "."]
    image: registry.oecloud.local/oecloud
    depends_on:
      - mongo
      - broadcaster
      - scheduler
      - routerservice
    deploy:
      mode: replicated
      replicas: 1
      update_config:
        delay: 60s
    environment:
      VIRTUAL_HOST: "https://oecloud.oecloudapp.ad.infosys.com,oecloud.oecloudapp.ad.infosys.com"
      SERVICE_PORTS: "3000"
      SCHEDULER_HOST: "scheduler"
      FORCE_SSL: "yes"
      CONSISTENT_HASH: "true"
      SERVICE_NAME: "oecloud"
      BROADCASTER_HOST: "broadcaster"
      ORCHESTRATOR: "dockerSwarm"
      ROUTER_HOST: "cep_router"
      APP_URL: "https://oecloud.oecloudapp.ad.infosys.com/api"
      NODE_TLS_REJECT_UNAUTHORIZED: "0"
      RABBITMQ_HOSTNAME: "rabbitmq"
    networks:
      - oecloud-network
      - router_network
    healthcheck:
      test: "curl http://0.0.0.0:3000/"
      interval: 1m30s
      timeout: 3s
      retries: 5

networks:
  oecloud-network:
  router_network:
    external: true
  1. Run docker stack deploy -c docker-compose.yml oe-cloudapp

Configuration

oe-job-scheduler is configurable through environment variables

  • RABBITMQ_HOSTNAME - Configurable hostname for rabbitmq: RABBITMQ_HOSTNAME=rabbitmq
  • DISABLE_JOB_RUNNER - Indicator for oecloud servers whether or not they should execute jobs. Should be set to false on oe-job-scheduler service.

Relevant Models

Job holds the job definition
Monitoring is responsible to monitor each execution of a job. We will discuss monitoring later in this guide.

How to Use Job

Following are the relevant properties:
jobModelName the modelName that holds the function to execute. The function should be on the prototype of the model.
jobFnName the function name to execute.
The function can get any number of arguments. The function is also passed as it’s 4 last arguments:

  • ctx - the context we got when posting the job. you can use this ctx in your queries.
  • monitoringId - should be passed to the callback.
  • version - should be passed to the callback.
  • callback - should be called at the end of the execution of the job function.
    invoked with err, monitoringId, version.

jobFnParams an array of objects that the function expects to get.
The length of parameters array should match the number of arguents the function expects and in the same order.
The last 4 arguments mentioned before should not be a part of the parameters array, they are being added automatically.
frequency the frequency to execute the job. Currently supports: “Once”, “Hourly”.
minute if the frquency is “Hourly”, indicates the minute within the hour that the job should start executing.
The job would start executing as soon as it can after that minute.
Currently supports: “0”-“59”.
timeZone if the frequency is “Hourly”, indicates the timeZone the job should start executing.
Currently supports: tz database timezones’ name format. List_of_tz_database_time_zones
jobDate if the frequency is “Once”, indicates the date in UTC standard that the job should start executing.
The job would start executing as soon as it can after that date.
priority indicates the priority of the job. Jobs with higher priority within the same time will start first.
Currently supports: 0-10.
Messages without a priority are treated as if their priority were 0. Messages with a priority higher than the maximum are treated as if they were with maximum priority.

Defining a Note model in note.json file in your oecloud application:

{
    "name": "Note",
    "base" : "BaseEntity",
    "strict" : false,
    "properties": {
        "title": {
            "type": "string"
        },
        "content": {
            "type": "string"
        }
    }
}

Defining job function in note.js file in your oecloud application:

var oeLogger = require('oe-logger');
var log = oeLogger('Note-logs');
var async = require('async');
module.exports = function (Model) {
  Model.prototype.changeTitle = function (title, ctx, monitoringId, version, callback) {
    Model.find({}, ctx, (err, notes) => {
      if (err) {
        return callback(err);
      }
      async.each(notes, function (note, cb) {
        note.updateAttribute('title', title, ctx, function (err) {
          if (err) {
            log.error(log.defaultContext(), err);
          }
          cb();
        });
      }, function (err) {
        callback(err, monitoringId, version);
      });
    });
  };
};

Posting an hourly job instance to oe-job-scheduler:

{
    "jobModelName": "Note",
    "jobFnName": "changeTitle",
    "jobFnParams": ["My new Title"],
    "frequency": "Hourly",
    "minute": "10",
    "timeZone":"Asia/Jerusalem",
    "priority": "1"
}

Posting a one time job instance to oe-job-scheduler:

{
    "jobModelName": "Note",
    "jobFnName": "changeTitle",
    "jobFnParams": ["My new Title"],
    "frequency": "Once",
    "jobDate": "2017-08-20T13:11:17.323Z",
    "priority": "1"
}

After posting the job instances to oe-job-scheduler, you should get 200 OK.
Once the time comes, the job is being ran and an instance of monitoring is being created in the oe-job-scheduler service for it to monitor the running job.

How to Use Monitoring

As we understand by now, each posted job has a frequency that indicates the job can be executed once or more times.
Each execution of a job is represented by a monitoring instance, which indicates the status of the running job.

Following are the relevant properties:
status the status of the job execution.
Currently supports: “New”, “Published”, “Processing”, “Failed Processing”, “Finished Processing”.
runnerStartTime the start date of the execution of the job in UTC standard.
runnerEndTime the end date of the execution of the job in UTC standard.
errorMsg if the execution of the job has failed, errorMsg has the error message.

Relation:
Monitoring instance belongs to a Job instance.
The foreign key is jobId.

Getting all Monitoring instaces of a job

A simple GET request to the oe-job-scheduler service on the Monitoring model with a query: {where: {jobId: SomeJobId}} will be sufficient.

Summary

We have seen how we can create 2 different kinds of jobs, have the jobs everything executed, and monitor them.