eddorre

Found 5 posts tagged with 'xmpp'

A System for Distributing Local and Regional News Data

July 10, 2009 — 2 Comments

I live in Vancouver, Washington but I commute to Portland for work every week day. For my commute, I usually take I-5 and therefore have to traverse Interstate Bridge to cross over into Oregon. The bridge allows for marine traffic on the Columbia River to pass underneath by lifting the spans and thus closing traffic off until it is clear. Naturally, because of the traffic implications, this is an event that occurs rather infrequently, but it still occurs nonetheless.

There have been times when I’ve been unfortunate enough to be stuck in traffic while a bridge lift was happening. Judging by the amount of other commuters and freight vehicles stuck beside me, I’m guessing that information about spans lifting isn’t being distributed efficiently.

The last time this happened, I tweeted the message: “Does anyone know if ODOT or WADOT make notifications (of any kind – web other otherwise) before they raise the I5 Interstate Bridge?” I was curious about this because I wanted to make a simple web app that tells you if the span is up or down.

A few days later, a response came from the OregonDOT’s twitter account, "Yes, we send traffic alert to Portland area traffic reporters before a bridge lift. @pdxcommute usually tweets about them. They shared a number for me to call for more information and while I have not yet called to get the details my mind started wondering how they would be able to distribute this information in a timely manner to multiple sources. I put the idea out of my head until I read Ilya Grigorik’s post titled HTTP PubSub: Webhooks & PubSubHubbub.

Using that as a springboard, I’ve come up with a conceptual system that addresses these questions.

Let’s say that you operate a web application that distributes traffic information to subscribers via IM, Email, SMS or Twitter. You’ll want to get that information from a reliable source and in Oregon that would be ODOT.

ODOT will surely have more than one service asking for traffic update data, so a polling mechanism on the client end is very inefficient for both the server and the client. Here, PubSubHubbub makes perfect sense. Images below are adapted from this Google Document (PubSubHubbub Subscription Flow Draft 0.1):

In step 1, you can see that my fictional web application (Subscriber) asks the ODOT Publisher to subscribe to traffic data. The Publisher sends information to the subscriber telling it where it can receive that data.

Step 1 in A System for Distributing Local and Regional News Data

In step 2, our web application subscribes to the data feed and tells the Hub to send updates to a specific URL.

Step 2 in A System for Distributing Local and Regional News Data

In step 3, there is some security checking from the Hub back to the Subscriber. The Hub sends the data to the URL that the Subscriber specified in step 2.

Step 3 in A System for Distributing Local and Regional News Data

In step 4, Someone at ODOT has posted a new traffic update and therefore the Publisher updates the specified Hub.

Step 4 in A System for Distributing Local and Regional News Data

In step 5, the Hub POSTs the update to the endpoint URL that the Subscriber specified in Step 2. Although the diagram below shows the interaction between the Hub and one Subscriber, in theory a Hub can update multiple Subscribers at once.

Step 5 in A System for Distributing Local and Regional News Data

In the final step, the Subscriber can then re-post the data via IM, Twitter, Email, or SMS or simply display it on their website. Once a Subscriber gets a hold of that data, then it’s up to them what they do with it.

Step 6 in A System for Distributing Local and Regional News Data

This conceptual method for delivering data is much more efficient. Besides the initial setup, the only data that’s being transferred is actual data and there is no useless polling going on. Although PubSubHubbub is one solution to the polling problem, it’s not the only one. Others are using the IM standard, XMPP, to also distribute information to Subscribers.

If you’re interested in PubSubHubbub, I strongly suggest that you read the documentation at Google’s Code page.

Extending XMPP and Ruby Part II

September 21, 2008 — 0 Comments

Last month I wrote the a post titled Extending XMPP and Ruby. The post dealt with incorporating a plugin system to be used with my XMPP script that I wrote about in the post XMPP and Ruby.

To summarize the goal of the XMPP Ruby script; it’s a lightweight XMPP (Jabber/Instant Message) agent that resides on servers. Using this agent, I can immediately tell if a server is up and running as well as pass it commands through my IM client and have it reply back to me with the output. To enforce security, the script had a set number of commands that it could execute.

This had the problem of not really being scalable or extensible. For example, in order to add allowed commands one would have to modify the actual XMPP script itself. Not good.

In order to rectify that, I authored the plugin system that dynamically loaded classes at start time. This made it possible for me to be able to instantiate any object from those classes and run the methods of those classes.

In the test script it worked fine, but when incorporated with the actual XMPP agent script it was still hobbled by the fact that if you wanted to instantiate a new object you’d have to modify the original script. Again, not scalable or extensible.

With this latest iteration, I have added the ability to dynamically instantiate objects based on plugins as well as the ability to call the methods of the object all from the IM interface.

figure1

Before we explore the code, let’s take a look at our file structure (pictured in Figure 1). Everything is contained in a top level directory titled xmpp_agent. There is an init.rb file which loads all of the plugins in the plugins directory which are: command.rb, iis.rb, network.rb, system.rb. Below the plugins, are the files test.rb and xmpp_agent.rb.

The test.rb file is a “scratchpad” file that I use to test functionality. It is not a unit test file. As the figure mentions, the command.rb plugin file is new to this iteration. This file is responsible for creating objects dynamically at runtime as well as dynamically executing methods from those objects.

Since running commands have been abstracted out to its own class, there is no need to have that resident in the xmpp_agent.rb file. That code has been removed from the previous incarnation (see XMPP and Ruby). Everything else remains the same in the xmpp_agent.rb file with the exception of a new require declaration at the top of the script; require ‘init’ – as this loads our plugins (see Extending XMPP and Ruby).

When you send the agent a message that is phrased like this (items in brackets are variables):

command: [plugin] [method name] [arguments]

It will create a new Command object and calls the run_command method.

The first thing that the run_command method does is it calls a command parser method shown below:


  def parse_command(command)
    #Strip the command part out of the string - we don't need it any more.
    command.slice!("command: ")
        
    #Create an array for the arguments
    arguments = command.split(" ")
    return arguments
  end

This removes the extraneous “command:” part of the IM message and returns the rest of the string to the run_command method which is defined below:


  def run_command(command)
    vars = self.parse_command(command)
    
    #Convert the first item in the array to a class
    begin
      class_name = Object.const_get(vars[0].capitalize)
    rescue Exception => e
      return "ERROR: " << e.message << ". Are you sure that the #{vars[0]} plugin is installed?"
    end
    
    #Remove the first item (class name) since it's not needed anymore
    vars.delete_at(0)
    
    #Get the method name and remove it from the array
    if vars[0]
      method_name = vars.delete_at(0)
    else
      return "ERROR: Are you missing the method name?"
    end
    
    #If there are more arguments left call remote method passing in args, else just call the remote method
    if vars[0]
      run_remote_method(class_name, method_name, vars.each {|x| "#{x}" })
    else
      run_remote_method(class_name, method_name)
    end
  end

Now that we have everything parsed like we need it, we call run_remote_method.


  def run_remote_method(class_name, method_name, *args)
    begin
      o = class_name.new
    rescue Exception => e
      return "ERROR: " << e.message << ". Can't create the object, is it in the plugin folder?"
    end
    
    begin
      o.send(method_name, *args)
    rescue Exception => e
      return "ERROR: " << e.message << ". Did you include this method in the plugin file?"
    end
  end

As you can see, we dynamically instantiate a new object based on the class name that we’re passing in. After the exception handling, we call the method of the new object followed by any arguments.

For example: Let’s say that I want my server to ping an address. Instead of logging into the server and running the ping command, I could use the IM client to tell the server to ping the address and give me the results. All I have to type into the IM chat window is the following: command: network ping -c2 eddorre.com. This will ping eddorre.com twice on *nix machines.

The script will dynamically instantiate a Network object (provided it’s in the plugins directory) and try to run the method ping followed by any arguments.

In order for this to work, the network plugin/class has to have a method for ping in it. Let’s take a look at the code for that:


  def ping(*args)
    command = 'ping ' << args.join(" ")
    #Execute the command and return the result
    `#{command}`
  end

From the code above, I have defined a method called ping which takes the arguments at the command line, appends them to “ping”, and then executes the command (the backtick symbols mean execute the command and return the result).

Now, I’ll admit, using IM as an interactive shell is sort of limiting, but it can be much more powerful. For example, let’s take a look at the IIS class. This class, would in theory, handle everything related to Microsoft’s IIS web server. Let’s say that we want to find out how many current anonymous users are on a specific web site on our web server. We can define a method to return that information via the IM interface.

For example, let’s say I call this from the IM interface: command: iis current_anon_users [website]

The code for the current_anon_users method is below:


require 'win32ole'

class Iis < Plugin
  def current_anon_users(site)
    wmi = WIN32OLE.connect("winmgmts:root\\cimv2:Win32_PerfRawData_W3SVC_WebService.Name='_#{site}'")
    wmi.CurrentAnonymousUsers
  end
end

The code is simple, use a WMI object to return the data using the built in performance monitor on the server.

Using this new extensibility, you would be able to send a message to an agent such as “record this show” or “turn on the lights” depending on what you wanted it to do. All you have to do is build the plugin for it.

Extending XMPP and Ruby

August 26, 2008 — 3 Comments

After writing up my XMPP Agent written in Ruby, my friend Billy wrote a comment about a similar system written by Michael Still the author of Practical MythTV. His system for controlling MythTV over XMPP is the gtalkbot.

Curious as to how he worked around some issues, I downloaded the Python source code and stumbled my way through it. I was struck by the fact that he used a plugin architecture that would allow other authors to extend the functionality of his original program.

After seeing that, I decided that I would try something similar.

Without worrying about the XMPP Agent, I set out to code a plugin architecture. In a directory I have the following files:

  • init.rb (should probably be called plugin_loader.rb or something more descriptive)
  • test.rb
  • /plugins (this is a directory)
    • network.rb (inside the plugins directory)
    • system.rb (inside the plugins directory)

The code inside the test.rb file is simple enough:


require 'init'

puts “This is the allowed command set”

puts ALLOWED_COMMANDS

The first line of the code, loads the init.rb file which does the heavy lifting. The init.rb file has a plugin class defined. Other plugins will inherit from this class.

The plugin class is defined as:


require 'find'

class Plugin
  
  attr_reader :allowed_commands
  
  def initialize
    @allowed_commands = %w{ iisreset }
  end
  
  def load_plugins(dir, name="/^[a-z]+.rb/")
    plugins = []
    Find.find(dir) do |path|
        Find.prune if [".",".."].include? path
        plugins << path if File.basename(path).include? ".rb"
    end
    
    plugins.each do |item|
      puts "Loading plugin => #{item}"
      #Load the file
      require File.join(File.dirname(__FILE__), item)
      
      #Extract the file name from the directory name (without .rb extension)
      file_name = File.basename(item, ".rb")
      
      #Create a new object and instantiate it and find the allowed_methods attribute
      c = Object.const_get(file_name.capitalize).new
      puts "Loading command set for plugin #{file_name.capitalize} "
      puts c.allowed_commands
      
      #Add it to the array for allowed_commands
      @allowed_commands << c.allowed_commands
    end
  end
  
  def run_command(command)
    #Strip the command part out of the string - we don't need it anymore
    command.slice!("command: ")
    
    #Create an array for the arguments
    arguments = command.split(" ")
    arguments.delete_at(0) #Delete the first index, this is the command itself without arguments
    
    #Loop through the arguments and then delete them from the command string
    arguments.each { |item| command.slice!(item) }
        
    if @allowed_commands.include? command
      puts "#{command} is an allowed command"
      result = `#{command} #{arguments.join(" ")}` #Backticks are a shortcut for system(yourcommand).
    else
      result = "#{command} cannot be run"
    end
    return result
  end
end

At the end of the init.rb file is the code that actually starts loading the plugins:


app_plugins = Plugin.new app_plugins.load_plugins("./plugins") ALLOWED_COMMANDS = app_plugins.allowed_commands

Now it’s easy to extend the test.rb script by instantiating a new System object (one of our plugins) just by this simple code in the test.rb file:

tester = System.new

I can then call methods defined in the System.rb file (shown below):


tester.test

System.rb file:


class System < Plugin
  
  attr_reader :allowed_commands
  
  def initialize
    @allowed_commands = %w{ set shutdown }  
  end
  
  def test
    "This is a test"
  end
end

Now all of this was just really another proof of concept to find out if it could be done. I’m happy with the results even though I’m sure that there is a better way of doing it.

XMPP and Ruby

July 11, 2008 — 2 Comments

Although I didn’t make it to Ezra’s RailsConf 2008 presentation on scaling Rails, I was highly interested in the topic and downloaded the slides immediately after they were available.

The big news from his presentation was the reveal of Vertebra which is billed as a Next Generation Cloud Computing/Automation Framework. One slide stood out and immediately got me thinking with the question XMPP is a realtime messaging protocol built fro IM/chat, great for communication between thousands of people, why not machines?

After a dealing with a couple of server failures at work where notification was less than satisfactory, I started mulling around the thought of using Ezra’s idea for a small scale XMPP agent that was used for server monitoring and command processing.

The result is just a small proof of concept that I put together for a recent lunch and learn demonstration. This simple XMPP agent logs into my XMPP server (I’m using Jive Software’s OpenFire) and sets its presence to available. It also immediately sends me a message saying that it’s reporting for duty.

I’ve implemented message handling in a FIFO manner with an array that acts like a queue. If you send the agent a message (using a standard XMPP client like Spark or Adium) it will reply with “Thank you for sending me the message {yourmessagehere}”. If you preface your message with command: then it will attempt to execute that command (provided that it’s in the allowed list of commands). The output of the command is then sent to the sender as an IM message.

Please remember that this is only a proof of concept and it’s not my intent to put this iteration into production.

So without further ado, the code.


require 'rubygems'
require 'xmpp4r'
include Jabber

class Agent
  
  def initialize
    user = JID.new('yourusernamehere/XMPPAgent')
    @password = 'yourpasswordhere'
    @client = Client.new(user)
  end

  def connect(server_name, port)
    #Connect to server sending username and password
    @client.connect(server_name, port)
    @client.auth(@password)
    
    post_connect if @client
  end
  
  def post_connect
    #Set default presence to available
    status = Presence.new.set_type(:available)
    @client.send(status)
    #Start a new queue array
    @queue = []
    register_callbacks
  end
  
  def disconnect
    @client.close
  end
  
  def register_callbacks
    @client.add_message_callback do |message|
      @queue << message unless message.body.nil?
    end
  end
  
  def send_message(recipient, text, reply=false)
    message = Message.new(recipient)
    message.type = :chat
      if reply
        message.body = "Thank you for sending me the message: " << text
      else
        message.body = text
      end
      @client.send(message)
  end

  def start_worker_thread
    worker_thread = Thread.new do
      puts "Started new worker thread"
      #Start a loop to listen for incoming messages
      loop do
        if !@queue.empty?
          @queue.each do |item|
            puts item
            #Remove the resource from the user, e.g., carlos@xmppserver/exodus = carlos@xmppserver
            sender = item.from.to_s.sub(/\/.+$/, '')
            
            #If the message included the line command: create a new command object and attempt to run it
            if item.body.include? "command: "
              send_message(sender, "I'll try to run " << item.body.to_s, false)
              input_command = Command.new
              command_result = input_command.run_command(item.body.to_s)
              send_message(sender, command_result, false)
            else
              send_message(sender, item.body.to_s, true)            
            end
            @queue.shift
            puts "Queue is now empty" if @queue.empty?
          end
        end
      end
      sleep 1
    end
    worker_thread.join
  end
end

class Command
  @@allowable_commands = %w{ ipconfig ifconfig iisreset ping dig }
  
  def run_command(command)
    #Strip the command part out of the string - we don't need it any more.
    command.slice!("command: ")
        
    #Create an array for the arguments
    arguments = command.split(" ")
    arguments.delete_at(0) # Delete the first index, this is the command itself without arguments
    arguments.each {|x| puts "Argument: #{x}"}
    
    #Loop through the arguments and delete them from the command string
    arguments.each {|x| command.slice!(x)}
    
    puts "This is the command after munging #{command.strip!}"
    
    if @@allowable_commands.include? command
      puts "#{command} is an allowed command"
      result = `#{command} #{arguments.join(" ")}` #Backticks are a shortcut for system("commandhere"). Join the arguments back in.
    else
      result = "#{command} cannot be run"
    end
    puts result
    return result
  end
end

bot = Agent.new
bot.connect("xmppserver", "5222")
bot.send_message("carlos@xmppserver", "Bot reporting for duty at #{Time.now}", false)
bot.start_worker_thread

Wildfire EIM Server

September 06, 2006 — 0 Comments

Late last month I completed our transition from our jabberd server to JiveSoftware’s Wildfire EIM Server for the the company that I work for.

Wildfire, which is free and open source, is extremely robust in its capabilities. The server, which uses Java, runs on Linux, Windows, and OS X.

Some of the things that initially drew me to the product was its use of LDAP for authentication integration and roster information, the use of SSL between client and server, and it’s growing user community.

If you think that you might be interested in deploying an EIM server at your company, you should definitely take a look at Wildfire.

I’ll cover some configuration gotchas and solutions that I ran into next time around.