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

Merge pull request #1496 from rubiojr/openstack-storage

OpenStack Storage Service
This commit is contained in:
Dan Prince 2013-01-28 12:34:28 -08:00
commit 0a0b3ae9cf
26 changed files with 1301 additions and 0 deletions

View file

@ -9,6 +9,8 @@ class OpenStack < Fog::Bin
Fog::Identity::OpenStack
when :network
Fog::Network::OpenStack
when :storage
Fog::Storage::OpenStack
else
raise ArgumentError, "Unrecognized service: #{key}"
end
@ -26,6 +28,9 @@ class OpenStack < Fog::Bin
when :network
Fog::Logger.warning("OpenStack[:network] is not recommended, use Network[:openstack] for portability")
Fog::Network.new(:provider => 'OpenStack')
when :storage
Fog::Logger.warning("OpenStack[:storage] is not recommended, use Storage[:openstack] for portability")
Fog::Network.new(:provider => 'OpenStack')
else
raise ArgumentError, "Unrecognized service: #{key.inspect}"
end

View file

@ -44,6 +44,7 @@ module Fog
service(:compute , 'openstack/compute' , 'Compute' )
service(:identity, 'openstack/identity', 'Identity')
service(:network, 'openstack/network', 'Network')
service(:storage, 'openstack/storage', 'Storage')
# legacy v1.0 style auth
def self.authenticate_v1(options, connection_options = {})
@ -199,6 +200,13 @@ module Fog
Fog::JSON.decode(response.body)
end
# CGI.escape, but without special treatment on spaces
def self.escape(str,extra_exclude_chars = '')
str.gsub(/([^a-zA-Z0-9_.-#{extra_exclude_chars}]+)/) do
'%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
end
end
end
end

View file

@ -0,0 +1,39 @@
require 'fog/core/collection'
require 'fog/openstack/models/storage/directory'
module Fog
module Storage
class OpenStack
class Directories < Fog::Collection
model Fog::Storage::OpenStack::Directory
def all
data = service.get_containers.body
load(data)
end
def get(key, options = {})
data = service.get_container(key, options)
directory = new(:key => key)
for key, value in data.headers
if ['X-Container-Bytes-Used', 'X-Container-Object-Count'].include?(key)
directory.merge_attributes(key => value)
end
end
directory.files.merge_attributes(options)
directory.files.instance_variable_set(:@loaded, true)
data.body.each do |file|
directory.files << directory.files.new(file)
end
directory
rescue Fog::Storage::OpenStack::NotFound
nil
end
end
end
end
end

View file

@ -0,0 +1,50 @@
require 'fog/core/model'
require 'fog/openstack/models/storage/files'
module Fog
module Storage
class OpenStack
class Directory < Fog::Model
identity :key, :aliases => 'name'
attribute :bytes, :aliases => 'X-Container-Bytes-Used'
attribute :count, :aliases => 'X-Container-Object-Count'
def destroy
requires :key
service.delete_container(key)
true
rescue Excon::Errors::NotFound
false
end
def files
@files ||= begin
Fog::Storage::OpenStack::Files.new(
:directory => self,
:service => service
)
end
end
def public=(new_public)
@public = new_public
end
def public_url
raise NotImplementedError
end
def save
requires :key
service.put_container(key)
true
end
end
end
end
end

View file

@ -0,0 +1,150 @@
require 'fog/core/model'
module Fog
module Storage
class OpenStack
class File < Fog::Model
identity :key, :aliases => 'name'
attribute :content_length, :aliases => ['bytes', 'Content-Length'], :type => :integer
attribute :content_type, :aliases => ['content_type', 'Content-Type']
attribute :etag, :aliases => ['hash', 'Etag']
attribute :last_modified, :aliases => ['last_modified', 'Last-Modified'], :type => :time
attribute :access_control_allow_origin, :aliases => ['Access-Control-Allow-Origin']
attribute :origin, :aliases => ['Origin']
def body
attributes[:body] ||= if last_modified
collection.get(identity).body
else
''
end
end
def body=(new_body)
attributes[:body] = new_body
end
def directory
@directory
end
def copy(target_directory_key, target_file_key, options={})
requires :directory, :key
options['Content-Type'] ||= content_type if content_type
options['Access-Control-Allow-Origin'] ||= access_control_allow_origin if access_control_allow_origin
options['Origin'] ||= origin if origin
service.copy_object(directory.key, key, target_directory_key, target_file_key, options)
target_directory = service.directories.new(:key => target_directory_key)
target_directory.files.get(target_file_key)
end
def destroy
requires :directory, :key
service.delete_object(directory.key, key)
true
end
def metadata
@metadata ||= headers_to_metadata
end
def owner=(new_owner)
if new_owner
attributes[:owner] = {
:display_name => new_owner['DisplayName'],
:id => new_owner['ID']
}
end
end
def public=(new_public)
new_public
end
def public_url
requires :key
self.collection.get_url(self.key)
end
def save(options = {})
requires :body, :directory, :key
options['Content-Type'] = content_type if content_type
options['Access-Control-Allow-Origin'] = access_control_allow_origin if access_control_allow_origin
options['Origin'] = origin if origin
options.merge!(metadata_to_headers)
data = service.put_object(directory.key, key, body, options)
update_attributes_from(data)
refresh_metadata
self.content_length = Fog::Storage.get_body_size(body)
self.content_type ||= Fog::Storage.get_content_type(body)
true
end
private
def directory=(new_directory)
@directory = new_directory
end
def refresh_metadata
metadata.reject! {|k, v| v.nil? }
end
def headers_to_metadata
key_map = key_mapping
Hash[metadata_attributes.map {|k, v| [key_map[k], v] }]
end
def key_mapping
key_map = metadata_attributes
key_map.each_pair {|k, v| key_map[k] = header_to_key(k)}
end
def header_to_key(opt)
opt.gsub(metadata_prefix, '').split('-').map {|k| k[0, 1].downcase + k[1..-1]}.join('_').to_sym
end
def metadata_to_headers
header_map = header_mapping
Hash[metadata.map {|k, v| [header_map[k], v] }]
end
def header_mapping
header_map = metadata.dup
header_map.each_pair {|k, v| header_map[k] = key_to_header(k)}
end
def key_to_header(key)
metadata_prefix + key.to_s.split(/[-_]/).map(&:capitalize).join('-')
end
def metadata_attributes
if last_modified
headers = service.head_object(directory.key, self.key).headers
headers.reject! {|k, v| !metadata_attribute?(k)}
else
{}
end
end
def metadata_attribute?(key)
key.to_s =~ /^#{metadata_prefix}/
end
def metadata_prefix
"X-Object-Meta-"
end
def update_attributes_from(data)
merge_attributes(data.headers.reject {|key, value| ['Content-Length', 'Content-Type'].include?(key)})
end
end
end
end
end

View file

@ -0,0 +1,94 @@
require 'fog/core/collection'
require 'fog/openstack/models/storage/file'
module Fog
module Storage
class OpenStack
class Files < Fog::Collection
attribute :directory
attribute :limit
attribute :marker
attribute :path
attribute :prefix
model Fog::Storage::OpenStack::File
def all(options = {})
requires :directory
options = {
'limit' => limit,
'marker' => marker,
'path' => path,
'prefix' => prefix
}.merge!(options)
merge_attributes(options)
parent = directory.collection.get(
directory.key,
options
)
if parent
load(parent.files.map {|file| file.attributes})
else
nil
end
end
alias :each_file_this_page :each
def each
if !block_given?
self
else
subset = dup.all
subset.each_file_this_page {|f| yield f}
while subset.length == (subset.limit || 10000)
subset = subset.all(:marker => subset.last.key)
subset.each_file_this_page {|f| yield f}
end
self
end
end
def get(key, &block)
requires :directory
data = service.get_object(directory.key, key, &block)
file_data = data.headers.merge({
:body => data.body,
:key => key
})
new(file_data)
rescue Fog::Storage::OpenStack::NotFound
nil
end
def get_url(key)
requires :directory
if self.directory.public_url
"#{self.directory.public_url}/#{Fog::OpenStack.escape(key, '/')}"
end
end
def head(key, options = {})
requires :directory
data = service.head_object(directory.key, key)
file_data = data.headers.merge({
:key => key
})
new(file_data)
rescue Fog::Storage::OpenStack::NotFound
nil
end
def new(attributes = {})
requires :directory
super({ :directory => directory }.merge!(attributes))
end
end
end
end
end

View file

@ -0,0 +1,27 @@
module Fog
module Storage
class OpenStack
class Real
# Copy object
#
# ==== Parameters
# * source_container_name<~String> - Name of source bucket
# * source_object_name<~String> - Name of source object
# * target_container_name<~String> - Name of bucket to create copy in
# * target_object_name<~String> - Name for new copy of object
# * options<~Hash> - Additional headers
def copy_object(source_container_name, source_object_name, target_container_name, target_object_name, options={})
headers = { 'X-Copy-From' => "/#{source_container_name}/#{source_object_name}" }.merge(options)
request({
:expects => 201,
:headers => headers,
:method => 'PUT',
:path => "#{Fog::OpenStack.escape(target_container_name)}/#{Fog::OpenStack.escape(target_object_name)}"
})
end
end
end
end
end

View file

@ -0,0 +1,22 @@
module Fog
module Storage
class OpenStack
class Real
# Delete an existing container
#
# ==== Parameters
# * name<~String> - Name of container to delete
#
def delete_container(name)
request(
:expects => 204,
:method => 'DELETE',
:path => Fog::OpenStack.escape(name)
)
end
end
end
end
end

View file

@ -0,0 +1,23 @@
module Fog
module Storage
class OpenStack
class Real
# Delete an existing object
#
# ==== Parameters
# * container<~String> - Name of container to delete
# * object<~String> - Name of object to delete
#
def delete_object(container, object)
request(
:expects => 204,
:method => 'DELETE',
:path => "#{Fog::OpenStack.escape(container)}/#{Fog::OpenStack.escape(object)}"
)
end
end
end
end
end

View file

@ -0,0 +1,44 @@
module Fog
module Storage
class OpenStack
class Real
# Get details for container and total bytes stored
#
# ==== Parameters
# * container<~String> - Name of container to retrieve info for
# * options<~String>:
# * 'limit'<~String> - Maximum number of objects to return
# * 'marker'<~String> - Only return objects whose name is greater than marker
# * 'prefix'<~String> - Limits results to those starting with prefix
# * 'path'<~String> - Return objects nested in the pseudo path
#
# ==== Returns
# * response<~Excon::Response>:
# * headers<~Hash>:
# * 'X-Account-Container-Count'<~String> - Count of containers
# * 'X-Account-Bytes-Used'<~String> - Bytes used
# * body<~Array>:
# * 'bytes'<~Integer> - Number of bytes used by container
# * 'count'<~Integer> - Number of items in container
# * 'name'<~String> - Name of container
# * item<~Hash>:
# * 'bytes'<~String> - Size of object
# * 'content_type'<~String> Content-Type of object
# * 'hash'<~String> - Hash of object (etag?)
# * 'last_modified'<~String> - Last modified timestamp
# * 'name'<~String> - Name of object
def get_container(container, options = {})
options = options.reject {|key, value| value.nil?}
request(
:expects => 200,
:method => 'GET',
:path => Fog::OpenStack.escape(container),
:query => {'format' => 'json'}.merge!(options)
)
end
end
end
end
end

View file

@ -0,0 +1,33 @@
module Fog
module Storage
class OpenStack
class Real
# List existing storage containers
#
# ==== Parameters
# * options<~Hash>:
# * 'limit'<~Integer> - Upper limit to number of results returned
# * 'marker'<~String> - Only return objects with name greater than this value
#
# ==== Returns
# * response<~Excon::Response>:
# * body<~Array>:
# * container<~Hash>:
# * 'bytes'<~Integer>: - Number of bytes used by container
# * 'count'<~Integer>: - Number of items in container
# * 'name'<~String>: - Name of container
def get_containers(options = {})
options = options.reject {|key, value| value.nil?}
request(
:expects => [200, 204],
:method => 'GET',
:path => '',
:query => {'format' => 'json'}.merge!(options)
)
end
end
end
end
end

View file

@ -0,0 +1,30 @@
module Fog
module Storage
class OpenStack
class Real
# Get details for object
#
# ==== Parameters
# * container<~String> - Name of container to look in
# * object<~String> - Name of object to look for
#
def get_object(container, object, &block)
params = {}
if block_given?
params[:response_block] = Proc.new
end
request(params.merge!({
:block => block,
:expects => 200,
:method => 'GET',
:path => "#{Fog::OpenStack.escape(container)}/#{Fog::OpenStack.escape(object)}"
}), false)
end
end
end
end
end

View file

@ -0,0 +1,51 @@
module Fog
module Storage
class OpenStack
class Real
# Get an expiring object https url from Cloud Files
#
# ==== Parameters
# * container<~String> - Name of container containing object
# * object<~String> - Name of object to get expiring url for
# * expires<~Time> - An expiry time for this url
#
# ==== Returns
# * response<~Excon::Response>:
# * body<~String> - url for object
#
# ==== See Also
# http://docs.rackspace.com/files/api/v1/cf-devguide/content/Create_TempURL-d1a444.html
def get_object_https_url(container, object, expires, options = {})
if @rackspace_temp_url_key.nil?
raise ArgumentError, "Storage must my instantiated with the :rackspace_temp_url_key option"
end
method = 'GET'
expires = expires.to_i
object_path_escaped = "#{@path}/#{Fog::OpenStack.escape(container)}/#{Fog::OpenStack.escape(object,"/")}"
object_path_unescaped = "#{@path}/#{Fog::OpenStack.escape(container)}/#{object}"
string_to_sign = "#{method}\n#{expires}\n#{object_path_unescaped}"
hmac = Fog::HMAC.new('sha1', @rackspace_temp_url_key)
sig = sig_to_hex(hmac.sign(string_to_sign))
"https://#{@host}#{object_path_escaped}?temp_url_sig=#{sig}&temp_url_expires=#{expires}"
end
private
def sig_to_hex(str)
str.unpack("C*").map { |c|
c.to_s(16)
}.map { |h|
h.size == 1 ? "0#{h}" : h
}.join
end
end
end
end
end

View file

@ -0,0 +1,28 @@
module Fog
module Storage
class OpenStack
class Real
# List number of objects and total bytes stored
#
# ==== Parameters
# * container<~String> - Name of container to retrieve info for
#
# ==== Returns
# * response<~Excon::Response>:
# * headers<~Hash>:
# * 'X-Container-Object-Count'<~String> - Count of containers
# * 'X-Container-Bytes-Used'<~String> - Bytes used
def head_container(container)
request(
:expects => 204,
:method => 'HEAD',
:path => Fog::OpenStack.escape(container),
:query => {'format' => 'json'}
)
end
end
end
end
end

View file

@ -0,0 +1,25 @@
module Fog
module Storage
class OpenStack
class Real
# List number of containers and total bytes stored
#
# ==== Returns
# * response<~Excon::Response>:
# * headers<~Hash>:
# * 'X-Account-Container-Count'<~String> - Count of containers
# * 'X-Account-Bytes-Used'<~String> - Bytes used
def head_containers
request(
:expects => 204,
:method => 'HEAD',
:path => '',
:query => {'format' => 'json'}
)
end
end
end
end
end

View file

@ -0,0 +1,23 @@
module Fog
module Storage
class OpenStack
class Real
# Get headers for object
#
# ==== Parameters
# * container<~String> - Name of container to look in
# * object<~String> - Name of object to look for
#
def head_object(container, object)
request({
:expects => 200,
:method => 'HEAD',
:path => "#{Fog::OpenStack.escape(container)}/#{Fog::OpenStack.escape(object)}"
}, false)
end
end
end
end
end

View file

@ -0,0 +1,37 @@
module Fog
module Storage
class OpenStack
class Real
# Set the account wide Temp URL Key. This is a secret key that's
# used to generate signed expiring URLs.
#
# Once the key has been set with this request you should create new
# Storage objects with the :rackspace_temp_url_key option then use
# the get_object_https_url method to generate expiring URLs.
#
# *** CAUTION *** changing this secret key will invalidate any expiring
# URLS generated with old keys.
#
# ==== Parameters
# * key<~String> - The new Temp URL Key
#
# ==== Returns
# * response<~Excon::Response>
#
# ==== See Also
# http://docs.rackspace.com/files/api/v1/cf-devguide/content/Set_Account_Metadata-d1a4460.html
def post_set_meta_temp_url_key(key)
request(
:expects => [201, 202, 204],
:method => 'POST',
:headers => {'X-Account-Meta-Temp-Url-Key' => key}
)
end
end
end
end
end

View file

@ -0,0 +1,22 @@
module Fog
module Storage
class OpenStack
class Real
# Create a new container
#
# ==== Parameters
# * name<~String> - Name for container, should be < 256 bytes and must not contain '/'
#
def put_container(name)
request(
:expects => [201, 202],
:method => 'PUT',
:path => Fog::OpenStack.escape(name)
)
end
end
end
end
end

View file

@ -0,0 +1,30 @@
module Fog
module Storage
class OpenStack
class Real
# Create a new object
#
# ==== Parameters
# * container<~String> - Name for container, should be < 256 bytes and must not contain '/'
# * object<~String> - Name for object
# * data<~String|File> - data to upload
# * options<~Hash> - config headers for object. Defaults to {}.
#
def put_object(container, object, data, options = {})
data = Fog::Storage.parse_data(data)
headers = data[:headers].merge!(options)
request(
:body => data[:body],
:expects => 201,
:idempotent => true,
:headers => headers,
:method => 'PUT',
:path => "#{Fog::OpenStack.escape(container)}/#{Fog::OpenStack.escape(object)}"
)
end
end
end
end
end

View file

@ -0,0 +1,25 @@
module Fog
module Storage
class OpenStack
class Real
# Create a new object
#
# ==== Parameters
# * container<~String> - Name for container, should be < 256 bytes and must not contain '/'
# * object<~String> - Name for object
#
def put_object_manifest(container, object)
path = "#{Fog::OpenStack.escape(container)}/#{Fog::OpenStack.escape(object)}"
request(
:expects => 201,
:headers => {'X-Object-Manifest' => path},
:method => 'PUT',
:path => path
)
end
end
end
end
end

View file

@ -0,0 +1,159 @@
require 'fog/openstack'
require 'fog/storage'
module Fog
module Storage
class OpenStack < Fog::Service
requires :openstack_auth_url, :openstack_username,
:openstack_api_key
recognizes :persistent, :openstack_service_name,
:openstack_service_type, :openstack_tenant,
:openstack_region
model_path 'fog/openstack/models/storage'
model :directory
collection :directories
model :file
collection :files
request_path 'fog/openstack/requests/storage'
request :copy_object
request :delete_container
request :delete_object
request :get_container
request :get_containers
request :get_object
request :get_object_https_url
request :head_container
request :head_containers
request :head_object
request :put_container
request :put_object
request :put_object_manifest
class Mock
def self.data
@data ||= Hash.new do |hash, key|
hash[key] = {}
end
end
def self.reset
@data = nil
end
def initialize(options={})
require 'mime/types'
@openstack_api_key = options[:openstack_api_key]
@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 'mime/types'
@openstack_api_key = options[:openstack_api_key]
@openstack_username = options[:openstack_username]
@openstack_auth_url = options[:openstack_auth_url]
@openstack_auth_token = options[:openstack_auth_token]
@openstack_storage_url = options[:openstack_storage_url]
@openstack_must_reauthenticate = false
@openstack_service_type = options[:openstack_service_type] || 'object_store'
@openstack_service_name = options[:openstack_service_name]
@openstack_region = options[:openstack_region]
@openstack_tenant = options[:openstack_tenant]
@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, parse_json = true, &block)
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]}",
}), &block)
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::Storage::OpenStack::NotFound.slurp(error)
else
error
end
end
if !response.body.empty? && parse_json && response.headers['Content-Type'] =~ %r{application/json}
response.body = Fog::JSON.decode(response.body)
end
response
end
private
def authenticate
if !@openstack_management_url || @openstack_must_reauthenticate
options = {
:openstack_api_key => @openstack_api_key,
:openstack_username => @openstack_username,
:openstack_auth_uri => URI.parse(@openstack_auth_url),
:openstack_service_type => @openstack_service_type,
:openstack_service_name => @openstack_service_name,
:openstack_region => @openstack_region,
:openstack_tenant => @openstack_tenant,
:openstack_endpoint_type => 'publicURL'
}
credentials = Fog::OpenStack.authenticate_v2(options, @connection_options)
@current_user = credentials[:user]
@current_tenant = credentials[:tenant]
@openstack_must_reauthenticate = false
@auth_token = credentials[:token]
@openstack_management_url = credentials[:server_management_url]
uri = URI.parse(@openstack_management_url)
else
@auth_token = @openstack_auth_token
uri = URI.parse(@openstack_management_url)
end
@host = uri.host
@path = uri.path
@path.sub!(/\/$/, '')
@port = uri.port
@scheme = uri.scheme
true
end
end
end
end
end

View file

@ -32,6 +32,9 @@ module Fog
when :rackspace
require 'fog/rackspace/storage'
Fog::Storage::Rackspace.new(attributes)
when :openstack
require 'fog/openstack/storage'
Fog::Storage::OpenStack.new(attributes)
else
raise ArgumentError.new("#{provider} is not a recognized storage provider")
end

View file

@ -0,0 +1,178 @@
Shindo.tests('Fog::OpenStack::Storage | file', ['openstack']) do
pending if Fog.mocking?
def object_meta_attributes
@instance.service.head_object(@directory.key, @instance.key).headers.reject {|k, v| !(k =~ /X-Object-Meta-/)}
end
def clear_metadata
@instance.metadata.tap do |metadata|
metadata.each_pair {|k, v| metadata[k] = nil }
end
end
file_attributes = {
:key => 'fog_file_tests',
:body => lorem_file
}
directory_attributes = {
# Add a random suffix to prevent collision
:key => "fogfilestests-#{rand(65536)}"
}
@directory = Fog::Storage[:openstack].
directories.
create(directory_attributes)
model_tests(@directory.files, file_attributes.merge(:etag => 'foo'), Fog.mocking?) do
tests('#save should not blow up with etag') do
@instance.save
end
end
model_tests(@directory.files, file_attributes, Fog.mocking?) do
tests("#metadata should load empty metadata").returns({}) do
@instance.metadata
end
tests('#save') do
tests('#metadata') do
before do
@instance.metadata[:foo] = 'bar'
@instance.save
end
after do
clear_metadata
@instance.save
end
tests("should update metadata").returns('bar') do
object_meta_attributes['X-Object-Meta-Foo']
end
tests('should cache metadata').returns('bar') do
@instance.metadata[:foo]
end
tests('should remove empty metadata').returns({}) do
@instance.metadata[:foo] = nil
@instance.save
object_meta_attributes
end
end
tests('#metadata keys') do
after do
clear_metadata
@instance.save
end
@instance.metadata[:foo_bar] = 'baz'
tests("should support compound key names").returns('baz') do
@instance.save
object_meta_attributes['X-Object-Meta-Foo-Bar']
end
@instance.metadata['foo'] = 'bar'
tests("should support string keys").returns('bar') do
@instance.save
object_meta_attributes['X-Object-Meta-Foo']
end
@instance.metadata['foo_bar'] = 'baz'
tests("should support compound string key names").returns('baz') do
@instance.save
object_meta_attributes['X-Object-Meta-Foo-Bar']
end
@instance.metadata['foo-bar'] = 'baz'
tests("should support hyphenated keys").returns('baz') do
@instance.save
object_meta_attributes['X-Object-Meta-Foo-Bar']
end
@instance.metadata['foo-bar'] = 'baz'
@instance.metadata[:'foo_bar'] = 'bref'
tests("should only support one value per metadata key").returns('bref') do
@instance.save
object_meta_attributes['X-Object-Meta-Foo-Bar']
end
end
end
tests("#access_control_allow_origin") do
tests("#access_control_allow_origin should default to nil").returns(nil) do
@instance.access_control_allow_origin
end
@instance.access_control_allow_origin = 'http://example.com'
@instance.save
tests("#access_control_allow_origin should return access control attribute").returns('http://example.com') do
@instance.access_control_allow_origin
end
@instance.access_control_allow_origin = 'foo'
@instance.save
tests("#access_control_allow_origin= should update access_control_allow_origin").returns('bar') do
@instance.access_control_allow_origin = 'bar'
@instance.save
@instance.access_control_allow_origin
end
tests("#access_control_allow_origin= should not blow up on nil") do
@instance.access_control_allow_origin = nil
@instance.save
end
end
end
model_tests(@directory.files, file_attributes, Fog.mocking?) do
tests("#origin") do
tests("#origin should default to nil").returns(nil) do
@instance.save
@instance.origin
end
@instance.origin = 'http://example.com'
@instance.save
tests("#origin should return access control attributes").returns('http://example.com') do
@instance.origin
end
@instance.attributes.delete('Origin')
@instance.origin = 'foo'
@instance.save
tests("#origin= should update origin").returns('bar') do
@instance.origin = 'bar'
@instance.save
@instance.origin
end
tests("#origin= should not blow up on nil") do
@instance.origin = nil
@instance.save
end
end
end
@directory.destroy
end

View file

@ -0,0 +1,64 @@
Shindo.tests('Fog::Storage[:openstack] | container requests', ["openstack"]) do
@container_format = [String]
@containers_format = [{
'bytes' => Integer,
'count' => Integer,
'name' => String
}]
tests('success') do
tests("#put_container('fogcontainertests')").succeeds do
pending if Fog.mocking?
Fog::Storage[:openstack].put_container('fogcontainertests')
end
tests("#get_container('fogcontainertests')").formats(@container_format) do
pending if Fog.mocking?
Fog::Storage[:openstack].get_container('fogcontainertests').body
end
tests("#get_containers").formats(@containers_format) do
pending if Fog.mocking?
Fog::Storage[:openstack].get_containers.body
end
tests("#head_container('fogcontainertests')").succeeds do
pending if Fog.mocking?
Fog::Storage[:openstack].head_container('fogcontainertests')
end
tests("#head_containers").succeeds do
pending if Fog.mocking?
Fog::Storage[:openstack].head_containers
end
tests("#delete_container('fogcontainertests')").succeeds do
pending if Fog.mocking?
Fog::Storage[:openstack].delete_container('fogcontainertests')
end
end
tests('failure') do
tests("#get_container('fognoncontainer')").raises(Fog::Storage::OpenStack::NotFound) do
pending if Fog.mocking?
Fog::Storage[:openstack].get_container('fognoncontainer')
end
tests("#head_container('fognoncontainer')").raises(Fog::Storage::OpenStack::NotFound) do
pending if Fog.mocking?
Fog::Storage[:openstack].head_container('fognoncontainer')
end
tests("#delete_container('fognoncontainer')").raises(Fog::Storage::OpenStack::NotFound) do
pending if Fog.mocking?
Fog::Storage[:openstack].delete_container('fognoncontainer')
end
end
end

View file

@ -0,0 +1,47 @@
Shindo.tests('Fog::Storage[:openstack] | large object requests', ["openstack"]) do
unless Fog.mocking?
@directory = Fog::Storage[:openstack].directories.create(:key => 'foglargeobjecttests')
end
tests('success') do
tests("#put_object('foglargeobjecttests', 'fog_large_object/1', ('x' * 6 * 1024 * 1024))").succeeds do
pending if Fog.mocking?
Fog::Storage[:openstack].put_object(@directory.identity, 'fog_large_object/1', ('x' * 6 * 1024 * 1024))
end
tests("#put_object('foglargeobjecttests', 'fog_large_object/2', ('x' * 4 * 1024 * 1024))").succeeds do
pending if Fog.mocking?
Fog::Storage[:openstack].put_object(@directory.identity, 'fog_large_object/2', ('x' * 4 * 1024 * 1024))
end
tests("#put_object_manifest('foglargeobjecttests', 'fog_large_object')").succeeds do
pending if Fog.mocking?
Fog::Storage[:openstack].put_object_manifest(@directory.identity, 'fog_large_object')
end
tests("#get_object('foglargeobjecttests', 'fog_large_object').body").succeeds do
pending if Fog.mocking?
Fog::Storage[:openstack].get_object(@directory.identity, 'fog_large_object').body == ('x' * 10 * 1024 * 1024)
end
unless Fog.mocking?
['fog_large_object', 'fog_large_object/1', 'fog_large_object/2'].each do |key|
@directory.files.new(:key => key).destroy
end
end
end
tests('failure') do
tests("put_object_manifest")
end
unless Fog.mocking?
@directory.destroy
end
end

View file

@ -0,0 +1,84 @@
Shindo.tests('Fog::Storage[:openstack] | object requests', ["openstack"]) do
unless Fog.mocking?
@directory = Fog::Storage[:openstack].directories.create(:key => 'fogobjecttests')
end
module OpenStackStorageHelpers
def override_path(path)
@path = path
end
end
tests('success') do
tests("#put_object('fogobjecttests', 'fog_object')").succeeds do
pending if Fog.mocking?
Fog::Storage[:openstack].put_object('fogobjecttests', 'fog_object', lorem_file)
end
tests("#get_object('fogobjectests', 'fog_object')").succeeds do
pending if Fog.mocking?
Fog::Storage[:openstack].get_object('fogobjecttests', 'fog_object')
end
tests("#get_object('fogobjecttests', 'fog_object', &block)").returns(lorem_file.read) do
pending if Fog.mocking?
data = ''
Fog::Storage[:openstack].get_object('fogobjecttests', 'fog_object') do |chunk, remaining_bytes, total_bytes|
data << chunk
end
data
end
tests("#head_object('fogobjectests', 'fog_object')").succeeds do
pending if Fog.mocking?
Fog::Storage[:openstack].head_object('fogobjecttests', 'fog_object')
end
tests("#delete_object('fogobjecttests', 'fog_object')").succeeds do
pending if Fog.mocking?
Fog::Storage[:openstack].delete_object('fogobjecttests', 'fog_object')
end
end
tests('failure') do
tests("#get_object('fogobjecttests', 'fog_non_object')").raises(Fog::Storage::OpenStack::NotFound) do
pending if Fog.mocking?
Fog::Storage[:openstack].get_object('fogobjecttests', 'fog_non_object')
end
tests("#get_object('fognoncontainer', 'fog_non_object')").raises(Fog::Storage::OpenStack::NotFound) do
pending if Fog.mocking?
Fog::Storage[:openstack].get_object('fognoncontainer', 'fog_non_object')
end
tests("#head_object('fogobjecttests', 'fog_non_object')").raises(Fog::Storage::OpenStack::NotFound) do
pending if Fog.mocking?
Fog::Storage[:openstack].head_object('fogobjecttests', 'fog_non_object')
end
tests("#head_object('fognoncontainer', 'fog_non_object')").raises(Fog::Storage::OpenStack::NotFound) do
pending if Fog.mocking?
Fog::Storage[:openstack].head_object('fognoncontainer', 'fog_non_object')
end
tests("#delete_object('fogobjecttests', 'fog_non_object')").raises(Fog::Storage::OpenStack::NotFound) do
pending if Fog.mocking?
Fog::Storage[:openstack].delete_object('fogobjecttests', 'fog_non_object')
end
tests("#delete_object('fognoncontainer', 'fog_non_object')").raises(Fog::Storage::OpenStack::NotFound) do
pending if Fog.mocking?
Fog::Storage[:openstack].delete_object('fognoncontainer', 'fog_non_object')
end
end
unless Fog.mocking?
@directory.destroy
end
end