1
0
Fork 0
mirror of https://github.com/fog/fog.git synced 2022-11-09 13:51:43 -05:00

Implement fog support for the Openstack Compute API v1.1. Includes

support for legacy v1.0 style auth and v2.0 keystone auth.
This commit is contained in:
Dan Prince 2011-09-26 02:41:54 -04:00
parent f812565897
commit 41f09986f4
48 changed files with 2016 additions and 0 deletions

View file

@ -38,6 +38,9 @@ module Fog
when :ninefold
require 'fog/ninefold/compute'
Fog::Compute::Ninefold.new(attributes)
when :openstack
require 'fog/openstack/compute'
Fog::Compute::OpenStack.new(attributes)
when :rackspace
require 'fog/rackspace/compute'
Fog::Compute::Rackspace.new(attributes)

View file

@ -48,6 +48,10 @@ An alternate file may be used by placing its path in the FOG_RC environment vari
:new_servers_username:
:public_key_path:
:private_key_path:
:openstack_api_key:
:openstack_username:
:openstack_auth_url:
:openstack_tenant:
:rackspace_api_key:
:rackspace_username:
:rackspace_servicenet:

112
lib/fog/openstack.rb Normal file
View file

@ -0,0 +1,112 @@
require(File.expand_path(File.join(File.dirname(__FILE__), 'core')))
module Fog
module OpenStack
extend Fog::Provider
module Errors
class ServiceError < Fog::Errors::Error
attr_reader :response_data
def self.slurp(error)
if error.response.body.empty?
data = nil
message = nil
else
data = MultiJson.decode(error.response.body)
message = data['message']
end
new_error = super(error, message)
new_error.instance_variable_set(:@response_data, data)
new_error
end
end
class InternalServerError < ServiceError; end
class Conflict < ServiceError; end
class NotFound < ServiceError; end
class ServiceUnavailable < ServiceError; end
class BadRequest < ServiceError
attr_reader :validation_errors
def self.slurp(error)
new_error = super(error)
unless new_error.response_data.nil?
new_error.instance_variable_set(:@validation_errors, new_error.response_data['validationErrors'])
end
new_error
end
end
end
service(:compute, 'openstack/compute', 'Compute')
# legacy v1.0 style auth
def self.authenticate_v1(options, connection_options = {})
openstack_auth_url = options[:openstack_auth_url]
uri = URI.parse(openstack_auth_url)
connection = Fog::Connection.new(openstack_auth_url, false, connection_options)
@openstack_api_key = options[:openstack_api_key]
@openstack_username = options[:openstack_username]
response = connection.request({
:expects => [200, 204],
:headers => {
'X-Auth-Key' => @openstack_api_key,
'X-Auth-User' => @openstack_username
},
:host => uri.host,
:method => 'GET',
:path => (uri.path and not uri.path.empty?) ? uri.path : 'v1.0'
})
return {
:token => response.headers['X-Auth-Token'],
:server_management_url => response.headers['X-Server-Management-Url']
}
end
# keystone style auth
def self.authenticate_v2(options, connection_options = {})
openstack_auth_url = options[:openstack_auth_url]
uri = URI.parse(openstack_auth_url)
connection = Fog::Connection.new(openstack_auth_url, false, connection_options)
@openstack_api_key = options[:openstack_api_key]
@openstack_username = options[:openstack_username]
@openstack_tenant = options[:openstack_tenant]
@compute_service_name = options[:openstack_compute_service_name]
req_body= {
'passwordCredentials' => {
'username' => @openstack_username,
'password' => @openstack_api_key
}
}
req_body['tenantId'] = @openstack_tenant if @openstack_tenant
response = connection.request({
:expects => [200, 204],
:body => req_body,
:host => uri.host,
:method => 'GET',
:path => (uri.path and not uri.path.empty?) ? uri.path : 'v2.0'
})
body=response.body
if body['auth']['serviceCatalog'] and body['auth']['serviceCatalog'][@compute_service_name] and body['auth']['serviceCatalog'][@compute_service_name][0] then
mgmt_url = body['auth']['serviceCatalog'][@compute_service_name][0]['publicURL']
token = body['auth']['token']['id']
return {
:token => token,
:server_management_url => mgmt_url
}
else
raise "Unable to parse service catalog."
end
end
end
end

View file

@ -0,0 +1,182 @@
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'openstack'))
require 'fog/compute'
module Fog
module Compute
class OpenStack < Fog::Service
requires :openstack_api_key, :openstack_username, :openstack_auth_url
recognizes :openstack_auth_token, :openstack_management_url, :persistent, :openstack_tenant, :openstack_compute_service_name
model_path 'fog/openstack/models/compute'
model :flavor
collection :flavors
model :image
collection :images
model :server
collection :servers
model :meta
collection :metadata
request_path 'fog/openstack/requests/compute'
request :create_server
request :delete_image
request :delete_server
request :get_flavor_details
request :get_image_details
request :get_server_details
request :list_addresses
request :list_private_addresses
request :list_public_addresses
request :list_flavors
request :list_flavors_detail
request :list_images
request :list_images_detail
request :list_servers
request :list_servers_detail
request :server_action
request :change_password_server
request :reboot_server
request :rebuild_server
request :resize_server
request :confirm_resized_server
request :revert_resized_server
request :create_image
request :update_server
request :set_metadata
request :update_metadata
request :list_metadata
request :get_meta
request :update_meta
request :delete_meta
class Mock
def self.data
@data ||= Hash.new do |hash, key|
hash[key] = {
:last_modified => {
:images => {},
:servers => {}
},
:images => {},
:servers => {}
}
end
end
def self.reset
@data = nil
end
def initialize(options={})
require 'multi_json'
@openstack_username = options[:openstack_username]
end
def data
self.class.data[@openstack_username]
end
def reset_data
self.class.data.delete(@openstack_username)
end
end
class Real
def initialize(options={})
require 'multi_json'
@openstack_api_key = options[:openstack_api_key]
@openstack_username = options[:openstack_username]
@openstack_tenant = options[:openstack_tenant]
@openstack_compute_service_name = options[:openstack_compute_service_name] || 'nova'
@openstack_auth_url = options[:openstack_auth_url]
@openstack_auth_token = options[:openstack_auth_token]
@openstack_management_url = options[:openstack_management_url]
@openstack_must_reauthenticate = false
@connection_options = options[:connection_options] || {}
authenticate
@persistent = options[:persistent] || false
@connection = Fog::Connection.new("#{@scheme}://#{@host}:#{@port}", @persistent, @connection_options)
end
def reload
@connection.reset
end
def request(params)
begin
response = @connection.request(params.merge({
:headers => {
'Content-Type' => 'application/json',
'X-Auth-Token' => @auth_token
}.merge!(params[:headers] || {}),
:host => @host,
:path => "#{@path}/#{params[:path]}",
:query => ('ignore_awful_caching' << Time.now.to_i.to_s)
}))
rescue Excon::Errors::Unauthorized => error
if error.response.body != 'Bad username or password' # token expiration
@openstack_must_reauthenticate = true
authenticate
retry
else # bad credentials
raise error
end
rescue Excon::Errors::HTTPStatusError => error
raise case error
when Excon::Errors::NotFound
Fog::Compute::OpenStack::NotFound.slurp(error)
else
error
end
end
unless response.body.empty?
response.body = MultiJson.decode(response.body)
end
response
end
private
def authenticate
if @openstack_must_reauthenticate || @openstack_auth_token.nil?
options = {
:openstack_api_key => @openstack_api_key,
:openstack_username => @openstack_username,
:openstack_auth_url => @openstack_auth_url,
:openstack_tenant => @openstack_tenant,
:openstack_compute_service_name => @openstack_compute_service_name
}
if @openstack_auth_url =~ /.*v2.0\/?$/
credentials = Fog::OpenStack.authenticate_v2(options, @connection_options)
else
credentials = Fog::OpenStack.authenticate_v1(options, @connection_options)
end
@auth_token = credentials[:token]
url = credentials[:server_management_url]
uri = URI.parse(url)
else
@auth_token = @openstack_auth_token
uri = URI.parse(@openstack_management_url)
end
@host = uri.host
@path = uri.path
# Force URL into v1.1 namespace (what this binding supports)
@path.sub!(/\/.*\/?/, '/v1.1/')
# Add tenant
@path += @openstack_tenant if @openstack_tenant
@port = uri.port
@scheme = uri.scheme
end
end
end
end
end

View file

@ -0,0 +1,20 @@
require 'fog/core/model'
module Fog
module Compute
class OpenStack
class Flavor < Fog::Model
identity :id
attribute :disk
attribute :name
attribute :ram
attribute :links
end
end
end
end

View file

@ -0,0 +1,28 @@
require 'fog/core/collection'
require 'fog/openstack/models/compute/flavor'
module Fog
module Compute
class OpenStack
class Flavors < Fog::Collection
model Fog::Compute::OpenStack::Flavor
def all
data = connection.list_flavors_detail.body['flavors']
load(data)
end
def get(flavor_id)
data = connection.get_flavor_details(flavor_id).body['flavor']
new(data)
rescue Fog::Compute::OpenStack::NotFound
nil
end
end
end
end
end

View file

@ -0,0 +1,57 @@
require 'fog/core/model'
require 'fog/openstack/models/compute/metadata'
module Fog
module Compute
class OpenStack
class Image < Fog::Model
identity :id
attribute :name
attribute :created_at, :aliases => 'created'
attribute :updated_at, :aliases => 'updated'
attribute :progress
attribute :status
attribute :minDisk
attribute :minRam
attribute :server, :aliases => 'server'
attribute :metadata
attribute :links
def initialize(attributes)
@connection = attributes[:connection]
super
end
def metadata
@metadata ||= begin
Fog::Compute::OpenStack::Metadata.new({
:connection => connection,
:parent => self
})
end
end
def metadata=(new_metadata={})
metas = []
new_metadata.each_pair {|k,v| metas << {"key" => k, "value" => v} }
metadata.load(metas)
end
def destroy
requires :id
connection.delete_image(id)
true
end
def ready?
status == 'ACTIVE'
end
end
end
end
end

View file

@ -0,0 +1,33 @@
require 'fog/core/collection'
require 'fog/openstack/models/compute/image'
module Fog
module Compute
class OpenStack
class Images < Fog::Collection
model Fog::Compute::OpenStack::Image
attribute :server
def all
data = connection.list_images_detail.body['images']
load(data)
if server
self.replace(self.select {|image| image.server_id == server.id})
end
end
def get(image_id)
data = connection.get_image_details(image_id).body['image']
new(data)
rescue Fog::Compute::OpenStack::NotFound
nil
end
end
end
end
end

View file

@ -0,0 +1,29 @@
require 'fog/core/model'
require 'fog/openstack/models/meta_parent'
module Fog
module Compute
class OpenStack
class Meta < Fog::Model
include Fog::Compute::OpenStack::MetaParent
identity :key
attribute :value
def destroy
requires :identity
connection.delete_meta(collection_name, @parent.id, key)
true
end
def save
requires :identity, :value
connection.update_meta(collection_name, @parent.id, key, value)
true
end
end
end
end
end

View file

@ -0,0 +1,69 @@
require 'fog/core/collection'
require 'fog/openstack/models/meta_parent'
require 'fog/openstack/models/compute/meta'
require 'fog/openstack/models/compute/image'
require 'fog/openstack/models/compute/server'
module Fog
module Compute
class OpenStack
class Metadata < Fog::Collection
model Fog::Compute::OpenStack::Meta
include Fog::Compute::OpenStack::MetaParent
def all
requires :parent
metadata = connection.list_metadata(collection_name, @parent.id).body['metadata']
metas = []
metadata.each_pair {|k,v| metas << {"key" => k, "value" => v} }
load(metas)
end
def get(key)
requires :parent
data = connection.get_meta(collection_name, @parent.id, key).body["meta"]
metas = []
data.each_pair {|k,v| metas << {"key" => k, "value" => v} }
new(metas[0])
rescue Fog::Compute::OpenStack::NotFound
nil
end
def update(data=nil)
requires :parent
connection.update_metadata(collection_name, @parent.id, meta_hash(data))
end
def set(data=nil)
requires :parent
connection.set_metadata(collection_name, @parent.id, meta_hash(data))
end
def new(attributes = {})
requires :parent
super({ :parent => @parent }.merge!(attributes))
end
private
def meta_hash(data=nil)
if data.nil?
data={}
self.each do |meta|
if meta.is_a?(Fog::Compute::OpenStack::Meta) then
data.store(meta.key, meta.value)
else
data.store(meta["key"], meta["value"])
end
end
end
data
end
end
end
end
end

View file

@ -0,0 +1,187 @@
require 'fog/compute/models/server'
require 'fog/openstack/models/compute/metadata'
module Fog
module Compute
class OpenStack
class Server < Fog::Compute::Server
identity :id
attribute :addresses
attribute :flavor
attribute :host_id, :aliases => 'hostId'
attribute :image
attribute :metadata
attribute :links
attribute :name
attribute :personality
attribute :progress
attribute :accessIPv4
attribute :accessIPv6
attribute :state, :aliases => 'status'
attr_reader :password
attr_writer :private_key, :private_key_path, :public_key, :public_key_path, :username, :image_ref, :flavor_ref
def initialize(attributes={})
@connection = attributes[:connection]
super
end
def metadata
@metadata ||= begin
Fog::Compute::OpenStack::Metadata.new({
:connection => connection,
:parent => self
})
end
end
def metadata=(new_metadata={})
metas = []
new_metadata.each_pair {|k,v| metas << {"key" => k, "value" => v} }
metadata.load(metas)
end
def destroy
requires :id
connection.delete_server(id)
true
end
def images
requires :id
connection.images(:server => self)
end
def private_ip_address
nil
end
def private_key_path
@private_key_path ||= Fog.credentials[:private_key_path]
@private_key_path &&= File.expand_path(@private_key_path)
end
def private_key
@private_key ||= private_key_path && File.read(private_key_path)
end
def public_ip_address
addresses['public'].first
end
def public_key_path
@public_key_path ||= Fog.credentials[:public_key_path]
@public_key_path &&= File.expand_path(@public_key_path)
end
def public_key
@public_key ||= public_key_path && File.read(public_key_path)
end
def image_ref
@image_ref
end
def image_ref=(new_image_ref)
@image_ref = new_image_ref
end
def flavor_ref
@flavor_ref
end
def flavor_ref=(new_flavor_ref)
@flavor_ref = new_flavor_ref
end
def ready?
self.state == 'ACTIVE'
end
def change_password(admin_password)
requires :id
connection.change_password_server(id, admin_password)
true
end
def rebuild
requires :id
connection.rebuild_server(id, name, metadata, personality)
true
end
def resize(flavor_ref)
requires :id
connection.resize_server(id, flavor_ref)
true
end
def revert_resize
requires :id
connection.revert_resize_server(id)
true
end
def confirm_resize
requires :id
connection.confirm_resize_server(id)
true
end
def reboot(type = 'SOFT')
requires :id
connection.reboot_server(id, type)
true
end
def save
raise Fog::Errors::Error.new('Resaving an existing object may create a duplicate') if identity
requires :flavor_ref, :image_ref, :name
meta_hash = {}
metadata.each { |meta| meta_hash.store(meta.key, meta.value) }
options = {
'metadata' => meta_hash,
'personality' => personality,
'accessIPv4' => accessIPv4,
'accessIPv6' => accessIPv6
}
options = options.reject {|key, value| value.nil?}
data = connection.create_server(name, image_ref, flavor_ref, options)
merge_attributes(data.body['server'])
true
end
def setup(credentials = {})
requires :public_ip_address, :identity, :public_key, :username
Fog::SSH.new(public_ip_address, username, credentials).run([
%{mkdir .ssh},
%{echo "#{public_key}" >> ~/.ssh/authorized_keys},
%{passwd -l #{username}},
%{echo "#{MultiJson.encode(attributes)}" >> ~/attributes.json},
%{echo "#{MultiJson.encode(metadata)}" >> ~/metadata.json}
])
rescue Errno::ECONNREFUSED
sleep(1)
retry
end
def username
@username ||= 'root'
end
private
def adminPass=(new_admin_pass)
@password = new_admin_pass
end
end
end
end
end

View file

@ -0,0 +1,36 @@
require 'fog/core/collection'
require 'fog/openstack/models/compute/server'
module Fog
module Compute
class OpenStack
class Servers < Fog::Collection
model Fog::Compute::OpenStack::Server
def all
data = connection.list_servers_detail.body['servers']
load(data)
end
def bootstrap(new_attributes = {})
server = create(new_attributes)
server.wait_for { ready? }
server.setup(:password => server.password)
server
end
def get(server_id)
if server = connection.get_server_details(server_id).body['server']
new(server)
end
rescue Fog::Compute::OpenStack::NotFound
nil
end
end
end
end
end

View file

@ -0,0 +1,33 @@
module Fog
module Compute
class OpenStack
module MetaParent
def parent
@parent
end
def parent=(new_parent)
@parent = new_parent
end
def collection_name
if @parent.class == Fog::Compute::OpenStack::Image
return "images"
elsif @parent.class == Fog::Compute::OpenStack::Server
return "servers"
else
raise "Metadata is not supported for this model type."
end
end
def metas_to_hash(metas)
hash = {}
metas.each { |meta| hash.store(meta.key, meta.value) }
hash
end
end
end
end
end

View file

@ -0,0 +1,24 @@
module Fog
module Compute
class OpenStack
class Real
def change_password_server(server_id, admin_password)
body = { 'changePassword' => { 'adminPass' => admin_password }}
server_action(server_id, body)
end
end
class Mock
def change_password_server(server_id, admin_password)
response = Excon::Response.new
response.status = 202
response
end
end
end
end
end

View file

@ -0,0 +1,24 @@
module Fog
module Compute
class OpenStack
class Real
def confirm_resized_server(server_id)
body = { 'confirmResize' => nil }
server_action(server_id, body, 204)
end
end
class Mock
def confirm_resized_server(server_id)
response = Excon::Response.new
response.status = 204
response
end
end
end
end
end

View file

@ -0,0 +1,27 @@
module Fog
module Compute
class OpenStack
class Real
def create_image(server_id, name, metadata={})
body = { 'createImage' => {
'name' => name,
'metadata' => metadata
}}
server_action(server_id, body)
end
end
class Mock
def create_image(server_id, name, metadata={})
response = Excon::Response.new
response.status = 202
response
end
end
end
end
end

View file

@ -0,0 +1,72 @@
module Fog
module Compute
class OpenStack
class Real
def create_server(name, image_ref, flavor_ref, options = {})
data = {
'server' => {
'flavorRef' => flavor_ref,
'imageRef' => image_ref,
'name' => name
}
}
if options['metadata']
data['server']['metadata'] = options['metadata']
end
if options['accessIPv4']
data['server']['accessIPv4'] = options['accessIPv4']
end
if options['accessIPv6']
data['server']['accessIPv6'] = options['accessIPv6']
end
if options['personality']
data['server']['personality'] = []
for file in options['personality']
data['server']['personality'] << {
'contents' => Base64.encode64(file['contents']),
'path' => file['path']
}
end
end
request(
:body => MultiJson.encode(data),
:expects => [200, 202],
:method => 'POST',
:path => 'servers.json'
)
end
end
class Mock
def create_server(name, image_ref, flavor_ref, options = {})
response = Excon::Response.new
response.status = 202
data = {
'addresses' => {},
'flavor' => {"id"=>"1", "links"=>[{"href"=>"http://nova1:8774/admin/flavors/1", "rel"=>"bookmark"}]},
'id' => Fog::Mock.random_numbers(6).to_s,
'image' => {"id"=>"3", "links"=>[{"href"=>"http://nova1:8774/admin/images/3", "rel"=>"bookmark"}]},
'links' => [{"href"=>"http://nova1:8774/v1.1/admin/servers/5", "rel"=>"self"}, {"href"=>"http://nova1:8774/admin/servers/5", "rel"=>"bookmark"}],
'hostId' => "123456789ABCDEF01234567890ABCDEF",
'metadata' => options['metadata'] || {},
'name' => options['name'] || "server_#{rand(999)}",
'accessIPv4' => options['accessIPv4'] || "",
'accessIPv6' => options['accessIPv6'] || "",
'progress' => 0,
'status' => 'BUILD'
}
self.data[:last_modified][:servers][data['id']] = Time.now
self.data[:servers][data['id']] = data
response.body = { 'server' => data.merge({'adminPass' => 'password'}) }
response
end
end
end
end
end

View file

@ -0,0 +1,40 @@
module Fog
module Compute
class OpenStack
class Real
def delete_image(image_id)
request(
:expects => 204,
:method => 'DELETE',
:path => "images/#{image_id}"
)
end
end
class Mock
def delete_image(image_id)
response = Excon::Response.new
if image = list_images_detail.body['images'].detect {|_| _['id'] == image_id}
if image['status'] == 'SAVING'
response.status = 409
raise(Excon::Errors.status_error({:expects => 202}, response))
else
self.data[:last_modified][:images].delete(image_id)
self.data[:images].delete(image_id)
response.status = 202
end
response
else
response.status = 400
raise(Excon::Errors.status_error({:expects => 202}, response))
end
end
end
end
end
end

View file

@ -0,0 +1,28 @@
module Fog
module Compute
class OpenStack
class Real
def delete_meta(collection_name, parent_id, key)
request(
:expects => 204,
:method => 'DELETE',
:path => "#{collection_name}/#{parent_id}/metadata/#{key}"
)
end
end
class Mock
def delete_meta(collection_name, parent_id, key)
response = Excon::Response.new
response.status = 204
response
end
end
end
end
end

View file

@ -0,0 +1,38 @@
module Fog
module Compute
class OpenStack
class Real
def delete_server(server_id)
request(
:expects => 204,
:method => 'DELETE',
:path => "servers/#{server_id}"
)
end
end
class Mock
def delete_server(server_id)
response = Excon::Response.new
if server = list_servers_detail.body['servers'].detect {|_| _['id'] == server_id}
if server['status'] == 'BUILD'
response.status = 409
raise(Excon::Errors.status_error({:expects => 204}, response))
else
self.data[:last_modified][:servers].delete(server_id)
self.data[:servers].delete(server_id)
response.status = 204
end
response
else
raise Fog::Compute::OpenStack::NotFound
end
end
end
end
end
end

View file

@ -0,0 +1,43 @@
module Fog
module Compute
class OpenStack
class Real
def get_flavor_details(flavor_ref)
request(
:expects => [200, 203],
:method => 'GET',
:path => "flavors/#{flavor_ref}.json"
)
end
end
class Mock
def get_flavor_details(flavor_ref)
response = Excon::Response.new
flavor = {
'1' => { 'id' => '1', 'name' => '256 server', 'ram' => 256, 'disk' => 10, 'links' => [] },
'2' => { 'id' => '2', 'name' => '512 server', 'ram' => 512, 'disk' => 20, 'links' => [] },
'3' => { 'id' => '3', 'name' => '1GB server', 'ram' => 1024, 'disk' => 40, 'links' => [] },
'4' => { 'id' => '4', 'name' => '2GB server', 'ram' => 2048, 'disk' => 80, 'links' => [] },
'5' => { 'id' => '5', 'name' => '4GB server', 'ram' => 4096, 'disk' => 160, 'links' => [] },
'6' => { 'id' => '6', 'name' => '8GB server', 'ram' => 8192, 'disk' => 320, 'links' => [] },
'7' => { 'id' => '7', 'name' => '15.5GB server', 'ram' => 15872, 'disk' => 620, 'links' => [] }
}[flavor_ref]
if flavor
response.status = 200
response.body = {
'flavor' => flavor
}
response
else
raise Fog::Compute::OpenStack::NotFound
end
end
end
end
end
end

View file

@ -0,0 +1,17 @@
module Fog
module Compute
class OpenStack
class Real
def get_image_details(image_id)
request(
:expects => [200, 203],
:method => 'GET',
:path => "images/#{image_id}.json"
)
end
end
end
end
end

View file

@ -0,0 +1,29 @@
module Fog
module Compute
class OpenStack
class Real
def get_meta(collection_name, parent_id, key)
request(
:expects => [200, 203],
:method => 'GET',
:path => "#{collection_name}/#{parent_id}/metadata/#{key}"
)
end
end
class Mock
def get_meta(collection_name, parent_id, key)
response = Excon::Response.new
response.status = 200
response.body = { 'meta' => {} }
response
end
end
end
end
end

View file

@ -0,0 +1,32 @@
module Fog
module Compute
class OpenStack
class Real
def get_server_details(server_id)
request(
:expects => [200, 203],
:method => 'GET',
:path => "servers/#{server_id}.json"
)
end
end
class Mock
def get_server_details(server_id)
response = Excon::Response.new
if server = list_servers_detail.body['servers'].detect {|_| _['id'] == server_id}
response.status = [200, 203][rand(1)]
response.body = { 'server' => server }
response
else
raise Fog::Compute::OpenStack::NotFound
end
end
end
end
end
end

View file

@ -0,0 +1,32 @@
module Fog
module Compute
class OpenStack
class Real
def list_addresses(server_id)
request(
:expects => [200, 203],
:method => 'GET',
:path => "servers/#{server_id}/ips.json"
)
end
end
class Mock
def list_addresses(server_id)
response = Excon::Response.new
if server = list_servers_detail.body['servers'].detect {|_| _['id'] == server_id}
response.status = [200, 203][rand(1)]
response.body = { 'addresses' => server['addresses'] }
response
else
raise Fog::Compute::OpenStack::NotFound
end
end
end
end
end
end

View file

@ -0,0 +1,38 @@
module Fog
module Compute
class OpenStack
class Real
def list_flavors
request(
:expects => [200, 203],
:method => 'GET',
:path => 'flavors.json'
)
end
end
class Mock
def list_flavors
response = Excon::Response.new
response.status = 200
response.body = {
'flavors' => [
{ 'name' => '256 server', 'id' => '1' },
{ 'name' => '512 server', 'id' => '2' },
{ 'name' => '1GB server', 'id' => '3' },
{ 'name' => '2GB server', 'id' => '4' },
{ 'name' => '4GB server', 'id' => '5' },
{ 'name' => '8GB server', 'id' => '6' },
{ 'name' => '15.5GB server', 'id' => '7' }
]
}
response
end
end
end
end
end

View file

@ -0,0 +1,38 @@
module Fog
module Compute
class OpenStack
class Real
def list_flavors_detail
request(
:expects => [200, 203],
:method => 'GET',
:path => 'flavors/detail.json'
)
end
end
class Mock
def list_flavors_detail
response = Excon::Response.new
response.status = 200
response.body = {
'flavors' => [
{ 'name' => '256 server', 'id' => '1', 'ram' => 256, 'disk' => 10, 'links' => [] },
{ 'name' => '512 server', 'id' => '2', 'ram' => 512, 'disk' => 20, 'links' => [] },
{ 'name' => '1GB server', 'id' => '3', 'ram' => 1024, 'disk' => 40, 'links' => [] },
{ 'name' => '2GB server', 'id' => '4', 'ram' => 2048, 'disk' => 80, 'links' => [] },
{ 'name' => '4GB server', 'id' => '5', 'ram' => 4096, 'disk' => 160, 'links' => [] },
{ 'name' => '8GB server', 'id' => '6', 'ram' => 8192, 'disk' => 320, 'links' => [] },
{ 'name' => '15.5GB server', 'id' => '7', 'ram' => 15872, 'disk' => 620, 'links' => [] }
]
}
response
end
end
end
end
end

View file

@ -0,0 +1,33 @@
module Fog
module Compute
class OpenStack
class Real
def list_images
request(
:expects => [200, 203],
:method => 'GET',
:path => 'images.json'
)
end
end
class Mock
def list_images
response = Excon::Response.new
data = list_images_detail.body['images']
images = []
for image in data
images << image.reject { |key, value| !['id', 'name'].include?(key) }
end
response.status = [200, 203][rand(1)]
response.body = { 'images' => images }
response
end
end
end
end
end

View file

@ -0,0 +1,39 @@
module Fog
module Compute
class OpenStack
class Real
def list_images_detail
request(
:expects => [200, 203],
:method => 'GET',
:path => 'images/detail.json'
)
end
end
class Mock
def list_images_detail
response = Excon::Response.new
images = self.data[:images].values
for image in images
case image['status']
when 'SAVING'
if Time.now - self.data[:last_modified][:images][image['id']] >= Fog::Mock.delay
image['status'] = 'ACTIVE'
end
end
end
response.status = [200, 203][rand(1)]
response.body = { 'images' => images.map {|image| image.reject {|key, value| !['id', 'name', 'status', 'updated'].include?(key)}} }
response
end
end
end
end
end

View file

@ -0,0 +1,28 @@
module Fog
module Compute
class OpenStack
class Real
def list_metadata(collection_name, parent_id)
request(
:expects => [200, 203],
:method => 'GET',
:path => "/#{collection_name}/#{parent_id}/metadata.json"
)
end
end
class Mock
def list_metadata(collection_name, parent_id)
response = Excon::Response.new
response.status = 200
response.body = {}
response
end
end
end
end
end

View file

@ -0,0 +1,32 @@
module Fog
module Compute
class OpenStack
class Real
def list_private_addresses(server_id)
request(
:expects => [200, 203],
:method => 'GET',
:path => "servers/#{server_id}/ips/private.json"
)
end
end
class Mock
def list_private_addresses(server_id)
response = Excon::Response.new
if server = list_servers_detail.body['servers'].detect {|_| _['id'] == server_id}
response.status = [200, 203][rand(1)]
response.body = { 'private' => server['addresses']['private'] }
response
else
raise Fog::Compute::Rackspace::NotFound
end
end
end
end
end
end

View file

@ -0,0 +1,32 @@
module Fog
module Compute
class OpenStack
class Real
def list_public_addresses(server_id)
request(
:expects => [200, 203],
:method => 'GET',
:path => "servers/#{server_id}/ips/public.json"
)
end
end
class Mock
def list_public_addresses(server_id)
response = Excon::Response.new
if server = list_servers_detail.body['servers'].detect {|_| _['id'] == server_id}
response.status = [200, 203][rand(1)]
response.body = { 'public' => server['addresses']['public'] }
response
else
raise Fog::Compute::Rackspace::NotFound
end
end
end
end
end
end

View file

@ -0,0 +1,33 @@
module Fog
module Compute
class OpenStack
class Real
def list_servers
request(
:expects => [200, 203],
:method => 'GET',
:path => 'servers.json'
)
end
end
class Mock
def list_servers
response = Excon::Response.new
data = list_servers_detail.body['servers']
servers = []
for server in data
servers << server.reject { |key, value| !['id', 'name'].include?(key) }
end
response.status = [200, 203][rand(1)]
response.body = { 'servers' => servers }
response
end
end
end
end
end

View file

@ -0,0 +1,39 @@
module Fog
module Compute
class OpenStack
class Real
def list_servers_detail
request(
:expects => [200, 203],
:method => 'GET',
:path => 'servers/detail.json'
)
end
end
class Mock
def list_servers_detail
response = Excon::Response.new
servers = self.data[:servers].values
for server in servers
case server['status']
when 'BUILD'
if Time.now - self.data[:last_modified][:servers][server['id']] > Fog::Mock.delay * 2
server['status'] = 'ACTIVE'
end
end
end
response.status = [200, 203][rand(1)]
response.body = { 'servers' => servers }
response
end
end
end
end
end

View file

@ -0,0 +1,24 @@
module Fog
module Compute
class OpenStack
class Real
def reboot_server(server_id, type = 'SOFT')
body = { 'reboot' => { 'type' => type }}
server_action(server_id, body)
end
end
class Mock
def reboot_server(server_id, type = 'SOFT')
response = Excon::Response.new
response.status = 202
response
end
end
end
end
end

View file

@ -0,0 +1,30 @@
module Fog
module Compute
class OpenStack
class Real
def rebuild_server(server_id, name, metadata=nil, personality=nil)
body = { 'rebuild' => {
'name' => name
}}
body['rebuild']['metadata'] = metadata if metadata
body['rebuild']['personality'] = personality if personality
#NOTE: the implementation returns 200 on rebuild
server_action(server_id, body, 200)
end
end
class Mock
def rebuild_server(server_id, name, metadata, personality)
response = Excon::Response.new
response.status = 202
response
end
end
end
end
end

View file

@ -0,0 +1,24 @@
module Fog
module Compute
class OpenStack
class Real
def resize_server(server_id, flavor_ref)
body = { 'resize' => { 'flavor' => { 'id' => flavor_ref }}}
server_action(server_id, body)
end
end
class Mock
def resize_server(server_id, flavor_ref)
response = Excon::Response.new
response.status = 202
response
end
end
end
end
end

View file

@ -0,0 +1,30 @@
module Fog
module Compute
class OpenStack
class Real
def revert_resized_server(server_id)
body = { 'revertResize' => nil }
server_action(server_id, body)
end
end
class Mock
def revert_resized_server(server_id)
response = Excon::Response.new
response.status = 202
self.data[:servers][server_id]['flavorId'] = self.data[:servers][server_id]['old_flavorId']
self.data[:servers][server_id].delete('old_flavorId')
self.data[:last_modified][:servers][server_id] = Time.now
self.data[:servers][server_id]['status'] = 'ACTIVE'
response
end
end
end
end
end

View file

@ -0,0 +1,18 @@
module Fog
module Compute
class OpenStack
class Real
def server_action(server_id, body, expects=202)
request(
:body => MultiJson.encode(body),
:expects => expects,
:method => 'POST',
:path => "servers/#{server_id}/action.json"
)
end
end
end
end
end

View file

@ -0,0 +1,45 @@
module Fog
module Compute
class OpenStack
class Real
def set_metadata(collection_name, parent_id, metadata = {})
request(
:body => MultiJson.encode({ 'metadata' => metadata }),
:expects => 200,
:method => 'PUT',
:path => "#{collection_name}/#{parent_id}/metadata"
)
end
end
class Mock
def set_metadata(collection_name, parent_id, metadata = {})
if collection_name == "images" then
if not list_images_detail.body['images'].detect {|_| _['id'] == parent_id}
raise Fog::Compute::OpenStack::NotFound
end
end
if collection_name == "servers" then
if not list_servers_detail.body['servers'].detect {|_| _['id'] == parent_id}
raise Fog::Compute::OpenStack::NotFound
end
end
response = Excon::Response.new
response.body = { "metadata" => metadata }
response.status = 200
response
end
end
end
end
end

View file

@ -0,0 +1,45 @@
module Fog
module Compute
class OpenStack
class Real
def update_meta(collection_name, parent_id, key, value)
request(
:body => MultiJson.encode({ 'meta' => { key => value }}),
:expects => 200,
:method => 'PUT',
:path => "#{collection_name}/#{parent_id}/metadata/#{key}"
)
end
end
class Mock
def update_meta(collection_name, parent_id, key, value)
if collection_name == "images" then
if not list_images_detail.body['images'].detect {|_| _['id'] == parent_id}
raise Fog::Compute::OpenStack::NotFound
end
end
if collection_name == "servers" then
if not list_servers_detail.body['servers'].detect {|_| _['id'] == parent_id}
raise Fog::Compute::OpenStack::NotFound
end
end
response = Excon::Response.new
response.body = { "meta" => { key => value } }
response.status = 200
response
end
end
end
end
end

View file

@ -0,0 +1,46 @@
module Fog
module Compute
class OpenStack
class Real
def update_metadata(collection_name, parent_id, metadata = {})
request(
:body => MultiJson.encode({ 'metadata' => metadata }),
:expects => 200,
:method => 'POST',
:path => "#{collection_name}/#{parent_id}/metadata.json"
)
end
end
class Mock
def update_metadata(collection_name, parent_id, metadata = {})
if collection_name == "images" then
if not list_images_detail.body['images'].detect {|_| _['id'] == parent_id}
raise Fog::Compute::OpenStack::NotFound
end
end
if collection_name == "servers" then
if not list_servers_detail.body['servers'].detect {|_| _['id'] == parent_id}
raise Fog::Compute::OpenStack::NotFound
end
end
#FIXME join w/ existing metadata here
response = Excon::Response.new
response.body = { "metadata" => metadata }
response.status = 200
response
end
end
end
end
end

View file

@ -0,0 +1,35 @@
module Fog
module Compute
class OpenStack
class Real
def update_server(server_id, options = {})
request(
:body => MultiJson.encode({ 'server' => options }),
:expects => 200,
:method => 'PUT',
:path => "servers/#{server_id}.json"
)
end
end
class Mock
def update_server(server_id, options)
response = Excon::Response.new
if server = list_servers_detail.body['servers'].detect {|_| _['id'] == server_id}
if options['name']
server['name'] = options['name']
end
response.status = 200
response
else
raise Fog::Compute::OpenStack::NotFound
end
end
end
end
end
end

View file

@ -38,6 +38,10 @@ if Fog.mock?
:ninefold_storage_token => 'ninefold_storage_token',
# :public_key_path => '~/.ssh/id_rsa.pub',
# :private_key_path => '~/.ssh/id_rsa',
:openstack_api_key => 'openstack_api_key',
:openstack_username => 'openstack_username',
:openstack_tenant => 'openstack_tenant',
:openstack_auth_url => 'openstack_auth_url',
:rackspace_api_key => 'rackspace_api_key',
:rackspace_username => 'rackspace_username',
:slicehost_password => 'slicehost_password',

View file

@ -0,0 +1,35 @@
Shindo.tests('Fog::Compute[:openstack] | flavor requests', ['openstack']) do
@flavor_format = {
'disk' => Integer,
'id' => String,
'name' => String,
'ram' => Integer,
'links' => Array
}
tests('success') do
tests('#get_flavor_details(1)').formats(@flavor_format) do
Fog::Compute[:openstack].get_flavor_details("1").body['flavor']
end
tests('#list_flavors').formats({'flavors' => [OpenStack::Compute::Formats::SUMMARY]}) do
Fog::Compute[:openstack].list_flavors.body
end
tests('#list_flavors_detail').formats({'flavors' => [@flavor_format]}) do
Fog::Compute[:openstack].list_flavors_detail.body
end
end
tests('failure') do
tests('#get_flavor_details(0)').raises(Fog::Compute::OpenStack::NotFound) do
Fog::Compute[:openstack].get_flavor_details("0")
end
end
end

View file

@ -0,0 +1,16 @@
class OpenStack
module Compute
module Formats
SUMMARY = {
'id' => String,
'name' => String
}
end
end
end

View file

@ -0,0 +1,64 @@
Shindo.tests('Fog::Compute[:openstack] | image requests', ['openstack']) do
@image_format = {
'created' => Fog::Nullable::String,
'id' => String,
'name' => String,
'progress' => Fog::Nullable::Integer,
'status' => String,
'updated' => String,
'minRam' => String,
'minDisk' => String,
#'server' => Hash,
'metadata' => Hash,
'links' => Array
}
tests('success') do
@image_id = 1
unless Fog.mocking?
Fog::Compute[:openstack].images.get(@image_id).wait_for { ready? }
end
tests("#get_image_details(#{@image_id})").formats(@image_format) do
pending if Fog.mocking?
Fog::Compute[:openstack].get_image_details(@image_id).body['image']
end
tests('#list_images').formats({'images' => [OpenStack::Compute::Formats::SUMMARY]}) do
Fog::Compute[:openstack].list_images.body
end
tests('#list_images_detail').formats({'images' => [@image_format]}) do
Fog::Compute[:openstack].list_images_detail.body
end
unless Fog.mocking?
Fog::Compute[:openstack].images.get(@image_id).wait_for { ready? }
end
if Fog.mocking?
tests("#delete_image(#{@image_id})").succeeds do
pending if Fog.mocking? # because it will fail without the wait just above here, which won't work
Fog::Compute[:openstack].delete_image(@image_id)
end
end
end
tests('failure') do
tests('#delete_image(0)').raises(Excon::Errors::BadRequest) do
Fog::Compute[:openstack].delete_image(0)
end
tests('#get_image_details(0)').raises(Fog::Compute::OpenStack::NotFound) do
pending if Fog.mocking?
Fog::Compute[:openstack].get_image_details(0)
end
end
end

View file

@ -0,0 +1,89 @@
Shindo.tests('Fog::Compute[:openstack] | server requests', ['openstack']) do
@server_format = {
'addresses' => Hash,
'flavor' => Hash,
'hostId' => String,
'id' => String,
'image' => Hash,
'metadata' => Hash,
'name' => String,
'progress' => Integer,
'status' => String,
'accessIPv4' => Fog::Nullable::String,
'accessIPv6' => Fog::Nullable::String,
'links' => Array
}
tests('success') do
@server_id = nil
tests('#create_server("test", 1, 19)').formats(@server_format.merge('adminPass' => String)) do
data = Fog::Compute[:openstack].create_server("test", 3, 1).body['server']
@server_id = data['id']
data
end
Fog::Compute[:openstack].servers.get(@server_id).wait_for { ready? }
tests("#get_server_details(#{@server_id})").formats(@server_format) do
Fog::Compute[:openstack].get_server_details(@server_id).body['server']
end
tests('#list_servers').formats({'servers' => [OpenStack::Compute::Formats::SUMMARY]}) do
Fog::Compute[:openstack].list_servers.body
end
tests('#list_servers_detail').formats({'servers' => [@server_format]}) do
Fog::Compute[:openstack].list_servers_detail.body
end
Fog::Compute[:openstack].servers.get(@server_id).wait_for { ready? }
tests("#update_server(#{@server_id}, :name => 'fogupdatedserver')").succeeds do
Fog::Compute[:openstack].update_server(@server_id, :name => 'fogupdatedserver')
end
Fog::Compute[:openstack].servers.get(@server_id).wait_for { ready? }
tests("#reboot_server(#{@server_id}, 'HARD')").succeeds do
Fog::Compute[:openstack].reboot_server(@server_id, 'HARD')
end
Fog::Compute[:openstack].servers.get(@server_id).wait_for { ready? }
tests("#reboot_server(#{@server_id}, 'SOFT')").succeeds do
Fog::Compute[:openstack].reboot_server(@server_id, 'SOFT')
end
Fog::Compute[:openstack].servers.get(@server_id).wait_for { ready? }
tests("#delete_server(#{@server_id})").succeeds do
Fog::Compute[:openstack].delete_server(@server_id)
end
end
tests('failure') do
tests('#delete_server(0)').raises(Fog::Compute::OpenStack::NotFound) do
Fog::Compute[:openstack].delete_server(0)
end
tests('#get_server_details(0)').raises(Fog::Compute::OpenStack::NotFound) do
Fog::Compute[:openstack].get_server_details(0)
end
tests("#update_server(0, :name => 'fogupdatedserver', :adminPass => 'fogupdatedserver')").raises(Fog::Compute::OpenStack::NotFound) do
Fog::Compute[:openstack].update_server(0, :name => 'fogupdatedserver', :adminPass => 'fogupdatedserver')
end
tests('#reboot_server(0)').raises(Fog::Compute::OpenStack::NotFound) do
pending if Fog.mocking?
Fog::Compute[:openstack].reboot_server(0)
end
end
end