diff --git a/fog.gemspec b/fog.gemspec index 038c69482..11ec2f6be 100644 --- a/fog.gemspec +++ b/fog.gemspec @@ -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' diff --git a/lib/fog/aws/models/rds/log_file.rb b/lib/fog/aws/models/rds/log_file.rb new file mode 100644 index 000000000..2b9e0aaf4 --- /dev/null +++ b/lib/fog/aws/models/rds/log_file.rb @@ -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 diff --git a/lib/fog/aws/models/rds/log_files.rb b/lib/fog/aws/models/rds/log_files.rb new file mode 100644 index 000000000..ea0c88466 --- /dev/null +++ b/lib/fog/aws/models/rds/log_files.rb @@ -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 diff --git a/lib/fog/aws/models/rds/snapshots.rb b/lib/fog/aws/models/rds/snapshots.rb index 44fcee37c..2a1d5edd1 100644 --- a/lib/fog/aws/models/rds/snapshots.rb +++ b/lib/fog/aws/models/rds/snapshots.rb @@ -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) diff --git a/lib/fog/aws/parsers/rds/describe_db_log_files.rb b/lib/fog/aws/parsers/rds/describe_db_log_files.rb new file mode 100644 index 000000000..da8998bdd --- /dev/null +++ b/lib/fog/aws/parsers/rds/describe_db_log_files.rb @@ -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 diff --git a/lib/fog/aws/parsers/rds/download_db_logfile_portion.rb b/lib/fog/aws/parsers/rds/download_db_logfile_portion.rb new file mode 100644 index 000000000..7ded7985c --- /dev/null +++ b/lib/fog/aws/parsers/rds/download_db_logfile_portion.rb @@ -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 diff --git a/lib/fog/aws/parsers/sts/assume_role.rb b/lib/fog/aws/parsers/sts/assume_role.rb new file mode 100644 index 000000000..2c28fce2c --- /dev/null +++ b/lib/fog/aws/parsers/sts/assume_role.rb @@ -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 diff --git a/lib/fog/aws/rds.rb b/lib/fog/aws/rds.rb index 1725a7abf..4e51e214b 100644 --- a/lib/fog/aws/rds.rb +++ b/lib/fog/aws/rds.rb @@ -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 diff --git a/lib/fog/aws/requests/rds/describe_db_log_files.rb b/lib/fog/aws/requests/rds/describe_db_log_files.rb new file mode 100644 index 000000000..0921403e5 --- /dev/null +++ b/lib/fog/aws/requests/rds/describe_db_log_files.rb @@ -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 diff --git a/lib/fog/aws/requests/rds/download_db_logfile_portion.rb b/lib/fog/aws/requests/rds/download_db_logfile_portion.rb new file mode 100644 index 000000000..4b2fae683 --- /dev/null +++ b/lib/fog/aws/requests/rds/download_db_logfile_portion.rb @@ -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 diff --git a/lib/fog/aws/requests/ses/send_raw_email.rb b/lib/fog/aws/requests/ses/send_raw_email.rb index 73c3a555c..3f7bfa867 100644 --- a/lib/fog/aws/requests/ses/send_raw_email.rb +++ b/lib/fog/aws/requests/ses/send_raw_email.rb @@ -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 diff --git a/lib/fog/aws/requests/storage/get_object.rb b/lib/fog/aws/requests/storage/get_object.rb index 79c3a3f8e..66026ce7c 100644 --- a/lib/fog/aws/requests/storage/get_object.rb +++ b/lib/fog/aws/requests/storage/get_object.rb @@ -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 diff --git a/lib/fog/aws/requests/sts/assume_role.rb b/lib/fog/aws/requests/sts/assume_role.rb new file mode 100644 index 000000000..afe393293 --- /dev/null +++ b/lib/fog/aws/requests/sts/assume_role.rb @@ -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 diff --git a/lib/fog/aws/ses.rb b/lib/fog/aws/ses.rb index 3d03c4ea8..0f9a489cb 100644 --- a/lib/fog/aws/ses.rb +++ b/lib/fog/aws/ses.rb @@ -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 diff --git a/lib/fog/aws/sts.rb b/lib/fog/aws/sts.rb index 8a8af8816..171fc14ac 100644 --- a/lib/fog/aws/sts.rb +++ b/lib/fog/aws/sts.rb @@ -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, diff --git a/lib/fog/bin/openstack.rb b/lib/fog/bin/openstack.rb index 8780b4160..8b837c2ab 100644 --- a/lib/fog/bin/openstack.rb +++ b/lib/fog/bin/openstack.rb @@ -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 diff --git a/lib/fog/google/compute.rb b/lib/fog/google/compute.rb index f6b53a78b..24f376ae9 100644 --- a/lib/fog/google/compute.rb +++ b/lib/fog/google/compute.rb @@ -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' diff --git a/lib/fog/google/models/compute/disk.rb b/lib/fog/google/models/compute/disk.rb new file mode 100644 index 000000000..8fbc927bc --- /dev/null +++ b/lib/fog/google/models/compute/disk.rb @@ -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 \ No newline at end of file diff --git a/lib/fog/google/models/compute/disks.rb b/lib/fog/google/models/compute/disks.rb new file mode 100644 index 000000000..4847c17b5 --- /dev/null +++ b/lib/fog/google/models/compute/disks.rb @@ -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 diff --git a/lib/fog/google/models/compute/image.rb b/lib/fog/google/models/compute/image.rb index c78c0ba84..48920059d 100644 --- a/lib/fog/google/models/compute/image.rb +++ b/lib/fog/google/models/compute/image.rb @@ -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 diff --git a/lib/fog/google/models/compute/server.rb b/lib/fog/google/models/compute/server.rb index 950ca7449..5ba4b807f 100644 --- a/lib/fog/google/models/compute/server.rb +++ b/lib/fog/google/models/compute/server.rb @@ -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 diff --git a/lib/fog/google/models/compute/servers.rb b/lib/fog/google/models/compute/servers.rb index 83700137f..df645a732 100644 --- a/lib/fog/google/models/compute/servers.rb +++ b/lib/fog/google/models/compute/servers.rb @@ -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? } diff --git a/lib/fog/google/requests/compute/get_disk.rb b/lib/fog/google/requests/compute/get_disk.rb index 801695aba..0d35a6f1f 100644 --- a/lib/fog/google/requests/compute/get_disk.rb +++ b/lib/fog/google/requests/compute/get_disk.rb @@ -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, diff --git a/lib/fog/google/requests/compute/insert_disk.rb b/lib/fog/google/requests/compute/insert_disk.rb index 6e7378362..a1a19c2ed 100644 --- a/lib/fog/google/requests/compute/insert_disk.rb +++ b/lib/fog/google/requests/compute/insert_disk.rb @@ -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 diff --git a/lib/fog/google/requests/compute/insert_server.rb b/lib/fog/google/requests/compute/insert_server.rb index ee089faad..39873ebfb 100644 --- a/lib/fog/google/requests/compute/insert_server.rb +++ b/lib/fog/google/requests/compute/insert_server.rb @@ -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) diff --git a/lib/fog/google/requests/storage/get_object.rb b/lib/fog/google/requests/storage/get_object.rb index f26018ebd..a2ac2879a 100644 --- a/lib/fog/google/requests/storage/get_object.rb +++ b/lib/fog/google/requests/storage/get_object.rb @@ -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 diff --git a/lib/fog/hp/requests/storage/get_object.rb b/lib/fog/hp/requests/storage/get_object.rb index af3a18d05..9695ae692 100644 --- a/lib/fog/hp/requests/storage/get_object.rb +++ b/lib/fog/hp/requests/storage/get_object.rb @@ -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 diff --git a/lib/fog/ibm/requests/compute/create_instance.rb b/lib/fog/ibm/requests/compute/create_instance.rb index 59d7e878a..cec5d0e5c 100644 --- a/lib/fog/ibm/requests/compute/create_instance.rb +++ b/lib/fog/ibm/requests/compute/create_instance.rb @@ -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 diff --git a/lib/fog/internet_archive/requests/storage/get_object.rb b/lib/fog/internet_archive/requests/storage/get_object.rb index 08da8e7ce..1cbeed717 100644 --- a/lib/fog/internet_archive/requests/storage/get_object.rb +++ b/lib/fog/internet_archive/requests/storage/get_object.rb @@ -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 diff --git a/lib/fog/metering.rb b/lib/fog/metering.rb new file mode 100644 index 000000000..3dc6244c6 --- /dev/null +++ b/lib/fog/metering.rb @@ -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 + diff --git a/lib/fog/openstack.rb b/lib/fog/openstack.rb index 774ebc8bd..f5c5d295e 100644 --- a/lib/fog/openstack.rb +++ b/lib/fog/openstack.rb @@ -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 = {}) diff --git a/lib/fog/openstack/metering.rb b/lib/fog/openstack/metering.rb new file mode 100644 index 000000000..e46c790dc --- /dev/null +++ b/lib/fog/openstack/metering.rb @@ -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 + diff --git a/lib/fog/openstack/models/metering/meter.rb b/lib/fog/openstack/models/metering/meter.rb new file mode 100644 index 000000000..e69de29bb diff --git a/lib/fog/openstack/models/metering/meters.rb b/lib/fog/openstack/models/metering/meters.rb new file mode 100644 index 000000000..e69de29bb diff --git a/lib/fog/openstack/models/metering/resource.rb b/lib/fog/openstack/models/metering/resource.rb new file mode 100644 index 000000000..9857cfe23 --- /dev/null +++ b/lib/fog/openstack/models/metering/resource.rb @@ -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 + diff --git a/lib/fog/openstack/models/metering/resources.rb b/lib/fog/openstack/models/metering/resources.rb new file mode 100644 index 000000000..6108b7bb9 --- /dev/null +++ b/lib/fog/openstack/models/metering/resources.rb @@ -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 diff --git a/lib/fog/openstack/network.rb b/lib/fog/openstack/network.rb index a39fc0535..3f0d95c02 100644 --- a/lib/fog/openstack/network.rb +++ b/lib/fog/openstack/network.rb @@ -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] || {} diff --git a/lib/fog/openstack/requests/metering/get_resource.rb b/lib/fog/openstack/requests/metering/get_resource.rb new file mode 100644 index 000000000..f398fea14 --- /dev/null +++ b/lib/fog/openstack/requests/metering/get_resource.rb @@ -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 diff --git a/lib/fog/openstack/requests/metering/get_samples.rb b/lib/fog/openstack/requests/metering/get_samples.rb new file mode 100644 index 000000000..f6cc9d8ae --- /dev/null +++ b/lib/fog/openstack/requests/metering/get_samples.rb @@ -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 diff --git a/lib/fog/openstack/requests/metering/get_statistics.rb b/lib/fog/openstack/requests/metering/get_statistics.rb new file mode 100644 index 000000000..c5e5650e4 --- /dev/null +++ b/lib/fog/openstack/requests/metering/get_statistics.rb @@ -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 diff --git a/lib/fog/openstack/requests/metering/list_meters.rb b/lib/fog/openstack/requests/metering/list_meters.rb new file mode 100644 index 000000000..59ad0ffd8 --- /dev/null +++ b/lib/fog/openstack/requests/metering/list_meters.rb @@ -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 diff --git a/lib/fog/openstack/requests/metering/list_resources.rb b/lib/fog/openstack/requests/metering/list_resources.rb new file mode 100644 index 000000000..8fdd0d8cb --- /dev/null +++ b/lib/fog/openstack/requests/metering/list_resources.rb @@ -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 diff --git a/lib/fog/rackspace.rb b/lib/fog/rackspace.rb index 7c996369d..5ecd5dafe 100644 --- a/lib/fog/rackspace.rb +++ b/lib/fog/rackspace.rb @@ -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 " " diff --git a/lib/fog/rackspace/block_storage.rb b/lib/fog/rackspace/block_storage.rb index 16ef199a3..e46a5cd97 100644 --- a/lib/fog/rackspace/block_storage.rb +++ b/lib/fog/rackspace/block_storage.rb @@ -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 diff --git a/lib/fog/rackspace/cdn.rb b/lib/fog/rackspace/cdn.rb index 3179499ae..95c9a0bc4 100644 --- a/lib/fog/rackspace/cdn.rb +++ b/lib/fog/rackspace/cdn.rb @@ -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 diff --git a/lib/fog/rackspace/compute.rb b/lib/fog/rackspace/compute.rb index 9c05cd87b..a6c0e10c1 100644 --- a/lib/fog/rackspace/compute.rb +++ b/lib/fog/rackspace/compute.rb @@ -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 diff --git a/lib/fog/rackspace/compute_v2.rb b/lib/fog/rackspace/compute_v2.rb index 882665b71..a86bba26f 100644 --- a/lib/fog/rackspace/compute_v2.rb +++ b/lib/fog/rackspace/compute_v2.rb @@ -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 diff --git a/lib/fog/rackspace/databases.rb b/lib/fog/rackspace/databases.rb index cd9a7ff08..824e4a7eb 100644 --- a/lib/fog/rackspace/databases.rb +++ b/lib/fog/rackspace/databases.rb @@ -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 diff --git a/lib/fog/rackspace/dns.rb b/lib/fog/rackspace/dns.rb index 9165ab47e..5ee2f0ea5 100644 --- a/lib/fog/rackspace/dns.rb +++ b/lib/fog/rackspace/dns.rb @@ -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 diff --git a/lib/fog/rackspace/identity.rb b/lib/fog/rackspace/identity.rb index 6ea9689af..d1048a893 100644 --- a/lib/fog/rackspace/identity.rb +++ b/lib/fog/rackspace/identity.rb @@ -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'] diff --git a/lib/fog/rackspace/load_balancers.rb b/lib/fog/rackspace/load_balancers.rb index 895bc0ed9..b6c7545f4 100644 --- a/lib/fog/rackspace/load_balancers.rb +++ b/lib/fog/rackspace/load_balancers.rb @@ -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 diff --git a/lib/fog/rackspace/models/identity/users.rb b/lib/fog/rackspace/models/identity/users.rb index 526629f37..646dadf31 100644 --- a/lib/fog/rackspace/models/identity/users.rb +++ b/lib/fog/rackspace/models/identity/users.rb @@ -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 diff --git a/lib/fog/rackspace/requests/storage/delete_multiple_objects.rb b/lib/fog/rackspace/requests/storage/delete_multiple_objects.rb new file mode 100644 index 000000000..daf646aa9 --- /dev/null +++ b/lib/fog/rackspace/requests/storage/delete_multiple_objects.rb @@ -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] 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 [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 diff --git a/lib/fog/rackspace/requests/storage/delete_static_large_object.rb b/lib/fog/rackspace/requests/storage/delete_static_large_object.rb new file mode 100644 index 000000000..8c556d27c --- /dev/null +++ b/lib/fog/rackspace/requests/storage/delete_static_large_object.rb @@ -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 [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 + diff --git a/lib/fog/rackspace/requests/storage/get_object.rb b/lib/fog/rackspace/requests/storage/get_object.rb index 2353e6fcd..374080138 100644 --- a/lib/fog/rackspace/requests/storage/get_object.rb +++ b/lib/fog/rackspace/requests/storage/get_object.rb @@ -23,7 +23,9 @@ module Fog :expects => 200, :method => 'GET', :path => "#{Fog::Rackspace.escape(container)}/#{Fog::Rackspace.escape(object)}" - }), false) + }), + false, + &block) end end diff --git a/lib/fog/rackspace/requests/storage/put_dynamic_obj_manifest.rb b/lib/fog/rackspace/requests/storage/put_dynamic_obj_manifest.rb new file mode 100644 index 000000000..58dd15ec0 --- /dev/null +++ b/lib/fog/rackspace/requests/storage/put_dynamic_obj_manifest.rb @@ -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 ("/") + # 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") "/" 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 diff --git a/lib/fog/rackspace/requests/storage/put_object_manifest.rb b/lib/fog/rackspace/requests/storage/put_object_manifest.rb index 5ed20a1ed..9fe419f09 100644 --- a/lib/fog/rackspace/requests/storage/put_object_manifest.rb +++ b/lib/fog/rackspace/requests/storage/put_object_manifest.rb @@ -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 ("/") - # 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") "/" 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 diff --git a/lib/fog/rackspace/requests/storage/put_static_obj_manifest.rb b/lib/fog/rackspace/requests/storage/put_static_obj_manifest.rb new file mode 100644 index 000000000..1b9df0f3c --- /dev/null +++ b/lib/fog/rackspace/requests/storage/put_static_obj_manifest.rb @@ -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] 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 + + diff --git a/lib/fog/rackspace/service.rb b/lib/fog/rackspace/service.rb index 6cc66fe47..8649cf273 100644 --- a/lib/fog/rackspace/service.rb +++ b/lib/fog/rackspace/service.rb @@ -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 diff --git a/lib/fog/rackspace/storage.rb b/lib/fog/rackspace/storage.rb index 1b0f47f2f..f48f9e1a8 100644 --- a/lib/fog/rackspace/storage.rb +++ b/lib/fog/rackspace/storage.rb @@ -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 diff --git a/lib/fog/riakcs/provisioning.rb b/lib/fog/riakcs/provisioning.rb index 88765c54b..232e0120d 100644 --- a/lib/fog/riakcs/provisioning.rb +++ b/lib/fog/riakcs/provisioning.rb @@ -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 diff --git a/lib/fog/riakcs/usage.rb b/lib/fog/riakcs/usage.rb index 51df897f0..82cf209dd 100644 --- a/lib/fog/riakcs/usage.rb +++ b/lib/fog/riakcs/usage.rb @@ -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 diff --git a/tests/aws/requests/rds/helper.rb b/tests/aws/requests/rds/helper.rb index 095a750b3..a78f99aa3 100644 --- a/tests/aws/requests/rds/helper.rb +++ b/tests/aws/requests/rds/helper.rb @@ -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, diff --git a/tests/aws/requests/rds/log_file_tests.rb b/tests/aws/requests/rds/log_file_tests.rb new file mode 100644 index 000000000..ba80fa998 --- /dev/null +++ b/tests/aws/requests/rds/log_file_tests.rb @@ -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 diff --git a/tests/aws/requests/sts/assume_role_tests.rb b/tests/aws/requests/sts/assume_role_tests.rb new file mode 100644 index 000000000..3b8fa3e51 --- /dev/null +++ b/tests/aws/requests/sts/assume_role_tests.rb @@ -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 diff --git a/tests/openstack/requests/metering/meter_tests.rb b/tests/openstack/requests/metering/meter_tests.rb new file mode 100644 index 000000000..1ae6c3fe5 --- /dev/null +++ b/tests/openstack/requests/metering/meter_tests.rb @@ -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 diff --git a/tests/openstack/requests/metering/resource_tests.rb b/tests/openstack/requests/metering/resource_tests.rb new file mode 100644 index 000000000..fc1c85d88 --- /dev/null +++ b/tests/openstack/requests/metering/resource_tests.rb @@ -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 diff --git a/tests/rackspace/block_storage_tests.rb b/tests/rackspace/block_storage_tests.rb index 6a0fb65f8..49ba62aed 100644 --- a/tests/rackspace/block_storage_tests.rb +++ b/tests/rackspace/block_storage_tests.rb @@ -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 \ No newline at end of file diff --git a/tests/rackspace/compute_tests.rb b/tests/rackspace/compute_tests.rb index a5d334ce8..638ed5776 100644 --- a/tests/rackspace/compute_tests.rb +++ b/tests/rackspace/compute_tests.rb @@ -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 diff --git a/tests/rackspace/compute_v2_tests.rb b/tests/rackspace/compute_v2_tests.rb index a35af0ad2..c8f33bebd 100644 --- a/tests/rackspace/compute_v2_tests.rb +++ b/tests/rackspace/compute_v2_tests.rb @@ -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 \ No newline at end of file diff --git a/tests/rackspace/databases_tests.rb b/tests/rackspace/databases_tests.rb index 423bd8f40..94b1fe39a 100644 --- a/tests/rackspace/databases_tests.rb +++ b/tests/rackspace/databases_tests.rb @@ -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 diff --git a/tests/rackspace/dns_tests.rb b/tests/rackspace/dns_tests.rb index d96a473cc..243886c48 100644 --- a/tests/rackspace/dns_tests.rb +++ b/tests/rackspace/dns_tests.rb @@ -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? diff --git a/tests/rackspace/identity_tests.rb b/tests/rackspace/identity_tests.rb new file mode 100644 index 000000000..ca3ad478a --- /dev/null +++ b/tests/rackspace/identity_tests.rb @@ -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 \ No newline at end of file diff --git a/tests/rackspace/load_balancer_tests.rb b/tests/rackspace/load_balancer_tests.rb index ac935d609..ffbfaee1b 100644 --- a/tests/rackspace/load_balancer_tests.rb +++ b/tests/rackspace/load_balancer_tests.rb @@ -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 diff --git a/tests/rackspace/rackspace_tests.rb b/tests/rackspace/rackspace_tests.rb index 3b43e6c44..a9ee31e06 100644 --- a/tests/rackspace/rackspace_tests.rb +++ b/tests/rackspace/rackspace_tests.rb @@ -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 \ No newline at end of file diff --git a/tests/rackspace/requests/identity/user_tests.rb b/tests/rackspace/requests/identity/user_tests.rb index 62803beb0..20026474b 100644 --- a/tests/rackspace/requests/identity/user_tests.rb +++ b/tests/rackspace/requests/identity/user_tests.rb @@ -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'] diff --git a/tests/rackspace/requests/storage/large_object_tests.rb b/tests/rackspace/requests/storage/large_object_tests.rb index e4b6be1af..3131e358b 100644 --- a/tests/rackspace/requests/storage/large_object_tests.rb +++ b/tests/rackspace/requests/storage/large_object_tests.rb @@ -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 diff --git a/tests/rackspace/requests/storage/object_tests.rb b/tests/rackspace/requests/storage/object_tests.rb index 196b60ca3..afc264447 100644 --- a/tests/rackspace/requests/storage/object_tests.rb +++ b/tests/rackspace/requests/storage/object_tests.rb @@ -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? diff --git a/tests/rackspace/service_tests.rb b/tests/rackspace/service_tests.rb new file mode 100644 index 000000000..d057ae000 --- /dev/null +++ b/tests/rackspace/service_tests.rb @@ -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 diff --git a/tests/rackspace/storage_tests.rb b/tests/rackspace/storage_tests.rb index 0d2f04d0f..fc3dc6b16 100644 --- a/tests/rackspace/storage_tests.rb +++ b/tests/rackspace/storage_tests.rb @@ -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?