From 2a9224cfa74bd05abd2f618ba73764ebf060fe87 Mon Sep 17 00:00:00 2001 From: ggoodale Date: Mon, 24 May 2010 11:03:52 -0700 Subject: [PATCH] [bbg] flesh out bbg support Added basic spec framework; changed class names to match the template fog seems to prefer Servers tests pass; create and delete implemented. Added an example script Properly handle a 409 response from the API Fixed BBG code to work with the new api endpoint changes; tweaks to the (currently nonexistent) reboot api call Implement the reboot command for blocks --- bin/fog | 3 +- examples/bluebox_create.rb | 33 ++++++++++ fog.gemspec | 24 ++++++++ lib/fog/bin.rb | 2 +- lib/fog/bluebox.rb | 31 +++++----- lib/fog/bluebox/bin.rb | 11 ++-- .../bluebox/models/{product.rb => flavor.rb} | 3 +- lib/fog/bluebox/models/flavors.rb | 41 +++++++++++++ .../bluebox/models/{template.rb => image.rb} | 2 +- .../models/{templates.rb => images.rb} | 8 +-- lib/fog/bluebox/models/products.rb | 37 ------------ lib/fog/bluebox/models/server.rb | 60 +++++++++++++++++++ lib/fog/bluebox/models/servers.rb | 44 ++++++++++++++ lib/fog/bluebox/parsers/create_block.rb | 48 +++++++++++++++ lib/fog/bluebox/parsers/get_block.rb | 47 +++++++++++++++ lib/fog/bluebox/parsers/get_blocks.rb | 33 ++++++++++ lib/fog/bluebox/parsers/get_flavor.rb | 33 ++++++++++ .../{get_products.rb => get_flavors.rb} | 16 +++-- .../{get_templates.rb => get_images.rb} | 6 +- lib/fog/bluebox/requests/create_block.rb | 49 +++++++++++++++ lib/fog/bluebox/requests/destroy_block.rb | 43 +++++++++++++ lib/fog/bluebox/requests/get_block.rb | 44 ++++++++++++++ lib/fog/bluebox/requests/get_blocks.rb | 37 ++++++++++++ lib/fog/bluebox/requests/get_flavor.rb | 42 +++++++++++++ .../{get_products.rb => get_flavors.rb} | 15 +++-- .../{get_templates.rb => get_images.rb} | 6 +- lib/fog/bluebox/requests/reboot_block.rb | 41 +++++++++++++ lib/fog/credentials.rb | 2 +- spec/bluebox/models/flavors_spec.rb | 14 +++++ spec/bluebox/models/server_spec.rb | 52 ++++++++++++++++ spec/bluebox/models/servers_spec.rb | 34 +++++++++++ spec/spec_helper.rb | 16 +++++ 32 files changed, 791 insertions(+), 86 deletions(-) create mode 100644 examples/bluebox_create.rb rename lib/fog/bluebox/models/{product.rb => flavor.rb} (91%) create mode 100644 lib/fog/bluebox/models/flavors.rb rename lib/fog/bluebox/models/{template.rb => image.rb} (85%) rename lib/fog/bluebox/models/{templates.rb => images.rb} (80%) delete mode 100644 lib/fog/bluebox/models/products.rb create mode 100644 lib/fog/bluebox/models/server.rb create mode 100644 lib/fog/bluebox/models/servers.rb create mode 100644 lib/fog/bluebox/parsers/create_block.rb create mode 100644 lib/fog/bluebox/parsers/get_block.rb create mode 100644 lib/fog/bluebox/parsers/get_blocks.rb create mode 100644 lib/fog/bluebox/parsers/get_flavor.rb rename lib/fog/bluebox/parsers/{get_products.rb => get_flavors.rb} (55%) rename lib/fog/bluebox/parsers/{get_templates.rb => get_images.rb} (76%) create mode 100644 lib/fog/bluebox/requests/create_block.rb create mode 100644 lib/fog/bluebox/requests/destroy_block.rb create mode 100644 lib/fog/bluebox/requests/get_block.rb create mode 100644 lib/fog/bluebox/requests/get_blocks.rb create mode 100644 lib/fog/bluebox/requests/get_flavor.rb rename lib/fog/bluebox/requests/{get_products.rb => get_flavors.rb} (51%) rename lib/fog/bluebox/requests/{get_templates.rb => get_images.rb} (83%) create mode 100644 lib/fog/bluebox/requests/reboot_block.rb create mode 100644 spec/bluebox/models/flavors_spec.rb create mode 100644 spec/bluebox/models/server_spec.rb create mode 100644 spec/bluebox/models/servers_spec.rb diff --git a/bin/fog b/bin/fog index 4ca63083e..b654c1597 100755 --- a/bin/fog +++ b/bin/fog @@ -5,7 +5,7 @@ require 'yaml' require File.join('fog', 'credentials') require File.join('fog', 'bin') -Fog.credential = (ARGV.first && :"#{ARGV.first}") || :default +Fog.credential = ARGV.first ? :"#{ARGV.first}" : :default unless Fog.credentials exit end @@ -16,6 +16,7 @@ require File.join('fog', 'rackspace', 'bin') require File.join('fog', 'slicehost', 'bin') require File.join('fog', 'terremark', 'bin') require File.join('fog', 'vcloud', 'bin') +require File.join('fog', 'bluebox', 'bin') if ARGV.length > 1 print(instance_eval(ARGV[1..-1].join(' ')).to_json) diff --git a/examples/bluebox_create.rb b/examples/bluebox_create.rb new file mode 100644 index 000000000..43c76ac86 --- /dev/null +++ b/examples/bluebox_create.rb @@ -0,0 +1,33 @@ +#!/usr/bin/env ruby + +# For example only - you'd want to use your own AMI id. +unless defined?(GENTOO_AMI) + GENTOO_AMI = 'ami-5ee70037' +end + +require 'rubygems' +require 'fog' + +@bluebox_api_key = "CHANGEME" +@aws_access_key_id = "CHANGEME" +@aws_secret_access_key = "CHANGEME" + +@flavor_id = "94fd37a7-2606-47f7-84d5-9000deda52ae" # Block 1GB Virtual Server +@image_id = "03807e08-a13d-44e4-b011-ebec7ef2c928" # Ubuntu 10.04 x64 LTS + +# Grab our current list of servers +@bbg_servers = Fog::Bluebox.new(:bluebox_api_key => @bluebox_api_key).servers +@ec2_servers = Fog::AWS::EC2.new(:aws_access_key_id => @aws_access_key_id, :aws_secret_access_key => @aws_secret_access_key).servers + +# Create a new server. +@server = @bbg_servers.new(:flavor_id => @flavor_id, :image_id => @image_id, + :name => "My Server", :password => "MyPassword") + +# Save the server, triggering its creation +@server.save + +if @server.status == 'error' + # The create failed - create a new server on Amazon instead + @server = @ec2_servers.new(:image_id => GENTOO_AMI) + @server.save +end diff --git a/fog.gemspec b/fog.gemspec index 9858d5de1..6416f2bfb 100644 --- a/fog.gemspec +++ b/fog.gemspec @@ -209,6 +209,27 @@ Gem::Specification.new do |s| lib/fog/aws/s3.rb lib/fog/aws/simpledb.rb lib/fog/bin.rb + lib/fog/bluebox.rb + lib/fog/bluebox/bin.rb + lib/fog/bluebox/models/flavor.rb + lib/fog/bluebox/models/flavors.rb + lib/fog/bluebox/models/image.rb + lib/fog/bluebox/models/images.rb + lib/fog/bluebox/models/server.rb + lib/fog/bluebox/models/servers.rb + lib/fog/bluebox/parsers/create_block.rb + lib/fog/bluebox/parsers/get_block.rb + lib/fog/bluebox/parsers/get_blocks.rb + lib/fog/bluebox/parsers/get_flavor.rb + lib/fog/bluebox/parsers/get_flavors.rb + lib/fog/bluebox/parsers/get_images.rb + lib/fog/bluebox/requests/create_block.rb + lib/fog/bluebox/requests/destroy_block.rb + lib/fog/bluebox/requests/get_block.rb + lib/fog/bluebox/requests/get_blocks.rb + lib/fog/bluebox/requests/get_flavor.rb + lib/fog/bluebox/requests/get_flavors.rb + lib/fog/bluebox/requests/get_images.rb lib/fog/collection.rb lib/fog/connection.rb lib/fog/credentials.rb @@ -416,6 +437,9 @@ Gem::Specification.new do |s| spec/aws/requests/simpledb/list_domains_spec.rb spec/aws/requests/simpledb/put_attributes_spec.rb spec/aws/requests/simpledb/select_spec.rb + spec/bluebox/models/flavors_spec.rb + spec/bluebox/models/server_spec.rb + spec/bluebox/models/servers_spec.rb spec/compact_progress_bar_formatter.rb spec/lorem.txt spec/rackspace/models/servers/flavors_spec.rb diff --git a/lib/fog/bin.rb b/lib/fog/bin.rb index 4b0eec5b3..fa072e158 100644 --- a/lib/fog/bin.rb +++ b/lib/fog/bin.rb @@ -5,7 +5,7 @@ module Fog def services services = [] - [::AWS, ::Local, ::Rackspace, ::Slicehost, ::Terremark, ::Vcloud].each do |service| + [::AWS, ::Local, ::Rackspace, ::Slicehost, ::Terremark, ::Vcloud, ::Bluebox].each do |service| if service.initialized? services << service end diff --git a/lib/fog/bluebox.rb b/lib/fog/bluebox.rb index 412c9e2c1..eefffd16f 100644 --- a/lib/fog/bluebox.rb +++ b/lib/fog/bluebox.rb @@ -1,16 +1,21 @@ module Fog module Bluebox + require 'fog/bluebox/models/images' + require 'fog/bluebox/requests/get_images' + require 'fog/bluebox/models/flavors' + require 'fog/bluebox/requests/get_flavors' + require 'fog/bluebox/models/flavor' + require 'fog/bluebox/requests/get_flavor' + require 'fog/bluebox/models/servers' + require 'fog/bluebox/requests/get_blocks' + require 'fog/bluebox/models/server' + require 'fog/bluebox/requests/get_block' + require 'fog/bluebox/requests/create_block' + require 'fog/bluebox/requests/destroy_block' + require 'fog/bluebox/requests/reboot_block' def self.new(options={}) - unless @required - require 'fog/bluebox/models/templates' - require 'fog/bluebox/requests/get_templates' - require 'fog/bluebox/models/products' - require 'fog/bluebox/requests/get_products' - @required = true - end - unless options[:bluebox_api_key] raise ArgumentError.new('bluebox_api_key is required to access Blue Box') end @@ -50,9 +55,9 @@ module Fog def initialize(options={}) @bluebox_api_key = options[:bluebox_api_key] - @host = options[:host] || "boxpanel.blueboxgrp.com" - @port = options[:port] || 443 - @scheme = options[:scheme] || 'https' + @host = options[:bluebox_host] || "boxpanel.blueboxgrp.com" + @port = options[:bluebox_port] || 443 + @scheme = options[:bluebox_scheme] || 'https' end def request(params) @@ -67,7 +72,7 @@ module Fog headers['Content-Type'] = 'application/xml' end - response = @connection.request({ + @connection.request({ :body => params[:body], :expects => params[:expects], :headers => headers.merge!(params[:headers] || {}), @@ -76,8 +81,6 @@ module Fog :parser => params[:parser], :path => params[:path] }) - - response end end diff --git a/lib/fog/bluebox/bin.rb b/lib/fog/bluebox/bin.rb index 7135c970a..355022d3b 100644 --- a/lib/fog/bluebox/bin.rb +++ b/lib/fog/bluebox/bin.rb @@ -1,5 +1,6 @@ module Bluebox class << self + if Fog.credentials[:bluebox_api_key] def initialized? @@ -9,10 +10,10 @@ module Bluebox def [](service) @@connections ||= Hash.new do |hash, key| credentials = Fog.credentials.reject do |k,v| - ![:bluebox_api_key].include?(k) + ![:bluebox_api_key, :bluebox_host, :bluebox_port, :bluebox_scheme].include?(k) end hash[key] = case key - when :slices + when :blocks Fog::Bluebox.new(credentials) end end @@ -20,15 +21,15 @@ module Bluebox end def flavors - self[:slices].flavors + self[:blocks].flavors end def images - self[:slices].images + self[:blocks].images end def servers - self[:slices].servers + self[:blocks].servers end else diff --git a/lib/fog/bluebox/models/product.rb b/lib/fog/bluebox/models/flavor.rb similarity index 91% rename from lib/fog/bluebox/models/product.rb rename to lib/fog/bluebox/models/flavor.rb index b119dc7fb..efffd1c25 100644 --- a/lib/fog/bluebox/models/product.rb +++ b/lib/fog/bluebox/models/flavor.rb @@ -3,12 +3,13 @@ require 'fog/model' module Fog module Bluebox - class Product < Fog::Model + class Flavor < Fog::Model identity :id attribute :name attribute :cost + attribute :description def bits # 64 diff --git a/lib/fog/bluebox/models/flavors.rb b/lib/fog/bluebox/models/flavors.rb new file mode 100644 index 000000000..f2f7a7475 --- /dev/null +++ b/lib/fog/bluebox/models/flavors.rb @@ -0,0 +1,41 @@ +require 'fog/collection' +require 'fog/bluebox/models/flavor' + +module Fog + module Bluebox + + class Mock + def flavors + Fog::Bluebox::Flavors.new(:connection => self) + end + end + + class Real + def flavors + Fog::Bluebox::Flavors.new(:connection => self) + end + end + + class Flavors < Fog::Collection + + model Fog::Bluebox::Flavor + + def all + data = connection.get_flavors.body['flavors'] + load(data) + end + + def get(product_id) + response = connection.get_flavor(product_id) + model.new(response.body) + rescue Excon::Errors::NotFound + # Whoops - no image with that id. + nil + rescue Excon::Errors::Forbidden + nil + end + + end + + end +end diff --git a/lib/fog/bluebox/models/template.rb b/lib/fog/bluebox/models/image.rb similarity index 85% rename from lib/fog/bluebox/models/template.rb rename to lib/fog/bluebox/models/image.rb index 06874b3f4..5fa21b933 100644 --- a/lib/fog/bluebox/models/template.rb +++ b/lib/fog/bluebox/models/image.rb @@ -3,7 +3,7 @@ require 'fog/model' module Fog module Bluebox - class Template < Fog::Model + class Image < Fog::Model identity :id diff --git a/lib/fog/bluebox/models/templates.rb b/lib/fog/bluebox/models/images.rb similarity index 80% rename from lib/fog/bluebox/models/templates.rb rename to lib/fog/bluebox/models/images.rb index 3edc14e5f..9ed7a97b0 100644 --- a/lib/fog/bluebox/models/templates.rb +++ b/lib/fog/bluebox/models/images.rb @@ -1,12 +1,12 @@ require 'fog/collection' -require 'fog/bluebox/models/template' +require 'fog/bluebox/models/image' module Fog module Bluebox class Mock def images(attributes = {}) - Fog::Bluebox::Templates.new({ + Fog::Bluebox::Images.new({ :connection => self }.merge!(attributes)) end @@ -14,7 +14,7 @@ module Fog class Real def images(attributes = {}) - Fog::Bluebox::Templates.new({ + Fog::Bluebox::Images.new({ :connection => self }.merge!(attributes)) end @@ -22,7 +22,7 @@ module Fog class Templates < Fog::Collection - model Fog::Bluebox::Template + model Fog::Bluebox::Image def all data = connection.get_templates.body['templates'] diff --git a/lib/fog/bluebox/models/products.rb b/lib/fog/bluebox/models/products.rb deleted file mode 100644 index 1f6d23796..000000000 --- a/lib/fog/bluebox/models/products.rb +++ /dev/null @@ -1,37 +0,0 @@ -require 'fog/collection' -require 'fog/bluebox/models/product' - -module Fog - module Bluebox - - class Mock - def products - Fog::Bluebox::Products.new(:connection => self) - end - end - - class Real - def products - Fog::Bluebox::Products.new(:connection => self) - end - end - - class Products < Fog::Collection - - model Fog::Bluebox::Product - - def all - data = connection.get_products.body['products'] - load(data) - end - - def get(product_id) - connection.get_product(product_id) - rescue Excon::Errors::Forbidden - nil - end - - end - - end -end diff --git a/lib/fog/bluebox/models/server.rb b/lib/fog/bluebox/models/server.rb new file mode 100644 index 000000000..1011f9555 --- /dev/null +++ b/lib/fog/bluebox/models/server.rb @@ -0,0 +1,60 @@ +require 'fog/model' + +module Fog + module Bluebox + + class BlockInstantiationError < StandardError; end + + class Server < Fog::Model + + identity :id + + attribute :memory + attribute :storage + attribute :hostname + attribute :cpu + attribute :ips + attribute :status + attribute :flavor_id + attribute :image_id + + # Not reported by the API, but used at create time + attr_accessor :name, :password, :hash, :text, :error + + def destroy + requires :id + connection.destroy_block(@id) + true + end + + def flavor + requires :flavor_id + connection.flavors.get(@flavor_id) + end + + def image + requires :image_id + connection.images.get(@image_id) + end + + def ready? + raise Fog::Bluebox::BlockInstantiationError, "Error creating block #{self.id}" if @status == 'error' + @status == 'running' + end + + def reboot(type = 'SOFT') + requires :id + connection.reboot_block(@id, type) + true + end + + def save + requires :flavor_id, :image_id, :name, :password + data = connection.create_block(@flavor_id, @image_id, @name, @password) + merge_attributes(data.body) + true + end + end + + end +end diff --git a/lib/fog/bluebox/models/servers.rb b/lib/fog/bluebox/models/servers.rb new file mode 100644 index 000000000..083358b97 --- /dev/null +++ b/lib/fog/bluebox/models/servers.rb @@ -0,0 +1,44 @@ +require 'fog/collection' +require 'fog/bluebox/models/server' + +module Fog + module Bluebox + + class Mock + def servers + Fog::Bluebox::Servers.new(:connection => self) + end + end + + class Real + def servers + Fog::Bluebox::Servers.new(:connection => self) + end + end + + class Servers < Fog::Collection + + model Fog::Bluebox::Server + + def all + data = connection.get_blocks.body['blocks'] + load(data) + end + + def get(server_id) + if server_id && server = connection.get_block(server_id).body + new(server) + elsif !server_id + nil + end + rescue Excon::Errors::NotFound + # No server found with that id + nil + rescue Excon::Errors::Forbidden + nil + end + + end + + end +end diff --git a/lib/fog/bluebox/parsers/create_block.rb b/lib/fog/bluebox/parsers/create_block.rb new file mode 100644 index 000000000..ffad16ca7 --- /dev/null +++ b/lib/fog/bluebox/parsers/create_block.rb @@ -0,0 +1,48 @@ +module Fog + module Parsers + module Bluebox + + class CreateBlock < Fog::Parsers::Base + + def reset + @response = {} + end + + def end_element(name) + case name + when 'address' + @response['ips'] ||= [] + @response['ips'] << @value + when 'memory', 'storage' + @response[name] = @value.to_i + when 'cpu' + @response[name] = @value.to_f + when 'id' + if @scope == 'product' + @response['flavor_id'] = @value + else + @response[name] = @value + end + when 'name', 'hostname', 'status', 'text', 'hash' + @response[name] = @value + when 'template' + @response['image_id'] = @value + when 'product' + @scope = nil + when 'error' # An error occurred + @response['status'] = 'error' + @response[name] = @value + end + end + + def start_element(name, attrs=[]) + super + case name + when 'product' + @scope = name + end + end + end + end + end +end diff --git a/lib/fog/bluebox/parsers/get_block.rb b/lib/fog/bluebox/parsers/get_block.rb new file mode 100644 index 000000000..3fd697b4e --- /dev/null +++ b/lib/fog/bluebox/parsers/get_block.rb @@ -0,0 +1,47 @@ +module Fog + module Parsers + module Bluebox + + class GetBlock < Fog::Parsers::Base + + def reset + @response = {} + end + + def end_element(name) + case name + when 'address' + @response['ips'] ||= [] + @response['ips'] << @value + when 'memory', 'storage' + @response[name] = @value.to_i + when 'cpu' + @response[name] = @value.to_f + when 'hostname', 'status' + @response[name] = @value + when 'id' + if @scope == 'product' + @response['flavor_id'] = @value + else + @response[name] = @value + end + when 'template' + @response['image_id'] = @value + when 'product' + @scope = nil + end + end + + def start_element(name, attrs=[]) + super + case name + when 'product' + @scope = name + end + end + + end + + end + end +end diff --git a/lib/fog/bluebox/parsers/get_blocks.rb b/lib/fog/bluebox/parsers/get_blocks.rb new file mode 100644 index 000000000..f78ed8aae --- /dev/null +++ b/lib/fog/bluebox/parsers/get_blocks.rb @@ -0,0 +1,33 @@ +module Fog + module Parsers + module Bluebox + + class GetBlocks < Fog::Parsers::Base + + def reset + @block = {} + @response = { 'blocks' => [] } + end + + def end_element(name) + case name + when 'ip' + @block['ips'] ||= [] + @block['ips'] << @value + when 'memory', 'storage' + @block[name] = @value.to_i + when 'cpu' + @block[name] = @value.to_f + when 'hostname', 'id' + @block[name] = @value + when 'record' + @response['blocks'] << @block + @block = {} + end + end + + end + + end + end +end diff --git a/lib/fog/bluebox/parsers/get_flavor.rb b/lib/fog/bluebox/parsers/get_flavor.rb new file mode 100644 index 000000000..296595fbf --- /dev/null +++ b/lib/fog/bluebox/parsers/get_flavor.rb @@ -0,0 +1,33 @@ +# Response formet: +# +# 0.15 +# 94fd37a7-2606-47f7-84d5-9000deda52ae +# Block 1GB Virtual Server +# + +module Fog + module Parsers + module Bluebox + + class GetFlavor < Fog::Parsers::Base + + def reset + @response = {} + end + + def end_element(name) + case name + when 'hash' + # Do nothing + when 'cost' + @response[name] = @value.to_f + else + @response[name] = @value + end + end + + end + + end + end +end diff --git a/lib/fog/bluebox/parsers/get_products.rb b/lib/fog/bluebox/parsers/get_flavors.rb similarity index 55% rename from lib/fog/bluebox/parsers/get_products.rb rename to lib/fog/bluebox/parsers/get_flavors.rb index 292fdd8ff..ea8913c0e 100644 --- a/lib/fog/bluebox/parsers/get_products.rb +++ b/lib/fog/bluebox/parsers/get_flavors.rb @@ -2,24 +2,22 @@ module Fog module Parsers module Bluebox - class GetProducts < Fog::Parsers::Base + class GetFlavors < Fog::Parsers::Base def reset @product = {} - @response = { 'products' => [] } + @response = { 'flavors' => [] } end def end_element(name) case name - when 'cost' - @product[name] = @value - when 'id' - @product[name] = @value - when 'description' - @product[name] = @value when 'record' - @response['products'] << @product + @response['flavors'] << @product @product = {} + when 'cost' + @product[name] = @value.to_f + else + @product[name] = @value end end diff --git a/lib/fog/bluebox/parsers/get_templates.rb b/lib/fog/bluebox/parsers/get_images.rb similarity index 76% rename from lib/fog/bluebox/parsers/get_templates.rb rename to lib/fog/bluebox/parsers/get_images.rb index fa6df84cb..484a8e8c0 100644 --- a/lib/fog/bluebox/parsers/get_templates.rb +++ b/lib/fog/bluebox/parsers/get_images.rb @@ -2,11 +2,11 @@ module Fog module Parsers module Bluebox - class GetTemplates < Fog::Parsers::Base + class GetImages < Fog::Parsers::Base def reset @template = {} - @response = { 'templates' => [] } + @response = { 'images' => [] } end def end_element(name) @@ -18,7 +18,7 @@ module Fog when 'description' @template[name] = @value when 'record' - @response['templates'] << @template + @response['images'] << @template @template = {} end end diff --git a/lib/fog/bluebox/requests/create_block.rb b/lib/fog/bluebox/requests/create_block.rb new file mode 100644 index 000000000..346811f5e --- /dev/null +++ b/lib/fog/bluebox/requests/create_block.rb @@ -0,0 +1,49 @@ +module Fog + module Bluebox + class Real + + require 'fog/bluebox/parsers/create_block' + + # Get list of slices + # ==== Parameters + # * flavor_id<~Integer> - Id of flavor to create slice with + # * image_id<~Integer> - Id of image to create slice with + # * name<~String> - Name of slice + # + # ==== Returns + # * response<~Excon::Response>: + # * body<~Array>: + # * 'addresses'<~Array> - Ip addresses for the slice + # * 'backup-id'<~Integer> - Id of backup slice was booted from + # * 'bw-in'<~Integer> - Incoming bandwidth total for current billing cycle, in Gigabytes + # * 'bw-out'<~Integer> - Outgoing bandwidth total for current billing cycle, in Gigabytes + # * 'flavor-id'<~Integer> - Id of flavor slice was booted from + # * 'id'<~Integer> - Id of the slice + # * 'image-id'<~Integer> - Id of image slice was booted from + # * 'name'<~String> - Name of the slice + # * 'progress'<~Integer> - Progress of current action, in percentage + # * 'root-password'<~String> - Root password of slice + # * 'status'<~String> - Current status of the slice + def create_block(flavor_id, image_id, name, password) + response = request( + :body => %Q{#{flavor_id}#{name}#{password}}, # ["template=#{image_id}","product=#{flavor_id}"].join("\n"), + :expects => [200, 409], + :method => 'POST', + :parser => Fog::Parsers::Bluebox::CreateBlock.new, + :path => '/api/blocks.xml' + ) + + response + end + + end + + class Mock + + def create_block(flavor_id, image_id, name, password) + raise MockNotImplemented.new("Contributions welcome!") + end + + end + end +end diff --git a/lib/fog/bluebox/requests/destroy_block.rb b/lib/fog/bluebox/requests/destroy_block.rb new file mode 100644 index 000000000..fecf97d0a --- /dev/null +++ b/lib/fog/bluebox/requests/destroy_block.rb @@ -0,0 +1,43 @@ +module Fog + module Bluebox + class Real + + # Get list of slices + # ==== Parameters + # * flavor_id<~Integer> - Id of flavor to create slice with + # * image_id<~Integer> - Id of image to create slice with + # * name<~String> - Name of slice + # + # ==== Returns + # * response<~Excon::Response>: + # * body<~Array>: + # * 'addresses'<~Array> - Ip addresses for the slice + # * 'backup-id'<~Integer> - Id of backup slice was booted from + # * 'bw-in'<~Integer> - Incoming bandwidth total for current billing cycle, in Gigabytes + # * 'bw-out'<~Integer> - Outgoing bandwidth total for current billing cycle, in Gigabytes + # * 'flavor-id'<~Integer> - Id of flavor slice was booted from + # * 'id'<~Integer> - Id of the slice + # * 'image-id'<~Integer> - Id of image slice was booted from + # * 'name'<~String> - Name of the slice + # * 'progress'<~Integer> - Progress of current action, in percentage + # * 'root-password'<~String> - Root password of slice + # * 'status'<~String> - Current status of the slice + def destroy_block(block_id) + request( + :expects => 200, + :method => 'DELETE', + :path => "api/blocks/#{block_id}.xml" + ) + end + + end + + class Mock + + def destroy_block(block_id) + raise MockNotImplemented.new("Contributions welcome!") + end + + end + end +end diff --git a/lib/fog/bluebox/requests/get_block.rb b/lib/fog/bluebox/requests/get_block.rb new file mode 100644 index 000000000..0a84c8d03 --- /dev/null +++ b/lib/fog/bluebox/requests/get_block.rb @@ -0,0 +1,44 @@ +module Fog + module Bluebox + class Real + + require 'fog/bluebox/parsers/get_block' + + # Get details of a block. We do this by looking at the + # + # ==== Parameters + # * slice_id<~Integer> - Id of slice to lookup + # + # ==== Returns + # * response<~Excon::Response>: + # * body<~Hash>: + # * 'addresses'<~Array> - Ip addresses for the slice + # * 'backup-id'<~Integer> - Id of backup slice was booted from + # * 'bw-in'<~Float> - Incoming bandwidth total for current billing cycle, in Gigabytes + # * 'bw-out'<~Float> - Outgoing bandwidth total for current billing cycle, in Gigabytes + # * 'flavor_id'<~Integer> - Id of flavor slice was booted from + # * 'id'<~Integer> - Id of the slice + # * 'image-id'<~Integer> - Id of image slice was booted from + # * 'name'<~String> - Name of the slice + # * 'progress'<~Integer> - Progress of current action, in percentage + # * 'status'<~String> - Current status of the slice + def get_block(block_id) + request( + :expects => 200, + :method => 'GET', + :parser => Fog::Parsers::Bluebox::GetBlock.new, + :path => "api/blocks/#{block_id}.xml" + ) + end + + end + + class Mock + + def get_slice(id) + raise MockNotImplemented.new("Contributions welcome!") + end + + end + end +end diff --git a/lib/fog/bluebox/requests/get_blocks.rb b/lib/fog/bluebox/requests/get_blocks.rb new file mode 100644 index 000000000..de462495e --- /dev/null +++ b/lib/fog/bluebox/requests/get_blocks.rb @@ -0,0 +1,37 @@ +module Fog + module Bluebox + class Real + + require 'fog/bluebox/parsers/get_blocks' + + # Get list of blocks + # + # ==== Returns + # * response<~Excon::Response>: + # * body<~Array>: + # * 'ips'<~Array> - Ip addresses for the block + # * 'id'<~String> - Id of the block + # * 'storage'<~Integer> - Disk space quota for the block + # * 'memory'<~Integer> - RAM quota for the block + # * 'cpu'<~Float> - The fractional CPU quota for this block + # * 'hostname'<~String> - The hostname for the block + def get_blocks + request( + :expects => 200, + :method => 'GET', + :parser => Fog::Parsers::Bluebox::GetBlocks.new, + :path => 'api/blocks.xml' + ) + end + + end + + class Mock + + def get_slices + raise MockNotImplemented.new("Contributions welcome!") + end + + end + end +end diff --git a/lib/fog/bluebox/requests/get_flavor.rb b/lib/fog/bluebox/requests/get_flavor.rb new file mode 100644 index 000000000..61cc8df9b --- /dev/null +++ b/lib/fog/bluebox/requests/get_flavor.rb @@ -0,0 +1,42 @@ +module Fog + module Bluebox + class Real + + require 'fog/bluebox/parsers/get_flavor' + + # Get details of a flavor + # + # ==== Parameters + # * flavor_id<~Integer> - Id of flavor to lookup + # + # ==== Returns + # * response<~Excon::Response>: + # * body<~Array>: + # * 'id'<~Integer> - Id of the flavor + # * 'name'<~String> - Name of the flavor + # * 'price'<~Integer> - Price in cents + # * 'ram'<~Integer> - Amount of ram for the flavor + def get_flavor(flavor_id) + request( + :expects => 200, + :method => 'GET', + :parser => Fog::Parsers::Bluebox::GetFlavor.new, + :path => "api/block_products/#{flavor_id}.xml" + ) + end + + end + + class Mock + + def get_flavor(flavor_id) + if flavor_id == "0" + nil + else + Flavor.new(:cost => 0.15, :description => "Test product", :id => "94fd37a7-2606-47f7-84d5-9000deda52ae", :name => "Bob's Haus o' Servers") + end + end + + end + end +end diff --git a/lib/fog/bluebox/requests/get_products.rb b/lib/fog/bluebox/requests/get_flavors.rb similarity index 51% rename from lib/fog/bluebox/requests/get_products.rb rename to lib/fog/bluebox/requests/get_flavors.rb index 8c8565acc..9c08c32bc 100644 --- a/lib/fog/bluebox/requests/get_products.rb +++ b/lib/fog/bluebox/requests/get_flavors.rb @@ -2,7 +2,7 @@ module Fog module Bluebox class Real - require 'fog/bluebox/parsers/get_products' + require 'fog/bluebox/parsers/get_flavors' # Get list of products # @@ -12,12 +12,12 @@ module Fog # * 'id'<~String> - UUID of the product # * 'description'<~String> - Description of the product # * 'cost'<~Decimal> - Hourly cost of the product - def get_products + def get_flavors request( :expects => 200, :method => 'GET', - :parser => Fog::Parsers::Bluebox::GetProducts.new, - :path => 'api/products.xml' + :parser => Fog::Parsers::Bluebox::GetFlavors.new, + :path => 'api/block_products.xml' ) end @@ -25,8 +25,11 @@ module Fog class Mock - def get_images - raise MockNotImplemented.new("Contributions welcome!") + def get_flavors + response = Excon::Response.new + response.status = 200 + response.body = { 'flavors' => [{:cost => 0.15, :description => "Test product", :id => "94fd37a7-2606-47f7-84d5-9000deda52ae", :name => "Bob's Haus o' Servers"}]} + response end end diff --git a/lib/fog/bluebox/requests/get_templates.rb b/lib/fog/bluebox/requests/get_images.rb similarity index 83% rename from lib/fog/bluebox/requests/get_templates.rb rename to lib/fog/bluebox/requests/get_images.rb index 89edd0051..ba9b5ffb6 100644 --- a/lib/fog/bluebox/requests/get_templates.rb +++ b/lib/fog/bluebox/requests/get_images.rb @@ -2,7 +2,7 @@ module Fog module Bluebox class Real - require 'fog/bluebox/parsers/get_templates' + require 'fog/bluebox/parsers/get_images' # Get list of OS templates # @@ -13,11 +13,11 @@ module Fog # * 'description'<~String> - Description of the image # * 'public'<~Boolean> - Public / Private image # * 'created'<~Datetime> - Timestamp of when the image was created - def get_templates + def get_images request( :expects => 200, :method => 'GET', - :parser => Fog::Parsers::Bluebox::GetTemplates.new, + :parser => Fog::Parsers::Bluebox::GetImages.new, :path => 'api/block_templates.xml' ) end diff --git a/lib/fog/bluebox/requests/reboot_block.rb b/lib/fog/bluebox/requests/reboot_block.rb new file mode 100644 index 000000000..4e4170926 --- /dev/null +++ b/lib/fog/bluebox/requests/reboot_block.rb @@ -0,0 +1,41 @@ +module Fog + module Bluebox + class Real + + # Reboot slice + # ==== Parameters + # * slice_id<~String> - Id of server to reboot + # * type<~String> - Type of reboot, must be in ['HARD', 'SOFT'] + # + # ==== Returns + # * response<~Excon::Response>: + # * body<~Hash>: + # * 'addresses'<~Array> - Ip addresses for the slice + # * 'backup-id'<~Integer> - Id of backup slice was booted from + # * 'bw-in'<~Float> - Incoming bandwidth total for current billing cycle, in Gigabytes + # * 'bw-out'<~Float> - Outgoing bandwidth total for current billing cycle, in Gigabytes + # * 'flavor_id'<~Integer> - Id of flavor slice was booted from + # * 'id'<~Integer> - Id of the slice + # * 'image-id'<~Integer> - Id of image slice was booted from + # * 'name'<~String> - Name of the slice + # * 'progress'<~Integer> - Progress of current action, in percentage + # * 'status'<~String> - Current status of the slice + def reboot_block(block_id, type = 'SOFT') + request( + :expects => 200, + :method => 'PUT', + :path => "api/blocks/#{block_id}/#{'soft_' if type == 'SOFT'}reboot.xml" + ) + end + + end + + class Mock + + def get_block(id) + raise MockNotImplemented.new("Contributions welcome!") + end + + end + end +end diff --git a/lib/fog/credentials.rb b/lib/fog/credentials.rb index a6c3a4b52..eae970f24 100644 --- a/lib/fog/credentials.rb +++ b/lib/fog/credentials.rb @@ -37,7 +37,7 @@ module Fog :slicehost_password: INTENTIONALLY_LEFT_BLANK :terremark_username: INTENTIONALLY_LEFT_BLANK :terremark_password: INTENTIONALLY_LEFT_BLANK - + :bluebox_api_key: INTENTIONALLY_LEFT_BLANK YML print(yml) raise(ArgumentError.new("Missing Credentials")) diff --git a/spec/bluebox/models/flavors_spec.rb b/spec/bluebox/models/flavors_spec.rb new file mode 100644 index 000000000..4fd6fd813 --- /dev/null +++ b/spec/bluebox/models/flavors_spec.rb @@ -0,0 +1,14 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require File.dirname(__FILE__) + '/../../shared_examples/flavors_examples' + +describe 'Fog::Bluebox::Flavors' do + + it_should_behave_like "Flavors" + + subject { @flavor = @flavors.all.first } + + before(:each) do + @flavors = Bluebox[:blocks].flavors + end + +end diff --git a/spec/bluebox/models/server_spec.rb b/spec/bluebox/models/server_spec.rb new file mode 100644 index 000000000..e210f289d --- /dev/null +++ b/spec/bluebox/models/server_spec.rb @@ -0,0 +1,52 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require File.dirname(__FILE__) + '/../../shared_examples/server_examples' + +describe 'Fog::Bluebox::Server' do + + it_should_behave_like "Server" + + # flavor 1 = 256, image 3 = gentoo 2008.0 + subject { + @flavor_id = "94fd37a7-2606-47f7-84d5-9000deda52ae" # Block 1GB Virtual Server + @image_id = "03807e08-a13d-44e4-b011-ebec7ef2c928" # Ubuntu 10.04 x64 LTS + @server = @servers.new(:flavor_id => @flavor_id, :image_id => @image_id, :name => Time.now.to_i.to_s, :password => "chunkybacon") + @server + } + + before(:each) do + @servers = Bluebox[:blocks].servers + end + + after(:each) do + if @server && !@server.new_record? + @server.wait_for { ready? } + @server.destroy.should be_true + end + end + + describe "#initialize" do + + it "should remap attributes from parser" do + server = @servers.new({ + 'ips' => 'ips', + 'image_id' => 'image_id', + 'flavor_id' => 'flavor_id', + 'cpu' => 'cpu', + 'memory' => 'memory', + 'storage' => 'storage', + 'hostname' => 'hostname', + 'status' => 'status' + }) + server.ips.should == 'ips' + server.flavor_id.should == 'flavor_id' + server.image_id.should == 'image_id' + server.cpu.should == 'cpu' + server.memory.should == 'memory' + server.storage.should == 'storage' + server.hostname.should == 'hostname' + server.status.should == 'status' + end + + end + +end diff --git a/spec/bluebox/models/servers_spec.rb b/spec/bluebox/models/servers_spec.rb new file mode 100644 index 000000000..7e3d1d9bc --- /dev/null +++ b/spec/bluebox/models/servers_spec.rb @@ -0,0 +1,34 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require File.dirname(__FILE__) + '/../../shared_examples/servers_examples' + +describe 'Fog::Bluebox::Servers' do + + it_should_behave_like "Servers" + + it "should have the proper status after a create call" do + subject.save + new_server = @servers.get(subject.id) + subject.status.should == "queued" + new_server.status.should == "queued" + end + + # flavor 1 = 256, image 3 = gentoo 2008.0 + subject { + @flavor_id = "94fd37a7-2606-47f7-84d5-9000deda52ae" # Block 1GB Virtual Server + @image_id = "03807e08-a13d-44e4-b011-ebec7ef2c928" # Ubuntu 10.04 x64 LTS + @server = @servers.new(:flavor_id => @flavor_id, :image_id => @image_id, :name => Time.now.to_i.to_s, :password => "chunkybacon") + @server + } + + before(:each) do + @servers = Bluebox[:blocks].servers + end + + after(:each) do + if @server && !@server.new_record? + @server.wait_for { ready? } + @server.destroy.should be_true + end + end + +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0c83c6ab8..ec41a7f7b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -70,6 +70,22 @@ module Slicehost end end +module Bluebox + class << self + def [](service) + @@connections ||= Hash.new do |hash, key| + credentials = Fog.credentials.reject do |k, v| + ![:bluebox_api_key, :bluebox_host, :bluebox_port, :bluebox_scheme].include?(k) + end + hash[key] = case key + when :blocks + Fog::Bluebox.new(credentials) + end + end + @@connections[service] + end + end +end def eventually(max_delay = 16, &block)