Merge pull request #1013 from jamesrose/master

Support for serverlove cloud services
This commit is contained in:
Sean Handley 2012-09-04 13:22:06 -07:00
commit 66038db5cb
29 changed files with 735 additions and 0 deletions

View File

@ -79,6 +79,7 @@ require 'fog/bin/ninefold'
require 'fog/bin/rackspace'
require 'fog/bin/openstack'
require 'fog/bin/ovirt'
require 'fog/bin/serverlove'
require 'fog/bin/stormondemand'
require 'fog/bin/terremark'
require 'fog/bin/vcloud'

31
lib/fog/bin/serverlove.rb Normal file
View File

@ -0,0 +1,31 @@
class Serverlove < Fog::Bin
class << self
def class_for(key)
case key
when :compute
Fog::Compute::Serverlove
else
raise ArgumentError, "Unrecognized service: #{key}"
end
end
def [](service)
@@connections ||= Hash.new do |hash, key|
hash[key] = case key
when :compute
Fog::Logger.warning("Serverlove[:compute] is not recommended, use Compute[:serverlove] for portability")
Fog::Compute.new(:provider => 'Serverlove')
else
raise ArgumentError, "Unrecognized service: #{key.inspect}"
end
end
@@connections[service]
end
def services
Fog::Serverlove.services
end
end
end

View File

@ -76,6 +76,9 @@ module Fog
require 'fog/rackspace/compute'
Fog::Compute::Rackspace.new(attributes)
end
when :serverlove
require 'fog/serverlove/compute'
Fog::Compute::Serverlove.new(attributes)
when :stormondemand
require 'fog/storm_on_demand/compute'
Fog::Compute::StormOnDemand.new(attributes)

View File

@ -21,6 +21,7 @@ require 'fog/ninefold'
require 'fog/rackspace'
require 'fog/openstack'
require 'fog/ovirt'
require 'fog/serverlove'
require 'fog/storm_on_demand'
require 'fog/vcloud'
require 'fog/virtual_box'

10
lib/fog/serverlove.rb Normal file
View File

@ -0,0 +1,10 @@
require(File.expand_path(File.join(File.dirname(__FILE__), 'core')))
module Fog
module Serverlove
extend Fog::Provider
service(:compute, 'serverlove/compute', 'Compute')
end
end

View File

@ -0,0 +1,97 @@
module Fog
module Compute
class Serverlove < Fog::Service
API_HOST = "api.z1-man.serverlove.com"
requires :serverlove_uuid, :serverlove_api_key
recognizes :serverlove_api_url
request_path 'fog/serverlove/requests/compute'
# Image
request :get_image
request :get_images
request :destroy_image
request :create_image
request :update_image
request :load_standard_image
# Server
request :get_servers
request :get_server
request :destroy_server
request :create_server
request :update_server
request :start_server
request :stop_server
request :shutdown_server
request :reset_server
model_path 'fog/serverlove/models/compute'
model :image
collection :images
model :server
collection :servers
class Mock
def initialize(options)
@serverlove_uuid = options[:serverlove_uuid] || Fog.credentials[:serverlove_uuid]
@serverlove_api_key = options[:serverlove_api_key] || Fog.credentials[:serverlove_api_key]
end
def request(options)
raise "Not implemented"
end
end
class Real
def initialize(options)
@api_uuid = options[:serverlove_uuid] || Fog.credentials[:serverlove_uuid]
@api_key = options[:serverlove_api_key] || Fog.credentials[:serverlove_api_key]
@api_host = options[:serverlove_api_url] || Fog.credentials[:serverlove_api_url] || API_HOST
@connection = Fog::Connection.new("https://#{@api_uuid}:#{@api_key}@#{@api_host}")
end
def request(params)
params = params.merge!(
:headers => {
"Accept" => "application/json"
}
)
params[:body] = encode_pairs(params[:options]) unless params[:options].nil?
response = @connection.request(params)
raise_if_error!(response)
response.body = Fog::JSON.decode(response.body) if response.body && response.body.length > 0
response
end
def encode_pairs(params)
params.keys.collect do |key|
"#{key} #{params[key]}"
end.join("\n")
end
# TODO
def raise_if_error!(response)
case response.status
when 400 then
raise 'omg'
end
end
end
end
end
end

View File

@ -0,0 +1,57 @@
require 'fog/core/model'
module Fog
module Compute
class Serverlove
class Image < Fog::Model
identity :id, :aliases => 'drive'
attribute :name
attribute :user
attribute :size
attribute :claimed
attribute :status
attribute :imaging
attribute :encryption_cipher, :aliases => 'encryption:cipher'
def save
attributes = {}
if(identity)
attributes = connection.update_image(identity, allowed_attributes).body
else
requires :name
requires :size
attributes = connection.create_image(allowed_attributes).body
end
merge_attributes(attributes)
self
end
def load_standard_image(standard_image_uuid)
requires :identity
connection.load_standard_image(identity, standard_image_uuid)
end
def ready?
status.upcase == 'ACTIVE'
end
def destroy
requires :identity
connection.destroy_image(identity)
self
end
def allowed_attributes
allowed = [:name, :size]
attributes.select {|k,v| allowed.include? k}
end
end
end
end
end

View File

@ -0,0 +1,26 @@
require 'fog/core/collection'
require 'fog/serverlove/models/compute/image'
module Fog
module Compute
class Serverlove
class Images < Fog::Collection
model Fog::Compute::Serverlove::Image
def all
data = connection.get_images.body
load(data)
end
def get(image_id)
data = connection.get_image(image_id).body
new(data)
end
end
end
end
end

View File

@ -0,0 +1,72 @@
require 'fog/core/model'
require 'fog/serverlove/util/compute/password_generator'
module Fog
module Compute
class Serverlove
class Server < Fog::Model
identity :id, :aliases => 'server'
attribute :name
attribute :cpu
attribute :mem
attribute :smp
attribute :ide_0_0, :aliases => 'ide:0:0'
attribute :ide_0_1, :aliases => 'ide:0:1'
attribute :ide_1_0, :aliases => 'ide:1:0'
attribute :ide_1_1, :aliases => 'ide:1:1'
attribute :boot
attribute :persistent
attribute :vnc
attribute :vnc_password, :aliases => 'vnc:password'
attribute :status
attribute :user
attribute :started
attribute :nic_0_model, :aliases => 'nic:0:model'
attribute :nic_0_dhcp, :aliases => 'nic:0:dhcp'
def save
attributes = {}
if(identity)
attributes = connection.update_server(identity, allowed_attributes).body
else
requires :name
requires :cpu
attributes = connection.create_server(self.defaults.merge(allowed_attributes)).body
end
merge_attributes(attributes)
self
end
def destroy
requires :identity
connection.destroy_server(identity)
self
end
def allowed_attributes
allowed = [
:name, :cpu, :smp, :mem, :persistent,
:vnc_password, :vnc,
:ide_0_0, :ide_0_1, :ide_1_0, :ide_1_1,
:boot, :nic_0_model, :nic_0_dhcp
]
attributes.select {|k,v| allowed.include? k}
end
def self.defaults
# TODO: Document default settings.
# Note that VNC password standards are strict (need explaining)
{ 'nic:0:model' => 'e1000', 'nic:0:dhcp' => 'auto',
'smp' => 'auto', 'vnc' => 'auto',
'vnc:password' => Fog::Compute::Serverlove::PasswordGenerator.generate
}
end
end
end
end
end

View File

@ -0,0 +1,26 @@
require 'fog/core/collection'
require 'fog/serverlove/models/compute/server'
module Fog
module Compute
class Serverlove
class Servers < Fog::Collection
model Fog::Compute::Serverlove::Server
def all
data = connection.get_servers.body
load(data)
end
def get(server_id)
data = connection.get_server(server_id).body
new(data)
end
end
end
end
end

View File

@ -0,0 +1,32 @@
module Fog
module Compute
class Serverlove
class Real
def create_image(options)
return nil if options.empty? || options.nil?
request(:method => "post", :path => "/drives/create", :expects => 200, :options => options)
end
end
class Mock
def create_image(options = {})
response = Excon::Response.new
response.status = 200
data = {
'drive' => Fog::Mock.random_numbers(1000000).to_s,
'name' => options['name'] || 'Test',
'size' => options['size'] || 12345
}
response.body = data
response
end
end
end
end
end

View File

@ -0,0 +1,34 @@
module Fog
module Compute
class Serverlove
class Real
def create_server(options)
return nil if options.empty? || options.nil?
request(:method => "post", :path => "/servers/create/stopped", :expects => 200, :options => options)
end
end
class Mock
def create_server(options = {})
response = Excon::Response.new
response.status = 200
data = {
'server' => Fog::Mock.random_numbers(1000000).to_s,
'name' => options['name'] || 'Test',
'cpu' => options['cpu'] || 1000,
'persistent' => options['persistent'] || false,
'vnc:password' => options['vnc:password'] || 'T35tServER!'
}
response.body = data
response
end
end
end
end
end

View File

@ -0,0 +1,13 @@
module Fog
module Compute
class Serverlove
class Real
def destroy_image(drive_id)
request(:method => "post", :path => "/drives/#{drive_id}/destroy", :expects => 204)
end
end
end
end
end

View File

@ -0,0 +1,13 @@
module Fog
module Compute
class Serverlove
class Real
def destroy_server(server_id)
request(:method => "post", :path => "/servers/#{server_id}/destroy", :expects => 204)
end
end
end
end
end

View File

@ -0,0 +1,13 @@
module Fog
module Compute
class Serverlove
class Real
def get_image(image_id)
request(:method => "get", :path => "/drives/#{image_id}/info", :expects => 200)
end
end
end
end
end

View File

@ -0,0 +1,13 @@
module Fog
module Compute
class Serverlove
class Real
def get_images
request(:method => "get", :path => "/drives/info", :expects => 200)
end
end
end
end
end

View File

@ -0,0 +1,13 @@
module Fog
module Compute
class Serverlove
class Real
def get_server(server_id)
request(:method => "get", :path => "/servers/#{server_id}/info", :expects => 200)
end
end
end
end
end

View File

@ -0,0 +1,13 @@
module Fog
module Compute
class Serverlove
class Real
def get_servers
request(:method => "get", :path => "/servers/info", :expects => 200)
end
end
end
end
end

View File

@ -0,0 +1,13 @@
module Fog
module Compute
class Serverlove
class Real
def load_standard_image(destination_image, source_image)
request(:method => "post", :path => "/drives/#{destination_image}/image/#{source_image}/gunzip", :expects => 204)
end
end
end
end
end

View File

@ -0,0 +1,13 @@
module Fog
module Compute
class Serverlove
class Real
def reset_server(server_id)
request(:method => "post", :path => "/servers/#{server_id}/reset", :expects => 204)
end
end
end
end
end

View File

@ -0,0 +1,13 @@
module Fog
module Compute
class Serverlove
class Real
def shutdown_server(server_id)
request(:method => "post", :path => "/servers/#{server_id}/shutdown", :expects => 204)
end
end
end
end
end

View File

@ -0,0 +1,13 @@
module Fog
module Compute
class Serverlove
class Real
def start_server(server_id)
request(:method => "post", :path => "/servers/#{server_id}/start", :expects => 200)
end
end
end
end
end

View File

@ -0,0 +1,13 @@
module Fog
module Compute
class Serverlove
class Real
def stop_server(server_id)
request(:method => "post", :path => "/servers/#{server_id}/stop", :expects => 204)
end
end
end
end
end

View File

@ -0,0 +1,15 @@
module Fog
module Compute
class Serverlove
class Real
def update_image(identifier, options)
return nil if identifier.nil? || identifier == ""
return nil if options.empty? || options.nil?
request(:method => "post", :path => "/drives/#{identifier}/set", :expects => 200, :options => options)
end
end
end
end
end

View File

@ -0,0 +1,15 @@
module Fog
module Compute
class Serverlove
class Real
def update_server(identifier, options)
return nil if identifier.nil? || identifier == ""
return nil if options.empty? || options.nil?
request(:method => "post", :path => "/servers/#{identifier}/set", :expects => 200, :options => options)
end
end
end
end
end

View File

@ -0,0 +1,11 @@
module Fog
module Compute
class Serverlove
class PasswordGenerator
def self.generate
('a'...'z').to_a.concat(('A'...'Z').to_a).shuffle[0,8].join
end
end
end
end
end

View File

@ -0,0 +1,57 @@
Shindo.tests('Fog::Compute[:serverlove] | drive requests', ['serverlove']) do
@image_format = {
'drive' => String,
'name' => String,
'user' => String,
'size' => Integer,
'claimed' => Fog::Nullable::String,
'status' => String,
'encryption:cipher' => String,
'read:bytes' => String,
'write:bytes' => String,
'read:requests' => String,
'write:requests' => String
}
tests('success') do
attributes = { 'name' => 'Test', 'size' => '24234567890' }
tests("#create_image").formats(@image_format) do
@image = Fog::Compute[:serverlove].create_image(attributes).body
end
tests("#list_images").succeeds do
Fog::Compute[:serverlove].images
end
tests("#update_image").returns(true) do
@image['name'] = "Diff"
Fog::Compute[:serverlove].update_image(@image['drive'], { name: @image['name'], size: @image['size']})
Fog::Compute[:serverlove].images.get(@image['drive']).name == "Diff"
end
tests("#load_standard_image").returns(true) do
# Load centos
Fog::Compute[:serverlove].load_standard_image(@image['drive'], '88ed067f-d2b8-42ce-a25f-5297818a3b6f')
Fog::Compute[:serverlove].images.get(@image['drive']).imaging != "" # This will be "x%" when imaging
end
tests("waits for imaging...").returns(true) do
while(percent_complete = Fog::Compute[:serverlove].images.get(@image['drive']).imaging)
sleep(1)
STDERR.print "#{percent_complete} "
break if percent_complete.include?("100")
end
STDERR.print "100% "
true
end
tests("#destroy_image").succeeds do
Fog::Compute[:serverlove].destroy_image(@image['drive'])
end
end
end

View File

@ -0,0 +1,85 @@
Shindo.tests('Fog::Compute[:serverlove] | server requests', ['serverlove']) do
@server_format = {
'server' => String,
'name' => String,
'user' => String,
'status' => String,
'started' => Fog::Nullable::String,
'cpu' => Integer,
'mem' => Integer,
'smp' => Fog::Nullable::String,
'persistent' => Fog::Nullable::String,
'vnc' => Fog::Nullable::String,
'vnc:password' => Fog::Nullable::String,
'nic:0:dhcp' => String,
'nic:0:model' => String
}
tests('success') do
attributes = { 'name' => 'Test', 'cpu' => '1000', 'mem' => '1000', 'persistent' => 'true' }
tests("#create_server").formats(@server_format) do
@server = Fog::Compute[:serverlove].create_server(Fog::Compute::Serverlove::Server.defaults.merge(attributes)).body
end
tests("#list_servers").succeeds do
Fog::Compute[:serverlove].servers
end
tests("#update_server").returns(true) do
@server['name'] = "Diff"
Fog::Compute[:serverlove].update_server(@server['server'], { name: @server['name']})
Fog::Compute[:serverlove].servers.get(@server['server']).name == "Diff"
end
tests("assigns drive to server").succeeds do
@image = Fog::Compute[:serverlove].create_image('name' => 'Test', 'size' => '24234567890').body
# Load debian
Fog::Compute[:serverlove].load_standard_image(@image['drive'], 'aca2fa0b-40bc-4e06-ad99-f1467690d5de')
Fog::Compute[:serverlove].update_server(@server['server'], { 'ide:0:0' => @image['drive'], 'boot' => 'ide:0:0'})
end
tests("waits for imaging...").returns(true) do
while(percent_complete = Fog::Compute[:serverlove].images.get(@image['drive']).imaging)
sleep(1)
STDERR.print "#{percent_complete} "
break if percent_complete.include?("100")
end
STDERR.print "100% "
true
end
tests("#start_server").returns(true) do
Fog::Compute[:serverlove].start_server(@server['server'])
Fog::Compute[:serverlove].servers.get(@server['server']).status == "active"
end
tests("#reset_server").returns(true) do
Fog::Compute[:serverlove].reset_server(@server['server'])
Fog::Compute[:serverlove].servers.get(@server['server']).status == "active"
end
tests("#shutdown_server").succeeds do
Fog::Compute[:serverlove].shutdown_server(@server['server'])
# Can't guarantee the OS will honour this command so don't test status
end
tests("#stop_server").returns(true) do
Fog::Compute[:serverlove].start_server(@server['server'])
Fog::Compute[:serverlove].stop_server(@server['server'])
Fog::Compute[:serverlove].servers.get(@server['server']).status == "stopped"
end
tests("#destroy_server").succeeds do
Fog::Compute[:serverlove].destroy_server(@server['server'])
end
tests("destroy test drive").succeeds do
Fog::Compute[:serverlove].destroy_image(@image['drive'])
end
end
end

View File

@ -0,0 +1,19 @@
require 'fog/serverlove/util/compute/password_generator'
Shindo.tests('Fog::Compute::Serverlove::PasswordGenerator | generate password', ['serverlove']) do
@password = Fog::Compute::Serverlove::PasswordGenerator.generate
tests("@password.length").returns(8) do
@password.length
end
tests("@password contains one capital letter").returns(true) do
@password.match(/[A-Z]/) && true
end
tests("@password contains one lower case letter").returns(true) do
@password.match(/[a-z]/) && true
end
end