dynamodb 0.0.2 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,126 @@
1
+ # Copyright 2011-2012 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
4
+ # may not use this file except in compliance with the License. A copy of
5
+ # the License is located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the "license" file accompanying this file. This file is
10
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
+ # ANY KIND, either express or implied. See the License for the specific
12
+ # language governing permissions and limitations under the License.
13
+
14
+ require 'net/http'
15
+ require 'net/https'
16
+ require 'openssl'
17
+
18
+ class Net::HTTP::ConnectionPool
19
+
20
+ # Used by Net::HTTP::ConnectionPool to wrap Net::HTTP::Session objects.
21
+ # Users should never need to interact with these session wrappers.
22
+ # @private
23
+ class Session
24
+
25
+ # @param [Net::HTTPSession] http_session
26
+ # @param [String] key
27
+ def initialize http_session, key
28
+ @http_session = http_session
29
+ @key = key
30
+ @created_at = Time.now
31
+ @last_used_at = nil
32
+ end
33
+
34
+ # @return [Net::HTTPSession]
35
+ attr_reader :http_session
36
+
37
+ # @return [String]
38
+ attr_reader :key
39
+
40
+ # @return [Time]
41
+ attr_reader :created_at
42
+
43
+ # @return [Time]
44
+ attr_reader :last_used_at
45
+
46
+ # @param [Integer] timeout Number of seconds before Net::HTTP should
47
+ # timeout while waiting to read a response.
48
+ def read_timeout= timeout
49
+ http_session.read_timeout = timeout
50
+ end
51
+
52
+ # Makes a HTTP request. See Net::HTTPSession#request documentation
53
+ # from the Ruby standard library for information about argments.
54
+ #
55
+ # connection.request(Net::HTTP::Get.new('/')) do |response|
56
+ # # Parse the response (status, headers and body) here.
57
+ # # You should be done with the response by the end of the block.
58
+ # end
59
+ #
60
+ # @yield [response]
61
+ # @yieldparam [Net::HTTPResponse] response
62
+ # @return [nil]
63
+ def request *args, &block
64
+ http_session.request(*args, &block)
65
+ @last_used_at = Time.now
66
+ nil
67
+ end
68
+
69
+ # Attempts to cleanly close the HTTP session.
70
+ # @return [nil]
71
+ def finish
72
+ begin
73
+ http_session.finish if http_session.started?
74
+ rescue IOError
75
+ end
76
+ nil
77
+ end
78
+
79
+ class << self
80
+
81
+ # Starts a new HTTP session and returns it wrapped in a {Session} object.
82
+ # @param [Connection] connection
83
+ # @param [Hash] options
84
+ # @option options [Integer] :open_timeout (15) The number of seconds to
85
+ # wait while trying to open the HTTP session before timeing out.
86
+ # @option options [Logger] :debug_logger HTTP wire traces are logged
87
+ # here when specified.
88
+ # @return [Session]
89
+ def start connection, options = {}
90
+
91
+ http_args = []
92
+ http_args << connection.host
93
+ http_args << connection.port
94
+ if connection.proxy?
95
+ http_args << connection.proxy_address
96
+ http_args << connection.proxy_port
97
+ http_args << connection.proxy_user
98
+ http_args << connection.proxy_password
99
+ end
100
+
101
+ http = Net::HTTP.new(*http_args)
102
+ http.set_debug_output(options[:debug_logger]) if options[:debug_logger]
103
+ http.open_timeout = options[:open_timeout] || 15
104
+
105
+ if connection.ssl?
106
+ http.use_ssl = true
107
+ if connection.ssl_verify_peer?
108
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
109
+ http.ca_file = connection.ssl_ca_file if connection.ssl_ca_file
110
+ http.ca_path = connection.ssl_ca_path if connection.ssl_ca_path
111
+ else
112
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
113
+ end
114
+ else
115
+ http.use_ssl = false
116
+ end
117
+
118
+ http.start
119
+
120
+ Session.new(http, connection.key)
121
+
122
+ end
123
+
124
+ end
125
+ end
126
+ end
@@ -1,131 +1,28 @@
1
1
  require 'spec_helper'
2
- require 'benchmark'
3
2
 
4
3
  describe DynamoDB::Connection do
5
- let(:token_service) {
6
- stub(:credentials =>
7
- DynamoDB::Credentials.new(
8
- "access_key_id",
9
- "secret_access_key",
10
- "session_token"
11
- )
12
- )
13
- }
14
-
15
- describe "#initialize" do
16
- it "can be initialized with a token service" do
17
- DynamoDB::Connection.new(:token_service => token_service)
18
- end
19
-
20
- it "can be initialized with an access key" do
21
- DynamoDB::Connection.new(
22
- :access_key_id => "id",
23
- :secret_access_key => "secret"
24
- )
25
- end
26
-
27
- context "no token service was provided" do
28
- it "requires an access_key_id and secret_access_key" do
29
- lambda {
30
- DynamoDB::Connection.new
31
- }.should raise_error(ArgumentError)
32
- end
33
- end
34
- end
35
-
36
4
  describe "#post" do
37
- let(:connection) { DynamoDB::Connection.new(:token_service => token_service) }
38
-
39
- before do
40
- Time.stub(:now => Time.at(1332635893)) # Sat Mar 24 20:38:13 -0400 2012
41
-
42
- @url = "https://dynamodb.us-east-1.amazonaws.com/"
43
- @headers = {
44
- 'Content-Type' => 'application/x-amz-json-1.0',
45
- 'Host' => 'dynamodb.us-east-1.amazonaws.com',
46
- 'X-Amz-Date' => 'Sun, 25 Mar 2012 00:38:13 GMT',
47
- 'X-Amz-Security-Token' => 'session_token',
48
- 'X-Amzn-Authorization' => 'AWS3 AWSAccessKeyId=access_key_id,Algorithm=HmacSHA256,Signature=2xa6v0WW+980q8Hgt+ym3/7C0D1DlkueGMugi1NWE+o='
49
- }
50
- end
5
+ let(:connection) { DynamoDB::Connection.new(:access_key_id => "id", :secret_access_key => "secret") }
51
6
 
52
7
  it "signs and posts a request" do
53
- @headers['X-Amz-Target'] = 'DynamoDB_20111205.ListTables'
54
- stub_request(:post, @url).
55
- with(
56
- :body => "{}",
57
- :headers => @headers
58
- ).
59
- to_return(
60
- :status => 200,
61
- :body => '{"TableNames":["example"]}',
62
- :headers => {}
63
- )
64
-
8
+ stub_request(:post, "https://dynamodb.us-east-1.amazonaws.com/").
9
+ to_return(status: 200, body: MultiJson.encode({"TableNames" => ["example"]}))
65
10
  result = connection.post :ListTables
66
- result.should == {"TableNames" => ["example"]}
67
- end
68
-
69
- it "type casts response when Query" do
70
- stub_request(:post, @url).
71
- to_return(
72
- :status => 200,
73
- :body => "{}",
74
- :headers => {}
75
- )
76
-
77
- response = connection.post :Query, :TableName => "people", :HashKeyId => {:N => "1"}
78
- response.should be_a_kind_of(DynamoDB::Response)
11
+ result.data.should == {"TableNames" => ["example"]}
79
12
  end
80
13
 
81
- it "type casts response when GetItem" do
82
- stub_request(:post, @url).
83
- to_return(
84
- :status => 200,
85
- :body => "{}",
86
- :headers => {}
87
- )
88
-
89
- response = connection.post :GetItem, :TableName => "people", :Key => {:HashKeyElement => {:N => "1"}, :RangeKeyElement => {:N => 2}}
90
- response.should be_a_kind_of(DynamoDB::Response)
91
- end
92
-
93
- context "when a failure occurs" do
94
- it "raises an error with the response attached" do
95
- stub_request(:post, @url).to_return(:status => 500, :body => "Failed for some reason.")
96
- error = nil
97
- begin
98
- connection.post :Query, :TableName => "people", :HashKeyId => {:N => "1"}
99
- rescue => e
100
- error = e
101
- end
102
- error.should be_an_instance_of(DynamoDB::ServerError)
103
- error.response.code.should == 500
104
- error.message.should == "500: Failed for some reason."
105
- end
106
- end
107
-
108
- context "when the connection fails" do
109
- it "raises a server error" do
110
- stub_request(:post, @url).to_return(:status => 0, :body => "")
111
- error = nil
112
- begin
113
- connection.post :Query, :TableName => "people", :HashKeyId => {:N => "1"}
114
- rescue => e
115
- error = e
116
- end
117
- error.should be_an_instance_of(DynamoDB::ServerError)
118
- error.response.code.should == 0
119
- end
14
+ it "creates a SuccessResponse when 200" do
15
+ stub_request(:post, "https://dynamodb.us-east-1.amazonaws.com/").
16
+ to_return(status: 200, body: "")
17
+ result = connection.post :ListTables
18
+ result.should be_a_kind_of(DynamoDB::SuccessResponse)
120
19
  end
121
20
 
122
- context "when the connection times out" do
123
- it "raises a DynamoDB::TimeoutError" do
124
- stub_request(:post, @url).to_timeout
125
- expect {
126
- connection.post :Query, :TableName => "people", :HashKeyId => {:N => "1"}
127
- }.to raise_error(DynamoDB::TimeoutError)
128
- end
21
+ it "creates a FailureResponse when 400" do
22
+ stub_request(:post, "https://dynamodb.us-east-1.amazonaws.com/").
23
+ to_return(status: 400, body: "")
24
+ result = connection.post :ListTables
25
+ result.should be_a_kind_of(DynamoDB::FailureResponse)
129
26
  end
130
27
  end
131
28
  end
@@ -0,0 +1,27 @@
1
+ require "spec_helper"
2
+
3
+ describe DynamoDB::FailureResponse do
4
+ describe "#error" do
5
+ it "returns a ClientError if the HTTP response code is between 400 and 499" do
6
+ http_response = stub("Response", code: "401", message: "Not authorized", body: "Error details")
7
+ response = DynamoDB::FailureResponse.new(http_response)
8
+ response.error.should be_an_instance_of(DynamoDB::ClientError)
9
+ response.error.message.should == "401: Not authorized"
10
+ response.body.should == "Error details"
11
+ end
12
+
13
+ it "returns a ServerError if the HTTP response code is between 500 and 599" do
14
+ http_response = stub("Response", code: "500", message: "Internal server error", body: "")
15
+ response = DynamoDB::FailureResponse.new(http_response)
16
+ response.error.should be_an_instance_of(DynamoDB::ServerError)
17
+ response.error.message.should == "500: Internal server error"
18
+ end
19
+
20
+ it "returns a ServerError otherwise" do
21
+ http_response = stub("Response", code: "0", message: "")
22
+ response = DynamoDB::FailureResponse.new(http_response)
23
+ response.error.should be_an_instance_of(DynamoDB::ServerError)
24
+ response.error.message.should == "0: "
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,71 @@
1
+ require "spec_helper"
2
+
3
+ describe DynamoDB::HttpHandler do
4
+ describe "#handle" do
5
+ let(:http_handler) { DynamoDB::HttpHandler.new }
6
+ let(:request) {
7
+ stub(DynamoDB::Request,
8
+ uri: URI("https://dynamo.local/"),
9
+ headers: {},
10
+ body: "{}"
11
+ )
12
+ }
13
+
14
+ it "performs an HTTP request given a DynamoDB::Request" do
15
+ http_request = stub_request(:post, "https://dynamo.local/").to_return(status: 200)
16
+ http_handler.handle(request)
17
+ http_request.should have_been_requested
18
+ end
19
+
20
+ it "returns a DynamoDB::SuccessResponse for successes" do
21
+ stub_request(:post, "https://dynamo.local/").to_return(status: 200)
22
+
23
+ response = http_handler.handle(request)
24
+ response.should be_an_instance_of(DynamoDB::SuccessResponse)
25
+ response.should be_success
26
+ end
27
+
28
+ it "returns a DynamoDB::Response for failures" do
29
+ stub_request(:post, "https://dynamo.local/").to_return(status: 500, body: "Server errors")
30
+
31
+ response = http_handler.handle(request)
32
+ response.should be_an_instance_of(DynamoDB::FailureResponse)
33
+ response.should_not be_success
34
+ end
35
+
36
+ it "returns a DynamoDB::Response for network errors" do
37
+ error = Errno::ECONNRESET.new
38
+ stub_request(:post, "https://dynamo.local/").to_raise(error)
39
+
40
+ response = http_handler.handle(request)
41
+ response.should be_an_instance_of(DynamoDB::FailureResponse)
42
+ response.should_not be_success
43
+ response.error.should == error
44
+ response.body.should be_nil
45
+ end
46
+
47
+ it "respects a custom timeout option (set on initialize)" do
48
+ stub_request(:post, "https://dynamo.local/").to_return(status: 200)
49
+ connection = Net::HTTP::ConnectionPool::Connection.any_instance
50
+ connection.should_receive(:read_timeout=).with(5)
51
+ http_handler.handle(request)
52
+ end
53
+ end
54
+
55
+ describe "#build_http_request" do
56
+ it "converts a DynamoDB::Request into a Net::HTTP::Post" do
57
+ request = stub(DynamoDB::Request,
58
+ uri: URI("https://dynamo.local/"),
59
+ headers: {"content-type" => "application/json"},
60
+ body: "POST body"
61
+ )
62
+
63
+ http_handler = DynamoDB::HttpHandler.new
64
+ http_request = http_handler.build_http_request(request)
65
+ http_request.should be_an_instance_of(Net::HTTP::Post)
66
+ http_request.path.should == "https://dynamo.local/"
67
+ http_request["content-type"].should == "application/json"
68
+ http_request.body.should == "POST body"
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,31 @@
1
+ require "spec_helper"
2
+
3
+ describe DynamoDB::Request do
4
+ let(:uri) { URI("https://dynamodb.us-east-1.amazonaws.com/") }
5
+ let(:credentials) { DynamoDB::Credentials.new("access_key_id", "secret_access_key") }
6
+ let(:data) { {} }
7
+ let(:request) {
8
+ DynamoDB::Request.new(
9
+ uri: uri,
10
+ api_version: "DynamoDB_20111205",
11
+ credentials: credentials,
12
+ data: data,
13
+ operation: "ListTables",
14
+ )
15
+ }
16
+
17
+ it "signs the request" do
18
+ Time.stub(now: Time.parse("20130508T201304Z"))
19
+
20
+ request.headers.should == {
21
+ "content-type"=>"application/x-amz-json-1.0",
22
+ "x-amz-target"=>"DynamoDB_20111205.ListTables",
23
+ "content-length"=>2,
24
+ "user-agent"=>"groupme/dynamodb",
25
+ "host"=>"dynamodb.us-east-1.amazonaws.com",
26
+ "x-amz-date"=>"20130508T201304Z",
27
+ "x-amz-content-sha256"=>"44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
28
+ "authorization"=>"AWS4-HMAC-SHA256 Credential=access_key_id/20130508/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;user-agent;x-amz-content-sha256;x-amz-date;x-amz-target, Signature=52fab5c720b35ce247f8388c5c8f66b300074ae88b666985ee26ca70d923be1d"
29
+ }
30
+ end
31
+ end
@@ -0,0 +1,93 @@
1
+ require 'spec_helper'
2
+
3
+ describe DynamoDB::SuccessResponse do
4
+ describe "#hash_key_element" do
5
+ it "returns the typecast value of HashKeyElement" do
6
+ body = {
7
+ "LastEvaluatedKey" => {
8
+ "HashKeyElement" => {"N" => "1"},
9
+ "RangeKeyElement" => {"N" => "1501"}
10
+ }
11
+ }
12
+ http_response = stub(Net::HTTPResponse, body: MultiJson.dump(body))
13
+ response = DynamoDB::SuccessResponse.new(http_response)
14
+ response.hash_key_element.should == 1
15
+ end
16
+ end
17
+
18
+ describe "#range_key_element" do
19
+ it "returns the typecast value of RangeKeyElement" do
20
+ body = {
21
+ "LastEvaluatedKey" => {
22
+ "HashKeyElement" => {"N" => "1"},
23
+ "RangeKeyElement" => {"N" => "1501"}
24
+ }
25
+ }
26
+ http_response = stub(Net::HTTPResponse, body: MultiJson.dump(body))
27
+ response = DynamoDB::SuccessResponse.new(http_response)
28
+ response.range_key_element.should == 1501
29
+ end
30
+ end
31
+
32
+ describe "#items" do
33
+ it "returns type-casted entries from the 'Items' key" do
34
+ body = {
35
+ "Items" => [
36
+ {
37
+ "name" => {"S" => "John Smith"},
38
+ "created_at" => {"N" => "1321564309.99428"},
39
+ "disabled" => {"N" => "0"},
40
+ "group_id" => {"N" => "1"},
41
+ "person_id" => {"N" => "1500"}
42
+ },
43
+ {
44
+ "name" => {"S" => "Jane Smith"},
45
+ "created_at" => {"N" => "1321564309.99428"},
46
+ "disabled" => {"N" => "1"},
47
+ "group_id" => {"N" => "1"},
48
+ "person_id" => {"N" => "1501"}
49
+ }
50
+ ],
51
+ "Count" => 1,
52
+ "ConsumedCapacityUnits" => 0.5
53
+ }
54
+ http_response = stub(Net::HTTPResponse, body: MultiJson.dump(body))
55
+
56
+ response = DynamoDB::SuccessResponse.new(http_response)
57
+ response.items[0]["name"].should == "John Smith"
58
+ response.items[0]["created_at"].should == 1321564309.99428
59
+ response.items[0]["disabled"].should == 0
60
+ response.items[0]["group_id"].should == 1
61
+ response.items[0]["person_id"].should == 1500
62
+
63
+ response.items[1]["name"].should == "Jane Smith"
64
+ response.items[1]["created_at"].should == 1321564309.99428
65
+ response.items[1]["disabled"].should == 1
66
+ response.items[1]["group_id"].should == 1
67
+ response.items[1]["person_id"].should == 1501
68
+ end
69
+ end
70
+
71
+ describe "#item" do
72
+ it "returns the type-casted 'Item' key" do
73
+ body = {
74
+ "Item"=> {
75
+ "name" => {"S" => "John Smith"},
76
+ "created_at" => {"N" => "1321564309.99428"},
77
+ "disabled" => {"N" => "0"},
78
+ "group_id" => {"N" => "1"},
79
+ "person_id" => {"N" => "1500"}
80
+ },
81
+ "ConsumedCapacityUnits" => 0.5
82
+ }
83
+ http_response = stub(Net::HTTPResponse, body: MultiJson.dump(body))
84
+
85
+ response = DynamoDB::SuccessResponse.new(http_response)
86
+ response.item["name"].should == "John Smith"
87
+ response.item["created_at"].should == 1321564309.99428
88
+ response.item["disabled"].should == 0
89
+ response.item["group_id"].should == 1
90
+ response.item["person_id"].should == 1500
91
+ end
92
+ end
93
+ end