Skip to content

Commit

Permalink
Merge together 'mitchellh' and 'crowdmob' forks
Browse files Browse the repository at this point in the history
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
mattheath committed Mar 31, 2014
2 parents fb4072e + 62e8fff commit 9232af3
Show file tree
Hide file tree
Showing 21 changed files with 1,797 additions and 440 deletions.
5 changes: 5 additions & 0 deletions CHANGES.md
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()
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# GoAMZ
# goamz - An Amazon Library for Go

[![Build Status](https://travis-ci.org/goamz/goamz.png?branch=master)](https://travis-ci.org/goamz/goamz)

Expand All @@ -11,12 +11,16 @@ The API of AWS is very comprehensive, though, and goamz doesn't even scratch the
The following packages are available at the moment:

```
github.com/goamz/goamz/autoscaling
github.com/goamz/goamz/aws
github.com/goamz/goamz/cloudfront
github.com/goamz/goamz/cloudwatch
github.com/goamz/goamz/dynamodb
github.com/goamz/goamz/ec2
github.com/goamz/goamz/elb
github.com/goamz/goamz/iam
github.com/goamz/goamz/rds
github.com/goamz/goamz/route53
github.com/goamz/goamz/s3
github.com/goamz/goamz/sqs
Expand Down
20 changes: 4 additions & 16 deletions aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
Expand Down Expand Up @@ -230,24 +229,13 @@ type credentials struct {
Expiration string
}

// GetMetaData retrieves instance metadata about the current machine.
//
// See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AESDG-chapter-instancedata.html for more details.
func GetMetaData(path string) (contents []byte, err error) {
c := http.Client{
Transport: &http.Transport{
Dial: func(netw, addr string) (net.Conn, error) {
deadline := time.Now().Add(5 * time.Second)
c, err := net.DialTimeout(netw, addr, time.Second*2)
if err != nil {
return nil, err
}
c.SetDeadline(deadline)
return c, nil
},
},
}

url := "http://169.254.169.254/latest/meta-data/" + path

resp, err := c.Get(url)
resp, err := RetryingClient.Get(url)
if err != nil {
return
}
Expand Down
124 changes: 124 additions & 0 deletions aws/client.go
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
}
121 changes: 121 additions & 0 deletions aws/client_test.go
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")
}
}
Loading

0 comments on commit 9232af3

Please sign in to comment.