Pages

Sunday, August 04, 2013

Parse OnDemand pricing for AWS in Ruby

Well I have been doing a LOT more Ruby programming since my last "Hello World" post. I figured that I should probably start sharing some of the more useful code.

First is this class which gives you a quick and easy way to determine the current OnDemand pricing for an instance type. There is a Gem out there for this already but I wanting something that was small which I understood myself (thats how you learn). The class is not really useful by itself, but I am writing some analysis of Spot pricing (will post when its fully complete) and it really need to be able to dynamically get the current pricing.

What that the code does is pull the current pricing from the Amazon web site, which returns a json file. This file has a deep data structure that is really overboard for most usage. Also, the naming for instance types is very different to the normal 'm1.small' format. Therefore the code does a mapping between the two.

All you need to do is copy and paste the Class into your file and then instantiate the instance and use the price method to get a specific value. For example

puts OnDemandPricing.new.price('ap-southeast-2','t1.micro','linux')

Use at your own risk.
#!/usr/bin/ruby

require "rubygems"
require "net/http"
require "uri"
require "json"
require "yaml"

class OnDemandPricing

  REGION_MAPPING = {
    'us-east'    => 'us-east-1',
    'us-west'    => 'us-west-1',
    'us-west-2'  => 'us-west-2',
    'eu-ireland' => 'eu-west-1',
    'apac-tokyo' => 'ap-northeast-1',
    'apac-sin'   => 'ap-southeast-1',
    'apac-syd'   => 'ap-southeast-2',
    'sa-east-1'  => 'sa-east-1'}

  TYPE_MAPPING = {
    'uODI'            => 't1',
    'stdODI'          => 'm1',
    'secgenstdODI'    => 'm3',
    'hiMemODI'        => 'm2',
    'hiCPUODI'        => 'c1',
    'clusterComputeI' => 'cc1',
    'clusterHiMemODI' => 'cr1',
    'clusterGPUI'     => 'cg1',
    'hiIoODI'         => 'hi1',
    'hiStoreODI'      => 'hs1'}

  SIZE_MAPPINGS = {
    'u' => 'micro', 
    'sm' => 'small',
    'med' => 'medium',
    'lg' => 'large',
    'xl' => 'xlarge',
    'xxl' => '2xlarge',
    'xxxxl' => '4xlarge',
    'xxxxxxxxl' => '8xlarge'}

  def autovivifying_hash
    # A little helper to save initialising the hash as we go, make Ruby more like Perl!
    # http://en.wikipedia.org/wiki/Autovivification
    Hash.new do |hash, key|
      hash[key] = autovivifying_hash
    end
  end

  def initialize
    uri = URI.parse(
      "http://aws.amazon.com/ec2/pricing/pricing-on-demand-instances.json")
    request = Net::HTTP::Get.new(uri.request_uri)
    http = Net::HTTP.new(uri.host, uri.port)
    response = http.request(request)
    data = JSON.parse(response.body)

    @price_table = autovivifying_hash

    # Walk the json structure finding the key data and load into an easy to
    # access hash of hash of hashes
    # being price_table[REGION][INSTANCE TYPE][OS linux/mswin] returns price
    data['config']['regions'].each do |reg|
      reg['instanceTypes'].each do |type|
        type['sizes'].each do |size|
          size['valueColumns'].each do |val|
            @price_table[REGION_MAPPING[reg['region']]] \
                        [
                         TYPE_MAPPING[type['type']] +
                         "." + 
                         SIZE_MAPPINGS[size['size']] 
                        ] \
                        [val['name']] = val['prices']['USD']
          end
        end
      end 
    end
  end

  def price (region, instance_type, os)
    return @price_table[region][instance_type][os]
  end
end # Class

#An example
puts OnDemandPricing.new.price('ap-southeast-2','t1.micro','linux') 
Enjoy. Any feedback use the comments.