From 9af158aa9c7027520712bc292eb4da30e10f17c1 Mon Sep 17 00:00:00 2001 From: Wesley Beary Date: Fri, 7 Aug 2009 00:28:53 -0700 Subject: [PATCH] first pass at mocking setup --- lib/fog.rb | 12 ++ lib/fog/aws.rb | 7 + lib/fog/aws/ec2.rb | 147 +++++++------- lib/fog/aws/requests/s3/get_service.rb | 72 ++++--- lib/fog/aws/s3.rb | 61 +++--- lib/fog/aws/simpledb.rb | 53 +++-- lib/fog/connection.rb | 268 +++++++++++++------------ spec/spec_helper.rb | 1 + 8 files changed, 336 insertions(+), 285 deletions(-) diff --git a/lib/fog.rb b/lib/fog.rb index 1c9711bcc..a1e23040d 100644 --- a/lib/fog.rb +++ b/lib/fog.rb @@ -1 +1,13 @@ require "#{File.dirname(__FILE__)}/fog/aws" + +module Fog + + def self.mocking=(new_mocking) + @mocking = new_mocking + end + + def self.mocking? + !!@mocking + end + +end diff --git a/lib/fog/aws.rb b/lib/fog/aws.rb index 52275e8c5..7ef83664a 100644 --- a/lib/fog/aws.rb +++ b/lib/fog/aws.rb @@ -1,3 +1,10 @@ +require 'rubygems' +require 'base64' +require 'cgi' +require 'digest/md5' +require 'hmac-sha1' +require 'mime/types' + current_directory = File.dirname(__FILE__) require "#{current_directory}/aws/ec2" require "#{current_directory}/aws/simpledb" diff --git a/lib/fog/aws/ec2.rb b/lib/fog/aws/ec2.rb index 36e82c9cb..1e5aa6d3e 100644 --- a/lib/fog/aws/ec2.rb +++ b/lib/fog/aws/ec2.rb @@ -1,78 +1,3 @@ -require 'rubygems' -require 'base64' -require 'cgi' -require 'hmac-sha2' - -current_directory = File.dirname(__FILE__) -require "#{current_directory}/../connection" -require "#{current_directory}/../parser" -require "#{current_directory}/../response" - -parsers_directory = "#{current_directory}/parsers/ec2" -require "#{parsers_directory}/allocate_address" -require "#{parsers_directory}/attach_volume" -require "#{parsers_directory}/basic" -require "#{parsers_directory}/create_key_pair" -require "#{parsers_directory}/create_snapshot" -require "#{parsers_directory}/create_volume" -require "#{parsers_directory}/describe_addresses" -require "#{parsers_directory}/describe_availability_zones" -require "#{parsers_directory}/describe_images" -require "#{parsers_directory}/describe_instances" -require "#{parsers_directory}/describe_key_pairs" -require "#{parsers_directory}/describe_regions" -require "#{parsers_directory}/describe_security_groups" -require "#{parsers_directory}/describe_snapshots" -require "#{parsers_directory}/describe_volumes" -require "#{parsers_directory}/detach_volume" -require "#{parsers_directory}/get_console_output" -require "#{parsers_directory}/run_instances" -require "#{parsers_directory}/terminate_instances" - -requests_directory = "#{current_directory}/requests/ec2" -require "#{requests_directory}/allocate_address" -require "#{requests_directory}/associate_address" -require "#{requests_directory}/attach_volume" -require "#{requests_directory}/authorize_security_group_ingress" -# TODO: require "#{requests_directory}/bundle_instance" -# TODO: require "#{requests_directory}/cancel_bundle_task" -# TODO: require "#{requests_directory}/confirm_product_instance" -require "#{requests_directory}/create_key_pair" -require "#{requests_directory}/create_security_group" -require "#{requests_directory}/create_snapshot" -require "#{requests_directory}/create_volume" -require "#{requests_directory}/delete_key_pair" -require "#{requests_directory}/delete_security_group" -require "#{requests_directory}/delete_snapshot" -require "#{requests_directory}/delete_volume" -# TODO: require "#{requests_directory}/deregister_image" -require "#{requests_directory}/describe_addresses" -require "#{requests_directory}/describe_availability_zones" -# TODO: require "#{requests_directory}/describe_bundle_tasks" -# TODO: require "#{requests_directory}/describe_image_attribute" -require "#{requests_directory}/describe_images" -require "#{requests_directory}/describe_instances" -require "#{requests_directory}/describe_key_pairs" -require "#{requests_directory}/describe_regions" -# TODO: require "#{requests_directory}/describe_reserved_instances" -# TODO: require "#{requests_directory}/describe_reserved_instances_offerings" -require "#{requests_directory}/describe_security_groups" -require "#{requests_directory}/describe_snapshots" -require "#{requests_directory}/describe_volumes" -require "#{requests_directory}/detach_volume" -require "#{requests_directory}/disassociate_address" -require "#{requests_directory}/get_console_output" -# TODO: require "#{requests_directory}/modify_image_attribute" -# TODO: require "#{requests_directory}/monitor_instances" -# TODO: require "#{requests_directory}/purchase_reserved_instances_offering" -require "#{requests_directory}/reboot_instances" -# TODO: require "#{requests_directory}/register_image" -require "#{requests_directory}/release_address" -require "#{requests_directory}/revoke_security_group_ingress" -require "#{requests_directory}/run_instances" -require "#{requests_directory}/terminate_instances" -# TODO: require "#{requests_directory}/unmonitor_instances" - module Fog module AWS class EC2 @@ -95,6 +20,78 @@ module Fog # ==== Returns # * EC2 object with connection to aws. def initialize(options={}) + Fog.mocking = options[:mocking] || false + + current_directory = File.dirname(__FILE__) + require "#{current_directory}/../connection" + require "#{current_directory}/../parser" + require "#{current_directory}/../response" + + parsers_directory = "#{current_directory}/parsers/ec2" + require "#{parsers_directory}/allocate_address" + require "#{parsers_directory}/attach_volume" + require "#{parsers_directory}/basic" + require "#{parsers_directory}/create_key_pair" + require "#{parsers_directory}/create_snapshot" + require "#{parsers_directory}/create_volume" + require "#{parsers_directory}/describe_addresses" + require "#{parsers_directory}/describe_availability_zones" + require "#{parsers_directory}/describe_images" + require "#{parsers_directory}/describe_instances" + require "#{parsers_directory}/describe_key_pairs" + require "#{parsers_directory}/describe_regions" + require "#{parsers_directory}/describe_security_groups" + require "#{parsers_directory}/describe_snapshots" + require "#{parsers_directory}/describe_volumes" + require "#{parsers_directory}/detach_volume" + require "#{parsers_directory}/get_console_output" + require "#{parsers_directory}/run_instances" + require "#{parsers_directory}/terminate_instances" + + requests_directory = "#{current_directory}/requests/ec2" + require "#{requests_directory}/allocate_address" + require "#{requests_directory}/associate_address" + require "#{requests_directory}/attach_volume" + require "#{requests_directory}/authorize_security_group_ingress" + # TODO: require "#{requests_directory}/bundle_instance" + # TODO: require "#{requests_directory}/cancel_bundle_task" + # TODO: require "#{requests_directory}/confirm_product_instance" + require "#{requests_directory}/create_key_pair" + require "#{requests_directory}/create_security_group" + require "#{requests_directory}/create_snapshot" + require "#{requests_directory}/create_volume" + require "#{requests_directory}/delete_key_pair" + require "#{requests_directory}/delete_security_group" + require "#{requests_directory}/delete_snapshot" + require "#{requests_directory}/delete_volume" + # TODO: require "#{requests_directory}/deregister_image" + require "#{requests_directory}/describe_addresses" + require "#{requests_directory}/describe_availability_zones" + # TODO: require "#{requests_directory}/describe_bundle_tasks" + # TODO: require "#{requests_directory}/describe_image_attribute" + require "#{requests_directory}/describe_images" + require "#{requests_directory}/describe_instances" + require "#{requests_directory}/describe_key_pairs" + require "#{requests_directory}/describe_regions" + # TODO: require "#{requests_directory}/describe_reserved_instances" + # TODO: require "#{requests_directory}/describe_reserved_instances_offerings" + require "#{requests_directory}/describe_security_groups" + require "#{requests_directory}/describe_snapshots" + require "#{requests_directory}/describe_volumes" + require "#{requests_directory}/detach_volume" + require "#{requests_directory}/disassociate_address" + require "#{requests_directory}/get_console_output" + # TODO: require "#{requests_directory}/modify_image_attribute" + # TODO: require "#{requests_directory}/monitor_instances" + # TODO: require "#{requests_directory}/purchase_reserved_instances_offering" + require "#{requests_directory}/reboot_instances" + # TODO: require "#{requests_directory}/register_image" + require "#{requests_directory}/release_address" + require "#{requests_directory}/revoke_security_group_ingress" + require "#{requests_directory}/run_instances" + require "#{requests_directory}/terminate_instances" + # TODO: require "#{requests_directory}/unmonitor_instances" + @aws_access_key_id = options[:aws_access_key_id] @aws_secret_access_key = options[:aws_secret_access_key] @hmac = HMAC::SHA256.new(@aws_secret_access_key) diff --git a/lib/fog/aws/requests/s3/get_service.rb b/lib/fog/aws/requests/s3/get_service.rb index ee28f6b0b..150200d9f 100644 --- a/lib/fog/aws/requests/s3/get_service.rb +++ b/lib/fog/aws/requests/s3/get_service.rb @@ -1,29 +1,53 @@ -module Fog - module AWS - class S3 +unless Fog.mocking? + + module Fog + module AWS + class S3 + + # List information about S3 buckets for authorized user + # + # ==== Returns + # * response<~Fog::AWS::Response>: + # * body<~Hash>: + # * 'Buckets'<~Hash>: + # * 'Name'<~String> - Name of bucket + # * 'CreationTime'<~Time> - Timestamp of bucket creation + # * 'Owner'<~Hash>: + # * 'DisplayName'<~String> - Display name of bucket owner + # * 'ID'<~String> - Id of bucket owner + def get_service + request({ + :expects => 200, + :headers => {}, + :host => @host, + :method => 'GET', + :parser => Fog::Parsers::AWS::S3::GetService.new, + :url => @host + }) + end - # List information about S3 buckets for authorized user - # - # ==== Returns - # * response<~Fog::AWS::Response>: - # * body<~Hash>: - # * 'Buckets'<~Hash>: - # * 'Name'<~String> - Name of bucket - # * 'CreationTime'<~Time> - Timestamp of bucket creation - # * 'Owner'<~Hash>: - # * 'DisplayName'<~String> - Display name of bucket owner - # * 'ID'<~String> - Id of bucket owner - def get_service - request({ - :expects => 200, - :headers => {}, - :host => @host, - :method => 'GET', - :parser => Fog::Parsers::AWS::S3::GetService.new, - :url => @host - }) end - end end + +else + + module Fog + module AWS + class S3 + + def get_service + response = Fog::Response.new + response.headers['Status'] = 200 + response.body = { + 'Buckets' => [ { 'CreationDate' => Time.now - rand(3600), 'Name' => 'foggetservice' } ], + 'Owner' => { 'DisplayName' => 'owner', 'ID' => 'some_id'} + } + response + end + + end + end + end + end diff --git a/lib/fog/aws/s3.rb b/lib/fog/aws/s3.rb index 845ca8216..a12a3df0b 100644 --- a/lib/fog/aws/s3.rb +++ b/lib/fog/aws/s3.rb @@ -1,36 +1,3 @@ -require 'rubygems' -require 'base64' -require 'cgi' -require 'digest/md5' -require 'hmac-sha1' -require 'mime/types' - -current_directory = File.dirname(__FILE__) -require "#{current_directory}/../connection" -require "#{current_directory}/../parser" -require "#{current_directory}/../response" - -parsers_directory = "#{current_directory}/parsers/s3" -require "#{parsers_directory}/copy_object" -require "#{parsers_directory}/get_bucket" -require "#{parsers_directory}/get_bucket_location" -require "#{parsers_directory}/get_request_payment" -require "#{parsers_directory}/get_service" - -requests_directory = "#{current_directory}/requests/s3" -require "#{requests_directory}/copy_object" -require "#{requests_directory}/delete_bucket" -require "#{requests_directory}/delete_object" -require "#{requests_directory}/get_bucket" -require "#{requests_directory}/get_bucket_location" -require "#{requests_directory}/get_object" -require "#{requests_directory}/get_request_payment" -require "#{requests_directory}/get_service" -require "#{requests_directory}/head_object" -require "#{requests_directory}/put_bucket" -require "#{requests_directory}/put_object" -require "#{requests_directory}/put_request_payment" - module Fog module AWS class S3 @@ -53,6 +20,34 @@ module Fog # ==== Returns # * S3 object with connection to aws. def initialize(options={}) + Fog.mocking = options[:mocking] || false + + current_directory = File.dirname(__FILE__) + require "#{current_directory}/../connection" + require "#{current_directory}/../parser" + require "#{current_directory}/../response" + + parsers_directory = "#{current_directory}/parsers/s3" + require "#{parsers_directory}/copy_object" + require "#{parsers_directory}/get_bucket" + require "#{parsers_directory}/get_bucket_location" + require "#{parsers_directory}/get_request_payment" + require "#{parsers_directory}/get_service" + + requests_directory = "#{current_directory}/requests/s3" + require "#{requests_directory}/copy_object" + require "#{requests_directory}/delete_bucket" + require "#{requests_directory}/delete_object" + require "#{requests_directory}/get_bucket" + require "#{requests_directory}/get_bucket_location" + require "#{requests_directory}/get_object" + require "#{requests_directory}/get_request_payment" + require "#{requests_directory}/get_service" + require "#{requests_directory}/head_object" + require "#{requests_directory}/put_bucket" + require "#{requests_directory}/put_object" + require "#{requests_directory}/put_request_payment" + @aws_access_key_id = options[:aws_access_key_id] @aws_secret_access_key = options[:aws_secret_access_key] @hmac = HMAC::SHA1.new(@aws_secret_access_key) diff --git a/lib/fog/aws/simpledb.rb b/lib/fog/aws/simpledb.rb index 9c91d21c5..68e37facd 100644 --- a/lib/fog/aws/simpledb.rb +++ b/lib/fog/aws/simpledb.rb @@ -1,31 +1,3 @@ -require 'rubygems' -require 'base64' -require 'cgi' -require 'hmac-sha2' - -current_directory = File.dirname(__FILE__) -require "#{current_directory}/../connection" -require "#{current_directory}/../parser" -require "#{current_directory}/../response" - -parsers_directory = "#{current_directory}/parsers/simpledb" -require "#{parsers_directory}/basic" -require "#{parsers_directory}/domain_metadata" -require "#{parsers_directory}/get_attributes" -require "#{parsers_directory}/list_domains" -require "#{parsers_directory}/select" - -requests_directory = "#{current_directory}/requests/simpledb" -require "#{requests_directory}/batch_put_attributes" -require "#{requests_directory}/create_domain" -require "#{requests_directory}/delete_attributes" -require "#{requests_directory}/delete_domain" -require "#{requests_directory}/domain_metadata" -require "#{requests_directory}/get_attributes" -require "#{requests_directory}/list_domains" -require "#{requests_directory}/put_attributes" -require "#{requests_directory}/select" - module Fog module AWS class SimpleDB @@ -48,6 +20,31 @@ module Fog # ==== Returns # * SimpleDB object with connection to aws. def initialize(options={}) + Fog.mocking = options[:mocking] || false + + current_directory = File.dirname(__FILE__) + require "#{current_directory}/../connection" + require "#{current_directory}/../parser" + require "#{current_directory}/../response" + + parsers_directory = "#{current_directory}/parsers/simpledb" + require "#{parsers_directory}/basic" + require "#{parsers_directory}/domain_metadata" + require "#{parsers_directory}/get_attributes" + require "#{parsers_directory}/list_domains" + require "#{parsers_directory}/select" + + requests_directory = "#{current_directory}/requests/simpledb" + require "#{requests_directory}/batch_put_attributes" + require "#{requests_directory}/create_domain" + require "#{requests_directory}/delete_attributes" + require "#{requests_directory}/delete_domain" + require "#{requests_directory}/domain_metadata" + require "#{requests_directory}/get_attributes" + require "#{requests_directory}/list_domains" + require "#{requests_directory}/put_attributes" + require "#{requests_directory}/select" + @aws_access_key_id = options[:aws_access_key_id] @aws_secret_access_key = options[:aws_secret_access_key] @hmac = HMAC::SHA256.new(@aws_secret_access_key) diff --git a/lib/fog/connection.rb b/lib/fog/connection.rb index 924cebc4f..c4d1db659 100644 --- a/lib/fog/connection.rb +++ b/lib/fog/connection.rb @@ -5,145 +5,163 @@ require 'uri' require "#{File.dirname(__FILE__)}/response" -module Fog - class Connection +unless Fog.mocking? - def initialize(url) - @uri = URI.parse(url) - @connection = TCPSocket.open(@uri.host, @uri.port) - if @uri.scheme == 'https' - @ssl_context = OpenSSL::SSL::SSLContext.new - @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE - @connection = OpenSSL::SSL::SSLSocket.new(@connection, @ssl_context) - @connection.sync_close = true - @connection.connect - end - end + module Fog + class Connection - # Messages for nicer exceptions, from rfc2616 - def error_message(expected, actual) - @messages ||= { - 100 => 'Continue', - 101 => 'Switching Protocols', - 200 => 'OK', - 201 =>'Created', - 202 => 'Accepted', - 203 => 'Non-Authoritative Information', - 204 => 'No Content', - 205 => 'Reset Content', - 206 => 'Partial Content', - 300 => 'Multiple Choices', - 301 => 'Moved Permanently', - 302 => 'Found', - 303 => 'See Other', - 304 => 'Not Modified', - 305 => 'Use Proxy', - 307 => 'Temporary Redirect', - 400 => 'Bad Request', - 401 => 'Unauthorized', - 402 => 'Payment Required', - 403 => 'Forbidden', - 404 => 'Not Found', - 405 => 'Method Not Allowed', - 406 => 'Not Acceptable', - 407 => 'Proxy Authentication Required', - 408 => 'Request Timeout', - 409 => 'Conflict', - 410 => 'Gone', - 411 => 'Length Required', - 412 => 'Precondition Failed', - 413 => 'Request Entity Too Large', - 414 => 'Request-URI Too Long', - 415 => 'Unsupported Media Type', - 416 => 'Requested Range Not Satisfiable', - 417 => 'Expectation Failed', - 500 => 'Internal Server Error', - 501 => 'Not Implemented', - 502 => 'Bad Gateway', - 503 => 'Service Unavailable', - 504 => 'Gateway Timeout' - } - "Expected(#{expected} #{@messages[expected]}) <=> Got(#{actual} #{@messages[actual]})" - end - - def request(params) - params[:path] ||= '' - unless params[:path][0] == '/' - params[:path] = '/' + params[:path].to_s - end - if params[:query] && !params[:query].empty? - params[:path] << "?#{params[:query]}" - end - request = "#{params[:method]} #{params[:path]} HTTP/1.1\r\n" - params[:headers] ||= {} - params[:headers]['Host'] = params[:host] - if params[:body] - params[:headers]['Content-Length'] = params[:body].length - end - for key, value in params[:headers] - request << "#{key}: #{value}\r\n" - end - request << "\r\n#{params[:body]}" - @connection.write(request) - - response = Fog::Response.new - response.request = params - response.status = @connection.readline[9..11].to_i - while true - data = @connection.readline.chomp! - if data == "" - break + def initialize(url) + @uri = URI.parse(url) + @connection = TCPSocket.open(@uri.host, @uri.port) + if @uri.scheme == 'https' + @ssl_context = OpenSSL::SSL::SSLContext.new + @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE + @connection = OpenSSL::SSL::SSLSocket.new(@connection, @ssl_context) + @connection.sync_close = true + @connection.connect end - header = data.split(': ') - response.headers[capitalize(header[0])] = header[1] end - if params[:parser] - body = Nokogiri::XML::SAX::PushParser.new(params[:parser]) - else - body = '' + # Messages for nicer exceptions, from rfc2616 + def error_message(expected, actual) + @messages ||= { + 100 => 'Continue', + 101 => 'Switching Protocols', + 200 => 'OK', + 201 =>'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout' + } + "Expected(#{expected} #{@messages[expected]}) <=> Got(#{actual} #{@messages[actual]})" end - unless params[:method] == 'HEAD' - if response.headers['Content-Length'] - body << @connection.read(response.headers['Content-Length'].to_i) - elsif response.headers['Transfer-Encoding'] == 'chunked' - while true - # 2 == "/r/n".length - chunk_size = @connection.readline.chomp!.to_i(16) + 2 - chunk = @connection.read(chunk_size) - body << chunk[0...-2] - if chunk_size == 2 - break + def request(params) + params[:path] ||= '' + unless params[:path][0] == '/' + params[:path] = '/' + params[:path].to_s + end + if params[:query] && !params[:query].empty? + params[:path] << "?#{params[:query]}" + end + request = "#{params[:method]} #{params[:path]} HTTP/1.1\r\n" + params[:headers] ||= {} + params[:headers]['Host'] = params[:host] + if params[:body] + params[:headers]['Content-Length'] = params[:body].length + end + for key, value in params[:headers] + request << "#{key}: #{value}\r\n" + end + request << "\r\n#{params[:body]}" + @connection.write(request) + + response = Fog::Response.new + response.request = params + response.status = @connection.readline[9..11].to_i + while true + data = @connection.readline.chomp! + if data == "" + break + end + header = data.split(': ') + response.headers[capitalize(header[0])] = header[1] + end + + if params[:parser] + body = Nokogiri::XML::SAX::PushParser.new(params[:parser]) + else + body = '' + end + + unless params[:method] == 'HEAD' + if response.headers['Content-Length'] + body << @connection.read(response.headers['Content-Length'].to_i) + elsif response.headers['Transfer-Encoding'] == 'chunked' + while true + # 2 == "/r/n".length + chunk_size = @connection.readline.chomp!.to_i(16) + 2 + chunk = @connection.read(chunk_size) + body << chunk[0...-2] + if chunk_size == 2 + break + end end end end + + if params[:parser] + body.finish + response.body = params[:parser].response + else + response.body = body + end + + if params[:expects] && params[:expects] != response.status + raise(error_message(params[:expects], response.status)) + else + response + end end - if params[:parser] - body.finish - response.body = params[:parser].response - else - response.body = body + private + + def capitalize(header) + words = header.split('-') + header = '' + for word in words + header << word[0..0].upcase << word[1..-1] << '-' + end + header.chop! end - if params[:expects] && params[:expects] != response.status - raise(error_message(params[:expects], response.status)) - else - response - end end - - private - - def capitalize(header) - words = header.split('-') - header = '' - for word in words - header << word[0..0].upcase << word[1..-1] << '-' - end - header.chop! - end - end + +else + + module Fog + class Connection + + def initialize(url) + end + + def request(params) + end + + end + end + end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a0feaf57c..2012b11a0 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,6 +2,7 @@ require 'spec' current_directory = File.dirname(__FILE__) require "#{current_directory}/../lib/fog" +Fog.mocking = true Spec::Runner.configure do |config| end