RSS
 

Posts Tagged ‘rufus-scheduler’

St. Ruby: Adding a Daemon and a Scheduler

09 Oct

Its time to add a real scheduler to the St. Ruby (Ragios) system. We need to be able to set time intervals for each system test performed by Ragios. The system admin sets the time interval for ragios to perform each test.

The Scheduler
We need to add a time_interval attribute to the SystemMonitor Class. Ragios will run the test_command method every time_interval.

#base class that defines the behavior of all system monitors
class SystemMonitor
...
    attr_reader :time_interval
 
   #defines the tests to run on a system
   def test_command
   end
...
end

Time interval could be in seconds, minutes, hours or combined.
Example

@time_interval = '3m'  #means the test will run every 3 minutes
@time_interval = '4h'  #every 4 hours
@time_interval = '30s' #every 30 seconds
@time_interval = '6h30m' #every 6 hours 30minutes

We need a scheduler to execute the tests every Time Interval. We will use the rufus-scheduler rubygem for this.

Installing the gem

sudo gem install rufus-scheduler --source http://gemcutter.org

Sample code using the scheduler

require 'rubygems'
require 'rufus/scheduler'
 
scheduler = Rufus::Scheduler.start_new
 
scheduler.every '40m' do
    puts 'download news feed'
end
 
scheduler.every '3h' do
    puts 'ping remote server'
end

Now lets implement a scheduler for Ragios.

#defines how services will be monitored
class Service < SystemMonitor
    def initialize
     super
    end
end
 
class TestServiceA < Service
   def initialize
      @test_description  = "Test Service A"
      @time_interval = '8h'
      super
   end
 
   def test_command
      #test servcie A
      #return TRUE if test passed; FALSE if failed;
   end
 
  def failed
  end
end #end of TestServiceA
 
class TestServiceB < Service
   def initialize
      @test_description  = "Test Service B"
      @time_interval = '45m'
      super
   end
 
  def test_command
      #test servcie B
      #return TRUE if test passed; FALSE if failed;
   end
 
  def failed
  end
end #end of TestServiceB
 
class TestServiceC < Service
   def initialize
      @test_description  = "Test Service C"
      @time_interval = '20s'
      super
   end
 
  def test_command
      #test servcie C
      #return TRUE if test passed; FALSE if failed;
   end
 
  def failed
  end
end #end of TestServiceC
 
jobs = [ TestServiceA.new, TestServiceB.new, TestServiceC.new]
 
puts "Welcome to Ragios"
puts "Initializing"
 
...
 
#schedule all the jobs to execute test_command() at every time_interval
scheduler = Rufus::Scheduler.start_new
jobs.each do |job|
    scheduler.every job.time_interval do
     begin
       if job.test_command
           puts job.test_description + "   [PASSED]" + " Created on: "+ Time.now.to_s
       else
           puts job.test_description +   "   [FAILED]" + " Created on: "+ Time.now.to_s
           job.failed
       end
       #catch all exceptions
      rescue Exception
          puts "ERROR: " +  $!  + " Created on: "+ Time.now.to_s
          job.error_handler
      end
   end #end of scheduler
end
 
#trap Ctrl-C to exit gracefully
puts "PRESS CTRL-C to QUIT"
  loop do
  trap("INT") { puts "\nExiting"; exit; }
    sleep(3)
  end

In the sample code, we have 3 tests running at different time intervals, we set the Tests to run on the Ragios system here;

jobs = [ TestServiceA.new, TestServiceB.new, TestServiceC.new]

We use the rufus-scheduler to set each test to run it’s test_command at its own specified time interval.
When a test fails it calls the failed method for that test.

scheduler = Rufus::Scheduler.start_new
jobs.each do |job|
    scheduler.every job.time_interval do
     begin
       if job.test_command
           puts job.test_description + "   [PASSED]" + " Created on: "+ Time.now.to_s
       else
           puts job.test_description +   "   [FAILED]" + " Created on: "+ Time.now.to_s
           job.failed
       end
       #catch all exceptions
      rescue Exception
          puts "ERROR: " +  $!  + " Created on: "+ Time.now.to_s
          job.error_handler
      end
   end #end of scheduler
end

We also added an Exception handler, in case the test_command or failed method throws an exception. This is useful because it means there maybe a bug in the ruby code for the test or something wrong with the test. We want to alert the system admin about the Exception. I added a new method called error_handler to the SystemMonitor Class (the superclass of all tests). This will help system admins and developers fix bugs in the test’s ruby code. $! is a ruby global variable that holds the details of the last Exception that occurred in the system.

From the view of a system admin Ragios is running tests but the tests are referred to as jobs in the code. The reason for this is that under the hood, Ragios views the tests as jobs. As far as Ragios is concerned its just running a bunch of jobs at their scheduled time intervals. This is a layer of abstraction. Ragios doesn’t know what a test is. It views them as jobs while a system admin views it as tests. This is why I call it tests in the description while its called jobs in the code. (Eventually we will hide all this stuff inside an object).

Finally, we want Ragios to run until its signaled to quit by a CTRL-C from the computer user. We will trap CTRL-C so that Ragios can exit gracefully.

puts "PRESS CTRL-C to QUIT"
  loop do
  trap("INT") { puts "\nExiting"; exit; }
    sleep(3)
  end

Real World Example
Lets try a real-world test. We will use the TestWebpageForHttp200 that tests a website to check if the home page is loading. It sends a http request to the webpage and returns TRUE (test passed) when it gets a http 200 response. It returns FALSE (test failed) when it gets a different http response code from 200.

class TestWebpageForHttp200 < Service
 
   attr_reader :test_url
 
   def initialize
        @contact = "obi@mail.com"
        @describe_test_result = "HTTP Request to " + @test_url
        super
        @time_interval = '1h'
   end
 
   #returns true when http request to test_url returns a 200 OK Response
   def test_command
     begin
           response = Net::HTTP.get_response(URI.parse(test_url))
           @test_result = response.code
 
          if response.code != "200"
               return FALSE
         else
	          return TRUE
         end
 
     rescue Exception
            @test_result =  $! # $! global variable reference to the Exception object
            return FALSE
     end
 
  end
 
end

This test will also return FALSE ( test failed) when the http request encounters an Exception such as a Timeout::Error or a BAD URL. After each test the response code of the http request will be stored in the @test_result variable. When the http request encounters an Exception, the details of the exception is stored in the @test_result variable.

Now lets write some specific tests that monitors real websites using TestWebpageForHttp200.

class TestMySite < TestWebpageForHttp200
   def initialize
      @test_description  = "My Website Test"
      @test_url = "http://www.whisperservers.com/blog/"
      super
   end
end
 
class TestMyBlog < TestWebpageForHttp200
#tests my blog, to check if the blog is loading
 
   def initialize
      @test_description  = "My Blog Test"
      @test_url = "http://obi-akubue.homeunix.org/"
      super
   end
end
 
class TestFakeSite < TestWebpageForHttp200
#tests a website that doesn't exist this test will always fail
   def initialize
      @test_description  = "Fake website"
      @test_url = "http://wenosee.org/"
      super
   end
end
 
jobs = [ TestMySite.new, TestMyBlog.new, TestFakeSite.new]
 
puts "Welcome to Ragios"
puts "Initializing"
...
#schedule all the jobs to execute test_command() at every time_interval
scheduler = Rufus::Scheduler.start_new
jobs.each do |job|
    scheduler.every job.time_interval do
     begin
       if job.test_command
           puts job.test_description + "   [PASSED]" + " Created on: "+ Time.now.to_s
       else
           puts job.test_description +   "   [FAILED]" + " Created on: "+ Time.now.to_s
           job.failed
       end
       #catch all exceptions
      rescue Exception
          puts "ERROR: " +  $!  + " Created on: "+ Time.now.to_s
          job.error_handler
      end
   end #end of scheduler
end
 
#trap Ctrl-C to exit gracefully
puts "PRESS CTRL-C to QUIT"
  loop do
  trap("INT") { puts "\nExiting"; exit; }
    sleep(3)
  end

The tests in the code above will run every hour because they all extend TestWebpageForHttp200 which has time_interval set to ‘1h’. If we want to set any of the tests to a different time interval we simply set the time_interval attribute in its initialize method.

I included a test to a fake website that doesn’t exist, this test will always fail.

class TestFakeSite < TestWebpageForHttp200
#tests a website that doesn't exist this test will always fail
   def initialize
      @test_description  = "Fake website"
      @test_url = "http://wenosee.org/"
      super
   end
end

When this test fails the system admin will receive an alert message like this via email or twitter.

Fake website FAILED!
HTTP Request to http://wenosee.org/ = getaddrinfo: Name or service not known
Created on: Tue Oct 05 17:49:49 -0400 2010

Ragios is able to give the system admin very detailed information on what went wrong when a test fails.

View the complete source code here
ragios.rb: http://gist.github.com/608890
Edit: View the source for the Ragios system here: http://github.com/obi-a/Ragios

The Daemon
Now we need Ragios to run as a Daemon process. Luckily, we have the Daemons ruby gem for this.
To install the gem

sudo gem install daemons

Lets write a script to make ragios.rb run as a daemon.
ragiosd

#!/usr/bin/ruby
 
  require 'rubygems'
  require 'daemons'
 
  options = {
  :app_name   => "ragios",
  :log_output => true,
  :monitor    => true
}
Daemons.run('ragios.rb', options)

This script should be in the same folder with ragios.rb.
To start Ragios as a daemon, we simply run the script.

Start the daemon

ruby ragiosd start

Stop the daemon

ruby ragiosd stop

Restart the daemon

ruby ragiosd restart

Check Status of the daemon

ruby ragiosd status

Test the daemon

ruby ragiosd run

In the ragiosd script, we have set the daemon to log its output and monitor the process.

 options = {
  :app_name   => "ragios",
  :log_output => true,
  :monitor    => true
}

When we start the daemon it will log its output in a file called ragios.output in the same folder with ragiosd. It will also log its actions in a log file called ragios.log

View the source for ragiosd here:
http://github.com/obi-a/Ragios

 
 
Premium Wordpress Plugin