Author Archive for Mike

How to ensure that only one instance of a script can run at a time

Whenever someone asks for a quick way to ensure that only one instance of a script can run at a time, I see a lot of people giving advice involving temporary lock/PID files (see here, here, and here). This is probably fine for most purposes, but I wonder why more people don’t just check the process table:

SCRIPT_NAME=`basename $0`
if [ `pgrep -c ${SCRIPT_NAME}` -gt 1 ]
then
  echo "Error: An instance of this script is already running!"
  exit 1
fi

This fragment uses pgrep to count the number of running processes with the same name as the currently running script. If the number of these processes is greater than one (the current script will always be counted), then we know that another instance is already running.

As far as I know (and I hope someone will correct me if I’m wrong), this technique avoids the race conditions associated with lock/PID files. It also avoids the possibility of orphaned lock/PID files in the event that your script doesn’t complete and is unable to clean up after itself.

dynect4r: A Ruby Library and Command Line Client for the Dynect REST API (Version 2)

Well, I should have listened to everyone who warned me about UltraDNS’s obscene prices. But I figured it’s only DNS, so how much more could they be compared to their competition? $50 per month? Maybe $100? Boy was I surprised to find out that UltraDNS’s prices are literally 10-25 times more than everyone else’s! Hilarious…

I’ve actually been a DynDNS customer since the late nineties or so (I have free custom DNS service for life for making a donation to them back when they were a much smaller company), so I had looked at Dyn.com‘s products before. I just must have gotten confused with all their different websites and DNS products, because I somehow got the impression that the DynDNS API wasn’t powerful enough to do what I wanted to do. I was absolutely wrong. After having written command line clients for both APIs (see ultradns4r, and now dynectr4), I think I speak from authority when I say the Dynect API is every bit as powerful as UltraDNS’s. And at 1/10th – 1/25th the cost of UltraDNS, going with Dynect is a no-brainer. But I’ve digressed long enough.

I wrote dynect4r for the same reason I wrote ultradns4r; I wanted to be able to manage all my DNS records via the command line. And now that I’ve learned how to package Ruby projects as gems, you can simply…

gem install dynect4r

and then do things like…

dynect4r-client -n test.example.org 1.1.1.1

Since the key feature of this project is the command line client, the actual library behind it is a pretty simple wrapper around rest-client. If you’re looking for something a bit more powerful to use in your own Ruby projects, you may be interested in dynect_rest by Adam Jacob from Opscode. We actually discovered each other’s projects last night in #chef, and realized that it would probably be a good idea to pool our efforts eventually.

A Chef Definition for Managing Iptables Rules

I wrote a Chef definition for managing Iptables rules a while back, but until now, it has only existed publicly as gist on github. I figured I’d post it here as an example of how to write your own Chef definitions.

The first thing I did was create an iptables cookbook with a default recipe that simply ensures that the iptables package is installed:

package "iptables" do
  package_name "iptables"
end

Next, I used the definitions documentation on the Opscode wiki to write the actual definition. What I’m basically doing here is accepting a few Iptables-specific parameters (table, chain, options) and dynamically building the execute resources to run the appropriate Iptables commands:

define :iptables_rule, :action => :create, :table => "filter", :chain => nil, :options => nil do
 
  include_recipe "iptables"
 
  if params[:table].empty?
    raise ArgumentError, "Missing required argument: table"
  end
  if params[:chain].nil? or params[:chain].empty?
    raise ArgumentError, "Missing required argument: chain"
  end
  if params[:options].nil? or params[:options].empty?
    raise ArgumentError, "Missing required argument: options"
  end
 
  iptables_bin = "/sbin/iptables"
  rule_id = "Chef Rule: #{params[:name]}"
  comment = "-m comment --comment \"#{rule_id}\""
 
  if params[:action] == :delete
    execute "delete_iptables_rule-#{params[:name]}" do
      command "#{iptables_bin} --table #{params[:table]} -D #{params[:chain]} #{params[:options]} #{comment}"
      only_if "#{iptables_bin} --table #{params[:table]} -S #{params[:chain]} | /bin/grep \"#{rule_id}\""
    end
  else
    execute "create_iptables_rule-#{params[:name]}" do
      command "#{iptables_bin} --table #{params[:table]} -A #{params[:chain]} #{params[:options]} #{comment}"
      not_if "#{iptables_bin} --table #{params[:table]} -S #{params[:chain]} | /bin/grep \"#{rule_id}\""
    end
  end
end

So now when I want to manage an Iptables rule with chef, I can just do something like this:

iptables_rule "jetty_port_80" do
  table "nat"
  chain "PREROUTING"
  options "--proto tcp --dport 80 --jump REDIRECT --to-port 8080"
end

ultradns4r: A Ruby Library and Command Line Client for the Neustar UltraDNS SOAP API

I just spent the last week or so learning more about SOAP than I ever wanted to know. ;-) Fortunately, the result of that hard work resulted in something that might benefit the EC2 community.

I am pleased to announce ultradns4r; a Ruby library and command line client for the Neustar UltraDNS SOAP API. I created this tool to alleviate the pain in dealing with EC2′s dynamic hostnames and IP addresses. Since it allows editing of arbitrary DNS records via the command line, it can be used to make EC2 instances update their own DNS records.

Lessons Learned

  • If you need to do SOAP in Ruby, just use Savon. Trust me on this.
  • WSSE authentication is a complete pain in the ass. I was unable to find any SOAP library (Ruby, Python, or Perl) that could authenticate with the UltraDNS API servers out of the box (Savon included). I ended up having to build the entire WSSE header manually in order to generate the exact XML needed.
  • Apparently, element order sometimes matters with SOAP! This is something I never expected, considering that (last I knew) the XML spec does not even allow you to enforce element or attribute order. This was also the cause of a lot of my WSSE problems. I found that for example, if the Password element came after the Nonce and Created elements (which is the case if you use Savon’s built-in WSSE authentication), then authentication would fail. Could this have been the result of having a buggy XML parser on the server side? In any case, this is one of those annoying issues to be aware of.

Better Disk Monitoring with Ganglia

The default disk monitoring module for Ganglia (moddisk.so) is pretty stupid, since it simply sums up the free disk space on all of your disks and presents it in a single graph. Frankly, this just doesn’t tell you much, so I wrote a more useful replacement:

This module reads a list of mounted file systems from a file (probably /proc/mounts) and creates a metric for each one, allowing you to graph disk usage for each file system separately. Enjoy!

Update: It appears that multidisk.py does the same thing, and is probably already included with your distribution. Oops! ;-)