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

Merge branch 'master'

This commit is contained in:
James Bence 2013-07-18 08:23:23 -07:00
commit 07b0b704b5
80 changed files with 2351 additions and 501 deletions

View file

@ -21,6 +21,7 @@ Gem::Specification.new do |s|
s.authors = ["geemus (Wesley Beary)"]
s.email = 'geemus@gmail.com'
s.homepage = 'http://github.com/fog/fog'
s.license = 'MIT'
## This sections is only necessary if you have C extensions.
# s.require_paths << 'ext'

View file

@ -0,0 +1,26 @@
require 'fog/core/model'
module Fog
module AWS
class RDS
class LogFile < Fog::Model
attribute :rds_id, :aliases => 'DBInstanceIdentifier'
attribute :name, :aliases => 'LogFileName'
attribute :size, :aliases => 'Size', :type => :integer
attribute :last_written, :aliases => 'LastWritten', :type => :time
attribute :content, :aliases => 'LogFileData'
attribute :marker, :aliases => 'Marker'
attribute :more_content_available, :aliases => 'AdditionalDataPending', :type => :boolean
def content_excerpt(marker=nil)
result = service.download_db_logfile_portion(self.rds_id, self.name, {:marker => marker})
merge_attributes(result.body['DownloadDBLogFilePortionResult'])
end
end
end
end
end

View file

@ -0,0 +1,50 @@
require 'fog/core/collection'
require 'fog/aws/models/rds/log_file'
module Fog
module AWS
class RDS
class LogFiles < Fog::Collection
attribute :filters
attribute :rds_id
model Fog::AWS::RDS::LogFile
def initialize(attributes)
self.filters ||= {}
super
end
# This method deliberately returns only a single page of results
def all(filters=filters)
self.filters.merge!(filters)
result = service.describe_db_log_files(rds_id, self.filters).body['DescribeDBLogFilesResult']
self.filters[:marker] = result['Marker']
load(result['DBLogFiles'])
end
def each(filters=filters)
if block_given?
begin
page = self.all(filters)
# We need to explicitly use the base 'each' method here on the page, otherwise we get infinite recursion
base_each = Fog::Collection.instance_method(:each)
base_each.bind(page).call { |log_file| yield log_file }
end while self.filters[:marker]
end
self
end
def get(file_name=nil)
if file_name
matches = self.select {|log_file| log_file.name.upcase == file_name.upcase}
return matches.first unless matches.empty?
end
rescue Fog::AWS::RDS::NotFound
end
end
end
end
end

View file

@ -21,26 +21,33 @@ module Fog
super
end
# This will return a single page based on the current or provided filters,
# updating the filters with the marker for the next page. Calling this repeatedly
# will iterate through pages.
# This method does NOT return all snapshots. Its implementation deliberately returns a single page
# of results for any one call. It will return a single page based on the current or provided filters,
# updating the filters with the marker for the next page. Calling this repeatedly will iterate
# through pages. See the implementation of each for an example of such iteration.
#
# It is arguably incorrect for the method not to return all snapshots, particularly considering the
# implementation in the corresponding 'elb' files. But this implementation has been released, and
# backwards-compatibility requires leaving it as implemented.
def all(filters = filters)
self.filters.merge!(filters)
snapshots = service.describe_db_snapshots(filters)
self.filters[:marker] = snapshots.body['DescribeDBSnapshotsResult']['Marker']
data = snapshots.body['DescribeDBSnapshotsResult']['DBSnapshots']
load(data)
page = service.describe_db_snapshots(self.filters).body['DescribeDBSnapshotsResult']
self.filters[:marker] = page['Marker']
load(page['DBSnapshots'])
end
# This will execute a block for each snapshot, fetching new pages of snapshots as required.
def each(filters = filters)
begin
page = self.all(filters)
# We need to explicitly use the base 'each' method here on the page, otherwise we get infinite recursion
base_each = Fog::Collection.instance_method(:each)
base_each.bind(page).call {|snapshot| yield snapshot}
end while self.filters[:marker]
if block_given?
begin
page = self.all(filters)
# We need to explicitly use the base 'each' method here on the page, otherwise we get infinite recursion
base_each = Fog::Collection.instance_method(:each)
base_each.bind(page).call { |snapshot| yield snapshot }
end while self.filters[:marker]
end
self
end
def get(identity)

View file

@ -0,0 +1,44 @@
module Fog
module Parsers
module AWS
module RDS
class DescribeDBLogFiles < Fog::Parsers::Base
attr_reader :rds_id
def initialize(rds_id)
@rds_id = rds_id
super()
end
def reset
@response = { 'DescribeDBLogFilesResult' => {'DBLogFiles' => []}, 'ResponseMetadata' => {} }
fresh_log_file
end
def fresh_log_file
@db_log_file = {'DBInstanceIdentifier' => @rds_id}
end
def start_element(name, attrs = [])
super
end
def end_element(name)
case name
when 'LastWritten' then @db_log_file[name] = Time.at(value.to_i / 1000)
when 'LogFileName' then @db_log_file[name] = value
when 'Size' then @db_log_file[name] = value.to_i
when 'DescribeDBLogFilesDetails'
@response['DescribeDBLogFilesResult']['DBLogFiles'] << @db_log_file
fresh_log_file
when 'Marker' then @response['DescribeDBLogFilesResult'][name] = value
when 'RequestId' then @response['ResponseMetadata'][name] = value
end
end
end
end
end
end
end

View file

@ -0,0 +1,26 @@
module Fog
module Parsers
module AWS
module RDS
class DownloadDBLogFilePortion < Fog::Parsers::Base
def reset
@response = { 'DownloadDBLogFilePortionResult' => {}, 'ResponseMetadata' => {} }
end
def start_element(name, attrs = [])
super
end
def end_element(name)
key = (name == 'RequestId') ? 'ResponseMetadata' : 'DownloadDBLogFilePortionResult'
@response[key][name] = value
end
end
end
end
end
end

View file

@ -0,0 +1,30 @@
module Fog
module Parsers
module AWS
module STS
class AssumeRole < Fog::Parsers::Base
def reset
@response = {}
end
def end_element(name)
case name
when 'SessionToken', 'SecretAccessKey', 'Expiration', 'AccessKeyId'
@response[name] = @value.strip
when 'Arn', 'AssumedRoleId'
@response[name] = @value.strip
when 'PackedPolicySize'
@response[name] = @value
when 'RequestId'
@response[name] = @value
end
end
end
end
end
end
end

View file

@ -53,6 +53,9 @@ module Fog
request :describe_orderable_db_instance_options
request :describe_db_log_files
request :download_db_logfile_portion
model_path 'fog/aws/models/rds'
model :server
collection :servers
@ -75,6 +78,9 @@ module Fog
model :instance_option
collection :instance_options
model :log_file
collection :log_files
class Mock
def self.data

View file

@ -0,0 +1,67 @@
module Fog
module AWS
class RDS
class Real
require 'fog/aws/parsers/rds/describe_db_log_files'
# Describe log files for a DB instance
# http://docs.amazonwebservices.com/AmazonRDS/latest/APIReference/API_DescribeDBLogFiles.html
# ==== Parameters
# * DBInstanceIdentifier <~String> - ID of instance to retrieve information for. Required.
# * Options <~Hash> - Hash of options. Optional. The following keys are used:
# * :file_last_written <~Long> - Filter available log files for those written after this time. Optional.
# * :file_size <~Long> - Filters the available log files for files larger than the specified size. Optional.
# * :filename_contains <~String> - Filters the available log files for log file names that contain the specified string. Optional.
# * :marker <~String> - The pagination token provided in the previous request. If this parameter is specified the response includes only records beyond the marker, up to MaxRecords. Optional.
# * :max_records <~Integer> - The maximum number of records to include in the response. If more records exist, a pagination token is included in the response. Optional.
# ==== Returns
# * response<~Excon::Response>:
# * body<~Hash>:
def describe_db_log_files(rds_id=nil, opts={})
params = {}
params['DBInstanceIdentifier'] = rds_id if rds_id
params['Marker'] = opts[:marker] if opts[:marker]
params['MaxRecords'] = opts[:max_records] if opts[:max_records]
params['FilenameContains'] = opts[:filename_contains] if opts[:filename_contains]
params['FileSize'] = opts[:file_size] if opts[:file_size]
params['FileLastWritten'] = opts[:file_last_written] if opts[:file_last_written]
request({
'Action' => 'DescribeDBLogFiles',
:parser => Fog::Parsers::AWS::RDS::DescribeDBLogFiles.new(rds_id)
}.merge(params))
end
end
class Mock
def describe_db_log_files(rds_id=nil, opts={})
response = Excon::Response.new
log_file_set = []
if rds_id
if server = self.data[:servers][rds_id]
log_file_set << {"LastWritten" => Time.parse('2013-07-05 17:00:00 -0700'), "LogFileName" => "error/mysql-error.log", "Size" => 0}
log_file_set << {"LastWritten" => Time.parse('2013-07-05 17:10:00 -0700'), "LogFileName" => "error/mysql-error-running.log", "Size" => 0}
log_file_set << {"LastWritten" => Time.parse('2013-07-05 17:20:00 -0700'), "LogFileName" => "error/mysql-error-running.log.0", "Size" => 8220}
log_file_set << {"LastWritten" => Time.parse('2013-07-05 17:30:00 -0700'), "LogFileName" => "error/mysql-error-running.log.1", "Size" => 0}
else
raise Fog::AWS::RDS::NotFound.new("DBInstance #{rds_id} not found")
end
else
raise Fog::AWS::RDS::NotFound.new('An identifier for an RDS instance must be provided')
end
response.status = 200
response.body = {
"ResponseMetadata" => { "RequestId" => Fog::AWS::Mock.request_id },
"DescribeDBLogFilesResult" => { "DBLogFiles" => log_file_set }
}
response
end
end
end
end
end

View file

@ -0,0 +1,63 @@
module Fog
module AWS
class RDS
class Real
require 'fog/aws/parsers/rds/download_db_logfile_portion'
# Retrieve a portion of a log file of a db instance
# http://docs.amazonwebservices.com/AmazonRDS/latest/APIReference/API_DownloadDBLogFilePortion.html
# ==== Parameters
# * DBInstanceIdentifier <~String> - ID of instance to retrieve information for. Required.
# * LogFileName <~String> - The name of the log file to be downloaded. Required.
# * Options <~Hash> - Hash of options. Optional. The following keys are used:
# * :marker <~String> - The pagination token provided in the previous request. If this parameter is specified the response includes only records beyond the marker, up to MaxRecords. Optional.
# * :max_records <~Integer> - The maximum number of records to include in the response. If more records exist, a pagination token is included in the response. Optional.
# * :number_of_lines <~Integer> - The number of lines to download. Optional.
# ==== Returns
# * response<~Excon::Response>:
# * body<~Hash>:
def download_db_logfile_portion(identifier=nil, filename=nil, opts={})
params = {}
params['DBInstanceIdentifier'] = identifier if identifier
params['LogFileName'] = filename if filename
params['Marker'] = opts[:marker] if opts[:marker]
params['MaxRecords'] = opts[:max_records] if opts[:max_records]
params['NumberOfLines'] = opts[:number_of_lines] if opts[:number_of_lines]
request({
'Action' => 'DownloadDBLogFilePortion',
:parser => Fog::Parsers::AWS::RDS::DownloadDBLogFilePortion.new
}.merge(params))
end
end
class Mock
def download_db_logfile_portion(identifier=nil, filename=nil, opts={})
response = Excon::Response.new
server_set = []
if identifier
if server = self.data[:servers][identifier]
server_set << server
else
raise Fog::AWS::RDS::NotFound.new("DBInstance #{identifier} not found")
end
else
server_set = self.data[:servers].values
end
response.status = 200
response.body = {
"ResponseMetadata" => { "RequestId"=> Fog::AWS::Mock.request_id },
"DescribeDBInstancesResult" => { "DBInstances" => server_set }
}
response
end
end
end
end
end

View file

@ -30,7 +30,7 @@ module Fog
request({
'Action' => 'SendRawEmail',
'RawMessage.Data' => Base64.encode64(raw_message).chomp!,
'RawMessage.Data' => Base64.encode64(raw_message.to_s).chomp!,
:parser => Fog::Parsers::AWS::SES::SendRawEmail.new
}.merge(params))
end

View file

@ -87,7 +87,7 @@ module Fog
if (object && !object[:delete_marker])
if options['If-Match'] && options['If-Match'] != object['ETag']
response.status = 412
elsif options['If-Modified-Since'] && options['If-Modified-Since'] > Time.parse(object['Last-Modified'])
elsif options['If-Modified-Since'] && options['If-Modified-Since'] >= Time.parse(object['Last-Modified'])
response.status = 304
elsif options['If-None-Match'] && options['If-None-Match'] == object['ETag']
response.status = 304

View file

@ -0,0 +1,46 @@
module Fog
module AWS
class STS
class Real
require 'fog/aws/parsers/sts/assume_role'
# Assume Role
#
# ==== Parameters
# * role_session_name<~String> - An identifier for the assumed role.
# * role_arn<~String> - The ARN of the role the caller is assuming.
# * external_id<~String> - An optional unique identifier required by the assuming role's trust identity.
# * policy<~String> - An optional JSON policy document
# * duration<~Integer> - Duration (of seconds) for the assumed role credentials to be valid (default 3600)
#
# ==== Returns
# * response<~Excon::Response>:
# * body<~Hash>:
# * 'Arn'<~String>: The ARN of the assumed role/user
# * 'AccessKeyId'<~String>: The AWS access key of the temporary credentials for the assumed role
# * 'SecretAccessKey'<~String>: The AWS secret key of the temporary credentials for the assumed role
# * 'SessionToken'<~String>: The AWS session token of the temporary credentials for the assumed role
# * 'Expiration'<~Time>: The expiration time of the temporary credentials for the assumed role
#
# ==== See Also
# http://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
#
def assume_role(role_session_name, role_arn, external_id=nil, policy=nil, duration=3600)
request({
'Action' => 'AssumeRole',
'RoleSessionName' => role_session_name,
'RoleArn' => role_arn,
'Policy' => policy && Fog::JSON.encode(policy),
'DurationSeconds' => duration,
'ExternalId' => external_id,
:idempotent => true,
:parser => Fog::Parsers::AWS::STS::AssumeRole.new
})
end
end
end
end
end

View file

@ -5,6 +5,9 @@ module Fog
class SES < Fog::Service
extend Fog::AWS::CredentialFetcher::ServiceMethods
class InvalidParameterError < Fog::Errors::Error; end
class MessageRejected < Fog::Errors::Error; end
requires :aws_access_key_id, :aws_secret_access_key
recognizes :region, :host, :path, :port, :scheme, :persistent, :use_iam_profile, :aws_session_token, :aws_credentials_expire_at
@ -103,15 +106,28 @@ module Fog
end
body.chop! # remove trailing '&'
response = @connection.request({
:body => body,
:expects => 200,
:headers => headers,
:idempotent => idempotent,
:host => @host,
:method => 'POST',
:parser => parser
})
begin
response = @connection.request({
:body => body,
:expects => 200,
:headers => headers,
:idempotent => idempotent,
:host => @host,
:method => 'POST',
:parser => parser
})
rescue Excon::Errors::HTTPStatusError => error
match = Fog::AWS::Errors.match_error(error)
raise if match.empty?
raise case match[:code]
when 'MessageRejected'
Fog::AWS::SES::MessageRejected.slurp(error, match[:message])
when 'InvalidParameterValue'
Fog::AWS::SES::InvalidParameterError.slurp(error, match[:message])
else
Fog::AWS::SES::Error.slurp(error, "#{match[:code]} => #{match[:message]}")
end
end
response
end

View file

@ -3,16 +3,18 @@ require 'fog/aws'
module Fog
module AWS
class STS < Fog::Service
extend Fog::AWS::CredentialFetcher::ServiceMethods
class EntityAlreadyExists < Fog::AWS::STS::Error; end
class ValidationError < Fog::AWS::STS::Error; end
requires :aws_access_key_id, :aws_secret_access_key
recognizes :host, :path, :port, :scheme, :persistent
recognizes :host, :path, :port, :scheme, :persistent, :aws_session_token, :use_iam_profile, :aws_credentials_expire_at
request_path 'fog/aws/requests/sts'
request :get_federation_token
request :get_session_token
request :assume_role
class Mock
def self.data
@ -33,7 +35,8 @@ module Fog
end
def initialize(options={})
@aws_access_key_id = options[:aws_access_key_id]
@use_iam_profile = options[:use_iam_profile]
setup_credentials(options)
end
def data
@ -43,10 +46,14 @@ module Fog
def reset_data
self.class.data.delete(@aws_access_key_id)
end
def setup_credentials(options)
@aws_access_key_id = options[:aws_access_key_id]
end
end
class Real
include Fog::AWS::CredentialFetcher::ConnectionMethods
# Initialize connection to STS
#
# ==== Notes
@ -67,10 +74,10 @@ module Fog
def initialize(options={})
require 'fog/core/parser'
@aws_access_key_id = options[:aws_access_key_id]
@aws_secret_access_key = options[:aws_secret_access_key]
@use_iam_profile = options[:use_iam_profile]
setup_credentials(options)
@connection_options = options[:connection_options] || {}
@hmac = Fog::HMAC.new('sha256', @aws_secret_access_key)
@host = options[:host] || 'sts.amazonaws.com'
@path = options[:path] || '/'
@persistent = options[:persistent] || false
@ -85,6 +92,14 @@ module Fog
private
def setup_credentials(options)
@aws_access_key_id = options[:aws_access_key_id]
@aws_secret_access_key = options[:aws_secret_access_key]
@aws_session_token = options[:aws_session_token]
@aws_credentials_expire_at = options[:aws_credentials_expire_at]
@hmac = Fog::HMAC.new('sha256', @aws_secret_access_key)
end
def request(params)
idempotent = params.delete(:idempotent)
parser = params.delete(:parser)
@ -93,6 +108,7 @@ module Fog
params,
{
:aws_access_key_id => @aws_access_key_id,
:aws_session_token => @aws_session_token,
:hmac => @hmac,
:host => @host,
:path => @path,

View file

@ -15,6 +15,8 @@ class OpenStack < Fog::Bin
Fog::Storage::OpenStack
when :volume
Fog::Volume::OpenStack
when :metering
Fog::Metering::OpenStack
else
raise ArgumentError, "Unrecognized service: #{key}"
end
@ -41,6 +43,9 @@ class OpenStack < Fog::Bin
when :volume
Fog::Logger.warning("OpenStack[:volume] is not recommended, use Volume[:openstack] for portability")
Fog::Volume.new(:provider => 'OpenStack')
when :metering
Fog::Logger.warning("OpenStack[:metering] is not recommended, use Metering[:openstack] for portability")
Fog::Metering.new(:provider => 'OpenStack')
else
raise ArgumentError, "Unrecognized service: #{key.inspect}"
end

View file

@ -53,6 +53,9 @@ module Fog
model :flavor
collection :flavors
model :disk
collection :disks
class Mock
include Collections
@ -126,7 +129,7 @@ module Fog
response.status = response.body["error"]["code"]
response.body["error"]["errors"].each do |error|
throw Fog::Errors::Error.new(error["message"])
raise Fog::Errors::Error.new(error["message"])
end
else
response.status = 200
@ -134,6 +137,22 @@ module Fog
response
end
def backoff_if_unfound(&block)
retries_remaining = 10
begin
result = block.call
rescue Exception => msg
if msg.to_s.include? 'was not found' and retries_remaining > 0
retries_remaining -= 1
sleep 0.1
retry
else
raise msg
end
end
result
end
end
RUNNING = 'RUNNING'

View file

@ -0,0 +1,80 @@
require 'fog/core/model'
module Fog
module Compute
class Google
class Disk < Fog::Model
identity :name
attribute :kind, :aliases => 'kind'
attribute :id, :aliases => 'id'
attribute :creation_timestamp, :aliases => 'creationTimestamp'
attribute :zone_name, :aliases => 'zone'
attribute :status, :aliases => 'status'
attribute :description, :aliases => 'description'
attribute :size_gb, :aliases => 'sizeGb'
attribute :self_link, :aliases => 'selfLink'
attribute :image_name, :aliases => 'image'
def save
data = service.insert_disk(name, size_gb, zone_name, image_name).body
data = service.backoff_if_unfound {service.get_disk(name, zone_name).body}
service.disks.merge_attributes(data)
end
def destroy
requires :name, :zone
service.delete_disk(name, zone)
end
def zone
if self.zone_name.is_a? String
service.get_zone(self.zone_name.split('/')[-1]).body["name"]
elsif zone_name.is_a? Excon::Response
service.get_zone(zone_name.body["name"]).body["name"]
else
self.zone_name
end
end
def get_as_boot_disk(writable=true)
mode = writable ? 'READ_WRITE' : 'READ_ONLY'
return {
'name' => name,
'type' => 'PERSISTENT',
'boot' => true,
'source' => self_link,
'mode' => mode
}
end
def ready?
data = service.get_disk(self.name, self.zone_name).body
data['zone_name'] = self.zone_name
self.merge_attributes(data)
self.status == RUNNING_STATE
end
def reload
requires :identity
requires :zone_name
return unless data = begin
collection.get(identity, zone_name)
rescue Excon::Errors::SocketError
nil
end
new_attributes = data.attributes
merge_attributes(new_attributes)
self
end
RUNNING_STATE = "READY"
end
end
end
end

View file

@ -0,0 +1,28 @@
require 'fog/core/collection'
require 'fog/google/models/compute/disk'
module Fog
module Compute
class Google
class Disks < Fog::Collection
model Fog::Compute::Google::Disk
def all(zone)
data = service.list_disks(zone).body["items"]
load(data)
end
def get(identity, zone)
data = service.get_disk(identity, zone).body
new(data)
rescue Excon::Errors::NotFound
nil
end
end
end
end
end

View file

@ -18,19 +18,24 @@ module Fog
requires :name
data = {}
if project
data = service.get_image(name, project).body
elsif
[ 'google', 'debian-cloud', 'centos-cloud' ].each do |owner|
begin
data = service.get_image(name, owner).body
data[:project] = owner
rescue
end
# Try looking for the image in known projects
[
self.service.project,
'google',
'debian-cloud',
'centos-cloud',
].each do |owner|
begin
data = service.get_image(name, owner).body
data[:project] = owner
rescue
end
end
self.merge_attributes(data)
raise ArgumentError, 'Specified image was not found' if data.empty?
self.merge_attributes(data)
self
end

View file

@ -13,6 +13,8 @@ module Fog
attribute :state, :aliases => 'status'
attribute :zone_name, :aliases => 'zone'
attribute :machine_type, :aliases => 'machineType'
attribute :disks, :aliases => 'disks'
attribute :kernel, :aliases => 'kernel'
attribute :metadata
def destroy
@ -60,24 +62,29 @@ module Fog
def save
requires :name
requires :image_name
requires :machine_type
requires :zone_name
if metadata.nil?
metadata = {}
if self.metadata.nil?
self.metadata = {}
end
metadata.merge!({
self.metadata.merge!({
"sshKeys" => "#{username}:#{File.read(public_key_path).strip}"
}) if :public_key_path
}) if public_key_path
data = service.insert_server(
name,
image_name,
zone_name,
machine_type,
metadata)
options = {
'image' => image_name,
'machineType' => machine_type,
'networkInterfaces' => network_interfaces,
'disks' => disks,
'kernel' => kernel,
'metadata' => metadata
}
options.delete_if {|key, value| value.nil?}
service.insert_server(name, zone_name, options)
data = service.backoff_if_unfound {service.get_server(self.name, self.zone_name).body}
service.servers.merge_attributes(data)
end
end

View file

@ -51,6 +51,11 @@ module Fog
:public_key_path => File.expand_path("~/.ssh/id_rsa.pub"),
:username => ENV['USER'],
}
if new_attributes[:disks]
new_attributes[:disks].each do |disk|
defaults.delete :image_name if disk['boot']
end
end
server = create(defaults.merge(new_attributes))
server.wait_for { sshable? }

View file

@ -13,6 +13,9 @@ module Fog
class Real
def get_disk(disk_name, zone_name=@default_zone)
if zone_name.start_with? 'http'
zone_name = zone_name.split('/')[-1]
end
api_method = @compute.disks.get
parameters = {
'project' => @project,

View file

@ -12,19 +12,25 @@ module Fog
class Real
def insert_disk(disk_name, disk_size, zone_name=@default_zone)
def insert_disk(disk_name, disk_size, zone_name=@default_zone, image_name=nil)
api_method = @compute.disks.insert
parameters = {
'project' => @project,
'zone' => zone_name
}
if image_name
# We don't know the owner of the image.
image = images.create({:name => image_name})
@image_url = @api_url + image.resource_url
parameters['sourceImage'] = @image_url
end
body_object = {
'name' => disk_name,
'sizeGb' => disk_size,
}
result = self.build_result(api_method, parameters,
body_object=body_object)
body_object)
response = self.build_response(result)
end

View file

@ -16,32 +16,57 @@ module Fog
{ "items" => metadata.map {|k,v| {"key" => k, "value" => v}} }
end
def insert_server(server_name, image_name,
zone_name, machine_name, metadata,
network_name=@default_network)
# We don't know the owner of the image.
image = images.create({:name => image_name})
@image_url = @api_url + image.resource_url
def insert_server(server_name, zone_name, options={}, *deprecated_args)
if deprecated_args.length > 0 or not options.is_a? Hash
raise ArgumentError.new 'Too many parameters specified. This may be the cause of code written for an outdated'\
' version of fog. Usage: server_name, zone_name, [options]'
end
api_method = @compute.instances.insert
parameters = {
'project' => @project,
'zone' => zone_name,
}
body_object = {
'name' => server_name,
'image' => @image_url,
'machineType' => @api_url + @project + "/zones/#{zone_name}/machineTypes/#{machine_name}",
'metadata' => format_metadata(metadata),
'networkInterfaces' => [{
'network' => @api_url + @project + "/global/networks/#{network_name}",
'accessConfigs' => [{
'type' => 'ONE_TO_ONE_NAT',
'name' => 'External NAT',
}]
}]
'project' => @project,
'zone' => zone_name,
}
body_object = {:name => server_name}
if options.has_key? 'image'
image_name = options.delete 'image'
# We don't know the owner of the image.
image = images.create({:name => image_name})
@image_url = @api_url + image.resource_url
body_object['image'] = @image_url
end
body_object['machineType'] = @api_url + @project + "/zones/#{zone_name}/machineTypes/#{options.delete 'machineType'}"
networkInterfaces = []
if @default_network
networkInterfaces << {
'network' => @api_url + @project + "/global/networks/#{@default_network}",
'accessConfigs' => [
{'type' => 'ONE_TO_ONE_NAT',
'name' => 'External NAT'}
]
}
end
# TODO: add other networks
body_object['networkInterfaces'] = networkInterfaces
if options['disks']
disks = []
options.delete('disks').each do |disk|
if disk.is_a? Disk
disks << disk.get_object
else
disks << disk
end
end
body_object['disks'] = disks
end
options['metadata'] = format_metadata options['metadata'] if options['metadata']
if options['kernel']
body_object['kernel'] = @api_url + "google/global/kernels/#{options.delete 'kernel'}"
end
body_object.merge! options # Adds in all remaining options that weren't explicitly handled.
result = self.build_result(api_method, parameters,
body_object=body_object)

View file

@ -73,7 +73,7 @@ module Fog
if (bucket = self.data[:buckets][bucket_name]) && (object = bucket[:objects][object_name])
if options['If-Match'] && options['If-Match'] != object['ETag']
response.status = 412
elsif options['If-Modified-Since'] && options['If-Modified-Since'] > Time.parse(object['Last-Modified'])
elsif options['If-Modified-Since'] && options['If-Modified-Since'] >= Time.parse(object['Last-Modified'])
response.status = 304
elsif options['If-None-Match'] && options['If-None-Match'] == object['ETag']
response.status = 304

View file

@ -43,7 +43,7 @@ module Fog
if (object = container[:objects][object_name])
if options['If-Match'] && options['If-Match'] != object['ETag']
response.status = 412
elsif options['If-Modified-Since'] && options['If-Modified-Since'] > Time.parse(object['Last-Modified'])
elsif options['If-Modified-Since'] && options['If-Modified-Since'] >= Time.parse(object['Last-Modified'])
response.status = 304
elsif options['If-None-Match'] && options['If-None-Match'] == object['ETag']
response.status = 304

View file

@ -15,7 +15,7 @@ module Fog
# * :ip<~String> - The ID of a static IP address to associate with this instance
# * :volume_id<~String> - The ID of a storage volume to associate with this instance
# * :vlan_id<~String> - The ID of a Vlan offering to associate with this instance.
# * :secondary_ip<~String> - The ID of a static IP address to associate with this instance as secondary IP
# * :secondary_ip<~String> - Comma separated list of static IP IDs to associate with this instance.
# * :is_mini_ephermal<~Boolean> - Whether or not the instance should be provisioned with the root segment only
# * :configuration_data<~Hash> - Arbitrary name/value pairs defined by the image being created
# * :anti_collocation_instance<~String> - The ID of an existing anti-collocated instance.
@ -32,24 +32,29 @@ module Fog
# * 'imageId'<~String>:
# * 'launchTime'<~Integer>: epoch time in ms representing when the instance was launched
def create_instance(name, image_id, instance_type, location, options={})
body_data = {
'name' => name,
'imageID' => image_id,
'instanceType' => instance_type,
'location' => location,
'publicKey' => options[:key_name],
'ip' => options[:ip],
'volumeID' => options[:volume_id],
'vlanID' => options[:vlan_id],
'isMiniEphemeral' => options[:is_mini_ephemeral],
'Configuration Data' => options[:configuration_data],
'antiCollocationInstance' => options[:anti_collocation_instance]
}
if options[:secondary_ip]
options[:secondary_ip].split(',').map(&:strip).each_with_index do |n, idx|
body_data.merge!({"secondary.ip.#{idx}" => n})
end
end
request(
:method => 'POST',
:expects => 200,
:path => '/instances',
:body => {
'name' => name,
'imageID' => image_id,
'instanceType' => instance_type,
'location' => location,
'publicKey' => options[:key_name],
'ip' => options[:ip],
'volumeID' => options[:volume_id],
'vlanID' => options[:vlan_id],
'SecondaryIP' => options[:secondary_ip],
'isMiniEphemeral' => options[:is_mini_ephemeral],
'Configuration Data' => options[:configuration_data],
'antiCollocationInstance' => options[:anti_collocation_instance],
}
:body => body_data
)
end

View file

@ -78,7 +78,7 @@ module Fog
if (object && !object[:delete_marker])
if options['If-Match'] && options['If-Match'] != object['ETag']
response.status = 412
elsif options['If-Modified-Since'] && options['If-Modified-Since'] > Time.parse(object['Last-Modified'])
elsif options['If-Modified-Since'] && options['If-Modified-Since'] >= Time.parse(object['Last-Modified'])
response.status = 304
elsif options['If-None-Match'] && options['If-None-Match'] == object['ETag']
response.status = 304

25
lib/fog/metering.rb Normal file
View file

@ -0,0 +1,25 @@
module Fog
module Metering
def self.[](provider)
self.new(:provider => provider)
end
def self.new(attributes)
attributes = attributes.dup # Prevent delete from having side effects
provider = attributes.delete(:provider).to_s.downcase.to_sym
if self.providers.include?(provider)
require "fog/#{provider}/metering"
return Fog::Metering.const_get(Fog.providers[provider]).new(attributes)
end
raise ArgumentError.new("#{provider} has no identity service")
end
def self.providers
Fog.services[:metering]
end
end
end

View file

@ -47,6 +47,7 @@ module Fog
service(:network, 'openstack/network', 'Network')
service(:storage, 'openstack/storage', 'Storage')
service(:volume, 'openstack/volume', 'Volume')
service(:metering, 'openstack/metering', 'Metering')
# legacy v1.0 style auth
def self.authenticate_v1(options, connection_options = {})

View file

@ -0,0 +1,215 @@
require 'fog/metering'
require 'fog/openstack'
module Fog
module Metering
class OpenStack < Fog::Service
requires :openstack_auth_url
recognizes :openstack_auth_token, :openstack_management_url, :persistent,
:openstack_service_type, :openstack_service_name, :openstack_tenant,
:openstack_api_key, :openstack_username,
:current_user, :current_tenant,
:openstack_endpoint_type
model_path 'fog/openstack/models/metering'
model :resource
collection :resources
request_path 'fog/openstack/requests/metering'
# Metering
request :get_resource
request :get_samples
request :get_statistics
request :list_meters
request :list_resources
class Mock
def self.data
@data ||= Hash.new do |hash, key|
hash[key] = {
:users => {},
:tenants => {}
}
end
end
def self.reset
@data = nil
end
def initialize(options={})
require 'multi_json'
@openstack_username = options[:openstack_username]
@openstack_tenant = options[:openstack_tenant]
@openstack_auth_uri = URI.parse(options[:openstack_auth_url])
@auth_token = Fog::Mock.random_base64(64)
@auth_token_expiration = (Time.now.utc + 86400).iso8601
management_url = URI.parse(options[:openstack_auth_url])
management_url.port = 8776
management_url.path = '/v1'
@openstack_management_url = management_url.to_s
@data ||= { :users => {} }
unless @data[:users].find {|u| u['name'] == options[:openstack_username]}
id = Fog::Mock.random_numbers(6).to_s
@data[:users][id] = {
'id' => id,
'name' => options[:openstack_username],
'email' => "#{options[:openstack_username]}@mock.com",
'tenantId' => Fog::Mock.random_numbers(6).to_s,
'enabled' => true
}
end
end
def data
self.class.data[@openstack_username]
end
def reset_data
self.class.data.delete(@openstack_username)
end
def credentials
{ :provider => 'openstack',
:openstack_auth_url => @openstack_auth_uri.to_s,
:openstack_auth_token => @auth_token,
:openstack_management_url => @openstack_management_url }
end
end
class Real
attr_reader :current_user
attr_reader :current_tenant
def initialize(options={})
require 'multi_json'
@openstack_auth_token = options[:openstack_auth_token]
unless @openstack_auth_token
missing_credentials = Array.new
@openstack_api_key = options[:openstack_api_key]
@openstack_username = options[:openstack_username]
missing_credentials << :openstack_api_key unless @openstack_api_key
missing_credentials << :openstack_username unless @openstack_username
raise ArgumentError, "Missing required arguments: #{missing_credentials.join(', ')}" unless missing_credentials.empty?
end
@openstack_tenant = options[:openstack_tenant]
@openstack_auth_uri = URI.parse(options[:openstack_auth_url])
@openstack_management_url = options[:openstack_management_url]
@openstack_must_reauthenticate = false
@openstack_service_type = options[:openstack_service_type] || ['metering']
@openstack_service_name = options[:openstack_service_name]
@openstack_endpoint_type = options[:openstack_endpoint_type] || 'adminURL'
@connection_options = options[:connection_options] || {}
@current_user = options[:current_user]
@current_tenant = options[:current_tenant]
authenticate
@persistent = options[:persistent] || false
@connection = Fog::Connection.new("#{@scheme}://#{@host}:#{@port}", @persistent, @connection_options)
end
def credentials
{ :provider => 'openstack',
:openstack_auth_url => @openstack_auth_uri.to_s,
:openstack_auth_token => @auth_token,
:openstack_management_url => @openstack_management_url,
:current_user => @current_user,
:current_tenant => @current_tenant }
end
def reload
@connection.reset
end
def request(params)
begin
response = @connection.request(params.merge({
:headers => {
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'X-Auth-Token' => @auth_token
}.merge!(params[:headers] || {}),
:host => @host,
:path => "#{@path}/v2/#{params[:path]}"#,
# Causes errors for some requests like tenants?limit=1
# :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_management_url || @openstack_must_reauthenticate
options = {
:openstack_tenant => @openstack_tenant,
:openstack_api_key => @openstack_api_key,
:openstack_username => @openstack_username,
:openstack_auth_uri => @openstack_auth_uri,
:openstack_auth_token => @openstack_auth_token,
:openstack_service_type => @openstack_service_type,
:openstack_service_name => @openstack_service_name,
:openstack_endpoint_type => @openstack_endpoint_type
}
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

@ -0,0 +1,24 @@
require 'fog/core/model'
module Fog
module Metering
class OpenStack
class Resource < Fog::Model
identity :resource_id
attribute :project_id
attribute :user_id
attribute :metadata
def initialize(attributes)
prepare_service_value(attributes)
super
end
end
end
end
end

View file

@ -0,0 +1,25 @@
require 'fog/core/collection'
require 'fog/openstack/models/metering/resource'
module Fog
module Metering
class OpenStack
class Resources < Fog::Collection
model Fog::Metering::OpenStack::Resource
def all(detailed=true)
load(service.list_resources.body)
end
def find_by_id(resource_id)
resource = service.get_resource(resource_id).body
new(resource)
rescue Fog::Metering::OpenStack::NotFound
nil
end
end
end
end
end

View file

@ -176,7 +176,7 @@ module Fog
@openstack_must_reauthenticate = false
@openstack_service_type = options[:openstack_service_type] || ['network']
@openstack_service_name = options[:openstack_service_name]
@openstack_endpoint_type = options[:openstack_endpoint_type] || 'adminURL'
@openstack_endpoint_type = options[:openstack_endpoint_type] || 'publicURL'
@openstack_region = options[:openstack_region]
@connection_options = options[:connection_options] || {}

View file

@ -0,0 +1,32 @@
module Fog
module Metering
class OpenStack
class Real
def get_resource(resource_id)
request(
:expects => 200,
:method => 'GET',
:path => "resources/#{resource_id}"
)
end
end
class Mock
def get_resource(resource_id)
response = Excon::Response.new
response.status = 200
response.body = {
'resource_id'=>'glance',
'project_id'=>'d646b40dea6347dfb8caee2da1484c56',
'user_id'=>'1d5fd9eda19142289a60ed9330b5d284',
'metadata'=>{}}
response
end
end
end
end
end

View file

@ -0,0 +1,55 @@
module Fog
module Metering
class OpenStack
class Real
def get_samples(meter_id, options=[])
data = {
'q' => Array.new
}
options.each do |opt|
filter = {}
['field', 'op', 'value'].each do |key|
filter[key] = opt[key] if opt[key]
end
data['q'] << filter unless filter.empty?
end
request(
:body => Fog::JSON.encode(data),
:expects => 200,
:method => 'GET',
:path => "meters/#{meter_id}"
)
end
end
class Mock
def get_samples(meter_id)
response = Excon::Response.new
response.status = 200
response.body = [{
'counter_name'=>'image.size',
'user_id'=>'1d5fd9eda19142289a60ed9330b5d284',
'resource_id'=>'glance',
'timestamp'=>'2013-04-03T23:44:21',
'resource_metadata'=>{},
'source'=>'artificial',
'counter_unit'=>'bytes',
'counter_volume'=>10.0,
'project_id'=>'d646b40dea6347dfb8caee2da1484c56',
'message_id'=>'14e4a902-9cf3-11e2-a054-003048f5eafc',
'counter_type'=>'gauge'}]
response
end
end
end
end
end

View file

@ -0,0 +1,56 @@
module Fog
module Metering
class OpenStack
class Real
def get_statistics(meter_id, options={})
data = {
'period' => options['period'],
'q' => Array.new
}
options['q'].each do |opt|
filter = {}
['field', 'op', 'value'].each do |key|
filter[key] = opt[key] if opt[key]
end
data['q'] << filter unless filter.empty?
end if options['q'].is_a? Array
request(
:body => Fog::JSON.encode(data),
:expects => 200,
:method => 'GET',
:path => "meters/#{meter_id}/statistics"
)
end
end
class Mock
def get_statistics(meter_id, options={})
response = Excon::Response.new
response.status = 200
response.body = [{
'count'=>143,
'duration_start'=>'2013-04-03T23:44:21',
'min'=>10.0,
'max'=>10.0,
'duration_end'=>'2013-04-04T23:24:21',
'period'=>0,
'period_end'=>'2013-04-04T23:24:21',
'duration'=>85200.0,
'period_start'=>'2013-04-03T23:44:21',
'avg'=>10.0,
'sum'=>1430.0}]
response
end
end
end
end
end

View file

@ -0,0 +1,50 @@
module Fog
module Metering
class OpenStack
class Real
def list_meters(options=[])
data = {
'q' => Array.new
}
options.each do |opt|
filter = {}
['field', 'op', 'value'].each do |key|
filter[key] = opt[key] if opt[key]
end
data['q'] << filter unless filter.empty?
end
request(
:body => Fog::JSON.encode(data),
:expects => 200,
:method => 'GET',
:path => 'meters'
)
end
end
class Mock
def list_meters(options={})
response = Excon::Response.new
response.status = 200
response.body = [{
'user_id'=>'1d5fd9eda19142289a60ed9330b5d284',
'name'=>'image.size',
'resource_id'=>'glance',
'project_id'=>'d646b40dea6347dfb8caee2da1484c56',
'type'=>'gauge',
'unit'=>'bytes'}]
response
end
end
end
end
end

View file

@ -0,0 +1,32 @@
module Fog
module Metering
class OpenStack
class Real
def list_resources(options = {})
request(
:expects => 200,
:method => 'GET',
:path => 'resources'
)
end
end
class Mock
def list_resources(options = {})
response = Excon::Response.new
response.status = 200
response.body = [{
'resource_id'=>'glance',
'project_id'=>'d646b40dea6347dfb8caee2da1484c56',
'user_id'=>'1d5fd9eda19142289a60ed9330b5d284',
'metadata'=>{}}]
response
end
end
end
end
end

View file

@ -28,7 +28,7 @@ module Fog
unless error.response.body.empty?
begin
data = Fog::JSON.decode(error.response.body)
message = data.values.first ? data.values.first['message'] : data['message']
message = extract_message(data)
rescue => e
Fog::Logger.warning("Received exception '#{e}' while decoding>> #{error.response.body}")
message = error.response.body
@ -42,6 +42,16 @@ module Fog
new_error.instance_variable_set(:@status_code, status_code)
new_error
end
private
def self.extract_message(data)
if data.is_a?(Hash)
message = data.values.first['message'] if data.values.first.is_a?(Hash)
message ||= data['message']
end
message || data.inspect
end
end
class InternalServerError < ServiceError; end
@ -96,6 +106,11 @@ module Fog
end
end
def self.json_response?(response)
return false unless response && response.headers
response.headers['Content-Type'] =~ %r{application/json}i ? true : false
end
def self.normalize_url(endpoint)
return nil unless endpoint
str = endpoint.chomp " "

View file

@ -87,40 +87,25 @@ module Fog
@connection = Fog::Connection.new(endpoint_uri.to_s, @persistent, @connection_options)
end
def request(params)
begin
response = @connection.request(params.merge!({
:headers => {
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'X-Auth-Token' => auth_token
}.merge!(params[:headers] || {}),
:host => endpoint_uri.host,
:path => "#{endpoint_uri.path}/#{params[:path]}"
}))
rescue Excon::Errors::NotFound => error
raise NotFound.slurp(error, region)
rescue Excon::Errors::BadRequest => error
raise BadRequest.slurp error
rescue Excon::Errors::InternalServerError => error
raise InternalServerError.slurp error
rescue Excon::Errors::HTTPStatusError => error
raise ServiceError.slurp error
end
unless response.body.empty?
response.body = Fog::JSON.decode(response.body)
end
response
def request(params, parse_json = true, &block)
super(params, parse_json, &block)
rescue Excon::Errors::NotFound => error
raise NotFound.slurp(error, region)
rescue Excon::Errors::BadRequest => error
raise BadRequest.slurp error
rescue Excon::Errors::InternalServerError => error
raise InternalServerError.slurp error
rescue Excon::Errors::HTTPStatusError => error
raise ServiceError.slurp error
end
def authenticate
options = {
def authenticate(options={})
super({
:rackspace_api_key => @rackspace_api_key,
:rackspace_username => @rackspace_username,
:rackspace_auth_url => @rackspace_auth_url,
:connection_options => @connection_options
}
super(options)
})
end
def service_name

View file

@ -152,30 +152,16 @@ module Fog
true
end
def request(params, parse_json = true)
begin
response = @connection.request(params.merge!({
:headers => {
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'X-Auth-Token' => auth_token
}.merge!(params[:headers] || {}),
:host => endpoint_uri.host,
:path => "#{endpoint_uri.path}/#{params[:path]}",
}))
rescue Excon::Errors::NotFound => error
raise Fog::Storage::Rackspace::NotFound.slurp(error, region)
rescue Excon::Errors::BadRequest => error
raise Fog::Storage::Rackspace::BadRequest.slurp error
rescue Excon::Errors::InternalServerError => error
raise Fog::Storage::Rackspace::InternalServerError.slurp error
rescue Excon::Errors::HTTPStatusError => error
raise Fog::Storage::Rackspace::ServiceError.slurp error
end
if !response.body.empty? && parse_json && response.headers['Content-Type'] =~ %r{application/json}
response.body = Fog::JSON.decode(response.body)
end
response
def request(params, parse_json = true, &block)
super(params, parse_json, &block)
rescue Excon::Errors::NotFound => error
raise Fog::Storage::Rackspace::NotFound.slurp(error, region)
rescue Excon::Errors::BadRequest => error
raise Fog::Storage::Rackspace::BadRequest.slurp error
rescue Excon::Errors::InternalServerError => error
raise Fog::Storage::Rackspace::InternalServerError.slurp error
rescue Excon::Errors::HTTPStatusError => error
raise Fog::Storage::Rackspace::ServiceError.slurp error
end
private

View file

@ -190,7 +190,6 @@ module Fog
@rackspace_servicenet = options[:rackspace_servicenet]
@rackspace_auth_token = options[:rackspace_auth_token]
@rackspace_endpoint = Fog::Rackspace.normalize_url(options[:rackspace_compute_v1_url] || options[:rackspace_management_url])
@rackspace_must_reauthenticate = false
@connection_options = options[:connection_options] || {}
authenticate
Excon.defaults[:ssl_verify_peer] = false if service_net?
@ -202,56 +201,29 @@ module Fog
@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 => endpoint_uri.host,
:path => "#{endpoint_uri.path}/#{params[:path]}",
}))
rescue Excon::Errors::Unauthorized => error
if error.response.body != 'Bad username or password' # token expiration
@rackspace_must_reauthenticate = true
authenticate
retry
else # bad credentials
raise error
end
rescue Excon::Errors::HTTPStatusError => error
raise case error
when Excon::Errors::NotFound
NotFound.slurp(error, region)
else
error
end
end
unless response.body.empty?
response.body = Fog::JSON.decode(response.body)
end
response
def request(params, parse_json = true, &block)
super(params, parse_json, &block)
rescue Excon::Errors::NotFound => error
raise NotFound.slurp(error, region)
rescue Excon::Errors::BadRequest => error
raise BadRequest.slurp error
rescue Excon::Errors::InternalServerError => error
raise InternalServerError.slurp error
rescue Excon::Errors::HTTPStatusError => error
raise ServiceError.slurp error
end
def service_net?
@rackspace_servicenet == true
end
def authenticate
if @rackspace_must_reauthenticate || @rackspace_auth_token.nil?
options = {
:rackspace_api_key => @rackspace_api_key,
:rackspace_username => @rackspace_username,
:rackspace_auth_url => @rackspace_auth_url,
:connection_options => @connection_options
}
super(options)
else
@auth_token = @rackspace_auth_token
@uri = URI.parse(@rackspace_endpoint)
end
def authenticate(options={})
super({
:rackspace_api_key => @rackspace_api_key,
:rackspace_username => @rackspace_username,
:rackspace_auth_url => @rackspace_auth_url,
:connection_options => @connection_options
})
end
def service_name

View file

@ -139,45 +139,25 @@ module Fog
@connection = Fog::Connection.new(endpoint_uri.to_s, @persistent, @connection_options)
end
def request(params)
begin
response = @connection.request(params.merge!({
:headers => {
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'X-Auth-Token' => auth_token
}.merge!(params[:headers] || {}),
:host => endpoint_uri.host,
:path => "#{endpoint_uri.path}/#{params[:path]}"
}))
rescue Excon::Errors::NotFound => error
raise NotFound.slurp(error, region)
rescue Excon::Errors::BadRequest => error
raise BadRequest.slurp error
rescue Excon::Errors::InternalServerError => error
raise InternalServerError.slurp error
rescue Excon::Errors::HTTPStatusError => error
raise ServiceError.slurp error
end
unless response.body.empty?
begin
response.body = Fog::JSON.decode(response.body)
rescue MultiJson::DecodeError => e
response.body = {}
end
end
response
def request(params, parse_json = true, &block)
super(params, parse_json, &block)
rescue Excon::Errors::NotFound => error
raise NotFound.slurp(error, region)
rescue Excon::Errors::BadRequest => error
raise BadRequest.slurp error
rescue Excon::Errors::InternalServerError => error
raise InternalServerError.slurp error
rescue Excon::Errors::HTTPStatusError => error
raise ServiceError.slurp error
end
def authenticate
options = {
def authenticate(options={})
super({
:rackspace_api_key => @rackspace_api_key,
:rackspace_username => @rackspace_username,
:rackspace_auth_url => @rackspace_auth_url,
:connection_options => @connection_options
}
super(options)
})
end
def service_name

View file

@ -85,45 +85,29 @@ module Fog
@connection = Fog::Connection.new(endpoint_uri.to_s, @persistent, @connection_options)
end
def request(params)
begin
response = @connection.request(params.merge!({
:headers => {
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'X-Auth-Token' => auth_token
}.merge!(params[:headers] || {}),
:host => endpoint_uri.host,
:path => "#{endpoint_uri.path}/#{params[:path]}"
}))
rescue Excon::Errors::NotFound => error
raise NotFound.slurp(error, region)
rescue Excon::Errors::BadRequest => error
raise BadRequest.slurp error
rescue Excon::Errors::InternalServerError => error
raise InternalServerError.slurp error
rescue Excon::Errors::HTTPStatusError => error
raise ServiceError.slurp error
end
unless response.body.empty?
response.body = Fog::JSON.decode(response.body)
end
response
def request(params, parse_json = true, &block)
super(params, parse_json, &block)
rescue Excon::Errors::NotFound => error
raise NotFound.slurp(error, region)
rescue Excon::Errors::BadRequest => error
raise BadRequest.slurp error
rescue Excon::Errors::InternalServerError => error
raise InternalServerError.slurp error
rescue Excon::Errors::HTTPStatusError => error
raise ServiceError.slurp error
end
def endpoint_uri(service_endpoint_url=nil)
@uri = super(@rackspace_endpoint || service_endpoint_url, :rackspace_database_url)
end
def authenticate
options = {
def authenticate(options={})
super({
:rackspace_api_key => @rackspace_api_key,
:rackspace_username => @rackspace_username,
:rackspace_auth_url => @rackspace_auth_url,
:connection_options => @connection_options
}
super(options)
})
end
private

View file

@ -6,6 +6,12 @@ module Fog
class Rackspace < Fog::Service
include Fog::Rackspace::Errors
class ServiceError < Fog::Rackspace::Errors::ServiceError; end
class InternalServerError < Fog::Rackspace::Errors::InternalServerError; end
class BadRequest < Fog::Rackspace::Errors::BadRequest; end
class Conflict < Fog::Rackspace::Errors::Conflict; end
class ServiceUnavailable < Fog::Rackspace::Errors::ServiceUnavailable; end
class CallbackError < Fog::Errors::Error
attr_reader :response, :message, :details
def initialize(response)
@ -95,9 +101,6 @@ module Fog
deprecation_warnings(options)
@connection_options[:headers] ||= {}
@connection_options[:headers].merge!({ 'Content-Type' => 'application/json', 'X-Auth-Token' => auth_token })
@persistent = options[:persistent] || false
@connection = Fog::Connection.new(endpoint_uri.to_s, @persistent, @connection_options)
end
@ -108,25 +111,22 @@ module Fog
private
def request(params)
#TODO - Unify code with other rackspace services
def request(params, parse_json = true, &block)
begin
response = @connection.request(params.merge!({
:path => "#{endpoint_uri.path}/#{params[:path]}"
}))
rescue Excon::Errors::BadRequest => error
raise Fog::Rackspace::Errors::BadRequest.slurp error
rescue Excon::Errors::Conflict => error
raise Fog::Rackspace::Errors::Conflict.slurp error
super(params, parse_json, &block)
rescue Excon::Errors::NotFound => error
raise NotFound.slurp(error, region)
rescue Excon::Errors::BadRequest => error
raise BadRequest.slurp error
rescue Excon::Errors::InternalServerError => error
raise InternalServerError.slurp error
rescue Excon::Errors::ServiceUnavailable => error
raise Fog::Rackspace::Errors::ServiceUnavailable.slurp error
raise ServiceUnavailable.slurp error
rescue Excon::Errors::Conflict => error
raise Conflict.slurp error
rescue Excon::Errors::HTTPStatusError => error
raise ServiceError.slurp error
end
unless response.body.empty?
response.body = Fog::JSON.decode(response.body)
end
response
end
def array_to_query_string(arr)
@ -164,15 +164,13 @@ module Fog
@auth_token = credentials['X-Auth-Token']
end
def authenticate
options = {
def authenticate(options={})
super({
:rackspace_api_key => @rackspace_api_key,
:rackspace_username => @rackspace_username,
:rackspace_auth_url => @rackspace_auth_url,
:connection_options => @connection_options
}
super(options)
})
end
end
end

View file

@ -3,6 +3,7 @@ require 'fog/rackspace'
module Fog
module Rackspace
class Identity < Fog::Service
US_ENDPOINT = 'https://identity.api.rackspacecloud.com/v2.0'
UK_ENDPOINT = 'https://lon.identity.api.rackspacecloud.com/v2.0'
@ -33,7 +34,7 @@ module Fog
request :update_user
request :delete_user
class Mock
class Mock < Fog::Rackspace::Service
attr_reader :service_catalog
def request
@ -41,7 +42,7 @@ module Fog
end
end
class Real
class Real < Fog::Rackspace::Service
attr_reader :service_catalog, :auth_token
def initialize(options={})
@ -50,36 +51,19 @@ module Fog
@rackspace_region = options[:rackspace_region]
@rackspace_auth_url = options[:rackspace_auth_url] || US_ENDPOINT
uri = URI.parse(@rackspace_auth_url)
@host = uri.host
@path = uri.path
@port = uri.port
@scheme = uri.scheme
@uri = URI.parse(@rackspace_auth_url)
@host = @uri.host
@path = @uri.path
@port = @uri.port
@scheme = @uri.scheme
@persistent = options[:persistent] || false
@connection_options = options[:connection_options] || {}
@connection = Fog::Connection.new(uri.to_s, @persistent, @connection_options)
@connection = Fog::Connection.new(@uri.to_s, @persistent, @connection_options)
authenticate
end
def request(params)
begin
parameters = params.merge!({
:headers => {
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'X-Auth-Token' => @auth_token
},
:host => @host,
:path => "#{@path}/#{params[:path]}"
})
response = @connection.request(parameters)
response.body = Fog::JSON.decode(response.body) unless response.body.empty?
response
end
end
def authenticate
def authenticate(options={})
data = self.create_token(@rackspace_username, @rackspace_api_key).body
@service_catalog = ServiceCatalog.from_response(self, data)
@auth_token = data['access']['token']['id']

View file

@ -119,41 +119,25 @@ module Fog
@connection = Fog::Connection.new(endpoint_uri.to_s, @persistent, @connection_options)
end
def request(params)
#TODO - Unify code with other rackspace services
begin
response = @connection.request(params.merge!({
:headers => {
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'X-Auth-Token' => auth_token
}.merge!(params[:headers] || {}),
:host => endpoint_uri.host,
:path => "#{endpoint_uri.path}/#{params[:path]}"
}))
rescue Excon::Errors::NotFound => error
raise NotFound.slurp(error, region)
rescue Excon::Errors::BadRequest => error
raise BadRequest.slurp error
rescue Excon::Errors::InternalServerError => error
raise InternalServerError.slurp error
rescue Excon::Errors::HTTPStatusError => error
raise ServiceError.slurp error
end
unless response.body.empty?
response.body = Fog::JSON.decode(response.body)
end
response
def request(params, parse_json = true, &block)
super(params, parse_json, &block)
rescue Excon::Errors::NotFound => error
raise NotFound.slurp(error, region)
rescue Excon::Errors::BadRequest => error
raise BadRequest.slurp error
rescue Excon::Errors::InternalServerError => error
raise InternalServerError.slurp error
rescue Excon::Errors::HTTPStatusError => error
raise ServiceError.slurp error
end
def authenticate
options = {
def authenticate(options={})
super({
:rackspace_api_key => @rackspace_api_key,
:rackspace_username => @rackspace_username,
:rackspace_auth_url => @rackspace_auth_url,
:connection_options => @connection_options
}
super(options)
})
end
def service_name

View file

@ -18,7 +18,7 @@ module Fog
new(data)
rescue Excon::Errors::NotFound
nil
rescue Excon::Errors::NotAuthorized
rescue Excon::Errors::Unauthorized
nil
end
@ -27,7 +27,7 @@ module Fog
new(data)
rescue Excon::Errors::NotFound
nil
rescue Excon::Errors::NotAuthorized
rescue Excon::Errors::Unauthorized
nil
end
end

View file

@ -0,0 +1,75 @@
module Fog
module Storage
class Rackspace
class Real
# Deletes multiple objects or containers with a single request.
#
# To delete objects from a single container, +container+ may be provided
# and +object_names+ should be an Array of object names within the container.
#
# To delete objects from multiple containers or delete containers,
# +container+ should be +nil+ and all +object_names+ should be prefixed with a container name.
#
# Containers must be empty when deleted. +object_names+ are processed in the order given,
# so objects within a container should be listed first to empty the container.
#
# Up to 10,000 objects may be deleted in a single request.
# The server will respond with +200 OK+ for all requests.
# +response.body+ must be inspected for actual results.
#
# @example Delete objects from a container
# object_names = ['object', 'another/object']
# conn.delete_multiple_objects('my_container', object_names)
#
# @example Delete objects from multiple containers
# object_names = ['container_a/object', 'container_b/object']
# conn.delete_multiple_objects(nil, object_names)
#
# @example Delete a container and all it's objects
# object_names = ['my_container/object_a', 'my_container/object_b', 'my_container']
# conn.delete_multiple_objects(nil, object_names)
#
# @param container [String,nil] Name of container.
# @param object_names [Array<String>] Object names to be deleted.
# @param options [Hash] Additional request headers.
#
# @return [Excon::Response]
# * body [Hash] - Results of the operation.
# * "Number Not Found" [Integer] - Number of missing objects or containers.
# * "Response Status" [String] - Response code for the subrequest of the last failed operation.
# * "Errors" [Array<object_name, response_status>]
# * object_name [String] - Object that generated an error when the delete was attempted.
# * response_status [String] - Response status from the subrequest for object_name.
# * "Number Deleted" [Integer] - Number of objects or containers deleted.
# * "Response Body" [String] - Response body for "Response Status".
#
# @raise [Fog::Storage::Rackspace::NotFound] HTTP 404
# @raise [Fog::Storage::Rackspace::BadRequest] HTTP 400
# @raise [Fog::Storage::Rackspace::InternalServerError] HTTP 500
# @raise [Fog::Storage::Rackspace::ServiceError]
# @raise [Excon::Errors::Unauthorized] HTTP 401
#
# @see http://docs.rackspace.com/files/api/v1/cf-devguide/content/Bulk_Delete-d1e2338.html
def delete_multiple_objects(container, object_names, options = {})
body = object_names.map do |name|
object_name = container ? "#{ container }/#{ name }" : name
URI.encode(object_name)
end.join("\n")
response = request({
:expects => 200,
:method => 'DELETE',
:headers => options.merge('Content-Type' => 'text/plain',
'Accept' => 'application/json'),
:body => body,
:query => { 'bulk-delete' => true }
}, false)
response.body = Fog::JSON.decode(response.body)
response
end
end
end
end
end

View file

@ -0,0 +1,50 @@
module Fog
module Storage
class Rackspace
class Real
# Delete a static large object.
#
# Deletes the SLO manifest +object+ and all segments that it references.
# The server will respond with +200 OK+ for all requests.
# +response.body+ must be inspected for actual results.
#
# @param container [String] Name of container.
# @param object [String] Name of the SLO manifest object.
# @param options [Hash] Additional request headers.
#
# @return [Excon::Response]
# * body [Hash] - Results of the operation.
# * "Number Not Found" [Integer] - Number of missing segments.
# * "Response Status" [String] - Response code for the subrequest of the last failed operation.
# * "Errors" [Array<object_name, response_status>]
# * object_name [String] - Object that generated an error when the delete was attempted.
# * response_status [String] - Response status from the subrequest for object_name.
# * "Number Deleted" [Integer] - Number of segments deleted.
# * "Response Body" [String] - Response body for Response Status.
#
# @raise [Fog::Storage::Rackspace::NotFound] HTTP 404
# @raise [Fog::Storage::Rackspace::BadRequest] HTTP 400
# @raise [Fog::Storage::Rackspace::InternalServerError] HTTP 500
# @raise [Fog::Storage::Rackspace::ServiceError]
# @raise [Excon::Errors::Unauthorized] HTTP 401
#
# @see http://docs.rackspace.com/files/api/v1/cf-devguide/content/Deleting_a_Large_Object-d1e2228.html
def delete_static_large_object(container, object, options = {})
response = request({
:expects => 200,
:method => 'DELETE',
:headers => options.merge('Content-Type' => 'text/plain',
'Accept' => 'application/json'),
:path => "#{Fog::Rackspace.escape(container)}/#{Fog::Rackspace.escape(object)}",
:query => { 'multipart-manifest' => 'delete' }
}, false)
response.body = Fog::JSON.decode(response.body)
response
end
end
end
end
end

View file

@ -23,7 +23,9 @@ module Fog
:expects => 200,
:method => 'GET',
:path => "#{Fog::Rackspace.escape(container)}/#{Fog::Rackspace.escape(object)}"
}), false)
}),
false,
&block)
end
end

View file

@ -0,0 +1,44 @@
module Fog
module Storage
class Rackspace
class Real
# Create a new dynamic large object manifest
#
# Creates an object with a +X-Object-Manifest+ header that specifies the common prefix ("<container>/<prefix>")
# for all uploaded segments. Retrieving the manifest object streams all segments matching this prefix.
# Segments must sort in the order they should be concatenated. Note that any future objects stored in the container
# along with the segments that match the prefix will be included when retrieving the manifest object.
#
# All segments must be stored in the same container, but may be in a different container than the manifest object.
# The default +X-Object-Manifest+ header is set to "+container+/+object+", but may be overridden in +options+
# to specify the prefix and/or the container where segments were stored.
# If overridden, names should be CGI escaped (excluding spaces) if needed (see {Fog::Rackspace.escape}).
#
# @param container [String] Name for container where +object+ will be stored. Should be < 256 bytes and must not contain '/'
# @param object [String] Name for manifest object.
# @param options [Hash] Config headers for +object+.
# @option options [String] 'X-Object-Manifest' ("container/object") "<container>/<prefix>" for segment objects.
#
# @raise [Fog::Storage::Rackspace::NotFound] HTTP 404
# @raise [Fog::Storage::Rackspace::BadRequest] HTTP 400
# @raise [Fog::Storage::Rackspace::InternalServerError] HTTP 500
# @raise [Fog::Storage::Rackspace::ServiceError]
# @raise [Excon::Errors::Unauthorized]
#
# @see http://docs.rackspace.com/files/api/v1/cf-devguide/content/Large_Object_Creation-d1e2019.html
def put_dynamic_obj_manifest(container, object, options = {})
path = "#{Fog::Rackspace.escape(container)}/#{Fog::Rackspace.escape(object)}"
headers = {'X-Object-Manifest' => path}.merge(options)
request(
:expects => 201,
:headers => headers,
:method => 'PUT',
:path => path
)
end
end
end
end
end

View file

@ -3,38 +3,11 @@ module Fog
class Rackspace
class Real
# Create a new manifest object
# Create a new dynamic large object manifest
#
# Creates an object with a +X-Object-Manifest+ header that specifies the common prefix ("<container>/<prefix>")
# for all uploaded segments. Retrieving the manifest object streams all segments matching this prefix.
# Segments must sort in the order they should be concatenated. Note that any future objects stored in the container
# along with the segments that match the prefix will be included when retrieving the manifest object.
#
# All segments must be stored in the same container, but may be in a different container than the manifest object.
# The default +X-Object-Manifest+ header is set to "+container+/+object+", but may be overridden in +options+
# to specify the prefix and/or the container where segments were stored.
# If overridden, names should be CGI escaped (excluding spaces) if needed (see {Fog::Rackspace.escape}).
#
# @param container [String] Name for container where +object+ will be stored. Should be < 256 bytes and must not contain '/'
# @param object [String] Name for manifest object.
# @param options [Hash] Config headers for +object+.
# @option options [String] 'X-Object-Manifest' ("container/object") "<container>/<prefix>" for segment objects.
#
# @raise [Fog::Storage::Rackspace::NotFound] HTTP 404
# @raise [Fog::Storage::Rackspace::BadRequest] HTTP 400
# @raise [Fog::Storage::Rackspace::InternalServerError] HTTP 500
# @raise [Fog::Storage::Rackspace::ServiceError]
#
# @see http://docs.rackspace.com/files/api/v1/cf-devguide/content/Large_Object_Creation-d1e2019.html
# This is an alias for {#put_dynamic_obj_manifest} for backward compatibility.
def put_object_manifest(container, object, options = {})
path = "#{Fog::Rackspace.escape(container)}/#{Fog::Rackspace.escape(object)}"
headers = {'X-Object-Manifest' => path}.merge(options)
request(
:expects => 201,
:headers => headers,
:method => 'PUT',
:path => path
)
put_dynamic_obj_manifest(container, object, options)
end
end

View file

@ -0,0 +1,60 @@
module Fog
module Storage
class Rackspace
class Real
# Create a new static large object manifest.
#
# A static large object is similar to a dynamic large object. Whereas a GET for a dynamic large object manifest
# will stream segments based on the manifest's +X-Object-Manifest+ object name prefix, a static large object
# manifest streams segments which are defined by the user within the manifest. Information about each segment is
# provided in +segments+ as an Array of Hash objects, ordered in the sequence which the segments should be streamed.
#
# When the SLO manifest is received, each segment's +etag+ and +size_bytes+ will be verified.
# The +etag+ for each segment is returned in the response to {#put_object}, but may also be calculated.
# e.g. +Digest::MD5.hexdigest(segment_data)+
#
# The maximum number of segments for a static large object is 1000, and all segments (except the last) must be
# at least 1 MiB in size. Unlike a dynamic large object, segments are not required to be in the same container.
#
# @example
# segments = [
# { :path => 'segments_container/first_segment',
# :etag => 'md5 for first_segment',
# :size_bytes => 'byte size of first_segment' },
# { :path => 'segments_container/second_segment',
# :etag => 'md5 for second_segment',
# :size_bytes => 'byte size of second_segment' }
# ]
# put_static_obj_manifest('my_container', 'my_large_object', segments)
#
# @param container [String] Name for container where +object+ will be stored.
# Should be < 256 bytes and must not contain '/'
# @param object [String] Name for manifest object.
# @param segments [Array<Hash>] Segment data for the object.
# @param options [Hash] Config headers for +object+.
#
# @raise [Fog::Storage::Rackspace::NotFound] HTTP 404
# @raise [Fog::Storage::Rackspace::BadRequest] HTTP 400
# @raise [Fog::Storage::Rackspace::InternalServerError] HTTP 500
# @raise [Fog::Storage::Rackspace::ServiceError]
# @raise [Excon::Errors::Unauthorized] HTTP 401
#
# @see http://docs.rackspace.com/files/api/v1/cf-devguide/content/Uploading_the_Manifext-d1e2227.html
def put_static_obj_manifest(container, object, segments, options = {})
request(
:expects => 201,
:method => 'PUT',
:headers => options,
:body => Fog::JSON.encode(segments),
:path => "#{Fog::Rackspace.escape(container)}/#{Fog::Rackspace.escape(object)}",
:query => { 'multipart-manifest' => 'put' }
)
end
end
end
end
end

View file

@ -26,12 +26,54 @@ module Fog
@uri = URI.parse url
end
def authenticate(options)
def authenticate(options={})
self.send authentication_method, options
end
def request(params, parse_json = true, &block)
first_attempt = true
begin
response = @connection.request(request_params(params), &block)
rescue Excon::Errors::Unauthorized => error
raise error unless first_attempt
first_attempt = false
authenticate
retry
end
process_response(response) if parse_json
response
end
private
def process_response(response)
if response && response.body && response.body.is_a?(String) && Fog::Rackspace.json_response?(response)
begin
response.body = Fog::JSON.decode(response.body)
rescue MultiJson::DecodeError => e
Fog::Logger.warning("Error Parsing response json - #{e}")
response.body = {}
end
end
end
def headers(options={})
h = {
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'X-Auth-Token' => auth_token
}.merge(options[:headers] || {})
end
def request_params(params)
params.merge({
:headers => headers(params),
:host => endpoint_uri.host,
:path => "#{endpoint_uri.path}/#{params[:path]}"
})
end
def authentication_method
if v2_authentication?
:authenticate_v2

View file

@ -25,6 +25,8 @@ module Fog
request :copy_object
request :delete_container
request :delete_object
request :delete_static_large_object
request :delete_multiple_objects
request :get_container
request :get_containers
request :get_object
@ -35,6 +37,8 @@ module Fog
request :put_container
request :put_object
request :put_object_manifest
request :put_dynamic_obj_manifest
request :put_static_obj_manifest
request :post_set_meta_temp_url_key
module Utils
@ -54,7 +58,7 @@ module Fog
end
end
end
end
class Mock < Fog::Rackspace::Service
include Utils
@ -107,7 +111,7 @@ module Fog
@rackspace_servicenet = options[:rackspace_servicenet]
@rackspace_auth_token = options[:rackspace_auth_token]
@rackspace_storage_url = options[:rackspace_storage_url]
@rackspace_cdn_url = options[:rackspace_cdn_url]
@rackspace_cdn_url = options[:rackspace_cdn_url]
@rackspace_region = options[:rackspace_region] || :dfw
@rackspace_temp_url_key = options[:rackspace_temp_url_key]
@rackspace_must_reauthenticate = false
@ -117,8 +121,8 @@ module Fog
@persistent = options[:persistent] || false
Excon.defaults[:ssl_verify_peer] = false if service_net?
@connection = Fog::Connection.new(endpoint_uri.to_s, @persistent, @connection_options)
end
end
# Return Account Details
# @return [Fog::Storage::Rackspace::Account] account details object
def account
@ -139,43 +143,21 @@ module Fog
end
def request(params, parse_json = true, &block)
begin
response = @connection.request(params.merge({
:headers => {
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'X-Auth-Token' => auth_token
}.merge!(params[:headers] || {}),
:host => endpoint_uri.host,
:path => "#{endpoint_uri.path}/#{params[:path]}",
}), &block)
rescue Excon::Errors::Unauthorized => error
if error.response.body != 'Bad username or password' # token expiration
@rackspace_must_reauthenticate = true
authenticate
retry
else # bad credentials
raise error
end
rescue Excon::Errors::NotFound => error
raise NotFound.slurp(error, region)
rescue Excon::Errors::BadRequest => error
raise BadRequest.slurp error
rescue Excon::Errors::InternalServerError => error
raise InternalServerError.slurp error
rescue Excon::Errors::HTTPStatusError => error
raise ServiceError.slurp error
end
if !response.body.empty? && parse_json && response.headers['Content-Type'] =~ %r{application/json}
response.body = Fog::JSON.decode(response.body)
end
response
super(params, parse_json, &block)
rescue Excon::Errors::NotFound => error
raise NotFound.slurp(error, region)
rescue Excon::Errors::BadRequest => error
raise BadRequest.slurp error
rescue Excon::Errors::InternalServerError => error
raise InternalServerError.slurp error
rescue Excon::Errors::HTTPStatusError => error
raise ServiceError.slurp error
end
def service_net?
@rackspace_servicenet == true
end
end
def authenticate
if @rackspace_must_reauthenticate || @rackspace_auth_token.nil?
options = {
@ -183,14 +165,14 @@ module Fog
:rackspace_username => @rackspace_username,
:rackspace_auth_url => @rackspace_auth_url,
:connection_options => @connection_options
}
}
super(options)
else
@auth_token = @rackspace_auth_token
@uri = URI.parse(@rackspace_storage_url)
end
end
def service_name
:cloudFiles
end
@ -206,15 +188,15 @@ module Fog
@uri.host = "snet-#{@uri.host}" if service_net?
@uri
end
private
private
def authenticate_v1(options)
credentials = Fog::Rackspace.authenticate(options, @connection_options)
endpoint_uri credentials['X-Storage-Url']
@auth_token = credentials['X-Auth-Token']
end
end
end
end

View file

@ -64,7 +64,8 @@ module Fog
:aws_secret_access_key => @riakcs_secret_access_key,
:host => @host,
:port => @port,
:scheme => @scheme
:scheme => @scheme,
:connection_options => @connection_options
)
end

View file

@ -56,7 +56,8 @@ module Fog
:aws_secret_access_key => @riakcs_secret_access_key,
:host => @host,
:port => @port,
:scheme => @scheme
:scheme => @scheme,
:connection_options => @connection_options
)
end
end

View file

@ -105,6 +105,19 @@ class AWS
})
DB_LOG_FILE = {
'LastWritten' => Time,
'Size' => Integer,
'LogFileName' => String
}
DESCRIBE_DB_LOG_FILES = BASIC.merge({
'DescribeDBLogFilesResult' => {
'Marker' => Fog::Nullable::String,
'DBLogFiles' => [DB_LOG_FILE]
}
})
SNAPSHOT={
'AllocatedStorage' => Integer,
'AvailabilityZone' => String,

View file

@ -0,0 +1,19 @@
Shindo.tests('AWS::RDS | log file requests', %w[aws rds]) do
tests('success') do
pending if Fog.mocking?
suffix = rand(65536).to_s(16)
@db_instance_id = "fog-test-#{suffix}"
tests('#describe_db_log_files').formats(AWS::RDS::Formats::DESCRIBE_DB_LOG_FILES) do
result = Fog::AWS[:rds].describe_db_log_files(@db_instance_id).body['DescribeDBLogFilesResult']
returns(true) { result['DBLogFiles'].size > 0 }
result
end
end
tests('failures') do
raises(Fog::AWS::RDS::NotFound) {Fog::AWS[:rds].describe_db_log_files('doesntexist')}
end
end

View file

@ -0,0 +1,19 @@
Shindo.tests('AWS::STS | assume role', ['aws']) do
@policy = {"Statement" => [{"Effect" => "Allow", "Action" => "*", "Resource" => "*"}]}
@response_format = {
'SessionToken' => String,
'SecretAccessKey' => String,
'Expiration' => String,
'AccessKeyId' => String,
'Arn' => String,
'RequestId' => String
}
tests("#assume_role('rolename', 'assumed_role_session', 'external_id', #{@policy.inspect}, 900)").formats(@response_format) do
pending if Fog.mocking?
Fog::AWS[:sts].assume_role("rolename","assumed_role_session","external_id", @policy, 900).body
end
end

View file

@ -0,0 +1,52 @@
Shindo.tests('Fog::Metering[:openstack] | meter requests', ['openstack']) do
@sample_format = {
'counter_name' => String,
'user_id' => String,
'resource_id' => String,
'timestamp' => String,
'resource_metadata' => Hash,
'source' => String,
'counter_unit' => String,
'counter_volume' => Float,
'project_id' => String,
'message_id' => String,
'counter_type' => String
}
@meter_format = {
'user_id' => String,
'name' => String,
'resource_id' => String,
'project_id' => String,
'type' => String,
'unit' => String
}
@statistics_format = {
'count' => Integer,
'duration_start' => String,
'min' => Float,
'max' => Float,
'duration_end' => String,
'period' => Integer,
'period_end' => String,
'duration' => Float,
'period_start' => String,
'avg' => Float,
'sum' => Float
}
tests('success') do
tests('#list_meters').formats([@meter_format]) do
Fog::Metering[:openstack].list_meters.body
end
tests('#get_samples').formats([@sample_format]) do
Fog::Metering[:openstack].get_samples('test').body
end
tests('#get_statistics').formats([@statistics_format]) do
Fog::Metering[:openstack].get_statistics('test').body
end
end
end

View file

@ -0,0 +1,19 @@
Shindo.tests('Fog::Metering[:openstack] | resource requests', ['openstack']) do
@resource_format = {
'resource_id' => String,
'project_id' => String,
'user_id' => String,
'metadata' => Hash,
}
tests('success') do
tests('#list_resource').formats([@resource_format]) do
Fog::Metering[:openstack].list_resources.body
end
tests('#get_resource').formats(@resource_format) do
Fog::Metering[:openstack].get_resource('test').body
end
end
end

View file

@ -101,4 +101,13 @@ Shindo.tests('Fog::Rackspace::BlockStorage', ['rackspace']) do
end
end
tests('reauthentication') do
pending if Fog.mocking?
@service = Fog::Rackspace::BlockStorage.new :rackspace_region => :ord
returns(true, "auth token populated") { !@service.send(:auth_token).nil? }
@service.instance_variable_set("@auth_token", "bad-token")
returns(200) { @service.list_volumes.status }
end
end

View file

@ -87,4 +87,13 @@ Shindo.tests('Rackspace | Compute', ['rackspace']) do
returns(true, "uses custom endpoint") { (@service.instance_variable_get("@uri").host =~ /snet-/) != nil }
end
end
tests('reauthentication') do
pending if Fog.mocking?
@service = Fog::Compute::Rackspace.new
returns(true, "auth token populated") { !@service.send(:auth_token).nil? }
@service.instance_variable_set("@auth_token", "bad-token")
returns(true) { [200, 203].include?(@service.list_flavors.status) }
end
end

View file

@ -100,4 +100,13 @@ Shindo.tests('Fog::Compute::RackspaceV2', ['rackspace']) do
end
end
tests('reauthentication') do
pending if Fog.mocking?
@service = Fog::Compute::RackspaceV2.new
returns(true, "auth token populated") { !@service.send(:auth_token).nil? }
@service.instance_variable_set("@auth_token", "bad_token")
returns(true) { [200, 203].include? @service.list_flavors.status }
end
end

View file

@ -102,6 +102,15 @@ Shindo.tests('Fog::Rackspace::Databases', ['rackspace']) do |variable|
end
end
tests('reauthentication') do
pending if Fog.mocking?
@service = Fog::Rackspace::Databases.new
returns(true, "auth token populated") { !@service.send(:auth_token).nil? }
@service.instance_variable_set("@auth_token", "bad_token")
returns(200) { @service.list_flavors.status }
end
@service = Fog::Rackspace::Databases.new
tests('#flavors').succeeds do

View file

@ -82,6 +82,15 @@ Shindo.tests('Fog::DNS::Rackspace', ['rackspace']) do
end
end
tests('reauthentication') do
pending if Fog.mocking?
@service =Fog::DNS::Rackspace.new
returns(true, "auth token populated") { !@service.send(:auth_token).nil? }
@service.instance_variable_set("@auth_token", "bad_token")
returns(200) { @service.list_domains.status }
end
tests('array_to_query_string') do
pending if Fog.mocking?

View file

@ -0,0 +1,25 @@
Shindo.tests('Fog::Rackspace::Identity', ['rackspace']) do
tests('current authentication') do
pending if Fog.mocking?
tests('variables populated').returns(200) do
@service = Fog::Rackspace::Identity.new :rackspace_auth_url => 'https://identity.api.rackspacecloud.com/v2.0', :connection_options => {:ssl_verify_peer => true}
returns(true, "auth token populated") { !@service.auth_token.nil? }
returns(false, "path populated") { @service.instance_variable_get("@uri").host.nil? }
returns(false, "service catalog populated") { @service.service_catalog.nil? }
@service.list_tenants.status
end
end
tests('reauthentication') do
pending if Fog.mocking?
@service = Fog::Rackspace::Identity.new :rackspace_region => :ord
returns(true, "auth token populated") { !@service.auth_token.nil? }
@service.instance_variable_set("@auth_token", "bad-token")
returns(true) { [200, 203].include? @service.list_tenants.status }
end
end

View file

@ -101,6 +101,16 @@ Shindo.tests('Fog::Rackspace::LoadBalancers', ['rackspace']) do
end
end
tests('reauthentication') do
pending if Fog.mocking?
@service = Fog::Rackspace::LoadBalancers.new
returns(true, "auth token populated") { !@service.send(:auth_token).nil? }
@service.instance_variable_set("@auth_token", "bad-token")
returns(200) { @service.list_load_balancers.status }
end
pending if Fog.mocking?
@service = Fog::Rackspace::LoadBalancers.new

View file

@ -18,4 +18,39 @@ Shindo.tests('Fog::Rackspace', ['rackspace']) do
end
end
tests('json_response?') do
returns(false, "nil") { Fog::Rackspace.json_response?(nil) }
tests('missing header').returns(false) do
response = Excon::Response.new
response.headers = nil #maybe this is a forced case
returns(true) { response.headers.nil? }
Fog::Rackspace.json_response?(response)
end
tests('nil Content-Type header').returns(false) do
response = Excon::Response.new
response.headers['Content-Type'] = nil
Fog::Rackspace.json_response?(response)
end
tests('text/html Content-Type header').returns(false) do
response = Excon::Response.new
response.headers['Content-Type'] = 'text/html'
Fog::Rackspace.json_response?(response)
end
tests('application/json Content-Type header').returns(true) do
response = Excon::Response.new
response.headers['Content-Type'] = 'application/json'
Fog::Rackspace.json_response?(response)
end
tests('APPLICATION/JSON Content-Type header').returns(true) do
response = Excon::Response.new
response.headers['Content-Type'] = 'APPLICATION/JSON'
Fog::Rackspace.json_response?(response)
end
end
end

View file

@ -41,7 +41,7 @@ Shindo.tests('Fog::Rackspace::Identity | users', ['rackspace']) do
service = Fog::Rackspace::Identity.new
id = nil
username = 'foguser'
username = "fog#{Time.now.to_i.to_s}"
email = 'fog_user@example.com'
enabled = true
password = 'Fog_password1'
@ -57,6 +57,9 @@ Shindo.tests('Fog::Rackspace::Identity | users', ['rackspace']) do
service.delete_user(id)
end
# there appears to be a werid caching issue. It's just easier to create a new username and continue on
username = "fog#{Time.now.to_i.to_s}"
tests('#create_user with password').succeeds do
data = service.create_user(username, email, enabled, :password => password ).body
id = data['user']['id']

View file

@ -1,106 +1,350 @@
Shindo.tests('Fog::Storage[:rackspace] | large object requests', ["rackspace"]) do
Shindo.tests('Fog::Storage[:rackspace] | large object requests', ['rackspace']) do
unless Fog.mocking?
@directory = Fog::Storage[:rackspace].directories.create(:key => 'foglargeobjecttests')
@directory = Fog::Storage[:rackspace].directories.create(:key => 'foglargeobjecttests')
@directory2 = Fog::Storage[:rackspace].directories.create(:key => 'foglargeobjecttests2')
@segments = {
:a => {
:container => @directory.identity,
:name => 'fog_large_object/a',
:data => 'a' * (1024**2 + 10),
:size => 1024**2 + 10,
:etag => 'c2e97007d59f0c19b850debdcb80cca5'
},
:b => {
:container => @directory.identity,
:name => 'fog_large_object/b',
:data => 'b' * (1024**2 + 20),
:size => 1024**2 + 20,
:etag => 'd35f50622a1259daad75ff7d5512c7ef'
},
:c => {
:container => @directory.identity,
:name => 'fog_large_object2/a',
:data => 'c' * (1024**2 + 30),
:size => 1024**2 + 30,
:etag => '901d3531a87d188041d4d5b43cb464c1'
},
:d => {
:container => @directory2.identity,
:name => 'fog_large_object2/b',
:data => 'd' * (1024**2 + 40),
:size => 1024**2 + 40,
:etag => '350c0e00525198813920a157df185c8d'
}
}
end
tests('success') do
tests("#put_object('foglargeobjecttests', 'fog_large_object/1', ('x' * 4 * 1024 * 1024))").succeeds do
tests('upload test segments').succeeds do
pending if Fog.mocking?
Fog::Storage[:rackspace].put_object(@directory.identity, 'fog_large_object/1', ('x' * 4 * 1024 * 1024))
@segments.each_value do |segment|
Fog::Storage[:rackspace].put_object(segment[:container], segment[:name], segment[:data])
end
end
tests("#put_object('foglargeobjecttests', 'fog_large_object/2', ('x' * 2 * 1024 * 1024))").succeeds do
tests('dynamic large object requests') do
pending if Fog.mocking?
Fog::Storage[:rackspace].put_object(@directory.identity, 'fog_large_object/2', ('x' * 2 * 1024 * 1024))
end
tests("#put_object('foglargeobjecttests', 'fog_large_object2/1', ('x' * 1 * 1024 * 1024))").succeeds do
pending if Fog.mocking?
Fog::Storage[:rackspace].put_object(@directory.identity, 'fog_large_object2/1', ('x' * 1 * 1024 * 1024))
end
tests("using default X-Object-Manifest header") do
tests("#put_object_manifest('foglargeobjecttests', 'fog_large_object')").succeeds do
pending if Fog.mocking?
tests('#put_object_manifest alias').succeeds do
Fog::Storage[:rackspace].put_object_manifest(@directory.identity, 'fog_large_object')
end
tests("#get_object streams all segments matching the default prefix").succeeds do
pending if Fog.mocking?
Fog::Storage[:rackspace].get_object(@directory.identity, 'fog_large_object').body == ('x' * 7 * 1024 * 1024)
tests('using default X-Object-Manifest header') do
tests('#put_dynamic_obj_manifest').succeeds do
Fog::Storage[:rackspace].put_dynamic_obj_manifest(@directory.identity, 'fog_large_object')
end
tests('#get_object streams all segments matching the default prefix').succeeds do
expected = @segments[:a][:data] + @segments[:b][:data] + @segments[:c][:data]
Fog::Storage[:rackspace].get_object(@directory.identity, 'fog_large_object').body == expected
end
# When the manifest object name is equal to the segment prefix, OpenStack treats it as if it's the first segment.
# So you must prepend the manifest object's Etag - Digest::MD5.hexdigest('')
tests('#head_object returns Etag that includes manifest object in calculation').succeeds do
etags = ['d41d8cd98f00b204e9800998ecf8427e', @segments[:a][:etag], @segments[:b][:etag], @segments[:c][:etag]]
expected = "\"#{ Digest::MD5.hexdigest(etags.join) }\"" # returned in quotes "\"2577f38428e895c50de6ea78ccc7da2a"\"
Fog::Storage[:rackspace].head_object(@directory.identity, 'fog_large_object').headers['Etag'] == expected
end
end
tests("#head_object returns Etag that includes manifest object in calculation").succeeds do
pending if Fog.mocking?
tests('specifying X-Object-Manifest segment prefix') do
tests('#put_dynamic_obj_manifest').succeeds do
options = { 'X-Object-Manifest' => "#{ @directory.identity }/fog_large_object/" }
Fog::Storage[:rackspace].put_dynamic_obj_manifest(@directory.identity, 'fog_large_object', options)
end
tests('#get_object streams segments only matching the specified prefix').succeeds do
expected = @segments[:a][:data] + @segments[:b][:data]
Fog::Storage[:rackspace].get_object(@directory.identity, 'fog_large_object').body == expected
end
tests('#head_object returns Etag that does not include manifest object in calculation').succeeds do
etags = [@segments[:a][:etag], @segments[:b][:etag]]
expected = "\"#{ Digest::MD5.hexdigest(etags.join) }\"" # returned in quotes "\"0f035ed3cc38aa0ef46dda3478fad44d"\"
Fog::Storage[:rackspace].head_object(@directory.identity, 'fog_large_object').headers['Etag'] == expected
end
end
tests('storing manifest in a different container than the segments') do
tests('#put_dynamic_obj_manifest').succeeds do
options = { 'X-Object-Manifest' => "#{ @directory.identity }/fog_large_object/" }
Fog::Storage[:rackspace].put_dynamic_obj_manifest(@directory2.identity, 'fog_large_object', options)
end
tests('#get_object').succeeds do
expected = @segments[:a][:data] + @segments[:b][:data]
Fog::Storage[:rackspace].get_object(@directory2.identity, 'fog_large_object').body == expected
end
etags = []
# When the manifest object name is equal to the prefix, OpenStack treats it as if it's the first segment.
etags << Digest::MD5.hexdigest('') # Etag for manifest object => "d41d8cd98f00b204e9800998ecf8427e"
etags << Digest::MD5.hexdigest('x' * 4 * 1024 * 1024) # => "44981362d3ba9b5bacaf017c2f29d355"
etags << Digest::MD5.hexdigest('x' * 2 * 1024 * 1024) # => "67b2f816a30e8956149b2d7beb479e51"
etags << Digest::MD5.hexdigest('x' * 1 * 1024 * 1024) # => "b561f87202d04959e37588ee05cf5b10"
expected = Digest::MD5.hexdigest(etags.join) # => "42e92048bd2c8085e7072b0b55fd76ab"
actual = Fog::Storage[:rackspace].head_object(@directory.identity, 'fog_large_object').headers['Etag']
actual.gsub('"', '') == expected # actual is returned in quotes "\"42e92048bd2c8085e7072b0b55fd76abu"\"
end
end
tests("specifying X-Object-Manifest segment prefix") do
tests('static large object requests') do
pending if Fog.mocking?
tests('single container') do
tests('#put_static_obj_manifest').succeeds do
segments = [
{ :path => "#{ @segments[:a][:container] }/#{ @segments[:a][:name] }",
:etag => @segments[:a][:etag],
:size_bytes => @segments[:a][:size] },
{ :path => "#{ @segments[:c][:container] }/#{ @segments[:c][:name] }",
:etag => @segments[:c][:etag],
:size_bytes => @segments[:c][:size] }
]
Fog::Storage[:rackspace].put_static_obj_manifest(@directory.identity, 'fog_large_object', segments)
end
tests('#head_object') do
etags = [@segments[:a][:etag], @segments[:c][:etag]]
etag = "\"#{ Digest::MD5.hexdigest(etags.join) }\"" # "\"ad7e633a12e8a4915b45e6dd1d4b0b4b\""
content_length = (@segments[:a][:size] + @segments[:c][:size]).to_s
response = Fog::Storage[:rackspace].head_object(@directory.identity, 'fog_large_object')
returns(etag, 'returns ETag computed from segments') { response.headers['Etag'] }
returns(content_length , 'returns Content-Length for all segments') { response.headers['Content-Length'] }
returns('True', 'returns X-Static-Large-Object header') { response.headers['X-Static-Large-Object'] }
end
tests('#get_object').succeeds do
expected = @segments[:a][:data] + @segments[:c][:data]
Fog::Storage[:rackspace].get_object(@directory.identity, 'fog_large_object').body == expected
end
tests('#delete_static_large_object') do
expected = {
'Number Not Found' => 0,
'Response Status' => '200 OK',
'Errors' => [],
'Number Deleted' => 3,
'Response Body' => ''
}
returns(expected, 'deletes manifest and segments') do
Fog::Storage[:rackspace].delete_static_large_object(@directory.identity, 'fog_large_object').body
end
end
tests("#put_object_manifest('foglargeobjecttests', 'fog_large_object', {'X-Object-Manifest' => 'foglargeobjecttests/fog_large_object/')").succeeds do
pending if Fog.mocking?
Fog::Storage[:rackspace].put_object_manifest(@directory.identity, 'fog_large_object', {'X-Object-Manifest' => "#{@directory.identity}/fog_large_object/"})
end
tests("#get_object streams segments only matching the specified prefix").succeeds do
pending if Fog.mocking?
Fog::Storage[:rackspace].get_object(@directory.identity, 'fog_large_object').body == ('x' * 6 * 1024 * 1024)
tests('multiple containers') do
tests('#put_static_obj_manifest').succeeds do
segments = [
{ :path => "#{ @segments[:b][:container] }/#{ @segments[:b][:name] }",
:etag => @segments[:b][:etag],
:size_bytes => @segments[:b][:size] },
{ :path => "#{ @segments[:d][:container] }/#{ @segments[:d][:name] }",
:etag => @segments[:d][:etag],
:size_bytes => @segments[:d][:size] }
]
Fog::Storage[:rackspace].put_static_obj_manifest(@directory2.identity, 'fog_large_object', segments)
end
tests('#head_object') do
etags = [@segments[:b][:etag], @segments[:d][:etag]]
etag = "\"#{ Digest::MD5.hexdigest(etags.join) }\"" # "\"9801a4cc4472896a1e975d03f0d2c3f8\""
content_length = (@segments[:b][:size] + @segments[:d][:size]).to_s
response = Fog::Storage[:rackspace].head_object(@directory2.identity, 'fog_large_object')
returns(etag, 'returns ETag computed from segments') { response.headers['Etag'] }
returns(content_length , 'returns Content-Length for all segments') { response.headers['Content-Length'] }
returns('True', 'returns X-Static-Large-Object header') { response.headers['X-Static-Large-Object'] }
end
tests('#get_object').succeeds do
expected = @segments[:b][:data] + @segments[:d][:data]
Fog::Storage[:rackspace].get_object(@directory2.identity, 'fog_large_object').body == expected
end
tests('#delete_static_large_object') do
expected = {
'Number Not Found' => 0,
'Response Status' => '200 OK',
'Errors' => [],
'Number Deleted' => 3,
'Response Body' => ''
}
returns(expected, 'deletes manifest and segments') do
Fog::Storage[:rackspace].delete_static_large_object(@directory2.identity, 'fog_large_object').body
end
end
end
tests("#head_object returns Etag that does not include manifest object in calculation").succeeds do
pending if Fog.mocking?
etags = []
etags << Digest::MD5.hexdigest('x' * 4 * 1024 * 1024) # => "44981362d3ba9b5bacaf017c2f29d355"
etags << Digest::MD5.hexdigest('x' * 2 * 1024 * 1024) # => "67b2f816a30e8956149b2d7beb479e51"
expected = Digest::MD5.hexdigest(etags.join) # => "0b348495a774eaa4d4c4bbf770820f84"
actual = Fog::Storage[:rackspace].head_object(@directory.identity, 'fog_large_object').headers['Etag']
actual.gsub('"', '') == expected # actual is returned in quotes "\"0b348495a774eaa4d4c4bbf770820f84"\"
end
end
tests("storing manifest object in a different container than the segments") do
tests("#put_object_manifest('foglargeobjecttests2', 'fog_large_object', {'X-Object-Manifest' => 'foglargeobjecttests/fog_large_object/'})").succeeds do
pending if Fog.mocking?
Fog::Storage[:rackspace].put_object_manifest(@directory2.identity, 'fog_large_object', {'X-Object-Manifest' => "#{@directory.identity}/fog_large_object/"})
end
tests("#get_object('foglargeobjecttests2', 'fog_large_object').body").succeeds do
pending if Fog.mocking?
Fog::Storage[:rackspace].get_object(@directory2.identity, 'fog_large_object').body == ('x' * 6 * 1024 * 1024)
end
end
unless Fog.mocking?
['fog_large_object', 'fog_large_object/1', 'fog_large_object/2', 'fog_large_object2/1'].each do |key|
@directory.files.new(:key => key).destroy
end
@directory2.files.new(:key => 'fog_large_object').destroy
end
end
tests('failure') do
tests("put_object_manifest")
tests('dynamic large object requests') do
pending if Fog.mocking?
tests('#put_dynamic_obj_manifest with missing container').raises(Fog::Storage::Rackspace::NotFound) do
Fog::Storage[:rackspace].put_dynamic_obj_manifest('fognoncontainer', 'fog_large_object')
end
end
tests('static large object requests') do
pending if Fog.mocking?
tests('upload test segments').succeeds do
Fog::Storage[:rackspace].put_object(@segments[:a][:container], @segments[:a][:name], @segments[:a][:data])
Fog::Storage[:rackspace].put_object(@segments[:b][:container], @segments[:b][:name], @segments[:b][:data])
end
tests('#put_static_obj_manifest with missing container').raises(Fog::Storage::Rackspace::NotFound) do
Fog::Storage[:rackspace].put_static_obj_manifest('fognoncontainer', 'fog_large_object', [])
end
tests('#put_static_obj_manifest with missing object') do
segments = [
{ :path => "#{ @segments[:c][:container] }/#{ @segments[:c][:name] }",
:etag => @segments[:c][:etag],
:size_bytes => @segments[:c][:size] }
]
expected = { 'Errors' => [[segments[0][:path], '404 Not Found']] }
error = nil
begin
Fog::Storage[:rackspace].put_static_obj_manifest(@directory.identity, 'fog_large_object', segments)
rescue => err
error = err
end
raises(Fog::Storage::Rackspace::BadRequest) do
raise error if error
end
expected['Errors'][0][0] = error.response_data['Errors'][0][0] rescue nil
returns(expected, 'returns error information') do
error.response_data
end
end
tests('#put_static_obj_manifest with invalid etag') do
segments = [
{ :path => "#{ @segments[:a][:container] }/#{ @segments[:a][:name] }",
:etag => @segments[:b][:etag],
:size_bytes => @segments[:a][:size] }
]
expected = { 'Errors' => [[segments[0][:path], 'Etag Mismatch']] }
error = nil
begin
Fog::Storage[:rackspace].put_static_obj_manifest(@directory.identity, 'fog_large_object', segments)
rescue => err
error = err
end
raises(Fog::Storage::Rackspace::BadRequest) do
raise error if error
end
expected['Errors'][0][0] = error.response_data['Errors'][0][0] rescue nil
returns(expected, 'returns error information') do
error.response_data
end
end
tests('#put_static_obj_manifest with invalid byte_size') do
segments = [
{ :path => "#{ @segments[:a][:container] }/#{ @segments[:a][:name] }",
:etag => @segments[:a][:etag],
:size_bytes => @segments[:b][:size] }
]
expected = { 'Errors' => [[segments[0][:path], 'Size Mismatch']] }
error = nil
begin
Fog::Storage[:rackspace].put_static_obj_manifest(@directory.identity, 'fog_large_object', segments)
rescue => err
error = err
end
raises(Fog::Storage::Rackspace::BadRequest) do
raise error if error
end
expected['Errors'][0][0] = error.response_data['Errors'][0][0] rescue nil
returns(expected, 'returns error information') do
error.response_data
end
end
tests('#delete_static_large_object with missing container').raises(Fog::Storage::Rackspace::NotFound) do
Fog::Storage[:rackspace].delete_static_large_object('fognoncontainer', 'fog_large_object')
end
tests('#delete_static_large_object with missing manifest').raises(Fog::Storage::Rackspace::NotFound) do
Fog::Storage[:rackspace].delete_static_large_object(@directory.identity, 'fog_non_object')
end
tests('#delete_static_large_object with missing segment') do
tests('#put_static_obj_manifest for segments :a and :b').succeeds do
segments = [
{ :path => "#{ @segments[:a][:container] }/#{ @segments[:a][:name] }",
:etag => @segments[:a][:etag],
:size_bytes => @segments[:a][:size] },
{ :path => "#{ @segments[:b][:container] }/#{ @segments[:b][:name] }",
:etag => @segments[:b][:etag],
:size_bytes => @segments[:b][:size] }
]
Fog::Storage[:rackspace].put_static_obj_manifest(@directory.identity, 'fog_large_object', segments)
end
tests('#delete_object segment :b').succeeds do
Fog::Storage[:rackspace].delete_object(@segments[:b][:container], @segments[:b][:name])
end
tests('#delete_static_large_object') do
expected = {
'Number Not Found' => 1,
'Response Status' => '200 OK',
'Errors' => [],
'Number Deleted' => 2,
'Response Body' => ''
}
returns(expected, 'deletes manifest and segment :a, and reports missing segment :b') do
Fog::Storage[:rackspace].delete_static_large_object(@directory.identity, 'fog_large_object').body
end
end
end
end
end

View file

@ -17,18 +17,18 @@ Shindo.tests('Fog::Storage[:rackspace] | object requests', ["rackspace"]) do
Fog::Storage[:rackspace].put_object('fogobjecttests', 'fog_object', lorem_file)
end
tests("#get_object('fogobjectests', 'fog_object')").returns(lorem_file.read) do
tests("#get_object('fogobjectests', 'fog_object')").succeeds do
pending if Fog.mocking?
Fog::Storage[:rackspace].get_object('fogobjecttests', 'fog_object').body
Fog::Storage[:rackspace].get_object('fogobjecttests', 'fog_object').body == lorem_file.read
end
tests("#get_object('fogobjecttests', 'fog_object', &block)").returns(lorem_file.read) do
tests("#get_object('fogobjecttests', 'fog_object', &block)").succeeds do
pending if Fog.mocking?
data = ''
Fog::Storage[:rackspace].get_object('fogobjecttests', 'fog_object') do |chunk, remaining_bytes, total_bytes|
data << chunk
end
data
data == lorem_file.read
end
tests("#head_object('fogobjectests', 'fog_object')").succeeds do
@ -70,40 +70,59 @@ Shindo.tests('Fog::Storage[:rackspace] | object requests', ["rackspace"]) do
storage = Fog::Storage::Rackspace.new(:rackspace_temp_url_key => "super_secret")
storage.extend RackspaceStorageHelpers
storage.override_path('/fake_version/fake_tenant')
object_url = storage.get_object_https_url('fogobjecttests', 'fog-object', expires_at)
object_url = storage.get_object_https_url('fogobjecttests', 'fog-object', expires_at)
object_url =~ /https:\/\/.*clouddrive.com\/[^\/]+\/[^\/]+\/fogobjecttests\/fog%2Dobject\?temp_url_sig=a24dd5fc955a57adce7d1b5bc4ec2c7660ab8396&temp_url_expires=1344149532/
end
tests("put_object with block") do
tests("#put_object('fogobjecttests', 'fog_object', &block)") do
pending if Fog.mocking?
pending if Fog.mocking?
tests("#put_object('fogobjecttests', 'fog_object', &block)").succeeds do
begin
file = lorem_file
buffer_size = file.size / 2 # chop it up into two buffers
buffer_size = file.stat.size / 2 # chop it up into two buffers
Fog::Storage[:rackspace].put_object('fogobjecttests', 'fog_block_object', nil) do
if file.pos < file.size
file.sysread(buffer_size)
else
""
end
file.read(buffer_size).to_s
end
ensure
file.close
end
end
tests("object successfully uploaded?").returns(lorem_file.read) do
pending if Fog.mocking?
Fog::Storage[:rackspace].get_object('fogobjecttests', 'fog_block_object').body
tests('#get_object').succeeds do
Fog::Storage[:rackspace].get_object('fogobjecttests', 'fog_block_object').body == lorem_file.read
end
tests("delete file").succeeds do
pending if Fog.mocking?
tests('#delete_object').succeeds do
Fog::Storage[:rackspace].delete_object('fogobjecttests', 'fog_block_object')
end
end
tests('#delete_multiple_objects') do
pending if Fog.mocking?
Fog::Storage[:rackspace].put_object('fogobjecttests', 'fog_object', lorem_file)
Fog::Storage[:rackspace].put_object('fogobjecttests', 'fog_object2', lorem_file)
Fog::Storage[:rackspace].directories.create(:key => 'fogobjecttests2')
Fog::Storage[:rackspace].put_object('fogobjecttests2', 'fog_object', lorem_file)
expected = {
"Number Not Found" => 0,
"Response Status" => "200 OK",
"Errors" => [],
"Number Deleted" => 2,
"Response Body" => ""
}
returns(expected, 'deletes multiple objects') do
Fog::Storage[:rackspace].delete_multiple_objects('fogobjecttests', ['fog_object', 'fog_object2']).body
end
returns(expected, 'deletes object and container') do
Fog::Storage[:rackspace].delete_multiple_objects(nil, ['fogobjecttests2/fog_object', 'fogobjecttests2']).body
end
end
end
tests('failure') do
@ -138,6 +157,42 @@ Shindo.tests('Fog::Storage[:rackspace] | object requests', ["rackspace"]) do
Fog::Storage[:rackspace].delete_object('fognoncontainer', 'fog_non_object')
end
tests('#delete_multiple_objects') do
pending if Fog.mocking?
expected = {
"Number Not Found" => 2,
"Response Status" => "200 OK",
"Errors" => [],
"Number Deleted" => 0,
"Response Body" => ""
}
returns(expected, 'reports missing objects') do
Fog::Storage[:rackspace].delete_multiple_objects('fogobjecttests', ['fog_non_object', 'fog_non_object2']).body
end
returns(expected, 'reports missing container') do
Fog::Storage[:rackspace].delete_multiple_objects('fognoncontainer', ['fog_non_object', 'fog_non_object2']).body
end
tests('deleting non-empty container') do
Fog::Storage[:rackspace].put_object('fogobjecttests', 'fog_object', lorem_file)
expected = {
"Number Not Found" => 0,
"Response Status" => "400 Bad Request",
"Errors" => [['fogobjecttests', '409 Conflict']],
"Number Deleted" => 1,
"Response Body" => ""
}
body = Fog::Storage[:rackspace].delete_multiple_objects(nil, ['fogobjecttests', 'fogobjecttests/fog_object']).body
expected['Errors'][0][0] = body['Errors'][0][0] rescue nil
returns(expected, 'deletes object but not container') { body }
end
end
end
unless Fog.mocking?

View file

@ -0,0 +1,83 @@
Shindo.tests('Fog::Rackspace::Service', ['rackspace']) do
tests('process_response') do
@service = Fog::Rackspace::Service.new
tests('nil').returns(nil) do
@service.send(:process_response, nil)
end
tests('response missing body').returns(nil) do
response = Excon::Response.new
response.body = nil
@service.send(:process_response, response)
end
tests('processes body').returns({'a'=>2, 'b'=>3}) do
response = Excon::Response.new
response.headers['Content-Type'] = "application/json"
response.body = "{\"a\":2,\"b\":3}"
@service.send(:process_response, response)
response.body
end
tests('process body with hash').returns({:a=>2, :b=>3}) do
response = Excon::Response.new
response.headers['Content-Type'] = "application/json"
response.body = {:a=>2, :b=>3}
@service.send(:process_response, response)
response.body
end
tests('handles malformed json').returns({}) do
response = Excon::Response.new
response.headers['Content-Type'] = "application/json"
response.body = "I am totally not json"
@service.send(:process_response, response)
response.body
end
end
tests('headers') do
# adding an implementation for auth_token to service instance. Normally this would come from a subclass.
def @service.auth_token
"my_auth_token"
end
HEADER_HASH = {
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'X-Auth-Token' => @service.auth_token
}.freeze
tests('without options').returns(HEADER_HASH) do
@service.send(:headers)
end
tests('with options not containing :header key').returns(HEADER_HASH) do
@service.send(:headers, {:a => 3})
end
tests('with options containing :header key').returns(HEADER_HASH.merge(:a => 3)) do
@service.send(:headers, :headers => {:a => 3})
end
end
tests('request_params') do
REQUEST_HASH = {
:path=>"/endpoint/my_service",
:headers=>{"Content-Type"=>"application/json", "Accept"=>"application/json", "X-Auth-Token"=>"my_auth_token"},
:host=>"fog.io"
}.freeze
uri = URI.parse("http://fog.io/endpoint")
@service.instance_variable_set("@uri", uri)
params = {:path => 'my_service'}
tests('returns request hash').returns(REQUEST_HASH) do
@service.send(:request_params, params)
end
end
end

View file

@ -98,6 +98,15 @@ Shindo.tests('Rackspace | Storage', ['rackspace']) do
returns(true, "uses custom endpoint") { (@service.instance_variable_get("@uri").host =~ /snet-/) != nil }
end
end
tests('reauthentication') do
pending if Fog.mocking?
@service = Fog::Storage::Rackspace.new
returns(true, "auth token populated") { !@service.send(:auth_token).nil? }
@service.instance_variable_set("@auth_token", "bad-token")
returns(204) { @service.head_containers.status }
end
tests('account').succeeds do
pending if Fog.mocking?