forked from goamz/goamz
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge together 'mitchellh' and 'crowdmob' forks
Conflicts: README.md aws/aws.go aws/aws_test.go ec2/ec2.go ec2/ec2_test.go ec2/ec2i_test.go ec2/ec2t_test.go ec2/ec2test/server.go ec2/responses_test.go ec2/sign.go ec2/sign_test.go exp/mturk/mturk.go exp/mturk/mturk_test.go exp/mturk/responses_test.go exp/mturk/sign_test.go exp/sdb/sdb_test.go exp/sdb/sign.go exp/sdb/sign_test.go exp/sns/responses_test.go exp/sns/sign.go exp/sns/sns.go exp/sns/sns_test.go iam/iam.go iam/iam_test.go iam/responses_test.go iam/sign.go s3/s3.go s3/s3_test.go s3/s3i_test.go s3/s3test/server.go s3/sign.go s3/sign_test.go testutil/http.go
- Loading branch information
Showing
21 changed files
with
1,797 additions
and
440 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Changes in this fork | ||
|
||
* Added EC2.CreateImage() | ||
* Added EC2.CreateKeyPair() | ||
* Added EC2.DeleteKeyPair() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
package aws | ||
|
||
import ( | ||
"math" | ||
"net" | ||
"net/http" | ||
"time" | ||
) | ||
|
||
type RetryableFunc func(*http.Request, *http.Response, error) bool | ||
type WaitFunc func(try int) | ||
type DeadlineFunc func() time.Time | ||
|
||
type ResilientTransport struct { | ||
// Timeout is the maximum amount of time a dial will wait for | ||
// a connect to complete. | ||
// | ||
// The default is no timeout. | ||
// | ||
// With or without a timeout, the operating system may impose | ||
// its own earlier timeout. For instance, TCP timeouts are | ||
// often around 3 minutes. | ||
DialTimeout time.Duration | ||
|
||
// MaxTries, if non-zero, specifies the number of times we will retry on | ||
// failure. Retries are only attempted for temporary network errors or known | ||
// safe failures. | ||
MaxTries int | ||
Deadline DeadlineFunc | ||
ShouldRetry RetryableFunc | ||
Wait WaitFunc | ||
transport *http.Transport | ||
} | ||
|
||
// Convenience method for creating an http client | ||
func NewClient(rt *ResilientTransport) *http.Client { | ||
rt.transport = &http.Transport{ | ||
Dial: func(netw, addr string) (net.Conn, error) { | ||
c, err := net.DialTimeout(netw, addr, rt.DialTimeout) | ||
if err != nil { | ||
return nil, err | ||
} | ||
c.SetDeadline(rt.Deadline()) | ||
return c, nil | ||
}, | ||
Proxy: http.ProxyFromEnvironment, | ||
} | ||
// TODO: Would be nice is ResilientTransport allowed clients to initialize | ||
// with http.Transport attributes. | ||
return &http.Client{ | ||
Transport: rt, | ||
} | ||
} | ||
|
||
var retryingTransport = &ResilientTransport{ | ||
Deadline: func() time.Time { | ||
return time.Now().Add(5 * time.Second) | ||
}, | ||
DialTimeout: 10 * time.Second, | ||
MaxTries: 3, | ||
ShouldRetry: awsRetry, | ||
Wait: ExpBackoff, | ||
} | ||
|
||
// Exported default client | ||
var RetryingClient = NewClient(retryingTransport) | ||
|
||
func (t *ResilientTransport) RoundTrip(req *http.Request) (*http.Response, error) { | ||
return t.tries(req) | ||
} | ||
|
||
// Retry a request a maximum of t.MaxTries times. | ||
// We'll only retry if the proper criteria are met. | ||
// If a wait function is specified, wait that amount of time | ||
// In between requests. | ||
func (t *ResilientTransport) tries(req *http.Request) (res *http.Response, err error) { | ||
for try := 0; try < t.MaxTries; try += 1 { | ||
res, err = t.transport.RoundTrip(req) | ||
|
||
if !t.ShouldRetry(req, res, err) { | ||
break | ||
} | ||
if res != nil { | ||
res.Body.Close() | ||
} | ||
if t.Wait != nil { | ||
t.Wait(try) | ||
} | ||
} | ||
|
||
return | ||
} | ||
|
||
func ExpBackoff(try int) { | ||
time.Sleep(100 * time.Millisecond * | ||
time.Duration(math.Exp2(float64(try)))) | ||
} | ||
|
||
func LinearBackoff(try int) { | ||
time.Sleep(time.Duration(try*100) * time.Millisecond) | ||
} | ||
|
||
// Decide if we should retry a request. | ||
// In general, the criteria for retrying a request is described here | ||
// http://docs.aws.amazon.com/general/latest/gr/api-retries.html | ||
func awsRetry(req *http.Request, res *http.Response, err error) bool { | ||
retry := false | ||
|
||
// Retry if there's a temporary network error. | ||
if neterr, ok := err.(net.Error); ok { | ||
if neterr.Temporary() { | ||
retry = true | ||
} | ||
} | ||
|
||
// Retry if we get a 5xx series error. | ||
if res != nil { | ||
if res.StatusCode >= 500 && res.StatusCode < 600 { | ||
retry = true | ||
} | ||
} | ||
|
||
return retry | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
package aws_test | ||
|
||
import ( | ||
"fmt" | ||
"github.com/goamz/goamz/aws" | ||
"io/ioutil" | ||
"net/http" | ||
"net/http/httptest" | ||
"strings" | ||
"testing" | ||
"time" | ||
) | ||
|
||
// Retrieve the response from handler using aws.RetryingClient | ||
func serveAndGet(handler http.HandlerFunc) (body string, err error) { | ||
ts := httptest.NewServer(handler) | ||
defer ts.Close() | ||
resp, err := aws.RetryingClient.Get(ts.URL) | ||
if err != nil { | ||
return | ||
} | ||
if resp.StatusCode != 200 { | ||
return "", fmt.Errorf("Bad status code: %d", resp.StatusCode) | ||
} | ||
greeting, err := ioutil.ReadAll(resp.Body) | ||
resp.Body.Close() | ||
if err != nil { | ||
return | ||
} | ||
return strings.TrimSpace(string(greeting)), nil | ||
} | ||
|
||
func TestClient_expected(t *testing.T) { | ||
body := "foo bar" | ||
|
||
resp, err := serveAndGet(func(w http.ResponseWriter, r *http.Request) { | ||
fmt.Fprintln(w, body) | ||
}) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if resp != body { | ||
t.Fatal("Body not as expected.") | ||
} | ||
} | ||
|
||
func TestClient_delay(t *testing.T) { | ||
body := "baz" | ||
wait := 4 | ||
resp, err := serveAndGet(func(w http.ResponseWriter, r *http.Request) { | ||
if wait < 0 { | ||
// If we dipped to zero delay and still failed. | ||
t.Fatal("Never succeeded.") | ||
} | ||
wait -= 1 | ||
time.Sleep(time.Second * time.Duration(wait)) | ||
fmt.Fprintln(w, body) | ||
}) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if resp != body { | ||
t.Fatal("Body not as expected.", resp) | ||
} | ||
} | ||
|
||
func TestClient_no4xxRetry(t *testing.T) { | ||
tries := 0 | ||
|
||
// Fail once before succeeding. | ||
_, err := serveAndGet(func(w http.ResponseWriter, r *http.Request) { | ||
tries += 1 | ||
http.Error(w, "error", 404) | ||
}) | ||
|
||
if err == nil { | ||
t.Fatal("should have error") | ||
} | ||
|
||
if tries != 1 { | ||
t.Fatalf("should only try once: %d", tries) | ||
} | ||
} | ||
|
||
func TestClient_retries(t *testing.T) { | ||
body := "biz" | ||
failed := false | ||
// Fail once before succeeding. | ||
resp, err := serveAndGet(func(w http.ResponseWriter, r *http.Request) { | ||
if !failed { | ||
http.Error(w, "error", 500) | ||
failed = true | ||
} else { | ||
fmt.Fprintln(w, body) | ||
} | ||
}) | ||
if failed != true { | ||
t.Error("We didn't retry!") | ||
} | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if resp != body { | ||
t.Fatal("Body not as expected.") | ||
} | ||
} | ||
|
||
func TestClient_fails(t *testing.T) { | ||
tries := 0 | ||
// Fail 3 times and return the last error. | ||
_, err := serveAndGet(func(w http.ResponseWriter, r *http.Request) { | ||
tries += 1 | ||
http.Error(w, "error", 500) | ||
}) | ||
if err == nil { | ||
t.Fatal(err) | ||
} | ||
if tries != 3 { | ||
t.Fatal("Didn't retry enough") | ||
} | ||
} |
Oops, something went wrong.